Return-Path: Delivered-To: apmail-incubator-directory-cvs-archive@www.apache.org Received: (qmail 43954 invoked from network); 10 Jun 2004 21:22:35 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur-2.apache.org with SMTP; 10 Jun 2004 21:22:35 -0000 Received: (qmail 72815 invoked by uid 500); 10 Jun 2004 21:22:53 -0000 Delivered-To: apmail-incubator-directory-cvs-archive@incubator.apache.org Received: (qmail 72676 invoked by uid 500); 10 Jun 2004 21:22:52 -0000 Mailing-List: contact directory-cvs-help@incubator.apache.org; run by ezmlm Precedence: bulk Reply-To: directory-dev@incubator.apache.org list-help: list-unsubscribe: list-post: Delivered-To: mailing list directory-cvs@incubator.apache.org Received: (qmail 72659 invoked by uid 99); 10 Jun 2004 21:22:52 -0000 Received: from [209.237.227.194] (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.27.1) with SMTP; Thu, 10 Jun 2004 14:22:52 -0700 Received: (qmail 43877 invoked by uid 65534); 10 Jun 2004 21:22:31 -0000 Date: 10 Jun 2004 21:22:31 -0000 Message-ID: <20040610212231.43875.qmail@minotaur.apache.org> From: adc@apache.org To: directory-cvs@incubator.apache.org Subject: svn commit: rev 21037 - incubator/directory/snickers/branches/ber-decoder/ber-codec/src/java/org/apache/snickers/ber X-Virus-Checked: Checked X-Spam-Rating: minotaur-2.apache.org 1.6.2 0/1000/N 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. - *

- * 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 + *

+ * 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. *

- *

+ *

* 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. *

- * - * @author - * Apache Directory Project + * + * @author Apache Directory Project * @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 ; } }