aboutsummaryrefslogblamecommitdiffstats
path: root/lib/snmp/test/snmp_test_mgr_misc.erl
blob: 315e3ebd9eacedf8c67640684470798eaaa98f15 (plain) (tree)
1
2
3
4
5
6
7
8
9

                   
  
                                                        
  


                                                                   
  






                                                                           
  








                                          
                                                                        






                                              
                          
 

                                      

                                            
                                                 
                              




                                                                           
 



















                                                                            































                                                                 



                                                                  

                           

                                                                
                   
                           
                                      



                                                                       

                               























































































                                                                               

















                                                                         
















                                                                         

















































































                                                                                  
























































































































































































































































































                                                                               
                                                                    

                                                                  






                                                                     
                                                     


                                                                


                                                          
                                          
                                        
                                       



                                                            
        
                                          
                                                       
                                                       

                                                                                  
                                           
                                                                 
                                          



                                                       


                       





















































                                                             































































































































































































                                                                             
                              
 

                


              

                               
 
%% 
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%% 

%%
%% ts:run(snmp, snmp_agent_test, [batch]).
%% 
-module(snmp_test_mgr_misc).

%% API
-export([start_link_packet/8, start_link_packet/9, start_link_packet/10,
	 stop/1, 
	 send_discovery_pdu/2, 
	 send_pdu/2, send_msg/4, send_bytes/2,
	 error/2,
	 get_pdu/1, set_pdu/2, format_hdr/1]).

%% internal exports
-export([init_packet/11]).

-compile({no_auto_import, [error/2]}).

-define(SNMP_USE_V3, true).
-include_lib("snmp/include/snmp_types.hrl").
-include_lib("snmp/src/misc/snmp_verbosity.hrl").
-include("snmp_test_lib.hrl").


%%----------------------------------------------------------------------
%% The InHandler process will receive messages on the form {snmp_pdu, Pdu}.
%%----------------------------------------------------------------------

start_link_packet(
  InHandler, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz) ->
    start_link_packet(
      InHandler, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
      false).

start_link_packet(
  InHandler, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
  Dbg) ->
    start_link_packet(
      InHandler, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
      Dbg, inet).

start_link_packet(
  InHandler, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
  Dbg, IpFamily) when is_integer(UdpPort) ->
    Args =
	[self(),
	 InHandler, AgentIp, UdpPort, TrapUdp,  VsnHdr, Version, Dir, BufSz,
	 Dbg, IpFamily],
    proc_lib:start_link(?MODULE, init_packet, Args).

stop(Pid) ->
    Pid ! {stop, self()},
    receive
	{Pid, stopped} -> ok
    end.
	

send_discovery_pdu(Pdu, PacketPid) when is_record(Pdu, pdu) ->
    PacketPid ! {send_discovery_pdu, self(), Pdu},
    await_discovery_response_pdu().

await_discovery_response_pdu() ->
    receive
	{discovery_response, Reply} ->
	    Reply
    end.
    

send_pdu(Pdu, PacketPid) when is_record(Pdu, pdu) ->
    PacketPid ! {send_pdu, Pdu}.

send_msg(Msg, PacketPid, Ip, Udp) when is_record(Msg, message) ->
    PacketPid ! {send_msg, Msg, Ip, Udp}.

send_bytes(Bytes, PacketPid) ->
    PacketPid ! {send_bytes, Bytes}.

%%--------------------------------------------------
%% The SNMP encode/decode process
%%--------------------------------------------------
init_packet(
  Parent,
  SnmpMgr, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
  DbgOptions, IpFamily) ->
    put(sname, mgr_misc),
    init_debug(DbgOptions),
    UdpOpts     = [{recbuf,BufSz}, {reuseaddr, true}, IpFamily],
    {ok, UdpId} = gen_udp:open(TrapUdp, UdpOpts),
    put(msg_id, 1),
    init_usm(Version, Dir),
    proc_lib:init_ack(Parent, self()),
    packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, []).

init_debug(Dbg) when is_atom(Dbg) ->
    put(debug,Dbg),
    %% put(verbosity, silence);
    put(verbosity, trace);
init_debug(DbgOptions) when is_list(DbgOptions) ->
    case lists:keysearch(debug, 1, DbgOptions) of
	{value, {_, Dbg}} when is_atom(Dbg) ->
	    put(debug, Dbg);
	_ ->
	    put(debug, false)
    end,
    case lists:keysearch(verbosity, 1, DbgOptions) of
	{value, {_, Ver}} when is_atom(Ver) ->
	    put(verbosity, Ver);
	_ ->
	    put(verbosity, silence)
    end,
    ok.


packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, MsgData) ->
    receive
	{send_discovery_pdu, From, Pdu} ->
	    d("packet_loop -> received send_discovery_pdu with"
	      "~n   From: ~p"
	      "~n   Pdu:  ~p", [From, Pdu]),
	    case mk_discovery_msg(Version, Pdu, VsnHdr, "") of
		error ->
		    ok;
		{M, B} when is_list(B) -> 
		    put(discovery, {M, From}),
		    display_outgoing_message(M),
		    udp_send(UdpId, AgentIp, UdpPort, B)
	    end,
	    packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, []);

	{send_pdu, Pdu} ->
	    d("packet_loop -> received send_pdu with"
	      "~n   Pdu:  ~p", [Pdu]),
	    case mk_msg(Version, Pdu, VsnHdr, MsgData) of
		error ->
		    ok;
		B when is_list(B) -> 
		    udp_send(UdpId, AgentIp, UdpPort, B)
	    end,
	    packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,[]);

	{send_msg, Msg, Ip, Udp} ->
	    d("packet_loop -> received send_msg with"
	      "~n   Msg:  ~p"
	      "~n   Ip:   ~p"
	      "~n   Udp:  ~p", [Msg,Ip,Udp]),
	    case catch snmp_pdus:enc_message(Msg) of
		{'EXIT', Reason} ->
		    error("Encoding error:"
			  "~n   Msg:    ~w"
			  "~n   Reason: ~w",[Msg, Reason]);
		B when is_list(B) -> 
		    udp_send(UdpId, Ip, Udp, B)
	    end,
	    packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,[]);
	{udp, UdpId, Ip, UdpPort, Bytes} ->
	    d("packet_loop -> received udp with"
	      "~n   UdpId:     ~p"
	      "~n   Ip:        ~p"
	      "~n   UdpPort:   ~p"
	      "~n   sz(Bytes): ~p", [UdpId, Ip, UdpPort, sz(Bytes)]),	    
	    MsgData3 = handle_udp_packet(Version, erase(discovery),
					 UdpId, Ip, UdpPort, Bytes,
					 SnmpMgr, AgentIp),
	    packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,
			MsgData3);
	{send_bytes, B} ->
	    d("packet_loop -> received send_bytes with"
	      "~n   sz(B): ~p", [sz(B)]),	    
	    udp_send(UdpId, AgentIp, UdpPort, B),
	    packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,[]);
	{stop, Pid} ->
	    d("packet_loop -> received stop from ~p", [Pid]),	    
	    gen_udp:close(UdpId),
	    Pid ! {self(), stopped},
	    exit(normal);
	Other ->
	    d("packet_loop -> received unknown"
	      "~n   ~p", [Other]),	    
	    exit({snmp_packet_got_other, Other})
    end.


handle_udp_packet(_V, undefined, 
		  UdpId, Ip, UdpPort,
		  Bytes, SnmpMgr, AgentIp) ->
    try snmp_pdus:dec_message_only(Bytes) of
        Message when Message#message.version =:= 'version-3' ->
            d("handle_udp_packet -> version 3"),
            handle_v3_message(SnmpMgr, UdpId, Ip, UdpPort, AgentIp,
                              Bytes, Message);

        Message when is_record(Message, message) ->
            d("handle_udp_packet -> version 1 or 2"),
            handle_v1_or_v2_message(SnmpMgr, UdpId, Ip, UdpPort, AgentIp,
                                    Bytes, Message)

    catch
        Class:Error:_ ->
            error("Decoding error (~w). Bytes: ~w ~n Error: ~w "
                  "(UDPport: ~w, Ip: ~w)",
                  [Class, Bytes, Error, UdpPort, Ip]),
            []
    end;
handle_udp_packet(V, {DiscoReqMsg, From}, _UdpId, _Ip, _UdpPort, 
		  Bytes, _, _AgentIp) ->
    DiscoRspMsg = (catch snmp_pdus:dec_message(Bytes)),
    display_incomming_message(DiscoRspMsg),
    _Reply = (catch check_discovery_result(V, DiscoReqMsg, DiscoRspMsg)),
    case (catch check_discovery_result(V, DiscoReqMsg, DiscoRspMsg)) of
	{ok, AgentEngineID} when is_list(AgentEngineID) ->
	    %% Ok, step 1 complete, now for step 2
	    %% Which we skip for now
	    OK = {ok, AgentEngineID}, 
	    From ! {discovery_response, OK},
	    [];
	Error ->
	    From ! {discovery_response, Error},
	    []
    end.

