/*
 * Copyright (C) 2005-2006 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.DataOutputStream;

import org.alfresco.filesys.netbios.RFCNetBIOSProtocol;
import org.alfresco.filesys.smb.PacketType;
import org.alfresco.filesys.smb.SMBErrorText;
import org.alfresco.filesys.smb.SMBStatus;
import org.alfresco.filesys.util.DataPacker;
import org.alfresco.filesys.util.HexDump;

/**
 * SMB packet type class
 */
public class SMBSrvPacket
{

    // Protocol type, either NetBIOS or TCP/IP native SMB
    //
    // All protocols reserve a 4 byte header, header is not used by Win32 NetBIOS

    public static final int PROTOCOL_NETBIOS = 0;
    public static final int PROTOCOL_TCPIP = 1;
    public static final int PROTOCOL_WIN32NETBIOS = 2;

    // SMB packet offsets, assuming an RFC NetBIOS transport

    public static final int SIGNATURE       = RFCNetBIOSProtocol.HEADER_LEN;
    public static final int COMMAND         = 4 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int ERRORCODE       = 5 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int ERRORCLASS      = 5 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int ERROR           = 7 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int FLAGS           = 9 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int FLAGS2          = 10 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int PIDHIGH         = 12 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int SID             = 18 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int SEQNO           = 20 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int TID             = 24 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int PID             = 26 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int UID             = 28 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int MID             = 30 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int WORDCNT         = 32 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int ANDXCOMMAND     = 33 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int ANDXRESERVED    = 34 + RFCNetBIOSProtocol.HEADER_LEN;
    public static final int PARAMWORDS      = 33 + RFCNetBIOSProtocol.HEADER_LEN;

    // SMB packet header length for a transaction type request

    public static final int TRANS_HEADERLEN = 66 + RFCNetBIOSProtocol.HEADER_LEN;

    // Minimum receive length for a valid SMB packet

    public static final int MIN_RXLEN = 32;

    // Default buffer size to allocate for SMB packets

    public static final int DEFAULT_BUFSIZE = 4096;

    // Flag bits

    public static final int FLG_SUBDIALECT          = 0x01;
    public static final int FLG_CASELESS            = 0x08;
    public static final int FLG_CANONICAL           = 0x10;
    public static final int FLG_OPLOCK              = 0x20;
    public static final int FLG_NOTIFY              = 0x40;
    public static final int FLG_RESPONSE            = 0x80;

    // Flag2 bits

    public static final int FLG2_LONGFILENAMES      = 0x0001;
    public static final int FLG2_EXTENDEDATTRIB     = 0x0002;
    public static final int FLG2_EXTENDEDSECURITY   = 0x0800;
    public static final int FLG2_READIFEXE          = 0x2000;
    public static final int FLG2_LONGERRORCODE      = 0x4000;
    public static final int FLG2_UNICODE            = 0x8000;

    // Security mode bits

    public static final int SEC_USER = 0x0001;
    public static final int SEC_ENCRYPT = 0x0002;

    // Raw mode bits

    public static final int RAW_READ = 0x0001;
    public static final int RAW_WRITE = 0x0002;

    // No chained AndX command indicator

    public static final int NO_ANDX_CMD = 0x00FF;

    // SMB packet buffer

    private byte[] m_smbbuf;

    // Received data length (actual buffer used)

    private int m_rxLen;

    // Packet type

    private int m_pkttype;

    // Current byte area pack/unpack position

    protected int m_pos;
    protected int m_endpos;

    /**
     * Default constructor
     */

    public SMBSrvPacket()
    {
        m_smbbuf = new byte[DEFAULT_BUFSIZE];
        InitializeBuffer();
    }

    /**
     * Construct an SMB packet using the specified packet buffer.
     * 
     * @param buf SMB packet buffer.
     */

    public SMBSrvPacket(byte[] buf)
    {
        m_smbbuf = buf;
    }

    /**
     * Construct an SMB packet of the specified size.
     * 
     * @param siz Size of SMB packet buffer to allocate.
     */

    public SMBSrvPacket(int siz)
    {
        m_smbbuf = new byte[siz];
        InitializeBuffer();
    }

    /**
     * Copy constructor.
     * 
     * @param buf SMB packet buffer.
     */

    public SMBSrvPacket(SMBSrvPacket pkt)
    {

        // Create a packet buffer of the same size

        m_smbbuf = new byte[pkt.getBuffer().length];

        // Copy the data from the specified packet

        System.arraycopy(pkt.getBuffer(), 0, m_smbbuf, 0, m_smbbuf.length);
    }

    /**
     * Copy constructor.
     * 
     * @param buf SMB packet buffer.
     * @param len Length of packet to be copied
     */

    public SMBSrvPacket(SMBSrvPacket pkt, int len)
    {

        // Create a packet buffer of the same size

        m_smbbuf = new byte[pkt.getBuffer().length];

        // Copy the data from the specified packet

        System.arraycopy(pkt.getBuffer(), 0, m_smbbuf, 0, len);
    }

