/*_############################################################################
  _## 
  _##  uxsnmp.h  
  _##
  _##  SNMP++v3.2.25
  _##  -----------------------------------------------
  _##  Copyright (c) 2001-2010 Jochen Katz, Frank Fock
  _##
  _##  This software is based on SNMP++2.6 from Hewlett Packard:
  _##  
  _##    Copyright (c) 1996
  _##    Hewlett-Packard Company
  _##  
  _##  ATTENTION: USE OF THIS SOFTWARE IS SUBJECT TO THE FOLLOWING TERMS.
  _##  Permission to use, copy, modify, distribute and/or sell this software 
  _##  and/or its documentation is hereby granted without fee. User agrees 
  _##  to display the above copyright notice and this license notice in all 
  _##  copies of the software and any documentation of the software. User 
  _##  agrees to assume all liability for the use of the software; 
  _##  Hewlett-Packard and Jochen Katz make no representations about the 
  _##  suitability of this software for any purpose. It is provided 
  _##  "AS-IS" without warranty of any kind, either express or implied. User 
  _##  hereby grants a royalty-free license to any and all derivatives based
  _##  upon this software code base. 
  _##  
  _##  Stuttgart, Germany, Thu Sep  2 00:07:47 CEST 2010 
  _##  
  _##########################################################################*/

#ifndef _UXSNMP_H_
#define _UXSNMP_H_

#include "snmp_pp/reentrant.h"
#include "snmp_pp/target.h"
#include "snmp_pp/oid.h"
#include "snmp_pp/address.h"

#ifdef SNMP_PP_NAMESPACE
namespace Snmp_pp {
#endif

#define SNMP_PP_WITH_UDPADDR // Snmp class has constructor with UdpAddress

//-----[ internally used defines ]----------------------------------------
#define MAXNAME 80                   // maximum name length
#define MAX_ADDR_LEN 10              // maximum address len, ipx is 4+6
#define SNMP_SHUTDOWN_MSG 0x0400+177 // shut down msg for stoping a blocked message
#ifndef INVALID_SOCKET 
#define INVALID_SOCKET ((SnmpSocket)(~0)) // value for invalid socket
#endif

//-----[ async defines for engine ]---------------------------------------
#define sNMP_PDU_GET_ASYNC       21
#define sNMP_PDU_GETNEXT_ASYNC   22
#define sNMP_PDU_SET_ASYNC       23
#define sNMP_PDU_GETBULK_ASYNC   24
#define sNMP_PDU_INFORM_ASYNC    25

//-----[ trap / notify macros ]-------------------------------------------
#define IP_NOTIFY  162     // IP notification
#define IPX_NOTIFY 0x2121  // IPX notification

//------[ forward declaration of Snmp class ]-----------------------------
class Snmp;
class EventListHolder;
class Pdu;
class v3MP;

//-----------[ async methods callback ]-----------------------------------
/**
 * Async methods of the class Snmp require the caller to provide a
 * callback address of a function with this typedef.
 *
 * @note It is not allowed to call any synchronous Snmp methods within the
 *       callback. Async methods are allowed.
 *
 * @param reason  - Reason for callback (see snmperrs.h)
 * @param session - Pointer to Snmp object that was used to send the request
 * @param pdu     - The received Pdu if reason indicates a received message
 * @param target  - source target
 * @param data    - Pointer passed to the async method
 */
typedef void (*snmp_callback)(int reason, Snmp *session,
                               Pdu &pdu, SnmpTarget &target, void *data);


//------------[ SNMP Class Def ]---------------------------------------------
//
/**
 * SNMP class defintion. The Snmp class provides an object oriented
 * approach to SNMP. The SNMP class is an encapsulation of SNMP
 * sessions, gets, sets and get nexts. The class manages all SNMP
 * resources and provides complete retry and timeout capability.
 *
 * This class is thread save.
 *
 * @note If you use the async methods to send requests you MUST call
 *       Snmp::eventListHolder->SNMPProcessPendingEvents() while waiting
 *       for the responses. This function triggers the resend of
 *       packets and calls your callback function if the response is
 *       received.
 *
 * @note Call srand() before creating the first Snmp object.
 */
class DLLOPT Snmp: public SnmpSynchronized
{
 public:
  //------------------[ constructors ]----------------------------------

