// Copyright 2025 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 Alan // @Date 2023/07/25 // Updated by Argo Zhang at 2025/11/21 using IP2Region.Net.Abstractions; using IP2Region.Net.Internal; using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Text; namespace IP2Region.Net.XDB; /// /// 实现类 /// /// /// /// public class Searcher(CachePolicy cachePolicy, string xdbPath) : ISearcher { private readonly ICacheStrategy _cacheStrategy = CacheStrategyFactory.CreateCacheStrategy(cachePolicy, xdbPath); /// /// /// public int IoCount => _cacheStrategy.IoCount; /// /// /// public string? Search(string ipStr) { var ipAddress = IPAddress.Parse(ipStr); return SearchCore(ipAddress.GetAddressBytes()); } /// /// /// public string? Search(IPAddress ipAddress) => SearchCore(ipAddress.GetAddressBytes()); /// /// /// [Obsolete("已弃用,请改用其他方法;Deprecated; please use Search(string) or Search(IPAddress) method.")] [ExcludeFromCodeCoverage] public string? Search(uint ipAddress) { var bytes = BitConverter.GetBytes(ipAddress); Array.Reverse(bytes); return SearchCore(bytes); } string? SearchCore(byte[] ipBytes) { // 重置 IO 计数器 _cacheStrategy.ResetIoCount(); // 每个 vector 索引项的字节数 var vectorIndexSize = 8; // vector 索引的列数 var vectorIndexCols = 256; // 计算得到 vector 索引项的开始地址。 var il0 = ipBytes[0]; var il1 = ipBytes[1]; var idx = il0 * vectorIndexCols * vectorIndexSize + il1 * vectorIndexSize; var vector = _cacheStrategy.GetVectorIndex(idx); var sPtr = BinaryPrimitives.ReadUInt32LittleEndian(vector.Span); var ePtr = BinaryPrimitives.ReadUInt32LittleEndian(vector.Span.Slice(4)); var length = ipBytes.Length; var indexSize = length * 2 + 6; var l = 0; var h = (ePtr - sPtr) / indexSize; var dataLen = 0; long dataPtr = 0; while (l <= h) { int m = (int)(l + h) >> 1; var p = sPtr + m * indexSize; var buff = _cacheStrategy.GetData(p, indexSize); var s = buff.Span.Slice(0, length); var e = buff.Span.Slice(length, length); if (ByteCompare(ipBytes, s) < 0) { h = m - 1; } else if (ByteCompare(ipBytes, e) > 0) { l = m + 1; } else { dataLen = BinaryPrimitives.ReadUInt16LittleEndian(buff.Span.Slice(length * 2, 2)); dataPtr = BinaryPrimitives.ReadUInt32LittleEndian(buff.Span.Slice(length * 2 + 2, 4)); break; } } var regionBuff = _cacheStrategy.GetData(dataPtr, dataLen); return Encoding.UTF8.GetString(regionBuff.Span.ToArray()); } static int ByteCompare(byte[] ip1, ReadOnlySpan ip2) => ip1.Length == 4 ? IPv4Compare(ip1, ip2) : IPv6Compare(ip1, ip2); static int IPv4Compare(byte[] ip1, ReadOnlySpan ip2) { var ret = 0; for (int i = 0; i < ip1.Length; i++) { var ip2Index = ip1.Length - 1 - i; if (ip1[i] < ip2[ip2Index]) { return -1; } else if (ip1[i] > ip2[ip2Index]) { return 1; } } return ret; } static int IPv6Compare(byte[] ip1, ReadOnlySpan ip2) { var ret = 0; for (int i = 0; i < ip1.Length; i++) { if (ip1[i] < ip2[i]) { return -1; } else if (ip1[i] > ip2[i]) { return 1; } } return ret; } /// /// /// public void Dispose() { _cacheStrategy.Dispose(); GC.SuppressFinalize(this); } }