/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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. 
 *  
 */
package org.apache.directory.shared.kerberos.components;


import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;

import org.apache.directory.api.asn1.Asn1Object;
import org.apache.directory.api.asn1.EncoderException;
import org.apache.directory.api.asn1.ber.tlv.BerValue;
import org.apache.directory.api.asn1.ber.tlv.TLV;
import org.apache.directory.api.asn1.ber.tlv.UniversalTag;
import org.apache.directory.api.util.Strings;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.shared.kerberos.KerberosConstants;
import org.apache.directory.shared.kerberos.KerberosTime;
import org.apache.directory.shared.kerberos.flags.TicketFlags;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Base class for encrypted parts of KDC responses.
 * 
 * The ASN.1 grammar for this structure is :
 * <pre>
 * EncKDCRepPart   ::= SEQUENCE {
 *         key             [0] EncryptionKey,
 *         last-req        [1] LastReq,
 *         nonce           [2] UInt32,
 *         key-expiration  [3] KerberosTime OPTIONAL,
 *         flags           [4] TicketFlags,
 *         authtime        [5] KerberosTime,
 *         starttime       [6] KerberosTime OPTIONAL,
 *         endtime         [7] KerberosTime,
 *         renew-till      [8] KerberosTime OPTIONAL,
 *         srealm          [9] Realm,
 *         sname           [10] PrincipalName,
 *         caddr           [11] HostAddresses OPTIONAL
 * }
 * </pre>
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class EncKdcRepPart implements Asn1Object
{
    /** The logger */
    private static final Logger log = LoggerFactory.getLogger( EncKdcRepPart.class );

    /** Speedup for logs */
    private static final boolean IS_DEBUG = log.isDebugEnabled();

    /** The encryption key */
    private EncryptionKey key;

    /** The time of the last request */
    private LastReq lastReq;

    /** The nonce */
    private int nonce;

    /** The KeyExpiration */
    private KerberosTime keyExpiration; //optional

    /** The Ticket flags */
    private TicketFlags flags = new TicketFlags();

    /** The initial Authentication time */
    private KerberosTime authTime;

    /** The ticket's start time */
    private KerberosTime startTime; //optional

    /** The Ticket expiration time */
    private KerberosTime endTime;

    /** Maximum endtime in a renewal */
    private KerberosTime renewTill; //optional

    /** The server's realm */
    private String srealm;

    /** The server's principal */
    private PrincipalName sname;

    /** The client addresses */
    private HostAddresses caddr; //optional

    // Storage for computed lengths
    private int keyLength;
    private int lastReqLength;
    private int nonceLength;
    private int flagsLength;
    private byte[] srealmBytes;
    private int srealmLength;
    private int snameLength;
    private int caddrLength;
    private int encKdcRepPartSeqLength;


    /**
     * Creates a new instance of EncKdcRepPart.
     */
    public EncKdcRepPart()
    {
    }


    /**
     * Returns the auth {@link KerberosTime}.
     *
     * @return The auth {@link KerberosTime}.
     */
    public KerberosTime getAuthTime()
    {
        return authTime;
    }


    /**
     * Sets the auth {@link KerberosTime}.
     *
     * @param time
     */
    public void setAuthTime( KerberosTime time )
    {
        authTime = time;
    }


    /**
     * Returns the client {@link HostAddresses}.
     *
     * @return The client {@link HostAddresses}.
     */
    public HostAddresses getClientAddresses()
    {
        return caddr;
    }


    /**
     * Sets the client {@link HostAddresses}.
     *
     * @param addresses
     */
    public void setClientAddresses( HostAddresses caddr )
    {
        this.caddr = caddr;
    }


    /**
     * Returns the end {@link KerberosTime}.
     *
     * @return The end {@link KerberosTime}.
     */
    public KerberosTime getEndTime()
    {
        return endTime;
    }


    /**
     * Sets the end {@link KerberosTime}.
     *
     * @param time
     */
    public void setEndTime( KerberosTime time )
    {
        endTime = time;
    }


    /**
     * Returns the {@link TicketFlags}.
     *
     * @return The {@link TicketFlags}.
     */
    public TicketFlags getFlags()
    {
        return flags;
    }


    /**
     * Sets the {@link TicketFlags}.
     *
     * @param flags
     */
    public void setFlags( TicketFlags flags )
    {
        this.flags = flags;
    }


    /**
     * Returns the {@link EncryptionKey}.
     *
     * @return The {@link EncryptionKey}.
     */
    public EncryptionKey getKey()
    {
        return key;
    }


    /**
     * Sets the {@link EncryptionKey}.
     *
     * @param key
     */
    public void setKey( EncryptionKey key )
    {
        this.key = key;
    }


    /**
     * Returns the key expiration {@link KerberosTime}.
     *
     * @return The key expiration {@link KerberosTime}.
     */
    public KerberosTime getKeyExpiration()
    {
        return keyExpiration;
    }


    /**
     * Sets the key expiration {@link KerberosTime}.
     *
     * @param expiration
     */
    public void setKeyExpiration( KerberosTime expiration )
    {
        keyExpiration = expiration;
    }


    /**
     * Returns the {@link LastReq}.
     *
     * @return The {@link LastReq}.
     */
    public LastReq getLastReq()
    {
        return lastReq;
    }


    /**
     * Sets the {@link LastReq}.
     *
     * @param lastReq The LastReq to set
     */
    public void setLastReq( LastReq lastReq )
    {
        this.lastReq = lastReq;
    }


    /**
     * Returns the nonce.
     *
     * @return The nonce.
     */
    public int getNonce()
    {
        return nonce;
    }


    /**
     * Sets the nonce.
     *
     * @param nonce
     */
    public void setNonce( int nonce )
    {
        this.nonce = nonce;
    }


    /**
     * Returns the renew till {@link KerberosTime}.
     *
     * @return The renew till {@link KerberosTime}.
     */
    public KerberosTime getRenewTill()
    {
        return renewTill;
    }


    /**
     * Sets the renew till {@link KerberosTime}.
     *
     * @param till
     */
    public void setRenewTill( KerberosTime till )
    {
        renewTill = till;
    }


    /**
     * Returns the server {@link PrincipalName}.
     *
     * @return The server {@link PrincipalName}.
     */
    public PrincipalName getSName()
    {
        return sname;
    }


    /**
     * Sets the server {@link PrincipalName}.
     *
     * @param principal The server PrincipalName
     */
    public void setSName( PrincipalName sname )
    {
        this.sname = sname;
    }


    /**
     * Returns the server realm.
     *
     * @return The server realm.
     */
    public String getSRealm()
    {
        return srealm;
    }


    /**
     * Sets the server realm.
     *
     * @param srealm The server realm
     */
    public void setSRealm( String srealm )
    {
        this.srealm = srealm;
    }


    /**
     * Returns the start {@link KerberosTime}.
     *
     * @return The start {@link KerberosTime}.
     */
    public KerberosTime getStartTime()
    {
        return startTime;
    }


    /**
     * Sets the start {@link KerberosTime}.
     *
     * @param time he start time to set
     */
    public void setStartTime( KerberosTime time )
    {
        startTime = time;
    }


    /**
     * Compute the EncKdcRepPart length
     * <pre>
     * EncKdcRepPart :
     * 
     * 0x30 L1 EncKdcRepPart sequence
     *  |
     *  +--> 0xA0 L2 key tag
     *  |     |
     *  |     +--> 0x30 L2-1 key ( EncryptionKey)
     *  |
     *  +--> 0xA1 L3 last-req tag
     *  |     |
     *  |     +--> 0x30 L3-1 last-req ( LastReq )
     *  |     
     *  +--> 0xA2 L4 nonce tag
     *  |     |
     *  |     +--> 0x02 L4-1 nonce (Int)
     *  |     
     * [+--> 0xA3 0x11 key-expiration tag]
     *  |     |
     *  |     +--> 0x18 0x0F key-expiration ( KerberosTime )
     *  |     
     *  +--> 0xA4 0x07 flags tag 
     *  |     |
     *  |     +--> 0x03 0x05 flags ( TicketFlags )
     *  |     
     *  +--> 0xA5 0x11 authtime tag
     *  |     |
     *  |     +--> 0x18 0x0F authtime ( KerberosTime )
     *  |     
     * [+--> 0xA6 0x11 starttime tag]
     *  |     |
     *  |     +--> 0x18 0x0F starttime ( KerberosTime )
     *  |     
     *  +--> 0xA7 0x11 endtime tag
     *  |     |
     *  |     +--> 0x18 0x0F endtime ( KerberosTime )
     *  |     
     * [+--> 0xA8 0x11 renew-till tag]
     *  |     |
     *  |     +--> 0x18 0x0F renew-till ( KerberosTime )
     *  |     
     *  +--> 0xA9 L5 srealm tag
     *  |     |
     *  |     +--> 0x1B L5-1 srealm ( KerberosString )
     *  |     
     *  +--> 0xAA L6 sname tag
     *  |     |
     *  |     +--> 0x30 L6-1 sname ( PrincipalName )
     *  |     
     * [+--> 0xAB L7 caddr tag]
     *        |
     *        +--> 0x30 L7-1 caddr ( HostAddresses )
     *  </pre>
     */
    public int computeLength()
    {
        // The key
        keyLength = key.computeLength();
        encKdcRepPartSeqLength = 1 + TLV.getNbBytes( keyLength ) + keyLength;

        // The last-req
        lastReqLength = lastReq.computeLength();
        encKdcRepPartSeqLength += 1 + TLV.getNbBytes( lastReqLength ) + lastReqLength;

        // The nonce
        nonceLength = BerValue.getNbBytes( nonce );
        nonceLength = 1 + TLV.getNbBytes( nonceLength ) + nonceLength;
        encKdcRepPartSeqLength += 1 + TLV.getNbBytes( nonceLength ) + nonceLength;

        // The keyExpiration
        if ( keyExpiration != null )
        {
            encKdcRepPartSeqLength += 1 + 1 + 0x11;
        }

        // The flags
        flagsLength = 1 + 1 + 5;
        encKdcRepPartSeqLength += 1 + TLV.getNbBytes( flagsLength ) + flagsLength;

        // The authtime
        encKdcRepPartSeqLength += 1 + 1 + 0x11;

        // The starttime, if any
        if ( startTime != null )
        {
            encKdcRepPartSeqLength += 1 + 1 + 0x11;
        }

        // The endtime
        encKdcRepPartSeqLength += 1 + 1 + 0x11;

        // The renew-till, if any
        if ( renewTill != null )
        {
            encKdcRepPartSeqLength += 1 + 1 + 0x11;
        }

        // The srealm
        srealmBytes = srealm.getBytes();
        srealmLength = 1 + TLV.getNbBytes( srealmBytes.length ) + srealmBytes.length;
        encKdcRepPartSeqLength += 1 + TLV.getNbBytes( srealmLength ) + srealmLength;

        // The sname
        snameLength = sname.computeLength();
        encKdcRepPartSeqLength += 1 + TLV.getNbBytes( snameLength ) + snameLength;

        // The caddr if any
        if ( caddr != null )
        {
            caddrLength = caddr.computeLength();
            encKdcRepPartSeqLength += 1 + TLV.getNbBytes( caddrLength ) + caddrLength;
        }

        return 1 + TLV.getNbBytes( encKdcRepPartSeqLength ) + encKdcRepPartSeqLength;
    }


    /**
     * Encode the EncKdcRepPart message to a PDU. 
     * 
     * @param buffer The buffer where to put the PDU. It should have been allocated
     * before, with the right size.
     * @return The constructed PDU.
     */
    public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
    {
        if ( buffer == null )
        {
            throw new EncoderException( I18n.err( I18n.ERR_148 ) );
        }

        try
        {
            // The EncKdcRepPart sequence
            buffer.put( UniversalTag.SEQUENCE.getValue() );
            buffer.put( TLV.getBytes( encKdcRepPartSeqLength ) );

            // The Key
            buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_KEY_TAG );
            buffer.put( TLV.getBytes( keyLength ) );
            key.encode( buffer );

            // The LastReq
            buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_LAST_REQ_TAG );
            buffer.put( TLV.getBytes( lastReqLength ) );
            lastReq.encode( buffer );

            // The nonce
            buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_NONCE_TAG );
            buffer.put( TLV.getBytes( nonceLength ) );
            BerValue.encode( buffer, nonce );

            // The key-expiration, if any
            if ( keyExpiration != null )
            {
                buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_KEY_EXPIRATION_TAG );
                buffer.put( TLV.getBytes( 0x11 ) );

                buffer.put( UniversalTag.GENERALIZED_TIME.getValue() );
                buffer.put( ( byte ) 0x0F );
                buffer.put( keyExpiration.getBytes() );
            }

            // The flags
            buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_FLAGS_TAG );
            buffer.put( TLV.getBytes( 0x07 ) );
            BerValue.encode( buffer, flags );

            // The authtime
            buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_AUTH_TIME_TAG );
            buffer.put( TLV.getBytes( 0x11 ) );
            buffer.put( UniversalTag.GENERALIZED_TIME.getValue() );
            buffer.put( ( byte ) 0x0F );
            buffer.put( authTime.getBytes() );

            // The starttime if any
            if ( startTime != null )
            {
                buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_START_TIME_TAG );
                buffer.put( TLV.getBytes( 0x11 ) );
                buffer.put( UniversalTag.GENERALIZED_TIME.getValue() );
                buffer.put( ( byte ) 0x0F );
                buffer.put( startTime.getBytes() );
            }

            // The endtime
            buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_END_TIME_TAG );
            buffer.put( TLV.getBytes( 0x11 ) );
            buffer.put( UniversalTag.GENERALIZED_TIME.getValue() );
            buffer.put( ( byte ) 0x0F );
            buffer.put( endTime.getBytes() );

            // The renew-till if any
            if ( renewTill != null )
            {
                buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_RENEW_TILL_TAG );
                buffer.put( TLV.getBytes( 0x11 ) );
                buffer.put( UniversalTag.GENERALIZED_TIME.getValue() );
                buffer.put( ( byte ) 0x0F );
                buffer.put( renewTill.getBytes() );
            }

            // The srealm
            buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_SREALM_TAG );
            buffer.put( TLV.getBytes( srealmLength ) );
            buffer.put( UniversalTag.GENERAL_STRING.getValue() );
            buffer.put( TLV.getBytes( srealmBytes.length ) );
            buffer.put( srealmBytes );

            // The sname
            buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_SNAME_TAG );
            buffer.put( TLV.getBytes( snameLength ) );
            sname.encode( buffer );

            // The caddr if any
            if ( caddr != null )
            {
                buffer.put( ( byte ) KerberosConstants.ENC_KDC_REP_PART_CADDR_TAG );
                buffer.put( TLV.getBytes( caddrLength ) );
                caddr.encode( buffer );
            }
        }
        catch ( BufferOverflowException boe )
        {
            log.error( I18n.err( I18n.ERR_140, 1 + TLV.getNbBytes( 0 ) + 0,
                buffer.capacity() ) );
            throw new EncoderException( I18n.err( I18n.ERR_138 ), boe );
        }

        if ( IS_DEBUG )
        {
            log.debug( "EncKdcRepPart encoding : {}", Strings.dumpBytes( buffer.array() ) );
            log.debug( "EncKdcRepPart initial value : {}", toString() );
        }

        return buffer;
    }


    /**
     * @see Object#toString()
     */
    public String toString()
    {
        StringBuilder sb = new StringBuilder();

        sb.append( "EncKdcRepPart : \n" );
        sb.append( "    key : " ).append( key ).append( "\n" );
        sb.append( "    last-req : " ).append( lastReq ).append( "\n" );
        sb.append( "    nonce : " ).append( nonce ).append( "\n" );

        if ( keyExpiration != null )
        {
            sb.append( "    key-expiration : " ).append( keyExpiration ).append( "\n" );
        }

        sb.append( "    flags : " ).append( flags ).append( "\n" );
        sb.append( "    authtime : " ).append( authTime ).append( "\n" );

        if ( startTime != null )
        {
            sb.append( "    starttime : " ).append( startTime ).append( "\n" );
        }

        sb.append( "    endtime : " ).append( endTime ).append( "\n" );

        if ( renewTill != null )
        {
            sb.append( "    renew-till : " ).append( renewTill ).append( "\n" );
        }

        sb.append( "    srealm : " ).append( srealm ).append( "\n" );
        sb.append( "    sname : " ).append( sname ).append( "\n" );

        if ( caddr != null )
        {
            sb.append( "    caddr : " ).append( caddr ).append( "\n" );
        }

        return sb.toString();
    }
}