    /**
     * Check the SMB AndX command for the required minimum parameter count and byte count.
     * 
     * @param off Offset to the AndX command within the SMB packet.
     * @param reqWords Minimum number of parameter words expected.
     * @param reqBytes Minimum number of bytes expected.
     * @return boolean True if the packet passes the checks, else false.
     */
    public final boolean checkAndXPacketIsValid(int off, int reqWords, int reqBytes)
    {

        // Check the received parameter word count

        if (getAndXParameterCount(off) < reqWords || getAndXByteCount(off) < reqBytes)
            return false;

        // Initial SMB packet checks passed

        return true;
    }

    /**
     * Check the SMB packet for a valid SMB signature, and the required minimum parameter count and
     * byte count.
     * 
     * @param reqWords Minimum number of parameter words expected.
     * @param reqBytes Minimum number of bytes expected.
     * @return boolean True if the packet passes the checks, else false.
     */
    public final boolean checkPacketIsValid(int reqWords, int reqBytes)
    {

        // Check for the SMB signature block

        if (m_smbbuf[SIGNATURE] != (byte) 0xFF || m_smbbuf[SIGNATURE + 1] != 'S' || m_smbbuf[SIGNATURE + 2] != 'M'
                || m_smbbuf[SIGNATURE + 3] != 'B')
            return false;

        // Check the received parameter word count

        if (getParameterCount() < reqWords || getByteCount() < reqBytes)
            return false;

        // Initial SMB packet checks passed

        return true;
    }

    /**
     * Check the SMB packet has a valid SMB signature.
     * 
     * @return boolean True if the SMB signature is valid, else false.
     */
    public final boolean checkPacketSignature()
    {

        // Check for the SMB signature block

        if (m_smbbuf[SIGNATURE] == (byte) 0xFF && m_smbbuf[SIGNATURE + 1] == 'S' && m_smbbuf[SIGNATURE + 2] == 'M'
                && m_smbbuf[SIGNATURE + 3] == 'B')
            return true;

        // Invalid SMB packet format

        return false;
    }

    /**
     * Clear the data byte count
     */

    public final void clearBytes()
    {
        int offset = getByteOffset() - 2;
        DataPacker.putIntelShort((short) 0, m_smbbuf, offset);
    }

    /**
     * Dump the SMB packet to the debug stream
     */

    public final void DumpPacket()
    {
        DumpPacket(false);
    }

    /**
     * Dump the SMB packet to the debug stream
     * 
     * @param dumpAll boolean
     */

    public final void DumpPacket(boolean dumpAll)
    {

        // Dump the command type

        int pCount = getParameterCount();
        System.out.print("** SMB Packet Type: " + getPacketTypeString());

        // Check if this is a response packet

        if (isResponse())
            System.out.println(" [Response]");
        else
            System.out.println();

        // Dump flags/secondary flags

        if (true)
        {

            // Dump the packet length

            System.out.println("** SMB Packet Dump");
            System.out.println("Packet Length : " + getLength());
            System.out.println("Byte Offset: " + getByteOffset() + ", Byte Count: " + getByteCount());

            // Dump the flags

            System.out.println("Flags: " + Integer.toBinaryString(getFlags()));
            System.out.println("Flags2: " + Integer.toBinaryString(getFlags2()));

            // Dump various ids

            System.out.println("TID: " + getTreeId());
            System.out.println("PID: " + getProcessId());
            System.out.println("UID: " + getUserId());
            System.out.println("MID: " + getMultiplexId());

            // Dump parameter words/count

            System.out.println("Parameter Words: " + pCount);
            StringBuffer str = new StringBuffer();

            for (int i = 0; i < pCount; i++)
            {
                str.setLength(0);
                str.append(" P");
                str.append(Integer.toString(i + 1));
                str.append(" = ");
                str.append(Integer.toString(getParameter(i)));
                while (str.length() < 16)
                    str.append(" ");
                str.append("0x");
                str.append(Integer.toHexString(getParameter(i)));
                System.out.println(str.toString());
            }

            // Response packet fields

            if (isResponse())
            {

                // Dump the error code

                System.out.println("Error: 0x" + Integer.toHexString(getErrorCode()));
                System.out.print("Error Class: ");

                switch (getErrorClass())
                {
                case SMBStatus.Success:
                    System.out.println("SUCCESS");
                    break;
                case SMBStatus.ErrDos:
                    System.out.println("ERRDOS");
                    break;
                case SMBStatus.ErrSrv:
                    System.out.println("ERRSRV");
                    break;
                case SMBStatus.ErrHrd:
                    System.out.println("ERRHRD");
                    break;
                case SMBStatus.ErrCmd:
                    System.out.println("ERRCMD");
                    break;
                default:
                    System.out.println("0x" + Integer.toHexString(getErrorClass()));
                    break;
                }

                // Display the SMB error text

                System.out.print("Error Text: ");
                System.out.println(SMBErrorText.ErrorString(getErrorClass(), getErrorCode()));
            }
        }

        // Dump the raw data

        if (true)
        {
            System.out.println("********** Raw SMB Data Dump **********");
            if (dumpAll)
                HexDump.Dump(m_smbbuf, getLength(), 4);
            else
                HexDump.Dump(m_smbbuf, getLength() < 100 ? getLength() : 100, 4);
        }

        System.out.println();
        System.out.flush();
    }

