diff --git a/binding/erlang/src/ip2region_util.erl b/binding/erlang/src/ip2region_util.erl index 0359b85..df892f9 100644 --- a/binding/erlang/src/ip2region_util.erl +++ b/binding/erlang/src/ip2region_util.erl @@ -2,25 +2,65 @@ %% 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. -%% -%% @doc -%% ip2region utils +%% +%% @doc +%% ip2region utils: IPv4/IPv6 parsing and version detection. %% @end %%%------------------------------------------------------------------- -module(ip2region_util). --export([ipv4_to_n/1]). +-export([ipv4_to_n/1, ip_version/1, ip_to_bytes/1]). -ipv4_to_n(IntIp) when is_integer(IntIp) -> IntIp; -ipv4_to_n({A, B, C, D}) -> - <> = <>, - N; -ipv4_to_n(Ip) when is_binary(Ip) -> - ipv4_to_n(binary_to_list(Ip)); -ipv4_to_n(Ip) when is_list(Ip) -> +-spec ip_version(Ip :: tuple() | list() | binary() | integer()) -> + ipv4 | ipv6 | {error, atom()}. +ip_version(Ip) when is_integer(Ip) -> ipv4; +ip_version({_, _, _, _}) -> ipv4; +ip_version({_, _, _, _, _, _, _, _}) -> ipv6; +ip_version(Ip) when is_binary(Ip) -> + ip_version(binary_to_list(Ip)); +ip_version(Ip) when is_list(Ip) -> case inet_parse:address(Ip) of - {ok, Addr} -> - ipv4_to_n(Addr); + {ok, {_, _, _, _}} -> ipv4; + {ok, {_, _, _, _, _, _, _, _}} -> ipv6; + _ -> {error, bad_ip_format} + end; +ip_version(_) -> + {error, bad_ip_format}. + +-spec ip_to_bytes(Ip :: tuple() | list() | binary() | integer()) -> + {ok, ipv4 | ipv6, binary()} | {error, atom()}. +ip_to_bytes(Ip) when is_integer(Ip) -> + {ok, ipv4, <>}; +ip_to_bytes({A, B, C, D}) -> + {ok, ipv4, <>}; +ip_to_bytes({A, B, C, D, E, F, G, H}) -> + {ok, ipv6, <>}; +ip_to_bytes(Ip) when is_binary(Ip) -> + ip_to_bytes(binary_to_list(Ip)); +ip_to_bytes(Ip) when is_list(Ip) -> + case inet_parse:address(Ip) of + {ok, {A, B, C, D}} -> + {ok, ipv4, <>}; + {ok, {A, B, C, D, E, F, G, H}} -> + {ok, ipv6, <>}; _ -> {error, bad_ip_format} - end. \ No newline at end of file + end; +ip_to_bytes(_) -> + {error, bad_ip_format}. + +-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({A, B, C, D}) -> + <> = <>, + N; +ipv4_to_n(Ip) when is_binary(Ip) -> + ipv4_to_n(binary_to_list(Ip)); +ipv4_to_n(Ip) when is_list(Ip) -> + case ip_to_bytes(Ip) of + {ok, ipv4, <>} -> N; + _ -> {error, bad_ip_format} + end; +ipv4_to_n(_) -> + {error, bad_ip_format}. diff --git a/binding/erlang/test/ip2region_util_test.erl b/binding/erlang/test/ip2region_util_test.erl new file mode 100644 index 0000000..200f0a0 --- /dev/null +++ b/binding/erlang/test/ip2region_util_test.erl @@ -0,0 +1,42 @@ +-module(ip2region_util_test). +-include_lib("eunit/include/eunit.hrl"). + +ipv4_inputs_test_() -> + [ + ?_assertEqual({ok, ipv4, <<1,0,8,0>>}, ip2region_util:ip_to_bytes("1.0.8.0")), + ?_assertEqual({ok, ipv4, <<1,0,8,0>>}, ip2region_util:ip_to_bytes(<<"1.0.8.0">>)), + ?_assertEqual({ok, ipv4, <<1,0,8,0>>}, ip2region_util:ip_to_bytes({1,0,8,0})), + ?_assertEqual({ok, ipv4, <<1,0,8,0>>}, ip2region_util:ip_to_bytes(16779264)), + ?_assertEqual(ipv4, ip2region_util:ip_version("1.0.8.0")), + ?_assertEqual(ipv4, ip2region_util:ip_version({1,0,8,0})) + ]. + +ipv6_inputs_test_() -> + [ + ?_assertEqual({ok, ipv6, <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1>>}, + ip2region_util:ip_to_bytes("::1")), + ?_assertEqual({ok, ipv6, <<0,0,0,0,0,0,0,0,0,0,255,255,1,0,8,0>>}, + ip2region_util:ip_to_bytes("::ffff:1.0.8.0")), + ?_assertEqual({ok, ipv6, <<0,0,0,0,0,0,0,0,0,0,255,255,1,0,8,0>>}, + ip2region_util:ip_to_bytes(<<"::ffff:1.0.8.0">>)), + ?_assertEqual({ok, ipv6, <<0,0,0,0,0,0,0,0,0,0,255,255,1,0,8,0>>}, + ip2region_util:ip_to_bytes({0,0,0,0,0,65535,256,2048})), + ?_assertEqual(ipv6, ip2region_util:ip_version("::1")), + ?_assertEqual(ipv6, ip2region_util:ip_version({0,0,0,0,0,0,0,1})) + ]. + +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)) + ]. + +legacy_ipv4_to_n_test_() -> + [ + ?_assertEqual(16779264, ip2region_util:ipv4_to_n("1.0.8.0")), + ?_assertEqual(16779264, ip2region_util:ipv4_to_n(<<"1.0.8.0">>)), + ?_assertEqual(16779264, ip2region_util:ipv4_to_n({1,0,8,0})), + ?_assertEqual(16779264, ip2region_util:ipv4_to_n(16779264)) + ].