handle_v3_message(Mgr, UdpId, Ip, UdpPort, AgentIp,
                  Bytes, Message) ->
    try handle_v3_msg(Bytes, Message) of
        {ok, NewData, MsgData} ->
            Msg = Message#message{data = NewData},
            case Mgr of
                {pdu, Pid} ->
                    Pdu = get_pdu(Msg), 
                    d("handle_v3_message -> send pdu to manager (~p): "
                      "~n   ~p", [Pid, Pdu]),
                    Pid ! {snmp_pdu, Pdu};
                {msg, Pid} ->
                    d("handle_v3_message -> send msg to manager (~p): "
                      "~n   ~p", [Pid, Msg]),
                    Pid ! {snmp_msg, Msg, Ip, UdpPort}
            end,
            MsgData

        catch
            throw:{error, Reason, B}:_ ->
                udp_send(UdpId, AgentIp, UdpPort, B),
                error("Decoding (v3) error. Auto-sending Report.\n"
                      "~n   Reason: ~w "
                      "(UDPport: ~w, Ip: ~w)",
                      [Reason, UdpPort, Ip]),
                [];

            throw:{error, Reason}:_ ->
                error("Decoding (v3) error. "
                      "~n   Bytes: ~w"
                      "~n   Reason: ~w "
                      "(UDPport: ~w, Ip: ~w)",
                      [Bytes, Reason, UdpPort, Ip]),
                [];

            Class:Error:_ ->
                error("Decoding (v3) error (~w). "
                      "~n   Bytes: ~w"
                      "~n   Error: ~w "
                      "(UDPport: ~w, Ip: ~w)",
                      [Class, Bytes, Error, UdpPort, Ip]),
                []

    end.

