/*

   Derby - Class org.apache.derby.impl.drda.DDMReader

   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.derby.impl.drda;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import org.apache.derby.iapi.services.sanity.SanityManager;

/**
    The DDMReader is used to read DRDA protocol.   DRDA Protocol is divided into
    three layers corresponding to the DDM three-tier architecture. For each layer,
    their is a DSS (Data Stream Structure) defined.
        Layer A     Communications management services
        Layer B     Agent services
        Layer C     Data management services
    <P>
    At layer A are request, reply and data correlation, structure chaining,
    continuation or termination of chains when errors are detected, interleaving
    and multi-leaving request, reply, and data DSSs for multitasking environments.
    For TCP/IP, the format of the DDM envelope is
        2 bytes     Length of the data
        1 byte      'D0' - indicates DDM data
        1 byte      DDM format byte(DSSFMT) - type of DSS(RQSDSS,RPYDSS), whether it is
                    chained, information about the next chained DSS
        2 bytes     request correlation identifier
    <P>
    The correlation identifier ties together a request, the request data and the
    reply.  In a chained DSS, each request has a correlation identifier which
    is higher than the previous request (all correlation identifiers must
    be greater than 0).
    <P>
    At layer B are object mapping, object validation and command routing.
    Layer B objects with data 5 bytes less than 32K bytes consist of
        2 bytes     Length
        2 bytes     Type of the object (code point)
        Object data
    Object data is either SCALAR or COLLECTION data.  Scalar data consists of
    a string of bytes formatted as the class description of the object required.
    Collections consist of a set of objects in which the entries in the collection
    are nested within the length/ code point of the collection.
    <P>
    Layer B objects with data >=32763 bytes long format is 
        2 bytes     Length - length of class, length, and extended total length fields
                    (high order bit set, indicating >=32763)
        2 bytes     Type of the object (code point)
        n bytes     Extended total length - length of the object
                    (n = Length - 4)
        Object data
    <P>
    At layer C are services each class of DDM object provides.

            |-------------------------------------------|
    Layer C | Specific  |   Specific    |   Specific    |
            | Commands  |   Replies     | Scalars and   |
            | and their |  and their    | Collections   |
            |-------------------------------------------|----------------|
    Layer B | Commands  |    Reply      | Scalars and   | Communications |
            |           |   Messages    | Collections   |                |
            |-----------|---------------|---------------|----------------|
    Layer A |  RQSDSS   |   RPYDSS      | OBJDSS        | CMNDSS         |
            |           |               |               | Mapped Data    |
            |-----------|---------------|---------------|----------------|
            |                DDM Data Stream Structures                  |
            |------------------------------------------------------------|
            
    DSS's may be chained so that more than one can be transmitted at a time
    to improve performance.
    For more details, see DRDA Volume 3 (Distributed Data Management(DDM)
        Architecture (DDS definition)
*/
class DDMReader
{
    private final static int DEFAULT_BUFFER_SIZE = 32767;
    private final static int MAX_MARKS_NESTING = 10;
    private final static int NO_CODEPOINT = -1;
    private final static int EMPTY_STACK = -1;
    private final static boolean ADJUST_LENGTHS = true;
    private final static boolean NO_ADJUST_LENGTHS = false;
    private final static long MAX_EXTDTA_SIZE= Long.MAX_VALUE;
    

    // magnitude represented in an int array, used in BigDecimal conversion
    private static final int[][] tenRadixMagnitude = {
      { 0x3b9aca00 }, // 10^9
      { 0x0de0b6b3, 0xa7640000 }, // 10^18
      { 0x033b2e3c, 0x9fd0803c, 0xe8000000 }, // 10^27
    };

    private DRDAConnThread agent;
    private Utf8CcsidManager utf8CcsidManager;
    private EbcdicCcsidManager ebcdicCcsidManager;
    private CcsidManager ccsidManager;

    // data buffer
    private byte[] buffer;
    private int pos;
    private int count;

    // DDM object collection
    // top of stack
    private int topDdmCollectionStack;
    // length of each object in the stack
    private long[] ddmCollectionLenStack;

    // DDM object length
    private long ddmScalarLen;

    // DSS Length
    private int dssLength;

    // DSS is larger than 32672 (continuation bit is set) so DSS is continued
    private boolean dssIsContinued;

    private boolean terminateChainOnErr;

    // next DSS in the chain has the same correlator
    private boolean dssIsChainedWithSameID;

    // next DSS in the chain has a different correlator
    private boolean dssIsChainedWithDiffID;
    
    // correlation id for the current DSS
    private int dssCorrelationID;

    // previous corelation id
    private int prevCorrelationID;

    // current server codepoint
    private int svrcod;

    // trace object of the associated session
    private DssTrace dssTrace;

    // input stream
    private InputStream inputStream;
    
    // State whether doing layer B Streaming or not.
    private boolean doingLayerBStreaming = false;;
    
    // For JMX statistics. Volatile to ensure we 
    // get one complete long, but we don't bother to synchronize, 
    // since this is just statistics.
    
    volatile long totalByteCount = 0;

    // constructor
    DDMReader (DRDAConnThread agent, DssTrace dssTrace)
    {
        buffer = new byte[DEFAULT_BUFFER_SIZE];
        ddmCollectionLenStack = new long[MAX_MARKS_NESTING];
        initialize(agent, dssTrace);
    }
    /**
     * This constructor is used for testing the protocol
    * It is used by ProtocolTestAdapter to read the protocol returned by the
     * server 
     */
    DDMReader(InputStream inputStream)
    {
        buffer = new byte[DEFAULT_BUFFER_SIZE];
        ddmCollectionLenStack = new long[MAX_MARKS_NESTING];
        
        this.inputStream = inputStream;
        initialize(null, null);
    }

    /**
     * Initialize values for this session, the reader is reused so we need to
     * set null and 0 values
     */
    protected void initialize(DRDAConnThread agent, DssTrace dssTrace)
    {
        this.agent = agent;
        this.utf8CcsidManager = new Utf8CcsidManager();
        this.ebcdicCcsidManager = new EbcdicCcsidManager();
        this.ccsidManager = ebcdicCcsidManager;
        if (agent != null)
        {
            inputStream = agent.getInputStream();
        }
        topDdmCollectionStack = EMPTY_STACK;
        svrcod = 0;
        pos = 0;
        count = 0;
        ddmScalarLen = 0;
        dssLength = 0;
        prevCorrelationID = DssConstants.CORRELATION_ID_UNKNOWN;
        dssCorrelationID = DssConstants.CORRELATION_ID_UNKNOWN;
        this.dssTrace = dssTrace;
        dssIsChainedWithDiffID = false;
        dssIsChainedWithSameID = false;
    }

    // Switch the ccsidManager to the UTF-8 instance
    protected void setUtf8Ccsid() {
        ccsidManager = utf8CcsidManager;
    }
    
    // Switch the ccsidManager to the EBCDIC instance
    protected void setEbcdicCcsid() {
        ccsidManager = ebcdicCcsidManager;
    }
    
    protected boolean terminateChainOnErr()
    {
        return terminateChainOnErr;
    }

    /**
     * Next DSS has same correlator as current DSS
     *
     * @return true if next DSS has the same correlator as current DSS
     */
    protected boolean isChainedWithSameID()
    {
        return dssIsChainedWithSameID;
    }