    /**
     * Get the data byte count for the SMB AndX command.
     * 
     * @param off Offset to the AndX command.
     * @return Data byte count
     */

    public final int getAndXByteCount(int off)
    {

        // Calculate the offset of the byte count

        int pos = off + 1 + (2 * getParameterCount());
        return (int) DataPacker.getIntelShort(m_smbbuf, pos);
    }

    /**
     * Get the AndX data byte area offset within the SMB packet
     * 
     * @param off Offset to the AndX command.
     * @return Data byte offset within the SMB packet.
     */

    public final int getAndXByteOffset(int off)
    {

        // Calculate the offset of the byte buffer

        int pCnt = getAndXParameterCount(off);
        int pos = off + (2 * pCnt) + 3; // parameter words + paramter count byte + byte data length
                                        // word
        return pos;
    }

    /**
     * Get the secondary command code
     * 
     * @return Secondary command code
     */

    public final int getAndXCommand()
    {
        return (int) (m_smbbuf[ANDXCOMMAND] & 0xFF);
    }

    /**
     * Get an AndX parameter word from the SMB packet.
     * 
     * @param off Offset to the AndX command.
     * @param idx Parameter index (zero based).
     * @return Parameter word value.
     * @exception java.lang.IndexOutOfBoundsException If the parameter index is out of range.
     */

    public final int getAndXParameter(int off, int idx) throws java.lang.IndexOutOfBoundsException
    {

        // Range check the parameter index

        if (idx > getAndXParameterCount(off))
            throw new java.lang.IndexOutOfBoundsException();

        // Calculate the parameter word offset

        int pos = off + (2 * idx) + 1;
        return (int) (DataPacker.getIntelShort(m_smbbuf, pos) & 0xFFFF);
    }

    /**
     * Get an AndX parameter integer from the SMB packet.
     * 
     * @param off Offset to the AndX command.
     * @param idx Parameter index (zero based).
     * @return Parameter integer value.
     * @exception java.lang.IndexOutOfBoundsException If the parameter index is out of range.
     */

    public final int getAndXParameterLong(int off, int idx) throws java.lang.IndexOutOfBoundsException
    {

        // Range check the parameter index

        if (idx > getAndXParameterCount(off))
            throw new java.lang.IndexOutOfBoundsException();

        // Calculate the parameter word offset

        int pos = off + (2 * idx) + 1;
        return DataPacker.getIntelInt(m_smbbuf, pos);
    }

    /**
     * Get the AndX command parameter count.
     * 
     * @param off Offset to the AndX command.
     * @return Parameter word count.
     */

    public final int getAndXParameterCount(int off)
    {
        return (int) m_smbbuf[off];
    }

    /**
     * Return the byte array used for the SMB packet
     * 
     * @return Byte array used for the SMB packet.
     */

    public final byte[] getBuffer()
    {
        return m_smbbuf;
    }

    /**
     * Return the total buffer size available to the SMB request
     * 
     * @return Total SMB buffer length available.
     */

    public final int getBufferLength()
    {
        return m_smbbuf.length - RFCNetBIOSProtocol.HEADER_LEN;
    }

    /**
     * Get the data byte count for the SMB packet
     * 
     * @return Data byte count
     */

    public final int getByteCount()
    {

        // Calculate the offset of the byte count

        int pos = PARAMWORDS + (2 * getParameterCount());
        return (int) DataPacker.getIntelShort(m_smbbuf, pos);
    }

    /**
     * Get the data byte area offset within the SMB packet
     * 
     * @return Data byte offset within the SMB packet.
     */

    public final int getByteOffset()
    {

        // Calculate the offset of the byte buffer

        int pCnt = getParameterCount();
        int pos = WORDCNT + (2 * pCnt) + 3;
        return pos;
    }

    /**
     * Get the SMB command
     * 
     * @return SMB command code.
     */

    public final int getCommand()
    {
        return (int) (m_smbbuf[COMMAND] & 0xFF);
    }

    /**
     * Get the SMB error class
     * 
     * @return SMB error class.
     */

    public final int getErrorClass()
    {
        return (int) m_smbbuf[ERRORCLASS] & 0xFF;
    }

    /**
     * Get the SMB error code
     * 
     * @return SMB error code.
     */

    public final int getErrorCode()
    {
        return (int) m_smbbuf[ERROR] & 0xFF;
    }

    /**
     * Get the SMB flags value.
     * 
     * @return SMB flags value.
     */

    public final int getFlags()
    {
        return (int) m_smbbuf[FLAGS] & 0xFF;
    }

    /**
     * Get the SMB flags2 value.
     * 
     * @return SMB flags2 value.
     */
    public final int getFlags2()
    {
        return (int) DataPacker.getIntelShort(m_smbbuf, FLAGS2);
    }

    /**
     * Calculate the total used packet length.
     * 
     * @return Total used packet length.
     */
    public final int getLength()
    {

        // Get the length of the first command in the packet

        return (getByteOffset() + getByteCount()) - SIGNATURE;
    }

