blob: 3972a553856c2956bc76752412e1777dedce996b [file] [log] [blame]
/*
* 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.api.asn1.ber;
import java.nio.ByteBuffer;
import org.apache.directory.api.asn1.DecoderException;
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.TLVBerDecoderMBean;
import org.apache.directory.api.asn1.ber.tlv.TLVStateEnum;
import org.apache.directory.api.asn1.util.Asn1StringUtils;
import org.apache.directory.api.i18n.I18n;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A BER TLV Tag component decoder. This decoder instantiate a Tag. The tag
* won't be implementations should not copy the handle to the Tag object
* delivered but should copy the data if they need it over the long term.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class Asn1Decoder implements TLVBerDecoderMBean
{
/** The logger */
private static final Logger LOG = LoggerFactory.getLogger( Asn1Decoder.class );
/** A speedup for logger */
private static final boolean IS_DEBUG = LOG.isDebugEnabled();
/** This flag is used to indicate that there are more bytes in the stream */
private static final boolean MORE = true;
/** This flag is used to indicate that there are no more bytes in the stream */
private static final boolean END = false;
/** Flag that is used to allow/disallow the indefinite form of Length */
private boolean indefiniteLengthAllowed;
/** The maximum number of bytes that could be used to encode the Length */
private int maxLengthLength;
/** The maximum number of bytes that could be used to encode the Tag */
private int maxTagLength;
/**
* A public constructor of an Asn1 Decoder.
*/
public Asn1Decoder()
{
indefiniteLengthAllowed = false;
maxLengthLength = 1;
maxTagLength = 1;
}
/**
* Treat the start of a TLV. It reads the tag and get its value.
*
* @param stream The ByteBuffer containing the PDU to decode
* @param container The container that stores the current state,
* the result and other informations.
* @return <code>true</code> if there are more bytes to read, <code>false
* </code> otherwise
*/
private boolean treatTagStartState( ByteBuffer stream, Asn1Container container )
{
if ( stream.hasRemaining() )
{
byte octet = stream.get();
TLV tlv = new TLV( container.getNewTlvId() );
tlv.setTag( octet );
// Store the current TLV in the container.
container.setCurrentTLV( tlv );
// Create a link between the current TLV with its parent
tlv.setParent( container.getParentTLV() );
// Switch to the next state, which is the Length decoding
container.setState( TLVStateEnum.LENGTH_STATE_START );
if ( IS_DEBUG )
{
byte tag = container.getCurrentTLV().getTag();
LOG.debug( "Tag {} has been decoded", Asn1StringUtils.dumpByte( tag ) );
}
return MORE;
}
else
{
// The stream has been exhausted
return END;
}
}
/**
* Dump the current TLV tree
*
* @param container The container
*/
private void dumpTLVTree( Asn1Container container )
{
StringBuffer sb = new StringBuffer();
TLV current = container.getCurrentTLV();
sb.append( "TLV" ).append( Asn1StringUtils.dumpByte( current.getTag() ) ).append( "(" ).append(
current.getExpectedLength() ).append( ")" );
current = current.getParent();
while ( current != null )
{
sb.append( "-TLV" ).append( Asn1StringUtils.dumpByte( current.getTag() ) ).append( "(" ).append(
current.getExpectedLength() ).append( ")" );
current = current.getParent();
}
if ( IS_DEBUG )
{
LOG.debug( "TLV Tree : {}", sb.toString() );
}
}
/**
* Check if the TLV tree is fully decoded
*
* @param container The container
* @return <code>true</code> if the TLV has been decoded
*/
private boolean isTLVDecoded( Asn1Container container )
{
TLV current = container.getCurrentTLV();
TLV parent = current.getParent();
while ( parent != null )
{
if ( parent.getExpectedLength() != 0 )
{
return false;
}
parent = parent.getParent();
}
BerValue value = current.getValue();
if ( ( value != null ) && ( value.getData() != null ) )
{
return ( current.getExpectedLength() == value.getData().length );
}
else
{
return current.getExpectedLength() == 0;
}
}
/**
* Treat the Length start. The tag has been decoded, so we have to deal with
* the LENGTH, which can be multi-bytes.
*
* @param stream The ByteBuffer containing the PDU to decode
* @param container The container that stores the current state,
* the result and other informations.
* @return <code>true</code> if there are more bytes to read, <code>false
* </code> otherwise
* @throws DecoderException Thrown if anything went wrong
*/
private boolean treatLengthStartState( ByteBuffer stream, Asn1Container container ) throws DecoderException
{
if ( stream.hasRemaining() )
{
byte octet = stream.get();
TLV tlv = container.getCurrentTLV();
if ( ( octet & TLV.LENGTH_LONG_FORM ) == 0 )
{
// We don't have a long form. The Length of the Value part is
// given by this byte.
tlv.setLength( octet );
tlv.setLengthNbBytes( 1 );
container.setState( TLVStateEnum.LENGTH_STATE_END );
}
else if ( ( octet & TLV.LENGTH_EXTENSION_RESERVED ) != TLV.LENGTH_EXTENSION_RESERVED )
{
int expectedLength = octet & TLV.LENGTH_SHORT_MASK;
if ( expectedLength > 4 )
{
String msg = I18n.err( I18n.ERR_00005_LENGTH_OVERFLOW );
LOG.error( msg );
throw new DecoderException( msg );
}
tlv.setLength( 0 );
tlv.setLengthNbBytes( 1 + expectedLength );
tlv.setLengthBytesRead( 1 );
container.setState( TLVStateEnum.LENGTH_STATE_PENDING );
}
else
{
String msg = I18n.err( I18n.ERR_00006_LENGTH_EXTENSION_RESERVED );
LOG.error( msg );
throw new DecoderException( msg );
}
return MORE;
}
else
{
return END;
}
}
/**
* This function is called when a Length is in the process of being decoded,
* but the lack of bytes in the buffer stopped the process.
*
* @param stream The ByteBuffer containing the PDU to decode
* @param container The container that stores the current state,
* the result and other informations.
* @return <code>true</code> if there are more bytes to read, <code>false
* </code> otherwise
*/
private boolean treatLengthPendingState( ByteBuffer stream, Asn1Container container )
{
if ( stream.hasRemaining() )
{
TLV tlv = container.getCurrentTLV();
int length = tlv.getLength();
while ( tlv.getLengthBytesRead() < tlv.getLengthNbBytes() )
{
byte octet = stream.get();
if ( IS_DEBUG )
{
LOG.debug( " current byte : {}", Asn1StringUtils.dumpByte( octet ) );
}
tlv.incLengthBytesRead();
length = ( length << 8 ) | ( octet & 0x00FF );
if ( !stream.hasRemaining() )
{
tlv.setLength( length );
if ( tlv.getLengthBytesRead() < tlv.getLengthNbBytes() )
{
container.setState( TLVStateEnum.LENGTH_STATE_PENDING );
return END;
}
else
{
container.setState( TLVStateEnum.LENGTH_STATE_END );
return MORE;
}
}
}
tlv.setLength( length );
container.setState( TLVStateEnum.LENGTH_STATE_END );
return MORE;
}
else
{
return END;
}
}
/**
* A debug function used to dump the expected length stack.
*
* @param tlv The current TLV.
* @return A string which represent the expected length stack.
*/
private String getParentLength( TLV tlv )
{
StringBuffer buffer = new StringBuffer();
buffer.append( "TLV expected length stack : " );
while ( true )
{
if ( tlv == null )
{
buffer.append( " - null" );
break;
}
else
{
buffer.append( " - " ).append( tlv.getExpectedLength() );
}
tlv = tlv.getParent();
}
return buffer.toString();
}
/**
* The Length is fully decoded. We have to call an action to check the size.
*
* @param container The container that stores the current state,
* the result and other informations.
* @throws DecoderException Thrown if anything went wrong
*/
private void treatLengthEndState( Asn1Container container ) throws DecoderException
{
TLV tlv = container.getCurrentTLV();
if ( tlv == null )
{
String msg = I18n.err( I18n.ERR_00007_TLV_NULL );
LOG.error( msg );
throw new DecoderException( msg );
}
int length = tlv.getLength();
// We will check the length here. What we must control is
// that the enclosing constructed TLV expected length is not
// exceeded by the current TLV.
TLV parentTLV = container.getParentTLV();
if ( IS_DEBUG )
{
LOG.debug( "Parent length : {}", getParentLength( parentTLV ) );
}
if ( parentTLV == null )
{
// This is the first TLV, so we can't check anything. We will
// just store this TLV as the root of the PDU
tlv.setExpectedLength( length );
container.setParentTLV( tlv );
if ( IS_DEBUG )
{
LOG.debug( "Root TLV[{}]", Integer.valueOf( length ) );
}
}
else
{
// We have a parent, so we will check that its expected length is
// not exceeded.
int expectedLength = parentTLV.getExpectedLength();
int currentLength = tlv.getSize();
if ( expectedLength < currentLength )
{
// The expected length is lower than the Value length of the
// current TLV. This is an error...
LOG.debug( "tlv[{}, {}]", Integer.valueOf( expectedLength ), Integer.valueOf( currentLength ) );
throw new DecoderException( I18n.err( I18n.ERR_00008_VALUE_LENGTH_ABOVE_EXPECTED_LENGTH, Integer
.valueOf( currentLength ), Integer.valueOf( expectedLength ) ) );
}
// deal with the particular case where expected length equal
// the current length, which means that the parentTLV has been
// completed.
if ( expectedLength == currentLength )
{
parentTLV.setExpectedLength( 0 );
// We also have to check that the current TLV is a constructed
// one.
// In this case, we have to switch from this parent TLV
// to the parent's parent TLV.
if ( tlv.isConstructed() )
{
// here, we also have another special case : a
// zero length TLV. We must then unstack all
// the parents which length is null.
if ( length == 0 )
{
// We will set the parent to the first parentTLV which
// expectedLength
// is not null, and it will become the new parent TLV
while ( parentTLV != null )
{
if ( parentTLV.getExpectedLength() != 0 )
{
// ok, we have an incomplete parent. we will
// stop the recursion right here
break;
}
else
{
parentTLV = parentTLV.getParent();
}
}
container.setParentTLV( parentTLV );
}
else
{
// The new Parent TLV is this Constructed TLV
container.setParentTLV( tlv );
}
tlv.setParent( parentTLV );
tlv.setExpectedLength( length );
}
else
{
tlv.setExpectedLength( length );
// It's over, the parent TLV has been completed.
// Go back to the parent's parent TLV until we find
// a tlv which is not complete.
while ( parentTLV != null )
{
if ( parentTLV.getExpectedLength() != 0 )
{
// ok, we have an incomplete parent. we will
// stop the recursion right here
break;
}
else
{
parentTLV = parentTLV.getParent();
}
}
container.setParentTLV( parentTLV );
}
}
else
{
// Renew the expected Length.
parentTLV.setExpectedLength( expectedLength - currentLength );
tlv.setExpectedLength( length );
if ( tlv.isConstructed() )
{
// We have a constructed tag, so we must switch the
// parentTLV
tlv.setParent( parentTLV );
container.setParentTLV( tlv );
}
}
}
if ( IS_DEBUG )
{
LOG.debug( "Length {} has been decoded", Integer.valueOf( length ) );
}
if ( length == 0 )
{
// The length is 0, so we can't expect a value.
container.setState( TLVStateEnum.TLV_STATE_DONE );
}
else
{
// Go ahead and decode the value part
container.setState( TLVStateEnum.VALUE_STATE_START );
}
}
/**
* Treat the Value part. We will distinguish two cases : - if the Tag is a
* Primitive one, we will get the value. - if the Tag is a Constructed one,
* nothing will be done.
*
* @param stream The ByteBuffer containing the PDU to decode
* @param container The container that stores the current state,
* the result and other informations.
* @return <code>true</code> if there are more bytes to read, <code>false
* </code> otherwise
*/
private boolean treatValueStartState( ByteBuffer stream, Asn1Container container )
{
TLV currentTlv = container.getCurrentTLV();
if ( TLV.isConstructed( currentTlv.getTag() ) && !container.isGathering() )
{
container.setState( TLVStateEnum.TLV_STATE_DONE );
return MORE;
}
else
{
int length = currentTlv.getLength();
int nbBytes = stream.remaining();
if ( nbBytes < length )
{
currentTlv.getValue().init( length );
currentTlv.getValue().setData( stream );
container.setState( TLVStateEnum.VALUE_STATE_PENDING );
return END;
}
else
{
currentTlv.getValue().init( length );
stream.get( currentTlv.getValue().getData(), 0, length );
container.setState( TLVStateEnum.TLV_STATE_DONE );
return MORE;
}
}
}
/**
* Treat a pending Value when we get more bytes in the buffer.
*
* @param stream The ByteBuffer containing the PDU to decode
* @param container The container that stores the current state,
* the result and other informations.
* @return <code>MORE</code> if some bytes remain in the buffer when the
* value has been decoded, <code>END</code> if whe still need to get some
* more bytes.
*/
private boolean treatValuePendingState( ByteBuffer stream, Asn1Container container )
{
TLV currentTlv = container.getCurrentTLV();
int length = currentTlv.getLength();
int currentLength = currentTlv.getValue().getCurrentLength();
int nbBytes = stream.remaining();
if ( ( currentLength + nbBytes ) < length )
{
currentTlv.getValue().addData( stream );
container.setState( TLVStateEnum.VALUE_STATE_PENDING );
return END;
}
else
{
int remaining = length - currentLength;
byte[] data = new byte[remaining];
stream.get( data, 0, remaining );
currentTlv.getValue().addData( data );
container.setState( TLVStateEnum.TLV_STATE_DONE );
return MORE;
}
}
/**
* When the TLV has been fully decoded, we have to execute the associated
* action and switch to the next TLV, which will start with a Tag.
*
* @param stream The ByteBuffer containing the PDU to decode
* @param container The container that stores the current state,
* the result and other informations.
* @return <code>true</code> if there are more bytes to read, <code>false
* </code> otherwise
* @throws DecoderException Thrown if anything went wrong
*/
@SuppressWarnings("unchecked")
private boolean treatTLVDoneState( ByteBuffer stream, Asn1Container container ) throws DecoderException
{
if ( IS_DEBUG )
{
dumpTLVTree( container );
}
// First, we have to execute the associated action
container.getGrammar().executeAction( container );
// Check if the PDU has been fully decoded.
if ( isTLVDecoded( container ) )
{
if ( container.getState() == TLVStateEnum.GRAMMAR_END )
{
// Change the state to DECODED
container.setState( TLVStateEnum.PDU_DECODED );
}
else
{
if ( container.isGrammarEndAllowed() )
{
// Change the state to DECODED
container.setState( TLVStateEnum.PDU_DECODED );
}
else
{
LOG.error( I18n.err( I18n.ERR_00009_MORE_TLV_EXPECTED ) );
throw new DecoderException( I18n.err( I18n.ERR_00010_TRUNCATED_PDU ) );
}
}
}
else
{
// Then we switch to the Start tag state and free the current TLV
container.setState( TLVStateEnum.TAG_STATE_START );
}
return stream.hasRemaining();
}
/**
* The decoder main function. This is where we read bytes from the stream
* and go through the automaton. It's an inifnite loop which stop when no
* more bytes are to be read. It can occurs if the ByteBuffer is exhausted
* or if the PDU has been fully decoded.
*
* @param stream The ByteBuffer containing the PDU to decode
* @param container The container that store the state, the result
* and other elements.
* @throws DecoderException Thrown if anything went wrong!
*/
public void decode( ByteBuffer stream, Asn1Container container ) throws DecoderException
{
/*
* We have to deal with the current state. This is an infinite loop,
* which will stop for any of these reasons :
* - STATE_END has been reached (hopefully, the most frequent case)
* - buffer is empty (it could happen)
* - STATE_OVERFLOW : bad situation ! The PDU may be a
* malevolous hand crafted ones, that try to "kill" our decoder. We
* must log it with all information to track back this case, and punish
* the guilty !
*/
boolean hasRemaining = stream.hasRemaining();
// Increment the PDU size counter.
container.incrementDecodedBytes( stream.remaining() );
if ( container.getDecodedBytes() > container.getMaxPDUSize() )
{
String message = I18n.err( I18n.ERR_00042_PDU_SIZE_TOO_LONG, container.getDecodedBytes(), container
.getMaxPDUSize() );
LOG.error( message );
throw new DecoderException( message );
}
if ( IS_DEBUG )
{
LOG.debug( ">>>==========================================" );
LOG.debug( "--> Decoding a PDU" );
LOG.debug( ">>>------------------------------------------" );
}
while ( hasRemaining )
{
if ( IS_DEBUG )
{
LOG.debug( "--- State = {} ---", container.getState() );
if ( stream.hasRemaining() )
{
byte octet = stream.get( stream.position() );
LOG.debug( " current byte : {}", Asn1StringUtils.dumpByte( octet ) );
}
else
{
LOG.debug( " no more byte to decode in the stream" );
}
}
switch ( container.getState() )
{
case TAG_STATE_START:
// Reset the GrammarEnd flag first
container.setGrammarEndAllowed( false );
hasRemaining = treatTagStartState( stream, container );
break;
case LENGTH_STATE_START:
hasRemaining = treatLengthStartState( stream, container );
break;
case LENGTH_STATE_PENDING:
hasRemaining = treatLengthPendingState( stream, container );
break;
case LENGTH_STATE_END:
treatLengthEndState( container );
break;
case VALUE_STATE_START:
hasRemaining = treatValueStartState( stream, container );
break;
case VALUE_STATE_PENDING:
hasRemaining = treatValuePendingState( stream, container );
break;
case VALUE_STATE_END:
hasRemaining = stream.hasRemaining();
// Nothing to do. We will never reach this state
break;
case TLV_STATE_DONE:
hasRemaining = treatTLVDoneState( stream, container );
break;
case PDU_DECODED:
// We have to deal with the case where there are
// more bytes in the buffer, but the PDU has been decoded.
if ( LOG.isDebugEnabled() )
{
LOG.debug( I18n.err( I18n.ERR_00043_REMAINING_BYTES_FOR_DECODED_PDU ) );
}
hasRemaining = false;
break;
default:
break;
}
}
if ( IS_DEBUG )
{
LOG.debug( "<<<------------------------------------------" );
if ( container.getState() == TLVStateEnum.PDU_DECODED )
{
if ( container.getCurrentTLV() != null )
{
LOG.debug( "<-- Stop decoding : {}", container.getCurrentTLV().toString() );
}
else
{
LOG.debug( "<-- Stop decoding : null current TLV" );
}
}
else
{
if ( container.getCurrentTLV() != null )
{
LOG.debug( "<-- End decoding : {}", container.getCurrentTLV().toString() );
}
else
{
LOG.debug( "<-- End decoding : null current TLV" );
}
}
LOG.debug( "<<<==========================================" );
}
}
/**
* {@inheritDoc}
*/
public int getMaxLengthLength()
{
return maxLengthLength;
}
/**
* {@inheritDoc}
*/
public int getMaxTagLength()
{
return maxTagLength;
}
/**
* {@inheritDoc}
*/
public void disallowIndefiniteLength()
{
this.indefiniteLengthAllowed = false;
}
/**
* {@inheritDoc}
*/
public void allowIndefiniteLength()
{
this.indefiniteLengthAllowed = true;
}
/**
* {@inheritDoc}
*/
public boolean isIndefiniteLengthAllowed()
{
return indefiniteLengthAllowed;
}
/**
* {@inheritDoc}
*/
public void setMaxLengthLength( int maxLengthLength ) throws DecoderException
{
if ( ( this.indefiniteLengthAllowed ) && ( maxLengthLength > 126 ) )
{
throw new DecoderException( I18n.err( I18n.ERR_00011_LENGTH_TOO_LONG_FOR_DEFINITE_FORM ) );
}
this.maxLengthLength = maxLengthLength;
}
/**
* {@inheritDoc}
*/
public void setMaxTagLength( int maxTagLength )
{
this.maxTagLength = maxTagLength;
}
}