/*
 * @(#)file      SnmpMsg.java
 * @(#)author    Sun Microsystems, Inc.
 * @(#)version   1.21
 * @(#)date      07/06/14
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 */

package com.sun.jmx.snmp;

import com.sun.jmx.snmp.SnmpSecurityParameters;
// java imports
//
import java.util.Vector;
import java.net.InetAddress;

// import debug stuff
//
import com.sun.jmx.trace.Trace;

import com.sun.jmx.snmp.SnmpStatusException;
/**
 * A partially decoded representation of an SNMP packet. It contains
 * the information contained in any SNMP message (SNMPv1, SNMPv2 or
 * SNMPv3).
 * <p><b>This API is a Sun Microsystems internal API  and is subject 
 * to change without notice.</b></p>
 * @since 1.5
 */
public abstract class SnmpMsg implements SnmpDefinitions {
    /**
     * The protocol version.
     * <P><CODE>decodeMessage</CODE> and <CODE>encodeMessage</CODE> do not
     * perform any check on this value.
     * <BR><CODE>decodeSnmpPdu</CODE> and <CODE>encodeSnmpPdu</CODE> only 
     * accept  the values 0 (for SNMPv1), 1 (for SNMPv2) and 3 (for SNMPv3).
     */
    public int version = 0;

    /**
     * Encoding of the PDU.
     * <P>This is usually the BER encoding of the PDU's syntax
     * defined in RFC1157 and RFC1902. However, this can be authenticated
     * or encrypted data (but you need to implemented your own 
     * <CODE>SnmpPduFactory</CODE> class).
     */
    public byte[] data = null;

    /**
     * Number of useful bytes in the <CODE>data</CODE> field.
     */
    public int dataLength = 0;

    /**
     * Source or destination address.
     * <BR>For an incoming message it's the source.
     * For an outgoing message it's the destination.
     */
    public InetAddress address = null;

    /**
     * Source or destination port.
     * <BR>For an incoming message it's the source.
     * For an outgoing message it's the destination.
     */
    public int port = 0;
    /**
     * Security parameters. Contain informations according to Security Model (Usm, community string based, ...).
     */
    public SnmpSecurityParameters securityParameters = null;
    /**
     * Returns the encoded SNMP version present in the passed byte array.
     * @param data The unmarshalled SNMP message.
     * @return The SNMP version (0, 1 or 3).
     */
    public static int getProtocolVersion(byte[] data) 
	throws SnmpStatusException {
	int version = 0;
	BerDecoder bdec = null;	
	try {
	    bdec = new BerDecoder(data);
	    bdec.openSequence();
	    version = bdec.fetchInteger();
	}
        catch(BerException x) {
            throw new SnmpStatusException("Invalid encoding") ;
        }
	try {
	    bdec.closeSequence();
	}
        catch(BerException x) {
        }
	return version;
    }

    /**
     * Returns the associated request ID.
     * @param data The flat message.
     * @return The request ID.
     */
    public abstract int getRequestId(byte[] data) throws SnmpStatusException;
    
    /**
     * Encodes this message and puts the result in the specified byte array.
     * For internal use only.
     * 
     * @param outputBytes An array to receive the resulting encoding.
     *
     * @exception ArrayIndexOutOfBoundsException If the result does not fit
     *                                           into the specified array.
     */
    public abstract int encodeMessage(byte[] outputBytes) 
	throws SnmpTooBigException;

     /**
     * Decodes the specified bytes and initializes this message.
     * For internal use only.
     * 
     * @param inputBytes The bytes to be decoded.
     *
     * @exception SnmpStatusException If the specified bytes are not a valid encoding.
     */
    public abstract void decodeMessage(byte[] inputBytes, int byteCount) 
        throws SnmpStatusException;
    
     /**
     * Initializes this message with the specified <CODE>pdu</CODE>.
     * <P>
     * This method initializes the data field with an array of 
     * <CODE>maxDataLength</CODE> bytes. It encodes the <CODE>pdu</CODE>. 
     * The resulting encoding is stored in the data field
     * and the length of the encoding is stored in <CODE>dataLength</CODE>.
     * <p>
     * If the encoding length exceeds <CODE>maxDataLength</CODE>, 
     * the method throws an exception.
     * 
     * @param pdu The PDU to be encoded.
     * @param maxDataLength The maximum length permitted for the data field.
     *
     * @exception SnmpStatusException If the specified <CODE>pdu</CODE> is not valid.
     * @exception SnmpTooBigException If the resulting encoding does not fit
     * into <CODE>maxDataLength</CODE> bytes.
     * @exception ArrayIndexOutOfBoundsException If the encoding exceeds <CODE>maxDataLength</CODE>.
     */
    public abstract void encodeSnmpPdu(SnmpPdu pdu, int maxDataLength) 
        throws SnmpStatusException, SnmpTooBigException;
    

    /**
     * Gets the PDU encoded in this message.
     * <P>
     * This method decodes the data field and returns the resulting PDU.
     * 
     * @return The resulting PDU.
     * @exception SnmpStatusException If the encoding is not valid.
     */
    public abstract SnmpPdu decodeSnmpPdu() 
        throws SnmpStatusException;