    /**
     * Calculate the total packet length, including header
     * 
     * @return Total packet length.
     */
    public final int getPacketLength()
    {

        // Get the length of the first command in the packet

        return getByteOffset() + getByteCount();
    }

    /**
     * Return the available buffer space for data bytes
     * 
     * @return int
     */
    public final int getAvailableLength()
    {
        return m_smbbuf.length - DataPacker.longwordAlign(getByteOffset());
    }

    /**
     * Return the available buffer space for data bytes for the specified buffer length
     * 
     * @param len int
     * @return int
     */
    public final int getAvailableLength(int len)
    {
        return len - DataPacker.longwordAlign(getByteOffset());
    }

    /**
     * Get the long SMB error code
     * 
     * @return Long SMB error code.
     */
    public final int getLongErrorCode()
    {
        return DataPacker.getIntelInt(m_smbbuf, ERRORCODE);
    }

    /**
     * Get the multiplex identifier.
     * 
     * @return Multiplex identifier.
     */
    public final int getMultiplexId()
    {
        return DataPacker.getIntelShort(m_smbbuf, MID);
    }

    /**
     * Dump the packet type
     * 
     * @return String
     */
    public final String getPacketTypeString()
    {

        String pktType = "";

        switch (getCommand())
        {
        case PacketType.Negotiate:
            pktType = "NEGOTIATE";
            break;
        case PacketType.SessionSetupAndX:
            pktType = "SESSION_SETUP";
            break;
        case PacketType.TreeConnect:
            pktType = "TREE_CONNECT";
            break;
        case PacketType.TreeConnectAndX:
            pktType = "TREE_CONNECT_ANDX";
            break;
        case PacketType.TreeDisconnect:
            pktType = "TREE_DISCONNECT";
            break;
        case PacketType.Search:
            pktType = "SEARCH";
            break;
        case PacketType.OpenFile:
            pktType = "OPEN_FILE";
            break;
        case PacketType.OpenAndX:
            pktType = "OPEN_ANDX";
            break;
        case PacketType.ReadFile:
            pktType = "READ_FILE";
            break;
        case PacketType.WriteFile:
            pktType = "WRITE_FILE";
            break;
        case PacketType.CloseFile:
            pktType = "CLOSE_FILE";
            break;
        case PacketType.CreateFile:
            pktType = "CREATE_FILE";
            break;
        case PacketType.GetFileAttributes:
            pktType = "GET_FILE_INFO";
            break;
        case PacketType.DiskInformation:
            pktType = "GET_DISK_INFO";
            break;
        case PacketType.CheckDirectory:
            pktType = "CHECK_DIRECTORY";
            break;
        case PacketType.RenameFile:
            pktType = "RENAME_FILE";
            break;
        case PacketType.DeleteDirectory:
            pktType = "DELETE_DIRECTORY";
            break;
        case PacketType.GetPrintQueue:
            pktType = "GET_PRINT_QUEUE";
            break;
        case PacketType.Transaction2:
            pktType = "TRANSACTION2";
            break;
        case PacketType.Transaction:
            pktType = "TRANSACTION";
            break;
        case PacketType.Transaction2Second:
            pktType = "TRANSACTION2_SECONDARY";
            break;
        case PacketType.TransactionSecond:
            pktType = "TRANSACTION_SECONDARY";
            break;
        case PacketType.Echo:
            pktType = "ECHO";
            break;
        case PacketType.QueryInformation2:
            pktType = "QUERY_INFORMATION_2";
            break;
        case PacketType.WriteAndClose:
            pktType = "WRITE_AND_CLOSE";
            break;
        case PacketType.SetInformation2:
            pktType = "SET_INFORMATION_2";
            break;
        case PacketType.FindClose2:
            pktType = "FIND_CLOSE2";
            break;
        case PacketType.LogoffAndX:
            pktType = "LOGOFF_ANDX";
            break;
        case PacketType.NTCancel:
            pktType = "NTCANCEL";
            break;
        case PacketType.NTCreateAndX:
            pktType = "NTCREATE_ANDX";
            break;
        case PacketType.NTTransact:
            pktType = "NTTRANSACT";
            break;
        case PacketType.NTTransactSecond:
            pktType = "NTTRANSACT_SECONDARY";
            break;
        case PacketType.ReadAndX:
            pktType = "READ_ANDX";
            break;
        default:
            pktType = "0x" + Integer.toHexString(getCommand());
            break;
        }

        // Return the packet type string

        return pktType;
    }

    /**
     * Get a parameter word from the SMB packet.
     * 
     * @param idx Parameter index (zero based).
     * @return Parameter word value.
     * @exception java.lang.IndexOutOfBoundsException If the parameter index is out of range.
     */

    public final int getParameter(int idx) throws java.lang.IndexOutOfBoundsException
    {

        // Range check the parameter index

        if (idx > getParameterCount())
            throw new java.lang.IndexOutOfBoundsException();

        // Calculate the parameter word offset

        int pos = WORDCNT + (2 * idx) + 1;
        return (int) (DataPacker.getIntelShort(m_smbbuf, pos) & 0xFFFF);
    }

    /**
     * Get the parameter count
     * 
     * @return Parameter word count.
     */