handle_v1_or_v2_message(Mgr, _UdpId, Ip, UdpPort, _AgentIp,
                        Bytes, Message) ->
    try snmp_pdus:dec_pdu(Message#message.data) of
        Pdu when is_record(Pdu, pdu) ->
            case Mgr of
                {pdu, Pid} ->
                    d("handle_v1_or_v2_message -> send pdu to manager (~p): "
                      "~n   ~p", [Pid, Pdu]),
                    Pid ! {snmp_pdu, Pdu};
                {msg, Pid} ->
                    d("handle_v1_or_v2_message -> send msg to manager (~p): "
                      "~n   ~p", [Pid, Pdu]),
                    Msg = Message#message{data = Pdu},
                    Pid ! {snmp_msg, Msg, Ip, UdpPort}
            end;
        Pdu when is_record(Pdu, trappdu) ->
            case Mgr of
                {pdu, Pid} ->
                    d("handle_v1_or_v2_message -> send trap-pdu to manager (~p): "
                      "~n   ~p", [Pid, Pdu]),
                    Pid ! {snmp_pdu, Pdu};
                {msg, Pid} ->
                    d("handle_v1_or_v2_message -> send trap-msg to manager (~p): "
                      "~n   ~p", [Pid, Pdu]),
                    Msg = Message#message{data = Pdu},
                    Pid ! {snmp_msg, Msg, Ip, UdpPort}
            end

    catch
        Class:Error:_ ->
            error("Decoding (v1 or v2) error (~w): "
                  "~n   Bytes: ~w"
                  "~n   Error: ~w "
                  "(UDPport: ~w, Ip: ~w)",
                  [Class, Bytes, Error, UdpPort, Ip])
    end.


%% This function assumes that the agent and the manager (thats us) 
%% has the same version.
check_discovery_result('version-3', DiscoReqMsg, DiscoRspMsg) ->
    ReqMsgID = getMsgID(DiscoReqMsg),
    RspMsgID = getMsgID(DiscoRspMsg),
    check_msgID(ReqMsgID, RspMsgID),
    ReqRequestId = getRequestId('version-3', DiscoReqMsg),
    RspRequestId = getRequestId('version-3', DiscoRspMsg),
    check_requestId(ReqRequestId, RspRequestId),
    {ok, getMsgAuthEngineID(DiscoRspMsg)};
check_discovery_result(Version, DiscoReqMsg, DiscoRspMsg) ->
    ReqRequestId = getRequestId(Version, DiscoReqMsg),
    RspRequestId = getRequestId(Version, DiscoRspMsg),
    check_requestId(ReqRequestId, RspRequestId),
    {ok, getSysDescr(DiscoRspMsg)}.

check_msgID(ID, ID) ->
    ok;
check_msgID(ReqMsgID, RspMsgID) ->
    throw({error, {invalid_msgID, ReqMsgID, RspMsgID}}).

check_requestId(Id,Id) ->
    ok;
check_requestId(ReqRequestId, RspRequestId) ->
    throw({error, {invalid_requestId, ReqRequestId, RspRequestId}}).

getMsgID(M) when is_record(M, message) ->
    (M#message.vsn_hdr)#v3_hdr.msgID.

getRequestId('version-3',M) when is_record(M, message) ->
    ((M#message.data)#scopedPdu.data)#pdu.request_id;
getRequestId(_Version,M) when is_record(M, message) ->
    (M#message.data)#pdu.request_id;
getRequestId(Version,M) ->
    io:format("************* ERROR ****************"
	      "~n   Version: ~w"
	      "~n   M:       ~w~n", [Version,M]),
    throw({error, {unknown_request_id, Version, M}}).
    
getMsgAuthEngineID(M) when is_record(M, message) ->
    SecParams1 = (M#message.vsn_hdr)#v3_hdr.msgSecurityParameters,
    SecParams2 = snmp_pdus:dec_usm_security_parameters(SecParams1),
    SecParams2#usmSecurityParameters.msgAuthoritativeEngineID.
    
getSysDescr(M) when is_record(M, message) ->
    getSysDescr((M#message.data)#pdu.varbinds);
getSysDescr([]) ->
    not_found;
getSysDescr([#varbind{oid = [1,3,6,1,2,1,1,1], value = Value}|_]) ->
    Value;
getSysDescr([#varbind{oid = [1,3,6,1,2,1,1,1,0], value = Value}|_]) ->
    Value;
getSysDescr([_|T]) ->
    getSysDescr(T).
    
handle_v3_msg(Packet, #message{vsn_hdr = V3Hdr, data = Data}) ->
    d("handle_v3_msg -> entry"),
    %% Code copied from snmp_mpd.erl
    #v3_hdr{msgID = MsgId, msgFlags = MsgFlags,
	    msgSecurityModel = MsgSecurityModel,
	    msgSecurityParameters = SecParams} = V3Hdr,
    SecModule = get_security_module(MsgSecurityModel), 
    d("handle_v3_msg -> SecModule: ~p", [SecModule]),
    SecLevel = hd(MsgFlags) band 3,
    d("handle_v3_msg -> SecLevel: ~p", [SecLevel]),
    IsReportable = snmp_misc:is_reportable(MsgFlags),
    SecRes = (catch SecModule:process_incoming_msg(list_to_binary(Packet), 
						   Data,SecParams,SecLevel)),
    {_SecEngineID, SecName, ScopedPDUBytes, SecData, _} =
	check_sec_module_result(SecRes, V3Hdr, Data, IsReportable),
    case (catch snmp_pdus:dec_scoped_pdu(ScopedPDUBytes)) of
	ScopedPDU when is_record(ScopedPDU, scopedPdu) -> 
	    {ok, ScopedPDU, {MsgId, SecName, SecData}};
	{'EXIT', Reason} ->
	    throw({error, Reason});
	Error ->
	    throw({error, {scoped_pdu_decode_failed, Error}})
    end;
handle_v3_msg(_Packet, BadMessage) ->
    throw({error, bad_message, BadMessage}).

get_security_module(?SEC_USM) ->
    snmpa_usm;
get_security_module(SecModel) ->
    throw({error, {unknown_sec_model, SecModel}}).

check_sec_module_result(Res, V3Hdr, Data, IsReportable) ->
    d("check_sec_module_result -> entry with"
      "~n   Res: ~p", [Res]),
    case Res of
	{ok, X} -> 
	    X;
	{error, Reason, []} ->
	    throw({error, {securityError, Reason}});
	{error, Reason, ErrorInfo} when IsReportable == true ->
	    #v3_hdr{msgID = MsgID, msgSecurityModel = MsgSecModel} = V3Hdr,
	    case generate_v3_report_msg(MsgID, MsgSecModel, Data, ErrorInfo) of
		error ->
		    throw({error, {securityError, Reason}});
		Packet ->
		    throw({error, {securityError, Reason}, Packet})
	    end;
	{error, Reason, _} ->
	    throw({error, {securityError, Reason}});
	Else ->
	    throw({error, {securityError, Else}})
    end.

generate_v3_report_msg(_MsgID, _MsgSecurityModel, Data, ErrorInfo) ->
    d("generate_v3_report_msg -> entry with"
      "~n   ErrorInfo: ~p", [ErrorInfo]),
    {Varbind, SecName, Opts} = ErrorInfo,
    ReqId =
	if is_record(Data, scopedPdu) -> (Data#scopedPdu.data)#pdu.request_id;
	   true -> 0
	end,
    Pdu = #pdu{type = report, request_id = ReqId,
	       error_status = noError, error_index = 0,
	       varbinds = [Varbind]},
    SecLevel = snmp_misc:get_option(securityLevel, Opts, 0),
    SnmpEngineID = snmp_framework_mib:get_engine_id(),
    ContextEngineID = 
	snmp_misc:get_option(contextEngineID, Opts, SnmpEngineID),
    ContextName = snmp_misc:get_option(contextName, Opts, ""),
    mk_msg('version-3', Pdu, {ContextName, SecName, SnmpEngineID, 
			      ContextEngineID, SecLevel},
	   undefined).


error(Format, Data) ->
    io:format("*** Error ***~n"),
    ok = io:format(Format, Data),
    io:format("~n").


mk_discovery_msg('version-3', Pdu, _VsnHdr, UserName) ->
    ScopedPDU = #scopedPdu{contextEngineID = "",
			   contextName     = "",
			   data            = Pdu},
    Bytes = snmp_pdus:enc_scoped_pdu(ScopedPDU),
    MsgID = get(msg_id),
    put(msg_id, MsgID+1),
    UsmSecParams = 
	#usmSecurityParameters{msgAuthoritativeEngineID = "",
			       msgAuthoritativeEngineBoots = 0,
			       msgAuthoritativeEngineTime = 0,
			       msgUserName = UserName,
			       msgPrivacyParameters = "",
			       msgAuthenticationParameters = ""},
    SecBytes = snmp_pdus:enc_usm_security_parameters(UsmSecParams),
    PduType = Pdu#pdu.type,
    Hdr = #v3_hdr{msgID                 = MsgID, 
		  msgMaxSize            = 1000,
		  msgFlags              = snmp_misc:mk_msg_flags(PduType, 0),
		  msgSecurityModel      = ?SEC_USM,
		  msgSecurityParameters = SecBytes},
    Msg = #message{version = 'version-3', vsn_hdr = Hdr, data = Bytes},
    case (catch snmp_pdus:enc_message_only(Msg)) of
	{'EXIT', Reason} ->
	    error("Discovery encoding error: "
		  "~n   Pdu:    ~w"
		  "~n   Reason: ~w",[Pdu, Reason]),
	    error;
	L when is_list(L) ->
	    {Msg#message{data = ScopedPDU}, L}
    end;
mk_discovery_msg(Version, Pdu, {Com, _, _, _, _}, _UserName) ->
    Msg = #message{version = Version, vsn_hdr = Com, data = Pdu},
    case catch snmp_pdus:enc_message(Msg) of
	{'EXIT', Reason} ->
	    error("Discovery encoding error:"
		  "~n   Pdu:    ~w"
		  "~n   Reason: ~w",[Pdu, Reason]),
	    error;
	L when is_list(L) -> 
	    {Msg, L}
    end.


mk_msg('version-3', Pdu, {Context, User, EngineID, CtxEngineId, SecLevel}, 
       MsgData) ->
    d("mk_msg(version-3) -> entry with"
      "~n   Pdu:         ~p"
      "~n   Context:     ~p"
      "~n   User:        ~p"
      "~n   EngineID:    ~p"
      "~n   CtxEngineID: ~p"
      "~n   SecLevel:    ~p", 
      [Pdu, Context, User, EngineID, CtxEngineId, SecLevel]),
    %% Code copied from snmp_mpd.erl
    {MsgId, SecName, SecData} =
	if
	    is_tuple(MsgData) andalso (Pdu#pdu.type =:= 'get-response') ->
		MsgData;
	    true -> 
		Md = get(msg_id),
		put(msg_id, Md + 1),
		{Md, User, []}
	end,
    ScopedPDU = #scopedPdu{contextEngineID = CtxEngineId,
			   contextName = Context,
			   data = Pdu},
    ScopedPDUBytes = snmp_pdus:enc_scoped_pdu(ScopedPDU),

    PduType = Pdu#pdu.type,
    V3Hdr = #v3_hdr{msgID      = MsgId,
		    msgMaxSize = 1000,
		    msgFlags   = snmp_misc:mk_msg_flags(PduType, SecLevel),
		    msgSecurityModel = ?SEC_USM},
    Message = #message{version = 'version-3', vsn_hdr = V3Hdr,
		       data = ScopedPDUBytes},
    SecEngineID = case PduType of
		      'get-response' -> snmp_framework_mib:get_engine_id();
		      _ -> EngineID
		  end,
    case catch snmpa_usm:generate_outgoing_msg(Message, SecEngineID,
					       SecName, SecData, SecLevel) of
	{'EXIT', Reason} ->
	    error("version-3 message encoding exit"
		  "~n   Pdu:    ~w"
		  "~n   Reason: ~w",[Pdu, Reason]),
	    error;
	{error, Reason} ->
	    error("version-3 message encoding error"
		  "~n   Pdu:    ~w"
		  "~n   Reason: ~w",[Pdu, Reason]),
	    error;
	Packet ->
	    Packet
    end;
mk_msg(Version, Pdu, {Com, _User, _EngineID, _Ctx, _SecLevel}, _SecData) ->
    Msg = #message{version = Version, vsn_hdr = Com, data = Pdu},
    case catch snmp_pdus:enc_message(Msg) of
	{'EXIT', Reason} ->
	    error("~w encoding error"
		  "~n   Pdu:    ~w"
		  "~n   Reason: ~w",[Version, Pdu, Reason]),
	    error;
	B when is_list(B) -> 
	    B
    end.

format_hdr(#message{version = 'version-3', 
		    vsn_hdr = #v3_hdr{msgID = MsgId},
		    data = #scopedPdu{contextName = CName}}) ->
    io_lib:format("v3, ContextName = \"~s\"  Message ID = ~w\n",
		  [CName, MsgId]);
format_hdr(#message{version = Vsn, vsn_hdr = Com}) ->
    io_lib:format("~w, CommunityName = \"~s\"\n", [vsn(Vsn), Com]).

vsn('version-1') -> v1;
vsn('version-2') -> v2c.


udp_send(UdpId, AgentIp, UdpPort, B) ->
    case (catch gen_udp:send(UdpId, AgentIp, UdpPort, B)) of
	{error,ErrorReason} ->
	    error("failed (error) sending message to ~p:~p: "
		  "~n   ~p",[AgentIp, UdpPort, ErrorReason]);
	{'EXIT',ExitReason} ->
	    error("failed (exit) sending message to ~p:~p:"
		  "~n   ~p",[AgentIp, UdpPort, ExitReason]);
	_ ->
	    ok
    end.


get_pdu(#message{version = 'version-3', data = #scopedPdu{data = Pdu}}) ->
    Pdu;
get_pdu(#message{data = Pdu}) ->
    Pdu.

set_pdu(Msg, RePdu) when Msg#message.version == 'version-3' ->
    SP = (Msg#message.data)#scopedPdu{data = RePdu}, 
    Msg#message{data = SP};
set_pdu(Msg, RePdu) ->
    Msg#message{data = RePdu}.


init_usm('version-3', Dir) ->
    ?vlog("init_usm -> create (and init) fake \"agent\" table", []),
    ets:new(snmp_agent_table, [set, public, named_table]),
    ets:insert(snmp_agent_table, {agent_mib_storage, persistent}),
    %% The local-db process may *still* be running (from a previous
    %% test case), on the way down, but not yet dead.
    %% Either way, before we start it, make sure its dead and *gone*!
    %% How do we do that without getting hung up? Calling the stop
    %% function, will not do since it uses Timeout=infinity.
    ?vlog("init_usm -> ensure (old) fake local-db is dead", []),
    ensure_local_db_dead(),
    ?vlog("init_usm -> try start fake local-db", []),
    case snmpa_local_db:start_link(normal, Dir,
                                   [{sname,     "MGR-LOCAL-DB"},
                                    {verbosity, trace}]) of
        {ok, Pid} ->
            ?vlog("started: ~p"
                  "~n      ~p", [Pid, process_info(Pid)]);
        {error, {already_started, Pid}} ->
            LDBInfo = process_info(Pid),
            ?vlog("already started: ~p"
                  "~n      ~p", [Pid, LDBInfo]),
            ?FAIL({still_running, snmpa_local_db, LDBInfo});
        {error, Reason} ->
            ?FAIL({failed_starting, snmpa_local_db, Reason})
    end,
    NameDb = snmpa_agent:db(snmpEngineID),
    ?vlog("init_usm -> try set manager engine-id", []),
    R = snmp_generic:variable_set(NameDb, "mgrEngine"),
    snmp_verbosity:print(info, info, "init_usm -> engine-id set result: ~p", [R]),
    ?vlog("init_usm -> try set engine boots (framework-mib)", []),
    snmp_framework_mib:set_engine_boots(1),
    ?vlog("init_usm -> try set engine time (framework-mib)", []),
    snmp_framework_mib:set_engine_time(1),
    ?vlog("init_usm -> try usm (mib) reconfigure", []),
    snmp_user_based_sm_mib:reconfigure(Dir),
    ?vlog("init_usm -> done", []),
    ok;
init_usm(_Vsn, _Dir) ->
    ok.

ensure_local_db_dead() ->
    ensure_dead(whereis(snmpa_local_db), 2000).

ensure_dead(Pid, Timeout) when is_pid(Pid) ->
    MRef = erlang:monitor(process, Pid),
    try
        begin
            ensure_dead_wait(Pid, MRef, Timeout),
            ensure_dead_stop(Pid, MRef, Timeout),
            ensure_dead_kill(Pid, MRef, Timeout),
            exit(failed_stop_local_db)
        end
    catch
        throw:ok ->
            ok
    end;
ensure_dead(_, _) ->
    ?vlog("ensure_dead -> already dead", []),
    ok.

ensure_dead_wait(Pid, MRef, Timeout) ->
    receive
        {'DOWN', MRef, process, Pid, _Info} ->
            ?vlog("ensure_dead_wait -> died peacefully", []),
            throw(ok)
    after Timeout ->
            ?vlog("ensure_dead_wait -> giving up", []),
            ok
    end.

ensure_dead_stop(Pid, MRef, Timeout) ->
    StopPid = spawn(fun() -> snmpa_local_db:stop() end),
    receive
        {'DOWN', MRef, process, Pid, _Info} ->
            ?vlog("ensure_dead -> dead (stopped)", []),
            throw(ok)
    after Timeout ->
            ?vlog("ensure_dead_stop -> giving up", []),
            exit(StopPid, kill),
            ok
    end.

ensure_dead_kill(Pid, MRef, Timeout) ->
    exit(Pid, kill),
    receive
        {'DOWN', MRef, process, Pid, _Info} ->
            ?vlog("ensure_dead -> dead (killed)", []),
            throw(ok)
    after Timeout ->
            ?vlog("ensure_dead_kill -> giving up", []),
            ok
    end.



display_incomming_message(M) ->
    display_message("Incomming",M).

display_outgoing_message(M) ->
    display_message("Outgoing", M).

display_message(Direction, M) when is_record(M, message) ->
    io:format("~s SNMP message:~n", [Direction]),
    V = M#message.version,
    display_version(V),
    display_hdr(V, M#message.vsn_hdr),
    display_msg_data(V, Direction, M#message.data);
display_message(Direction, M) ->
    io:format("~s message unknown: ~n~p", [Direction, M]).

display_version('version-3') ->
    display_prop("Version",'SNMPv3');
display_version(V) ->
    display_prop("Version",V).

display_hdr('version-3',H) ->
    display_msgID(H#v3_hdr.msgID),
    display_msgMaxSize(H#v3_hdr.msgMaxSize),
    display_msgFlags(H#v3_hdr.msgFlags),
    SecModel = H#v3_hdr.msgSecurityModel,
    display_msgSecurityModel(SecModel),
    display_msgSecurityParameters(SecModel,H#v3_hdr.msgSecurityParameters);
display_hdr(_V,Community) -> 
    display_community(Community).

display_community(Community) ->
    display_prop("Community",Community).

display_msgID(Id) ->
    display_prop("msgID",Id).

display_msgMaxSize(Size) ->
    display_prop("msgMaxSize",Size).

display_msgFlags([Flags]) ->
    display_prop("msgFlags",Flags);
display_msgFlags([]) ->
    display_prop("msgFlags",no_value_to_display);
display_msgFlags(Flags) ->
    display_prop("msgFlags",Flags).

display_msgSecurityModel(?SEC_USM) ->
    display_prop("msgSecurityModel",'USM');
display_msgSecurityModel(Model) ->
    display_prop("msgSecurityModel",Model).

display_msgSecurityParameters(?SEC_USM,Params) ->
    display_usmSecurityParameters(Params);
display_msgSecurityParameters(_Model,Params) ->
    display_prop("msgSecurityParameters",Params).

display_usmSecurityParameters(P) when is_list(P) ->
    P1 = lists:flatten(P),
    display_usmSecurityParameters(snmp_pdus:dec_usm_security_parameters(P1));
display_usmSecurityParameters(P) when is_record(P,usmSecurityParameters) ->
    ID = P#usmSecurityParameters.msgAuthoritativeEngineID,
    display_msgAuthoritativeEngineID(ID),
    Boots = P#usmSecurityParameters.msgAuthoritativeEngineBoots,
    display_msgAuthoritativeEngineBoots(Boots),
    Time = P#usmSecurityParameters.msgAuthoritativeEngineTime,
    display_msgAuthoritativeEngineTime(Time),
    Name = P#usmSecurityParameters.msgUserName,
    display_msgUserName(Name),
    SecParams = P#usmSecurityParameters.msgAuthenticationParameters,
    display_msgAuthenticationParameters(SecParams),
    PrivParams = P#usmSecurityParameters.msgPrivacyParameters,
    display_msgPrivacyParameters(PrivParams);
display_usmSecurityParameters(P) ->
    display_prop("unknown USM sec paraams",P).

display_msgAuthoritativeEngineID(ID) ->
    display_prop("msgAuthoritativeEngineID",ID).

display_msgAuthoritativeEngineBoots(V) ->
    display_prop("msgAuthoritativeEngineBoots",V).

display_msgAuthoritativeEngineTime(V) ->
    display_prop("msgAuthoritativeEngineTime",V).

display_msgUserName(V) ->
    display_prop("msgUserName",V).

display_msgAuthenticationParameters(V) ->
    display_prop("msgAuthenticationParameters",V).

display_msgPrivacyParameters(V) ->
    display_prop("msgPrivacyParameters",V).

display_msg_data('version-3',Direction,D) when is_record(D,scopedPdu) ->
    display_scoped_pdu(Direction,D);
display_msg_data(_Version,Direction,D) when is_record(D,pdu) ->
    display_pdu(Direction,D);
display_msg_data(_Version,_Direction,D) ->
    display_prop("Unknown message data",D).

display_scoped_pdu(Direction,P) ->
    display_contextEngineID(P#scopedPdu.contextEngineID),
    display_contextName(P#scopedPdu.contextName),
    display_scoped_pdu_data(Direction,P#scopedPdu.data).

display_contextEngineID(Id) ->
    display_prop("contextEngineID",Id).

display_contextName(Name) ->
    display_prop("contextName",Name).

display_scoped_pdu_data(Direction,D) when is_record(D,pdu) ->
    display_pdu(Direction,D);
display_scoped_pdu_data(Direction,D) when is_record(D,trappdu) ->
    display_trappdu(Direction,D);
display_scoped_pdu_data(_Direction,D) ->
    display_prop("Unknown scoped pdu data",D).

display_pdu(Direction, P) ->
    io:format("~s PDU:~n", [Direction]),
    display_type(P#pdu.type),
    display_request_id(P#pdu.request_id),
    display_error_status(P#pdu.error_status),
    display_error_index(P#pdu.error_index),
    display_varbinds(P#pdu.varbinds).

display_type(T) ->
    display_prop("Type",T).

display_request_id(Id) ->
    display_prop("Request id",Id).

display_error_status(S) ->
    display_prop("Error status",S).

display_error_index(I) ->
    display_prop("Error index",I).

display_varbinds([H|T]) ->
    display_prop_hdr("Varbinds"),
    display_varbind(H),
    display_varbinds(T);
display_varbinds([]) ->
    ok.

display_varbind(V) when is_record(V,varbind) ->
    display_oid(V#varbind.oid),
    display_vtype(V#varbind.variabletype),
    display_value(V#varbind.value),
    display_org_index(V#varbind.org_index);
display_varbind(V) ->
    display_prop("\tVarbind",V).

display_oid(V) ->
    display_prop("\tOid",V).
    
display_vtype(V) ->
    display_prop("\t\tVariable type",V).
    
display_value(V) ->
    display_prop("\t\tValue",V).
    
display_org_index(V) ->
    display_prop("\t\tOrg index",V).

display_trappdu(Direction,P) ->
    io:format("~s TRAP-PDU:~n",[Direction]),
    display_prop("TRAP-PDU",P).

display_prop(S,no_value_to_display) ->
    io:format("\t~s: ~n",[S]);
display_prop(S,V) ->
    io:format("\t~s: ~p~n",[S,V]).


display_prop_hdr(S) ->
    io:format("\t~s:~n",[S]).


%%----------------------------------------------------------------------
%% Debug
%%----------------------------------------------------------------------

sz(L) when is_list(L) ->
    length(lists:flatten(L));
sz(B) when is_binary(B) ->
    size(B);
sz(O) ->
    {unknown_size, O}.

d(F)   -> d(F, []).
d(F,A) -> d(get(debug), F, A).

d(true, F, A) ->
    print(F, A);
d(_,_F,_A) -> 
    ok.

print(F, A) ->
    ?PRINT2("MGR_PS " ++ F, A).