/*
 * AirportBaseStationConfigurator
 *
 * Copyright (C) 2000, Jonathan Sevy <jsevy@mcs.drexel.edu>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */


package snmp;

import java.io.*;
import java.math.*;
import java.net.*;
import java.util.*;


/**
*	The class SNMPv1CommunicationInterface defines methods for communicating with SNMP entities.
*	The approach is that from version 1 of SNMP, using no encryption of data. Communication occurs
*	via UDP, using port 161, the standard SNMP port.
*/

public class SNMPv1CommunicationInterface
{
	public static final int SNMPPORT = 161;
	public static final int MAXSIZE = 512;
	
	private int version;
	private InetAddress hostAddress;
	private String community;
	DatagramSocket dSocket;
	
	public int requestID = 1;
			
	
	
	
	/**
	*	Construct a new communication object to communicate with the specified host using the
	*	given community name. The version setting should be either 0 (version 1) or 1 (version 2,
	*	a la RFC 1157).
	*/
	
	public SNMPv1CommunicationInterface(int version, InetAddress hostAddress, String community)
		throws SocketException
	{
		this.version = version;
		this.hostAddress = hostAddress;
		this.community = community;
		
		dSocket = new DatagramSocket();
		dSocket.setSoTimeout(15000);	//15 seconds
	}
	
	
	
	
	/**
	*	Permits setting timeout value for underlying datagram socket (in milliseconds).
	*/
	
	public void setSocketTimeout(int socketTimeout)
		throws SocketException
	{
		dSocket.setSoTimeout(socketTimeout);
	}
	
	
	
	/**
	*	Close the "connection" with the devive.
	*/
	
	public void closeConnection()
		throws SocketException
	{
		dSocket.close();
	}

	
	
	
	
	
	/**
	*	Retrieve all MIB variable values subsequent to the starting object identifier
	*	given in startID (in dotted-integer notation). Return as SNMPVarBindList object.
	*	Uses SNMPGetNextRequests to retrieve variable values in sequence.
	*	@throws IOException Thrown when timeout experienced while waiting for response to request.
	*	@throws SNMPBadValueException 
	*/
	
	public SNMPVarBindList retrieveAllMIBInfo(String startID)
		throws IOException, SNMPBadValueException
	{
		// send GetNextRequests until receive
		// an error message or a repeat of the object identifier we sent out
		SNMPVarBindList retrievedVars = new SNMPVarBindList();
		
		
		int errorStatus = 0;
		int errorIndex = 0;
		
		SNMPObjectIdentifier requestedObjectIdentifier = new SNMPObjectIdentifier(startID);
		SNMPVariablePair nextPair = new SNMPVariablePair(requestedObjectIdentifier, new SNMPInteger(0));
		SNMPSequence varList = new SNMPSequence();
		varList.addSNMPObject(nextPair);
		SNMPPDU pdu = new SNMPPDU(SNMPBERCodec.SNMPGETNEXTREQUEST, requestID, errorStatus, errorIndex, varList);
		SNMPMessage message = new SNMPMessage(version, community, pdu);
		byte[] messageEncoding = message.getBEREncoding();
		DatagramPacket outPacket = new DatagramPacket(messageEncoding, messageEncoding.length, hostAddress, SNMPPORT);
		
		
		dSocket.send(outPacket);
		
		
		
		while (errorStatus == 0)
		{
			
			DatagramPacket inPacket = new DatagramPacket(new byte[MAXSIZE], MAXSIZE);
		
			dSocket.receive(inPacket);
			
			byte[] encodedMessage = inPacket.getData();
			
			
			SNMPMessage receivedMessage = new SNMPMessage(SNMPBERCodec.extractNextTLV(encodedMessage,0).value);
			//errorStatus = ((BigInteger)((SNMPInteger)((receivedMessage.getPDU()).getSNMPObjectAt(1))).getValue()).intValue();
			
			
			varList = (receivedMessage.getPDU()).getVarBindList();
			SNMPSequence newPair = (SNMPSequence)(varList.getSNMPObjectAt(0));
			
			SNMPObjectIdentifier newObjectIdentifier = (SNMPObjectIdentifier)(newPair.getSNMPObjectAt(0));
			SNMPObject newValue = newPair.getSNMPObjectAt(1);
			
			retrievedVars.addSNMPObject(newPair);
			
			
			if (requestedObjectIdentifier.equals(newObjectIdentifier))
				break;
				
			requestedObjectIdentifier = newObjectIdentifier;
		
			requestID++;
			pdu = new SNMPPDU(SNMPBERCodec.SNMPGETNEXTREQUEST, requestID, errorStatus, errorIndex, varList);
			message = new SNMPMessage(version, community, pdu);
			messageEncoding = message.getBEREncoding();
			outPacket = new DatagramPacket(messageEncoding, messageEncoding.length, hostAddress, SNMPPORT);
			
			dSocket.send(outPacket);
			
		}
			
		
		return retrievedVars;
		
	}
	
	
	
