// 
//    jSNMP - SNMPv1 & v2 Compliant Libraries for Java
//    Copyright (C) 2000  PlatformWorks, Inc.
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Lesser General Public
//    License as published by the Free Software Foundation; either
//    version 2.1 of the License, or (at your option) any later version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//    Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public
//    License along with this library; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//   
// For more information contact: 
//  Brian Weaver    <weave@opennms.org>
//  http://www.opennms.org/
//
//
// Tab Size = 8
//
// 9/8/00 - Bob Snider  <bsnider@seekone.com>
//      Adapted from SnmpTrapSession

package org.opennms.protocols.snmp;

import java.lang.*;
import java.net.*;
import java.io.*;
import java.util.*;

//import org.opennms.protocols.snmp.*;
import org.opennms.protocols.snmp.asn1.*;

/**
 * <P>The agent session is used to receive SNMPv1 Get, GetNext
 * and Set messages. The messages are received
 * on the configured port, or the default(162) port and
 * then decoded using the set ASN.1 codec.</P>
 *
 * @author  <a href="http://www.opennms.org/">OpenNMS</a>
 * @author  <a href="mailto:bsnider@seekone.com">Bob Snider</a>
 *
 * @see SnmpAgentHandler
 */
public final class SnmpAgentSession extends Object
{
  /**
   * <P>Defines a error due to a thown exception. When the
   * SnmpAgentSessionError method is invoked in the trap
   * handler, the exception object is passed as the ref
   * parameter.</P>
   *
   * @see SnmpAgentSession#SnmpAgentSessionError
   *
   */
  public final static int   ERROR_EXCEPTION     = -1;

  /**
   * <P>Defines an error condition with an invalid
   * PDU. For the moment this is not actually used,
   * but reserved for future use. When the session
   * trap handler error method is invoke the pdu
   * in error should be passed as the ref parameters
   *
   * @see SnmpAgentSession#SnmpAgentSessionError
   */
  public final static int ERROR_INVALID_PDU = -2;

  /**
   * This is the default port where traps should be sent
   * and received as defined by the RFC. 
   *
   */
  public final static int DEFAULT_PORT  = 161;
  
  /**
   * This is the default community string for Gets
   */
  private final static String DEFAULT_WRITE_COMMUNITY = "private";

  /**
   * The default port were traps are sent and
   * received by this session.
   */
  private int m_port;   
     
  /**
   * The default SNMP trap callback handler. If this is not
   * set and it is needed then an SnmpHandlerNotDefinedException
   * is thrown.
   *
   */
  private SnmpPortal m_portal;

  /**
   * ASN.1 codec used to encode/decode snmp
   * traps that are sent and received by this
   * session.
   */
  private AsnEncoder m_encoder;

  /**
   * The public trap handler that process
   * received traps.
   *
   */
  private SnmpAgentHandler m_handler;

  /**
   * If this boolean value is set then the receiver
   * thread is terminated due to an exception that
   * was generated in either a handler or a socket
   * error. This is considered a fatal exception.
   */
  private boolean   m_threadException;

  /**
   * This is the saved fatal exception that 
   * can be rethrown by the application
   */
  private Throwable m_why;

  /**
   * Used to hold the community strings for validation, and the
   * agent port to listen on.
   */
  private SnmpPeer m_peer;
  