  /** @name Constructors and Destructor */
  //@{

  /**
   * Construct a new SNMP session using the given UDP port.
   *
   * @param status
   *    after creation of the session this parameter will 
   *    hold the creation status.
   * @param port
   *    an UDP port to be used for the session
   * @param bind_ipv6
   *    Set this to true if IPv6 should be used. The default is
   *    IPv4.
   */
  Snmp(int &status, const unsigned short port = 0,
       const bool bind_ipv6 = false);

  /**
   * Construct a new SNMP session using the given UDP address.
   * Thus, binds the session on a specific IPv4 or IPv6 address. 
   *
   * @param status
   *    after creation of the session this parameter will 
   *    hold the creation status.
   * @param addr
   *    an UDP address to be used for the session
   */	
  Snmp(int &status, const UdpAddress &addr);

  /**
   * Construct a new SNMP session using the given UDP addresses.
   * Using this constructor will bind to both IPv4 and IPv6 ports.
   *
   * @param status
   *    after creation of the session this parameter will 
   *    hold the creation status.
   * @param addr_v4
   *    an IPv4 UDP address to be used for the session
   * @param addr_v6
   *    an IPv6 UDP address to be used for the session
   */	
  Snmp(int &status,  const UdpAddress& addr_v4, const UdpAddress& addr_v6);

  //-------------------[ destructor ]------------------------------------
  /**
   * Destructor.
   */
  virtual ~Snmp();

  //@}

  //--------[ Get the version of the snmp++ library ]--------------------
  /**
   * Get the version of the snmp++ library.
   *
   * @return The version of the snmp++ lib at runtime.
   */
  static const char *get_version();


  //-------------------[ returns error string ]--------------------------
  /**
   * Returns a human readable error string.
   *
   * @param c - Error code returned by any method of this class
   * @return Null terminated error string.
   */
  static const char *error_msg(const int c);
#ifdef _SNMPv3
  /**
   * Returns a human readable error string.
   * If a report message is returned, then the contained Oid can be
   * used to get a error string.
   *
   * @param v3Oid - Oid of a SNMPv3 report Pdu
   * @return Null terminated error string.
   */
  static const char* error_msg(const Oid& v3Oid);
#endif

  //------------------------[ Windows Sockets ]----------------------------

  /**
   * Initialize the Winsock library (WSAStartup).
   *
   * @note on Win32 this method *must* be called before creating Snmp or
   *       Address objects.
   */
  static void socket_startup();


  /**
   * Shut down the Winsock library (WSACleanup).
   */
  static void socket_cleanup();

  //------------------------[ send requests ]------------------------------

  /** @name Sending SNMP Pdus
   */
  //@{