    /**
     * Next DSS has different correlator than current DSS
     *
     * @return true if next DSS has a different correlator than current DSS
     */
    protected boolean isChainedWithDiffID()
    {
        return dssIsChainedWithDiffID;
    }

    /**
     * Length of current DDM object
     *
     * @return length of DDM object
     */
    protected long getDdmLength()
    {
        return ddmScalarLen;
    }

    /**
     * Is there more in this DDM object
     *
     * @return true if DDM length is > 0
     */
    protected boolean moreDdmData()
    {
        return ddmScalarLen > 0;
    }

    /**
     * Is there more in this DDS object
     *
     * @return true if DDS length is > 0
     */
    protected boolean moreDssData()
    {
        return dssLength > 0;
    }

    /** 
     * Is there more data in the buffer
     *
     * @return true if there is more data in the buffer
     */
    protected boolean moreData()
    {
        return (pos - count) > 0;
    }

    /**
     * Check for the command protocol
     *
     * @return true if this is a command; false otherwise
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected boolean isCmd() throws DRDAProtocolException, java.io.UnsupportedEncodingException
    {
        ensureALayerDataInBuffer(4);
        String val = new String(buffer, 0, 4, NetworkServerControlImpl.DEFAULT_ENCODING);
        return NetworkServerControlImpl.isCmd(val);
    }

    /**
     * Read DSS header
     * DSS Header format is 
     *  2 bytes - length
     *  1 byte  - 'D0'  - indicates DDM data
     *  1 byte  - DSS format
     *      |---|---------|----------|
     *      | 0 |  flags  |  type    |
     *      |---|---------|----------|
     *      | 0 | 1  2  3 | 4 5 6 7  |
     *      |---|---------|----------|
     *      bit 0 - '0'
     *      bit 1 - '0' - unchained, '1' - chained
     *      bit 2 - '0' - do not continue on error, '1' - continue on error
     *      bit 3 - '0' - next DSS has different correlator, '1' - next DSS has
     *                      same correlator
     *      type - 1 - Request DSS
     *           - 2 - Reply DSS
     *           - 3 - Object DSS
     *           - 4 - Communications DSS
     *           - 5 - Request DSS where no reply is expected
     *  2 bytes - request correlation id
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected int readDssHeader () throws DRDAProtocolException
    {
        ensureALayerDataInBuffer (6);

        // read out the DSS length
        dssLength = ((buffer[pos] & 0xff) << 8) +
                    ((buffer[pos + 1] & 0xff) << 0);
        pos += 2;
        // check for the continuation bit and update length as needed.
        if ((dssLength & DssConstants.CONTINUATION_BIT) == 
                DssConstants.CONTINUATION_BIT) 
        {
            dssLength = DssConstants.MAX_DSS_LENGTH;
            dssIsContinued = true;
        }
        else 
        {
            dssIsContinued = false;
        }

        if (dssLength < 6)
            agent.throwSyntaxrm(CodePoint.SYNERRCD_DSS_LESS_THAN_6,
                               DRDAProtocolException.NO_CODPNT_ARG);

        // If the GDS id is not valid, or
        // if the reply is not an RQSDSS nor
        // a OBJDSS, then throw an exception.

        if ((buffer[pos++] & 0xff) != DssConstants.DSS_ID)
            agent.throwSyntaxrm(CodePoint.SYNERRCD_CBYTE_NOT_D0,
                               DRDAProtocolException.NO_CODPNT_ARG);

        int gdsFormatter = buffer[pos++] & 0xff;
        
        if (((gdsFormatter & 0x0F) != DssConstants.DSSFMT_RQSDSS)
            &&((gdsFormatter & 0x0F) != DssConstants.DSSFMT_OBJDSS)) 
        {
            agent.throwSyntaxrm(CodePoint.SYNERRCD_FBYTE_NOT_SUPPORTED,
                               DRDAProtocolException.NO_CODPNT_ARG);
        }

        // Determine if the current DSS is chained with the
        // next DSS, with the same or different request ID.
        if ((gdsFormatter & DssConstants.DSSCHAIN) == DssConstants.DSSCHAIN) 
        {   // on indicates structure chained to next structure
            if ((gdsFormatter & DssConstants.DSSCHAIN_SAME_ID) 
                    == DssConstants.DSSCHAIN_SAME_ID) 
            {
                dssIsChainedWithSameID = true;
                dssIsChainedWithDiffID = false;
            }
            else 
            {
                dssIsChainedWithSameID = false;
                dssIsChainedWithDiffID = true;
            }
            if ((gdsFormatter & DssConstants.DSSCHAIN_ERROR_CONTINUE) 
                == DssConstants.DSSCHAIN_ERROR_CONTINUE)
                terminateChainOnErr = false;
            else
                terminateChainOnErr = true;
        }
        else 
        {
            // chaining bit not b'1', make sure DSSFMT same id not b'1'
            if ((gdsFormatter & DssConstants.DSSCHAIN_SAME_ID) 
                    == DssConstants.DSSCHAIN_SAME_ID)
            {  // Next DSS can not have same correlator
                agent.throwSyntaxrm(CodePoint.SYNERRCD_CHAIN_OFF_SAME_NEXT_CORRELATOR,
                                   DRDAProtocolException.NO_CODPNT_ARG);
            }
            // chaining bit not b'1', make sure no error continuation
            if ((gdsFormatter & DssConstants.DSSCHAIN_ERROR_CONTINUE) 
                == DssConstants.DSSCHAIN_ERROR_CONTINUE) 
            { // must be 'do not continue on error'
                agent.throwSyntaxrm(CodePoint.SYNERRCD_CHAIN_OFF_ERROR_CONTINUE,
                                   DRDAProtocolException.NO_CODPNT_ARG);
            }

            dssIsChainedWithSameID = false;
            dssIsChainedWithDiffID = false;
        }

        dssCorrelationID =
            ((buffer[pos] & 0xff) << 8) +
            ((buffer[pos + 1] & 0xff) << 0);
        pos += 2;
        if (SanityManager.DEBUG)
            trace("dssLength = " + dssLength + " correlationID = " + dssCorrelationID);

        //check that correlationID is the same as previous
        if (prevCorrelationID != DssConstants.CORRELATION_ID_UNKNOWN && 
            dssCorrelationID != prevCorrelationID)
        {
            agent.throwSyntaxrm(CodePoint.SYNERRCD_CHAIN_OFF_ERROR_CONTINUE,
                               DRDAProtocolException.NO_CODPNT_ARG);
        }
        
        // set up previous correlation id to check that next DSS is correctly
        // formatted
        if (dssIsChainedWithSameID)
            prevCorrelationID = dssCorrelationID;
        else
            prevCorrelationID = DssConstants.CORRELATION_ID_UNKNOWN;

        dssLength -= 6;

        return dssCorrelationID;
    }
    /**
     * Read Reply DSS
     * This is used in testing the protocol.  We shouldn't see a reply
     * DSS when we are servicing DRDA commands
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected void readReplyDss() throws DRDAProtocolException
    {
        ensureALayerDataInBuffer (6);

        // read out the DSS length
        dssLength = ((buffer[pos++] & 0xff) << 8) +
                    ((buffer[pos++] & 0xff) << 0);

        // check for the continuation bit and update length as needed.
        if ((dssLength & DssConstants.CONTINUATION_BIT) == 
                DssConstants.CONTINUATION_BIT) 
        {
            dssLength = DssConstants.MAX_DSS_LENGTH;
            dssIsContinued = true;
        }
        else 
        {
            dssIsContinued = false;
        }

        if (dssLength < 6)
            agent.throwSyntaxrm(CodePoint.SYNERRCD_DSS_LESS_THAN_6,
                               DRDAProtocolException.NO_CODPNT_ARG);

        // If the GDS id is not valid, throw exception

        if ((buffer[pos++] & 0xff) != DssConstants.DSS_ID)
            agent.throwSyntaxrm(CodePoint.SYNERRCD_CBYTE_NOT_D0,
                               DRDAProtocolException.NO_CODPNT_ARG);

        int gdsFormatter = buffer[pos++] & 0xff;
        
        // Determine if the current DSS is chained with the
        // next DSS, with the same or different request ID.
        if ((gdsFormatter & DssConstants.DSSCHAIN) == DssConstants.DSSCHAIN) 
        {   // on indicates structure chained to next structure
            if ((gdsFormatter & DssConstants.DSSCHAIN_SAME_ID) 
                    == DssConstants.DSSCHAIN_SAME_ID) 
            {
                dssIsChainedWithSameID = true;
                dssIsChainedWithDiffID = false;
            }
            else 
            {
                dssIsChainedWithSameID = false;
                dssIsChainedWithDiffID = true;
            }
        }
        else 
        {
            dssIsChainedWithSameID = false;
            dssIsChainedWithDiffID = false;
        }

        dssCorrelationID =
            ((buffer[pos++] & 0xff) << 8) +
            ((buffer[pos++] & 0xff) << 0);

        if (SanityManager.DEBUG)                    
            trace("dssLength = " + dssLength + " correlationID = " + dssCorrelationID);

        dssLength -= 6;

    }

    /**
     * Read the DDM Length and CodePoint
     *
     * @param isLayerBStreamingPossible true only when layer B streaming is possible
     * @return - returns codepoint
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected int readLengthAndCodePoint( boolean isLayerBStreamingPossible ) 
        throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (4, NO_ADJUST_LENGTHS);

        ddmScalarLen = readCodePoint();
        int codePoint = readCodePoint();
        
        if (SanityManager.DEBUG)
            trace("length = "+ ddmScalarLen + " codepoint = " + java.lang.Integer.toHexString(codePoint));
        // SYNERRCD 0x0D - Object code point index not supported.
        // the object codepoint index will not be checked here since
        // the parse methods will catch any incorrect/unexpected codepoint values
        // and report them as unsupported objects or parameters.

        // Check if this DDM has extended length field
        if ((ddmScalarLen & DssConstants.CONTINUATION_BIT) == DssConstants.CONTINUATION_BIT) 
        {
            int numberOfExtendedLenBytes = ((int)ddmScalarLen - 
                    DssConstants.CONTINUATION_BIT) - 4;
            int adjustSize = 0;
            ensureBLayerDataInBuffer (numberOfExtendedLenBytes, NO_ADJUST_LENGTHS);
            switch (numberOfExtendedLenBytes) {
            case 8:
                 ddmScalarLen =
                    ((buffer[pos++] & 0xFFL) << 56) +
                    ((buffer[pos++] & 0xFFL) << 48) +
                    ((buffer[pos++] & 0xFFL) << 40) +
                    ((buffer[pos++] & 0xFFL) << 32) +
                    ((buffer[pos++] & 0xFFL) << 24) +
                    ((buffer[pos++] & 0xFFL) << 16) +
                    ((buffer[pos++] & 0xFFL) << 8) +
                    ((buffer[pos++] & 0xFFL) << 0);
                adjustSize = 12;
                break;
            case 6:
                ddmScalarLen =
                    ((buffer[pos++] & 0xFFL) << 40) +
                    ((buffer[pos++] & 0xFFL) << 32) +
                    ((buffer[pos++] & 0xFFL) << 24) +
                    ((buffer[pos++] & 0xFFL) << 16) +
                    ((buffer[pos++] & 0xFFL) << 8) +
                    ((buffer[pos++] & 0xFFL) << 0);
                adjustSize = 10;
                break;
            case 4:
                ddmScalarLen =
                    ((buffer[pos++] & 0xFFL) << 24) +
                    ((buffer[pos++] & 0xFFL) << 16) +
                    ((buffer[pos++] & 0xFFL) << 8) +
                    ((buffer[pos++] & 0xFFL) << 0);
                adjustSize = 8;
                break;
                
            case 0:
                
                if( isLayerBStreamingPossible &&
                    ( codePoint == CodePoint.EXTDTA || 
                      codePoint == CodePoint.QRYDTA ) ){
                    
                    startLayerBStreaming();
                    adjustSize = 4;
                    
                }else {
                    agent.throwSyntaxrm(CodePoint.SYNERRCD_INCORRECT_EXTENDED_LEN,
                                        DRDAProtocolException.NO_CODPNT_ARG);
                }
                
                break;
                
            default:
                agent.throwSyntaxrm(CodePoint.SYNERRCD_INCORRECT_EXTENDED_LEN,
                               DRDAProtocolException.NO_CODPNT_ARG);
        }

            // adjust the lengths here. this is a special case since the
            // extended length bytes do not include their own length.
            for (int i = 0; i <= topDdmCollectionStack; i++) {
                ddmCollectionLenStack[i] -= adjustSize;
            }
            dssLength -= adjustSize;
        }
        else {
            if (ddmScalarLen < 4)
                agent.throwSyntaxrm(CodePoint.SYNERRCD_OBJ_LEN_LESS_THAN_4,
                                   DRDAProtocolException.NO_CODPNT_ARG);
            adjustLengths (4);
        }
        return codePoint;
    }

    /**
     * Read the CodePoint
     *
     * @return - returns codepoint
     */
    protected int readCodePoint()
    {
        return( ((buffer[pos++] & 0xff) << 8) +
          ((buffer[pos++] & 0xff) << 0));
    }

