From d9fc17727e9a63bd5eb45f90267776e9ec1c4539 Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Sat, 4 Apr 2026 18:39:31 +0800 Subject: [PATCH] add bytes ip sub and middle util func --- binding/golang/xdb/util.go | 53 ++++++++++++++++++ binding/golang/xdb/util_test.go | 97 +++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/binding/golang/xdb/util.go b/binding/golang/xdb/util.go index 4b48113..6df4813 100644 --- a/binding/golang/xdb/util.go +++ b/binding/golang/xdb/util.go @@ -82,6 +82,59 @@ func IPSubOne(ip []byte) []byte { return r } +// IPSub Sub the spcecified two byte ip +func IPSub(sip, eip []byte) ([]byte, error) { + if len(sip) != len(eip) { + return []byte{}, fmt.Errorf("length of the two ips are not the same") + } + + var carry uint16 = 0 + var result = make([]byte, len(sip)+1) + + for i := len(sip) - 1; i >= 0; i-- { + sum := uint16(sip[i]) + uint16(eip[i]) + carry + result[i+1] = byte(sum) // Store standard 8-bit result + carry = sum >> 8 // Extract the 1-bit carry for the next byte + } + + // check and append the carry + if carry > 0 { + result[0] = byte(carry) + return result, nil + } else { + return result[1:], nil + } +} + +// IPHalf get the half value of an input byte ip +func IPHalf(ip []byte) []byte { + var length = len(ip) + var result = make([]byte, length) + // Tracks the bit falling off from the previous byte + var carry byte = 0 + + for i := 0; i < length; i++ { + // 1. Shift current byte right by 1 + // 2. Or (|) with the carry from the previous byte (shifted to the MSB position) + result[i] = (ip[i] >> 1) | (carry << 7) + + // 3. Capture the Least Significant Bit (LSB) to use as carry for the next byte + carry = ip[i] & 1 + } + + return result +} + +// IPMiddle get the middle value of two input ip address +func IPMiddle(sip, eip []byte) ([]byte, error) { + buf, err := IPSub(sip, eip) + if err != nil { + return []byte{}, fmt.Errorf("IPSub(%s, %s): %w", IP2String(sip), IP2String(eip), err) + } + + return IPHalf(buf), nil +} + // Verify if the current Searcher could be used to search the specified xdb file. // Why do we need this check ? // The future features of the xdb impl may cause the current searcher not able to work properly. diff --git a/binding/golang/xdb/util_test.go b/binding/golang/xdb/util_test.go index 75696a7..930fcbc 100644 --- a/binding/golang/xdb/util_test.go +++ b/binding/golang/xdb/util_test.go @@ -9,6 +9,7 @@ package xdb import ( + "encoding/binary" "fmt" "testing" "time" @@ -41,6 +42,102 @@ func TestIPCompare(t *testing.T) { } } +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) + + counter := 0 + buf := make([]byte, 4) + for i := 0; i < 0x2FFFFFFF; i++ { + binary.BigEndian.PutUint32(buf, uint32(i)) + subVal, err := IPSub(buf, bytesToSub) + if err != nil { + t.Fatalf("failed to IPSub(%s,%s): %s", IP2String(buf), strToSub, err) + } + + // do it as two integers + byteSub := int(binary.BigEndian.Uint32(subVal)) + intSub := i + intToSub + if byteSub != intSub { + t.Fatal("byte and int sub value are not the same") + } + + counter++ + } + + t.Logf("test done with %d ips", counter) +} + +func TestIPHalf(t *testing.T) { + var buf = make([]byte, 4) + for i := 0; i < 0xFFFFFFFF; i++ { + binary.BigEndian.PutUint32(buf, uint32(i)) + half := IPHalf(buf) + + // do it as two integers + byteMiddle := binary.BigEndian.Uint32(half) + intMidle := i >> 1 + if byteMiddle != uint32(intMidle) { + t.Fatal("byte middle and int middle are not the same") + } + } +} + +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) + + counter := 0 + buf := make([]byte, 4) + for i := 0; i < 0x0FFFFFFF; i++ { + binary.BigEndian.PutUint32(buf, uint32(i)) + midVal, err := IPMiddle(sBytes, buf) + if err != nil { + t.Fatalf("failed to IPMiddle(%s,%s): %s", sIPStr, IP2String(buf), err) + } + + // do it as two integers + byteMid := int(binary.BigEndian.Uint32(midVal)) + intMid := (sInt + i) >> 1 + if byteMid != intMid { + t.Fatal("byte and int middle value are not the same") + } + + counter++ + } + + t.Logf("test done with %d ips", counter) +} + func TestLoadVectorIndex(t *testing.T) { vIndex, err := LoadVectorIndexFromFile("../../../data/ip2region_v4.xdb") if err != nil {