  /**
   * Send a blocking SNMP-GET request.
   *
   * @param pdu    - Pdu to send
   * @param target - Target for the get
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int get(Pdu &pdu, const SnmpTarget &target);

  /**
   * Send a async SNMP-GET request.
   *
   * @param pdu      - Pdu to send
   * @param target   - Target for the get
   * @param callback - User callback function to use
   * @param callback_data - User definable data pointer
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int get(Pdu &pdu, const SnmpTarget &target,
		  const snmp_callback callback,
		  const void *callback_data = 0);

  /**
   * Send a blocking SNMP-GETNEXT request.
   *
   * @param pdu    - Pdu to send
   * @param target - Target for the getnext
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int get_next(Pdu &pdu, const SnmpTarget &target);

  /**
   * Send a async SNMP-GETNEXT request.
   *
   * @param pdu      - Pdu to send
   * @param target   - Target for the getnext
   * @param callback - User callback function to use
   * @param callback_data - User definable data pointer
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int get_next(Pdu &pdu, const SnmpTarget &target,
                       const snmp_callback callback,
		       const void *callback_data = 0);

  /**
   * Send a blocking SNMP-SET request.
   *
   * @param pdu    - Pdu to send
   * @param target - Target for the set
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int set(Pdu &pdu, const SnmpTarget &target);

  /**
   * Send a async SNMP-SET request.
   *
   * @param pdu      - Pdu to send
   * @param target   - Target for the set
   * @param callback - User callback function to use
   * @param callback_data - User definable data pointer
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int set(Pdu &pdu, const SnmpTarget &target,
		  const snmp_callback callback,
		  const void * callback_data = 0);

  /**
   * Send a blocking SNMP-GETBULK request.
   *
   * @param pdu           - Pdu to send
   * @param target        - Target for the getbulk
   * @param non_repeaters - number of non repeaters
   * @param max_reps      - maximum number of repetitions
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int get_bulk(Pdu &pdu, const SnmpTarget &target,
		       const int non_repeaters, const int max_reps);

  /**
   * Send a async SNMP-GETBULK request.
   *
   * @param pdu           - Pdu to send
   * @param target        - Target for the getbulk
   * @param non_repeaters - number of non repeaters
   * @param max_reps      - maximum number of repetitions
   * @param callback      - User callback function to use
   * @param callback_data - User definable data pointer
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int get_bulk(Pdu &pdu, const SnmpTarget &target,
		       const int non_repeaters, const int max_reps,
		       const snmp_callback callback,
		       const void *callback_data = 0);

  /**
   * Send a SNMP-TRAP.
   *
   * @param pdu    - Pdu to send
   * @param target - Target for the trap
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int trap(Pdu &pdu, const SnmpTarget &target);


  /**
   * Send a SNMPv3-REPORT.
   *
   * @param pdu    - Pdu to send
   * @param target - Target for the report (must be a UTarget)
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int report(Pdu &pdu, const SnmpTarget &target);

  /**
   * Send a blocking INFORM-REQ.
   *
   * @param pdu    - Pdu to send
   * @param target - Target for the inform
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int inform(Pdu &pdu, const SnmpTarget &target);

  /**
   * Send a async INFORM-REQ.
   *
   * @param pdu    - Pdu to send
   * @param target - Target for the inform
   * @param callback      - User callback function to use
   * @param callback_data - User definable data pointer
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int inform(Pdu &pdu, const SnmpTarget &target,
		     const snmp_callback callback,
		     const void * callback_data = 0);

  /**
   * Send a RESPONSE.
   *
   * @param pdu    - Pdu to send
   * @param target - Target for the response
   * @param fd     - file descriptor to use, should be the one
   *                 that was passed to the callback function
   *
   * @return SNMP_CLASS_SUCCES or a negative error code
   */
  virtual int response(Pdu &pdu, const SnmpTarget &target,
		       const SnmpSocket fd = INVALID_SOCKET);


  /**
   * Send a SNMP Broadcast message.
   *
   * This member function sends out a valid SNMP message to a
   * broadcast address and waits for responses. The source addresses
   * of the response messages are added to the collection.
   *
   * The message is sent only once.
   *
   * @note SNMP_BROADCAST has to be defined in config_snmp_pp.h.
   *
   * @note There is no SNMP standard that defines "SNMP Broadcast
   *       discovery". SNMP agents are not forced to answer requests
   *       that are sent to a broadcast address.
   *
   * @note Do not use this method while waiting for other responses,
   *       as these responses will be added to the collection and dropped
   *       by this method. Solution for this problem: Use a special
   *       Snmp object only for broadcasts.
   *
   * @param addresses   - The addresses of the agents, that answered.
   * @param timeout_sec - Timeout in seconds
   * @param addr        - Broadcast address
   * @param version     - SNMP version to use
   * @param community   - Only needed for SNMPv1/v2c, defaults to "public"
   *
   */
  virtual int broadcast_discovery(UdpAddressCollection &addresses,
				  const int timeout_sec,
				  const UdpAddress &addr,
				  const snmp_version version,
				  const OctetStr *community = 0);

#ifdef _SNMPv3
  virtual int engine_id_discovery(OctetStr &engine_id,
				  const int timeout_sec,
				  const UdpAddress &addr);
#endif
  //@}

