From ebff6c7a2f775e64f9f51aed4bfbd794b05fdbf2 Mon Sep 17 00:00:00 2001 From: "Matthew R. Wilson" Date: Mon, 2 Nov 2020 17:35:43 -0800 Subject: [PATCH] Thing seem to be working! --- example/example.go | 88 ++++++++++++++++++++++++++++++++++++++++------ response.go | 43 ++++++++++++++++------ screen.go | 34 +++++++----------- util.go | 4 +++ 4 files changed, 126 insertions(+), 43 deletions(-) diff --git a/example/example.go b/example/example.go index 7575268..b375871 100644 --- a/example/example.go +++ b/example/example.go @@ -13,16 +13,41 @@ import ( ) func init() { + // put the go3270 library in debug mode go3270.Debug = os.Stderr } -// A Screen is an array of go3270.Field structs: -var loginScreen = go3270.Screen{ - {Row: 0, Col: 30, Intense: true, Content: "3270 Example Screen"}, - {Row: 1, Col: 0, Content: "First Name . . ."}, - {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}, +// A Screen is an array of go3270.Field structs. We will build two screens, +// a query screen and a result screen: + +var screen1 = go3270.Screen{ + {Row: 0, Col: 27, Intense: true, Content: "3270 Example Application"}, + {Row: 2, Col: 0, Content: "Welcome to the go3270 example application. Please enter your name."}, + {Row: 4, Col: 0, Content: "First Name . . ."}, + {Row: 4, Col: 19, Name: "fname", Write: true}, + {Row: 4, Col: 40}, // field "stop" character + {Row: 5, Col: 0, Content: "Last Name . . . ."}, + {Row: 5, Col: 19, Name: "lname", Write: true}, + {Row: 5, Col: 40}, // field "stop" character, + {Row: 7, Col: 0, Content: "Press"}, + {Row: 7, Col: 6, Intense: true, Content: "enter"}, + {Row: 7, Col: 12, Content: "to submit your name."}, + {Row: 22, Col: 0, Content: "PF3 Exit"}, +} + +var screen2 = go3270.Screen{ + {Row: 0, Col: 27, Intense: true, Content: "3270 Example Application"}, + {Row: 2, Col: 0, Content: "Thank you for submitting your name. Here's what I know:"}, + {Row: 4, Col: 0, Content: "Your first name is"}, + {Row: 4, Col: 19, Name: "fname"}, // We're giving this field a name to replace its value at runtime + {Row: 5, Col: 0, Content: "And your last name is"}, + {Row: 5, Col: 22, Name: "lname"}, // We're giving this field a name to replace its value at runtime + {Row: 7, Col: 0, Content: "Press"}, + {Row: 7, Col: 6, Intense: true, Content: "enter"}, + {Row: 7, Col: 12, Content: "to enter your name again, or"}, + {Row: 7, Col: 41, Intense: true, Content: "PF3"}, + {Row: 7, Col: 45, Content: "to quit and disconnect."}, + {Row: 22, Col: 0, Content: "PF3 Exit"}, } func main() { @@ -30,6 +55,8 @@ func main() { if err != nil { panic(err) } + fmt.Println("LISTENING ON PORT 3270 FOR CONNECTIONS") + fmt.Println("Press Ctrl-C to end server.") for { conn, err := ln.Accept() if err != nil { @@ -39,15 +66,54 @@ func main() { } } +// handle is the handler for individual user connections. func handle(conn net.Conn) { defer conn.Close() + // Always begin new connection by negotiating the telnet options go3270.NegotiateTelnet(conn) - _, err := go3270.ShowScreen(loginScreen, nil, 1, 19, conn) - if err != nil { - panic(err) + + // We will loop forever until the user quits with PF3 + var fieldValues map[string]string + for { + // Show the first screen, and wait to get a client response. Place + // the cursor at the beginning of the first input field. + // We're passing in the fieldValues map to carry values over from + // the previous submission. We could pass nil, instead, if always want + // the fields to start out blank. + response, err := go3270.ShowScreen(screen1, fieldValues, 4, 20, conn) + if err != nil { + fmt.Println(err) + return + } + + // If the user pressed PF3, exit + if response.AID == go3270.AIDPF3 { + break + } + + // If anything OTHER than "Enter", restart the loop + if response.AID != go3270.AIDEnter { + continue + } + + // User must have pressed "Enter", so let's display the second screen, + // echoing their responses back to them. + fieldValues = response.Values + response, err = go3270.ShowScreen(screen2, fieldValues, 0, 0, conn) + if err != nil { + fmt.Println(err) + return + } + + // If the user pressed PF3, exit + if response.AID == go3270.AIDPF3 { + break + } + + // If they pressed anything else, just let the loop continue... + continue } fmt.Println("Connection closed") - } diff --git a/response.go b/response.go index e78b0d4..a7d501e 100644 --- a/response.go +++ b/response.go @@ -61,7 +61,7 @@ const ( AIDClear AID = 0x6D ) -func readResponse(c net.Conn) (Response, error) { +func readResponse(c net.Conn, fm fieldmap) (Response, error) { var r Response aid, err := readAID(c) if err != nil { @@ -69,6 +69,13 @@ func readResponse(c net.Conn) (Response, error) { } r.AID = aid + // If the use pressed clear, we should return now + // TODO: actually, we should consume the 0xffef, but that will + // currently get taken care of in our next AID search. + if r.AID == AIDClear { + return r, nil + } + row, col, _, err := readPosition(c) if err != nil { return r, err @@ -76,10 +83,13 @@ func readResponse(c net.Conn) (Response, error) { r.Col = col r.Row = row - if err = readFields(c); err != nil { + var fieldValues map[string]string + if fieldValues, err = readFields(c, fm); err != nil { return r, err } + r.Values = fieldValues + return r, nil } @@ -126,41 +136,42 @@ func readPosition(c net.Conn) (row, col, addr int, err error) { return row, col, addr, nil } -func readFields(c net.Conn) error { +func readFields(c net.Conn, fm fieldmap) (map[string]string, error) { buf := make([]byte, 1) var infield bool var fieldpos int var fieldval bytes.Buffer + var values = make(map[string]string) var err error // consume bytes until we get 0xffef for { // Read a byte if _, err = c.Read(buf); err != nil { - return err + return nil, 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())) + handleField(fieldpos, fieldval.Bytes(), fm, values) } // consume the next byte, which is probably 0xef if _, err = c.Read(buf); err != nil { - return err + return nil, err } - return nil + return values, 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())) + handleField(fieldpos, fieldval.Bytes(), fm, values) } // Start a new field infield = true @@ -168,7 +179,7 @@ func readFields(c net.Conn) error { fieldpos = 0 if _, _, fieldpos, err = readPosition(c); err != nil { - return err + return nil, err } continue } @@ -179,10 +190,22 @@ func readFields(c net.Conn) error { continue } fieldval.WriteByte(buf[0]) - } } +func handleField(addr int, value []byte, fm fieldmap, values map[string]string) bool { + name, ok := fm[addr] + + // Field is not present in the fieldmap + if !ok { + return false + } + + // Otherwise, populate the value + values[name] = string(e2a(value)) + return true +} + // 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 { diff --git a/screen.go b/screen.go index 36fce45..c11fdd6 100644 --- a/screen.go +++ b/screen.go @@ -6,7 +6,6 @@ package go3270 import ( "bytes" - "fmt" "net" ) @@ -41,6 +40,10 @@ type Field struct { // names, type Screen []Field +// fieldmap is a map of field buffer addresses and the corresponding field +// name. +type fieldmap map[int]string + // ShowScreen writes the 3270 datastream for the screen to a connection. // Fields that aren't valid (e.g. outside of the 24x80 screen) are silently // ignored. If a named field has an entry in the values map, the content of @@ -53,7 +56,7 @@ func ShowScreen(screen Screen, values map[string]string, crow, ccol int, conn net.Conn) (Response, error) { var b bytes.Buffer - var fieldmap = make(map[int]string) // field buffer positions -> name + var fm = make(fieldmap) // field buffer positions -> name b.WriteByte(0xf5) // Erase/Write to terminal b.WriteByte(0xc3) // WCC = Reset, Unlock Keyboard, Reset MDT @@ -80,10 +83,13 @@ func ShowScreen(screen Screen, values map[string]string, crow, ccol int, b.Write(a2e([]byte(content))) } - // If a writable field, add it to the field map + // If a writable field, add it to the field map. We add 1 to bufaddr + // to make the value match the reported position (I'm guessing it's + // because we get the position of the field's first input position, + // not the position of the field attribute byte). if fld.Write { bufaddr := fld.Row*80 + fld.Col - fieldmap[bufaddr] = fld.Name + fm[bufaddr+1] = fld.Name } } @@ -99,28 +105,12 @@ func ShowScreen(screen Screen, values map[string]string, crow, ccol int, b.Write([]byte{0xff, 0xef}) // Telnet IAC EOR // Now write the datastream to the writer, returning any potential error. - if Debug != nil { - fmt.Fprintf(Debug, "%x\n", b.Bytes()) - } + debugf("sending datastream: %x\n", b.Bytes()) 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) + return readResponse(conn, fm) } // sba is the "set buffer address" 3270 command. diff --git a/util.go b/util.go index aa92c96..c4713ca 100644 --- a/util.go +++ b/util.go @@ -9,13 +9,17 @@ import ( "io" ) +// Enable go3270 library debugging by setting Debug to an io.Writer. +// Disable debugging by setting it to nil (the default value). var Debug io.Writer +// debugf will print to the Debug io.Writer if it isn't nil. func debugf(format string, a ...interface{}) { if Debug == nil { return } + fmt.Fprintf(Debug, "dbg: ") fmt.Fprintf(Debug, format, a...) }