sdlan/src/sdlan_util.erl
2026-04-27 16:43:33 +08:00

167 lines
5.7 KiB
Erlang
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

%%%-------------------------------------------------------------------
%%% @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).
%% 生成随机字节
rand_byte(Num) when is_integer(Num), Num > 0 ->
rand_byte0(Num, <<>>).
rand_byte0(0, Acc) ->
Acc;
rand_byte0(Num, Acc) ->
Byte = ceil(rand:uniform() * 255),
rand_byte0(Num - 1, <<Acc/binary, Byte>>).
%% 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.
json_data(Data) ->
jiffy:encode(#{<<"result">> => Data}, [force_utf8]).
json_error(ErrCode, ErrMessage) when is_integer(ErrCode), is_binary(ErrMessage) ->
jiffy:encode(#{<<"error">> => #{<<"code">> => ErrCode, <<"message">> => ErrMessage}}, [force_utf8]).
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>>.
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)]).
term_to_binary(Term) ->
iolist_to_binary(io_lib:format("~p", [Term])).