    public final int getParameterCount()
    {
        return (int) m_smbbuf[WORDCNT];
    }

    /**
     * Get the specified parameter words, as an int value.
     * 
     * @param idx Parameter index (zero based).
     * @param val Parameter value.
     */

    public final int getParameterLong(int idx)
    {
        int pos = WORDCNT + (2 * idx) + 1;
        return DataPacker.getIntelInt(m_smbbuf, pos);
    }

    /**
     * Get the process indentifier (PID)
     * 
     * @return Process identifier value.
     */
    public final int getProcessId()
    {
        return DataPacker.getIntelShort(m_smbbuf, PID);
    }

    /**
     * Get the actual received data length.
     * 
     * @return int
     */
    public final int getReceivedLength()
    {
        return m_rxLen;
    }

    /**
     * Get the session identifier (SID)
     * 
     * @return Session identifier (SID)
     */

    public final int getSID()
    {
        return DataPacker.getIntelShort(m_smbbuf, SID);
    }

    /**
     * Get the tree identifier (TID)
     * 
     * @return Tree identifier (TID)
     */

    public final int getTreeId()
    {
        return DataPacker.getIntelShort(m_smbbuf, TID);
    }

    /**
     * Get the user identifier (UID)
     * 
     * @return User identifier (UID)
     */

    public final int getUserId()
    {
        return DataPacker.getIntelShort(m_smbbuf, UID);
    }

    /**
     * Determine if there is a secondary command in this packet.
     * 
     * @return Secondary command code
     */

    public final boolean hasAndXCommand()
    {

        // Check if there is a secondary command

        int andxCmd = getAndXCommand();

        if (andxCmd != 0xFF && andxCmd != 0)
            return true;
        return false;
    }

    /**
     * Initialize the SMB packet buffer.
     */

    private final void InitializeBuffer()
    {

        // Set the packet signature

        m_smbbuf[SIGNATURE] = (byte) 0xFF;
        m_smbbuf[SIGNATURE + 1] = (byte) 'S';
        m_smbbuf[SIGNATURE + 2] = (byte) 'M';
        m_smbbuf[SIGNATURE + 3] = (byte) 'B';
    }

    /**
     * Determine if this packet is an SMB response, or command packet
     * 
     * @return true if this SMB packet is a response, else false
     */

    public final boolean isResponse()
    {
        int resp = getFlags();
        if ((resp & FLG_RESPONSE) != 0)
            return true;
        return false;
    }

    /**
     * Check if the response packet is valid, ie. type and flags
     * 
     * @return true if the SMB packet is a response packet and the response is valid, else false.
     */

    public final boolean isValidResponse()
    {

        // Check if this is a response packet, and the correct type of packet

        if (isResponse() && getCommand() == m_pkttype && this.getErrorClass() == SMBStatus.Success)
            return true;
        return false;
    }

    /**
     * Check if the packet contains ASCII or Unicode strings
     * 
     * @return boolean
     */
    public final boolean isUnicode()
    {
        return (getFlags2() & FLG2_UNICODE) != 0 ? true : false;
    }

    /**
     * Check if the packet is using caseless filenames
     * 
     * @return boolean
     */
    public final boolean isCaseless()
    {
        return (getFlags() & FLG_CASELESS) != 0 ? true : false;
    }

    /**
     * Check if long file names are being used
     * 
     * @return boolean
     */
    public final boolean isLongFileNames()
    {
        return (getFlags2() & FLG2_LONGFILENAMES) != 0 ? true : false;
    }

    /**
     * Check if long error codes are being used
     * 
     * @return boolean
     */
    public final boolean isLongErrorCode()
    {
        return (getFlags2() & FLG2_LONGERRORCODE) != 0 ? true : false;
    }

    /**
     * Pack a byte (8 bit) value into the byte area
     * 
     * @param val byte
     */
    public final void packByte(byte val)
    {
        m_smbbuf[m_pos++] = val;
    }

    /**
     * Pack a byte (8 bit) value into the byte area
     * 
     * @param val int
     */
    public final void packByte(int val)
    {
        m_smbbuf[m_pos++] = (byte) val;
    }

    /**
     * Pack the specified bytes into the byte area
     * 
     * @param byts byte[]
     * @param len int
     */
    public final void packBytes(byte[] byts, int len)
    {
        for (int i = 0; i < len; i++)
            m_smbbuf[m_pos++] = byts[i];
    }

    /**
     * Pack a string using either ASCII or Unicode into the byte area
     * 
     * @param str String
     * @param uni boolean
     */
    public final void packString(String str, boolean uni)
    {

        // Check for Unicode or ASCII

        if (uni)
        {

            // Word align the buffer position, pack the Unicode string

            m_pos = DataPacker.wordAlign(m_pos);
            DataPacker.putUnicodeString(str, m_smbbuf, m_pos, true);
            m_pos += (str.length() * 2) + 2;
        }
        else
        {

            // Pack the ASCII string

            DataPacker.putString(str, m_smbbuf, m_pos, true);
            m_pos += str.length() + 1;
        }
    }

