Early response parsing work

This commit is contained in:
Matthew R. Wilson committed 2020-11-02 16:12:45 -08:00
1 parent 0243fc3e13
commit adb42dea54
5 files changed
+354 -36

No files matched your search

+33
View File
@@ -0,0 +1,33 @@
// This file is part of https://github.com/racingmars/go3270/
// Copyright 2020 by Matthew R. Wilson, licensed under the MIT license. See
// LICENSE in the project root for license information.
package go3270
import (
"testing"
)
func TestEncode(t *testing.T) {
encoded := getpos(0, 0)
if encoded[0] != 0x40 || encoded[1] != 0x40 {
t.Error("Position (0, 0) not correctly encoded")
}
encoded = getpos(11, 39)
if encoded[0] != 0x4e || encoded[1] != 0xd7 {
t.Error("Position (11, 39) not correctly encoded")
}
}
func TestDecode(t *testing.T) {
decoded := decodeBufAddr([2]byte{0x40, 0x40})
if decoded != 0 {
t.Error("Buffer address incorrectly decoded")
}
decoded = decodeBufAddr([2]byte{0x4e, 0xd7})
if decoded != 919 {
t.Error("Buffer address incorrectly decoded")
}
}
+13 -15
View File
@@ -7,14 +7,22 @@ package main
import ( import (
"fmt" "fmt"
"net" "net"
"os"
"github.com/racingmars/go3270" "github.com/racingmars/go3270"
) )
func init() {
go3270.Debug = os.Stderr
}
// A Screen is an array of go3270.Field structs:
var loginScreen = go3270.Screen{ var loginScreen = go3270.Screen{
{Row: 0, Col: 0, Intense: true, Content: "Testing . . ."}, {Row: 0, Col: 30, Intense: true, Content: "3270 Example Screen"},
{Row: 1, Col: 0, Content: "Name . . ."}, {Row: 1, Col: 0, Content: "First Name . . ."},
{Row: 1, Col: 14, Name: "name", Write: true}, {Row: 1, Col: 18, Name: "fname", Write: true, Content: "Test"},
{Row: 2, Col: 0, Content: "Last Name . . . ."},
{Row: 2, Col: 18, Name: "lname", Write: true},
} }
func main() { func main() {
@@ -35,21 +43,11 @@ func handle(conn net.Conn) {
defer conn.Close() defer conn.Close()
go3270.NegotiateTelnet(conn) go3270.NegotiateTelnet(conn)
err := go3270.WriteScreen(loginScreen, 10, 15, conn) _, err := go3270.ShowScreen(loginScreen, nil, 1, 19, conn)
if err != nil { if err != nil {
panic(err) panic(err)
} }
for { fmt.Println("Connection closed")
rbuf := make([]byte, 255)
n, err := conn.Read(rbuf)
if err != nil {
break
}
for i := 0; i < n; i++ {
fmt.Printf("%x", rbuf[i])
}
fmt.Printf("\n")
}
} }
+192
View File
@@ -0,0 +1,192 @@
// This file is part of https://github.com/racingmars/go3270/
// Copyright 2020 by Matthew R. Wilson, licensed under the MIT license. See
// LICENSE in the project root for license information.
package go3270
import (
"bytes"
"net"
)
// Response encapsulates data received from a 3270 client in response to the
// previously sent screen.
type Response struct {
// Which Action ID key did the user press?
AID AID
// Row the cursor was on (0-based).
Row int
// Column the cursor was on (0-based).
Col int
// Field values.
Values map[string]string
}
// AID is an Action ID character.
type AID byte
const (
AIDNone AID = 0x60
AIDEnter AID = 0x7D
AIDPF1 AID = 0xF1
AIDPF2 AID = 0xF2
AIDPF3 AID = 0xF3
AIDPF4 AID = 0xF4
AIDPF5 AID = 0xF5
AIDPF6 AID = 0xF6
AIDPF7 AID = 0xF7
AIDPF8 AID = 0xF8
AIDPF9 AID = 0xF9
AIDPF10 AID = 0x7A
AIDPF11 AID = 0x7B
AIDPF12 AID = 0x7C
AIDPF13 AID = 0xC1
AIDPF14 AID = 0xC2
AIDPF15 AID = 0xC3
AIDPF16 AID = 0xC4
AIDPF17 AID = 0xC5
AIDPF18 AID = 0xC6
AIDPF19 AID = 0xC7
AIDPF20 AID = 0xC8
AIDPF21 AID = 0xC9
AIDPF22 AID = 0x4A
AIDPF23 AID = 0x4B
AIDPF24 AID = 0x4C
AIDPA1 AID = 0x6C
AIDPA2 AID = 0x6E
AIDPA3 AID = 0x6B
AIDClear AID = 0x6D
)
func readResponse(c net.Conn) (Response, error) {
var r Response
aid, err := readAID(c)
if err != nil {
return r, err
}
r.AID = aid
row, col, _, err := readPosition(c)
if err != nil {
return r, err
}
r.Col = col
r.Row = row
if err = readFields(c); err != nil {
return r, err
}
return r, nil
}
func readAID(c net.Conn) (AID, error) {
buf := make([]byte, 1)
for {
_, err := c.Read(buf)
if err != nil {
return AIDNone, err
}
b := buf[0]
if (b == 0x60) || (b >= 0x6b && b <= 0x6e) ||
(b >= 0x7a && b <= 0x7d) || (b >= 0x4a && b <= 0x4c) ||
(b >= 0xf1 && b <= 0xf9) || (b >= 0xc1 && b <= 0xc9) {
// We found a valid AID
debugf("Got AID byte: %x\n", b)
return AID(b), nil
}
// Consume non-AID bytes continuing loop
debugf("Got non-AID byte: %x\n", b)
}
}
func readPosition(c net.Conn) (row, col, addr int, err error) {
buf := make([]byte, 1)
raw := make([]byte, 2)
// Read two bytes
for i := 0; i < 2; i++ {
if _, err := c.Read(buf); err != nil {
return 0, 0, 0, err
}
raw[i] = buf[0]
}
// Decode the raw position
addr = decodeBufAddr([2]byte{raw[0], raw[1]})
row = addr % 80
col = (addr - row) / 80
debugf("Got position bytes %02x %02x, decoded to %d\n", raw[0], raw[1],
addr)
return row, col, addr, nil
}
func readFields(c net.Conn) error {
buf := make([]byte, 1)
var infield bool
var fieldpos int
var fieldval bytes.Buffer
var err error
// consume bytes until we get 0xffef
for {
// Read a byte
if _, err = c.Read(buf); err != nil {
return err
}
// Check for end of data stream (0xffef)
if buf[0] == 0xff {
// Finish the current field
if infield {
// TODO
debugf("Field %d: %s\n", fieldpos, e2a(fieldval.Bytes()))
}
// consume the next byte, which is probably 0xef
if _, err = c.Read(buf); err != nil {
return err
}
return nil
}
// No? Check for start-of-field
if buf[0] == 0x11 {
// Finish the previous field, if necessary
if infield {
// TODO
debugf("Field %d: %s\n", fieldpos, e2a(fieldval.Bytes()))
}
// Start a new field
infield = true
fieldval = bytes.Buffer{}
fieldpos = 0
if _, _, fieldpos, err = readPosition(c); err != nil {
return err
}
continue
}
// Consume all other bytes as field contents if we're in a field
if !infield {
debugf("Got unexpected byte while processing fields: %02x\n", buf[0])
continue
}
fieldval.WriteByte(buf[0])
}
}
// decodeBufAddr decodes a raw 2-byte encoded buffer address and returns the
// integer value of the address (i.e. 0-1919)
func decodeBufAddr(raw [2]byte) int {
hi := decodes[raw[0]] << 6
lo := decodes[raw[1]]
return hi | lo
}
+70 -21
View File
@@ -7,7 +7,7 @@ package go3270
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io" "net"
) )
// Field is a field on the 3270 screen. // Field is a field on the 3270 screen.
@@ -41,13 +41,19 @@ type Field struct {
// names, // names,
type Screen []Field type Screen []Field
// WriteScreen writes the 3270 datastream for the screen to a writer. Fields // ShowScreen writes the 3270 datastream for the screen to a connection.
// that aren't valid (e.g. outside of the 24x80 screen) are silently ignored. // Fields that aren't valid (e.g. outside of the 24x80 screen) are silently
// After writing the fields, the curser is set to crow, ccol, which are // ignored. If a named field has an entry in the values map, the content of
// 0-based positions: row 0-23 and col 0-79. Errors from io.Writer.Write() // the field from the values map is used INSTEAD OF the Field struct's Content
// are returned if encountered. // field. The values map may be nil if no overrides are needed. After writing
func WriteScreen(screen Screen, crow, ccol int, w io.Writer) error { // the fields, the cursor is set to crow, ccol, which are 0-based positions:
// row 0-23 and col 0-79. Errors from conn.Write() are returned if
// encountered.
func ShowScreen(screen Screen, values map[string]string, crow, ccol int,
conn net.Conn) (Response, error) {
var b bytes.Buffer var b bytes.Buffer
var fieldmap = make(map[int]string) // field buffer positions -> name
b.WriteByte(0xf5) // Erase/Write to terminal b.WriteByte(0xf5) // Erase/Write to terminal
b.WriteByte(0xc3) // WCC = Reset, Unlock Keyboard, Reset MDT b.WriteByte(0xc3) // WCC = Reset, Unlock Keyboard, Reset MDT
@@ -61,8 +67,23 @@ func WriteScreen(screen Screen, crow, ccol int, w io.Writer) error {
b.Write(sba(fld.Row, fld.Col)) b.Write(sba(fld.Row, fld.Col))
b.Write(sf(fld.Write, fld.Intense)) b.Write(sf(fld.Write, fld.Intense))
if fld.Content != "" {
b.Write(a2e([]byte(fld.Content))) // Use fld.Content, unless the field is named and appears in the
// value map.
content := fld.Content
if fld.Name != "" {
if val, ok := values[fld.Name]; ok {
content = val
}
}
if content != "" {
b.Write(a2e([]byte(content)))
}
// If a writable field, add it to the field map
if fld.Write {
bufaddr := fld.Row*80 + fld.Col
fieldmap[bufaddr] = fld.Name
} }
} }
@@ -73,14 +94,33 @@ func WriteScreen(screen Screen, crow, ccol int, w io.Writer) error {
if ccol < 0 || ccol > 79 { if ccol < 0 || ccol > 79 {
ccol = 0 ccol = 0
} }
//b.Write(ic(crow, ccol)) b.Write(ic(crow, ccol))
b.Write([]byte{0xff, 0xef}) // Telnet IAC EOR b.Write([]byte{0xff, 0xef}) // Telnet IAC EOR
// Now write the datastream to the writer, returning any potential error. // Now write the datastream to the writer, returning any potential error.
fmt.Printf("%x\n", b.Bytes()) if Debug != nil {
_, err := w.Write(b.Bytes()) fmt.Fprintf(Debug, "%x\n", b.Bytes())
return err }
if _, err := conn.Write(b.Bytes()); err != nil {
return Response{}, err
}
// Now wait for the response. We want to read bytes that start with an AID
// and end with 0xFFEF.
// for {
// rbuf := make([]byte, 1)
// n, err := conn.Read(rbuf)
// if err != nil {
// return Response{}, err
// }
// for i := 0; i < n; i++ {
// fmt.Printf("%x", rbuf[i])
// }
// fmt.Printf("\n")
// }
return readResponse(conn)
} }
// sba is the "set buffer address" 3270 command. // sba is the "set buffer address" 3270 command.
@@ -97,6 +137,11 @@ func sf(write, intense bool) []byte {
result[0] = 0x1d // SF result[0] = 0x1d // SF
if !write { if !write {
result[1] |= 1 << 5 // set "bit 2" result[1] |= 1 << 5 // set "bit 2"
} else {
// The MDT bit -- we always want writable field values returned,
// even if unchanged
result[1] |= 1 // set "bit 7"
} }
if intense { if intense {
result[1] |= 1 << 3 // set "bit 4" result[1] |= 1 << 3 // set "bit 4"
@@ -126,11 +171,15 @@ func getpos(row, col int) []byte {
return result return result
} }
// codes are the 3270 control character I/O codes, pre-computed as provided type readerState int
// at http://www.tommysprinkle.com/mvs/P3270/iocodes.htm
var codes = []byte{0x40, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, const (
0xc9, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0xd1, 0xd2, 0xd3, 0xd4, stateNone readerState = iota
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, stateGotAID
0x61, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0x6a, 0x6b, 0x6c, stateGotFirstAddr
0x6d, 0x6e, 0x6f, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, stateGotSecondAddr
0xf9, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f} stateInField
stateGotFirstFieldAddr
stateGotSecondFieldAddr
stateGot
)
+46
View File
@@ -0,0 +1,46 @@
// This file is part of https://github.com/racingmars/go3270/
// Copyright 2020 by Matthew R. Wilson, licensed under the MIT license. See
// LICENSE in the project root for license information.
package go3270
import (
"fmt"
"io"
)
var Debug io.Writer
func debugf(format string, a ...interface{}) {
if Debug == nil {
return
}
fmt.Fprintf(Debug, format, a...)
}
// codes are the 3270 control character I/O codes, pre-computed as provided
// at http://www.tommysprinkle.com/mvs/P3270/iocodes.htm
var codes = []byte{0x40, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
0xc9, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0xd1, 0xd2, 0xd3, 0xd4,
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0x6a, 0x6b, 0x6c,
0x6d, 0x6e, 0x6f, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f}
// decodes is the inverse of the above table; -1 is used in invalid positions
var decodes = []int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, -1, -1, -1, -1, -1, -1,
-1, -1, 42, 43, 44, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
58, 59, 60, 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 2,
3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 17, 18, 19, 20, 21, 22,
23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, 34, 35, 36, 37, 38, 39, 40,
41, -1, -1, -1, -1, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1,
-1, -1, -1, -1}