  /**
   * <P>The internal trap handler class is designed to 
   * receive information from the enclosed SnmpPortal
   * class. The information is the processed and forwarded
   * when appropiate to the SnmpAgentHandler registered
   * with the session.</P>
   *
   */
  private class AgentHandler 
    implements SnmpPacketHandler
  {
    /**
     * Who to pass as the session parameter
     */
    private SnmpAgentSession m_forWhom;

    /**
     * <P>Creates an internal agent handler to be
     * the intermediary for the interface between
     * the SnmpPortal and the Session.</P>
     *
     * @param sess The agent session reference.
     *
     */
    public AgentHandler(SnmpAgentSession sess)
    {
      m_forWhom = sess;
    }
        
    /**
     * <P>Processes the default V1 & V2 messages.</P>
     *
     * @param manager   The sending manager
     * @param port  The remote port.
     * @param version   The SNMP Version of the message.
     * @param community The community string from the message.
     * @param pduType   The type of pdu
     * @param pdu   The actual pdu
     *
     * @exception SnmpPduEncodingException Thrown if the pdu fails to decode.
     */
    public void processSnmpMessage(InetAddress  manager,
                   int      port,
                   SnmpInt32    version,
                   SnmpOctetString  community,
                   int      pduType,
                   SnmpPduPacket    pdu) throws SnmpPduEncodingException
    {
      try
    {
      int cmd = pdu.getCommand();
      // System.out.println("PDU command......... " + cmd);
      String tst = new String(community.getString());
      String rd  = m_peer.getParameters().getReadCommunity();
      String wr  = m_peer.getParameters().getWriteCommunity();
      switch(cmd)
        {
        case SnmpPduPacket.GET:
          // validate request
          if(tst.equals(rd))
            handleResponse(manager, port, pdu, m_handler.snmpReceivedGet(pdu, false));
          break;

        case SnmpPduPacket.GETNEXT:
          // validate request
          if(tst.equals(rd))
              handleResponse(manager, port, pdu, m_handler.snmpReceivedGet(pdu, true));
          break;

        case SnmpPduPacket.SET:
          // validate request
          if(tst.equals(wr))
              handleResponse(manager, port, pdu, m_handler.snmpReceivedSet(pdu));
          break;

        default:
          m_handler.snmpReceivedPdu(m_forWhom,
                    manager,
                    port,
                    community,
                    pdu);
          break;
        }
    }
      catch(Exception e)
    {
                // discard
    }
    }

    private void handleResponse(InetAddress manager,
                                int     port,
                                SnmpPduPacket   pdu,
                                SnmpPduRequest  response)
    {
        if (response != null) {
            response.setRequestId(pdu.getRequestId());
            try {
                m_forWhom.send(new SnmpPeer(manager, port), response);
                // System.out.println("Response Sent");
            }
            catch (Exception e) {
                System.out.println("Error sending response "+e);
            }
        }
    }

    /**
     * <P>Processes V1 trap messages.</P>
     *
     * @param agent     The sending agent
     * @param port      The remote port.
     * @param community The community string from the message.
     * @param pdu       The actual pdu
     *
     * @exception SnmpPduEncodingException Thrown if the pdu fails to decode.
     */
    public void processSnmpTrap(InetAddress agent, 
                int     port, 
                SnmpOctetString community,
                SnmpPduTrap pdu) throws SnmpPduEncodingException
    {
      // discard
    }


    /** 
     * <P>Invoked when bad datagrams are received.</P>
     *
     * @param p The datagram packet in question.
     *
     */
    public void processBadDatagram(DatagramPacket p)
    {
      // do nothing - discard?
    }

    /**
     * <P>Invoked when an exception occurs in the session.</P>
     *
     * @param e The exception.
     */
    public void processException(Exception e)
    {
      try
    {
      m_handler.SnmpAgentSessionError(m_forWhom,
                      ERROR_EXCEPTION,
                      e);
    }
      catch(Exception e1)
    {
                // discard
    }
    }
  }

  /**
   * Used to disallow the default constructor.
   *
   * @exception java.lang.UnsupportedOperationException Thrown if the
   *    constructor is called.
   *
   */
  private SnmpAgentSession() 
    throws java.lang.UnsupportedOperationException
  {
    throw new java.lang.UnsupportedOperationException("Illegal constructor call");
  }

  /**
   * The SnmpAgentSession constructor that takes a packet
   * handler as parameter.  It will listen on the default port and use
   * the default community strings for validation.
   *
   * @param handler The handler associated for message processing.
   *
   * @exception java.net.SocketException If thrown it is from the creation
   *        of a DatagramSocket.
   * @exception java.lang.SecurityException Thrown if the security manager
   *        disallows the creation of the handler.
   */
  public SnmpAgentSession(SnmpAgentHandler handler) 
    throws SocketException
  {
    this(handler, new SnmpPeer(null, DEFAULT_PORT));
    m_peer.getParameters().setWriteCommunity(DEFAULT_WRITE_COMMUNITY);
  }