    /**
     * Pack a string using either ASCII or Unicode into the byte area
     * 
     * @param str String
     * @param uni boolean
     * @param nul boolean
     */
    public final void packString(String str, boolean uni, boolean nul)
    {

        // Check for Unicode or ASCII

        if (uni)
        {

            // Word align the buffer position, pack the Unicode string

            m_pos = DataPacker.wordAlign(m_pos);
            DataPacker.putUnicodeString(str, m_smbbuf, m_pos, nul);
            m_pos += (str.length() * 2);
            if (nul == true)
                m_pos += 2;
        }
        else
        {

            // Pack the ASCII string

            DataPacker.putString(str, m_smbbuf, m_pos, true);
            m_pos += str.length();
            if (nul == true)
                m_pos++;
        }
    }

    /**
     * Pack a word (16 bit) value into the byte area
     * 
     * @param val int
     */
    public final void packWord(int val)
    {
        DataPacker.putIntelShort(val, m_smbbuf, m_pos);
        m_pos += 2;
    }

    /**
     * Pack an integer (32 bit) value into the byte area
     * 
     * @param val int
     */
    public final void packInt(int val)
    {
        DataPacker.putIntelInt(val, m_smbbuf, m_pos);
        m_pos += 4;
    }

    /**
     * Pack a long integer (64 bit) value into the byte area
     * 
     * @param val long
     */
    public final void packLong(long val)
    {
        DataPacker.putIntelLong(val, m_smbbuf, m_pos);
        m_pos += 8;
    }

    /**
     * Return the current byte area buffer position
     * 
     * @return int
     */
    public final int getPosition()
    {
        return m_pos;
    }

    /**
     * Unpack a byte value from the byte area
     * 
     * @return int
     */
    public final int unpackByte()
    {
        return (int) m_smbbuf[m_pos++];
    }

    /**
     * Unpack a block of bytes from the byte area
     * 
     * @param len int
     * @return byte[]
     */
    public final byte[] unpackBytes(int len)
    {
        if (len <= 0)
            return null;

        byte[] buf = new byte[len];
        System.arraycopy(m_smbbuf, m_pos, buf, 0, len);
        m_pos += len;
        return buf;
    }

    /**
     * Unpack a word (16 bit) value from the byte area
     * 
     * @return int
     */
    public final int unpackWord()
    {
        int val = DataPacker.getIntelShort(m_smbbuf, m_pos);
        m_pos += 2;
        return val;
    }

    /**
     * Unpack an integer (32 bit) value from the byte area
     * 
     * @return int
     */
    public final int unpackInt()
    {
        int val = DataPacker.getIntelInt(m_smbbuf, m_pos);
        m_pos += 4;
        return val;
    }

    /**
     * Unpack a long integer (64 bit) value from the byte area
     * 
     * @return long
     */
    public final long unpackLong()
    {
        long val = DataPacker.getIntelLong(m_smbbuf, m_pos);
        m_pos += 8;
        return val;
    }

    /**
     * Unpack a string from the byte area
     * 
     * @param uni boolean
     * @return String
     */
    public final String unpackString(boolean uni)
    {

        // Check for Unicode or ASCII

        String ret = null;

        if (uni)
        {

            // Word align the current buffer position

            m_pos = DataPacker.wordAlign(m_pos);
            ret = DataPacker.getUnicodeString(m_smbbuf, m_pos, 255);
            if (ret != null)
                m_pos += (ret.length() * 2) + 2;
        }
        else
        {

            // Unpack the ASCII string

            ret = DataPacker.getString(m_smbbuf, m_pos, 255);
            if (ret != null)
                m_pos += ret.length() + 1;
        }

        // Return the string

        return ret;
    }

    /**
     * Check if there is more data in the byte area
     * 
     * @return boolean
     */
    public final boolean hasMoreData()
    {
        if (m_pos < m_endpos)
            return true;
        return false;
    }

    /**
     * Send the SMB response packet.
     * 
     * @param out Output stream associated with the session socket.
     * @param proto Protocol type, either PROTOCOL_NETBIOS or PROTOCOL_TCPIP
     * @exception java.io.IOException If an I/O error occurs.
     */
    public final void SendResponseSMB(DataOutputStream out, int proto) throws java.io.IOException
    {

        // Use the packet length

        int siz = getLength();
        SendResponseSMB(out, proto, siz);
    }

    /**
     * Send the SMB response packet.
     * 
     * @param out Output stream associated with the session socket.
     * @param proto Protocol type, either PROTOCOL_NETBIOS or PROTOCOL_TCPIP
     * @param len Packet length
     * @exception java.io.IOException If an I/O error occurs.
     */
    public final void SendResponseSMB(DataOutputStream out, int proto, int len) throws java.io.IOException
    {

        // Make sure the response flag is set

        int flg = getFlags();
        if ((flg & FLG_RESPONSE) == 0)
            setFlags(flg + FLG_RESPONSE);

        // NetBIOS SMB protocol

        if (proto == PROTOCOL_NETBIOS)
        {

            // Fill in the NetBIOS message header, this is already allocated as
            // part of the users buffer.

            m_smbbuf[0] = (byte) RFCNetBIOSProtocol.SESSION_MESSAGE;
            m_smbbuf[1] = (byte) 0;

            DataPacker.putShort((short) len, m_smbbuf, 2);
        }
        else
        {

            // TCP/IP native SMB

            DataPacker.putInt(len, m_smbbuf, 0);
        }

        // Output the data packet

        len += RFCNetBIOSProtocol.HEADER_LEN;
        out.write(m_smbbuf, 0, len);
    }

