You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by ad...@apache.org on 2004/06/10 23:22:31 UTC

svn commit: rev 21037 - incubator/directory/snickers/branches/ber-decoder/ber-codec/src/java/org/apache/snickers/ber

Author: adc
Date: Thu Jun 10 14:22:30 2004
New Revision: 21037

Modified:
   incubator/directory/snickers/branches/ber-decoder/ber-codec/src/java/org/apache/snickers/ber/BERDecoder.java
Log:
Quick check in for Alex to look at.

Modified: incubator/directory/snickers/branches/ber-decoder/ber-codec/src/java/org/apache/snickers/ber/BERDecoder.java
==============================================================================
--- incubator/directory/snickers/branches/ber-decoder/ber-codec/src/java/org/apache/snickers/ber/BERDecoder.java	(original)
+++ incubator/directory/snickers/branches/ber-decoder/ber-codec/src/java/org/apache/snickers/ber/BERDecoder.java	Thu Jun 10 14:22:30 2004
@@ -14,164 +14,116 @@
  *   limitations under the License.
  *
  */
-package org.apache.snickers.ber ;
+package org.apache.snickers.ber;
 
+import java.nio.ByteBuffer;
+import java.util.EmptyStackException;
 
-import java.util.Stack ;
-
-import java.nio.ByteBuffer ;
-
-import org.apache.commons.lang.ArrayUtils ;
-
-import org.apache.commons.codec.DecoderException ;
-import org.apache.commons.codec.stateful.DecoderMonitor ;
-import org.apache.commons.codec.stateful.DecoderCallback ;
-import org.apache.commons.codec.stateful.StatefulDecoder ;
-import org.apache.commons.codec.stateful.DecoderMonitorAdapter ;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.stateful.DecoderCallback;
+import org.apache.commons.codec.stateful.DecoderMonitor;
+import org.apache.commons.codec.stateful.StatefulDecoder;
 
 
 /**
- * A decoder that decodes BER encoded bytes to Tag Value Length (TLV) tuples.  
- * This decoder is a low level event based parser which operates in a fashion 
- * similar to the way SAX works except the elements of concern are the tag, 
+ * A decoder that decodes BER encoded bytes to Tag Value Length (TLV) tuples.
+ * This decoder is a low level event based parser which operates in a fashion
+ * similar to the way SAX works except the elements of concern are the tag,
  * length, and value entities.  The decoder is a state machine which processes
  * input as it is made available.
- * <p>
- * A Stack is used to track the state of the decoder between decode calls.  It 
- * maintains the nesting of TLV tuples.  Rather than creating new TLV tuple 
- * instances every time a single tuple is reused for primitive types and new 
+ * <p/>
+ * A Stack is used to track the state of the decoder between decode calls.  It
+ * maintains the nesting of TLV tuples.  Rather than creating new TLV tuple
+ * instances every time a single tuple is reused for primitive types and new
  * tlv tuples are cloned for constructed types which are pushed onto the stack.
- * The tuple fed to the callback must therefore be used very carefully - its 
- * values must be copied to prevent their loss if they are to be used later 
+ * The tuple fed to the callback must therefore be used very carefully - its
+ * values must be copied to prevent their loss if they are to be used later
  * after the callback invokation has returned.
  * </p>
- * <p>
+ * <p/>
  * Note that all tuples are not created equal.  Constructed TLVs nesting others
- * will have null value members or empty buffers.  Only TLV tuples of primitive 
- * types or the leaf TLV tuples of the TLV tuple tree will contain non null 
- * values.  Therefore the nature of a TLV tuple should be investigated by 
+ * will have null value members or empty buffers.  Only TLV tuples of primitive
+ * types or the leaf TLV tuples of the TLV tuple tree will contain non null
+ * values.  Therefore the nature of a TLV tuple should be investigated by
  * callbacks before attempting to interpret their values.  Also this decoder
  * chunks value data returning it in parts rather than in one complete peice
  * in the end.  The value of the TLV Tuple returned is the part of the value
  * that was read from the input fed into the decoder.  These 'chunks' returned
  * by callback makes it so there are no size limits to the value of a TLV. Again
- * to reiterate chunking on values is only performed on primitive TLV Tuple 
- * types. 
+ * to reiterate chunking on values is only performed on primitive TLV Tuple
+ * types.
  * </p>
- * 
- * @author <a href="mailto:directory-dev@incubator.apache.org">
- * Apache Directory Project</a>
+ *
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
  * @version $Rev$
  */