    /**
     * Push DDM Length on to collection stack
     */
    protected void markCollection()
    {
        ddmCollectionLenStack[++topDdmCollectionStack] = ddmScalarLen;
        ddmScalarLen = 0;
    }

    /**
     *  Get the next CodePoint from a collection
     *  @return NO_CODEPOINT if collection stack is empty or remaining length is
     *      0; otherwise,  read length and code point
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected int getCodePoint() throws DRDAProtocolException
    {
        if (topDdmCollectionStack == EMPTY_STACK) 
        {
            return NO_CODEPOINT;
        }
        else 
        {
            // if the collecion is exhausted then return NO_CODEPOINT
            if (ddmCollectionLenStack[topDdmCollectionStack] == 0) 
                {
                // done with this collection so remove it's length from the stack
                ddmCollectionLenStack[topDdmCollectionStack--] = 0;
                return NO_CODEPOINT;
            }
            else {
                return readLengthAndCodePoint( false );
            }
        }
    }
    /**
     * Get the next CodePoint from a collection and check that it matches the specified
     *  CodePoint
     * @param   codePointCheck  - codePoint to check against
     * @return  codePoint
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected int getCodePoint(int codePointCheck) throws DRDAProtocolException
    {
        int codePoint = getCodePoint();
        if (codePoint != codePointCheck)
            agent.missingCodePoint(codePoint);
        return codePoint;
    }
    /**
     * The following routines read different types from the input stream
     * Data can be in network order or platform order depending on whether the
     * data is part of the protocol or data being received
     * The platform is determined by EXCSAT protocol
     */

