ip2region_private/binding/golang/xdb/util_test.go

314 lines
9.0 KiB
Go

// Copyright 2022 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// ---
// @Author Lion <chenxin619315@gmail.com>
// @Date 2022/06/16
package xdb
import (
"encoding/binary"
"fmt"
"testing"
"time"
)
// bytesToInt64 converts a big-endian byte slice (variable length) to int64.
// This handles the carry byte produced by IPSub when the addition overflows 4 bytes.
func bytesToInt64(buf []byte) int64 {
var v int64
for _, b := range buf {
v = (v << 8) | int64(b)
}
return v
}
func TestParseIP(t *testing.T) {
var ips = []string{"29.34.191.255", "2c0f:fff0::", "2fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"}
for _, ip := range ips {
bytes, err := ParseIP(ip)
if err != nil {
t.Errorf("check ip `%s`: %s\n", IP2String(bytes), err)
}
nip := IP2String(bytes)
fmt.Printf("checkip: (%s / %s), isEqual: %v\n", ip, nip, ip == nip)
}
}
func TestIPCompare(t *testing.T) {
var ipPairs = [][]string{
{"1.2.3.4", "1.2.3.5"},
{"58.250.36.41", "58.250.30.41"},
{"2c10::", "2e00::"},
{"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"},
{"fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "fe00::"},
}
for _, pairs := range ipPairs {
fmt.Printf("IPCompare(%s, %s): %d\n", pairs[0], pairs[1], IPCompare([]byte(pairs[0]), []byte(pairs[1])))
}
}
func TestIPSub(t *testing.T) {
var strToSub = "1.2.3.4"
bytesToSub, err := ParseIP(strToSub)
if err != nil {
t.Fatalf("failed to parse ip %s", strToSub)
}
var intToSub = int(binary.BigEndian.Uint32(bytesToSub))
t.Logf("to sub ip: %d -> %s", intToSub, strToSub)
// edge cases
edgeValues := []uint32{0, 1, 2, 0x7FFFFFFF, 0x80000000, 0xFFFFFFFE, 0xFFFFFFFF}
for _, v := range edgeValues {
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, v)
subVal, err := IPSub(buf, bytesToSub)
if err != nil {
t.Fatalf("failed to IPSub(%s,%s): %s", IP2String(buf), strToSub, err)
}
byteSub := bytesToInt64(subVal)
intSub := int64(v) + int64(intToSub)
if byteSub != intSub {
t.Fatalf("IPSub(%d, %d): byte=%d, int=%d", v, intToSub, byteSub, intSub)
}
}
// stride-based sampling across the full range
counter := 0
const stride = 0x100000
buf := make([]byte, 4)
for i := uint32(0); i < 0xFFFFFFFF-stride; i += stride {
binary.BigEndian.PutUint32(buf, i)
subVal, err := IPSub(buf, bytesToSub)
if err != nil {
t.Fatalf("failed to IPSub(%s,%s): %s", IP2String(buf), strToSub, err)
}
byteSub := bytesToInt64(subVal)
intSub := int64(i) + int64(intToSub)
if byteSub != intSub {
t.Fatal("byte and int sub value are not the same")
}
counter++
}
// also test the final boundary value
binary.BigEndian.PutUint32(buf, 0xFFFFFFFF)
subVal, err := IPSub(buf, bytesToSub)
if err != nil {
t.Fatalf("failed to IPSub(%s,%s): %s", IP2String(buf), strToSub, err)
}
byteSub := bytesToInt64(subVal)
intSub := int64(4294967295) + int64(intToSub)
if byteSub != intSub {
t.Fatalf("IPSub(0xFFFFFFFF, %d): byte=%d, int=%d", intToSub, byteSub, intSub)
}
counter++
t.Logf("test done with %d ips (sampled)", counter)
}
func TestIPHalf(t *testing.T) {
// edge cases
edgeValues := []uint32{0, 1, 2, 0x7FFFFFFF, 0x80000000, 0xFFFFFFFE, 0xFFFFFFFF}
for _, v := range edgeValues {
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, v)
half := IPHalf(buf)
byteMiddle := binary.BigEndian.Uint32(half)
intMidle := v >> 1
if byteMiddle != intMidle {
t.Fatalf("IPHalf(0x%08x): byte=0x%08x, int=0x%08x", v, byteMiddle, intMidle)
}
}
// stride-based sampling across the full range
const stride = 0x100000
buf := make([]byte, 4)
for i := uint32(0); i < 0xFFFFFFFF-stride; i += stride {
binary.BigEndian.PutUint32(buf, i)
half := IPHalf(buf)
byteMiddle := binary.BigEndian.Uint32(half)
intMidle := i >> 1
if byteMiddle != intMidle {
t.Fatalf("IPHalf(0x%08x): byte=0x%08x, int=0x%08x", i, byteMiddle, intMidle)
}
}
}
func TestSubOverflow(t *testing.T) {
var ip1Str = "255.255.255.250"
ip1Bytes, err := ParseIP(ip1Str)
if err != nil {
t.Fatalf("failed to ParseIP(%s): %s", ip1Str, err)
}
var buff = make([]byte, 4)
for i := 0; i < 10; i++ {
binary.BigEndian.PutUint32(buff, uint32(i))
ipSub, err := IPSub(ip1Bytes, buff)
if err != nil {
t.Fatalf("failed to IPSub(%s, %s): %s", ip1Str, IP2String(buff), err)
}
t.Logf("IPSub(%s, %s) = %+v", ip1Str, IP2String(buff), ipSub)
}
}
func TestIPMiddle(t *testing.T) {
var sIPStr = "0.0.0.0"
sBytes, err := ParseIP(sIPStr)
if err != nil {
t.Fatalf("failed to parse ip %s", sIPStr)
}
var sInt = int(binary.BigEndian.Uint32(sBytes))
t.Logf("start ip: %d -> %s", sInt, sIPStr)
// edge cases
edgeValues := []uint32{0, 1, 2, 0x7FFFFFFF, 0x80000000, 0xFFFFFFFE, 0xFFFFFFFF}
for _, v := range edgeValues {
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, v)
midVal, err := IPMiddle(sBytes, buf)
if err != nil {
t.Fatalf("failed to IPMiddle(%s,%s): %s", sIPStr, IP2String(buf), err)
}
byteMid := int(binary.BigEndian.Uint32(midVal))
intMid := (sInt + int(v)) >> 1
if byteMid != intMid {
t.Fatalf("IPMiddle(%d, %d): byte=%d, int=%d", sInt, v, byteMid, intMid)
}
}
// stride-based sampling across the full range
counter := 0
const stride = 0x100000
buf := make([]byte, 4)
for i := uint32(0); i < 0xFFFFFFFF-stride; i += stride {
binary.BigEndian.PutUint32(buf, i)
midVal, err := IPMiddle(sBytes, buf)
if err != nil {
t.Fatalf("failed to IPMiddle(%s,%s): %s", sIPStr, IP2String(buf), err)
}
byteMid := int(binary.BigEndian.Uint32(midVal))
intMid := (sInt + int(i)) >> 1
if byteMid != intMid {
t.Fatal("byte and int middle value are not the same")
}
counter++
}
t.Logf("test done with %d ips (sampled)", counter)
}
func TestLoadVectorIndex(t *testing.T) {
vIndex, err := LoadVectorIndexFromFile("../../../data/ip2region_v4.xdb")
if err != nil {
fmt.Printf("failed to load vector index: %s\n", err)
return
}
fmt.Printf("vIndex length: %d\n", len(vIndex))
}
func TestLoadContent(t *testing.T) {
buff, err := LoadContentFromFile("../../../data/ip2region_v4.xdb")
if err != nil {
fmt.Printf("failed to load xdb content: %s\n", err)
return
}
fmt.Printf("buff length: %d\n", len(buff))
}
func TestIPv4CompareCorrectness(t *testing.T) {
tests := []struct {
name string
ip1 []byte // big-endian (from ParseIP)
ip2 []byte // little-endian (from xdb index)
want int
}{
// equal
{"equal", []byte{1, 2, 3, 4}, []byte{4, 3, 2, 1}, 0},
{"equal_zero", []byte{0, 0, 0, 0}, []byte{0, 0, 0, 0}, 0},
{"equal_max", []byte{255, 255, 255, 255}, []byte{255, 255, 255, 255}, 0},
// less at each byte position
{"less_b0", []byte{0, 2, 3, 4}, []byte{4, 3, 2, 1}, -1},
{"less_b1", []byte{1, 1, 3, 4}, []byte{4, 3, 2, 1}, -1},
{"less_b2", []byte{1, 2, 2, 4}, []byte{4, 3, 2, 1}, -1},
{"less_b3", []byte{1, 2, 3, 3}, []byte{4, 3, 2, 1}, -1},
// greater at each byte position
{"greater_b0", []byte{2, 2, 3, 4}, []byte{4, 3, 2, 1}, 1},
{"greater_b1", []byte{1, 3, 3, 4}, []byte{4, 3, 2, 1}, 1},
{"greater_b2", []byte{1, 2, 4, 4}, []byte{4, 3, 2, 1}, 1},
{"greater_b3", []byte{1, 2, 3, 5}, []byte{4, 3, 2, 1}, 1},
// edge: LE ip2 with byte0=0x00 (common for small IPs)
{"le_zero_leading", []byte{1, 0, 0, 1}, []byte{1, 0, 0, 1}, 0},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ip2Copy := make([]byte, 4)
copy(ip2Copy, tc.ip2)
got := IPv4.IPCompare(tc.ip1, tc.ip2)
if got != tc.want {
t.Errorf("IPv4.IPCompare(%v, %v) = %d, want %d", tc.ip1, tc.ip2, got, tc.want)
}
// verify ip2 is not mutated
for i := 0; i < 4; i++ {
if tc.ip2[i] != ip2Copy[i] {
t.Errorf("IPv4.IPCompare mutated ip2 at byte %d: was 0x%02x, now 0x%02x", i, ip2Copy[i], tc.ip2[i])
}
}
})
}
}
func TestIPCompareNoMutation(t *testing.T) {
// verify that IPv4.IPCompare does NOT mutate the input ip2 slice
ip1, _ := ParseIP("1.2.3.4")
ip2Orig := []byte{0x04, 0x03, 0x02, 0x01} // LE representation of 1.2.3.4
ip2Copy := make([]byte, 4)
copy(ip2Copy, ip2Orig)
result := IPv4.IPCompare(ip1, ip2Orig)
if result != 0 {
t.Fatalf("IPv4.IPCompare(1.2.3.4, LE[1.2.3.4]) = %d, want 0", result)
}
// verify ip2 is unchanged
for i := 0; i < 4; i++ {
if ip2Orig[i] != ip2Copy[i] {
t.Fatalf("IPv4.IPCompare mutated ip2 at byte %d: was 0x%02x, now 0x%02x", i, ip2Copy[i], ip2Orig[i])
}
}
_ = ip2Copy
}
func TestLoadHeader(t *testing.T) {
header, err := LoadHeaderFromFile("../../../data/ip2region_v4.xdb")
if err != nil {
fmt.Printf("failed to load xdb header info: %s\n", err)
return
}
fmt.Printf("Version : %d\n", header.Version)
fmt.Printf("IndexPolicy : %s\n", header.IndexPolicy.String())
fmt.Printf("CreatedAt : %d(%s)\n", header.CreatedAt, time.Unix(int64(header.CreatedAt), 0).Format(time.RFC3339))
fmt.Printf("StartIndexPtr : %d\n", header.StartIndexPtr)
fmt.Printf("EndIndexPtr : %d\n", header.EndIndexPtr)
fmt.Printf("IPVersion : %d\n", header.IPVersion)
fmt.Printf("RuntimePtrBytes : %d\n", header.RuntimePtrBytes)
}