	private String hexByte(byte b)
	{
		int pos = b;
		if (pos < 0)
			pos += 256;
		String returnString = new String();
		returnString += Integer.toHexString(pos/16);
		returnString += Integer.toHexString(pos%16);
		return returnString;
	}
	
	
	
	
	
	/**
	*	Retrieve the MIB variable values corresponding to the object identifier
	*	given in itemID (in dotted-integer notation). Return as SNMPVarBindList object; if no
	*	such variable (either due to device not supporting it, or community name having incorrect
	*	access privilege), variable value will be SNMPNull object
	*	@throws IOException Thrown when timeout experienced while waiting for response to request.
	*	@throws SNMPBadValueException 
	*/
	
	public SNMPVarBindList getMIBEntry(String itemID)
		throws IOException, SNMPBadValueException, SNMPGetException
	{
		// send GetRequest to specified host to retrieve specified object identifier
		
		SNMPVarBindList retrievedVars = new SNMPVarBindList();
		
		
		int errorStatus = 0;
		int errorIndex = 0;
		
		
		SNMPObjectIdentifier requestedObjectIdentifier = new SNMPObjectIdentifier(itemID);
		SNMPVariablePair nextPair = new SNMPVariablePair(requestedObjectIdentifier, new SNMPInteger(0));
		SNMPSequence varList = new SNMPSequence();
		varList.addSNMPObject(nextPair);
		SNMPPDU pdu = new SNMPPDU(SNMPBERCodec.SNMPGETREQUEST, requestID, errorStatus, errorIndex, varList);
		
		SNMPMessage message = new SNMPMessage(version, community, pdu);
		
		byte[] messageEncoding = message.getBEREncoding();
		
		
		/*
		System.out.println("Request Message bytes:");
		
		for (int i = 0; i < messageEncoding.length; ++i)
			System.out.print(hexByte(messageEncoding[i]) + " ");
		*/
		
		DatagramPacket outPacket = new DatagramPacket(messageEncoding, messageEncoding.length, hostAddress, SNMPPORT);
		
		
		dSocket.send(outPacket);
		
		DatagramPacket inPacket = new DatagramPacket(new byte[MAXSIZE], MAXSIZE);
		
		
		while (true)	// wait until receive reply for requestID & OID (or error)
		{
			
			dSocket.receive(inPacket);
			
			byte[] encodedMessage = inPacket.getData();
			
			/*
			System.out.println("Message bytes:");
			
			for (int i = 0; i < encodedMessage.length; ++i)
			{
				System.out.print(hexByte(encodedMessage[i]) + " ");
			}
			*/
			
			
			SNMPMessage receivedMessage = new SNMPMessage(SNMPBERCodec.extractNextTLV(encodedMessage,0).value);
			SNMPPDU receivedPDU = receivedMessage.getPDU();
			
			// check request identifier; if incorrect, just ignore packet and continue waiting
			if (receivedPDU.getRequestID() == requestID)
			{
				
				// check error status; if retrieval problem, throw SNMPGetException
				if (receivedPDU.getErrorStatus() != 0)
					throw new SNMPGetException("OID " + itemID + " not available for retrieval");		
				
				
				varList = receivedPDU.getVarBindList();
				SNMPSequence newPair = (SNMPSequence)(varList.getSNMPObjectAt(0));
				
				SNMPObjectIdentifier newObjectIdentifier = (SNMPObjectIdentifier)(newPair.getSNMPObjectAt(0));
				SNMPObject newValue = newPair.getSNMPObjectAt(1);
				
				// check the object identifier to make sure the correct variable has been received;
				// if not, just continue waiting for receive
				if (newObjectIdentifier.toString().equals(itemID))
				{
					// got the right one; add it to retrieved var list and break!
					retrievedVars.addSNMPObject(newPair);
					break;
				}
			
			}
			
		}
		
		
		requestID++;
		
		
		return retrievedVars;
		
	}
	
	
	
	
	