    /**
     * Dumps the content of a byte buffer using hexadecimal form.
     *
     * @param b The buffer to dump.
     * @param offset The position of the first byte to be dumped.
     * @param len The number of bytes to be dumped starting from offset.
     *
     * @return The string containing the dump.
     */
    public static String dumpHexBuffer(byte [] b, int offset, int len) { 
        StringBuffer buf = new StringBuffer(len << 1) ;
        int k = 1 ; 
        int flen = offset + len ; 
 
        for (int i = offset; i < flen ; i++) { 
            int j = b[i] & 0xFF ; 
            buf.append(Character.forDigit((j >>> 4) , 16)) ; 
            buf.append(Character.forDigit((j & 0x0F), 16)) ; 
            k++ ; 
            if (k%16 == 0) { 
                buf.append('\n') ; 
                k = 1 ; 
            } else 
                buf.append(' ') ; 
        } 
        return buf.toString() ; 
    }

    /**
     * Dumps this message in a string.
     *
     * @return The string containing the dump.
     */
    public String printMessage() {
        StringBuffer sb = new StringBuffer() ;
        sb.append("Version: ") ;
        sb.append(version) ;
        sb.append("\n") ;
        if (data == null) {
            sb.append("Data: null") ;
        }
        else {
            sb.append("Data: {\n") ;
            sb.append(dumpHexBuffer(data, 0, dataLength)) ;
            sb.append("\n}\n") ;
        }

        return sb.toString() ;
    }

    /**
     * For SNMP Runtime private use only.
     */
    public void encodeVarBindList(BerEncoder benc, 
				  SnmpVarBind[] varBindList) 
        throws SnmpStatusException, SnmpTooBigException {
        //
        // Remember: the encoder does backward encoding
        //
        int encodedVarBindCount = 0 ;
        try {
            benc.openSequence() ;
            if (varBindList != null) {
                for (int i = varBindList.length - 1 ; i >= 0 ; i--) {
                    SnmpVarBind bind = varBindList[i] ;
                    if (bind != null) {
                        benc.openSequence() ;
                        encodeVarBindValue(benc, bind.value) ;
                        benc.putOid(bind.oid.longValue()) ;
                        benc.closeSequence() ;
                        encodedVarBindCount++ ;
                    }
                }  
            }  
            benc.closeSequence() ;
        }
        catch(ArrayIndexOutOfBoundsException x) {
            throw new SnmpTooBigException(encodedVarBindCount) ;
        }
    }
  
    /**
     * For SNMP Runtime private use only.
     */
    void encodeVarBindValue(BerEncoder benc, 
			    SnmpValue v)throws SnmpStatusException {
        if (v == null) {
            benc.putNull() ;
        }
        else if (v instanceof SnmpIpAddress) {
            benc.putOctetString(((SnmpIpAddress)v).byteValue(), SnmpValue.IpAddressTag) ;
        }
        else if (v instanceof SnmpCounter) {
            benc.putInteger(((SnmpCounter)v).longValue(), SnmpValue.CounterTag) ;
        }
        else if (v instanceof SnmpGauge) {
            benc.putInteger(((SnmpGauge)v).longValue(), SnmpValue.GaugeTag) ;
        }
        else if (v instanceof SnmpTimeticks) {
            benc.putInteger(((SnmpTimeticks)v).longValue(), SnmpValue.TimeticksTag) ;
        }
        else if (v instanceof SnmpOpaque) {
            benc.putOctetString(((SnmpOpaque)v).byteValue(), SnmpValue.OpaqueTag) ;
        }
        else if (v instanceof SnmpInt) {
            benc.putInteger(((SnmpInt)v).intValue()) ;
        }
        else if (v instanceof SnmpString) {
            benc.putOctetString(((SnmpString)v).byteValue()) ;
        }
        else if (v instanceof SnmpOid) {
            benc.putOid(((SnmpOid)v).longValue()) ;
        }
        else if (v instanceof SnmpCounter64) {
            if (version == snmpVersionOne) {
                throw new SnmpStatusException("Invalid value for SNMP v1 : " + v) ;
            }  
            benc.putInteger(((SnmpCounter64)v).longValue(), SnmpValue.Counter64Tag) ;
        }
        else if (v instanceof SnmpNull) {
            int tag = ((SnmpNull)v).getTag() ;
            if ((version == snmpVersionOne) && (tag != SnmpValue.NullTag)) {
                throw new SnmpStatusException("Invalid value for SNMP v1 : " + v) ;
            }
            if ((version == snmpVersionTwo) &&
                (tag != SnmpValue.NullTag) && 
                (tag != SnmpVarBind.errNoSuchObjectTag) && 
                (tag != SnmpVarBind.errNoSuchInstanceTag) &&
                (tag != SnmpVarBind.errEndOfMibViewTag)) {
                throw new SnmpStatusException("Invalid value " + v) ;
            }
            benc.putNull(tag) ;
        }
        else {
            throw new SnmpStatusException("Invalid value " + v) ;
        }
    
    }