    /**
     * Read byte value
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected byte readByte () throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (1, ADJUST_LENGTHS);
        return buffer[pos++];
    }

    /**
     * Read byte value and mask out high order bytes before returning
     * @return value
     */
    protected int readUnsignedByte () throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (1, ADJUST_LENGTHS);
        return (int ) (buffer[pos++] & 0xff);
    }

    /**
     * Read network short value
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected int readNetworkShort () throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (2, ADJUST_LENGTHS);
        return ((buffer[pos++] & 0xff) << 8) +
          ((buffer[pos++] & 0xff) << 0);
    }

    /**
     * Read signed network short value
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected int readSignedNetworkShort () throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (2, ADJUST_LENGTHS);
        return (short)(((buffer[pos++] & 0xff) << 8) +
          ((buffer[pos++] & 0xff) << 0));
    }
    /**
     * Read platform short value
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected short readShort (int byteOrder) throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (2, ADJUST_LENGTHS);
        short s = SignedBinary.getShort (buffer, pos, byteOrder);

        pos += 2;

        return s;
    }

    /**
     * Read network int value
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected int readNetworkInt () throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (4, ADJUST_LENGTHS);
        return ((buffer[pos++] & 0xff) << 24) +
               ((buffer[pos++] & 0xff) << 16) +
               ((buffer[pos++] & 0xff) << 8) +
               ((buffer[pos++] & 0xff) << 0);
    }

    /**
     * Read platform int value
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected int readInt (int byteOrder) throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (4, ADJUST_LENGTHS);
        int i = SignedBinary.getInt (buffer, pos, byteOrder);

        pos += 4;

        return i;
    }

    /**
     * Read network long value
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected long readNetworkLong () throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (8, ADJUST_LENGTHS);

        return ((buffer[pos++] & 0xffL) << 56) +
               ((buffer[pos++] & 0xffL) << 48) +
               ((buffer[pos++] & 0xffL) << 40) +
               ((buffer[pos++] & 0xffL) << 32) +
               ((buffer[pos++] & 0xffL) << 24) +
               ((buffer[pos++] & 0xffL) << 16) +
               ((buffer[pos++] & 0xffL) << 8) +
               ((buffer[pos++] & 0xffL) << 0);
    }

    
    /**
     * Read network six byte value and put it in a long v
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected long readNetworkSixByteLong() throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (6, ADJUST_LENGTHS);

        return (
                ((buffer[pos++] & 0xffL) << 40) +
               ((buffer[pos++] & 0xffL) << 32) +
               ((buffer[pos++] & 0xffL) << 24) +
               ((buffer[pos++] & 0xffL) << 16) +
               ((buffer[pos++] & 0xffL) << 8) +
               ((buffer[pos++] & 0xffL) << 0));
    }

    /**
     * Read platform long value
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected long readLong (int byteOrder) throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (8, ADJUST_LENGTHS);
        long l = SignedBinary.getLong (buffer, pos, byteOrder);

        pos += 8;

        return l;
    }

    /**
     * Read platform float value
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected float readFloat(int byteOrder) throws DRDAProtocolException
    {
        return Float.intBitsToFloat(readInt(byteOrder));
    }

    /**
     * Read platform double value
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected double readDouble(int byteOrder) throws DRDAProtocolException
    {
        return Double.longBitsToDouble(readLong(byteOrder));
    }

    /**
     * Read a BigDecimal value
     * @param   precision of the BigDecimal
     * @param   scale of the BigDecimal
     * @return  value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected BigDecimal readBigDecimal(int precision, int scale) throws DRDAProtocolException
    {
      // The byte-length of a packed decimal with precision p is always p/2 + 1
      int length = precision / 2 + 1;

      ensureBLayerDataInBuffer (length, ADJUST_LENGTHS);

      // check for sign.
      int signum;
      if ((buffer[pos+length-1] & 0x0F) == 0x0D)
        signum = -1;
      else
        signum =  1;

      if (precision <= 18) {
        // can be handled by long without overflow.
        long value = packedNybblesToLong(buffer, pos, 0, length*2-1);
        if (signum < 0) {
            value = -value;
        }

        pos += length;
        return BigDecimal.valueOf(value, scale);
      }
      else if (precision <= 27) {
        // get the value of last 9 digits (5 bytes).
        int lo = packedNybblesToInt(buffer, pos, (length-5)*2, 9);
        // get the value of another 9 digits (5 bytes).
        int me = packedNybblesToInt(buffer, pos, (length-10)*2+1, 9);
        // get the value of the rest digits.
        int hi = packedNybblesToInt(buffer, pos, 0, (length-10)*2+1);

        // compute the int array of magnitude.
        int[] value = computeMagnitude(new int[] {hi, me, lo});

        // convert value to a byte array of magnitude.
        byte[] magnitude = new byte[12];
        magnitude[0]  = (byte)(value[0] >>> 24);
        magnitude[1]  = (byte)(value[0] >>> 16);
        magnitude[2]  = (byte)(value[0] >>> 8);
        magnitude[3]  = (byte)(value[0]);
        magnitude[4]  = (byte)(value[1] >>> 24);
        magnitude[5]  = (byte)(value[1] >>> 16);
        magnitude[6]  = (byte)(value[1] >>> 8);
        magnitude[7]  = (byte)(value[1]);
        magnitude[8]  = (byte)(value[2] >>> 24);
        magnitude[9]  = (byte)(value[2] >>> 16);
        magnitude[10] = (byte)(value[2] >>> 8);
        magnitude[11] = (byte)(value[2]);

        pos += length;
        return new java.math.BigDecimal (new java.math.BigInteger(signum, magnitude), scale);
      }
      else if (precision <= 31) {
        // get the value of last 9 digits (5 bytes).
        int lo   = packedNybblesToInt(buffer, pos, (length-5)*2, 9);
        // get the value of another 9 digits (5 bytes).
        int meLo = packedNybblesToInt(buffer, pos, (length-10)*2+1, 9);
        // get the value of another 9 digits (5 bytes).
        int meHi = packedNybblesToInt(buffer, pos, (length-14)*2, 9);
        // get the value of the rest digits.
        int hi   = packedNybblesToInt(buffer, pos, 0, (length-14)*2);

        // compute the int array of magnitude.
        int[] value = computeMagnitude(new int[] {hi, meHi, meLo, lo});

        // convert value to a byte array of magnitude.
        byte[] magnitude = new byte[16];
        magnitude[0]  = (byte)(value[0] >>> 24);
        magnitude[1]  = (byte)(value[0] >>> 16);
        magnitude[2]  = (byte)(value[0] >>> 8);
        magnitude[3]  = (byte)(value[0]);
        magnitude[4]  = (byte)(value[1] >>> 24);
        magnitude[5]  = (byte)(value[1] >>> 16);
        magnitude[6]  = (byte)(value[1] >>> 8);
        magnitude[7]  = (byte)(value[1]);
        magnitude[8]  = (byte)(value[2] >>> 24);
        magnitude[9]  = (byte)(value[2] >>> 16);
        magnitude[10] = (byte)(value[2] >>> 8);
        magnitude[11] = (byte)(value[2]);
        magnitude[12] = (byte)(value[3] >>> 24);
        magnitude[13] = (byte)(value[3] >>> 16);
        magnitude[14] = (byte)(value[3] >>> 8);
        magnitude[15] = (byte)(value[3]);

        pos += length;
        return new java.math.BigDecimal (new java.math.BigInteger(signum, magnitude), scale);
      }
      else {
        pos += length;
        // throw an exception here if nibbles is greater than 31
        throw new java.lang.IllegalArgumentException("Decimal may only be up to 31 digits!");
      }
    }

    /**
     * Creates an InputStream which can stream EXTDTA objects.
     * The InputStream uses this DDMReader to read data from network. The 
     * DDMReader should not be used before all data in the stream has been read.
     * @param checkNullability used to check if the stream is null. If it is 
     * null, this method returns null
     * @return EXTDTAReaderInputStream object which can be passed to prepared
     *         statement as a binary stream.
     * @exception DRDAProtocolException standard DRDA protocol exception
     */
    EXTDTAReaderInputStream getEXTDTAReaderInputStream
        (final boolean checkNullability)
        throws DRDAProtocolException
    {
        if (checkNullability && isEXTDTANull()) {
            return null;
        }

        // Check if we must read the status byte sent by the client.
        boolean readEXTDTAStatusByte =
                agent.getSession().appRequester.supportsEXTDTAAbort();
            
        if (doingLayerBStreaming) {
            return new LayerBStreamedEXTDTAReaderInputStream(
                    this, readEXTDTAStatusByte);
        } else {
            return new StandardEXTDTAReaderInputStream(
                    this, readEXTDTAStatusByte);
        }

    }
    
    /**
     * This method is used by EXTDTAReaderInputStream to read the first chunk 
     * of data.
     * This lengthless method must be called only when layer B streaming.
     *
     * @exception DRDAProtocolException standard DRDA protocol exception
     */
    ByteArrayInputStream readLOBInitStream() 
        throws DRDAProtocolException
    {
        if ( SanityManager.DEBUG ) {
            SanityManager.ASSERT( doingLayerBStreaming );
        }
        
        return readLOBInitStream( 0 );
        
    }
    

    /**
     * This method is used by EXTDTAReaderInputStream to read the first chunk 
     * of data.
     * @param desiredLength the desired length of chunk. This parameter is ignored when layerB Streaming is doing.
     * @exception DRDAProtocolException standard DRDA protocol exception
     */
    ByteArrayInputStream readLOBInitStream(final long desiredLength) 
        throws DRDAProtocolException
    {
        return readLOBChunk(false, desiredLength);
    }
    
    
    /**
     * This method is used by EXTDTAReaderInputStream to read the next chunk 
     * of data.
     *
     * Calling this method finishes layer B streaming 
     * if continuation of DSS segment was finished.
     * This lengthless method must be called only when layer B streaming.
     *
     * @exception IOException IOException
     */
    ByteArrayInputStream readLOBContinuationStream ()
        throws IOException
    {
        if ( SanityManager.DEBUG ) {
            SanityManager.ASSERT( doingLayerBStreaming );
        }
        return readLOBContinuationStream( 0 );
    }
    

    /**
     * This method is used by EXTDTAReaderInputStream to read the next chunk 
     * of data.
     *
     * Furthermore, when Layer B streaming is carried out,
     * calling this method finishes layer B streaming 
     * if continuation of DSS segment was finished.
     *
     * @param desiredLength the desired length of chunk. This parameter is ignored when layerB Streaming is doing.
     * @exception IOException IOException
     */
    ByteArrayInputStream readLOBContinuationStream (final long desiredLength)
        throws IOException
    {
        try {
            return readLOBChunk(true, desiredLength);
        } catch (DRDAProtocolException e) {
            e.printStackTrace(agent.getServer().logWriter);
            throw new IOException(e.getMessage());
        }
    }

    /**
     * This method is used by EXTDTAReaderInputStream to read the next chunk 
     * of data.
     *
     * Furthermore, when Layer B streaming is carried out,
     * calling this method may finish layer B streaming.
     *
     * @param readHeader set to true if the dss continuation should be read
     * @param desiredLength the desired length of chunk. This parameter is ignored when layerB Streaming is doing.
     * @exception DRDAProtocolException standard DRDA protocol exception
     */
    private ByteArrayInputStream readLOBChunk
        (final boolean readHeader, final long desiredLength)
        throws DRDAProtocolException
    {
        if (readHeader) {
            readDSSContinuationHeader();
        }
        
        int copySize = doingLayerBStreaming ? 
            dssLength : 
            (int) Math.min(dssLength, desiredLength);
        
        // read the segment
        ensureALayerDataInBuffer (copySize);
        
        if( ! doingLayerBStreaming ){
            adjustLengths (copySize);
            
        }else{
            dssLength -= copySize;
            
        }
        
        // Create ByteArrayInputStream on top of buffer. 
        // This will not make a copy of the buffer.
        ByteArrayInputStream bais = 
            new ByteArrayInputStream(buffer, pos, copySize);
        pos += copySize;
        
        if( doingLayerBStreaming && 
            ! dssIsContinued )
            finishLayerBStreaming();
        
        return bais;
    }

    byte[] getExtData (long desiredLength, boolean checkNullability) throws DRDAProtocolException
  {

      if ( SanityManager.DEBUG ) {
            SanityManager.ASSERT( ! doingLayerBStreaming );
        }

    boolean readHeader;
    int copySize;
    ByteArrayOutputStream baos;
    boolean isLengthAndNullabilityUnknown = false;

    
    if (desiredLength != -1) {
        // allocate a stream based on a known amount of data
        baos = new ByteArrayOutputStream ((int) desiredLength);
    }
    else {
        // allocate a stream to hold an unknown amount of data
        baos = new ByteArrayOutputStream ();
        //isLengthAndNullabilityUnknown = true;
        // If we aren't given a  length get the whole thing.
        desiredLength = MAX_EXTDTA_SIZE;
    }
    

    // check for a null EXTDTA value, if it is nullable and if streaming
    if (checkNullability)
      if (isEXTDTANull())
        return null;

    // set the amount to read for the first segment
    copySize = (int) Math.min(dssLength,desiredLength); //note: has already been adjusted for headers


    //if (checkNullability)  // don't count the null byte we've already read
    //copySize--;

    do {
      // determine if a continuation header needs to be read after the data
      if (dssIsContinued)
        readHeader = true;
      else
        readHeader = false;

      // read the segment
      ensureALayerDataInBuffer (copySize);
      adjustLengths (copySize);
      baos.write (buffer, pos, copySize);
      pos += copySize;
      desiredLength -= copySize;

      // read the continuation header, if necessary
      if (readHeader)
        readDSSContinuationHeader ();

      copySize = (int) Math.min(dssLength,desiredLength); //note: has already been adjusted for headers

    }
    while (readHeader == true && desiredLength > 0);

    return baos.toByteArray();
  }


  // reads a DSS continuation header
  // prereq: pos is positioned on the first byte of the two-byte header
  // post:   dssIsContinued is set to true if the continuation bit is on, false otherwise
  //         dssLength is set to DssConstants.MAXDSS_LEN - 2 (don't count the header for the next read)
  // helper method for getEXTDTAData
    private void readDSSContinuationHeader () throws DRDAProtocolException
  {
    ensureALayerDataInBuffer(2);

    dssLength =
      ((buffer[pos++]&0xFF) << 8) +
      ((buffer[pos++]&0xFF) << 0);

    if ((dssLength & 0x8000) == 0x8000) {
      dssLength = DssConstants.MAX_DSS_LENGTH;
      dssIsContinued = true;
    }
    else {
      dssIsContinued = false;
    }
    // it is a syntax error if the dss continuation header length
    // is less than or equal to two
    if (dssLength <= 2) {
        agent.throwSyntaxrm(CodePoint.SYNERRCD_DSS_CONT_LESS_OR_EQUAL_2,
                               DRDAProtocolException.NO_CODPNT_ARG);
    }

    dssLength -= 2;  // avoid consuming the DSS cont header
  }