	/**
	*	Retrieve the MIB variable value corresponding to the object identifier following that
	*	given in itemID (in dotted-integer notation). Return as SNMPVarBindList object; if no
	*	such variable (either due to device not supporting it, or community name having incorrect
	*	access privilege), variable value will be SNMPNull object
	*	@throws IOException Thrown when timeout experienced while waiting for response to request.
	*	@throws SNMPBadValueException 
	*/
	
	public SNMPVarBindList getNextMIBEntry(String itemID)
		throws IOException, SNMPBadValueException, SNMPGetException
	{
		// send GetRequest to specified host to retrieve specified object identifier
		
		SNMPVarBindList retrievedVars = new SNMPVarBindList();
		
		
		int errorStatus = 0;
		int errorIndex = 0;
		
		
		SNMPObjectIdentifier requestedObjectIdentifier = new SNMPObjectIdentifier(itemID);
		SNMPVariablePair nextPair = new SNMPVariablePair(requestedObjectIdentifier, new SNMPInteger(0));
		SNMPSequence varList = new SNMPSequence();
		varList.addSNMPObject(nextPair);
		SNMPPDU pdu = new SNMPPDU(SNMPBERCodec.SNMPGETNEXTREQUEST, requestID, errorStatus, errorIndex, varList);
		
		SNMPMessage message = new SNMPMessage(version, community, pdu);
		
		byte[] messageEncoding = message.getBEREncoding();
		
		
		/*
		System.out.println("Request Message bytes:");
		
		for (int i = 0; i < messageEncoding.length; ++i)
			System.out.print(hexByte(messageEncoding[i]) + " ");
		*/
		
		DatagramPacket outPacket = new DatagramPacket(messageEncoding, messageEncoding.length, hostAddress, SNMPPORT);
		
		
		dSocket.send(outPacket);
		
		DatagramPacket inPacket = new DatagramPacket(new byte[MAXSIZE], MAXSIZE);
		
		
		while (true)	// wait until receive reply for requestID & OID (or error)
		{
			
			dSocket.receive(inPacket);
			
			byte[] encodedMessage = inPacket.getData();
			
			/*
			System.out.println("Message bytes:");
			
			for (int i = 0; i < encodedMessage.length; ++i)
			{
				System.out.print(hexByte(encodedMessage[i]) + " ");
			}
			*/
			
			
			SNMPMessage receivedMessage = new SNMPMessage(SNMPBERCodec.extractNextTLV(encodedMessage,0).value);
			SNMPPDU receivedPDU = receivedMessage.getPDU();
			
			// check request identifier; if incorrect, just ignore packet and continue waiting
			if (receivedPDU.getRequestID() == requestID)
			{
				
				// check error status; if retrieval problem, throw SNMPGetException
				if (receivedPDU.getErrorStatus() != 0)
					throw new SNMPGetException("OID " + itemID + " not available for retrieval");		
				
				
				varList = receivedPDU.getVarBindList();
				SNMPSequence newPair = (SNMPSequence)(varList.getSNMPObjectAt(0));
				
				SNMPObjectIdentifier newObjectIdentifier = (SNMPObjectIdentifier)(newPair.getSNMPObjectAt(0));
				SNMPObject newValue = newPair.getSNMPObjectAt(1);
				
				retrievedVars.addSNMPObject(newPair);
				
				break;
			
			}
			
		}
		
		
		requestID++;
		
		
		return retrievedVars;
		
	}
	
	
	
	
	
	
	
	/**
	*	Set the MIB variable value of the object identifier
	*	given in startID (in dotted-integer notation). Return SNMPVarBindList object returned
	*	by device in its response; can be used to check that setting was successful.
	*	Uses SNMPGetNextRequests to retrieve variable values in sequence.
	*	@throws IOException Thrown when timeout experienced while waiting for response to request.
	*	@throws SNMPBadValueException 
	*/
	
