/*
 * Copyright (C) 2005 Alfresco, Inc.
 *
 * Licensed under the Mozilla Public License version 1.1 
 * with a permitted attribution clause. You may obtain a
 * copy of the License at
 *
 *   http://www.alfresco.org/legal/license.txt
 *
 * 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.alfresco.filesys.smb.server;

import java.io.IOException;

import org.alfresco.filesys.netbios.RFCNetBIOSProtocol;
import org.alfresco.filesys.smb.PacketType;
import org.alfresco.filesys.smb.TransactBuffer;
import org.alfresco.filesys.util.DataBuffer;
import org.alfresco.filesys.util.DataPacker;

/**
 * SMB server transact packet class
 */
class SMBSrvTransPacket extends SMBTransPacket
{

    // Define the number of standard parameters for a server response

    private static final int StandardParamsResponse = 10;

    // Offset to the setup response paramaters

    protected static final int SetupOffsetResponse = PARAMWORDS + (StandardParamsResponse * 2);

    /**
     * Construct an SMB transaction packet
     * 
     * @param buf Buffer that contains the SMB transaction packet.
     */

    public SMBSrvTransPacket(byte[] buf)
    {
        super(buf);
    }

    /**
     * Construct an SMB transaction packet
     * 
     * @param siz Size of packet to allocate.
     */

    public SMBSrvTransPacket(int siz)
    {
        super(siz);

        // Set the multiplex id for this transaction

        setMultiplexId(getNextMultiplexId());
    }

    /**
     * Initialize the transact reply parameters.
     * 
     * @param pkt Reply SMB packet.
     * @param prmCnt Count of returned parameter bytes.
     * @param prmPos Starting offset to the parameter block.
     * @param dataCnt Count of returned data bytes.
     * @param dataPos Starting offset to the data block.
     */
    public final static void initTransactReply(SMBSrvPacket pkt, int prmCnt, int prmPos, int dataCnt, int dataPos)
    {

        // Set the total parameter words

        pkt.setParameterCount(10);

        // Set the total parameter/data bytes

        pkt.setParameter(0, prmCnt);
        pkt.setParameter(1, dataCnt);

        // Clear the reserved parameter

        pkt.setParameter(2, 0);

        // Set the parameter byte count/offset for this packet

        pkt.setParameter(3, prmCnt);
        pkt.setParameter(4, prmPos - RFCNetBIOSProtocol.HEADER_LEN);

        // Set the parameter displacement

        pkt.setParameter(5, 0);

        // Set the data byte count/offset for this packet

        pkt.setParameter(6, dataCnt);
        pkt.setParameter(7, dataPos - RFCNetBIOSProtocol.HEADER_LEN);

        // Set the data displacement

        pkt.setParameter(8, 0);

        // Set up word count

        pkt.setParameter(9, 0);
    }

    /**
     * Calculate the data item size from the data descriptor string.
     * 
     * @param desc java.lang.String
     * @return int
     */
    protected final static int CalculateDataItemSize(String desc)
    {

        // Scan the data descriptor string and calculate the data item size

        int len = 0;
        int pos = 0;

        while (pos < desc.length())
        {

            // Get the current data item type

            char dtype = desc.charAt(pos++);
            int dlen = 1;

            // Check if a data length has been specified

            if (pos < desc.length() && Character.isDigit(desc.charAt(pos)))
            {

                // Convert the data length string

                int numlen = 1;
                int numpos = pos + 1;
                while (numpos < desc.length() && Character.isDigit(desc.charAt(numpos++)))
                    numlen++;

                // Set the data length

                dlen = Integer.parseInt(desc.substring(pos, pos + numlen));

                // Update the descriptor string position

                pos = numpos - 1;
            }

            // Convert the current data item

            switch (dtype)
            {

            // Word (16 bit) data type

            case 'W':
                len += 2;
                break;

            // Integer (32 bit) data type

            case 'D':
                len += 4;
                break;

            // Byte data type, may be multiple bytes if 'B<n>'

            case 'B':
                len += dlen;
                break;

            // Null terminated string data type, offset into buffer only

            case 'z':
                len += 4;
                break;

            // Skip 'n' bytes in the buffer

            case '.':
                len += dlen;
                break;

            // Integer (32 bit) data type converted to a date/time value

            case 'T':
                len += 4;
                break;

            } // end switch data type

        } // end while descriptor string

        // Return the data length of each item

        return len;
    }