    /**
     * For SNMP Runtime private use only.
     */
    public SnmpVarBind[] decodeVarBindList(BerDecoder bdec) 
	throws BerException {
	    bdec.openSequence() ;
	    Vector tmp = new Vector() ;
	    while (bdec.cannotCloseSequence()) {
		SnmpVarBind bind = new SnmpVarBind() ;
		bdec.openSequence() ;
		bind.oid = new SnmpOid(bdec.fetchOid()) ;
		bind.setSnmpValue(decodeVarBindValue(bdec)) ;
		bdec.closeSequence() ;
		tmp.addElement(bind) ;
	    }
	    bdec.closeSequence() ;
	    SnmpVarBind[] varBindList= new SnmpVarBind[tmp.size()] ;
	    tmp.copyInto(varBindList);
	    return varBindList ;
	}
    
    
    /**
     * For SNMP Runtime private use only.
     */
    SnmpValue decodeVarBindValue(BerDecoder bdec) 
	throws BerException {
        SnmpValue result = null ;
        int tag = bdec.getTag() ;

	// bugId 4641696 : RuntimeExceptions must be transformed in 
	//                 BerException.
        switch(tag) {
	    
            //
            // Simple syntax
            //
        case BerDecoder.IntegerTag :
	    try {
		result = new SnmpInt(bdec.fetchInteger()) ;
	    } catch(RuntimeException r) {
		throw new BerException();
		// BerException("Can't build SnmpInt from decoded value.");
	    }
            break ;
        case BerDecoder.OctetStringTag :
	    try {
		result = new SnmpString(bdec.fetchOctetString()) ;
	    } catch(RuntimeException r) {
		throw new BerException();
		// BerException("Can't build SnmpString from decoded value.");
	    }
            break ;
        case BerDecoder.OidTag :
	    try {
		result = new SnmpOid(bdec.fetchOid()) ;
	    } catch(RuntimeException r) {
		throw new BerException();
		// BerException("Can't build SnmpOid from decoded value.");
	    }
            break ;
        case BerDecoder.NullTag :
            bdec.fetchNull() ;
	    try {
		result = new SnmpNull() ;
	    } catch(RuntimeException r) {
		throw new BerException();
		// BerException("Can't build SnmpNull from decoded value.");
	    }
            break ;

            //
            // Application syntax
            //
        case SnmpValue.IpAddressTag :
	    try {
		result = new SnmpIpAddress(bdec.fetchOctetString(tag)) ;
	    } catch (RuntimeException r) {
		throw new  BerException();
	      // BerException("Can't build SnmpIpAddress from decoded value.");
	    }
            break ;
        case SnmpValue.CounterTag :
	    try {
		result = new SnmpCounter(bdec.fetchIntegerAsLong(tag)) ;
	    } catch(RuntimeException r) {
		throw new BerException();
		// BerException("Can't build SnmpCounter from decoded value.");
	    }
            break ;
        case SnmpValue.GaugeTag :
	    try {
		result = new SnmpGauge(bdec.fetchIntegerAsLong(tag)) ;
	    } catch(RuntimeException r) {
		throw new BerException();
		// BerException("Can't build SnmpGauge from decoded value.");
	    }
            break ;
        case SnmpValue.TimeticksTag :
	    try {
		result = new SnmpTimeticks(bdec.fetchIntegerAsLong(tag)) ;
	    } catch(RuntimeException r) {
		throw new BerException();
	     // BerException("Can't build SnmpTimeticks from decoded value.");
	    }
            break ;
        case SnmpValue.OpaqueTag :
	    try {
		result = new SnmpOpaque(bdec.fetchOctetString(tag)) ;
	    } catch(RuntimeException r) {
		throw new BerException();
		// BerException("Can't build SnmpOpaque from decoded value.");
	    }
            break ;
      
            //
            // V2 syntaxes
            //
        case SnmpValue.Counter64Tag :
            if (version == snmpVersionOne) {
                throw new BerException(BerException.BAD_VERSION) ;
            }
	    try {
		result = new SnmpCounter64(bdec.fetchIntegerAsLong(tag)) ;
	    } catch(RuntimeException r) {
		throw new BerException();
	     // BerException("Can't build SnmpCounter64 from decoded value.");
	    }
            break ;
    
        case SnmpVarBind.errNoSuchObjectTag :
            if (version == snmpVersionOne) {
                throw new BerException(BerException.BAD_VERSION) ;
            }
            bdec.fetchNull(tag) ;
            result = SnmpVarBind.noSuchObject ;
            break ;
        
        case SnmpVarBind.errNoSuchInstanceTag :
            if (version == snmpVersionOne) {
                throw new BerException(BerException.BAD_VERSION) ;
            }
            bdec.fetchNull(tag) ;
            result = SnmpVarBind.noSuchInstance ;
            break ;
        
        case SnmpVarBind.errEndOfMibViewTag :
            if (version == snmpVersionOne) {
                throw new BerException(BerException.BAD_VERSION) ;
            }
            bdec.fetchNull(tag) ;
            result = SnmpVarBind.endOfMibView ;
            break ;
        
        default:
            throw new BerException() ;

        }
    
        return result ;
    }

}