-public class BERDecoder implements StatefulDecoder, DecoderCallback
+public class BERDecoder implements StatefulDecoder
 {
-    /** empty byte buffer to be reused */
-    private static final ByteBuffer EMPTY_BUFFER = 
-        ByteBuffer.wrap( ArrayUtils.EMPTY_BYTE_ARRAY ) ;
-    /** the callback used by this decoder */
-    private static final BERDecoderCallback DEFAULT_CALLBACK = 
-        new BERDecoderCallbackAdapter() ;
-    /** the monitor used by this decoder */
-    private static final DecoderMonitor DEFAULT_MONITOR =
-        new DecoderMonitorAdapter() ;
-    
-    /** this decoder's callback */
-    private BERDecoderCallback cb = DEFAULT_CALLBACK ;
-    /** the monitor used by this decoder */
-    private DecoderMonitor monitor = DEFAULT_MONITOR ;
-    
-    /** the single TLV tuple used by this decoder */
-    private final Tuple tlv = new Tuple() ;
-
-    /** a decoder used to decode tag octets */ 
-    private final TagDecoder tagDecoder = new TagDecoder() ;
-    /** a decoder used to decode length octets */ 
-    private final LengthDecoder lengthDecoder = new LengthDecoder() ;
-    
-    /** stack of nested/constructed TLV tuples */
-    private final Stack tlvStack = new Stack() ;
-
-    /** the state of this decoder */
-    private BERDecoderState state = BERDecoderState.getStartState() ;
-    
-    
+    public final State TAG_STATE = new TagState();
+    public final State LENGTH_STATE = new LengthState();
+    public final State VALUE_STATE = new ValueState();
+
+    /**
+     * the single TLV tuple used by this decoder
+     */
+    Tuple tlv;
+
+    Tag tag = new Tag();
+    Length length = new Length();
+
+    /**
+     * stack of nested/constructed TLV tuples
+     */
+    final TupleStack stack;
+
+    /**
+     * the state of this decoder
+     */
+    private State state = TAG_STATE;
+
+
     /**
      * Creates a stateful BER decoder which limits the tuple's value size.
      */
     public BERDecoder()
     {
-        tagDecoder.setCallback( this ) ;
-        lengthDecoder.setCallback( this ) ;
+        stack = new TupleStack( this );
     }
-    
-    
-    // ------------------------------------------------------------------------
-    // StatefulDecoder Methods
-    // ------------------------------------------------------------------------
 
-    
     /**
-     * Expects a ByteBuffer containing BER encoded data.
-     * 
-     * @see org.apache.commons.codec.stateful.StatefulDecoder#decode(
-     * java.lang.Object)
-     * @throws ClassCastException if the encoded argument is not a ByteBuffer
-     * @throws IllegalArgumentException if the buffer is null or empty
+     * Obtain the current incomplete tuple that is on the top of the stack.
+     *
+     * @return the current incomplete tuple that is on the top of the stack
+     */
+    public Tuple getCurrentTuple()
+    {
+        return tlv;
+//        try
+//        {
+//            return stack.peek();
+//        }
+//        catch ( EmptyStackException ese )
+//        {
+//            return tlv;
+//        }
+    }
+
+    /**
+     * Obtain the current state of the decoder.
+     *
+     * @return the current state of the decoder
      */
-    public void decode( Object encoded ) throws DecoderException
+    public State getState()
     {
-        ByteBuffer buf = ( ByteBuffer ) encoded ;
-        
-        /* --------------------------------------------------------------------
-           Handle any unusual input by informing the monitor. 
-           ------------------------------------------------------------------ */
-        
-        if ( buf == null && monitor != null )
-        {
-            String msg = "ignoring null argument to decode()" ;
-            monitor.warning( this, new IllegalArgumentException( msg ) ) ;
-            return ;
-        }
-
-        if ( buf.remaining() == 0 && monitor != null )
-        {
-            String msg = "ignoring empty buffer" ;
-            monitor.warning( this, new IllegalArgumentException( msg ) ) ;
-            return ;
-        }
-        
-        /*
-         * This loop is used instead of costly recursion.  This requires each
-         * of the statewise decode methods to process bytes from the buffer.  If
-         * they can process enough to switch state they do and return 
-         * immediately.  This loop makes sure the next processing state is 
-         * handled if there is more data for that state.
-         */
-        while ( buf.hasRemaining() )
-        {    
-            switch( state.getValue() )
-            {
-                case( BERDecoderState.TAG_VAL ):
-                    tagDecoder.decode( buf ) ;
-                    break ;
-                case( BERDecoderState.LENGTH_VAL ):
-                    lengthDecoder.decode( buf ) ;
-                    break ;
-                case( BERDecoderState.VALUE_VAL ):
-                    decodeValue( buf ) ;
-                    break ;
-            }
-        }
+        return state;
     }
-    
 
     /* (non-Javadoc)
-     * @see org.apache.commons.codec.stateful.StatefulDecoder#setCallback(
-     * org.apache.commons.codec.stateful.DecoderCallback)
+     * @see StatefulDecoder#setCallback(DecoderCallback)
      */
     public void setCallback( DecoderCallback cb )
     {
-        this.cb = ( BERDecoderCallback ) cb ;
+        stack.setCallback( cb );
     }
 
 
@@ -181,355 +133,179 @@
      */
     public void setDecoderMonitor( DecoderMonitor monitor )
     {
-        this.monitor = monitor ;
+        stack.setMonitor( monitor );
     }
-    
-    
-    // ------------------------------------------------------------------------
-    // State Based Decode Methods
-    // ------------------------------------------------------------------------
-    
-    
+
     /**
-     * Extracts the value portion from the buffer for a primitive type.
-     * 
-     * @param buf the byte byffer containing BER encoded data 
+     * Expects a ByteBuffer containing BER encoded data.
+     *
+     * @throws ClassCastException       if the encoded argument is not a ByteBuffer
+     * @throws IllegalArgumentException if the buffer is null or empty
+     * @see org.apache.commons.codec.stateful.StatefulDecoder#decode(java.lang.Object)
      */
-    private void decodeValue( ByteBuffer buf )
+    public void decode( Object encoded ) throws DecoderException
     {
-        int needToRead = Length.UNDEFINED ;
+        if ( encoded == null ) throw new IllegalArgumentException( "Parameter 'encoded' is null" );
+        ByteBuffer buffer = (ByteBuffer) encoded;
 
         /*
-         * setup to start decoding the value by figuring how much we need to
-         * read at this point - previous reads of the value may have already
-         * occurred.
+         * This loop is used instead of costly recursion.  This requires each
+         * of the statewise decode methods to process bytes from the buffer.  If
+         * they can process enough to switch state they do and return
+         * immediately.  This loop makes sure the next processing state is
+         * handled if there is more data for that state.
          */
-        if ( tlv.valueIndex == Length.UNDEFINED )
-        {
-            needToRead = tlv.length ;
-        }
-        else
+        while ( buffer.hasRemaining() )
         {
-            needToRead = tlv.length - tlv.valueIndex ;
+            state.decode( buffer );
         }
-        
-        /*
-         * check if we have the remainder of the value to complete the 
-         * TLV within the current buffer - if so we read all of it
+    }
+
+
+    public abstract class State
+    {
+
+        /**
+         * Decodes a peice of encoded data.  The nature of this call, synchronous
+         * verses asynchonous, with respect to driving the actual decoding of the
+         * encoded data argument is determined by an implementation.  A return from
+         * this method does not guarrantee any callbacks: zero or more callbacks
+         * may occur during this call.
+         *
+         * @param encoded an object representing a peice of encoded data
          */
-        if ( buf.remaining() >= needToRead )
-        {
-            tlv.valueChunk = ( ByteBuffer ) buf.slice().limit( needToRead ) ;
-            buf.position( buf.position() + needToRead ) ;
-            tlv.valueIndex = tlv.length ;
-            tlv.index += tlv.length ;
-            
-            cb.partialValueDecoded( tlv ) ;
-            fireDecodeOccurred( tlv ) ;
-            updateStack( needToRead ) ;
-            tlv.clear() ;
-            state = BERDecoderState.TAG ;
-        }
-        
-        /*
-         * the buffer does not contain the rest of the value we need in order
-         * to complete the current TLV - the value is fragmented so we read
-         * what we can and update indices by that amount.
+        protected abstract void decode( ByteBuffer encoded ) throws DecoderException;
+
+    }
+
+    protected class TagState extends State
+    {
+        /**
+         * Decodes a peice of encoded data.  The nature of this call, synchronous
+         * verses asynchonous, with respect to driving the actual decoding of the
+         * encoded data argument is determined by an implementation.  A return from
+         * this method does not guarrantee any callbacks: zero or more callbacks
+         * may occur during this call.
+         *
+         * @param buf an object representing a peice of encoded data
          */
-        else
+        protected void decode( ByteBuffer buf ) throws DecoderException
         {
-            if ( tlv.valueIndex == Length.UNDEFINED )
+            while ( buf.hasRemaining() )
             {
-                tlv.valueIndex = 0 ;
+                byte octet = buf.get();
+                tag.add( octet );
+
+                if ( tag.isFixated() )
+                {
+                    tlv = new Tuple();
+
+                    tlv.rawTag = tag.getRawTag();
+                    tlv.id = tag.getId();
+                    tlv.isPrimitive = tag.isPrimitive();
+                    tlv.typeClass = tag.getTypeClass();
+
+                    state = LENGTH_STATE;
+
+                    tag.clear();
+
+                    return;
+                }
             }
-            
-            int remaining = buf.remaining() ;
-            tlv.valueChunk = buf.slice() ;
-            buf.position( buf.limit() ) ;
-            tlv.valueIndex += remaining ;
-            tlv.index +=remaining ;
-            
-            cb.partialValueDecoded( tlv ) ;
-            updateStack( remaining ) ;
+
+            state = TAG_STATE;
         }
     }
-    
-    
-    
-    /* (non-Javadoc)
-     * @see org.apache.commons.codec.stateful.DecoderCallback#decodeOccurred(
-     * org.apache.commons.codec.stateful.StatefulDecoder, java.lang.Object)
-     */
-    public void decodeOccurred( StatefulDecoder decoder, Object decoded )
+
+    protected class LengthState extends State
     {
-        if ( decoder == tagDecoder )
-        {
-            Tag tag = ( Tag ) decoded ;
-            tlv.rawTag = tag.getRawTag() ;
-            tlv.id = tag.getId() ;
-            tlv.isPrimitive = tag.isPrimitive() ;
-            tlv.typeClass = tag.getTypeClass() ;
-            tlv.index = tag.size() ;
-
-            fireTagDecoded() ;
-            updateStack( tag.size() ) ;
-            state = state.getNext( tag.isPrimitive() ) ;
-        }
-        else if ( decoder == lengthDecoder )
+        /**
+         * Decodes a peice of encoded data.  The nature of this call, synchronous
+         * verses asynchonous, with respect to driving the actual decoding of the
+         * encoded data argument is determined by an implementation.  A return from
+         * this method does not guarrantee any callbacks: zero or more callbacks
+         * may occur during this call.
+         *
+         * @param buf an object representing a peice of encoded data
+         */
+        protected void decode( ByteBuffer buf ) throws DecoderException
         {
-            Length length = ( Length ) decoded ;
-            tlv.length = length.getLength() ;
-            
-            if ( tlv.length == Length.INDEFINATE )
-            {
-                tlv.index = Length.INDEFINATE ;
-                tlv.valueIndex = Length.INDEFINATE ;
-            }
-            else
-            {    
-                tlv.index += length.size() ;
-            }
-            
-            fireLengthDecoded() ;
-            updateStack( length.size() ) ;
-            
-            if ( ! tlv.isPrimitive )
+
+            while ( buf.hasRemaining() )
             {
-                if ( tlv.isIndefinate() || tlv.length > 0 )
-                {    
-                    tlvStack.push( tlv.clone() ) ;
-                }
-                else
+                byte octet = buf.get();
+                length.add( octet );
+
+                if ( length.isFixated() )
                 {
-                    state = BERDecoderState.VALUE ;
-                    fireDecodeOccurred( tlv ) ;
+                    tlv.length = length.getLength();
+
+                    stack.push( tlv );
+
+                    if ( tlv.isIndefiniteTerminator() || tlv.isIndefinite() || tlv.getLength() == 0 || !tlv.isPrimitive() )
+                    {
+                        state = TAG_STATE;
+                    }
+                    else
+                    {
+                        state = VALUE_STATE;
+                    }
+
+                    length.clear();
+
+                    return;
                 }
-                
-                state = BERDecoderState.TAG ;
-                tlv.clear() ;
             }
-            else if ( tlv.isIndefinateTerminator() )
-            {
-                return ;
-            }
-            else if ( tlv.length > 0 )
-            {
-                state = BERDecoderState.VALUE ;
-            }
-            else
-            {
-                state = BERDecoderState.VALUE ;
-                tlv.valueChunk = EMPTY_BUFFER ;
-                cb.partialValueDecoded( tlv ) ;
-                fireDecodeOccurred( tlv ) ;
-                state = BERDecoderState.TAG ;
-            }
-        }
-        else
-        {
-            throw new IllegalArgumentException( "unrecognized decoder" ) ;
-        }
-    }
-    
-    
-    // ------------------------------------------------------------------------
-    // private utility methods
-    // ------------------------------------------------------------------------
-    
-    
-    /**
-     * Fires a tag decoded event by making the appropriate calls to the 
-     * callback and the monitor.   If the monitor is a BERDecoderMonitor with
-     * extended reporting, then those methods are invoked.
-     * 
-     * Also as a side-effect this method clears the tag buffer once it has 
-     * finished notifying the monitor and calling the callback. 
-     */
-    private void fireTagDecoded()
-    {
-        if ( cb != null )
-        {
-            cb.tagDecoded( tlv ) ;
-        }
 
-        if ( monitor != null && monitor instanceof BERDecoderMonitor )
-        {
-            BERDecoderMonitor berMonitor = ( BERDecoderMonitor ) monitor ;
-            berMonitor.tagDecoded( tlv ) ;
-        }
-    }
-    
-    
-    /**
-     * Fires a length decoded event by making the appropriate calls to the 
-     * callback and the monitor.   If the monitor is a BERDecoderMonitor with
-     * extended reporting, then those methods are invoked.
-     * 
-     * Also as a side-effect this method clears the length buffer once it has 
-     * finished notifying the monitor and calling the callback. 
-     */
-    private void fireLengthDecoded()
-    {
-        if ( cb != null )
-        {
-            cb.lengthDecoded( tlv ) ;
-        }
-        
-        if ( monitor != null && monitor instanceof BERDecoderMonitor )
-        {
-            BERDecoderMonitor berMonitor = ( BERDecoderMonitor ) monitor ;
-            berMonitor.lengthDecoded( tlv ) ;
-        }
-    }
-    
-    
-    /**
-     * Fires a complete TLV decoded event by making the appropriate calls to 
-     * the callback and the monitor.
-     */
-    private void fireDecodeOccurred( Tuple tlv )
-    {
-        if ( cb != null )
-        {
-            cb.decodeOccurred( this, tlv ) ;
-        }
-        
-        if ( monitor != null )
-        {
-            monitor.callbackOccured( this, cb, tlv ) ;
+            state = LENGTH_STATE;
         }
     }
 
-    
-    /**
-     * Increments the indices of constructed TLV's within the TLV Stack.
-     * 
-     * @param increment the amount to increment indices by.
-     */
-    private void updateStack( int increment )
+    protected class ValueState extends State
     {
-        for ( int ii = 0; ii < tlvStack.size(); ii++ )
-        {
-            Tuple t = ( Tuple ) tlvStack.get( ii ) ;
-            
-            if ( t.isIndefinate() )
-            {
-                continue ;
-            }
-                
-            t.index += increment ;
-                
-            if ( t.valueIndex == Length.UNDEFINED )
-            {
-                t.valueIndex = 0 ;
-            }
-                
-            t.valueIndex += increment ;
-        }
-        
-        if ( tlvStack.isEmpty() )
-        {
-            return ;
-        }
-        
-        do
+        /**
+         * Decodes a peice of encoded data.  The nature of this call, synchronous
+         * verses asynchonous, with respect to driving the actual decoding of the
+         * encoded data argument is determined by an implementation.  A return from
+         * this method does not guarrantee any callbacks: zero or more callbacks
+         * may occur during this call.
+         *
+         * @param buffer an object representing a peice of encoded data
+         */
+        protected void decode( ByteBuffer buffer ) throws DecoderException
         {
-            Tuple top = ( Tuple ) tlvStack.peek() ;
-            
-            if ( top.isIndefinate() && tlv.isIndefinateTerminator() )
-            {
-                tlvStack.pop() ;
-                state = BERDecoderState.VALUE ;
-                fireDecodeOccurred( top ) ;
-                state = BERDecoderState.TAG ;
-            }
-            else if ( top.isIndefinate() )
-            {
-                break ;
-            }
-            else if ( top.valueIndex >= top.length )
-            {
-                tlvStack.pop() ;
-                state = BERDecoderState.VALUE ;
-                fireDecodeOccurred( top ) ;
-                state = BERDecoderState.TAG ;
+            /*
+             * setup to start decoding the value by figuring how much we need to
+             * read at this point - previous reads of the value may have already
+             * occurred.
+             */
+            int needToRead = tlv.remaining();
+
+            if ( buffer.remaining() >= needToRead )
+            {
+                /*
+                 * check if we have the remainder of the value to complete the
+                 * TLV within the current buffer - if so we read all of it
+                 */
+
+                stack.contents( (ByteBuffer) buffer.slice().limit( needToRead ) );
+                buffer.position( buffer.position() + needToRead );
+
+                state = TAG_STATE;
             }
             else
             {
-                break ;
-            }
-            
-        } while( tlvStack.size() > 0 ) ;
-    }
+                /*
+                 * the buffer does not contain the rest of the value we need in order
+                 * to complete the current TLV - the value is fragmented so we read
+                 * what we can and update indices by that amount.
+                 */
 
+                stack.contents( (ByteBuffer) buffer.slice() );
+                buffer.position( buffer.limit() );
 
-    /*
-     
-     Why copy the raw tag here when we can maintain our own stack in the
-     digester that does the pushing and popping instead?  Keep this here
-     until we decide what to do.
-     
-    public int[] getTagNestingPattern()
-    {
-        int stackSz = tlvStack.size() ;
-        int[] pattern = new int[stackSz+1] ;
-        pattern[stackSz] = tlv.rawTag ;
-        
-        for ( int ii = 0; ii < stackSz; ii++ )
-        {
-            pattern[ii] = ( ( Tuple ) tlvStack.get( ii ) ).rawTag ;
-        }
-        
-        return pattern ;
-    }
-    */
-    
-    
-    // ------------------------------------------------------------------------
-    // Methods used for testing
-    // ------------------------------------------------------------------------
-    
-    
-    /**
-     * Gets the current state of this BERDecoder.  Used only for debugging and 
-     * testing.
-     * 
-     * @return the state enum
-     */
-    BERDecoderState getState()
-    {
-        return state ;
-    }
-    
-    
-    /**
-     * Gets a cloned copy of the current tuple.  Used only for debugging and 
-     * testing.
-     * 
-     * @return a clone of the current tlv 
-     */
-    Tuple getCurrentTuple()
-    {
-        return ( Tuple ) tlv.clone() ;
-    }
-    
-    
-    /**
-     * Gets a deep copy of the constructed tuple stack.  Used only for debugging
-     * and testing.
-     * 
-     * @return a deep copy of the tuple stack
-     */
-    Stack getTupleStack()
-    {
-        Stack stack = new Stack() ;
-        
-        for ( int ii = 0; ii < tlvStack.size(); ii++ )
-        {
-            Tuple t = ( Tuple ) tlvStack.get( ii ) ;
-            stack.add( t.clone() ) ;
+                state = VALUE_STATE;
+            }
         }
-        
-        return stack ;
     }
 }