%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(snmp_pdus).
-define(SNMP_USE_V3, true).
-include("snmp_types.hrl").
-define(VMODULE,"PDUS").
-include("snmp_verbosity.hrl").
%% See RFC1155, RFC1157, RFC1901, RFC1902, RFC1905, RFC2272
%% API
-export([enc_message/1, enc_message_only/1, enc_pdu/1,
enc_varbind/1,
enc_oct_str_tag/1, enc_scoped_pdu/1,
enc_usm_security_parameters/1,
dec_message/1, dec_message_only/1, dec_pdu/1,
dec_scoped_pdu_data/1, dec_scoped_pdu/1,
dec_usm_security_parameters/1,
strip_encrypted_scoped_pdu_data/1,
octet_str_to_bits/1, bits_to_str/1,
get_encoded_length/1]).
%% Returns the number of octets required to encode Length.
get_encoded_length(Length) ->
length(elength(Length)).
dec_message([48 | Bytes]) ->
Bytes2 = get_data_bytes(Bytes),
case dec_snmp_version(Bytes2) of
{'version-3', Rest} ->
dec_rest_v3_msg(Rest);
{Vsn, Rest} -> % 1 or 2
dec_rest_v1_v2_msg(Vsn, Rest)
end.
dec_message_only([48 | Bytes]) ->
Bytes2 = get_data_bytes(Bytes),
case dec_snmp_version(Bytes2) of
{'version-3', Rest} ->
dec_rest_v3_msg_only(Rest);
{Vsn, Rest} -> % 1 or 2
dec_rest_v1_v2_msg_only(Vsn, Rest)
end.
dec_snmp_version(Bytes) ->
case (catch dec_int_tag(Bytes, 10)) of
{error, {bad_integer, BadInt}} ->
exit({bad_version, BadInt});
{SNMPversion, Rest} when is_integer(SNMPversion) andalso is_list(Rest) ->
{dec_snmp_ver(SNMPversion), Rest};
{'EXIT', Reason} ->
exit(Reason)
end.
dec_snmp_ver(0) ->
'version-1';
dec_snmp_ver(1) ->
'version-2';
dec_snmp_ver(3) ->
'version-3';
dec_snmp_ver(Vsn) ->
exit({bad_version, Vsn}).
dec_rest_v1_v2_msg(Vsn, Rest1) ->
{Community, Rest2} = dec_oct_str_tag(Rest1),
PDU = dec_pdu(Rest2),
#message{version = Vsn, vsn_hdr = Community, data = PDU}.
dec_rest_v1_v2_msg_only(Vsn, Rest1) ->
{Community, Rest2} = dec_oct_str_tag(Rest1),
#message{version = Vsn, vsn_hdr = Community, data = Rest2}.
dec_rest_v3_msg_only([48 | Bytes]) -> % starts with header data sequence
{Size, Tail} = dec_len(Bytes),
{HBytes, Bytes1} = split_at(Tail, Size, []),
%% Decode HeaderData
{MsgID, HBytes1} = dec_int_tag(HBytes),
chk_msg_id(MsgID),
{MsgMaxSize, HBytes2} = dec_int_tag(HBytes1),
chk_msg_max_size(MsgMaxSize),
{MsgFlags, HBytes3} = dec_oct_str_tag(HBytes2),
{MsgSecurityModel, []} = dec_int_tag(HBytes3),
chk_msg_sec_model(MsgSecurityModel),
%% Continue with Message
% {MsgSecurityParameters, Bytes2} = dec_oct_str_tag(Bytes1),
[4 | Bytes1a] = Bytes1,
{Size1a, Tail1a} = dec_len(Bytes1a),
{MsgSecurityParameters, Bytes2} = split_at(Tail1a, Size1a, []),
%% [48 , HdrDataLen, HdrData, 4, MsgSecLen, MsgSec, ...]
%% NOTE: HdrDataLen is always so small that one octet is enough to
%% encode its length.
%% MsgSecLen is worse... but for USM, it is small enough for
%% one octet. USM is currently the only secmodel.
%% 1 + 1 + Size + 1 + 1 + Size1a
HdrSize = Size + Size1a + 4,
V3Hdr = #v3_hdr{msgID = MsgID,
msgMaxSize = MsgMaxSize,
msgFlags = MsgFlags, %dec_msg_flags(MsgFlags),
msgSecurityModel = MsgSecurityModel,
msgSecurityParameters = MsgSecurityParameters,
hdr_size = HdrSize},
#message{version = 'version-3', vsn_hdr = V3Hdr, data = Bytes2}.
dec_rest_v3_msg(Bytes) ->
Message = dec_rest_v3_msg_only(Bytes),
Data = Message#message.data,
Message#message{data = dec_scoped_pdu_data(Data)}.
dec_scoped_pdu_data([48 | Bytes]) -> % plaintext
{ScopedPdu, []} = dec_scoped_pdu_notag(Bytes),
ScopedPdu;
dec_scoped_pdu_data([4 | Bytes]) -> % encryptedPDU
{EncryptedPDU, []} = dec_oct_str_notag(Bytes),
EncryptedPDU.
dec_scoped_pdu([48 | Bytes]) ->
element(1, dec_scoped_pdu_notag(Bytes)).
dec_scoped_pdu_notag(Bytes) ->
Bytes1 = get_data_bytes(Bytes),
{ContextEngineID, Bytes2} = dec_oct_str_tag(Bytes1),
{ContextName, Bytes3} = dec_oct_str_tag(Bytes2),
Pdu = dec_pdu(Bytes3),
{#scopedPdu{contextEngineID = ContextEngineID,
contextName = ContextName,
data = Pdu},
[]}.
dec_pdu_tag(160) ->
'get-request';
dec_pdu_tag(161) ->
'get-next-request';
dec_pdu_tag(162) ->
'get-response';
dec_pdu_tag(163) ->
'set-request';
%% 164 SNMPv1 Trap
%% 165 Bulk
dec_pdu_tag(166) ->
'inform-request';
dec_pdu_tag(167) ->
'snmpv2-trap';
dec_pdu_tag(168) ->
report.
dec_pdu([164 | Bytes]) -> % It's a trap
Bytes2 = get_data_bytes(Bytes),
{Enterprise, Rest1} = dec_oid_tag(Bytes2),
{{'IpAddress', AgentAddr}, Rest2} = dec_value(Rest1),
{GenericTrap, Rest3} = dec_int_tag(Rest2),
{SpecificTrap, Rest4} = dec_int_tag(Rest3),
{{'TimeTicks', TimeStamp}, VBBytes} = dec_value(Rest4),
VBs = dec_VBs(VBBytes),
#trappdu{enterprise = Enterprise, agent_addr = AgentAddr,
generic_trap = GenericTrap, specific_trap = SpecificTrap,
time_stamp = TimeStamp, varbinds = VBs};
dec_pdu([165 | Bytes]) -> % Bulk
Bytes2 = get_data_bytes(Bytes),
{RequestID, Rest1} = dec_int_tag(Bytes2),
{NonRepeaters, Rest2} = dec_int_tag(Rest1),
{MaxRepetitions,VBbytes} = dec_int_tag(Rest2),
VBs = dec_VBs(VBbytes),
#pdu{type = 'get-bulk-request', request_id = RequestID,
error_status = NonRepeaters, error_index = MaxRepetitions,
varbinds = VBs};
dec_pdu([PduTag | Bytes]) ->
Type = dec_pdu_tag(PduTag),
Bytes2 = get_data_bytes(Bytes),
{RequestID, Rest1} = dec_int_tag(Bytes2),
{ErrStat, Rest2} = dec_int_tag(Rest1),
ErrStatus = case lists:keysearch(ErrStat, 2, errMsgs()) of
{value, {ErrStatName, _ErrStat}} ->
ErrStatName;
false ->
ErrStat
end,
{ErrIndex, VarbindsBytes} = dec_int_tag(Rest2),
VBs = dec_VBs(VarbindsBytes),
#pdu{type = Type, request_id = RequestID, error_status = ErrStatus,
error_index = ErrIndex, varbinds = VBs}.
dec_VBs([48 | Bytes]) ->
Bytes1 = get_data_bytes(Bytes),
dec_individual_VBs(Bytes1, 1, []).
dec_individual_VBs([], _No, VBs) ->
lists:reverse(VBs);
dec_individual_VBs([48 | Bytes], OrgIndex, AccVBs) ->
{_SizeOfThisVB, Bytes2} = dec_len(Bytes),
{Oid, Rest} = dec_oid_tag(Bytes2),
{{Type, Value}, Rest2} = dec_value(Rest),
% perhaps we should check that we have eaten SizeOfThisVB bytes, but we
% don't consider ourselves to have time for such list traversing stuff.
dec_individual_VBs(Rest2, OrgIndex + 1, [#varbind{oid = Oid,
variabletype = Type,
value = Value,
org_index = OrgIndex}
| AccVBs]).
dec_usm_security_parameters([48 | Bytes1]) ->
{_Len, Bytes2} = dec_len(Bytes1),
{MsgAuthEngineID, Bytes3} = dec_oct_str_tag(Bytes2),
{MsgAuthEngineBoots, Bytes4} = dec_int_tag(Bytes3),
{MsgAuthEngineTime, Bytes5} = dec_int_tag(Bytes4),
{MsgUserName, Bytes6} = dec_oct_str_tag(Bytes5),
{MsgAuthParams, Bytes7} = dec_oct_str_tag(Bytes6),
{MsgPrivParams, []} = dec_oct_str_tag(Bytes7),
#usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID,
msgAuthoritativeEngineBoots = MsgAuthEngineBoots,
msgAuthoritativeEngineTime = MsgAuthEngineTime,
msgUserName = MsgUserName,
msgAuthenticationParameters = MsgAuthParams,
msgPrivacyParameters = MsgPrivParams}.
strip_encrypted_scoped_pdu_data([48 | Bytes]) ->
{Size, Tail} = dec_len(Bytes),
[48 | elength(Size)] ++ strip(Size, Tail).
strip(N, [H|T]) when N > 0 -> [H | strip(N-1, T)];
strip(0, _Tail) ->
[].
%%----------------------------------------------------------------------
%% Returns:{Type, Value}
%%----------------------------------------------------------------------
dec_value([6 | Bytes]) ->
{Value, Rest} = dec_oid_notag(Bytes),
{{'OBJECT IDENTIFIER', Value}, Rest};
dec_value([5,0 | T]) ->
{{'NULL', 'NULL'}, T};
dec_value([2 | Bytes]) ->
{Value, Rest} = dec_integer_notag(Bytes),
{{'INTEGER', Value}, Rest};
dec_value([4 | Bytes]) ->
{Value, Rest} = dec_oct_str_notag(Bytes),
{{'OCTET STRING', Value}, Rest};
dec_value([64 | Bytes]) ->
{Value, Rest} = dec_oct_str_notag(Bytes),
{{'IpAddress', Value}, Rest};
dec_value([65 | Bytes]) ->
{Value, Rest} = dec_integer_notag(Bytes),
if Value >= 0, Value =< 4294967295 ->
{{'Counter32', Value}, Rest};
true ->
exit({error, {bad_counter32, Value}})
end;
dec_value([66 | Bytes]) ->
{Value, Rest} = dec_integer_notag(Bytes),
if Value >= 0, Value =< 4294967295 ->
{{'Unsigned32', Value}, Rest};
true ->
exit({error, {bad_unsigned32, Value}})
end;
dec_value([67 | Bytes]) ->
{Value, Rest} = dec_integer_notag(Bytes),
if Value >= 0, Value =< 4294967295 ->
{{'TimeTicks', Value}, Rest};
true ->
exit({error, {bad_timeticks, Value}})
end;
dec_value([68 | Bytes]) ->
{Value, Rest} = dec_oct_str_notag(Bytes),
{{'Opaque', Value}, Rest};
dec_value([70 | Bytes]) ->
{Value, Rest} = dec_integer_notag(Bytes),
if Value >= 0, Value =< 18446744073709551615 ->
{{'Counter64', Value}, Rest};
true ->
exit({error, {bad_counter64, Value}})
end;
dec_value([128,0|T]) ->
{{'NULL', noSuchObject}, T};
dec_value([129,0|T]) ->
{{'NULL', noSuchInstance}, T};
dec_value([130,0|T]) ->
{{'NULL', endOfMibView}, T}.
%%----------------------------------------------------------------------
%% Purpose: Uses the beginning length bytes to return the actual data.
%% If data has the wrong length, the program is exited.
%% Pre: Tag is removed.
%%----------------------------------------------------------------------
get_data_bytes(Bytes) ->
{Size, Tail} = dec_len(Bytes),
if
length(Tail) =:= Size ->
Tail;
true ->
exit({error, {wrong_length, Bytes}})
end.
split_at(L, 0, Acc) ->
{lists:reverse(Acc), L};
split_at([H|T], N, Acc) ->
split_at(T, N-1, [H|Acc]).
%%----------------------------------------------------------------------
%% All decoding routines return: {Data, RestBytes}
%%----------------------------------------------------------------------
dec_int_tag([2 | Bytes]) ->
dec_integer_notag(Bytes).
dec_int_tag([2 | Bytes], SizeLimit) ->
dec_integer_notag(Bytes, SizeLimit).
dec_integer_notag(Ints) ->
dec_integer_notag(Ints, infinity).
dec_integer_notag(Ints, SizeLimit) ->
case dec_len(Ints) of
{Size, Ints2} when SizeLimit =:= infinity ->
do_dec_integer_notag(Size, Ints2);
{Size, Ints2} when (is_integer(SizeLimit) andalso
(Size =< SizeLimit)) ->
do_dec_integer_notag(Size, Ints2);
{BadSize, _BadInts2} ->
throw({error, {bad_integer, {BadSize, SizeLimit}}})
end.
do_dec_integer_notag(Size, Ints) ->
if hd(Ints) band 128 == 0 -> %% Positive number
dec_pos_int(Ints, Size, 8 * (Size - 1));
true -> %% Negative
dec_neg_int(Ints, Size, 8 * (Size - 1))
end.
dec_pos_int(T, 0, _) -> {0, T};
dec_pos_int([Byte|Tail], Size, Shift) ->
{Int, Rest} = dec_pos_int(Tail, Size - 1, Shift - 8),
{(Byte bsl Shift) bor Int, Rest}.
dec_neg_int(T, 0, _) -> {0, T};
dec_neg_int([Byte|Tail], Size, Shift) ->
{Int, Rest} = dec_pos_int(Tail, Size - 1, Shift-8),
{(-128 + (Byte band 127) bsl Shift) bor Int, Rest}.
dec_oct_str_tag([4 | Bytes]) ->
dec_oct_str_notag(Bytes).
dec_oct_str_notag(Bytes) ->
{Size, Tail} = dec_len(Bytes),
split_at(Tail, Size, []).
dec_oid_tag([6 | Bytes]) ->
dec_oid_notag(Bytes).
dec_oid_notag(Bytes) ->
{Size, [H | Tail]} = dec_len(Bytes),
{Oid, Rest} = dec_oid_elements(Tail, Size - 1, []),
{[H div 40, H rem 40 | Oid], Rest}.
dec_oid_elements(L, 0, Acc) ->
{lists:reverse(Acc), L};
dec_oid_elements([Dig|Tail], Size, Acc) when Dig < 128 ->
dec_oid_elements(Tail, Size - 1, [Dig | Acc]);
dec_oid_elements([Dig|Tail], Size, Acc) ->
{Num, Neaten, Tl} = dec_oid_element(Tail,1,Dig band 127),
dec_oid_elements(Tl, Size - Neaten, [Num|Acc]).
dec_oid_element([Dig|Tail], Neaten, Num) when Dig < 128 ->
{Num*128+Dig,Neaten+1,Tail};
dec_oid_element([Dig|Tail],Neaten, Num) ->
dec_oid_element(Tail, Neaten+1, Num*128 + (Dig band 127)).
chk_msg_id(MsgId) when (MsgId >= 0) andalso (MsgId =< 2147483647) -> ok;
chk_msg_id(MsgId) -> exit({bad_msg_id, MsgId}).
chk_msg_max_size(MMS) when (MMS >= 484) andalso (MMS =< 2147483647) -> ok;
chk_msg_max_size(MMS) -> exit({bad_msg_max_size, MMS}).
chk_msg_sec_model(MsgSecurityModel) when MsgSecurityModel >= 0,
MsgSecurityModel =< 2147483647 -> ok;
chk_msg_sec_model(MsgSecurityModel) ->
exit({bad_msg_sec_model, MsgSecurityModel}).
%%----------------------------------------------------------------------
%% Code copied from the original ASN.1 compiler written by
%% [email protected]
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Returns: {Len, Tail}
%%----------------------------------------------------------------------
dec_len([128|_Tail]) ->
%% indefinite form - not allowed in SNMP
exit({asn1_error, indefinite_length});
dec_len([Hd|Tl]) when Hd >= 0 ->
%% definite form
if
Hd < 128 -> % 8th bit is cleared
%% Short form (actually, we can remove this test, since snmp_pdus
%% performs this test _before_ calling this function)
{Hd,Tl};
true ->
%% Long form
No = Hd band 127, % clear 8th bit
{DigList, Rest} = head(No, Tl),
Size = dec_integer_len(DigList),
{Size, Rest}
end.
dec_integer_len([D]) ->
D;
dec_integer_len([A,B]) ->
(A bsl 8) bor B;
dec_integer_len([A,B,C]) ->
(A bsl 16) bor (B bsl 8) bor C;
%% More than 3 elements for length => either *very* long packet
%% (which we don't handle), or the length is encoded with more octets
%% than necessary (in which case the first octet must be 0).
dec_integer_len([0 | T]) ->
dec_integer_len(T).
%%-----------------------------------------------------------------
%% head(N, List) -> {List1, List2}
%% List == List1 ++ List2
%% length(List1) == N
%%-----------------------------------------------------------------
head(L,List) ->
head(L,List,[]).
head(0,L,Res) ->
{lists:reverse(Res),L};
head(Int,[H|Tail],Res) ->
head(Int-1,Tail,[H|Res]);
head(Int, [], _Res) ->
exit({asn1_error, {bad_length, Int}}).
%%%----------------------------------------------------------------------
%%% ENCODING ENCODING ENCODING ENCODING ENCODING ENCODING ENCODING ENCODING
%%%----------------------------------------------------------------------
enc_message(#message{version = Ver, vsn_hdr = VsnHdr, data = Data}) ->
VerBytes = enc_version(Ver),
Bytes =
case Ver of
'version-3' ->
V3HeaderBytes = enc_v3_header(VsnHdr),
DataBytes = enc_scoped_pdu(Data),
V3HeaderBytes ++ DataBytes;
_ ->
ComBytes = enc_community(VsnHdr),
DataBytes = enc_pdu(Data),
ComBytes ++ DataBytes
end,
Bytes2 = VerBytes ++ Bytes,
Len = elength(length(Bytes2)),
[48 | Len] ++ Bytes2.
enc_message_only(#message{version = Ver, vsn_hdr = VsnHdr, data = DataBytes}) ->
VerBytes = enc_version(Ver),
Bytes =
case Ver of
'version-3' ->
V3HeaderBytes = enc_v3_header(VsnHdr),
V3HeaderBytes ++ DataBytes;
_ ->
ComBytes = enc_community(VsnHdr),
ComBytes ++ DataBytes
end,
Bytes2 = VerBytes ++ Bytes,
Len = elength(length(Bytes2)),
[48 | Len] ++ Bytes2.
enc_version('version-1') ->
[2,1,0];
enc_version('version-2') ->
[2,1,1];
enc_version('version-3') ->
[2,1,3].
enc_community(Com) ->
enc_oct_str_tag(Com).
enc_v3_header(#v3_hdr{msgID = MsgID,
msgMaxSize = MsgMaxSize,
msgFlags = MsgFlags,
msgSecurityModel = MsgSecurityModel,
msgSecurityParameters = MsgSecurityParameters}) ->
Bytes = lists:append([enc_integer_tag(MsgID),
enc_integer_tag(MsgMaxSize),
enc_oct_str_tag(MsgFlags),
enc_integer_tag(MsgSecurityModel)]),
Len = elength(length(Bytes)),
lists:append([[48 | Len], Bytes, enc_oct_str_tag(MsgSecurityParameters)]).
enc_scoped_pdu(#scopedPdu{contextEngineID = ContextEngineID,
contextName = ContextName,
data = Data}) ->
Bytes = lists:append([enc_oct_str_tag(ContextEngineID),
enc_oct_str_tag(ContextName),
enc_pdu(Data)]),
Len = elength(length(Bytes)),
[48 | Len] ++ Bytes.
enc_pdu(PDU) when PDU#pdu.type =:= 'get-request' ->
enc_pdu(160, PDU);
enc_pdu(PDU) when PDU#pdu.type =:= 'get-next-request' ->
enc_pdu(161, PDU);
enc_pdu(PDU) when PDU#pdu.type =:= 'get-response' ->
enc_pdu(162, PDU);
enc_pdu(PDU) when PDU#pdu.type =:= 'set-request' ->
enc_pdu(163, PDU);
enc_pdu(PDU) when PDU#pdu.type =:= 'get-bulk-request' ->
enc_pdu(165, PDU);
enc_pdu(PDU) when PDU#pdu.type =:= 'inform-request' ->
enc_pdu(166, PDU);
enc_pdu(PDU) when PDU#pdu.type =:= 'snmpv2-trap' ->
enc_pdu(167, PDU);
enc_pdu(PDU) when PDU#pdu.type =:= report ->
enc_pdu(168, PDU);
enc_pdu(TrapPDU) when is_record(TrapPDU, trappdu) ->
enc_Trap(TrapPDU).
enc_pdu(Tag,PDU) ->
Bytes2 = enc_pdu2(PDU),
Len2 = elength(length(Bytes2)),
lists:append([Tag | Len2], Bytes2).
enc_pdu2(#pdu{type = Type, request_id = ReqId, error_index = ErrIndex,
error_status = ErrStat, varbinds = VBs}) ->
ReqBytes = enc_integer_tag(ReqId),
Val = err_val(ErrStat,Type),
ErrStatBytes = enc_integer_tag(Val),
ErrIndexBytes = enc_integer_tag(ErrIndex),
VBsBytes = enc_VarBindList(VBs),
lists:append([ReqBytes, ErrStatBytes, ErrIndexBytes, VBsBytes]).
enc_usm_security_parameters(
#usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID,
msgAuthoritativeEngineBoots = MsgAuthEngineBoots,
msgAuthoritativeEngineTime = MsgAuthEngineTime,
msgUserName = MsgUserName,
msgAuthenticationParameters = MsgAuthParams,
msgPrivacyParameters = MsgPrivParams}) ->
Bytes1 = enc_oct_str_tag(MsgAuthEngineID),
Bytes2 = enc_integer_tag(MsgAuthEngineBoots),
Bytes3 = enc_integer_tag(MsgAuthEngineTime),
Bytes4 = enc_oct_str_tag(MsgUserName),
Bytes5 = enc_oct_str_tag(MsgAuthParams),
Bytes6 = enc_oct_str_tag(MsgPrivParams),
Bytes7 = lists:append([Bytes1, Bytes2, Bytes3, Bytes4, Bytes5, Bytes6]),
Len = elength(length(Bytes7)),
[48 | Len] ++ Bytes7.
err_val(Int,'get-bulk-request') when is_integer(Int) -> Int;
err_val(ErrStat, _) ->
{value, {_ErrStat, Val}} = lists:keysearch(ErrStat, 1, errMsgs()),
Val.
errMsgs() ->
[{noError,0},{tooBig,1},{noSuchName,2},
{badValue,3},{readOnly,4},{genErr,5},
%% v2
{noAccess,6},{wrongType,7},{wrongLength,8},{wrongEncoding,9},
{wrongValue,10},{noCreation,11},{inconsistentValue,12},
{resourceUnavailable,13},{commitFailed,14},{undoFailed,15},
{authorizationError,16},{notWritable,17},{inconsistentName,18}].
enc_VarBindList(EncodedVBs) when is_integer(hd(EncodedVBs)) ->
Len1 = elength(length(EncodedVBs)),
lists:append([48 | Len1],EncodedVBs);
enc_VarBindList(VBs) ->
Bytes1 = lists:append(lists:map(fun enc_varbind/1, VBs)),
Len1 = elength(length(Bytes1)),
lists:append([48 | Len1],Bytes1).
enc_varbind(Varbind) ->
Bytes1 = enc_VarBind_attributes(Varbind),
Len1 = elength(length(Bytes1)),
lists:append([48 | Len1],Bytes1).
enc_VarBind_attributes(#varbind{oid = Oid, variabletype = Type,value = Val}) ->
OidBytes = enc_oid_tag(Oid),
ValueBytes = enc_value(Type, Val),
lists:append(OidBytes, ValueBytes).
enc_value('INTEGER',Val) ->
enc_integer_tag(Val);
enc_value('OCTET STRING', Val) ->
enc_oct_str_tag(Val);
enc_value('BITS', Val) ->
enc_oct_str_tag(bits_to_str(Val));
enc_value('OBJECT IDENTIFIER', Val) ->
enc_oid_tag(Val);
enc_value('IpAddress',Val) ->
Bytes2 = enc_oct_str_notag(Val),
Len2 = elength(length(Bytes2)),
lists:append([64 | Len2],Bytes2);
enc_value('Opaque', Val) ->
Bytes2 = enc_oct_str_notag(Val),
Len2 = elength(length(Bytes2)),
lists:append([68 | Len2],Bytes2);
enc_value(_Type, noSuchObject) ->
[128,0];
enc_value(_Type, noSuchInstance) ->
[129,0];
enc_value(_Type, endOfMibView) ->
[130,0];
enc_value('NULL', _Val) ->
[5,0];
enc_value(Type, Val) ->
Bytes2 = enc_integer_notag(Val),
Len2 = elength(length(Bytes2)),
lists:append([enc_val_tag(Type,Val) | Len2],Bytes2).
enc_val_tag('Counter32',Val) when (Val >= 0) andalso (Val =< 4294967295) ->
65;
enc_val_tag('Unsigned32', Val) when (Val >= 0) andalso (Val =< 4294967295) ->
66;
enc_val_tag('TimeTicks', Val) when (Val >= 0) andalso (Val =< 4294967295) ->
67;
enc_val_tag('Counter64', Val) when ((Val >= 0) andalso
(Val =< 18446744073709551615)) ->
70.
%%----------------------------------------------------------------------
%% Impl according to RFC1906, section 8
%% For example: the number 1010 0000 (=160) 0100 0001 (=65) is represented as
%% the octet string: 1000 0010, 0000 0101 (=[130,5])
%%----------------------------------------------------------------------
bits_to_str(0) -> "";
bits_to_str(Int) ->
[rev_int8(Int band 255) | bits_to_str(Int div 256)].
rev_int8(Val) ->
rev_int(Val,0,1,128).
rev_int(_Val,Res,256,0) -> Res;
rev_int(Val,Res,OldBit,NewBit) when Val band OldBit =/= 0 ->
rev_int(Val,Res+NewBit,OldBit*2,NewBit div 2);
rev_int(Val,Res,OldBit,NewBit) ->
rev_int(Val,Res,OldBit*2,NewBit div 2).
octet_str_to_bits(Str) ->
octet_str_to_bits(Str,1).
octet_str_to_bits("",_) -> 0;
octet_str_to_bits([Byte|Bytes],Mul) ->
Mul*rev_int8(Byte)+octet_str_to_bits(Bytes,Mul*256).
enc_Trap(TrapPdu) when is_record(TrapPdu, trappdu) ->
Bytes1 = enc_trap_data(TrapPdu),
Len1 = elength(length(Bytes1)),
lists:append([164 | Len1],Bytes1).
enc_trap_data(#trappdu{enterprise = Enterprise,
agent_addr = AgentAddr,
generic_trap = GenericTrap,
specific_trap = SpecificTrap,
time_stamp = TimeStamp,
varbinds = VBs}) ->
L1 = enc_oid_tag(Enterprise),
L2 = enc_value('IpAddress', AgentAddr),
L3 = enc_integer_tag(GenericTrap),
L4 = enc_integer_tag(SpecificTrap),
L5 = enc_value('TimeTicks', TimeStamp),
L6 = enc_VarBindList(VBs),
lists:append([L1,L2,L3,L4,L5,L6]).
enc_oid_tag([E1,E2|RestOid]) when E1 * 40 + E2 =< 255 ->
Head = 40*E1 + E2, % weird
Res = e_object_elements(RestOid, []),
lists:append([6 | elength(length(Res) + 1)],[Head|Res]).
e_object_elements([Num | T], Res) ->
e_object_elements(T, lists:append(e_object_element(Num),Res));
e_object_elements([], Res) -> lists:reverse(Res).
%%----------------------------------------------------------------------
%% The reversed encoding for an oid-element
%%----------------------------------------------------------------------
e_object_element(Num) when Num > 0 ->
[Last|T] = e_object_element2(Num),
[Last-128|T];
e_object_element(0) -> [0].
e_object_element2(Num) when Num > 0 ->
Byte = (Num rem 128),
[128+Byte | e_object_element2((Num-Byte) div 128)];
e_object_element2(0) -> [].
enc_integer_tag(Val) when Val >= 0 -> %% stdcase positive ints
Bytes = eint(Val,[]),
[2 | elength(length(Bytes))] ++ Bytes;
enc_integer_tag(Val) -> %% It's a negative number
Bytes = enint(Val,[]),
[2 | elength(length(Bytes))] ++ Bytes.
enc_integer_notag(Val) when Val >= 0 -> %% stdcase positive ints
eint(Val,[]);
enc_integer_notag(Val) -> %% It's a negative number
enint(Val,[]).
eint(0, [B|Acc]) when B < 128 ->
[B|Acc];
eint(N, Acc) ->
eint(N bsr 8, [N band 16#ff| Acc]).
enint(-1, [B1|T]) when B1 > 127 ->
[B1|T];
enint(N, Acc) ->
enint(N bsr 8, [N band 16#ff|Acc]).
enc_oct_str_tag(OStr) when is_list(OStr) ->
lists:append([4|elength(length(OStr))],OStr);
enc_oct_str_tag(OBin) ->
[4 | elength(size(OBin))] ++ binary_to_list(OBin).
enc_oct_str_notag(OStr) -> OStr.
%%-----------------------------------------------------------------
%% Always use definite form
%%-----------------------------------------------------------------
%% Short
elength(L) when L < 127 ->
[L];
%% 3 cases of long form
elength(L) when L =< 16#FF ->
[2#10000001,L];
elength(L) when L =< 16#FFFF ->
[2#10000010,(L bsr 8),(L band 16#FF)];
elength(L) when L =< 16#7FFFFF ->
[2#10000011,(L bsr 16),((L band 16#FF00) bsr 8), (L band 16#FF)].