    /**
     * Return the offset to the data block within the SMB packet. The data block is word aligned
     * within the byte buffer area of the SMB packet. This method must be called after the parameter
     * count has been set.
     * 
     * @param prmLen Parameter block length, in bytes.
     * @return int Offset to the data block area.
     */
    public final int getDataBlockOffset(int prmLen)
    {

        // Get the position of the parameter block

        int pos = getParameterBlockOffset() + prmLen;
        if ((pos & 0x01) != 0)
            pos++;
        return pos;
    }

    /**
     * Return the data block offset.
     * 
     * @return int Offset to data block within packet.
     */
    public final int getRxDataBlock()
    {
        return getParameter(12) + RFCNetBIOSProtocol.HEADER_LEN;
    }

    /**
     * Return the received transaction data block length.
     * 
     * @return int
     */
    public final int getRxDataBlockLength()
    {
        return getParameter(11);
    }

    /**
     * Get the required transact parameter word (16 bit).
     * 
     * @param prmIdx int
     * @return int
     */
    public final int getRxParameter(int prmIdx)
    {

        // Get the parameter block offset

        int pos = getRxParameterBlock();

        // Get the required transact parameter word.

        pos += prmIdx * 2; // 16 bit words
        return DataPacker.getIntelShort(getBuffer(), pos);
    }

    /**
     * Return the position of the parameter block within the received packet.
     * 
     * @param prmblk Array to unpack the parameter block words into.
     */

    public final int getRxParameterBlock()
    {

        // Get the offset to the parameter words, add the NetBIOS header length
        // to the offset.

        return getParameter(10) + RFCNetBIOSProtocol.HEADER_LEN;
    }

    /**
     * Return the received transaction parameter block length.
     * 
     * @return int
     */
    public final int getRxParameterBlockLength()
    {
        return getParameter(9);
    }

    /**
     * Return the received transaction setup parameter count.
     * 
     * @return int
     */
    public final int getRxParameterCount()
    {
        return getParameterCount() - STD_PARAMS;
    }

    /**
     * Get the required transact parameter int value (32-bit).
     * 
     * @param prmIdx int
     * @return int
     */
    public final int getRxParameterInt(int prmIdx)
    {

        // Get the parameter block offset

        int pos = getRxParameterBlock();

        // Get the required transact parameter word.

        pos += prmIdx * 2; // 16 bit words
        return DataPacker.getIntelInt(getBuffer(), pos);
    }

    /**
     * Get the required transact parameter string.
     * 
     * @param pos Offset to the string within the parameter block.
     * @param uni Unicode if true, else ASCII
     * @return int
     */
    public final String getRxParameterString(int pos, boolean uni)
    {

        // Get the parameter block offset

        pos += getRxParameterBlock();

        // Get the transact parameter string

        byte[] buf = getBuffer();
        int len = (buf[pos++] & 0x00FF);
        return DataPacker.getString(buf, pos, len, uni);
    }

    /**
     * Get the required transact parameter string.
     * 
     * @param pos Offset to the string within the parameter block.
     * @param len Length of the string.
     * @param uni Unicode if true, else ASCII
     * @return int
     */
    public final String getRxParameterString(int pos, int len, boolean uni)
    {

        // Get the parameter block offset

        pos += getRxParameterBlock();

        // Get the transact parameter string

        byte[] buf = getBuffer();
        return DataPacker.getString(buf, pos, len, uni);
    }

    /**
     * Return the received transaction name.
     * 
     * @return java.lang.String
     */
    public final String getRxTransactName()
    {

        // Check if the transaction has a name

        if (getCommand() == PacketType.Transaction2)
            return "";

        // Unpack the transaction name string

        int pos = getByteOffset();
        return DataPacker.getString(getBuffer(), pos, getByteCount());
    }

    /**
     * Return the setup parameter count
     * 
     * @return int
     */
    public final int getSetupCount()
    {
        return getParameter(13) & 0xFF;
    }

    /**
     * Return the buffer offset to the setup parameters
     * 
     * @return int
     */
    public final int getSetupOffset()
    {
        return WORDCNT + 29; // 14 setup words + word count byte
    }

    /**
     * Return the specified transaction setup parameter.
     * 
     * @param idx Setup parameter index.
     * @return int
     */

    public final int getSetupParameter(int idx)
    {

        // Check if the setup parameter index is valid

        if (idx >= getRxParameterCount())
            throw new java.lang.ArrayIndexOutOfBoundsException();

        // Get the setup parameter

        return getParameter(idx + STD_PARAMS);
    }

