%%%============================================================================= %%% Copyright (c) 2012-2019 Lindenbaum GmbH %%% %%% Permission to use, copy, modify, and/or distribute this software for any %%% purpose with or without fee is hereby granted, provided that the above %%% copyright notice and this permission notice appear in all copies. %%% %%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %%% %%% @doc %%% A module providing common utility functions. %%% @end %%%============================================================================= -module(eipmi_util). -export([ normalize/2, format/2, get_env/2, update_val/3, copy_val/3, merge_vals/2, from_bcd_plus/1, from_packed_ascii/1, from_base26/1, get_bool/1, binary_to_string/1, join_nl/1, decode_event_data/4, warn/2 ]). %%%============================================================================= %%% API %%%============================================================================= %%------------------------------------------------------------------------------ %% @doc %% Normalizes a string or a binary to a binary with the specified length %% (in bytes). %% @end %%------------------------------------------------------------------------------ -spec normalize(non_neg_integer(), string() | binary()) -> binary(). normalize(Length, String) when is_list(String) -> normalize(Length, list_to_binary(String)); normalize(Length, Binary) when is_binary(Binary) -> case Length - byte_size(Binary) of 0 -> Binary; PadSize when PadSize < 0 -> erlang:binary_part(Binary, 0, Length); PadSize -> <> end. %%------------------------------------------------------------------------------ %% @doc %% A flattening wrapper for {@link io_lib:format/2}. %% @end %%------------------------------------------------------------------------------ -spec format(string(), [term()]) -> string(). format(Format, Args) -> lists:flatten(io_lib:format(Format, Args)). %%------------------------------------------------------------------------------ %% @doc %% Return the value of a configuration property from the eipmi application %% environment. %% @end %%------------------------------------------------------------------------------ -spec get_env(atom(), term()) -> term(). get_env(Property, Default) -> proplists:get_value(Property, application:get_all_env(eipmi), Default). %%------------------------------------------------------------------------------ %% @doc %% Update the value of a property in a proplist. This will remove all previous %% values associated with the property. %% @end %%------------------------------------------------------------------------------ -spec update_val(atom(), term(), proplists:proplist()) -> proplists:proplist(). update_val(Property, Value, PropList) -> [{Property, Value} | proplists:delete(Property, PropList)]. %%------------------------------------------------------------------------------ %% @doc %% Copies the value of a property from one proplist to another. If the property %% is not found in the original proplist the original destination proplist is %% returned. This will remove all previous values associated with the property. %% @end %%------------------------------------------------------------------------------ -spec copy_val(atom(), proplists:proplist(), proplists:proplist()) -> proplists:proplist(). copy_val(Property, DestPropList, SrcPropList) -> case proplists:get_value(Property, SrcPropList) of undefined -> DestPropList; Value -> update_val(Property, Value, DestPropList) end. %%------------------------------------------------------------------------------ %% @doc %% Merges two proplists. The resulting proplist will preserve all key/values from %% proplist one. Additionally values for missing keys will be added from the %% second proplist. %% @end %%------------------------------------------------------------------------------ -spec merge_vals(proplists:proplist(), proplists:proplist()) -> proplists:proplist(). merge_vals(PropList1, PropList2) -> lists:usort( lists:foldl( fun ({K, V}, Acc) -> update_val(K, V, Acc); (K, Acc) -> update_val(K, true, Acc) end, PropList2, PropList1 ) ). %%------------------------------------------------------------------------------ %% @doc %% Decodes a BCD plus encoded binary into a string. %% @end %%------------------------------------------------------------------------------ -spec from_bcd_plus(bitstring()) -> string(). from_bcd_plus(Bitstring) -> from_bcd_plus(Bitstring, ""). from_bcd_plus(<<>>, Acc) -> lists:reverse(Acc); from_bcd_plus(<<16#0:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$0 | Acc]); from_bcd_plus(<<16#1:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$1 | Acc]); from_bcd_plus(<<16#2:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$2 | Acc]); from_bcd_plus(<<16#3:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$3 | Acc]); from_bcd_plus(<<16#4:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$4 | Acc]); from_bcd_plus(<<16#5:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$5 | Acc]); from_bcd_plus(<<16#6:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$6 | Acc]); from_bcd_plus(<<16#7:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$7 | Acc]); from_bcd_plus(<<16#8:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$8 | Acc]); from_bcd_plus(<<16#9:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$9 | Acc]); from_bcd_plus(<<16#a:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [32 | Acc]); from_bcd_plus(<<16#b:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$- | Acc]); from_bcd_plus(<<16#c:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$. | Acc]); from_bcd_plus(<<16#d:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$: | Acc]); from_bcd_plus(<<16#e:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$, | Acc]); from_bcd_plus(<<16#f:4, Rest/bitstring>>, Acc) -> from_bcd_plus(Rest, [$_ | Acc]). %%------------------------------------------------------------------------------ %% @doc %% Decodes a packed 6bit ASCII encoded binary into a string. %% @end %%------------------------------------------------------------------------------ -spec from_packed_ascii(binary()) -> string(). from_packed_ascii(Binary) -> from_packed_ascii1(Binary, ""). from_packed_ascii1(<<>>, Acc) -> lists:reverse(Acc); from_packed_ascii1(<>, Acc) -> from_packed_ascii2(Carry2, Rest, [Char1 + 32 | Acc]). from_packed_ascii2(Carry2, <>, Acc) -> from_packed_ascii3(Carry3, Rest, [Char2 bsl 2 + Carry2 + 32 | Acc]); from_packed_ascii2(_Carry2, <<>>, Acc) -> lists:reverse(Acc). from_packed_ascii3(Carry3, <>, Acc) -> from_packed_ascii1(Rest, [Char4 + 32 | [Char3 bsl 4 + Carry3 + 32 | Acc]]); from_packed_ascii3(_Carry3, <<>>, Acc) -> lists:reverse(Acc). %%------------------------------------------------------------------------------ %% @doc %% Decodes a string from a base 26 encoded integer where `0' corresponds to %% `A' and `25' corresponds to `Z'. %% @end %%------------------------------------------------------------------------------ -spec from_base26(non_neg_integer()) -> string(). from_base26(Number) -> from_base26(Number, []). from_base26(Number, Acc) when Number < 26 -> [65 + Number | Acc]; from_base26(Number, Acc) -> from_base26((Number div 26) - 1, [65 + Number rem 26 | Acc]). %%------------------------------------------------------------------------------ %% @doc %% Return `false' if given value is `0', `true' otherwise. %% @end %%------------------------------------------------------------------------------ get_bool(0) -> false; get_bool(_) -> true. %%------------------------------------------------------------------------------ %% @doc %% Convert a binary into a string removing all contained zero bytes. %% @end %%------------------------------------------------------------------------------ -spec binary_to_string(binary()) -> string(). binary_to_string(Binary) -> [C || C <- binary_to_list(Binary), C =/= 0]. %%------------------------------------------------------------------------------ %% @doc %% Convert a binary into a string removing all contained zero bytes. %% @end %%------------------------------------------------------------------------------ -spec join_nl([string()]) -> string(). join_nl(StringList) -> string:join(StringList, io_lib:nl()). %%------------------------------------------------------------------------------ %% @doc %% A function to decode event data bytes from SEL or PET events. %% @end %%------------------------------------------------------------------------------ -spec decode_event_data( eipmi_sensor:reading(), eipmi_sensor:type(), 0 | 1, binary() ) -> [eipmi_sensor:value()]. decode_event_data(threshold, Type, Assertion, Data) -> decode_threshold(Type, Assertion, pad_event_data(Data)); decode_event_data(Reading, Type, Assertion, Data) when is_atom(Reading) -> decode_generic(Reading, Type, Assertion, pad_event_data(Data)); decode_event_data(Reading, Type, Assertion, Data) when is_integer(Reading) -> decode_oem(Reading, Type, Assertion, pad_event_data(Data)). %%------------------------------------------------------------------------------ %% @doc %% A logger-aware wrapper to log warnings. %% @end %%------------------------------------------------------------------------------ -spec warn(string(), [term()]) -> ok. -ifdef(OTP_RELEASE). warn(Fmt, Args) -> logger:warning(Fmt, Args). -else. warn(Fmt, Args) -> error_logger:warning_msg(Fmt, Args). -endif. %%%============================================================================= %%% Internal functions %%%============================================================================= %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_threshold(Type, Assertion, <<1:2, 0:2, Offset:4, B2:8, _:8>>) -> eipmi_sensor:get_value(threshold, Type, Offset, Assertion, 16#ff, 16#ff) ++ [{raw_reading, <>}]; decode_threshold(Type, Assertion, <<1:2, 1:2, Offset:4, B2:8, B3:8>>) -> eipmi_sensor:get_value(threshold, Type, Offset, Assertion, 16#ff, 16#ff) ++ [{raw_reading, <>}, {raw_threshold, <>}]; decode_threshold(Type, Assertion, <>) -> Byte2 = case E2 of 0 -> 16#ff; _ -> B2 end, Byte3 = case E3 of 0 -> 16#ff; _ -> B3 end, eipmi_sensor:get_value(threshold, Type, Offset, Assertion, Byte2, Byte3). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_generic( Reading, Type, Assertion, <<1:2, E3:2, Off:4, SOff:4, POff:4, B3:8>> ) -> Severity = maybe_value(severity_value, severity, Type, SOff, 0), Previous = maybe_value(previous_value, Reading, Type, POff, Assertion), decode_generic( Reading, Type, Assertion, <<0:2, E3:2, Off:4, 16#ff:8, B3:8>> ) ++ Severity ++ Previous; decode_generic(Reading, Type, Assertion, <>) -> Byte2 = case E2 of 0 -> 16#ff; _ -> B2 end, Byte3 = case E3 of 0 -> 16#ff; _ -> B3 end, eipmi_sensor:get_value(Reading, Type, Offset, Assertion, Byte2, Byte3). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_oem( Reading, Type, Assertion, <<1:2, E3:2, Off:4, SOff:4, POff:4, B3:8>> ) -> Severity = maybe_value(severity_value, severity, Type, SOff, 0), Previous = maybe_value(previous_value, Reading, Type, POff, Assertion), decode_oem(Reading, Type, Assertion, <<0:2, E3:2, Off:4, 16#ff:8, B3:8>>) ++ Severity ++ Previous; decode_oem(Reading, Type, Assertion, <>) -> Byte2 = case E2 of 2 -> B2; _ -> 16#ff end, Byte3 = case E3 of 2 -> B3; _ -> 16#ff end, eipmi_sensor:get_value(Reading, Type, Offset, Assertion, Byte2, Byte3). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ pad_event_data(Data = <<_:1/binary>>) -> <>; pad_event_data(Data = <<_:2/binary>>) -> <>; pad_event_data(Data = <<_:3/binary>>) -> Data. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ maybe_value(_Tag, _Reading, _Type, 16#f, _Assert) -> []; maybe_value(Tag, Reading, Type, Offset, Assert) -> Vs = eipmi_sensor:get_value(Reading, Type, Offset, Assert, 16#ff, 16#ff), [{Tag, proplists:get_value(sensor_value, Vs)}].