314 lines
9.0 KiB
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)
|
|
}
|