    /**
     * Return the maximum return paramater byte count
     * 
     * @return int
     */
    public final int getMaximumReturnParameterCount()
    {
        return getParameter(2);
    }

    /**
     * Return the maximum return data byte count
     * 
     * @return int
     */
    public final int getMaximumReturnDataCount()
    {
        return getParameter(3);
    }

    /**
     * Return the maximum return setup count
     * 
     * @return int
     */
    public final int getMaximumReturnSetupCount()
    {
        return getParameter(4);
    }

    /**
     * Return the specified transaction setup parameter 32bit value.
     * 
     * @param idx Setup parameter index.
     * @return int
     */

    public final int getSetupParameterInt(int idx)
    {

        // Check if the setup parameter index is valid

        if (idx >= getRxParameterCount())
            throw new java.lang.ArrayIndexOutOfBoundsException();

        // Get the setup parameter

        return getParameterLong(idx + STD_PARAMS);
    }

    /**
     * Set the total parameter block length, in bytes
     * 
     * @param cnt int
     */
    public final void setTotalParameterCount(int cnt)
    {
        setParameter(0, cnt);
    }

    /**
     * Set the total data block length, in bytes
     * 
     * @param cnt int
     */
    public final void setTotalDataCount(int cnt)
    {
        setParameter(1, cnt);
    }

    /**
     * Set the parameter block count for this packet
     * 
     * @param len int
     */
    public final void setParameterBlockCount(int len)
    {
        setParameter(3, len);
    }

    /**
     * Set the parameter block offset
     * 
     * @param off int
     */
    public final void setParameterBlockOffset(int off)
    {
        setParameter(4, off != 0 ? off - RFCNetBIOSProtocol.HEADER_LEN : 0);
    }

    /**
     * Set the parameter block displacement within the total parameter block
     * 
     * @param disp int
     */
    public final void setParameterBlockDisplacement(int disp)
    {
        setParameter(5, disp);
    }

    /**
     * Set the data block count for this packet
     * 
     * @param len int
     */
    public final void setDataBlockCount(int len)
    {
        setParameter(6, len);
    }

    /**
     * Set the data block offset, from the start of the packet
     * 
     * @param off int
     */
    public final void setDataBlockOffset(int off)
    {
        setParameter(7, off != 0 ? off - RFCNetBIOSProtocol.HEADER_LEN : 0);
    }

    /**
     * Set the data block displacement within the total data block
     * 
     * @param disp int
     */
    public final void setDataBlockDisplacement(int disp)
    {
        setParameter(8, disp);
    }

