From 445eb4cb8eeede71dd539063af731d31c84435a9 Mon Sep 17 00:00:00 2001 From: Alice39s Date: Sun, 28 Jun 2026 01:50:39 +0900 Subject: [PATCH] fix(erlang): integer guard, cache dedup, dead macro cleanup --- binding/erlang/README.md | 12 ++-- binding/erlang/README_zh.md | 12 ++-- binding/erlang/benchmarks/xdb-benchmark.sh | 2 +- binding/erlang/include/ip2region.hrl | 18 +----- binding/erlang/src/ip2region_sup.erl | 15 +++-- binding/erlang/src/ip2region_util.erl | 6 +- binding/erlang/src/ip2region_worker.erl | 69 ++++++++------------- binding/erlang/src/ip2region_xdb.erl | 10 +++ binding/erlang/src/xdb.erl | 17 +++-- binding/erlang/src/xdb_benchmark.erl | 18 +++--- binding/erlang/test/ip2region_sup_test.erl | 7 ++- binding/erlang/test/ip2region_util_test.erl | 6 +- 12 files changed, 86 insertions(+), 106 deletions(-) diff --git a/binding/erlang/README.md b/binding/erlang/README.md index 73eeb6b..7bb9e2b 100644 --- a/binding/erlang/README.md +++ b/binding/erlang/README.md @@ -158,11 +158,11 @@ System: CPU : Apple M4 Cores : 10 cores / 10 threads Erlang : Erlang/OTP 29 [erts-17.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace] - Loaded : 487169 IPs in 1.574 s + Loaded : 487169 IPs in 1.335 s Benchmarks: - file total= 9.460s count= 487169 qps= 51500.40 avg= 0.019417 ms/op (19.417 us/op) - cache total= 0.150s count= 487169 qps= 3245240.41 avg= 0.000308 ms/op ( 0.308 us/op) + cold total= 9.601s count= 487169 qps= 50740.66 avg= 0.019708 ms/op (19.708 us/op) + warm total= 0.160s count= 487169 qps= 3053164.29 avg= 0.000328 ms/op ( 0.328 us/op) Done. ``` @@ -174,11 +174,11 @@ System: CPU : Apple M4 Cores : 10 cores / 10 threads Erlang : Erlang/OTP 29 [erts-17.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace] - Loaded : 638953 IPs in 2.724 s + Loaded : 638953 IPs in 2.949 s Benchmarks: - file total= 16.326s count= 638953 qps= 39137.65 avg= 0.025551 ms/op (25.551 us/op) - cache total= 0.469s count= 638953 qps= 1361972.38 avg= 0.000734 ms/op ( 0.734 us/op) + cold total= 20.504s count= 638953 qps= 31162.52 avg= 0.032090 ms/op (32.090 us/op) + warm total= 0.444s count= 638953 qps= 1437781.56 avg= 0.000696 ms/op ( 0.696 us/op) Done. ``` diff --git a/binding/erlang/README_zh.md b/binding/erlang/README_zh.md index a6d3022..89d3d72 100644 --- a/binding/erlang/README_zh.md +++ b/binding/erlang/README_zh.md @@ -135,11 +135,11 @@ System: CPU : Apple M4 Cores : 10 cores / 10 threads Erlang : Erlang/OTP 29 [erts-17.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace] - Loaded : 487169 IPs in 1.574 s + Loaded : 487169 IPs in 1.335 s Benchmarks: - file total= 9.460s count= 487169 qps= 51500.40 avg= 0.019417 ms/op (19.417 us/op) - cache total= 0.150s count= 487169 qps= 3245240.41 avg= 0.000308 ms/op ( 0.308 us/op) + cold total= 9.601s count= 487169 qps= 50740.66 avg= 0.019708 ms/op (19.708 us/op) + warm total= 0.160s count= 487169 qps= 3053164.29 avg= 0.000328 ms/op ( 0.328 us/op) Done. ``` @@ -151,11 +151,11 @@ System: CPU : Apple M4 Cores : 10 cores / 10 threads Erlang : Erlang/OTP 29 [erts-17.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace] - Loaded : 638953 IPs in 2.724 s + Loaded : 638953 IPs in 2.949 s Benchmarks: - file total= 16.326s count= 638953 qps= 39137.65 avg= 0.025551 ms/op (25.551 us/op) - cache total= 0.469s count= 638953 qps= 1361972.38 avg= 0.000734 ms/op ( 0.734 us/op) + cold total= 20.504s count= 638953 qps= 31162.52 avg= 0.032090 ms/op (32.090 us/op) + warm total= 0.444s count= 638953 qps= 1437781.56 avg= 0.000696 ms/op ( 0.696 us/op) Done. ``` diff --git a/binding/erlang/benchmarks/xdb-benchmark.sh b/binding/erlang/benchmarks/xdb-benchmark.sh index e8aebc3..77b647b 100755 --- a/binding/erlang/benchmarks/xdb-benchmark.sh +++ b/binding/erlang/benchmarks/xdb-benchmark.sh @@ -10,7 +10,7 @@ PROJECT_ROOT="$(cd ../.. && pwd)" if [ "$VERSION" = "ipv6" ]; then DB_FILE="${PROJECT_ROOT}/data/ip2region_v6.xdb" DATA_FILE="${PROJECT_ROOT}/data/ipv6_source.txt" - DB_CONFIG="[{ipv4, \"ip2region.xdb\"}, {ipv6, \"${DB_FILE}\"}]" + DB_CONFIG="[{ipv6, \"${DB_FILE}\"}]" elif [ "$VERSION" = "ipv4" ]; then DATA_FILE="${PROJECT_ROOT}/data/ipv4_source.txt" DB_CONFIG="[{ipv4, \"ip2region.xdb\"}]" diff --git a/binding/erlang/include/ip2region.hrl b/binding/erlang/include/ip2region.hrl index 2e7ea80..1cf474f 100644 --- a/binding/erlang/include/ip2region.hrl +++ b/binding/erlang/include/ip2region.hrl @@ -4,20 +4,11 @@ -define(NONE, none). -define(APP_NAME, ip2region). --define(XDB_VECTOR_INDEX, ets_xdb_vector_index). --define(XDB_SEGMENT_INDEX, ets_xdb_segement_index). --define(IP2REGION_CACHE, ets_ip2region_cache). - - -define(XDB_HEADER_SIZE, 256). -define(XDB_VECTOR_COLS, 256). --define(XDB_VECTOR_INDEX_SIZE, 8). +-define(XDB_VECTOR_INDEX_SIZE, 8). -define(XDB_VECTOR_INDEX_COUNT, (16#10000)). %% 256*256 --define(XDB_SEGMENT_INDEX_SIZE, 14). - --define(IP2REGION_POOL, ip2region_pool). - %% xdb header field offsets (bytes) -define(XDB_HEADER_VERSION_OFFSET, 0). -define(XDB_HEADER_INDEX_POLICY_OFFSET, 2). @@ -26,7 +17,6 @@ -define(XDB_HEADER_END_INDEX_PTR_OFFSET, 12). -define(XDB_HEADER_IP_VERSION_OFFSET, 16). -define(XDB_HEADER_RUNTIME_PTR_BYTES_OFFSET, 18). --define(XDB_HEADER_INFO_LENGTH, 256). %% IP versions as stored in xdb header -define(IP_VERSION_4, 4). @@ -45,12 +35,8 @@ -define(IP2REGION_CACHE_V6, ets_ip2region_cache_v6). %% Per-version pool names. ?IP2REGION_POOL is kept as the legacy v4 pool name. +-define(IP2REGION_POOL, ip2region_pool). -define(IP2REGION_POOL_V4, ip2region_pool_v4). -define(IP2REGION_POOL_V6, ip2region_pool_v6). --ifndef(IF). --define(IF(C, T, F), case (C) of true -> (T); false -> (F) end). --define(IF(C, T), ?IF(C, T, skip)). -endif. - --endif. \ No newline at end of file diff --git a/binding/erlang/src/ip2region_sup.erl b/binding/erlang/src/ip2region_sup.erl index e93d505..a111589 100644 --- a/binding/erlang/src/ip2region_sup.erl +++ b/binding/erlang/src/ip2region_sup.erl @@ -41,10 +41,6 @@ init([]) -> %% create_table() -> Opts = [named_table, set, public, {read_concurrency, true}, {keypos, 1}], - %% Legacy tables (kept for backward compatibility) - ensure_table(?XDB_VECTOR_INDEX, Opts), - ensure_table(?XDB_SEGMENT_INDEX, Opts), - ensure_table(?IP2REGION_CACHE, Opts), %% Version-specific tables for dual-stack support ensure_table(?XDB_VECTOR_INDEX_V4, Opts), ensure_table(?XDB_VECTOR_INDEX_V6, Opts), @@ -64,11 +60,14 @@ pool_child_specs() -> {ok, PoolArgsCfg} = application:get_env(poolargs), Versions = [Version || {Version, _File} <- DbConfig], UseLegacyName = (Versions == [ipv4]), + V4PoolName = case UseLegacyName of + true -> ?IP2REGION_POOL; + false -> ?IP2REGION_POOL_V4 + end, + ok = application:set_env(?APP_NAME, v4_pool_name, V4PoolName), lists:foldl( - fun({ipv4, File}, Acc) when UseLegacyName -> - [make_pool_spec(?IP2REGION_POOL, ipv4, File, PoolArgsCfg) | Acc]; - ({ipv4, File}, Acc) -> - [make_pool_spec(?IP2REGION_POOL_V4, ipv4, File, PoolArgsCfg) | Acc]; + fun({ipv4, File}, Acc) -> + [make_pool_spec(V4PoolName, ipv4, File, PoolArgsCfg) | Acc]; ({ipv6, File}, Acc) -> [make_pool_spec(?IP2REGION_POOL_V6, ipv6, File, PoolArgsCfg) | Acc]; (_, Acc) -> diff --git a/binding/erlang/src/ip2region_util.erl b/binding/erlang/src/ip2region_util.erl index df892f9..db5ec46 100644 --- a/binding/erlang/src/ip2region_util.erl +++ b/binding/erlang/src/ip2region_util.erl @@ -13,7 +13,7 @@ -spec ip_version(Ip :: tuple() | list() | binary() | integer()) -> ipv4 | ipv6 | {error, atom()}. -ip_version(Ip) when is_integer(Ip) -> ipv4; +ip_version(Ip) when is_integer(Ip), Ip >= 0, Ip =< 16#FFFFFFFF -> ipv4; ip_version({_, _, _, _}) -> ipv4; ip_version({_, _, _, _, _, _, _, _}) -> ipv6; ip_version(Ip) when is_binary(Ip) -> @@ -29,7 +29,7 @@ ip_version(_) -> -spec ip_to_bytes(Ip :: tuple() | list() | binary() | integer()) -> {ok, ipv4 | ipv6, binary()} | {error, atom()}. -ip_to_bytes(Ip) when is_integer(Ip) -> +ip_to_bytes(Ip) when is_integer(Ip), Ip >= 0, Ip =< 16#FFFFFFFF -> {ok, ipv4, <>}; ip_to_bytes({A, B, C, D}) -> {ok, ipv4, <>}; @@ -51,7 +51,7 @@ ip_to_bytes(_) -> -spec ipv4_to_n(Ip :: tuple() | list() | binary() | integer()) -> non_neg_integer() | {error, atom()}. -ipv4_to_n(IntIp) when is_integer(IntIp) -> IntIp; +ipv4_to_n(IntIp) when is_integer(IntIp), IntIp >= 0, IntIp =< 16#FFFFFFFF -> IntIp; ipv4_to_n({A, B, C, D}) -> <> = <>, N; diff --git a/binding/erlang/src/ip2region_worker.erl b/binding/erlang/src/ip2region_worker.erl index e207635..216fd76 100644 --- a/binding/erlang/src/ip2region_worker.erl +++ b/binding/erlang/src/ip2region_worker.erl @@ -18,8 +18,7 @@ -record(state, { xdb_fd, - version :: ipv4 | ipv6, - segment_index_size :: pos_integer() + version :: ipv4 | ipv6 }). %%========================================== @@ -63,14 +62,14 @@ init(Args) -> {ok, IoDevice} = file:open(XdbFileName, [read, binary]), {ok, HeaderBin} = file:read(IoDevice, ?XDB_HEADER_SIZE), {ok, Header} = ip2region_xdb:parse_header(HeaderBin), - Version = resolve_version(Header), - SegmentIndexSize = ip2region_xdb:segment_index_size(Version), - load_vector_index(IoDevice, Version), - {ok, #state{ - xdb_fd = IoDevice, - version = Version, - segment_index_size = SegmentIndexSize - }}. + case resolve_version(Header) of + {ok, Version} -> + load_vector_index(IoDevice, Version), + {ok, #state{xdb_fd = IoDevice, version = Version}}; + {error, Reason} -> + file:close(IoDevice), + {stop, Reason} + end. handle_call(Request, From, State) -> try @@ -137,18 +136,18 @@ do_info(Info, State) -> resolve_version(Header) -> case ip2region_xdb:header_version(Header) of - 2 -> ipv4; + 2 -> {ok, ipv4}; 3 -> case ip2region_xdb:header_ip_version(Header) of - ?IP_VERSION_4 -> ipv4; - ?IP_VERSION_6 -> ipv6; - _ -> ipv4 + ?IP_VERSION_4 -> {ok, ipv4}; + ?IP_VERSION_6 -> {ok, ipv6}; + V -> {error, {invalid_xdb_ip_version, V}} end; - _ -> ipv4 + V -> {error, {invalid_xdb_version, V}} end. load_vector_index(IoDevice, Version) -> - Table = vector_index_table(Version), + Table = ip2region_xdb:vector_index_table(Version), case ets:info(Table, size) of undefined -> Opts = [named_table, set, public, {read_concurrency, true}, {keypos, 1}], @@ -162,7 +161,7 @@ load_vector_index(IoDevice, Version) -> load_vector_index_data(IoDevice, Table) -> {ok, VectorIndexBin} = - file:read(IoDevice, ?XDB_VECTOR_INDEX_COUNT * 8), + file:read(IoDevice, ?XDB_VECTOR_INDEX_COUNT * ?XDB_VECTOR_INDEX_SIZE), load_vector_index_aux(VectorIndexBin, 0, Table). load_vector_index_aux(<<>>, _Index, _Table) -> ok; @@ -170,24 +169,15 @@ load_vector_index_aux(<>, ets:insert(Table, {Index, SPtr, EPtr}), load_vector_index_aux(VectorIndexBin, Index + 1, Table). -search_ip(IoDevice, IpInt, State) when is_integer(IpInt) -> - search_ip(IoDevice, <>, State); -search_ip(IoDevice, Ip, #state{version = Version, segment_index_size = SegSize}) -> - CacheTable = cache_table(Version), - VectorTable = vector_index_table(Version), - SegmentTable = segment_index_table(Version), - case ets:lookup(CacheTable, Ip) of - [{_, RegionInfo}] -> - RegionInfo; - _ -> - <> = Ip, - VectorIdx = A * ?XDB_VECTOR_COLS + B, - [{_, SPtr, EPtr}] = ets:lookup(VectorTable, VectorIdx), - RegionInfo = search_ip(IoDevice, Ip, SPtr, EPtr, 0, - (EPtr - SPtr) div SegSize, SegSize, Version, SegmentTable), - ets:insert_new(CacheTable, {Ip, RegionInfo}), - RegionInfo - end. +search_ip(IoDevice, Ip, #state{version = Version}) -> + SegSize = ip2region_xdb:segment_index_size(Version), + VectorTable = ip2region_xdb:vector_index_table(Version), + SegmentTable = ip2region_xdb:segment_index_table(Version), + <> = Ip, + VectorIdx = A * ?XDB_VECTOR_COLS + B, + [{_, SPtr, EPtr}] = ets:lookup(VectorTable, VectorIdx), + search_ip(IoDevice, Ip, SPtr, EPtr, 0, + (EPtr - SPtr) div SegSize, SegSize, Version, SegmentTable). search_ip(IoDevice, Ip, SPtr, EPtr, Low, High, SegSize, Version, SegmentTable) when Low =< High -> Middle = (Low + High) bsr 1, @@ -240,12 +230,3 @@ decode_segment_index(Bin, ?XDB_SEGMENT_INDEX_SIZE_V4) -> decode_segment_index(Bin, ?XDB_SEGMENT_INDEX_SIZE_V6) -> <> = Bin, {SIp, EIp, DataLen, DataPtr}. - -vector_index_table(ipv4) -> ?XDB_VECTOR_INDEX_V4; -vector_index_table(ipv6) -> ?XDB_VECTOR_INDEX_V6. - -segment_index_table(ipv4) -> ?XDB_SEGMENT_INDEX_V4; -segment_index_table(ipv6) -> ?XDB_SEGMENT_INDEX_V6. - -cache_table(ipv4) -> ?IP2REGION_CACHE_V4; -cache_table(ipv6) -> ?IP2REGION_CACHE_V6. diff --git a/binding/erlang/src/ip2region_xdb.erl b/binding/erlang/src/ip2region_xdb.erl index fc0f14e..eea6999 100644 --- a/binding/erlang/src/ip2region_xdb.erl +++ b/binding/erlang/src/ip2region_xdb.erl @@ -14,6 +14,7 @@ header_start_index_ptr/1, header_end_index_ptr/1, header_ip_version/1, header_runtime_ptr_bytes/1]). -export([segment_index_size/1]). +-export([vector_index_table/1, segment_index_table/1, cache_table/1]). -include("ip2region.hrl"). @@ -55,3 +56,12 @@ header_runtime_ptr_bytes(#xdb_header{runtime_ptr_bytes = B}) -> B. -spec segment_index_size(ipv4 | ipv6) -> pos_integer(). segment_index_size(ipv4) -> ?XDB_SEGMENT_INDEX_SIZE_V4; segment_index_size(ipv6) -> ?XDB_SEGMENT_INDEX_SIZE_V6. + +vector_index_table(ipv4) -> ?XDB_VECTOR_INDEX_V4; +vector_index_table(ipv6) -> ?XDB_VECTOR_INDEX_V6. + +segment_index_table(ipv4) -> ?XDB_SEGMENT_INDEX_V4; +segment_index_table(ipv6) -> ?XDB_SEGMENT_INDEX_V6. + +cache_table(ipv4) -> ?IP2REGION_CACHE_V4; +cache_table(ipv6) -> ?IP2REGION_CACHE_V6. diff --git a/binding/erlang/src/xdb.erl b/binding/erlang/src/xdb.erl index d7e7b4a..d160382 100644 --- a/binding/erlang/src/xdb.erl +++ b/binding/erlang/src/xdb.erl @@ -13,7 +13,7 @@ -export([search/1]). -spec search(Ip :: tuple() | list() | binary() | integer()) -> - Result :: list() | {error, Reason::atom()}. + Result :: list() | {error, term()}. search(Ip) -> case ip2region_util:ip_to_bytes(Ip) of {ok, ipv4, IpBin} -> @@ -25,23 +25,22 @@ search(Ip) -> end. do_search(PoolName, Version, IpBin) -> - CacheTable = cache_table(Version), + CacheTable = ip2region_xdb:cache_table(Version), case ets:lookup(CacheTable, IpBin) of [{_, Region}] -> Region; _ -> Worker = poolboy:checkout(PoolName, true, infinity), try - ip2region_worker:search(Worker, IpBin) + Region = ip2region_worker:search(Worker, IpBin), + ets:insert(CacheTable, {IpBin, Region}), + Region after poolboy:checkin(PoolName, Worker) end end. v4_pool() -> - case whereis(?IP2REGION_POOL) of - Pid when is_pid(Pid) -> ?IP2REGION_POOL; - undefined -> ?IP2REGION_POOL_V4 + case application:get_env(?APP_NAME, v4_pool_name) of + {ok, PoolName} -> PoolName; + undefined -> ?IP2REGION_POOL end. - -cache_table(ipv4) -> ?IP2REGION_CACHE_V4; -cache_table(ipv6) -> ?IP2REGION_CACHE_V6. diff --git a/binding/erlang/src/xdb_benchmark.erl b/binding/erlang/src/xdb_benchmark.erl index e5e5ad1..0aa22db 100644 --- a/binding/erlang/src/xdb_benchmark.erl +++ b/binding/erlang/src/xdb_benchmark.erl @@ -46,7 +46,7 @@ cpu_info() -> {unix, linux} -> Model = linux_cpu_field("model name"), Clock = format_clock_mhz(linux_cpu_field("cpu MHz")), - Cores = trim(os:cmd("grep -c '^processor' /proc/cpuinfo 2>/dev/null")), + Cores = string:trim(os:cmd("grep -c '^processor' /proc/cpuinfo 2>/dev/null")), Threads = Cores, {Model, Clock, Cores, Threads}; _ -> @@ -54,28 +54,28 @@ cpu_info() -> end. sysctl(Key) -> - trim(os:cmd("sysctl -n " ++ Key ++ " 2>/dev/null")). + string:trim(os:cmd("sysctl -n " ++ Key ++ " 2>/dev/null")). linux_cpu_field(Key) -> Cmd = "grep -m1 '^" ++ Key ++ "' /proc/cpuinfo 2>/dev/null | cut -d: -f2- | sed 's/^ *//'", - trim(os:cmd(Cmd)). + string:trim(os:cmd(Cmd)). first_non_empty(["" | Rest]) -> first_non_empty(Rest); first_non_empty([Val | _]) -> Val; first_non_empty([]) -> "". format_clock(HzStr) -> - case string:to_integer(trim(HzStr)) of + case string:to_integer(string:trim(HzStr)) of {ok, Hz, _} when Hz > 1000000000 -> lists:flatten(io_lib:format("~.2f GHz", [Hz / 1000000000])); {ok, Hz, _} when Hz > 1000000 -> - lists:flatten(io_lib:format("~.2f GHz", [Hz / 1000000000])); + lists:flatten(io_lib:format("~.2f MHz", [Hz / 1000000])); _ -> "" end. format_clock_mhz(MhzStr) -> - case string:to_float(trim(MhzStr)) of + case string:to_float(string:trim(MhzStr)) of {ok, Mhz, _} -> lists:flatten(io_lib:format("~.3f GHz", [Mhz / 1000])); _ -> @@ -108,8 +108,8 @@ load_test_data(Fd, IpList) -> run(IpList) -> garbage_collect(), io:format("~nBenchmarks:~n", []), - run_test("file", IpList), - run_test("cache", IpList), + run_test("cold", IpList), + run_test("warm", IpList), io:format("~nDone.~n", []). run_test(Label, IpList) -> @@ -128,5 +128,3 @@ run_test_aux([]) -> ok; run_test_aux([Ip | Tail]) -> xdb:search(Ip), run_test_aux(Tail). - -trim(Str) -> string:trim(Str). diff --git a/binding/erlang/test/ip2region_sup_test.erl b/binding/erlang/test/ip2region_sup_test.erl index d7a9c6f..3ed6162 100644 --- a/binding/erlang/test/ip2region_sup_test.erl +++ b/binding/erlang/test/ip2region_sup_test.erl @@ -6,8 +6,11 @@ pools_started_test() -> application:stop(ip2region), application:unload(ip2region), ok = application:load(ip2region), - {ok, Cwd} = file:get_cwd(), - V6File = filename:join([Cwd, "..", "..", "data", "ip2region_v6.xdb"]), + TestDir = filename:dirname(?FILE), + ErlangDir = filename:dirname(TestDir), + BindingDir = filename:dirname(ErlangDir), + RepoRoot = filename:dirname(BindingDir), + V6File = filename:join([RepoRoot, "data", "ip2region_v6.xdb"]), ok = application:set_env(ip2region, db, [ {ipv4, "ip2region.xdb"}, {ipv6, V6File} diff --git a/binding/erlang/test/ip2region_util_test.erl b/binding/erlang/test/ip2region_util_test.erl index 200f0a0..ff8d0f2 100644 --- a/binding/erlang/test/ip2region_util_test.erl +++ b/binding/erlang/test/ip2region_util_test.erl @@ -30,7 +30,11 @@ invalid_inputs_test_() -> ?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes("xxx.0.8.0")), ?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes("::ggg")), ?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes({1,2,3})), - ?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes(atom)) + ?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes(atom)), + ?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes(-1)), + ?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes(16#100000000)), + ?_assertEqual({error, bad_ip_format}, ip2region_util:ipv4_to_n(-1)), + ?_assertEqual({error, bad_ip_format}, ip2region_util:ipv4_to_n(16#100000000)) ]. legacy_ipv4_to_n_test_() ->