// checks the null EXTDTA byte
  // returns true if null, false otherwise
  // helper method for getEXTDTAData
  private boolean isEXTDTANull () throws DRDAProtocolException
  {
    // make sure that the null byte is in the buffer
    ensureALayerDataInBuffer (1);
    adjustLengths (1);

    // examine the null byte
    byte nullByte = buffer[pos++];
    if (nullByte == (byte)0x00)
      return false;

    return true;
  }


   /**
    * Convert a range of packed nybbles (up to 9 digits without overflow) to an int.
    * Note that for performance purpose, it does not do array-out-of-bound checking.
    * @param buffer         buffer to read from
    * @param offset         offset in the buffer
    * @param startNybble        start nybble
    * @param numberOfNybbles    number of nybbles
    * @return   an int value
    */
    private int packedNybblesToInt (byte[] buffer,
                                         int offset,
                                         int startNybble,
                                         int numberOfNybbles)
    {
      int value = 0;

      int i = startNybble / 2;
      if ((startNybble % 2) != 0) {
        // process low nybble of the first byte if necessary.
        value += buffer[offset+i] & 0x0F;
        i++;
      }

      int endNybble = startNybble + numberOfNybbles -1;
      for (; i<(endNybble+1)/2; i++) {
        value = value*10 + ((buffer[offset+i] & 0xF0) >>> 4); // high nybble.
        value = value*10 +  (buffer[offset+i] & 0x0F);        // low nybble.
      }

      if ((endNybble % 2) == 0) {
        // process high nybble of the last byte if necessary.
        value = value*10 + ((buffer[offset+i] & 0xF0) >>> 4);
      }

      return value;
    }

    /**
     * Convert a range of packed nybbles (up to 18 digits without overflow) to a long.
     * Note that for performance purpose, it does not do array-out-of-bound checking.
     * @param buffer        buffer to read from
     * @param offset        offset in the buffer
     * @param startNybble       start nybble
     * @param numberOfNybbles   number of nybbles
     * @return  an long value
     */
    private long packedNybblesToLong (byte[] buffer,
                                           int offset,
                                           int startNybble,
                                           int numberOfNybbles)
    {
      long value = 0;

      int i = startNybble / 2;
      if ((startNybble % 2) != 0) {
        // process low nybble of the first byte if necessary.
        value += buffer[offset+i] & 0x0F;
        i++;
      }

      int endNybble = startNybble + numberOfNybbles -1;
      for (; i<(endNybble+1)/2; i++) {
        value = value*10 + ((buffer[offset+i] & 0xF0) >>> 4); // high nybble.
        value = value*10 +  (buffer[offset+i] & 0x0F);        // low nybble.
      }

      if ((endNybble % 2) == 0) {
        // process high nybble of the last byte if necessary.
        value = value*10 + ((buffer[offset+i] & 0xF0) >>> 4);
      }

      return value;
    }

    /**
     * Compute the int array of magnitude from input value segments.
     * @param   input value segments
     * @return  array of int magnitudes
     */
    private int[] computeMagnitude(int[] input)
    {
        int length = input.length;
        int[] mag = new int[length];

        mag[length-1] = input[length-1];
        for (int i=0; i<length-1; i++) {
          int carry = 0;
          int j = tenRadixMagnitude[i].length-1;
          int k = length-1;
          for (; j>=0; j--, k--) {
            long product = (input[length-2-i] & 0xFFFFFFFFL) * (tenRadixMagnitude[i][j] & 0xFFFFFFFFL)
                         + (mag[k] & 0xFFFFFFFFL) // add previous value
                         + (carry & 0xFFFFFFFFL); // add carry
            carry  = (int) (product >>> 32);
            mag[k] = (int) (product & 0xFFFFFFFFL);
          }
          mag[k] = (int) carry;
        }
        return mag;
    }

    /**
     * Read encrypted string
     * @param   decryptM  decryption manager
     * @param   securityMechanism security mechanism
     * @param   initVector   initialization vector for cipher
     * @param   sourcePublicKey  public key (as in Deffie-Hellman algorithm)
     *                           from source (encryptor)
     * @return  decrypted string
     *
     * @exception DRDAProtocolException if a protocol error is detected
     * @exception java.sql.SQLException wrapping any exception in decryption
     */
    protected String readEncryptedString (DecryptionManager decryptM, int securityMechanism,
                                         byte[] initVector, byte[] sourcePublicKey)
            throws DRDAProtocolException, java.sql.SQLException
    {
        byte[] cipherText = readBytes();
        byte[] plainText = null;
        plainText = decryptM.decryptData(cipherText, securityMechanism, initVector,
                                             sourcePublicKey);
        if (plainText == null)
            return null;
        else
            return ccsidManager.convertToJavaString(plainText);
    }

    /**
     * Read string value
     * Strings in DRDA protocol are encoded in EBCDIC by default so we
     * need to convert to UCS2
     * @param length  - length of string to read
     * @return value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected String readString (int length) throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (length, ADJUST_LENGTHS);

        String result = ccsidManager.convertToJavaString(buffer, pos, length);
        pos += length;
        return result;
    }

    /**
     * Read string value into a <code>DRDAString</code> object.
     *
     * @param dst  destination for the read string
     * @param size size (in bytes) of string to read
     * @param unpad if true, remove padding (trailing spaces)
     *
     * @exception DRDAProtocolException
     */
    protected void readString(DRDAString dst, int size, boolean unpad)
        throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer(size, ADJUST_LENGTHS);
        int startPos = pos;
        pos += size;
        if (unpad) {
            while ((size > 0) &&
                   (buffer[startPos + size - 1] == ccsidManager.space)) {
                --size;
            }
        }
        dst.setBytes(buffer, startPos, size);
    }

    /**
     * Read encoded string value
     * @param length  - length of string to read
     * @return value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected String readString (int length, String encoding) 
        throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (length, ADJUST_LENGTHS);
        String s = null;

        try {
          s = new String (buffer, pos, length, encoding);
        }
        catch (java.io.UnsupportedEncodingException e) {
            agent.agentError("UnsupportedEncodingException in readString, encoding = " 
                    + encoding);
            e.printStackTrace(agent.getServer().logWriter);
        }
        
        pos += length;
        return s;
    }

    /**
     * Read specified length of string value in DDM data with default encoding
     * @param length  - length of string to read
     * @return value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected String readStringData(int length)
        throws DRDAProtocolException
    {
        return readString(length, NetworkServerControlImpl.DEFAULT_ENCODING);
    }

    /**
     * Read length delimited string value in DDM data with default encoding
     * @return value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected String readLDStringData(String encoding)
        throws DRDAProtocolException
    {
        int length = readNetworkShort();
        return readString(length, encoding);
    }

    /**
     * Read string value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected String readString () throws DRDAProtocolException
    {
        return readString((int)ddmScalarLen);
    }

    /**
     * Read byte string value
     * @param length  - length of string to read
     * @return byte array
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected byte[] readBytes (int length) throws DRDAProtocolException
    {
        byte[] b;

        if (length < DssConstants.MAX_DSS_LENGTH)
        {
            ensureBLayerDataInBuffer (length, ADJUST_LENGTHS);
            b = new byte[length];
            System.arraycopy(buffer,pos,b,0,length);
            pos +=length;
        }
        else
            b = getExtData(length,false);
        return b;
    }
    
    /**
     * Read byte string value
     * @return byte array
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected byte[] readBytes () throws DRDAProtocolException
    {
        return readBytes((int)ddmScalarLen);
    }

    /**
     * Skip byte string value
     * @param length  - length of string to skip
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected void skipBytes (int length) throws DRDAProtocolException
    {
        ensureBLayerDataInBuffer (length, ADJUST_LENGTHS);
        pos += length;
    }

    /**
     * Skip byte string value
     *
     * @exception DRDAProtocolException
     */
    protected void skipBytes () throws DRDAProtocolException
    {
        skipBytes((int)ddmScalarLen);
    }

    /**
     * Skip remaining DSS
     *
     * @exception DRDAProtocolException
     */
    protected void skipDss() throws DRDAProtocolException
    {
        while (dssIsContinued)
        {
            skipBytes((int)dssLength);
            readDSSContinuationHeader();
        }
        skipBytes((int)dssLength);
        topDdmCollectionStack = EMPTY_STACK;
        ddmScalarLen = 0;
        dssLength = 0;

    }

    protected void clearBuffer() throws DRDAProtocolException
    {
        skipBytes(java.lang.Math.min(dssLength, count - pos));
        dssIsChainedWithSameID = false;
        dssIsChainedWithDiffID = false;
    }

    /**
     * Convert EBCDIC byte array to unicode string
     *
     * @param   buf - byte array
     * @return string
     */
    protected String convertBytes(byte[] buf)
    {
        return ccsidManager.convertToJavaString (buf, 0, buf.length);
    }

    // Private methods
    /**
     * Adjust remaining length
     *
     * @param length - adjustment length
     */
    private void adjustLengths(int length)
    {
        ddmScalarLen -= length;
        for (int i = 0; i <= topDdmCollectionStack; i++) {
          ddmCollectionLenStack[i] -= length;
        }
        dssLength -= length;
    }

    /********************************************************************/
    /*   NetworkServerControl  command protocol reading routines        
     */
    /********************************************************************/
    /**
     * Read string value
     * @param length  - length of string to read
     * @return value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected String readCmdString (int length) throws DRDAProtocolException, java.io.UnsupportedEncodingException
    {
        if (length == 0)
            return null;

        ensureBLayerDataInBuffer (length, ADJUST_LENGTHS);
        String result = new String (buffer, pos, length,
                                        NetworkServerControlImpl.DEFAULT_ENCODING);
        pos += length;
        return result;
    }
    /**
     * Read string value
     * @return value
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    protected String readCmdString () throws DRDAProtocolException, java.io.UnsupportedEncodingException
    {
        int length = readNetworkShort();
        return readCmdString(length);
        
    }

    /**************************************************************************/
    /*   Private methods
    /**************************************************************************/
    /**
     * Make sure a certain amount of Layer A data is in the buffer.
     * The data will be in the buffer after this method is called.
     *
     * @param desiredDataSize - amount of data we need
     *
     * @exception   DRDAProtocolException
     */
    private void ensureALayerDataInBuffer (int desiredDataSize) 
        throws DRDAProtocolException
    {
        // calulate the the number of bytes in the buffer.
        int avail = count - pos;

        // read more bytes off the network if the data is not in the buffer already.
        if (avail < desiredDataSize) 
        {
          fill (desiredDataSize - avail);
        }
    }
    /**
     * Make sure a certain amount of Layer B data is in the buffer.
     * The data will be in the buffer after this method is called.
     *
     * @param desiredDataSize - amount of data we need
     * @param adjustLen - whether to adjust the remaining lengths
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    private void ensureBLayerDataInBuffer (int desiredDataSize, boolean adjustLen) 
        throws DRDAProtocolException
    {
        if (dssIsContinued && (desiredDataSize > dssLength)) {
            // The data that we want is split across multiple DSSs
            int continueDssHeaderCount =
                (desiredDataSize - dssLength) / DssConstants.MAX_DSS_LENGTH + 1;
            // Account for the extra header bytes (2 length bytes per DSS)
            ensureALayerDataInBuffer(
                    desiredDataSize + 2 * continueDssHeaderCount);
            compressBLayerData(continueDssHeaderCount);
        } else {
            ensureALayerDataInBuffer(desiredDataSize);
        }

        if (adjustLen)
            adjustLengths(desiredDataSize);
    }

    /**
     * Compress B Layer data if extended total length is used
     * by removing the continuation headers
     *
     * @param continueDssHeaderCount - amount of data we need
     *
     * @exception   throws DRDAProtocolException
     */
    private void compressBLayerData (int continueDssHeaderCount) 
        throws DRDAProtocolException
    {

        
        // jump to the last continuation header.
        int tempPos = 0;
        for (int i = 0; i < continueDssHeaderCount; i++) 
        {
            // the first may be less than the size of a full DSS
            if (i == 0) 
            {
                // only jump by the number of bytes remaining in the current DSS
                tempPos = pos + dssLength;
            }
            else 
            {
                // all other jumps are for a full continued DSS
                tempPos += DssConstants.MAX_DSS_LENGTH;
            }
        }


        // for each of the DSS headers to remove,
        // read out the continuation header and increment the DSS length by the
        // size of the continuation bytes,  then shift the continuation data as needed.
        int shiftSize = 0;
        int bytesToShift = 0;
        int continueHeaderLength = 0;
        int newdssLength = 0;


        for (int i = 0; i < continueDssHeaderCount; i++) 
        {
            continueHeaderLength = ((buffer[tempPos] & 0xff) << 8) +
                ((buffer[tempPos + 1] & 0xff) << 0);

            if (i == 0) 
            {
                // if this is the last one (farthest down stream and first to strip out)

                if ((continueHeaderLength & DssConstants.CONTINUATION_BIT) 
                        == DssConstants.CONTINUATION_BIT)
                {
                  // the last DSS header is again continued
                  continueHeaderLength = DssConstants.MAX_DSS_LENGTH;
                  dssIsContinued = true;
                }
                else 
                {
                  // the last DSS header was not contiued so update continue state flag
                  dssIsContinued = false;
                }
                // the very first shift size is 2
                shiftSize = 2;
            }
            else 
            {
                // already removed the last header so make sure the chaining flag is on
                if ((continueHeaderLength & DssConstants.CONTINUATION_BIT) == 
                        DssConstants.CONTINUATION_BIT)
                {
                  continueHeaderLength = DssConstants.MAX_DSS_LENGTH;
                }
                else 
                {
                  // this is a syntax error but not really certain which one.
                  // for now pick 0x02 which is DSS header Length does not 
                  // match the number
                    // of bytes of data found.
                    agent.throwSyntaxrm(CodePoint.SYNERRCD_DSS_LENGTH_BYTE_NUMBER_MISMATCH,
                                       DRDAProtocolException.NO_CODPNT_ARG);
                }
                // increase the shift size by 2
                shiftSize += 2;
            }

            // it is a syntax error if the DSS continuation is less 
            // than or equal to two
            if (continueHeaderLength <= 2) 
            {
                agent.throwSyntaxrm(CodePoint.SYNERRCD_DSS_CONT_LESS_OR_EQUAL_2,
                               DRDAProtocolException.NO_CODPNT_ARG);
            }

            newdssLength += (continueHeaderLength-2);

            // calculate the number of bytes to shift
            if (i != (continueDssHeaderCount - 1))
                bytesToShift = DssConstants.MAX_DSS_LENGTH;
            else
                bytesToShift = dssLength;

            tempPos -= (bytesToShift - 2);
            System.arraycopy(buffer, tempPos - shiftSize, buffer, tempPos,
                             bytesToShift);
        }
        // reposition the start of the data after the final DSS shift.
        pos = tempPos;
        dssLength += newdssLength;
    }

    /**
     * Methods to manage the data buffer.
     * Methods orginally from JCC
     * RESOLVE: need to check if this is the best performing way of doing this
     */

    /**
     * This is a helper method which shifts the buffered bytes from
     * wherever they are in the current buffer to the beginning of
     * different buffer (note these buffers could be the same).
     * State information is updated as needed after the shift.
     * @param destinationBuffer - buffer to shift data to
     */
    private void shiftBuffer (byte[] destinationBuffer)
    {
        // calculate the size of the data in the current buffer.
        int sz = count - pos;
        if (SanityManager.DEBUG) {
            if ((sz < 0 || pos < 0) )
            {
                SanityManager.THROWASSERT(
                          "Unexpected data size or position. sz=" + sz + 
                          " count=" + count +" pos=" + pos);
            }
        }
        
        // copy this data to the new buffer startsing at position 0.
        System.arraycopy (buffer, pos, destinationBuffer, 0, sz);

        // update the state information for data in the new buffer.
        pos = 0;
        count = sz;

        // replace the old buffer with the new buffer.
        buffer = destinationBuffer;
    }
    /**
     * This method makes sure there is enough room in the buffer
     * for a certain number of bytes.  This method will allocate
     * a new buffer if needed and shift the bytes in the current buffer
     * to make ensure space is available for a fill.  Right now
     * this method will shift bytes as needed to make sure there is
     * as much room as possible in the buffer before trying to
     * do the read.  The idea is to try to have space to get as much data as possible
     * if we need to do a read on the socket's stream.
     *
     * @param desiredSpace - amount of data we need
     */
    private void ensureSpaceInBufferForFill (int desiredSpace)
    {
        // calculate the total unused space in the buffer.
        // this includes any space at the end of the buffer and any free
        // space at the beginning resulting from bytes already read.
        int currentAvailableSpace = (buffer.length - count) + pos;

        // check to see if there is enough free space.
        if (currentAvailableSpace < desiredSpace) {

            // there is not enough free space so we need more storage.
            // we are going to double the buffer unless that happens to still be 
            // too small. If more than double the buffer is needed, 
            // use the smallest amount over this as possible.
            int doubleBufferSize = (2 * buffer.length);
            int minumNewBufferSize = (desiredSpace - currentAvailableSpace) + 
                buffer.length;
            int newsz = minumNewBufferSize <= doubleBufferSize ? 
                doubleBufferSize : minumNewBufferSize;

            byte[] newBuffer = new byte[newsz];

            // shift everything from the old buffer to the new buffer
            shiftBuffer (newBuffer);
        }
        else {

            // there is enough free space in the buffer but let's make sure
            // it is all at the end.
            // this is also important because if we are going to do a read, 
            // it would be nice
            // to get as much data as possible and making room at the end 
            // if the buffer helps to ensure this.
            if (pos != 0) {
                shiftBuffer (buffer);
            }
        }
    }

    /**
     * This method will attempt to read a minimum number of bytes
     * from the underlying stream.  This method will keep trying to
     * read bytes until it has obtained at least the minimum number.
     * @param minimumBytesNeeded - minimum required bytes
     *
     * @exception DRDAProtocolException if a protocol error is detected
     */
    private void fill (int minimumBytesNeeded) throws DRDAProtocolException
    {
        // make sure that there is enough space in the buffer to hold
        // the minimum number of bytes needed.
        ensureSpaceInBufferForFill (minimumBytesNeeded);

        // read until the minimum number of bytes needed is now in the buffer.
        // hopefully the read method will return as many bytes as it can.
        int totalBytesRead = 0;
        int actualBytesRead = 0;
        do {
            try {
                actualBytesRead = inputStream.read (
                  buffer, count, buffer.length - count);
            } catch (java.net.SocketTimeoutException ste) {

                // Transport the timeout out through the layers. This
                // exception is caught in DRDAConnThread.run();
                throw new DRDASocketTimeoutException(agent);

            } catch (java.io.IOException ioe) {
                agent.markCommunicationsFailure("DDMReader.fill()",
                                                "InputStream.read()", ioe.getMessage(), "*");
            } finally {
                if ((dssTrace != null) && dssTrace.isComBufferTraceOn())
                  dssTrace.writeComBufferData (buffer,
                                               count,
                                               actualBytesRead,
                                               DssTrace.TYPE_TRACE_RECEIVE,
                                               "Request",
                                               "fill",
                                               5);
            }
            if (actualBytesRead != -1)
            {
                count += actualBytesRead;
                totalBytesRead += actualBytesRead;
            }

        } while ((totalBytesRead < minimumBytesNeeded) && (actualBytesRead != -1));

        if (actualBytesRead == -1) 
        {
            if (totalBytesRead < minimumBytesNeeded) 
            {
                agent.markCommunicationsFailure ("DDMReader.fill()",
                  "InputStream.read()", "insufficient data", "*");
            }
        }
        totalByteCount += totalBytesRead;
    }

    /**
     * Print a internal trace message
     */
    private void trace(String msg)
    {
        if (agent != null)
            agent.trace(msg);
    }

    /**
     * Return chaining bit for current DSS.
     */
    protected byte getCurrChainState() {

        if (!dssIsChainedWithSameID && !dssIsChainedWithDiffID)
            return DssConstants.DSS_NOCHAIN;

        if (dssIsChainedWithSameID)
            return DssConstants.DSSCHAIN_SAME_ID;

        return DssConstants.DSSCHAIN;

    }
    
    
    private void startLayerBStreaming() {
        doingLayerBStreaming = true;
    }
    
    
    private void finishLayerBStreaming() {
        doingLayerBStreaming = false;
    }
    
    
    boolean doingLayerBStreaming() {
        return doingLayerBStreaming;
    }
    
    
}