    /**
     * Send a success SMB response packet.
     * 
     * @param out Output stream associated with the session socket.
     * @param proto Protocol type, either PROTOCOL_NETBIOS or PROTOCOL_TCPIP
     * @exception java.io.IOException If an I/O error occurs.
     */

    public final void SendSuccessSMB(DataOutputStream out, int proto) throws java.io.IOException
    {

        // Clear the parameter and byte counts

        setParameterCount(0);
        setByteCount(0);

        // Send the success response

        SendResponseSMB(out, proto);
    }

    /**
     * Set the AndX data byte count for this SMB packet.
     * 
     * @param off AndX command offset.
     * @param cnt Data byte count.
     */

    public final void setAndXByteCount(int off, int cnt)
    {
        int offset = getAndXByteOffset(off) - 2;
        DataPacker.putIntelShort(cnt, m_smbbuf, offset);
    }

    /**
     * Set the AndX data byte area in the SMB packet
     * 
     * @param off Offset to the AndX command.
     * @param byts Byte array containing the data to be copied to the SMB packet.
     */

    public final void setAndXBytes(int off, byte[] byts)
    {
        int offset = getAndXByteOffset(off) - 2;
        DataPacker.putIntelShort(byts.length, m_smbbuf, offset);

        offset += 2;

        for (int idx = 0; idx < byts.length; m_smbbuf[offset + idx] = byts[idx++])
            ;
    }

    /**
     * Set the secondary SMB command
     * 
     * @param cmd Secondary SMB command code.
     */

    public final void setAndXCommand(int cmd)
    {
        m_smbbuf[ANDXCOMMAND] = (byte) cmd;
        m_smbbuf[ANDXRESERVED] = (byte) 0;
    }

    /**
     * Set the AndX command for an AndX command block.
     * 
     * @param off Offset to the current AndX command.
     * @param cmd Secondary SMB command code.
     */

    public final void setAndXCommand(int off, int cmd)
    {
        m_smbbuf[off + 1] = (byte) cmd;
        m_smbbuf[off + 2] = (byte) 0;
    }

    /**
     * Set the specified AndX parameter word.
     * 
     * @param off Offset to the AndX command.
     * @param idx Parameter index (zero based).
     * @param val Parameter value.
     */

    public final void setAndXParameter(int off, int idx, int val)
    {
        int pos = off + (2 * idx) + 1;
        DataPacker.putIntelShort(val, m_smbbuf, pos);
    }

    /**
     * Set the AndX parameter count
     * 
     * @param off Offset to the AndX command.
     * @param cnt Parameter word count.
     */

    public final void setAndXParameterCount(int off, int cnt)
    {
        m_smbbuf[off] = (byte) cnt;
    }

    /**
     * Set the data byte count for this SMB packet
     * 
     * @param cnt Data byte count.
     */

    public final void setByteCount(int cnt)
    {
        int offset = getByteOffset() - 2;
        DataPacker.putIntelShort(cnt, m_smbbuf, offset);
    }

    /**
     * Set the data byte count for this SMB packet
     */

    public final void setByteCount()
    {
        int offset = getByteOffset() - 2;
        int len = m_pos - getByteOffset();
        DataPacker.putIntelShort(len, m_smbbuf, offset);
    }

    /**
     * Set the data byte area in the SMB packet
     * 
     * @param byts Byte array containing the data to be copied to the SMB packet.
     */

    public final void setBytes(byte[] byts)
    {
        int offset = getByteOffset() - 2;
        DataPacker.putIntelShort(byts.length, m_smbbuf, offset);

        offset += 2;

        for (int idx = 0; idx < byts.length; m_smbbuf[offset + idx] = byts[idx++])
            ;
    }

    /**
     * Set the SMB command
     * 
     * @param cmd SMB command code
     */

    public final void setCommand(int cmd)
    {
        m_pkttype = cmd;
        m_smbbuf[COMMAND] = (byte) cmd;
    }

    /**
     * Set the error class and code.
     * 
     * @param errCode int
     * @param errClass int
     */
    public final void setError(int errCode, int errClass)
    {

        // Set the error class and code

        setErrorClass(errClass);
        setErrorCode(errCode);
    }

    /**
     * Set the error class/code.
     * 
     * @param longError boolean
     * @param ntErr int
     * @param errCode int
     * @param errClass int
     */
    public final void setError(boolean longError, int ntErr, int errCode, int errClass)
    {

        // Check if the error code is a long/NT status code

        if (longError)
        {

            // Set the NT status code

            setLongErrorCode(ntErr);

            // Set the NT status code flag

            if (isLongErrorCode() == false)
                setFlags2(getFlags2() + SMBSrvPacket.FLG2_LONGERRORCODE);
        }
        else
        {

            // Set the error class and code

            setErrorClass(errClass);
            setErrorCode(errCode);
        }
    }