	public SNMPVarBindList setMIBEntry(String itemID, SNMPObject newValue)
		throws IOException, SNMPBadValueException, SNMPSetException
	{
		// send SetRequest to specified host to set value of specified object identifier
		
		SNMPVarBindList retrievedVars = new SNMPVarBindList();
		
		int errorStatus = 0;
		int errorIndex = 0;
		
		SNMPObjectIdentifier requestedObjectIdentifier = new SNMPObjectIdentifier(itemID);
		SNMPVariablePair nextPair = new SNMPVariablePair(requestedObjectIdentifier, newValue);
		
			
		
		SNMPSequence varList = new SNMPSequence();
		varList.addSNMPObject(nextPair);
		SNMPPDU pdu = new SNMPPDU(SNMPBERCodec.SNMPSETREQUEST, requestID, errorStatus, errorIndex, varList);
		
		
		SNMPMessage message = new SNMPMessage(version, community, pdu);
		byte[] messageEncoding = message.getBEREncoding();
		
		/*
		System.out.println("Message bytes:");
		
		for (int i = 0; i < messageEncoding.length; ++i)
		{
			System.out.print(getHex(messageEncoding[i]) + " ");
		}
		*/
		
		
		DatagramPacket outPacket = new DatagramPacket(messageEncoding, messageEncoding.length, hostAddress, SNMPPORT);
		
		
		dSocket.send(outPacket);
		
		
		DatagramPacket inPacket = new DatagramPacket(new byte[MAXSIZE], MAXSIZE);
		
		
		while (true)	// wait until receive reply for correct OID (or error)
		{
			
			dSocket.receive(inPacket);
			
			
			byte[] encodedMessage = inPacket.getData();
			
			/*
			System.out.println("Message bytes:");
			
			for (int i = 0; i < encodedMessage.length; ++i)
			{
				System.out.print((encodedMessage[i]) + " ");
			}
			*/
		
		
			
			SNMPMessage receivedMessage = new SNMPMessage(SNMPBERCodec.extractNextTLV(encodedMessage,0).value);
			
			SNMPPDU receivedPDU = receivedMessage.getPDU();
			
			
			// check request identifier; if incorrect, just ignore packet and continue waiting
			if (receivedPDU.getRequestID() == requestID)
			{
				
				// check error status; if retrieval problem, throw SNMPGetException
				if (receivedPDU.getErrorStatus() != 0)
				{
					switch (receivedPDU.getErrorStatus())
					{
						case 1:
							throw new SNMPSetException("Value supplied for OID " + itemID + " too big.");
						
						case 2:
							throw new SNMPSetException("OID " + itemID + " not available for setting.");
						
						case 3:
							throw new SNMPSetException("Bad value supplied for OID " + itemID + ".");
							
						case 4:
							throw new SNMPSetException("OID " + itemID + " read-only.");
							
						default:
							throw new SNMPSetException("Error setting OID " + itemID + ".");	
							
					}
				}
				
				
				varList = receivedPDU.getVarBindList();
				SNMPSequence newPair = (SNMPSequence)(varList.getSNMPObjectAt(0));
				
				// check the object identifier to make sure the correct variable has been received;
				// if not, just continue waiting for receive
				if (((SNMPObjectIdentifier)newPair.getSNMPObjectAt(0)).toString().equals(itemID))
				{
					// got the right one; add it to retrieved var list and break!
					retrievedVars.addSNMPObject(newPair);
					break;
				}
			
			}
			
		}
		
		
		requestID++;
	
		
		return retrievedVars;
		
	}
	
	
	/*
	public void broadcastDiscovery(String itemID)
		throws IOException, SNMPBadValueException
	{
		// send GetRequest to all hosts to retrieve specified object identifier
		
	
		int errorStatus = 0;
		int errorIndex = 0;
		
		int requestID = 0;
		SNMPObjectIdentifier requestedObjectIdentifier = new SNMPObjectIdentifier(itemID);
		SNMPVariablePair nextPair = new SNMPVariablePair(requestedObjectIdentifier, new SNMPInteger(0));
		SNMPSequence varList = new SNMPSequence();
		varList.addSNMPObject(nextPair);
		SNMPPDU pdu = new SNMPPDU(SNMPBERCodec.SNMPGETREQUEST, requestID, errorStatus, errorIndex, varList);
		SNMPMessage message = new SNMPMessage(0, community, pdu);
		byte[] messageEncoding = message.getBEREncoding();
		DatagramPacket outPacket = new DatagramPacket(messageEncoding, messageEncoding.length, hostAddress, SNMPPORT);
		
		
		dSocket.send(outPacket);
		
		
		
	}
	
	
	
	
	public String receiveDiscovery()
		throws IOException, SNMPBadValueException
	{
		// receive responses from hosts responding to discovery message
		
		int MAXSIZE = 512;
			
		String returnString = new String();
		
		int errorStatus = 0;
		int errorIndex = 0;
		int requestID = 0;
		
		
		DatagramPacket inPacket = new DatagramPacket(new byte[MAXSIZE], MAXSIZE);
		
		dSocket.receive(inPacket);
		String hostString = inPacket.getAddress().toString();
		returnString += "Packet received from: " + hostString + "\n";
		
		byte[] encodedMessage = inPacket.getData();
		
		
		returnString += "Message bytes:" + "\n";
		
		for (int i = 0; i < encodedMessage.length; ++i)
		{
			returnString += (encodedMessage[i]) + " ";
		}
		
		
		SNMPMessage receivedMessage = new SNMPMessage(SNMPBERCodec.extractNextTLV(encodedMessage,0).value);
		SNMPSequence varList = (receivedMessage.getPDU()).getVarBindList();
		SNMPSequence newPair = (SNMPSequence)(varList.getSNMPObjectAt(0));
		SNMPObject newValue = newPair.getSNMPObjectAt(1);
		
		// return just value string
		returnString += newValue.toString() + "\n\n";
		
		
		
		returnString += "Received message contents:\n";
		returnString += receivedMessage.toString() + "\n";
		System.out.println(receivedMessage.toString());
		
	
		
		return returnString;
		
	}
	
	
	
	
	public String discoverDevices(String itemID)
		throws IOException, SNMPBadValueException
	{
		// send GetRequest to all hosts to retrieve specified object identifier
		
		int MAXSIZE = 512;
		
			
		String returnString = new String();
		
		try
		{
			int errorStatus = 0;
			int errorIndex = 0;
			
			DatagramSocket dSocket = new DatagramSocket();
			
			int requestID = 0;
			SNMPObjectIdentifier requestedObjectIdentifier = new SNMPObjectIdentifier(itemID);
			SNMPVariablePair nextPair = new SNMPVariablePair(requestedObjectIdentifier, new SNMPInteger(0));
			SNMPSequence varList = new SNMPSequence();
			varList.addSNMPObject(nextPair);
			SNMPPDU pdu = new SNMPPDU(SNMPBERCodec.SNMPGETREQUEST, requestID, errorStatus, errorIndex, varList);
			SNMPMessage message = new SNMPMessage(0, community, pdu);
			byte[] messageEncoding = message.getBEREncoding();
			DatagramPacket outPacket = new DatagramPacket(messageEncoding, messageEncoding.length, hostAddress, SNMPPORT);
			
			
			dSocket.send(outPacket);
			
			
			DatagramPacket inPacket = new DatagramPacket(new byte[MAXSIZE], MAXSIZE);
			
			while (true)
			{
				dSocket.receive(inPacket);
				String hostString = inPacket.getAddress().toString();
				returnString += "Packet received from: " + hostString + "\n";
				
				byte[] encodedMessage = inPacket.getData();
				
				
				returnString += "Message bytes:" + "\n";
				
				for (int i = 0; i < encodedMessage.length; ++i)
				{
					returnString += (encodedMessage[i]) + " ";
				}
				
				
				SNMPMessage receivedMessage = new SNMPMessage(SNMPBERCodec.extractNextTLV(encodedMessage,0).value);
				returnString += "Received message contents:\n";
				returnString += receivedMessage.toString() + "\n";
				
	
			}
		}
		catch (Exception e)
		{
		
		}
		
		return returnString;
		
	}
	
	
	
	private String getHex(byte theByte)
	{
		int b = theByte;
		
		if (b < 0)
			b += 256;
		
		String returnString = new String(Integer.toHexString(b));
		
		// add leading 0 if needed
		if (returnString.length()%2 == 1)
			returnString = "0" + returnString;
			
		return returnString;
	}
	
	*/
	
	
	
}