clean up output; add ReleaseVersion tests

This commit is contained in:
Justin Bastress 2018-02-28 13:16:52 -05:00
parent f61e698ea3
commit 6fbbc0a182
4 changed files with 141 additions and 35 deletions

@ -1,7 +1,6 @@
package oracle
import (
"fmt"
"net"
"strconv"
@ -18,9 +17,13 @@ type HandshakeLog struct {
// server returns in the Accept packet.
GlobalServiceOptions map[string]bool `json:"global_service_options,omitempty"`
// ConnectFlags is the ste of ConnectFlags values that the server returns
// in the Accept packet. Both are included in the array.
ConnectFlags [2]map[string]bool `json:"connect_flags,omitempty"`
// ConnectFlags0 is the first set of ConnectFlags values that the server
// returns in the Accept packet for the first.
ConnectFlags0 map[string]bool `json:"connect_flags0,omitempty"`
// ConnectFlags1 is the second set of ConnectFlags values that the server
// returns in the Accept packet for the first.
ConnectFlags1 map[string]bool `json:"connect_flags1,omitempty"`
// DidResend is true if the server sent a Resend packet in response to the
// client's first Connect packet.
@ -28,15 +31,28 @@ type HandshakeLog struct {
// RedirectTarget is the connect descriptor returned by the server in the
// Redirect packet, if one is sent. Otherwise it is empty/omitted.
RedirectTarget string `json:"redirect_target,omitempty"`
RedirectTargetRaw string `json:"redirect_target_raw,omitempty"`
// RefuseError is the Data from the Refuse packet returned by the server;
RedirectTarget Descriptor `json:"redirect_target,omitempty"`
// RefuseErrorRaW is the Data from the Refuse packet returned by the server;
// it is empty if the server does not return a Refuse packet.
RefuseError string `json:"refuse_error,omitempty"`
RefuseErrorRaw string `json:"refuse_error_raw,omitempty"`
// RefuseReason describes the reason the request was refused as given in the
// Refuse packet from the server. TODO: finalize format.
RefuseReason string `json:"refuse_reason,omitempty"`
RefuseError Descriptor `json:"refuse_error,omitempty"`
// RefuseReasonApp is the "AppReason" returned by the server in a Refused
// response packet.
RefuseReasonApp string `json:"refuse_reason_app,omitempty"`
// RefuseReasonSys is the "SysReason" returned by the server in a Refused
// response packet.
RefuseReasonSys string `json:"refuse_reason_sys,omitempty"`
// RefuseVersion is the parsed DESCRIPTION.VSNNUM field from the RefuseError
// string returned by the server in the Refuse packet, in dotted-decimal
// format.
RefuseVersion string `json:"refuse_version,omitempty"`
// DidResend is set to true if the server sent a Resend packet after the
// first Connect packet.
@ -84,7 +100,10 @@ func (conn *Connection) readPacket() (*TNSPacket, error) {
// Automatically handles Resend responses; the caller is responsible for
// handling other exceptional cases.
func (conn *Connection) SendPacket(packet TNSPacketBody) (TNSPacketBody, error) {
toSend := conn.tnsDriver.EncodePacket(&TNSPacket{Body: packet})
toSend, err := conn.tnsDriver.EncodePacket(&TNSPacket{Body: packet})
if err != nil {
return nil, err
}
if err := conn.send(toSend); err != nil {
return nil, err
@ -162,13 +181,26 @@ func (conn *Connection) Connect(connectDescriptor string) (*HandshakeLog, error)
case *TNSAccept:
accept = resp
case *TNSRedirect:
result.RedirectTarget = string(resp.Data)
result.RedirectTargetRaw = string(resp.Data)
if parsed, err := DecodeDescriptor(result.RedirectTargetRaw); err != nil {
result.RedirectTarget = parsed
}
// TODO: Follow redirects?
return &result, nil
case *TNSRefuse:
result.RefuseError = string(resp.Data)
// TODO: do better
result.RefuseReason = fmt.Sprintf("AppReason: 0x%02x; SysReason: 0x%02x", resp.AppReason, resp.SysReason)
result.RefuseErrorRaw = string(resp.Data)
result.RefuseReasonApp = resp.AppReason.String()
result.RefuseReasonSys = resp.SysReason.String()
if desc, err := DecodeDescriptor(result.RefuseErrorRaw); err == nil {
result.RefuseError = desc
if versions := desc.GetValues("DESCRIPTION.VSNNUM"); len(versions) > 0 {
// If there are multiple VSNNUMs, we only care about the first.
decVersion := versions[0]
if intVersion, err := strconv.ParseUint(decVersion, 10, 32); err == nil {
result.RefuseVersion = ReleaseVersion(intVersion).String()
}
}
}
return &result, nil
default:
return &result, ErrUnexpectedResponse
@ -177,12 +209,13 @@ func (conn *Connection) Connect(connectDescriptor string) (*HandshakeLog, error)
// TODO: Unclear what these do. Taken from my client.
result.AcceptVersion = accept.Version
result.GlobalServiceOptions = accept.GlobalServiceOptions.Set()
result.ConnectFlags = [2]map[string]bool{
accept.ConnectFlags0.Set(),
accept.ConnectFlags1.Set(),
}
result.ConnectFlags0 = accept.ConnectFlags0.Set()
result.ConnectFlags1 = accept.ConnectFlags1.Set()
// uint32 PID + uint32 ??
supervisorBytes0 := []byte{0x00, 0x00, 0x04, 0xec, 0x19, 0x2c, 0x7b, 0x4c}
// In real clients, seems to be a small u32 followed by some kind of u32
// counter/timestamp.
supervisorBytes0 := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
supervisorBytes1 := []byte{
0xde, 0xad, 0xbe, 0xef,

@ -153,7 +153,7 @@ type TNSDriver struct {
// EncodePacket encodes the packet (header + body). If header is nil, create one
// with no flags and the type set to the body's type. If header.Length == 0, set
// it to the appropriate value (length of encoded body + 8).
func (driver *TNSDriver) EncodePacket(packet *TNSPacket) []byte {
func (driver *TNSDriver) EncodePacket(packet *TNSPacket) ([]byte, error) {
body := packet.Body.Encode()
if packet.Header == nil {
packet.Header = &TNSHeader{
@ -170,7 +170,7 @@ func (driver *TNSDriver) EncodePacket(packet *TNSPacket) []byte {
// It is up to the user to check the body length for overflows before calling Encode
if driver.Mode == TNSModeOld {
if (len(body) + 8) > 0xffff {
panic(fmt.Errorf("Body too large to fit into 16-bit length (%d bytes)", len(body)))
return nil, ErrInvalidInput
}
packet.Header.Length = uint32(len(body) + 8)
} else {
@ -178,7 +178,7 @@ func (driver *TNSDriver) EncodePacket(packet *TNSPacket) []byte {
}
}
header := packet.Header.Encode()
return append(header, body...)
return append(header, body...), nil
}
// TNSFlags is the type for the TNS header's flags.
@ -831,6 +831,11 @@ func ReadTNSAccept(reader io.Reader, header *TNSHeader) (*TNSAccept, error) {
// TODO: details.
type RefuseReason uint8
func (reason RefuseReason) String() string {
// TODO: Get better const error mappings. AppReason = 0x22 = syntax error?
return fmt.Sprintf("0x%02x", uint8(reason))
}
// TNSRefuse is returned by the server when (...TODO: details -- not returned on
// failed auth from TNSConnect).
type TNSRefuse struct {

@ -2,6 +2,7 @@ package oracle
import (
"bytes"
"encoding/binary"
"encoding/hex"
"encoding/json"
"strconv"
@ -482,7 +483,10 @@ func TestTNSConnect(t *testing.T) {
driver := getTNSDriver()
for tag, info := range validTNSConnect {
bin := fromHex(info.Encoding)
encoded := driver.EncodePacket(info.Value)
encoded, err := driver.EncodePacket(info.Value)
if err != nil {
t.Fatalf("%s: TNSConnect Error encoding packet: %v", tag, err)
}
if !bytes.Equal(bin, encoded) {
t.Errorf("%s: TNSConnect.Encode mismatch:[\n%s\n]", tag, interleave(bin, encoded))
}
@ -511,7 +515,10 @@ func TestTNSAccept(t *testing.T) {
driver := getTNSDriver()
for tag, info := range validTNSAccept {
bin := fromHex(info.Encoding)
encoded := driver.EncodePacket(info.Value)
encoded, err := driver.EncodePacket(info.Value)
if err != nil {
t.Fatalf("%s: TNSAccept Error encoding packet: %v", tag, err)
}
if !bytes.Equal(bin, encoded) {
t.Errorf("%s: TNSAccept.Encode mismatch:[\n%s\n]", tag, interleave(bin, encoded))
}
@ -540,7 +547,10 @@ func TestTNSData(t *testing.T) {
driver := getTNSDriver()
for tag, info := range validTNSData {
bin := fromHex(info.Encoding)
encoded := driver.EncodePacket(info.Value)
encoded, err := driver.EncodePacket(info.Value)
if err != nil {
t.Fatalf("%s: TNSData Error encoding packet: %v", tag, err)
}
if !bytes.Equal(bin, encoded) {
t.Errorf("%s: TNSData.Encode mismatch:[\n%s\n]", tag, interleave(bin, encoded))
}
@ -753,3 +763,52 @@ func TestDecodeDescriptor(t *testing.T) {
}
}
}
var releaseVersions = map[string]ReleaseVersion{
"1.2.3.4.5": ReleaseVersion(0x01230405),
"0.0.0.0.0": ReleaseVersion(0),
"255.15.15.255.255": ReleaseVersion(0xFFFFFFFF),
}
var badReleaseVersions = []string{
"",
"1",
"1.2",
"1.2.3",
"1.2.3.4",
"256.0.0.0.0",
"0.16.0.0.0",
"0.0.16.0.0",
"0.0.0.256.0",
"0.0.0.0.256",
"a.b.c.d.e",
"A.B.C.D.E",
"p.q.r.s.t",
}
func TestReleaseVersion(t *testing.T) {
expectedBytes := make([]byte, 4)
for stringValue, version := range releaseVersions {
actualString := version.String()
if stringValue != actualString {
t.Errorf("ReleaseVersion.String() failed: 0x%08x gave %s, expected %s", uint32(version), actualString, stringValue)
}
binary.BigEndian.PutUint32(expectedBytes, uint32(version))
actualBytes := version.Bytes()
if !bytes.Equal(expectedBytes, actualBytes) {
t.Errorf("ReleaseVersion.Bytes() failed: 0x%08x gave %v, expected %v", uint32(version), actualBytes, expectedBytes)
}
encoded, err := EncodeReleaseVersion(stringValue)
if err != nil {
t.Fatalf("EncodeReleaseVersion(%s) failed: %v", stringValue, err)
}
if encoded != version {
t.Errorf("EncodeReleaseVersion(%s) failed: got 0x%08x, expected 0x%08x", stringValue, uint32(encoded), uint32(version))
}
}
for _, bad := range badReleaseVersions {
if ret, err := EncodeReleaseVersion(bad); err == nil {
t.Errorf("Successfully encoded bad ReleaseVersion %s: 0x%08x", bad, uint32(ret))
}
}
}

@ -40,23 +40,32 @@ connect_flags = [
]
nsn_services = [
"Authentication",
"Encryption",
"DataIntegrity",
"Supervisor",
"Authentication",
"Encryption",
"DataIntegrity",
"Supervisor",
]
parsed_descriptor = ListOf(SubRecord({
"key": String(),
"value": String(),
}))
oracle_scan_response = SubRecord({
"result": SubRecord({
"handshake": SubRecord({
"accept_version": Unsigned16BitInteger(),
"global_service_options": flagsSet(global_service_options),
"connect_flags": List(flagsSet(connect_flags)),
"connect_flags0": flagsSet(connect_flags),
"connect_flags1": flagsSet(connect_flags),
"did_resend": Boolean(),
"redirect_target": String(),
"refuse_error": String(),
# TODO: Finalize format of refuse_reason
"refuse_reason": String(),
"redirect_target_raw": String(),
"redirect_target": parsed_descriptor,
"refuse_error_raw": String(),
"refuse_error": parsed_descriptor,
"refuse_version": String(),
"refuse_reason_app": String(),
"refuse_reason_sys": String(),
"nsn_version": String(),
"nsn_service_versions": SubRecord({
"Authentication": String(),