diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/snmp/test/snmp_test_mgr.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/snmp/test/snmp_test_mgr.erl')
-rw-r--r-- | lib/snmp/test/snmp_test_mgr.erl | 1139 |
1 files changed, 1139 insertions, 0 deletions
diff --git a/lib/snmp/test/snmp_test_mgr.erl b/lib/snmp/test/snmp_test_mgr.erl new file mode 100644 index 0000000000..085dc8600f --- /dev/null +++ b/lib/snmp/test/snmp_test_mgr.erl @@ -0,0 +1,1139 @@ +%% +%% %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_test_mgr). + +%%---------------------------------------------------------------------- +%% This module implements a simple SNMP manager for Erlang. +%%---------------------------------------------------------------------- + +%% c(snmp_test_mgr). +%% snmp_test_mgr:start(). +%% snmp_test_mgr:g([[sysContact,0]]). + +%% snmp_test_mgr:start([{engine_id, "mbjk's engine"}, v3, {agent, "clip"}, {mibs, ["../mibs/SNMPv2-MIB"]}]). + +%% snmp_test_mgr:start([{engine_id, "agentEngine"}, {user, "iwl_test"}, {dir, "mgr_conf"}, {sec_level, authPriv}, v3, {agent, "clip"}]). + +%% User interface +-export([start_link/1, start/1, stop/0, + d/0, discovery/0, + g/1, s/1, gn/1, gn/0, r/0, gb/3, rpl/1, + send_bytes/1, + expect/2,expect/3,expect/4,expect/6,get_response/2, + receive_response/0, + purify_oid/1, + oid_to_name/1, name_to_oid/1]). + +%% Internal exports +-export([get_oid_from_varbind/1, + var_and_value_to_varbind/2, flatten_oid/2, make_vb/1]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). + +-include_lib("snmp/include/snmp_types.hrl"). +-include_lib("snmp/include/STANDARD-MIB.hrl"). + +-record(state,{dbg = true, + quiet, + parent, + timeout = 3500, + print_traps = true, + mini_mib, + packet_server, + last_sent_pdu, + last_received_pdu}). + +-define(SERVER, ?MODULE). +-define(PACK_SERV, snmp_test_mgr_misc). + +start_link(Options) -> + gen_server:start_link({local, ?SERVER}, ?MODULE, {Options, self()}, []). + +start(Options) -> + gen_server:start({local, ?SERVER}, ?MODULE, {Options, self()}, []). + +stop() -> + call(stop). + +d() -> + discovery(). + +discovery() -> + call(discovery). + +g(Oids) -> + cast({get, Oids}). + +%% VarsAndValues is: {PlainOid, o|s|i, Value} (unknown mibs) | {Oid, Value} +s(VarsAndValues) -> + cast({set, VarsAndValues}). + +gn(Oids) when is_list(Oids) -> + cast({get_next, Oids}); +gn(N) when is_integer(N) -> + cast({iter_get_next, N}). +gn() -> + cast(iter_get_next). + +r() -> + cast(resend_pdu). + +gb(NonRepeaters, MaxRepetitions, Oids) -> + cast({bulk, {NonRepeaters, MaxRepetitions, Oids}}). + +rpl(RespPdu) -> + cast({response, RespPdu}). + +send_bytes(Bytes) -> + cast({send_bytes, Bytes}). + +purify_oid(Oid) -> + call({purify_oid, Oid}, 5000). + +oid_to_name(Oid) -> + call({oid_to_name, Oid}, 5000). + +name_to_oid(Name) -> + call({name_to_oid, Name}, 5000). + + +%%---------------------------------------------------------------------- +%% Purpose: For writing test sequences +%% Args: Y=any (varbinds) | trap | timeout | VarBinds | ErrStatus +%% Returns: ok|{error, Id, Reason} +%%---------------------------------------------------------------------- +expect(Id,Y) -> echo_errors(expect_impl(Id,Y)). +expect(Id,v2trap,VBs) -> echo_errors(expect_impl(Id,v2trap,VBs)); +expect(Id,report,VBs) -> echo_errors(expect_impl(Id,report,VBs)); +expect(Id,{inform, Reply},VBs) -> + echo_errors(expect_impl(Id,{inform,Reply},VBs)). +expect(Id,Err,Idx,VBs) -> echo_errors(expect_impl(Id,Err,Idx,VBs)). +expect(Id,trap, Enterp, Generic, Specific, ExpectedVarbinds) -> + echo_errors(expect_impl(Id,trap,Enterp,Generic, + Specific,ExpectedVarbinds)). + +%%----------------------------------------------------------------- +%% Purpose: For writing test sequences +%%----------------------------------------------------------------- +get_response(Id, Vars) -> echo_errors(get_response_impl(Id, Vars)). + +%%---------------------------------------------------------------------- +%% Receives a response from the agent. +%% Returns: a PDU or {error, Reason}. +%% It doesn't receive traps though. +%%---------------------------------------------------------------------- +receive_response() -> + receive_response(get_timeout()). + +receive_response(Timeout) -> + d("await response within ~w ms",[Timeout]), + receive + {snmp_pdu, PDU} when is_record(PDU, pdu) -> + d("received PDU: ~n\t~p",[PDU]), + PDU + after Timeout -> + d("response timeout",[]), + {error, timeout} + end. + + +get_timeout() -> + case get(receive_response_timeout) of + Int when is_integer(Int) and (Int > 0) -> + Int; + _ -> + get_timeout(os:type()) + end. + +get_timeout(vxworks) -> 7000; +get_timeout(_) -> 3500. + +%%---------------------------------------------------------------------- +%% Receives a trap from the agent. +%% Returns: TrapPdu|{error, Reason} +%%---------------------------------------------------------------------- +receive_trap(Timeout) -> + d("await trap within ~w ms",[Timeout]), + receive + {snmp_pdu, PDU} when is_record(PDU, trappdu) -> + d("received trap-PDU: ~n\t~p",[PDU]), + PDU + after Timeout -> + d("trap timeout",[]), + {error, timeout} + end. + +%%---------------------------------------------------------------------- +%% Options: List of +%% {agent_udp, UDPPort}, {agent, Agent} +%% Optional: +%% {community, String ("public" is default}, quiet, +%% {mibs, List of Filenames}, {trap_udp, UDPPort (default 5000)}, +%%---------------------------------------------------------------------- +init({Options, CallerPid}) -> + put(sname, mgr), + put(verbosity, debug), + {A1,A2,A3} = erlang:now(), + random:seed(A1,A2,A3), + case (catch is_options_ok(Options)) of + true -> + put(debug, get_value(debug, Options, false)), + d("init -> (~p) extract options",[self()]), + PacksDbg = get_value(packet_server_debug, Options, false), + io:format("[~w] ~p -> PacksDbg: ~p~n", [?MODULE, self(), PacksDbg]), + RecBufSz = get_value(recbuf, Options, 1024), + io:format("[~w] ~p -> RecBufSz: ~p~n", [?MODULE, self(), RecBufSz]), + Mibs = get_value(mibs, Options, []), + io:format("[~w] ~p -> Mibs: ~p~n", [?MODULE, self(), Mibs]), + Udp = get_value(agent_udp, Options, 4000), + io:format("[~w] ~p -> Udp: ~p~n", [?MODULE, self(), Udp]), + User = get_value(user, Options, "initial"), + io:format("[~w] ~p -> User: ~p~n", [?MODULE, self(), User]), + EngineId = get_value(engine_id, Options, "agentEngine"), + io:format("[~w] ~p -> EngineId: ~p~n", [?MODULE, self(), EngineId]), + CtxEngineId = get_value(context_engine_id, Options, EngineId), + io:format("[~w] ~p -> CtxEngineId: ~p~n", [?MODULE, self(), CtxEngineId]), + TrapUdp = get_value(trap_udp, Options, 5000), + io:format("[~w] ~p -> TrapUdp: ~p~n", [?MODULE, self(), TrapUdp]), + Dir = get_value(dir, Options, "."), + io:format("[~w] ~p -> Dir: ~p~n", [?MODULE, self(), Dir]), + SecLevel = get_value(sec_level, Options, noAuthNoPriv), + io:format("[~w] ~p -> SecLevel: ~p~n", [?MODULE, self(), SecLevel]), + MiniMIB = snmp_mini_mib:create(Mibs), + io:format("[~w] ~p -> MiniMIB: ~p~n", [?MODULE, self(), MiniMIB]), + Version = case lists:member(v2, Options) of + true -> 'version-2'; + false -> + case lists:member(v3, Options) of + true -> 'version-3'; + false -> 'version-1' + end + end, + io:format("[~w] ~p -> Version: ~p~n", [?MODULE, self(), Version]), + Com = case Version of + 'version-3' -> + get_value(context, Options, ""); + _ -> + get_value(community, Options, "public") + end, + io:format("[~w] ~p -> Com: ~p~n", [?MODULE, self(), Com]), + VsnHdrD = + {Com, User, EngineId, CtxEngineId, mk_seclevel(SecLevel)}, + io:format("[~w] ~p -> VsnHdrD: ~p~n", [?MODULE, self(), VsnHdrD]), + AgIp = case snmp_misc:assq(agent, Options) of + {value, Tuple4} when is_tuple(Tuple4) andalso + (size(Tuple4) =:= 4) -> + Tuple4; + {value, Host} when is_list(Host) -> + {ok, Ip} = snmp_misc:ip(Host), + Ip + end, + io:format("[~w] ~p -> AgIp: ~p~n", [?MODULE, self(), AgIp]), + Quiet = lists:member(quiet, Options), + io:format("[~w] ~p -> Quiet: ~p~n", [?MODULE, self(), Quiet]), + PackServ = start_packet_server(Quiet, Options, CallerPid, + AgIp, Udp, TrapUdp, + VsnHdrD, Version, Dir, RecBufSz, + PacksDbg), + d("init -> packet server: ~p",[PackServ]), + State = #state{parent = CallerPid, + quiet = Quiet, + mini_mib = MiniMIB, + packet_server = PackServ}, + d("init -> done",[]), + {ok, State}; + + {error, Reason} -> + {stop,Reason} + end. + +start_packet_server(false, _Options, _CallerPid, AgIp, Udp, TrapUdp, + VsnHdrD, Version, Dir, RecBufSz, PacksDbg) -> + d("start_packet_server -> entry", []), + ?PACK_SERV:start_link_packet({msg, self()}, + AgIp, Udp, TrapUdp, + VsnHdrD, Version, Dir, RecBufSz, + PacksDbg); +start_packet_server(true, Options, CallerPid, AgIp, Udp, TrapUdp, + VsnHdrD, Version, Dir, RecBufSz, PacksDbg) -> + Type = get_value(receive_type, Options, pdu), + d("start_packet_server -> entry with" + "~n CallerPid: ~p" + "~n when" + "~n Type: ~p",[CallerPid, Type]), + ?PACK_SERV:start_link_packet({Type, CallerPid}, + AgIp, Udp, TrapUdp, + VsnHdrD, Version, Dir, RecBufSz, + PacksDbg). + +is_options_ok([{mibs,List}|Opts]) when is_list(List) -> + is_options_ok(Opts); +is_options_ok([quiet|Opts]) -> + is_options_ok(Opts); +is_options_ok([{agent,_}|Opts]) -> + is_options_ok(Opts); +is_options_ok([{agent_udp,Int}|Opts]) when is_integer(Int) -> + is_options_ok(Opts); +is_options_ok([{trap_udp,Int}|Opts]) when is_integer(Int) -> + is_options_ok(Opts); +is_options_ok([{community,List}|Opts]) when is_list(List) -> + is_options_ok(Opts); +is_options_ok([{dir,List}|Opts]) when is_list(List) -> + is_options_ok(Opts); +is_options_ok([{sec_level,noAuthNoPriv}|Opts]) -> + is_options_ok(Opts); +is_options_ok([{sec_level,authNoPriv}|Opts]) -> + is_options_ok(Opts); +is_options_ok([{sec_level,authPriv}|Opts]) -> + is_options_ok(Opts); +is_options_ok([{context,List}|Opts]) when is_list(List) -> + is_options_ok(Opts); +is_options_ok([{user,List}|Opts]) when is_list(List) -> + is_options_ok(Opts); +is_options_ok([{engine_id,List}|Opts]) when is_list(List) -> + is_options_ok(Opts); +is_options_ok([{context_engine_id,List}|Opts]) when is_list(List) -> + is_options_ok(Opts); +is_options_ok([v1|Opts]) -> + is_options_ok(Opts); +is_options_ok([v2|Opts]) -> + is_options_ok(Opts); +is_options_ok([v3|Opts]) -> + is_options_ok(Opts); +is_options_ok([{debug,Bool}|Opts]) -> + case is_bool(Bool) of + ok -> + is_options_ok(Opts); + error -> + {error, {bad_option, debug, Bool}} + end; +is_options_ok([{packet_server_debug,Bool}|Opts]) -> + case is_bool(Bool) of + ok -> + is_options_ok(Opts); + error -> + {error, {bad_option, packet_server_debug, Bool}} + end; +is_options_ok([{recbuf,Sz}|Opts]) when (0 < Sz) and (Sz =< 65535) -> + is_options_ok(Opts); +is_options_ok([InvOpt|_]) -> + {error,{invalid_option,InvOpt}}; +is_options_ok([]) -> true. + +is_bool(true) -> ok; +is_bool(false) -> ok; +is_bool(_) -> error. + +mk_seclevel(noAuthNoPriv) -> 0; +mk_seclevel(authNoPriv) -> 1; +mk_seclevel(authPriv) -> 3. + + +handle_call({purify_oid, Oid}, _From, #state{mini_mib = MiniMib} = State) -> + d("handle_call -> purify_oid for ~p",[Oid]), + Reply = (catch purify_oid(Oid, MiniMib)), + {reply, Reply, State}; + +handle_call({find_pure_oid, XOid}, _From, State) -> + d("handle_call -> find_pure_oid for ~p",[XOid]), + {reply, catch flatten_oid(XOid, State#state.mini_mib), State}; + +handle_call({oid_to_name, Oid}, _From, State) -> + d("handle_call -> oid_to_name for Oid: ~p",[Oid]), + Reply = + case lists:keysearch(Oid, 1, State#state.mini_mib) of + {value, {_Oid, Name, _Type}} -> + {ok, Name}; + false -> + {error, {no_such_oid, Oid}} + end, + {reply, Reply, State}; + +handle_call({name_to_oid, Name}, _From, State) -> + d("handle_call -> name_to_oid for Name: ~p",[Name]), + Reply = + case lists:keysearch(Name, 2, State#state.mini_mib) of + {value, {Oid, _Name, _Type}} -> + {ok, Oid}; + false -> + {error, {no_such_name, Name}} + end, + {reply, Reply, State}; + +handle_call(stop, _From, #state{mini_mib = MiniMIB} = State) -> + d("handle_call -> stop request",[]), + snmp_mini_mib:delete(MiniMIB), + {stop, normal, ok, State#state{mini_mib = undefined}}; + +handle_call(discovery, _From, State) -> + d("handle_call -> discovery",[]), + {Reply, NewState} = execute_discovery(State), + {reply, Reply, NewState}. + + +handle_cast({get, Oids}, State) -> + d("handle_cast -> get request for ~p", [Oids]), + {noreply, execute_request(get, Oids, State)}; + +handle_cast({set, VariablesAndValues}, State) -> + d("handle_cast -> set request for ~p", [VariablesAndValues]), + {noreply, execute_request(set, VariablesAndValues, State)}; + +handle_cast({get_next, Oids}, State) -> + d("handle_cast -> get-next request for ~p", [Oids]), + {noreply, execute_request(get_next, Oids, State)}; + +handle_cast(iter_get_next, State) + when is_record(State#state.last_received_pdu, pdu) -> + d("handle_cast -> iter_get_next request", []), + PrevPDU = State#state.last_received_pdu, + Oids = [get_oid_from_varbind(Vb) || Vb <- PrevPDU#pdu.varbinds], + {noreply, execute_request(get_next, Oids, State)}; + +handle_cast(iter_get_next, State) -> + ?PACK_SERV:error("[Iterated get-next] No Response PDU to " + "start iterating from.", []), + {noreply, State}; + +handle_cast({iter_get_next, N}, State) -> + d("handle_cast -> iter_get_next(~p) request",[N]), + if + is_record(State#state.last_received_pdu, pdu) -> + PDU = get_next_iter_impl(N, State#state.last_received_pdu, + State#state.mini_mib, + State#state.packet_server), + {noreply, State#state{last_received_pdu = PDU}}; + true -> + ?PACK_SERV:error("[Iterated get-next] No Response PDU to " + "start iterating from.", []), + {noreply, State} + end; + +handle_cast(resend_pdu, #state{last_sent_pdu = PDU} = State) -> + d("handle_cast -> resend_pdu request when" + "~n PDU = ~p", [PDU]), + send_pdu(PDU#pdu{request_id = make_request_id()}, + State#state.mini_mib, + State#state.packet_server), + {noreply, State}; + +handle_cast({bulk, Args}, State) -> + d("handle_bulk -> bulk request for ~p", [Args]), + {noreply, execute_request(bulk, Args, State)}; + +handle_cast({response, RespPdu}, State) -> + d("handle_cast -> response request with ~p", [RespPdu]), + ?PACK_SERV:send_pdu(RespPdu, State#state.packet_server), + {noreply, State}; + +handle_cast({send_bytes, Bytes}, State) -> + d("handle_cast -> send-bytes request for ~p bytes", [sizeOf(Bytes)]), + ?PACK_SERV:send_bytes(Bytes, State#state.packet_server), + {noreply, State}; + +handle_cast(Msg, State) -> + d("handle_cast -> unknown message: " + "~n ~p", [Msg]), + {noreply, State}. + + +handle_info({snmp_msg, Msg, Ip, Udp}, State) -> + io:format("* Got PDU: ~s", [?PACK_SERV:format_hdr(Msg)]), + PDU = ?PACK_SERV:get_pdu(Msg), + echo_pdu(PDU, State#state.mini_mib), + case PDU#pdu.type of + 'inform-request' -> + %% Generate a response... + RespPDU = PDU#pdu{type = 'get-response', + error_status = noError, + error_index = 0}, + RespMsg = ?PACK_SERV:set_pdu(Msg, RespPDU), + ?PACK_SERV:send_msg(RespMsg, State#state.packet_server, Ip, Udp); + _Else -> + ok + end, + {noreply, State#state{last_received_pdu = PDU}}; + +handle_info(Info, State) -> + d("handle_info -> unknown info: " + "~n ~p", [Info]), + {noreply, State}. + + +terminate(Reason, State) -> + d("terminate -> with Reason: ~n\t~p",[Reason]), + ?PACK_SERV:stop(State#state.packet_server). + + +%%---------------------------------------------------------------------- +%% Returns: A new State +%%---------------------------------------------------------------------- +execute_discovery(State) -> + Pdu = make_discovery_pdu(), + Reply = ?PACK_SERV:send_discovery_pdu(Pdu, State#state.packet_server), + {Reply, State#state{last_sent_pdu = Pdu}}. + + +execute_request(Operation, Data, State) -> + case (catch make_pdu(Operation, Data, State#state.mini_mib)) of + {error, {Format, Data2}} -> + report_error(State, Format, Data2), + State; + {error, _Reason} -> + State; + PDU when is_record(PDU, pdu) -> + send_pdu(PDU, State#state.mini_mib, State#state.packet_server), + State#state{last_sent_pdu = PDU} + end. + +report_error(#state{quiet = true, parent = Pid}, Format, Args) -> + Reason = lists:flatten(io_lib:format(Format, Args)), + Pid ! {oid_error, Reason}; +report_error(_, Format, Args) -> + ?PACK_SERV:error(Format, Args). + + +get_oid_from_varbind(#varbind{oid = Oid}) -> Oid. + +send_pdu(PDU, _MiniMIB, PackServ) -> + ?PACK_SERV:send_pdu(PDU, PackServ). + +%%---------------------------------------------------------------------- +%% Purpose: Unnesting of oids like [myTable, 3, 4, "hej", 45] to +%% [1,2,3,3,4,104,101,106,45] +%%---------------------------------------------------------------------- + +purify_oid([A|T], MiniMib) when is_atom(A) -> + Oid2 = + case snmp_mini_mib:oid(MiniMib, A) of + false -> + throw({error, {unknown_aliasname, A}}); + Oid -> + lists:flatten([Oid|T]) + end, + {ok, verify_pure_oid(Oid2)}; +purify_oid(L, _) when is_list(L) -> + {ok, verify_pure_oid(lists:flatten(L))}; +purify_oid(X, _) -> + {error, {invalid_oid, X}}. + +verify_pure_oid([]) -> + []; +verify_pure_oid([H | T]) when is_integer(H) and (H >= 0) -> + [H | verify_pure_oid(T)]; +verify_pure_oid([H | _]) -> + throw({error, {not_pure_oid, H}}). + +flatten_oid(XOid, DB) -> + Oid = case XOid of + [A|T] when is_atom(A) -> + [remove_atom(A, DB)|T]; + L when is_list(L) -> + XOid; + Shit -> + throw({error, + {"Invalid oid, not a list of integers: ~w", [Shit]}}) + end, + check_is_pure_oid(lists:flatten(Oid)). + +remove_atom(AliasName, DB) when is_atom(AliasName) -> + case snmp_mini_mib:oid(DB, AliasName) of + false -> + throw({error, {"Unknown aliasname in oid: ~w", [AliasName]}}); + Oid -> + Oid + end; +remove_atom(X, _DB) -> + X. + +%%---------------------------------------------------------------------- +%% Throws if not a list of integers +%%---------------------------------------------------------------------- +check_is_pure_oid([]) -> []; +check_is_pure_oid([X | T]) when is_integer(X) and (X >= 0) -> + [X | check_is_pure_oid(T)]; +check_is_pure_oid([X | _T]) -> + throw({error, {"Invalid oid, it contains a non-integer: ~w", [X]}}). + +get_next_iter_impl(0, PrevPDU, _MiniMIB, _PackServ) -> + PrevPDU; +get_next_iter_impl(N, PrevPDU, MiniMIB, PackServ) -> + Oids = [get_oid_from_varbind(Vb) || Vb <- PrevPDU#pdu.varbinds], + PDU = make_pdu(get_next, Oids, MiniMIB), + send_pdu(PDU, MiniMIB, PackServ), + case receive_response() of + {error, timeout} -> + io:format("(timeout)~n"), + get_next_iter_impl(N, PrevPDU, MiniMIB, PackServ); + {error, _Reason} -> + PrevPDU; + RPDU when is_record(RPDU, pdu) -> + io:format("(~w)", [N]), + echo_pdu(RPDU, MiniMIB), + get_next_iter_impl(N-1, RPDU, MiniMIB, PackServ) + end. + +%%-------------------------------------------------- +%% Used to resend a PDU. Takes the old PDU and +%% generates a fresh one (with a new requestID). +%%-------------------------------------------------- + +make_pdu(set, VarsAndValues, MiniMIB) -> + VBs = [var_and_value_to_varbind(VAV, MiniMIB) || VAV <- VarsAndValues], + make_pdu_impl(set, VBs); +make_pdu(bulk, {NonRepeaters, MaxRepetitions, Oids}, MiniMIB) -> + Foids = [flatten_oid(Oid, MiniMIB) || Oid <- Oids], + #pdu{type = 'get-bulk-request', + request_id = make_request_id(), + error_status = NonRepeaters, + error_index = MaxRepetitions, + varbinds = [make_vb(Oid) || Oid <- Foids]}; +make_pdu(Operation, Oids, MiniMIB) -> + Foids = [flatten_oid(Oid, MiniMIB) || Oid <- Oids], + make_pdu_impl(Operation, Foids). + +make_pdu_impl(get, Oids) -> + #pdu{type = 'get-request', + request_id = make_request_id(), + error_status = noError, + error_index = 0, + varbinds = [make_vb(Oid) || Oid <- Oids]}; + +make_pdu_impl(get_next, Oids) -> + #pdu{type = 'get-next-request', + request_id = make_request_id(), + error_status = noError, + error_index = 0, + varbinds = [make_vb(Oid) || Oid <- Oids]}; + +make_pdu_impl(set, Varbinds) -> + #pdu{type = 'set-request', + request_id = make_request_id(), + error_status = noError, + error_index = 0, + varbinds = Varbinds}. + +make_discovery_pdu() -> + make_pdu_impl(get, []). + +var_and_value_to_varbind({Oid, Type, Value}, MiniMIB) -> + Oid2 = flatten_oid(Oid, MiniMIB), + #varbind{oid = Oid2, + variabletype = char_to_type(Type), + value = Value}; +var_and_value_to_varbind({XOid, Value}, MiniMIB) -> + Oid = flatten_oid(XOid, MiniMIB), + #varbind{oid = Oid, + variabletype = snmp_mini_mib:type(MiniMIB, Oid), + value = Value}. + +char_to_type(o) -> + 'OBJECT IDENTIFIER'; +char_to_type(i) -> + 'INTEGER'; +char_to_type(u) -> + 'Unsigned32'; +char_to_type(g) -> % Gauge, Gauge32 + 'Unsigned32'; +char_to_type(s) -> + 'OCTET STRING'. + +make_vb(Oid) -> + #varbind{oid = Oid, variabletype = 'NULL', value = 'NULL'}. + +make_request_id() -> + random:uniform(16#FFFFFFF-1). + +echo_pdu(PDU, MiniMIB) -> + io:format("~s", [snmp_misc:format_pdu(PDU, MiniMIB)]). + + +%%---------------------------------------------------------------------- +%% Test Sequence +%%---------------------------------------------------------------------- +echo_errors({error, Id, {ExpectedFormat, ExpectedData}, {Format, Data}})-> + io:format("* Unexpected Behaviour * Id: ~w.~n" + " Expected: " ++ ExpectedFormat ++ "~n" + " Got: " ++ Format ++ "~n", + [Id] ++ ExpectedData ++ Data), + {error, Id, {ExpectedFormat, ExpectedData}, {Format, Data}}; +echo_errors(ok) -> ok; +echo_errors({ok, Val}) -> {ok, Val}. + +get_response_impl(Id, Vars) -> + case receive_response() of + #pdu{type = 'get-response', + error_status = noError, + error_index = 0, + varbinds = VBs} -> + match_vars(Id, find_pure_oids2(Vars), VBs, []); + + #pdu{type = Type2, + request_id = ReqId, + error_status = Err2, + error_index = Index2} -> + {error, + Id, + {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w", + ['get-response', noError, 0, ReqId]}, + {"Type: ~w, ErrStat: ~w, Idx: ~w", + [Type2, Err2, Index2]}}; + + {error, Reason} -> + format_reason(Id, Reason) + end. + + + +%%---------------------------------------------------------------------- +%% Returns: ok | {error, Id, {ExpectedFormat, ExpectedData}, {Format, Data}} +%%---------------------------------------------------------------------- +expect_impl(Id, any) -> + io:format("expect_impl(~w, any) -> entry ~n", [Id]), + case receive_response() of + PDU when is_record(PDU, pdu) -> ok; + {error, Reason} -> format_reason(Id, Reason) + end; + +expect_impl(Id, return) -> + io:format("expect_impl(~w, return) -> entry ~n", [Id]), + case receive_response() of + PDU when is_record(PDU, pdu) -> {ok, PDU}; + {error, Reason} -> format_reason(Id, Reason) + end; + +expect_impl(Id, trap) -> + io:format("expect_impl(~w, trap) -> entry ~n", [Id]), + case receive_trap(3500) of + PDU when is_record(PDU, trappdu) -> ok; + {error, Reason} -> format_reason(Id, Reason) + end; + +expect_impl(Id, timeout) -> + io:format("expect_impl(~w, timeout) -> entry ~n", [Id]), + receive + X -> + io:format("expect_impl(~w, timeout) -> " + "received unexpected message: ~n~p~n", [Id, X]), + {error, Id, {"Timeout", []}, {"Message ~w", [X]}} + after 3500 -> + ok + end; + +expect_impl(Id, Err) when is_atom(Err) -> + io:format("expect_impl(~w, ~w) -> entry ~n", [Id, Err]), + case receive_response() of + #pdu{error_status = Err} -> + ok; + + #pdu{request_id = ReqId, + error_status = OtherErr} -> + io:format("expect_impl(~w, ~w) -> " + "received pdu (~w) with unexpected error-status: " + "~n~p~n", [Id, Err, ReqId, OtherErr]), + {error, Id, {"ErrorStatus: ~w, RequestId: ~w", [Err,ReqId]}, + {"ErrorStatus: ~w", [OtherErr]}}; + + {error, Reason} -> + format_reason(Id, Reason) + end; + +expect_impl(Id, ExpectedVarbinds) when is_list(ExpectedVarbinds) -> + io:format("expect_impl(~w) -> entry with" + "~n ExpectedVarbinds: ~p~n", [Id, ExpectedVarbinds]), + case receive_response() of + #pdu{type = 'get-response', + error_status = noError, + error_index = 0, + varbinds = VBs} -> + io:format("expect_impl(~w) -> received pdu with" + "~n VBs: ~p~n", [Id, VBs]), + check_vars(Id, find_pure_oids(ExpectedVarbinds), VBs); + + #pdu{type = Type2, + request_id = ReqId, + error_status = Err2, + error_index = Index2} -> + io:format("expect_impl(~w) -> received unexpected pdu with" + "~n Type2: ~p" + "~n ReqId: ~p" + "~n Err2: ~p" + "~n Index2: ~p" + "~n", [Id, Type2, ReqId, Err2, Index2]), + {error, Id, {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w", + ['get-response', noError, 0, ReqId]}, + {"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}}; + + {error, Reason} -> + format_reason(Id, Reason) + end. + +expect_impl(Id, v2trap, ExpectedVarbinds) when is_list(ExpectedVarbinds) -> + io:format("expect_impl(~w, v2trap) -> entry with" + "~n ExpectedVarbinds: ~p~n", [Id, ExpectedVarbinds]), + case receive_response() of + #pdu{type = 'snmpv2-trap', + error_status = noError, + error_index = 0, + varbinds = VBs} -> + io:format("expect_impl(~w, v2trap) -> received pdu with" + "~n VBs: ~p~n", [Id, VBs]), + check_vars(Id, find_pure_oids(ExpectedVarbinds), VBs); + + #pdu{type = Type2, + request_id = ReqId, + error_status = Err2, + error_index = Index2} -> + io:format("expect_impl(~w, v2trap) -> received unexpected pdu with" + "~n Type2: ~p" + "~n ReqId: ~p" + "~n Err2: ~p" + "~n Index2: ~p" + "~n", [Id, Type2, ReqId, Err2, Index2]), + {error, Id, {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w", + ['snmpv2-trap', noError, 0, ReqId]}, + {"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}}; + + {error, Reason} -> + format_reason(Id, Reason) + end; + +expect_impl(Id, report, ExpectedVarbinds) when is_list(ExpectedVarbinds) -> + io:format("expect_impl(~w, report) -> entry with" + "~n ExpectedVarbinds: ~p~n", [Id, ExpectedVarbinds]), + case receive_response() of + #pdu{type = 'report', + error_status = noError, + error_index = 0, + varbinds = VBs} -> + io:format("expect_impl(~w, report) -> received pdu with" + "~n VBs: ~p~n", [Id, VBs]), + check_vars(Id, find_pure_oids(ExpectedVarbinds), VBs); + + #pdu{type = Type2, + request_id = ReqId, + error_status = Err2, + error_index = Index2} -> + io:format("expect_impl(~w, report) -> received unexpected pdu with" + "~n Type2: ~p" + "~n ReqId: ~p" + "~n Err2: ~p" + "~n Index2: ~p" + "~n", [Id, Type2, ReqId, Err2, Index2]), + {error, Id, {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w", + [report, noError, 0, ReqId]}, + {"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}}; + + {error, Reason} -> + format_reason(Id, Reason) + end; + +expect_impl(Id, {inform, Reply}, ExpectedVarbinds) + when is_list(ExpectedVarbinds) -> + io:format("expect_impl(~w, inform) -> entry with" + "~n Reply: ~p" + "~n ExpectedVarbinds: ~p" + "~n", [Id, Reply, ExpectedVarbinds]), + Resp = receive_response(), + case Resp of + #pdu{type = 'inform-request', + error_status = noError, + error_index = 0, + varbinds = VBs} -> + io:format("expect_impl(~w, inform) -> received pdu with" + "~n VBs: ~p~n", [Id, VBs]), + case check_vars(Id, find_pure_oids(ExpectedVarbinds), VBs) of + ok when (Reply == true) -> + io:format("expect_impl(~w, inform) -> send ok response" + "~n", [Id]), + RespPDU = Resp#pdu{type = 'get-response', + error_status = noError, + error_index = 0}, + ?MODULE:rpl(RespPDU), + ok; + ok when (element(1, Reply) == error) -> + io:format("expect_impl(~w, inform) -> send error response" + "~n", [Id]), + {error, Status, Index} = Reply, + RespPDU = Resp#pdu{type = 'get-response', + error_status = Status, + error_index = Index}, + ?MODULE:rpl(RespPDU), + ok; + ok when (Reply == false) -> + io:format("expect_impl(~w, inform) -> no response sent" + "~n", [Id]), + ok; + Else -> + io:format("expect_impl(~w, inform) -> " + "~n Else: ~p" + "~n", [Id, Else]), + Else + end; + + #pdu{type = Type2, + request_id = ReqId, + error_status = Err2, + error_index = Index2} -> + io:format("expect_impl(~w, inform) -> received unexpected pdu with" + "~n Type2: ~p" + "~n ReqId: ~p" + "~n Err2: ~p" + "~n Index2: ~p" + "~n", [Id, Type2, ReqId, Err2, Index2]), + {error, Id, {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w", + ['inform-request', noError, 0, ReqId]}, + {"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}}; + + {error, Reason} -> + io:format("expect_impl(~w, inform) -> receive failed" + "~n Reason: ~p" + "~n", [Id, Reason]), + format_reason(Id, Reason) + end. + +expect_impl(Id, Err, Index, any) -> + io:format("expect_impl(~w, any) -> entry with" + "~n Err: ~p" + "~n Index: ~p" + "~n", [Id, Err, Index]), + case receive_response() of + #pdu{type = 'get-response', + error_status = Err, + error_index = Index} -> + io:format("expect_impl(~w, any) -> received expected pdu" + "~n", [Id]), + ok; + + #pdu{type = 'get-response', error_status = Err} when (Index == any) -> + io:format("expect_impl(~w, any) -> received expected pdu (any)" + "~n", [Id]), + ok; + + #pdu{type = 'get-response', + request_id = ReqId, + error_status = Err, + error_index = Idx} when is_list(Index) -> + io:format("expect_impl(~w, any) -> received pdu: " + "~n ReqId: ~p" + "~n Err: ~p" + "~n Idx: ~p" + "~n", [Id, ReqId, Err, Idx]), + case lists:member(Idx, Index) of + true -> + ok; + false -> + {error, Id, {"ErrStat: ~w, Idx: ~w, RequestId: ~w", + [Err, Index, ReqId]}, + {"ErrStat: ~w, Idx: ~w", [Err, Idx]}} + end; + + #pdu{type = Type2, + request_id = ReqId, + error_status = Err2, + error_index = Index2} -> + io:format("expect_impl(~w, any) -> received unexpected pdu: " + "~n Type2: ~p" + "~n ReqId: ~p" + "~n Err2: ~p" + "~n Index2: ~p" + "~n", [Id, Type2, ReqId, Err2, Index2]), + {error, Id, {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w", + ['get-response', Err, Index, ReqId]}, + {"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}}; + + {error, Reason} -> + format_reason(Id, Reason) + end; + +expect_impl(Id, Err, Index, ExpectedVarbinds) -> + io:format("expect_impl(~w) -> entry with" + "~n Err: ~p" + "~n Index: ~p" + "~n ExpectedVarbinds: ~p" + "~n", [Id, Err, Index, ExpectedVarbinds]), + PureVBs = find_pure_oids(ExpectedVarbinds), + case receive_response() of + #pdu{type = 'get-response', + error_status = Err, + error_index = Index, + varbinds = VBs} -> + check_vars(Id, PureVBs, VBs); + + #pdu{type = 'get-response', + error_status = Err, + varbinds = VBs} when (Index == any) -> + check_vars(Id, PureVBs, VBs); + + #pdu{type = 'get-response', + request_id = ReqId, + error_status = Err, + error_index = Idx, + varbinds = VBs} when is_list(Index) -> + case lists:member(Idx, Index) of + true -> + check_vars(Id, PureVBs, VBs); + false -> + {error,Id, + {"ErrStat: ~w, Idx: ~w, Varbinds: ~w, RequestId: ~w", + [Err,Index,PureVBs,ReqId]}, + {"ErrStat: ~w, Idx: ~w, Varbinds: ~w", + [Err,Idx,VBs]}} + end; + + #pdu{type = Type2, + request_id = ReqId, + error_status = Err2, + error_index = Index2, + varbinds = VBs} -> + {error,Id, + {"Type: ~w, ErrStat: ~w, Idx: ~w, Varbinds: ~w, RequestId: ~w", + ['get-response',Err,Index,PureVBs,ReqId]}, + {"Type: ~w, ErrStat: ~w Idx: ~w Varbinds: ~w", + [Type2,Err2,Index2,VBs]}}; + + {error, Reason} -> + format_reason(Id, Reason) + end. + +expect_impl(Id, trap, Enterp, Generic, Specific, ExpectedVarbinds) -> + PureE = find_pure_oid(Enterp), + case receive_trap(3500) of + #trappdu{enterprise = PureE, + generic_trap = Generic, + specific_trap = Specific, + varbinds = VBs} -> + check_vars(Id, find_pure_oids(ExpectedVarbinds), VBs); + + #trappdu{enterprise = Ent2, + generic_trap = G2, + specific_trap = Spec2, + varbinds = VBs} -> + {error, Id, + {"Enterprise: ~w, Generic: ~w, Specific: ~w, Varbinds: ~w", + [PureE, Generic, Specific, ExpectedVarbinds]}, + {"Enterprise: ~w, Generic: ~w, Specific: ~w, Varbinds: ~w", + [Ent2, G2, Spec2, VBs]}}; + + {error, Reason} -> + format_reason(Id, Reason) + end. + +format_reason(Id, Reason) -> + {error, Id, {"?", []}, {"~w", [Reason]}}. + +%%---------------------------------------------------------------------- +%% Args: Id, ExpectedVarbinds, GotVarbinds +%% Returns: ok +%% Fails: if not ok +%%---------------------------------------------------------------------- +check_vars(_Id,[], []) -> + ok; +check_vars(Id,Vars, []) -> + {error, Id, {"More Varbinds (~w)", [Vars]}, {"Too few", []}}; +check_vars(Id,[], Varbinds) -> + {error,Id, {"Fewer Varbinds", []}, {"Too many (~w)", [Varbinds]}}; +check_vars(Id,[{_XOid, any} | Vars], [#varbind{oid = _Oid} |Vbs]) -> + check_vars(Id,Vars, Vbs); +check_vars(Id,[{Oid, Val} | Vars], [#varbind{oid = Oid, value = Val} |Vbs]) -> + check_vars(Id,Vars, Vbs); +check_vars(Id,[{Oid, Val} | _], [#varbind{oid = Oid, value = Val2} |_]) -> + {error, Id, {" Varbind: ~w = ~w", [Oid, Val]}, {"Value: ~w", [Val2]}}; +check_vars(Id,[{Oid, _Val} | _], [#varbind{oid = Oid2, value = _Val2} |_]) -> + {error, Id, {"Oid: ~w", [Oid]}, {"Oid: ~w", [Oid2]}}. + +match_vars(Id, [Oid|T], [#varbind{oid = Oid, value = Value} | Vbs], Res) -> + match_vars(Id, T, Vbs, [Value | Res]); +match_vars(_Id, [], [], Res) -> + {ok, lists:reverse(Res)}; +match_vars(Id, [Oid | _], [#varbind{oid = Oid2}], _Res) -> + {error, Id, {" Oid: ~w", [Oid]}, {"Oid2: ~w", [Oid2]}}; +match_vars(Id, Vars, [], _Res) -> + {error, Id, {"More Varbinds (~w)", [Vars]}, {"Too few", []}}; +match_vars(Id, [], Varbinds, _Res) -> + {error,Id, {"Fewer Varbinds", []}, {"Too many (~w)", [Varbinds]}}. + + + +find_pure_oids([]) -> []; +find_pure_oids([{XOid, Q}|T]) -> + [{find_pure_oid(XOid), Q} | find_pure_oids(T)]. + +find_pure_oids2([]) -> []; +find_pure_oids2([XOid|T]) -> + [find_pure_oid(XOid) | find_pure_oids2(T)]. + + +%%---------------------------------------------------------------------- +%% Returns: Oid +%% Fails: malformed oids +%%---------------------------------------------------------------------- +find_pure_oid(XOid) -> + case gen_server:call(?MODULE, {find_pure_oid, XOid}, infinity) of + {error, {Format, Data}} -> + ok = io:format(Format, Data), + exit(malformed_oid); + Oid when is_list(Oid) -> Oid + end. + +get_value(Opt, Opts, Default) -> + case snmp_misc:assq(Opt,Opts) of + {value, C} -> C; + false -> Default + end. + + +%%---------------------------------------------------------------------- + +call(Req) -> + call(Req, infinity). + +call(Req, To) -> + gen_server:call(?SERVER, Req, To). + +cast(Msg) -> + gen_server:cast(?SERVER, Msg). + + +%%---------------------------------------------------------------------- +%% Debug +%%---------------------------------------------------------------------- + +sizeOf(L) when is_list(L) -> + length(lists:flatten(L)); +sizeOf(B) when is_binary(B) -> + size(B). + +d(F,A) -> d(get(debug),F,A). + +d(true,F,A) -> + io:format("*** [~s] MGR_DBG *** " ++ F ++ "~n", + [format_timestamp(now())|A]); +d(_,_F,_A) -> + ok. + +format_timestamp({_N1, _N2, N3} = Now) -> + {Date, Time} = calendar:now_to_datetime(Now), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", + [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), + lists:flatten(FormatDate). + |