  /**
   * The default SnmpAgentSession constructor that takes a packet
   * handler and SnmpPeer as parameter.  
   *
   * The peer is not used for anything except specifying the port to
   * listen to and the community strings for validation.
   *  
   * @param handler The handler associated for message processing.
   * @param peer Specifies the port and community strings.
   *
   * @exception java.net.SocketException If thrown it is from the creation
   *        of a DatagramSocket.
   */
  public SnmpAgentSession(SnmpAgentHandler handler, SnmpPeer peer) 
    throws SocketException
  {
    m_peer      = peer;
    m_port      = peer.getPort();
    m_encoder   = peer.getParameters().getEncoder();
    m_handler   = handler;
    m_portal    = new SnmpPortal(new AgentHandler(this), 
                 m_encoder, 
                 m_port);
    m_threadException= false;
    m_why        = null;
  }

  /**
   * Returns the trap handler for this trap session.
   *
   * @return The SnmpAgentHandler
   */
  public SnmpAgentHandler getHandler()
  {
    return m_handler;
  }

  /**
   * Sets the trap handler for the session.
   *
   * @param hdl The new packet handler
   *
   */
  public void setHandler(SnmpAgentHandler hdl)
  {
    m_handler = hdl;
  }

  /**
   * Sets the default encoder.
   *
   * @param encoder The new encoder
   *
   */
  public void setAsnEncoder(AsnEncoder encoder)
  {
    m_encoder = encoder;
    m_portal.setAsnEncoder(encoder);
  }

  /**
   * Gets the AsnEncoder for the session. 
   *
   * @return the AsnEncoder
   */
  public AsnEncoder getAsnEncoder()
  {
    return m_encoder;
  }


  /**
   * Used to close the session. Once called the session should
   * be considered invalid and unusable.
   *
   */
  public void close()
  {
    m_portal.close();
  }

  /**
   * If an exception occurs in the SNMP receiver
   * thread then raise() will rethrow the exception.
   *
   * @exception java.lang.Throwable The base for thrown
   *    exceptions.
   */
  public void raise() throws Throwable
  {
    if(m_threadException)
      throw m_why;
  }

  /**
   * Transmits the specified SnmpRequest to the SnmpPeer defined.
   * First the SnmpPdu contained within the request is encoded using 
   * the peer AsnEncoder, as defined by the SnmpParameters. Once 
   * the packet is encoded it is transmitted to the agent defined 
   * by SnmpPeer. If an error occurs an appropiate exception is generated.
   *
   * @param peer    The remote peer to send to.
   * @param pdu The pdu to transmit
   *
   * @exception SnmpPduEncodingException    Thrown if an encoding exception 
   *        occurs at the session level
   * @exception org.opennms.protocols.snmp.asn1.AsnEncodingException Thrown 
   *        if an encoding exception occurs in the AsnEncoder object.
   * @exception java.io.IOException Thrown if an error occurs sending the 
   *        encoded datagram
   *
   * @see SnmpRequest
   * @see SnmpParameters
   * @see SnmpPeer
   *
   */
  public void send(SnmpPeer peer, SnmpPduPacket pdu) 
    throws  SnmpPduEncodingException, 
    AsnEncodingException,
    java.io.IOException
  {
    //
    // break down the pieces into usable variables
    //
    SnmpParameters parms = peer.getParameters();


    //
    // Get the encoder and start
    // the encoding process
    //
    AsnEncoder encoder = parms.getEncoder();

    //
    // get a suitable buffer (16k)
    //
    int begin  = 0;
    int offset = 0;
    byte[] buf= new byte[16 * 1024];

    //
    // encode the snmp version
    //
    SnmpInt32 version = new SnmpInt32(parms.getVersion());
    offset = version.encodeASN(buf, offset, encoder);

    //
    // get the correct community string. The
    // SET command uses the write community, all
    // others use the read community
    //
    SnmpOctetString community = new SnmpOctetString(parms.getReadCommunity().getBytes());

    //
    // encode the community strings
    //
    offset = community.encodeASN(buf, offset, encoder);
    offset = pdu.encodeASN(buf, offset, encoder);

    //
    // build the header, don't forget to mark the
    // pivot point
    //
    int pivot = offset;
    offset = encoder.buildHeader(buf, 
                 offset,
                 (byte)(ASN1.SEQUENCE | ASN1.CONSTRUCTOR),
                 pivot);

    //
    // rotate the buffer around the pivot point
    //
    SnmpUtil.rotate(buf, 0, pivot, offset);

    //
    // transmit the datagram
    //
    m_portal.send(peer, buf, offset);
  }

} // end of SnmpAgentSession class