  /**
   * Cancel a pending request.
   *
   * @param rid - The request id to cancel
   *
   * @return SNMP_CLASS_SUCCES or SNMP_CLASS_INVALID_REQID on failure
   */
  virtual int cancel(const unsigned long rid);


  /** @name Trap and Inform handling
   */
  //@{

  /**
   * Set the port for listening to traps and informs.
   *
   * @note This function must be called before notify_register(),
   *       otherwise the default port is used.
   */
  virtual void notify_set_listen_port(const int port);

  /**
   * Get the port that is used for listening to traps and informs.
   */
  virtual int notify_get_listen_port();

  /**
   * Register to get traps and informs.
   *
   * @note Every call to one of the notify_register() methods overwrites
   *       the previous given values.
   *
   * @param trapids       - ids to listen for
   * @param targets       - targets to listen for
   * @param callback      - User callback function to use
   * @param callback_data - User definable data pointer
   *
   * @return SNMP_CLASS_SUCCESS, SNMP_CLASS_TL_FAILED or SNMP_CLASS_TL_IN_USE
   */
  virtual int notify_register(const OidCollection    &trapids,
                              const TargetCollection &targets,
                              const snmp_callback callback,
                              const void *callback_data=0);

  /**
   * Unregister to get traps and informs.
   * Undo the call to notify_register().
   *
   * @return Always SNMP_CLASS_SUCCESS
   */
  virtual int notify_unregister();

  /**
   * Get notify register info.
   *
   * @param trapids       - ids listened for
   * @param targets       - targets listened for
   *
   * @return SNMP_CLASS_SUCCESS or SNMP_CLASS_INVALID if not registered
   */
  virtual int get_notify_filter(OidCollection &trapids,
				 TargetCollection &targets);

  //-----------------------[ access the trap reception info ]---------------
  /**
   * Get a pointer to the callback function used for trap reception.
   *
   * @return Pointer to the function set through notify_register()
   */
  snmp_callback get_notify_callback() { return notifycallback; };

  /**
   * Get a pointer to the data that is passed to the callback function.
   *
   * @return Pointer to the data set through notify_register()
   */
  void *get_notify_callback_data() { return notifycallback_data; };

  //@}

  /**
   * Send raw UDP data.
   * This method may be used to send any data to the recepient.
   *
   * @param send_buf - Data buffer
   * @param send_len - Length of the data
   * @param address  - Recepient
   * @param fd       - socket to use, if not specified, the socket of the
   *                   object is used
   *
   * @return 0 on success, -1 on failure
   */
  virtual int send_raw_data(unsigned char *send_buf,
                            size_t send_len, UdpAddress &address,
                            SnmpSocket fd = INVALID_SOCKET);

  const IpAddress &get_listen_address() const {return listen_address; };

  /**
   * Start one thread listening for responses and notifications.
   * This method is used to start response and notification processing in a
   * multi threadded setup.
   *
   * @note start_poll_thread() itself is not thread safe. The caller must make
   *       sure that only one thread is calling start_poll_thread() or
   *       stop_poll_thread() at any point in time.
   *
   * @param timeout - Timeout for each call of the select() or poll()
   *                  system call.
   *
   * @return true if the thread is now running and false if it failed to start.
   */
  bool start_poll_thread(const int timeout);

