%%%------------------------------------------------------------------- %%% @author anlicheng %%% @copyright (C) 2024, %%% @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)] || <> <= 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, <>). %% 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; ipv4_to_int(Ip) when is_binary(Ip) -> Parts0 = binary:split(Ip, <<".">>, [global]), Parts = lists:map(fun binary_to_integer/1, Parts0), <> = iolist_to_binary(Parts), IpInt. -spec int_to_ipv4(Ip :: integer()) -> binary(). int_to_ipv4(Ip) when is_integer(Ip) -> <> = <>, <<(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}) -> <>; 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(<>) -> 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])).