    /**
     * Set the SMB error class.
     * 
     * @param cl SMB error class.
     */

    public final void setErrorClass(int cl)
    {
        m_smbbuf[ERRORCLASS] = (byte) (cl & 0xFF);
    }

    /**
     * Set the SMB error code
     * 
     * @param sts SMB error code.
     */

    public final void setErrorCode(int sts)
    {
        m_smbbuf[ERROR] = (byte) (sts & 0xFF);
    }

    /**
     * Set the long SMB error code
     * 
     * @param err Long SMB error code.
     */

    public final void setLongErrorCode(int err)
    {
        DataPacker.putIntelInt(err, m_smbbuf, ERRORCODE);
    }

    /**
     * Set the SMB flags value.
     * 
     * @param flg SMB flags value.
     */

    public final void setFlags(int flg)
    {
        m_smbbuf[FLAGS] = (byte) flg;
    }

    /**
     * Set the SMB flags2 value.
     * 
     * @param flg SMB flags2 value.
     */

    public final void setFlags2(int flg)
    {
        DataPacker.putIntelShort(flg, m_smbbuf, FLAGS2);
    }

    /**
     * Set the multiplex identifier.
     * 
     * @param mid Multiplex identifier
     */

    public final void setMultiplexId(int mid)
    {
        DataPacker.putIntelShort(mid, m_smbbuf, MID);
    }

    /**
     * Set the specified parameter word.
     * 
     * @param idx Parameter index (zero based).
     * @param val Parameter value.
     */

    public final void setParameter(int idx, int val)
    {
        int pos = WORDCNT + (2 * idx) + 1;
        DataPacker.putIntelShort(val, m_smbbuf, pos);
    }

    /**
     * Set the parameter count
     * 
     * @param cnt Parameter word count.
     */

    public final void setParameterCount(int cnt)
    {

        // Set the parameter count

        m_smbbuf[WORDCNT] = (byte) cnt;

        // Reset the byte area pointer

        resetBytePointer();
    }

    /**
     * Set the specified parameter words.
     * 
     * @param idx Parameter index (zero based).
     * @param val Parameter value.
     */

    public final void setParameterLong(int idx, int val)
    {
        int pos = WORDCNT + (2 * idx) + 1;
        DataPacker.putIntelInt(val, m_smbbuf, pos);
    }

    /**
     * Set the pack/unpack position
     * 
     * @param pos int
     */
    public final void setPosition(int pos)
    {
        m_pos = pos;
    }

    /**
     * Set the process identifier value (PID).
     * 
     * @param pid Process identifier value.
     */

    public final void setProcessId(int pid)
    {
        DataPacker.putIntelShort(pid, m_smbbuf, PID);
    }

    /**
     * Set the actual received data length.
     * 
     * @param len int
     */
    public final void setReceivedLength(int len)
    {
        m_rxLen = len;
    }

    /**
     * Set the packet sequence number, for connectionless commands.
     * 
     * @param seq Sequence number.
     */

    public final void setSeqNo(int seq)
    {
        DataPacker.putIntelShort(seq, m_smbbuf, SEQNO);
    }

    /**
     * Set the session id.
     * 
     * @param sid Session id.
     */
    public final void setSID(int sid)
    {
        DataPacker.putIntelShort(sid, m_smbbuf, SID);
    }

    /**
     * Set the tree identifier (TID)
     * 
     * @param tid Tree identifier value.
     */

    public final void setTreeId(int tid)
    {
        DataPacker.putIntelShort(tid, m_smbbuf, TID);
    }

    /**
     * Set the user identifier (UID)
     * 
     * @param uid User identifier value.
     */

    public final void setUserId(int uid)
    {
        DataPacker.putIntelShort(uid, m_smbbuf, UID);
    }

    /**
     * Reset the byte pointer area for packing/unpacking data items from the packet
     */
    public final void resetBytePointer()
    {
        m_pos = getByteOffset();
        m_endpos = m_pos + getByteCount();
    }

    /**
     * Set the unpack pointer to the specified offset, for AndX processing
     * 
     * @param off int
     * @param len int
     */
    public final void setBytePointer(int off, int len)
    {
        m_pos = off;
        m_endpos = m_pos + len;
    }

    /**
     * Align the byte area pointer on an int (32bit) boundary
     */
    public final void alignBytePointer()
    {
        m_pos = DataPacker.longwordAlign(m_pos);
    }

    /**
     * Reset the byte/parameter pointer area for packing/unpacking data items from the packet, and
     * align the buffer on an int (32bit) boundary
     */
    public final void resetBytePointerAlign()
    {
        m_pos = DataPacker.longwordAlign(getByteOffset());
        m_endpos = m_pos + getByteCount();
    }

    /**
     * Skip a number of bytes in the parameter/byte area
     * 
     * @param cnt int
     */
    public final void skipBytes(int cnt)
    {
        m_pos += cnt;
    }

    /**
     * Set the data buffer
     * 
     * @param buf byte[]
     */
    public final void setBuffer(byte[] buf)
    {
        m_smbbuf = buf;
    }
}