  /**
   * Stop the thread listening for responses and notifications.
   * This method is used to stop the thread started with start_poll_thread().
   *
   * @note stop_poll_thread() itself is not thread safe. The caller must make
   *       sure that only one thread is calling start_poll_thread() or
   *       stop_poll_thread() at any point in time.
   */
  void stop_poll_thread();

  EventListHolder *get_eventListHolder() { return eventListHolder; };

protected:

  /**
   * Check for the status of the worker thread.
   * @return BOOL - TRUE - if running, FALSE - otherwise
   */
  bool is_running(void) const
      { return m_bThreadRunning; };
	
  /**
   * This is a working thread for the recovery of the pending events.
   *
   * @param pSnmp [in] pointer to the whole object
   *
   * @return int
   *          0 - if succesful,
   *          1 - in the case of error
   */
#ifdef WIN32
  static int process_thread(Snmp *pSnmp);
#else
  static void* process_thread(void *arg);
#endif

 protected:

  /**
   * Generate a unique (for this Snmp obect) request id.
   *
   * @return Unique id between PDU_MIN_RID and PDU_MAX_RID
   */
  long MyMakeReqId();

  /**
   * Common init function used by constructors.
   */
  void init(int& status, IpAddress*[2],
            const unsigned short port_v4, const unsigned short port_v6);

  /**
   * Set the notify timestamp of a trap pdu if the user did not set it.
   */
  void check_notify_timestamp(Pdu &pdu);

  //-----------[ Snmp Engine ]----------------------------------------
  /**
   * gets, sets and get nexts go through here....
   * This mf does all snmp sending and reception
   * except for traps which are sent using trap().
   *
   * @note that for a UTarget with an empty engine id the
   *       Utarget::set_engine_id() may be called.
   */
  int snmp_engine( Pdu &pdu,                  // pdu to use
                   long int non_reps,         // get bulk only
                   long int max_reps,         // get bulk only
                   const SnmpTarget &target,        // destination target
                   const snmp_callback cb,    // async callback function
                   const void *cbd,          // callback data
		   SnmpSocket fd = INVALID_SOCKET,
		   int reports_received = 0);

  //--------[ map action ]------------------------------------------------
  // map the snmp++ action to a SMI pdu type
  void map_action(unsigned short action, unsigned short &pdu_action);

#ifdef _SNMPv3
  /**
   * Internal used callback data structure for async v3 requests.
   */
  struct V3CallBackData
  {
    Pdu *pdu;                  ///< The Pdu that was sent
    long int non_reps;         ///< For GET-BULK requests
    long int max_reps;         ///< For GET-BULK requests
    SnmpTarget *target;        ///< Pointer to the Target object to use
    snmp_callback oldCallback; ///< User callback function
    const void *cbd;           ///< User callback data
    int reports_received;      ///< How many reports are already received
  };

  friend void v3CallBack(int reason, Snmp *snmp, Pdu &pdu,
			 SnmpTarget &target, void *v3cd);
  friend void deleteV3Callback(struct Snmp::V3CallBackData *&cbData);
#endif

  //---[ instance variables ]
  SnmpSocket iv_snmp_session;
#ifdef SNMP_PP_IPv6
  SnmpSocket iv_snmp_session_ipv6;
#endif

  IpAddress listen_address;
  long current_rid;                   // current rid to use

  // inform receive member variables
  snmp_callback  notifycallback;
  void * notifycallback_data;

  // this member var will simulate a global var
  EventListHolder *eventListHolder;

private:

  bool m_bThreadRunning;
  int m_iPollTimeOut;

  // Keep track of the thread.
#ifdef _THREADS
#ifdef WIN32
  HANDLE m_hThread;
  HANDLE m_hThreadEndEvent;
#elif defined (CPU) && CPU == PPC603
  int m_hThread;
#else
  pthread_t m_hThread;
#endif
#endif
};

#ifdef SNMP_PP_NAMESPACE
} // end of namespace Snmp_pp
#endif 

#endif