    /**
     * Send one or more transaction response SMBs to the client
     * 
     * @param sess SMBSrvSession
     * @param tbuf TransactBuffer
     * @exception java.io.IOException If an I/O error occurs.
     */
    protected final void doTransactionResponse(SMBSrvSession sess, TransactBuffer tbuf) throws IOException
    {

        // Initialize the transaction response packet

        setCommand(tbuf.isType());

        // Get the individual buffers from the transact buffer

        tbuf.setEndOfBuffer();

        DataBuffer setupBuf = tbuf.getSetupBuffer();
        DataBuffer paramBuf = tbuf.getParameterBuffer();
        DataBuffer dataBuf = tbuf.getDataBuffer();

        // Set the parameter count

        if (tbuf.hasSetupBuffer())
            setParameterCount(StandardParamsResponse + setupBuf.getLengthInWords());
        else
            setParameterCount(StandardParamsResponse);

        // Clear the parameters

        for (int i = 0; i < getParameterCount(); i++)
            setParameter(i, 0);

        // Get the total parameter/data block lengths

        int totParamLen = paramBuf != null ? paramBuf.getLength() : 0;
        int totDataLen = dataBuf != null ? dataBuf.getLength() : 0;

        // Initialize the parameters

        setTotalParameterCount(totParamLen);
        setTotalDataCount(totDataLen);

        // Get the available data space within the packet

        int availBuf = getAvailableLength();
        int clientLen = getAvailableLength(sess.getClientMaximumBufferSize());
        if (availBuf > clientLen)
            availBuf = clientLen;

        // Check if the transaction parameter block and data block will fit within a single request
        // packet

        int plen = totParamLen;
        int dlen = totDataLen;

        if ((plen + dlen) > availBuf)
        {

            // Calculate the parameter/data block sizes to send in the first request packet

            if (plen > 0)
            {

                // Check if the parameter block can fit into the packet

                if (plen <= availBuf)
                {

                    // Pack all of the parameter block and fill the remaining buffer with the data
                    // block

                    if (dlen > 0)
                        dlen = availBuf - plen;
                }
                else
                {

                    // Split the parameter/data space in the packet

                    plen = availBuf / 2;
                    dlen = plen;
                }
            }
            else if (dlen > availBuf)
            {

                // Fill the packet with the first section of the data block

                dlen = availBuf;
            }
        }

        // Set the parameter/data block counts for this packet

        setParameterBlockCount(plen);
        setDataBlockCount(dlen);

        // Pack the setup bytes

        if (setupBuf != null)
            setupBuf.copyData(getBuffer(), SetupOffsetResponse);

        // Pack the parameter block

        int pos = DataPacker.wordAlign(getByteOffset());
        setPosition(pos);

        // Set the parameter block offset, from the start of the SMB packet

        setParameterBlockCount(plen);
        setParameterBlockOffset(pos);

        int packLen = -1;

        if (paramBuf != null)
        {

            // Pack the parameter block

            packLen = paramBuf.copyData(getBuffer(), pos, plen);

            // Update the buffer position for the data block

            pos = DataPacker.longwordAlign(pos + packLen);
            setPosition(pos);
        }

        // Set the data block offset

        setDataBlockCount(dlen);
        setDataBlockOffset(pos);

        // Pack the data block

        if (dataBuf != null)
        {

            // Pack the data block

            packLen = dataBuf.copyData(getBuffer(), pos, dlen);

            // Update the end of buffer position

            setPosition(pos + packLen);
        }

        // Set the byte count for the SMB packet

        setByteCount();

        // Send the start of the transaction request

        sess.sendResponseSMB(this);

        // Get the available parameter/data block buffer space for the secondary packet

        availBuf = getAvailableLength();
        if (availBuf > clientLen)
            availBuf = clientLen;

        // Loop until all parameter/data block data has been sent to the server

        TransactBuffer rxBuf = null;

        while ((paramBuf != null && paramBuf.getAvailableLength() > 0)
                || (dataBuf != null && dataBuf.getAvailableLength() > 0))
        {

            // Setup the NT transaction secondary packet to send the remaining parameter/data blocks

            setCommand(tbuf.isType());

            // Get the remaining parameter/data block lengths

            plen = paramBuf != null ? paramBuf.getAvailableLength() : 0;
            dlen = dataBuf != null ? dataBuf.getAvailableLength() : 0;

            if ((plen + dlen) > availBuf)
            {

                // Calculate the parameter/data block sizes to send in the first request packet

                if (plen > 0)
                {

                    // Check if the remaining parameter block can fit into the packet

                    if (plen <= availBuf)
                    {

                        // Pack all of the parameter block and fill the remaining buffer with the
                        // data block

                        if (dlen > 0)
                            dlen = availBuf - plen;
                    }
                    else
                    {

                        // Split the parameter/data space in the packet

                        plen = availBuf / 2;
                        dlen = plen;
                    }
                }
                else if (dlen > availBuf)
                {

                    // Fill the packet with the first section of the data block

                    dlen = availBuf;
                }
            }

            // Pack the parameter block data, if any

            resetBytePointerAlign();

            packLen = -1;
            pos = getPosition();

            if (plen > 0 && paramBuf != null)
            {

                // Set the parameter block offset, from the start of the SMB packet

                setParameterBlockOffset(pos);
                setParameterBlockCount(plen);
                setParameterBlockDisplacement(paramBuf.getDisplacement());

                // Pack the parameter block

                packLen = paramBuf.copyData(getBuffer(), pos, plen);

                // Update the buffer position for the data block

                pos = DataPacker.wordAlign(pos + packLen);
                setPosition(pos);
            }
            else
            {

                // No parameter data, clear the count/offset

                setParameterBlockCount(0);
                setParameterBlockOffset(pos);
            }

            // Pack the data block, if any

            if (dlen > 0 && dataBuf != null)
            {

                // Set the data block offset

                setDataBlockOffset(pos);
                setDataBlockCount(dlen);
                setDataBlockDisplacement(dataBuf.getDisplacement());

                // Pack the data block

                packLen = dataBuf.copyData(getBuffer(), pos, dlen);

                // Update the end of buffer position

                setPosition(pos + packLen);
            }
            else
            {

                // No data, clear the count/offset

                setDataBlockCount(0);
                setDataBlockOffset(pos);
            }

            // Set the byte count for the SMB packet to set the overall length

            setByteCount();

            // Send the transaction response packet

            sess.sendResponseSMB(this);
        }
    }
}