169 lines
5.9 KiB
Erlang
169 lines
5.9 KiB
Erlang
%%%-------------------------------------------------------------------
|
||
%%% @author anlicheng
|
||
%%% @copyright (C) 2024, <COMPANY>
|
||
%%% @doc
|
||
%%%
|
||
%%% @end
|
||
%%% Created : 11. 3月 2024 11:10
|
||
%%%-------------------------------------------------------------------
|
||
-module(sdlan_util).
|
||
-author("anlicheng").
|
||
|
||
%% API
|
||
-export([rand_byte/1, md5/1, format_mac/1, assert_call/2, mac_str_to_bin/1]).
|
||
-export([json_data/1, json_error/2]).
|
||
-export([is_broadcast_mac/1, is_multicast_mac/1]).
|
||
-export([ipv4_to_int/1, int_to_ipv4/1, ips/2, format_ip/1]).
|
||
-export([ipv6_to_bytes/1, ipv6_bytes_to_binary/1, ipv6_assist_info/0]).
|
||
-export([hmac/2, term_to_binary/1]).
|
||
|
||
-spec format_mac(Mac :: binary()) -> binary().
|
||
format_mac(Mac) when is_binary(Mac) ->
|
||
Hex = fun
|
||
(N) when N < 10 ->
|
||
$0 + N;
|
||
(N) ->
|
||
$a + (N - 10)
|
||
end,
|
||
Y = [[Hex(X0), Hex(X1)] || <<X0:4, X1:4>> <= Mac],
|
||
list_to_binary(lists:flatten(lists:join(":", Y))).
|
||
|
||
-spec mac_str_to_bin(MacBin :: binary()) -> binary().
|
||
mac_str_to_bin(MacBin) when is_binary(MacBin) ->
|
||
% 过滤掉 : 和 -,只保留十六进制字符
|
||
HexBin = binary:replace(MacBin, <<":">>, <<>>, [global]),
|
||
HexBin2 = binary:replace(HexBin, <<"-">>, <<>>, [global]),
|
||
% 解码为 6 字节 MAC 二进制
|
||
binary:decode_hex(HexBin2).
|
||
|
||
%% 生成随机字节
|
||
-spec rand_byte(Num :: pos_integer()) -> binary().
|
||
rand_byte(Num) when is_integer(Num), Num > 0 ->
|
||
crypto:strong_rand_bytes(Num).
|
||
|
||
%% md5哈希算法
|
||
-spec md5(string() | binary()) -> string().
|
||
md5(Str) when is_binary(Str) ->
|
||
md5(binary_to_list(Str));
|
||
md5(Str) when is_list(Str) ->
|
||
Hash = binary_to_list(erlang:md5(Str)),
|
||
lists:flatten([hex(I) || I <- Hash]).
|
||
|
||
hex(I) when I > 16#f ->
|
||
[hex0((I band 16#f0) bsr 4), hex0(I band 16#0f)];
|
||
hex(I) ->
|
||
[$0, hex0(I)].
|
||
hex0(10) -> $a;
|
||
hex0(11) -> $b;
|
||
hex0(12) -> $c;
|
||
hex0(13) -> $d;
|
||
hex0(14) -> $e;
|
||
hex0(15) -> $f;
|
||
hex0(I) -> $0 + I.
|
||
|
||
-spec json_data(Data :: term()) -> iodata().
|
||
json_data(Data) ->
|
||
json:encode(#{<<"result">> => Data}).
|
||
|
||
-spec json_error(ErrCode :: integer(), ErrMessage :: binary()) -> iodata().
|
||
json_error(ErrCode, ErrMessage) when is_integer(ErrCode), is_binary(ErrMessage) ->
|
||
json:encode(#{<<"error">> => #{<<"code">> => ErrCode, <<"message">> => ErrMessage}}).
|
||
|
||
-spec assert_call(Condition :: boolean(), F :: fun(() -> T)) -> T | ok.
|
||
assert_call(true, F) ->
|
||
F();
|
||
assert_call(false, _) ->
|
||
ok.
|
||
|
||
-spec is_broadcast_mac(Mac :: binary()) -> boolean().
|
||
is_broadcast_mac(Mac) when is_binary(Mac) ->
|
||
Mac =:= <<16#FF,16#FF,16#FF,16#FF,16#FF,16#FF>>.
|
||
|
||
-spec is_multicast_mac(Mac :: binary()) -> boolean().
|
||
is_multicast_mac(Mac) when is_binary(Mac) ->
|
||
binary:part(Mac, 0, 3) =:= <<16#01,16#00,16#5E>>.
|
||
|
||
|
||
-spec format_ip(Ip :: term()) -> term().
|
||
format_ip(Ip) when is_integer(Ip) ->
|
||
int_to_ipv4(Ip);
|
||
format_ip(Ip) ->
|
||
Ip.
|
||
|
||
-spec ipv4_to_int(Ip :: integer() | binary() | inet:ip4_address()) -> integer().
|
||
ipv4_to_int(Ip) when is_integer(Ip) ->
|
||
Ip;
|
||
ipv4_to_int({Ip0, Ip1, Ip2, Ip3}) ->
|
||
<<Ip:32>> = <<Ip0, Ip1, Ip2, Ip3>>,
|
||
Ip;
|
||
ipv4_to_int(Ip) when is_binary(Ip) ->
|
||
Parts0 = binary:split(Ip, <<".">>, [global]),
|
||
Parts = lists:map(fun binary_to_integer/1, Parts0),
|
||
<<IpInt:32>> = iolist_to_binary(Parts),
|
||
IpInt.
|
||
|
||
-spec int_to_ipv4(Ip :: integer()) -> binary().
|
||
int_to_ipv4(Ip) when is_integer(Ip) ->
|
||
<<Ip0, Ip1, Ip2, Ip3>> = <<Ip:32>>,
|
||
<<(integer_to_binary(Ip0))/binary, $., (integer_to_binary(Ip1))/binary, $., (integer_to_binary(Ip2))/binary, $., (integer_to_binary(Ip3))/binary>>.
|
||
|
||
-spec ips(NetAddr :: binary(), MaskLen :: integer()) -> [Ip :: integer()].
|
||
ips(NetAddr, MaskLen) when is_binary(NetAddr), is_integer(MaskLen) ->
|
||
Mask = 16#FFFFFFFF bsr MaskLen,
|
||
Net0 = ipv4_to_int(NetAddr),
|
||
%% 防止网络地址给得不对,比如: "192.168.1.101",
|
||
L = 32 - MaskLen,
|
||
Net = (Net0 bsr L) bsl L,
|
||
lists:map(fun(V) -> Net + V end, lists:seq(1, Mask - 1)).
|
||
|
||
-spec ipv6_to_bytes(Address :: binary() | string() | inet:ip6_address()) -> binary().
|
||
ipv6_to_bytes(Address) when is_binary(Address) ->
|
||
ipv6_to_bytes(binary_to_list(Address));
|
||
ipv6_to_bytes({A, B, C, D, E, F, G, H}) ->
|
||
<<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>;
|
||
ipv6_to_bytes(Address) when is_list(Address) ->
|
||
case inet:parse_ipv6strict_address(string:trim(Address)) of
|
||
{ok, Ip6Address} ->
|
||
ipv6_to_bytes(Ip6Address);
|
||
{error, _} ->
|
||
<<"">>
|
||
end;
|
||
ipv6_to_bytes(_) ->
|
||
<<"">>.
|
||
|
||
-spec ipv6_bytes_to_binary(Bytes :: binary()) -> Bin :: binary().
|
||
ipv6_bytes_to_binary(<<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>) ->
|
||
Segments = [integer_to_list(X, 16) || X <- [A, B, C, D, E, F, G, H]],
|
||
% 填充每个段以确保是4位
|
||
Padded = [string:pad(S, 4, leading, $0) || S <- Segments],
|
||
% 合并成IPv6地址格式,这里没有处理最简化形式的缩写
|
||
iolist_to_binary(lists:flatten(string:join(Padded, ":")));
|
||
ipv6_bytes_to_binary(_) ->
|
||
<<"">>.
|
||
|
||
-spec ipv6_assist_info() -> undefined | {ok, {binary(), integer()}}.
|
||
ipv6_assist_info() ->
|
||
case application:get_env(sdlan, ipv6_assist) of
|
||
{ok, Props} ->
|
||
Port = proplists:get_value(port, Props, 0),
|
||
GlobalAddr6 = proplists:get_value(global_addr6, Props, <<"">>),
|
||
case {Port, ipv6_to_bytes(GlobalAddr6)} of
|
||
{Port0, V6Bytes = <<_:128>>} when is_integer(Port0), Port0 > 0 ->
|
||
{ok, {V6Bytes, Port0}};
|
||
_ ->
|
||
undefined
|
||
end;
|
||
_ ->
|
||
undefined
|
||
end.
|
||
|
||
-spec hmac(Key :: binary(), Data :: binary()) -> string().
|
||
hmac(Key, Data) when is_binary(Key), is_binary(Data) ->
|
||
Digest = crypto:mac(hmac, md5, Key, Data),
|
||
%% 转成十六进制小写字符串,和 PHP hash_hmac 一致
|
||
lists:flatten([io_lib:format("~2.16.0b", [B]) || B <- binary:bin_to_list(Digest)]).
|
||
|
||
-spec term_to_binary(Term :: term()) -> binary().
|
||
term_to_binary(Term) ->
|
||
iolist_to_binary(io_lib:format("~p", [Term])).
|