mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
1109 lines
30 KiB
Go
1109 lines
30 KiB
Go
//go:build plugin_oracle || !plugin_selective
|
|
|
|
package services
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oracleConnectData
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOracleConnectDataDoesNotExposeClientIdentity(t *testing.T) {
|
|
connectData := oracleConnectData("db.example", 1521, "ORCL")
|
|
|
|
for _, value := range []string{"CID=", "PROGRAM=", "USER=", "fscan"} {
|
|
if bytes.Contains([]byte(connectData), []byte(value)) {
|
|
t.Fatalf("oracle connect data contains client-identifying value %q: %s", value, connectData)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOracleConnectDataFormat(t *testing.T) {
|
|
cd := oracleConnectData("localhost", 1521, "XE")
|
|
if !strings.Contains(cd, "HOST=localhost") {
|
|
t.Errorf("missing host: %s", cd)
|
|
}
|
|
if !strings.Contains(cd, "PORT=1521") {
|
|
t.Errorf("missing port: %s", cd)
|
|
}
|
|
if !strings.Contains(cd, "SERVICE_NAME=XE") {
|
|
t.Errorf("missing service name: %s", cd)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// toInt64 / toUint64
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestToInt64(t *testing.T) {
|
|
cases := []struct {
|
|
in interface{}
|
|
want int64
|
|
}{
|
|
{int(42), 42},
|
|
{int16(-100), -100},
|
|
{int32(0x7fffffff), 0x7fffffff},
|
|
{int64(-1), -1},
|
|
{uint8(255), 255},
|
|
{uint16(1000), 1000},
|
|
{uint32(99999), 99999},
|
|
{uint64(1), 1},
|
|
{uint(7), 7},
|
|
}
|
|
for _, c := range cases {
|
|
if got := toInt64(c.in); got != c.want {
|
|
t.Errorf("toInt64(%v) = %d, want %d", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestToInt64Panic(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Error("expected panic for unsupported type")
|
|
}
|
|
}()
|
|
toInt64("string")
|
|
}
|
|
|
|
func TestToUint64(t *testing.T) {
|
|
cases := []struct {
|
|
in interface{}
|
|
want uint64
|
|
}{
|
|
{int(5), 5},
|
|
{int16(300), 300},
|
|
{int32(65535), 65535},
|
|
{int64(1 << 40), 1 << 40},
|
|
{uint8(0xff), 0xff},
|
|
{uint16(0xffff), 0xffff},
|
|
{uint32(0xffffffff), 0xffffffff},
|
|
{uint64(^uint64(0)), ^uint64(0)},
|
|
{uint(42), 42},
|
|
}
|
|
for _, c := range cases {
|
|
if got := toUint64(c.in); got != c.want {
|
|
t.Errorf("toUint64(%v) = %d, want %d", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestToUint64Panic(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Error("expected panic for unsupported type")
|
|
}
|
|
}()
|
|
toUint64(3.14)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oraclePKCS5Padding
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOraclePKCS5Padding(t *testing.T) {
|
|
// block size 16 — "hello" (5 bytes) → 11 bytes of padding (value 0x0b)
|
|
padded := oraclePKCS5Padding([]byte("hello"), 16)
|
|
if len(padded) != 16 {
|
|
t.Fatalf("expected length 16, got %d", len(padded))
|
|
}
|
|
for _, b := range padded[5:] {
|
|
if b != 11 {
|
|
t.Fatalf("expected padding byte 0x0b, got 0x%02x", b)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOraclePKCS5PaddingAligned(t *testing.T) {
|
|
// input length == block size → adds a full block of padding
|
|
padded := oraclePKCS5Padding([]byte("1234567890123456"), 16)
|
|
if len(padded) != 32 {
|
|
t.Fatalf("expected 32, got %d", len(padded))
|
|
}
|
|
for _, b := range padded[16:] {
|
|
if b != 16 {
|
|
t.Fatalf("bad padding byte: 0x%02x", b)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oracleExtractCode
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOracleExtractCode(t *testing.T) {
|
|
cases := []struct {
|
|
msg string
|
|
want int
|
|
}{
|
|
{"(ERR=12505)", 12505},
|
|
{"something CODE=1017 blah", 1017},
|
|
{"no code here", 0},
|
|
{"err=0042 trailing", 42},
|
|
{"CODE= 28000", 28000},
|
|
}
|
|
for _, c := range cases {
|
|
if got := oracleExtractCode(c.msg); got != c.want {
|
|
t.Errorf("oracleExtractCode(%q) = %d, want %d", c.msg, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oracleRefuseError
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOracleRefuseErrorShortPacket(t *testing.T) {
|
|
err := oracleRefuseError([]byte{0, 1, 2})
|
|
if err == nil || !strings.Contains(err.Error(), "refused") {
|
|
t.Errorf("expected 'refused' error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOracleRefuseErrorWithMessage(t *testing.T) {
|
|
msg := "(ERR=12505)"
|
|
raw := make([]byte, 12+len(msg))
|
|
binary.BigEndian.PutUint16(raw[10:12], uint16(len(msg)))
|
|
copy(raw[12:], msg)
|
|
err := oracleRefuseError(raw)
|
|
if err == nil {
|
|
t.Fatal("expected non-nil error")
|
|
}
|
|
if !strings.Contains(err.Error(), "12505") {
|
|
t.Errorf("error should mention code 12505: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOracleRefuseErrorNoCode(t *testing.T) {
|
|
msg := "connection not allowed"
|
|
raw := make([]byte, 12+len(msg))
|
|
binary.BigEndian.PutUint16(raw[10:12], uint16(len(msg)))
|
|
copy(raw[12:], msg)
|
|
err := oracleRefuseError(raw)
|
|
if err == nil || !strings.Contains(err.Error(), msg) {
|
|
t.Errorf("expected message in error: %v", err)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oracleGenerateSpeedyKey
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOracleGenerateSpeedyKey(t *testing.T) {
|
|
key := oracleGenerateSpeedyKey([]byte("buffer"), []byte("secret"), 1)
|
|
if len(key) != 64 {
|
|
t.Fatalf("expected 64 bytes, got %d", len(key))
|
|
}
|
|
}
|
|
|
|
func TestOracleGenerateSpeedyKeyDeterministic(t *testing.T) {
|
|
a := oracleGenerateSpeedyKey([]byte("buf"), []byte("key"), 10)
|
|
b := oracleGenerateSpeedyKey([]byte("buf"), []byte("key"), 10)
|
|
if !bytes.Equal(a, b) {
|
|
t.Error("speedy key should be deterministic")
|
|
}
|
|
}
|
|
|
|
func TestOracleGenerateSpeedyKeyDiffTurns(t *testing.T) {
|
|
a := oracleGenerateSpeedyKey([]byte("buf"), []byte("key"), 1)
|
|
b := oracleGenerateSpeedyKey([]byte("buf"), []byte("key"), 2)
|
|
if bytes.Equal(a, b) {
|
|
t.Error("different turns should produce different keys")
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oracleKeyFromUserPass
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOracleKeyFromUserPass(t *testing.T) {
|
|
key, err := oracleKeyFromUserPass("scott", "tiger")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(key) != 16 {
|
|
t.Fatalf("expected 16 bytes, got %d", len(key))
|
|
}
|
|
}
|
|
|
|
func TestOracleKeyFromUserPassCaseInsensitive(t *testing.T) {
|
|
k1, _ := oracleKeyFromUserPass("SCOTT", "TIGER")
|
|
k2, _ := oracleKeyFromUserPass("scott", "tiger")
|
|
if !bytes.Equal(k1, k2) {
|
|
t.Error("key should be case-insensitive")
|
|
}
|
|
}
|
|
|
|
func TestOracleKeyFromUserPassEmpty(t *testing.T) {
|
|
key, err := oracleKeyFromUserPass("", "")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(key) != 16 {
|
|
t.Fatalf("expected 16 bytes, got %d", len(key))
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oracleDecryptSessionKey / oracleEncryptSessionKey
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOracleEncryptDecryptSessionKey(t *testing.T) {
|
|
encKey := bytes.Repeat([]byte{0xAB}, 16)
|
|
plain := bytes.Repeat([]byte{0x55}, 16)
|
|
|
|
enc, err := oracleEncryptSessionKey(false, encKey, plain)
|
|
if err != nil {
|
|
t.Fatalf("encrypt error: %v", err)
|
|
}
|
|
dec, err := oracleDecryptSessionKey(false, encKey, enc)
|
|
if err != nil {
|
|
t.Fatalf("decrypt error: %v", err)
|
|
}
|
|
if !bytes.Equal(dec, plain) {
|
|
t.Errorf("round-trip failed: got %x, want %x", dec, plain)
|
|
}
|
|
}
|
|
|
|
func TestOracleDecryptSessionKeyInvalidHex(t *testing.T) {
|
|
_, err := oracleDecryptSessionKey(false, bytes.Repeat([]byte{0}, 16), "ZZZZ")
|
|
if err == nil {
|
|
t.Error("expected error for invalid hex")
|
|
}
|
|
}
|
|
|
|
func TestOracleEncryptSessionKeyPadding(t *testing.T) {
|
|
encKey := bytes.Repeat([]byte{0x11}, 16)
|
|
plain := bytes.Repeat([]byte{0x22}, 16)
|
|
|
|
// with padding — result should be hex-encoded and longer (full padded block)
|
|
encPad, err := oracleEncryptSessionKey(true, encKey, plain)
|
|
if err != nil {
|
|
t.Fatalf("encrypt (padding) error: %v", err)
|
|
}
|
|
// without padding — result is hex of origLen bytes
|
|
encNoPad, err := oracleEncryptSessionKey(false, encKey, plain)
|
|
if err != nil {
|
|
t.Fatalf("encrypt (no padding) error: %v", err)
|
|
}
|
|
// padded result includes extra padding block so it's longer
|
|
if len(encPad) <= len(encNoPad) {
|
|
t.Errorf("padded result (%d) should be longer than non-padded (%d)", len(encPad), len(encNoPad))
|
|
}
|
|
}
|
|
|
|
func TestOracleDecryptSessionKeyWithPadding(t *testing.T) {
|
|
encKey := bytes.Repeat([]byte{0xCC}, 16)
|
|
// Use 13 bytes so PKCS5 padding is 3 bytes (0x03 0x03 0x03), well within blockSize
|
|
plain := []byte("hello world!!")
|
|
|
|
enc, err := oracleEncryptSessionKey(true, encKey, plain)
|
|
if err != nil {
|
|
t.Fatalf("encrypt error: %v", err)
|
|
}
|
|
dec, err := oracleDecryptSessionKey(true, encKey, enc)
|
|
if err != nil {
|
|
t.Fatalf("decrypt error: %v", err)
|
|
}
|
|
if !bytes.Equal(dec, plain) {
|
|
t.Errorf("round-trip (padding) failed: got %q, want %q", dec, plain)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oracleAlterSession
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOracleAlterSession(t *testing.T) {
|
|
s := oracleAlterSession()
|
|
if !strings.Contains(s, "ALTER SESSION") {
|
|
t.Errorf("expected ALTER SESSION: %s", s)
|
|
}
|
|
if !strings.Contains(s, "NLS_LANGUAGE='AMERICAN'") {
|
|
t.Errorf("expected NLS_LANGUAGE: %s", s)
|
|
}
|
|
// must be null-terminated
|
|
if s[len(s)-1] != 0 {
|
|
t.Error("expected null terminator")
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oracleTZBytes
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOracleTZBytes(t *testing.T) {
|
|
b := oracleTZBytes()
|
|
if len(b) != 11 {
|
|
t.Fatalf("expected 11 bytes, got %d", len(b))
|
|
}
|
|
// first 4 bytes are always 0x80,0,0,0
|
|
if b[0] != 0x80 {
|
|
t.Errorf("expected 0x80 at [0], got 0x%02x", b[0])
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oracleTypeReps
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOracleTypeReps(t *testing.T) {
|
|
nego := &oracleTCPNego{}
|
|
compileCaps := make([]byte, 45)
|
|
reps := oracleTypeReps(nego, compileCaps)
|
|
if len(reps) == 0 {
|
|
t.Error("expected non-empty type reps")
|
|
}
|
|
// all values should be valid int16
|
|
for i, v := range reps {
|
|
if v < -1 || v > 10 {
|
|
// only known sentinel values are 0, 1, 10
|
|
_ = i // non-fatal: just make sure no panic
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// session buffer operations (putBytes / putInt / putUint / putClr / putKeyVal)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func newTestSession() *oracleSession {
|
|
return &oracleSession{
|
|
version: 315,
|
|
clrChunkSize: 0x40,
|
|
}
|
|
}
|
|
|
|
func TestSessionPutBytes(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putBytes(0x01, 0x02, 0x03)
|
|
if !bytes.Equal(s.out.Bytes(), []byte{1, 2, 3}) {
|
|
t.Errorf("unexpected output: %x", s.out.Bytes())
|
|
}
|
|
}
|
|
|
|
func TestSessionPutIntBigEndian(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putInt(uint16(0x0102), 2, true, false)
|
|
if !bytes.Equal(s.out.Bytes(), []byte{0x01, 0x02}) {
|
|
t.Errorf("big-endian uint16: %x", s.out.Bytes())
|
|
}
|
|
}
|
|
|
|
func TestSessionPutIntLittleEndian(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putInt(uint32(0x01020304), 4, false, false)
|
|
if !bytes.Equal(s.out.Bytes(), []byte{0x04, 0x03, 0x02, 0x01}) {
|
|
t.Errorf("little-endian uint32: %x", s.out.Bytes())
|
|
}
|
|
}
|
|
|
|
func TestSessionPutIntCompress(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putInt(int(256), 4, true, true)
|
|
out := s.out.Bytes()
|
|
// compressed: size byte + encoded bytes
|
|
if len(out) < 2 {
|
|
t.Fatalf("compressed output too short: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestSessionPutIntCompressZero(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putInt(int(0), 4, true, true)
|
|
out := s.out.Bytes()
|
|
if len(out) != 1 || out[0] != 0 {
|
|
t.Errorf("zero compressed should be single 0x00: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestSessionPutIntCompressNegative(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putInt(int(-1), 4, true, true)
|
|
out := s.out.Bytes()
|
|
// high bit of size byte should be set for negative
|
|
if len(out) < 2 || out[0]&0x80 == 0 {
|
|
t.Errorf("negative compress: expected high bit set in size byte: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestSessionPutInt1Byte(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putInt(uint8(0xAB), 1, true, false)
|
|
out := s.out.Bytes()
|
|
if len(out) != 1 || out[0] != 0xAB {
|
|
t.Errorf("1-byte int: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestSessionPutUintBigEndian(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putUint(uint16(0xBEEF), 2, true, false)
|
|
if !bytes.Equal(s.out.Bytes(), []byte{0xBE, 0xEF}) {
|
|
t.Errorf("putUint big-endian: %x", s.out.Bytes())
|
|
}
|
|
}
|
|
|
|
func TestSessionPutUintCompress(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putUint(uint32(0), 4, true, true)
|
|
out := s.out.Bytes()
|
|
if len(out) != 1 || out[0] != 0 {
|
|
t.Errorf("putUint compress zero: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestSessionPutUint1Byte(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putUint(uint8(7), 1, true, false)
|
|
out := s.out.Bytes()
|
|
if len(out) != 1 || out[0] != 7 {
|
|
t.Errorf("putUint 1-byte: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestSessionPutClrEmpty(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putClr(nil)
|
|
out := s.out.Bytes()
|
|
if len(out) != 1 || out[0] != 0 {
|
|
t.Errorf("empty clr: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestSessionPutClrShort(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putClr([]byte("hello"))
|
|
out := s.out.Bytes()
|
|
if out[0] != 5 || string(out[1:]) != "hello" {
|
|
t.Errorf("short clr: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestSessionPutClrLong(t *testing.T) {
|
|
// len > 0xfc triggers chunked encoding (0xfe prefix)
|
|
s := newTestSession()
|
|
data := bytes.Repeat([]byte("A"), 300)
|
|
s.putClr(data)
|
|
out := s.out.Bytes()
|
|
if out[0] != 0xfe {
|
|
t.Errorf("expected 0xfe for long CLR, got 0x%02x", out[0])
|
|
}
|
|
}
|
|
|
|
func TestSessionPutString(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putString("abc")
|
|
out := s.out.Bytes()
|
|
if out[0] != 3 || string(out[1:]) != "abc" {
|
|
t.Errorf("putString: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestSessionPutKeyVal(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putKeyValString("KEY", "VAL", 1)
|
|
out := s.out.Bytes()
|
|
if len(out) == 0 {
|
|
t.Error("putKeyValString produced no output")
|
|
}
|
|
// must contain key and value text somewhere
|
|
if !bytes.Contains(out, []byte("KEY")) {
|
|
t.Error("KEY not found in output")
|
|
}
|
|
if !bytes.Contains(out, []byte("VAL")) {
|
|
t.Error("VAL not found in output")
|
|
}
|
|
}
|
|
|
|
func TestSessionPutKeyValEmptyKey(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putKeyVal(nil, []byte("val"), 0)
|
|
out := s.out.Bytes()
|
|
// empty key → single 0x00 byte at start
|
|
if out[0] != 0 {
|
|
t.Errorf("empty key should start with 0x00, got 0x%02x", out[0])
|
|
}
|
|
}
|
|
|
|
func TestSessionPutKeyValEmptyVal(t *testing.T) {
|
|
s := newTestSession()
|
|
s.putKeyVal([]byte("key"), nil, 0)
|
|
out := s.out.Bytes()
|
|
if !bytes.Contains(out, []byte("key")) {
|
|
t.Error("key not found in output")
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// session reset
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestSessionReset(t *testing.T) {
|
|
s := newTestSession()
|
|
s.in = []byte{1, 2, 3}
|
|
s.index = 2
|
|
s.summary = &oracleSummary{retCode: 5}
|
|
s.putBytes(0xAA)
|
|
s.reset()
|
|
|
|
if s.in != nil {
|
|
t.Error("in should be nil after reset")
|
|
}
|
|
if s.index != 0 {
|
|
t.Error("index should be 0 after reset")
|
|
}
|
|
if s.summary != nil {
|
|
t.Error("summary should be nil after reset")
|
|
}
|
|
if s.out.Len() != 0 {
|
|
t.Error("out buffer should be empty after reset")
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// session read (from in-memory buffer)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestSessionRead(t *testing.T) {
|
|
s := newTestSession()
|
|
s.in = []byte{10, 20, 30, 40}
|
|
b, err := s.read(2)
|
|
if err != nil {
|
|
t.Fatalf("read error: %v", err)
|
|
}
|
|
if !bytes.Equal(b, []byte{10, 20}) {
|
|
t.Errorf("got %v", b)
|
|
}
|
|
b2, _ := s.read(2)
|
|
if !bytes.Equal(b2, []byte{30, 40}) {
|
|
t.Errorf("second read got %v", b2)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetByte(t *testing.T) {
|
|
s := newTestSession()
|
|
s.in = []byte{0xAB}
|
|
b, err := s.getByte()
|
|
if err != nil || b != 0xAB {
|
|
t.Errorf("getByte: %v, %v", b, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetBytes(t *testing.T) {
|
|
s := newTestSession()
|
|
s.in = []byte{1, 2, 3}
|
|
b, err := s.getBytes(3)
|
|
if err != nil || !bytes.Equal(b, []byte{1, 2, 3}) {
|
|
t.Errorf("getBytes: %v, %v", b, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetInt(t *testing.T) {
|
|
s := newTestSession()
|
|
// big-endian uint16 = 0x0102
|
|
s.in = []byte{0x01, 0x02}
|
|
v, err := s.getInt(2, false, true)
|
|
if err != nil || v != 0x0102 {
|
|
t.Errorf("getInt BE: %d, %v", v, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetInt64Compress(t *testing.T) {
|
|
// compressed: size=2, value=0x0102
|
|
s := newTestSession()
|
|
s.in = []byte{0x02, 0x01, 0x02}
|
|
v, err := s.getInt64(0, true, true)
|
|
if err != nil || v != 0x0102 {
|
|
t.Errorf("getInt64 compress: %d, %v", v, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetInt64CompressNegative(t *testing.T) {
|
|
// negative compressed: size byte has 0x80 set, size=1, value=1 → -1
|
|
s := newTestSession()
|
|
s.in = []byte{0x81, 0x01}
|
|
v, err := s.getInt64(0, true, true)
|
|
if err != nil || v != -1 {
|
|
t.Errorf("getInt64 compress negative: %d, %v", v, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetInt64CompressZero(t *testing.T) {
|
|
s := newTestSession()
|
|
s.in = []byte{0x00}
|
|
v, err := s.getInt64(0, true, true)
|
|
if err != nil || v != 0 {
|
|
t.Errorf("getInt64 compress zero: %d, %v", v, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetNullTermString(t *testing.T) {
|
|
s := newTestSession()
|
|
s.in = append([]byte("hello\x00world"), make([]byte, 50)...)
|
|
str, err := s.getNullTermString(20)
|
|
if err != nil || str != "hello" {
|
|
t.Errorf("getNullTermString: %q, %v", str, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetNullTermStringNoNull(t *testing.T) {
|
|
s := newTestSession()
|
|
s.in = []byte("hello")
|
|
str, err := s.getNullTermString(5)
|
|
if err != nil || str != "hello" {
|
|
t.Errorf("no-null getNullTermString: %q, %v", str, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetClrEmpty(t *testing.T) {
|
|
s := newTestSession()
|
|
s.in = []byte{0x00} // length = 0 → nil
|
|
b, err := s.getClr()
|
|
if err != nil || b != nil {
|
|
t.Errorf("getClr empty: %v, %v", b, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetClrShort(t *testing.T) {
|
|
s := newTestSession()
|
|
s.in = append([]byte{0x03}, []byte("abc")...)
|
|
b, err := s.getClr()
|
|
if err != nil || string(b) != "abc" {
|
|
t.Errorf("getClr short: %v, %v", b, err)
|
|
}
|
|
}
|
|
|
|
func TestSessionGetClrNullAndFd(t *testing.T) {
|
|
for _, marker := range []byte{0xff, 0xfd} {
|
|
s := newTestSession()
|
|
s.in = []byte{marker}
|
|
b, err := s.getClr()
|
|
if err != nil || b != nil {
|
|
t.Errorf("getClr 0x%02x: %v, %v", marker, b, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// hasError / oracleError
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestHasErrorNilSummary(t *testing.T) {
|
|
s := newTestSession()
|
|
if s.hasError() {
|
|
t.Error("nil summary should not be an error")
|
|
}
|
|
}
|
|
|
|
func TestHasErrorRetCode0(t *testing.T) {
|
|
s := newTestSession()
|
|
s.summary = &oracleSummary{retCode: 0}
|
|
if s.hasError() {
|
|
t.Error("retCode 0 should not be an error")
|
|
}
|
|
}
|
|
|
|
func TestHasErrorRetCode1403(t *testing.T) {
|
|
s := newTestSession()
|
|
s.summary = &oracleSummary{retCode: 1403}
|
|
if s.hasError() {
|
|
t.Error("retCode 1403 (no data) should not be an error")
|
|
}
|
|
}
|
|
|
|
func TestHasErrorRetCodeNonZero(t *testing.T) {
|
|
s := newTestSession()
|
|
s.summary = &oracleSummary{retCode: 1017}
|
|
if !s.hasError() {
|
|
t.Error("retCode 1017 should be an error")
|
|
}
|
|
}
|
|
|
|
func TestOracleErrorNilSummary(t *testing.T) {
|
|
s := newTestSession()
|
|
err := s.oracleError()
|
|
if err == nil {
|
|
t.Error("expected error")
|
|
}
|
|
}
|
|
|
|
func TestOracleErrorWithMessage(t *testing.T) {
|
|
s := newTestSession()
|
|
s.summary = &oracleSummary{retCode: 1017, errorMessage: []byte("ORA-01017")}
|
|
err := s.oracleError()
|
|
if err == nil || !strings.Contains(err.Error(), "ORA-01017") {
|
|
t.Errorf("expected ORA-01017 in error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOracleErrorNoMessage(t *testing.T) {
|
|
s := newTestSession()
|
|
s.summary = &oracleSummary{retCode: 1017}
|
|
err := s.oracleError()
|
|
if err == nil || !strings.Contains(err.Error(), "ORA-01017") {
|
|
t.Errorf("expected formatted ORA-01017: %v", err)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ANO write helpers (output shape verification)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestWriteANOHeader(t *testing.T) {
|
|
s := newTestSession()
|
|
s.writeANOHeader(101, 4, 0)
|
|
out := s.out.Bytes()
|
|
// first 4 bytes = 0xdeadbeef big-endian
|
|
if len(out) < 4 || binary.BigEndian.Uint32(out[:4]) != 0xdeadbeef {
|
|
t.Errorf("ANO header magic wrong: %x", out[:4])
|
|
}
|
|
}
|
|
|
|
func TestWriteANOServiceHeader(t *testing.T) {
|
|
s := newTestSession()
|
|
s.writeANOServiceHeader(2, 3)
|
|
out := s.out.Bytes()
|
|
// 2 bytes serviceType + 2 bytes subPackets + 4 bytes zeros = 8
|
|
if len(out) != 8 {
|
|
t.Errorf("expected 8 bytes, got %d: %x", len(out), out)
|
|
}
|
|
if binary.BigEndian.Uint16(out[0:2]) != 2 {
|
|
t.Errorf("serviceType wrong: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestWriteANOPacketHeader(t *testing.T) {
|
|
s := newTestSession()
|
|
s.writeANOPacketHeader(8, 5)
|
|
out := s.out.Bytes()
|
|
if len(out) != 4 {
|
|
t.Errorf("expected 4 bytes, got %d", len(out))
|
|
}
|
|
if binary.BigEndian.Uint16(out[0:2]) != 8 {
|
|
t.Errorf("length field wrong: %x", out)
|
|
}
|
|
if binary.BigEndian.Uint16(out[2:4]) != 5 {
|
|
t.Errorf("type field wrong: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestWriteANOVersion(t *testing.T) {
|
|
s := newTestSession()
|
|
s.writeANOVersion()
|
|
out := s.out.Bytes()
|
|
// 4-byte header (len=4,type=5) + 4-byte version = 8 bytes
|
|
if len(out) != 8 {
|
|
t.Errorf("expected 8 bytes, got %d: %x", len(out), out)
|
|
}
|
|
}
|
|
|
|
func TestWriteANOStatus(t *testing.T) {
|
|
s := newTestSession()
|
|
s.writeANOStatus(0xfcff)
|
|
out := s.out.Bytes()
|
|
// 4-byte header + 2-byte status = 6 bytes
|
|
if len(out) != 6 {
|
|
t.Errorf("expected 6 bytes, got %d: %x", len(out), out)
|
|
}
|
|
}
|
|
|
|
func TestWriteANOBytes(t *testing.T) {
|
|
s := newTestSession()
|
|
s.writeANOBytes([]byte{0xAA, 0xBB})
|
|
out := s.out.Bytes()
|
|
// 4-byte header + 2 data bytes = 6
|
|
if len(out) != 6 {
|
|
t.Errorf("expected 6 bytes, got %d: %x", len(out), out)
|
|
}
|
|
if out[4] != 0xAA || out[5] != 0xBB {
|
|
t.Errorf("data bytes wrong: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestWriteANOUB1(t *testing.T) {
|
|
s := newTestSession()
|
|
s.writeANOUB1(0x07)
|
|
out := s.out.Bytes()
|
|
// 4-byte header + 1 byte = 5
|
|
if len(out) != 5 {
|
|
t.Errorf("expected 5 bytes, got %d", len(out))
|
|
}
|
|
if out[4] != 0x07 {
|
|
t.Errorf("UB1 value wrong: %x", out)
|
|
}
|
|
}
|
|
|
|
func TestWriteANOUB2Array(t *testing.T) {
|
|
s := newTestSession()
|
|
s.writeANOUB2Array([]int{1, 2, 3, 4})
|
|
out := s.out.Bytes()
|
|
// header 4 + deadbeef 4 + const 2 + count 4 + 4*2 = 22
|
|
if len(out) != 4+4+2+4+4*2 {
|
|
t.Errorf("expected 22 bytes, got %d: %x", len(out), out)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ANO read helpers (round-trip through in-buffer)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestReadANOHeader(t *testing.T) {
|
|
s := newTestSession()
|
|
// build a valid ANO header in the in buffer
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint32(0xdeadbeef)) // magic
|
|
binary.Write(&buf, binary.BigEndian, uint16(101)) // length
|
|
binary.Write(&buf, binary.BigEndian, uint32(0x0b200200)) // version
|
|
binary.Write(&buf, binary.BigEndian, uint16(4)) // serviceCount
|
|
buf.WriteByte(0) // flags
|
|
s.in = buf.Bytes()
|
|
|
|
h, err := s.readANOHeader()
|
|
if err != nil {
|
|
t.Fatalf("readANOHeader error: %v", err)
|
|
}
|
|
if h.serviceCount != 4 {
|
|
t.Errorf("serviceCount: %d", h.serviceCount)
|
|
}
|
|
}
|
|
|
|
func TestReadANOHeaderBadMagic(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint32(0xCAFEBABE)) // wrong magic
|
|
binary.Write(&buf, binary.BigEndian, uint16(101))
|
|
binary.Write(&buf, binary.BigEndian, uint32(0x0b200200))
|
|
binary.Write(&buf, binary.BigEndian, uint16(2))
|
|
buf.WriteByte(0)
|
|
s.in = buf.Bytes()
|
|
|
|
_, err := s.readANOHeader()
|
|
if err == nil || !strings.Contains(err.Error(), "mismatch") {
|
|
t.Errorf("expected mismatch error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestReadANOServiceHeader(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint16(1)) // serviceType
|
|
binary.Write(&buf, binary.BigEndian, uint16(3)) // subPackets
|
|
binary.Write(&buf, binary.BigEndian, uint32(0)) // errCode
|
|
s.in = buf.Bytes()
|
|
|
|
svcType, subPkts, errCode, err := s.readANOServiceHeader()
|
|
if err != nil || svcType != 1 || subPkts != 3 || errCode != 0 {
|
|
t.Errorf("readANOServiceHeader: %d %d %d %v", svcType, subPkts, errCode, err)
|
|
}
|
|
}
|
|
|
|
func TestReadANOPacketHeader(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint16(8)) // length
|
|
binary.Write(&buf, binary.BigEndian, uint16(5)) // type
|
|
s.in = buf.Bytes()
|
|
|
|
length, err := s.readANOPacketHeader(5)
|
|
if err != nil || length != 8 {
|
|
t.Errorf("readANOPacketHeader: %d, %v", length, err)
|
|
}
|
|
}
|
|
|
|
func TestReadANOPacketHeaderTypeMismatch(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint16(4))
|
|
binary.Write(&buf, binary.BigEndian, uint16(99))
|
|
s.in = buf.Bytes()
|
|
|
|
_, err := s.readANOPacketHeader(5) // expect 5, got 99
|
|
if err == nil || !strings.Contains(err.Error(), "mismatch") {
|
|
t.Errorf("expected type mismatch error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestReadANOVersion(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint16(4)) // length
|
|
binary.Write(&buf, binary.BigEndian, uint16(5)) // type=5
|
|
binary.Write(&buf, binary.BigEndian, uint32(0x0b200200)) // version
|
|
s.in = buf.Bytes()
|
|
|
|
v, err := s.readANOVersion()
|
|
if err != nil || v != 0x0b200200 {
|
|
t.Errorf("readANOVersion: 0x%x, %v", v, err)
|
|
}
|
|
}
|
|
|
|
func TestReadANOStatus(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint16(2)) // length
|
|
binary.Write(&buf, binary.BigEndian, uint16(6)) // type=6
|
|
binary.Write(&buf, binary.BigEndian, uint16(0xfbff)) // status
|
|
s.in = buf.Bytes()
|
|
|
|
status, err := s.readANOStatus()
|
|
if err != nil || status != 0xfbff {
|
|
t.Errorf("readANOStatus: 0x%x, %v", status, err)
|
|
}
|
|
}
|
|
|
|
func TestReadANOUB1(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint16(1)) // length
|
|
binary.Write(&buf, binary.BigEndian, uint16(2)) // type=2
|
|
buf.WriteByte(0x42)
|
|
s.in = buf.Bytes()
|
|
|
|
v, err := s.readANOUB1()
|
|
if err != nil || v != 0x42 {
|
|
t.Errorf("readANOUB1: 0x%x, %v", v, err)
|
|
}
|
|
}
|
|
|
|
func TestReadANOString(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint16(4)) // length
|
|
binary.Write(&buf, binary.BigEndian, uint16(0)) // type=0
|
|
buf.WriteString("TEST")
|
|
s.in = buf.Bytes()
|
|
|
|
str, err := s.readANOString()
|
|
if err != nil || str != "TEST" {
|
|
t.Errorf("readANOString: %q, %v", str, err)
|
|
}
|
|
}
|
|
|
|
func TestReadANOBytes(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint16(3)) // length
|
|
binary.Write(&buf, binary.BigEndian, uint16(1)) // type=1
|
|
buf.Write([]byte{0xAA, 0xBB, 0xCC})
|
|
s.in = buf.Bytes()
|
|
|
|
b, err := s.readANOBytes()
|
|
if err != nil || !bytes.Equal(b, []byte{0xAA, 0xBB, 0xCC}) {
|
|
t.Errorf("readANOBytes: %x, %v", b, err)
|
|
}
|
|
}
|
|
|
|
func TestSkipANOPacket(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint16(3)) // length
|
|
binary.Write(&buf, binary.BigEndian, uint16(7)) // type (ignored)
|
|
buf.Write([]byte{0xAA, 0xBB, 0xCC})
|
|
s.in = buf.Bytes()
|
|
|
|
if err := s.skipANOPacket(); err != nil {
|
|
t.Errorf("skipANOPacket error: %v", err)
|
|
}
|
|
if s.index != len(s.in) {
|
|
t.Errorf("expected to consume all %d bytes, index=%d", len(s.in), s.index)
|
|
}
|
|
}
|
|
|
|
func TestSkipANOPacketZeroLength(t *testing.T) {
|
|
s := newTestSession()
|
|
var buf bytes.Buffer
|
|
binary.Write(&buf, binary.BigEndian, uint16(0)) // length=0
|
|
binary.Write(&buf, binary.BigEndian, uint16(3))
|
|
s.in = buf.Bytes()
|
|
|
|
if err := s.skipANOPacket(); err != nil {
|
|
t.Errorf("skipANOPacket zero-length error: %v", err)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// getDlc / getKeyVal (compressed-length wrappers)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestGetDlcZeroLength(t *testing.T) {
|
|
s := newTestSession()
|
|
// compressed int 0 → single 0x00 byte
|
|
s.in = []byte{0x00}
|
|
b, err := s.getDlc()
|
|
if err != nil || b != nil {
|
|
t.Errorf("getDlc zero: %v, %v", b, err)
|
|
}
|
|
}
|
|
|
|
func TestGetKeyVal(t *testing.T) {
|
|
s := newTestSession()
|
|
// build: key="K", val="V", num=7
|
|
// all DLC values use compressed encoding (putUint + putClr)
|
|
write := func(b *bytes.Buffer, data []byte) {
|
|
// compressed uint32 for len
|
|
l := len(data)
|
|
tmp := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(tmp, uint64(l))
|
|
tmp = bytes.TrimLeft(tmp, "\x00")
|
|
if len(tmp) == 0 {
|
|
b.WriteByte(0)
|
|
} else {
|
|
b.WriteByte(byte(len(tmp)))
|
|
b.Write(tmp)
|
|
}
|
|
// clr: single byte len + data
|
|
b.WriteByte(byte(l))
|
|
b.Write(data)
|
|
}
|
|
writeCompressedInt := func(b *bytes.Buffer, n int) {
|
|
tmp := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(tmp, uint64(n))
|
|
tmp = bytes.TrimLeft(tmp, "\x00")
|
|
if len(tmp) == 0 {
|
|
b.WriteByte(0)
|
|
} else {
|
|
b.WriteByte(byte(len(tmp)))
|
|
b.Write(tmp)
|
|
}
|
|
}
|
|
var buf bytes.Buffer
|
|
write(&buf, []byte("K"))
|
|
write(&buf, []byte("V"))
|
|
writeCompressedInt(&buf, 7)
|
|
s.in = buf.Bytes()
|
|
|
|
key, val, num, err := s.getKeyVal()
|
|
if err != nil {
|
|
t.Fatalf("getKeyVal error: %v", err)
|
|
}
|
|
if string(key) != "K" || string(val) != "V" || num != 7 {
|
|
t.Errorf("getKeyVal: key=%q val=%q num=%d", key, val, num)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// oracleEncryptPassword (random prefix — just check it decodes and expands)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestOracleEncryptPassword(t *testing.T) {
|
|
key := bytes.Repeat([]byte{0x42}, 32) // AES-256
|
|
enc, err := oracleEncryptPassword([]byte("secret"), key, false)
|
|
if err != nil {
|
|
t.Fatalf("oracleEncryptPassword error: %v", err)
|
|
}
|
|
if len(enc) == 0 {
|
|
t.Error("expected non-empty hex output")
|
|
}
|
|
// must be valid hex
|
|
if _, err = hex.DecodeString(enc); err != nil {
|
|
t.Errorf("output not valid hex: %v", err)
|
|
}
|
|
}
|