FileDocCategorySizeDatePackage
SDPClient.javaAPI DocphoneME MR2 API (J2ME)34534Wed May 02 18:00:32 BST 2007com.sun.kvem.jsr082.bluetooth

SDPClient.java

/*
 *
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package com.sun.kvem.jsr082.bluetooth;
import javax.bluetooth.DataElement;
import javax.bluetooth.L2CAPConnection;
import javax.bluetooth.UUID;
import java.util.Enumeration;
import java.util.Hashtable;
import java.io.IOException;

/**
 * SDPClient class provides a client side of SDP connection as described in
 * Bluetooth Specification version 1.2.
 */
class SDPClient {

    /** Max retrievable service record handles. Maybe defined via property. */
    private static final int MAX_SERVICE_RECORD_COUNT = 0x0fff;

    /** Max total size of retrievable attributes. Maybe defined via property. */
    private static final int MAX_ATTRIBUTE_BYTE_COUNT = 0xffff;

    /**
     * The lowest possible value of transaction ID.
     * The number must be positive.
     */
    private static final int firstTransactionID = 0x0001;

    /** The maximum possible value of transaction ID. */
    private static final int maxTransactionID = 0xffff;

    /** Next transaction ID. */
    private static int effectiveTransactionID = firstTransactionID;

    /** Maps Bluetooth addresses to transport connections. */
    private static Hashtable transportStorage = new Hashtable();

    /** Transport connection for this client. */
    private SDPTransport transport;

    /** Bluetooth address this client is connected to. */
    private String address = null;

    /**
     * Constructs an <code>SDPClient<code> object and opens SDP connection
     * to the remote device with the specified Bluetooth address.
     *
     * @param bluetoothAddress bluetooth address of SDP server
     */
    SDPClient(String bluetoothAddress) throws IOException {
        address = bluetoothAddress;
        synchronized (transportStorage) {
            transport = (SDPTransport)transportStorage.get(address);
            if (transport == null) {
                transport = new SDPTransport(address);
                transportStorage.put(address, transport);
            }
        }
        transport.addRef();
    }

    /**
     * Retrieves next new transaction ID.
     *
     * @return new transaction ID.
     */
    static synchronized short newTransactionID() {
        int transactionID = effectiveTransactionID++;
        if (effectiveTransactionID > maxTransactionID) {
            // strictly speaking, this is not quite safe,
            // need revisit : if we have a pending
            // transaction after 64K of subsequent calls
            effectiveTransactionID = firstTransactionID;
        }
        return (short)transactionID;
    }

    /**
     * Frees transaction ID.
     *
     * @param transactionID the ID to free.
     */
    static synchronized void freeTransactionID(short transactionID) {
        // empty in this implementation
    }

    /**
     * Closes connection of this client to the specified server.
     *
     * @throws IOException if no connection is open
     */
    void close() throws IOException {
        if (transport == null) {
            throw new IOException("Connection is already closed.");
        }
        transport.release();
        transport = null;
    }

    /**
     * Initiates ServiceSearch transaction that is used to search for
     * services that have all the UUIDs specified on a server.
     */
    void serviceSearchRequest(UUID[] uuidSet, int transactionID,
        SDPResponseListener listener) throws IOException {
        try {
            transport.serviceSearchRequest(uuidSet, transactionID, listener);
        } catch (IOException ioe) {
            transport.cancelAll(SDPResponseListener.IO_ERROR);
            throw ioe;
        }
    }

    /**
     * Initiates ServiceAttribute transaction that retrieves
     * specified attribute values from a specific service record.
     */
    void serviceAttributeRequest(int serviceRecordHandle, int[] attrSet,
        int transactionID, SDPResponseListener listener) throws IOException {
        try {
            transport.serviceAttributeRequest(serviceRecordHandle, attrSet,
                transactionID, listener);
        } catch (IOException e) {
            transport.cancelAll(SDPResponseListener.IO_ERROR);
            throw e;
        }
    }

    /**
     * Initiates ServiceSearchAttribute transaction that searches for services
     * on a server by UUIDs specified and retrieves values of specified
     * parameters for service records found.
     */
    void serviceSearchAttributeRequest(int[] attrSet, UUID[] uuidSet,
        int transactionID, SDPResponseListener listener) throws IOException {
        try {
            transport.serviceSearchAttributeRequest(attrSet, uuidSet,
                transactionID, listener);
        } catch (IOException e) {
            transport.cancelAll(SDPResponseListener.IO_ERROR);
            throw e;
        }
    }

    /**
     * Cancels transaction with given ID.
     */
    boolean cancelServiceSearch(int transactionID) {
        return transport.cancelServiceSearch(transactionID);
    }

    /**
     * SDP transport connection.
     */
    private static class SDPTransport {

        /** Bluetooth address of the server. */
        private String address;

        /**
         * Reference counter keeps the number of SDP connections which
         * use this transport. When this value reaches zero, the L2CAP
         * connection is closed and the transport is removed from the global
         * SDPClient.transportStorage hashtable.
         */
        private int refCount = 0;

        /** The L2CAP (logical link) connection. */
        private L2CAPConnection connection;

        /**
         * Object that performs reading from and writing to L2CAP connection.
         */
        private DataL2CAPReaderWriter rw;

        /** Maps transaction IDs to ServiceTransaction objects. */
        private Hashtable transactions = new Hashtable();

        /** Response receiver. */
        private Receiver receiver = new Receiver();

        /** Lock for synchronizing reading from connection. */
        private Object readLock = new Object();

        /** Lock for synchronizing writing to connection. */
        private Object writeLock = new Object();

        /**
         * Helper object which serializes and restores
         * <code>DataElement</code>s.
         */
        private static DataElementSerializer des = new DataElementSerializer();

        /** ID of SDP_ErrorResponse protocol data unit. */
        private static final int SDP_ERROR_RESPONSE = 0x01;

        /** ID of SDP_ServiceSearchRequest protocol data unit. */
        private static final int SDP_SERVICE_SEARCH_REQUEST = 0x02;

        /** ID of SDP_ServiceSearchResponse protocol data unit. */
        private static final int SDP_SERVICE_SEARCH_RESPONSE = 0x03;

        /** ID of SDP_ServiceAttributeRequest protocol data unit. */
        private static final int SDP_SERVICE_ATTRIBUTE_REQUEST = 0x04;

        /** ID of SDP_ServiceAttributeResponse protocol data unit. */
        private static final int SDP_SERVICE_ATTRIBUTE_RESPONSE = 0x05;

        /** ID of SDP_ServiceSearchAttributeRequest protocol data unit. */
        private static final int SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST = 0x06;

        /** ID of SDP_ServiceSearchAttributeResponse protocol data unit. */
        private static final int SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE = 0x07;

        /** L2CAP URL starting string. */
        private static final String SDP_L2CAP_URL_BEGIN = "//";

        /** L2CAP URL trailing string. */
        private static final String SDP_L2CAP_URL_END = ":0001";

        /**
         * Constricts <code>SDPTransport</code> instance that reflects transport
         * connections to the specified server.
         *
         * @param bluetoothAddress bluetooth address of the server
         */
        private SDPTransport(String bluetoothAddress) throws IOException {
            address = bluetoothAddress;
            connection = (L2CAPConnection)SDP.getL2CAPConnection(
                SDP_L2CAP_URL_BEGIN + bluetoothAddress + SDP_L2CAP_URL_END);
            rw = new DataL2CAPReaderWriter(connection);
        }

        /**
         * Increases reference counter. This object and the underlying L2CAP
         * connection will live while the counter is positive.
         */
        private synchronized void addRef() {
            refCount++;
        }

        /**
         * Decreases reference counter. If the counter becomes equal to zero,
         * L2CAP connection is closed and the transport is removed from the
         * global SDPClient.transportStorage hashtable.
         */
        private synchronized void release() {
            refCount--;
            if (refCount == 0) {
                try {
                    connection.close();
                } catch (IOException e) {
                    // just ignore it, we're done with this object anyway
                }
                receiver.cancel();
                synchronized (transportStorage) {
                    transportStorage.remove(address);
                }
            }
        }

        /**
         * Cancels a transaction with specified ID.
         */
        boolean cancelTransaction(int transactionID, int reason) {
            // key for transactions hastable is effectiveTransactionID not
            // transactionID so we need to iterate over the hashatble looking
            // for the transactionID
            Enumeration e = transactions.elements();
            while (e.hasMoreElements()) {
                ServiceTransaction trans = (ServiceTransaction)e.nextElement();
                if (trans != null && trans.transactionID == transactionID) {
                    transactions.remove(
                        new Integer(trans.effectiveTransactionID));
                    // the transaction is removed from transactions hashtable by
                    // ServiceTransaction.end() called from
                    // ServiceTransaction.cancel(). The cancel()  reports to the
                    // listener what transaction is terminated.
                    trans.cancel(reason);
                    return true;
                }
            }
            return false;
        }

        /**
         * Cancels all current transactions. Called in case of I/O failure
         * in the underlying L2CAP connection.
         */
        synchronized void cancelAll(int reason) {
            // force actual release of resources
            refCount = 1;
            release();
            Enumeration e = transactions.elements();
            while (e.hasMoreElements()) {
                ServiceTransaction trans = (ServiceTransaction)e.nextElement();
                trans.listener.errorResponse(reason, "",
                    trans.transactionID);
            }
            transactions.clear();
        }

        /**
         * Starts ServiceSearch transaction.
         */
        void serviceSearchRequest(UUID[] uuidSet, int transactionID,
                SDPResponseListener listener) throws IOException {
            new ServiceSearchTransaction(transactionID, listener,
                    uuidSet).start();
        }

        /**
         * Starts ServiceAttribute transaction.
         */
        void serviceAttributeRequest(int serviceRecordHandle,
                int[] attrSet, int transactionID, SDPResponseListener listener)
                throws IOException {
            new ServiceAttributeTransaction(transactionID, listener,
                    serviceRecordHandle, attrSet).start();
        }

        /**
         * Starts ServiceSearchAttribute transaction.
         */
        void serviceSearchAttributeRequest(int[] attrSet, UUID[] uuidSet,
                int transactionID, SDPResponseListener listener)
                throws IOException {
            new ServiceSearchAttributeTransaction(transactionID, listener,
                    attrSet, uuidSet).start();
        }

        /**
         * Cancels a transaction with specified ID with reason
         * <code>SDPResponseListener.TERMINATED</code>.
         */
        boolean cancelServiceSearch(int transactionID) {
            return cancelTransaction(transactionID,
                    SDPResponseListener.TERMINATED);
        }

        /**
         * Gets next SDP server response, if any, and passes it to the
         * corresponding listener. If a response is received, the transaction
         * it belongs to is stopped.
         *
         * @throws IOException if an I/O error occurs
         */
        void processResponse() throws IOException {
            ServiceTransaction trans;
            synchronized (readLock) {
                byte pduID = rw.readByte();
                short transID = rw.readShort();
                short length = rw.readShort();
                // System.out.println("received pdu " + pduID +
                //         " trans " + transID);

                trans = (ServiceTransaction)transactions.get(
                        new Integer(transID));

                if (trans == null) {
                    // System.out.println("not found");
                    // transaction we are not aware of; skip this pdu
                    rw.readBytes(length);
                    return;
                }
                if (pduID == SDP_ERROR_RESPONSE) {
                    trans.error(length);
                    return;
                }
                trans.readParameters(length);
            }
            trans.process();
        }

        /**
         * SDP response receiver.
         */
        private class Receiver implements Runnable {
            /** A counter to identify if the receiver has clients. */
            private int startCounter = 0;

            /** Identifies if receiving is cancelled. */
            private boolean canceled = false;

            /**
             * Identifies if receiving thread is running (false) or not (true).
             */
            private boolean stopped = true;

            /**
             * Starts receiving thread if it is not running.
             */
            synchronized void start() {
                if (startCounter == 0) {
                    if (stopped) {
                        stopped = false;
                        new Thread(this).start();
                    }
                }
                startCounter++;
            }

            /** Decrements number of receiving thread clients. */
            synchronized void stop() {
                startCounter--;
            }

            /** Cancels receiving responses. */
            synchronized void cancel() {
                canceled = true;
            }

            /**
             * The <code>run()</code> method.
             *
             * @see java.lang.Runnable
             */
            public void run() {
                while (true) {
                    synchronized (this) {
                        if ((startCounter <= 0) || canceled) {
                            startCounter = 0;
                            stopped = true;
                            break;
                        }
                    }
                    try {
                        processResponse();
                    } catch (IOException ioe) {
                        synchronized (this) {
                            if (startCounter <= 0 || canceled) {
                                startCounter = 0;
                                stopped = true;
                                break;
                            }
                        }
                        cancelAll(SDPResponseListener.IO_ERROR);
                    }
                }
            }
        }

        /**
         * This abstract class provides base functionality for all SDP
         * transactions.
         */
        private abstract class ServiceTransaction {

            /** PDU ID (see Bluetooth Specification 1.2 Vol 3 page 131) */
            byte pduID;
            /** Transcation ID used to identify this transaction. */
            int transactionID;
            /** Effective transaction ID. */
            int effectiveTransactionID;
            /** Length of all parameters. */
            long parameterLength;
            /** Continuation state used with partial responses. */
            byte[] continuationState;
            /** Listener to report request result to. */
            SDPResponseListener listener;

            /**
             * Class constructor.
             *
             * @param pduID protocol data unit ID
             * @param transactionID transaction ID of the first request
             * @param listener listener object which will receive
             *                 completion and error notifications
             */
            ServiceTransaction(int pduID, int transactionID,
                SDPResponseListener listener) {
                this.pduID = (byte)pduID;
                this.transactionID = transactionID;
                effectiveTransactionID = newTransactionID();
                this.listener = listener;
            }

            /**
             * Updates the effective transaction ID with a new value.
             */
            final void updateTransactionID() {
                transactions.remove(new Integer(effectiveTransactionID));
                effectiveTransactionID = newTransactionID();
                transactions.put(new Integer(effectiveTransactionID), this);
            }

            /**
             * Starts this transaction.
             *
             * @throws IOException when an I/O error occurs
             */
            final void start() throws IOException {
                addRef();
                receiver.start();

                transactions.put(new Integer(effectiveTransactionID), this);

                try {
                    synchronized (writeLock) {
                        rw.writeByte(pduID);
                        rw.writeShort((short)effectiveTransactionID);
                        rw.writeShort((short)(parameterLength + 1));
                        writeParameters();
                        rw.writeByte((byte)0x00);
                        rw.flush();
                    }
                } catch (IOException e) {
                    end();
                    throw e;
                }
            }

            /**
             * Re-submits the original request with continuation state
             * data received with the incomplete response.
             *
             * @throws IOException when an I/O error occurs
             */
            final void proceed() throws IOException {
                updateTransactionID();
                synchronized (writeLock) {
                    rw.writeByte(pduID);
                    rw.writeShort((short)effectiveTransactionID);
                    rw.writeShort((short)(parameterLength + 1 +
                            continuationState.length));
                    writeParameters();
                    rw.writeByte((byte)continuationState.length);
                    rw.writeBytes(continuationState);
                    rw.flush();
                }
            }

            /**
             * Extracts continuation state parameter.
             *
             * @return true if the continuation state is present,
             *         false otherwise
             * @throws IOException when an I/O error occurs
             */
            final boolean readContinuationState() throws IOException {
                byte infoLength = rw.readByte();
                if (infoLength == 0) {
                    continuationState = null;
                    return false;
                }
                continuationState = rw.readBytes(infoLength);
                return true;
            }

            /**
             * Terminates this transaction and reports error to the listener.
             *
             * @param reason error code which will be reported
             */
            final void cancel(int reason) {
                end();
                listener.errorResponse(reason, "", transactionID);
            }

            /**
             * Ends this transaction by unregistering it in the outer class.
             */
            final void end() {
                transactions.remove(new Integer(effectiveTransactionID));
                receiver.stop();
                release();
            }

            /**
             * Reads error PDU, ends this transaction and reports listener the
             * error code retrieved.
             *
             * @param length length of PDU's parameters
             */
            final void error(int length) throws IOException {
                short errorCode = rw.readShort();
                byte[] infoBytes = rw.readBytes(length - 2);
                end();
                listener.errorResponse(errorCode, new String(infoBytes),
                        transactionID);
            }

            /**
             * Processes this transaction by either re-submitting the original
             * request if the last response was incomplete, or providing the
             * listener with the results if the transaction was completed.
             *
             * @throws IOException when an I/O error occurs
             */
            final void process() throws IOException {
                if (continuationState != null) {
                    try {
                        proceed();
                    } catch (IOException e) {
                        end();
                        throw e;
                    }
                } else {
                    end();
                    complete();
                }
            }

            /**
             * Writes transaction-specific parameters into the PDU.
             *
             * @throws IOException when an I/O error occurs
             */
            abstract void writeParameters() throws IOException;

            /**
             * Reads transaction-specific parameters from the PDU.
             *
             * @param length length of PDU's parameters
             * @throws IOException when an I/O error occurs
             */
            abstract void readParameters(int length) throws IOException;

            /**
             * Completes the transaction by calling corresponding listener's
             * method with the data retrieved.
             */
            abstract void complete();

        }

        /**
         * Provides ServiceSearch transaction functionality.
         */
        class ServiceSearchTransaction extends ServiceTransaction {

            /** ServiceSearchPattern (BT Spec 1.2 Vol 3 page 135). */
            DataElement serviceSearchPattern;
            /** Acquired service record handles. */
            int[] handleList;
            /** Current position in the handleList. */
            int offset;

            /**
             * Constructs ServiceSearchTransaction object.
             */
            ServiceSearchTransaction(int transactionID,
                    SDPResponseListener listener, UUID[] uuidSet) {
                super(SDP_SERVICE_SEARCH_REQUEST, transactionID, listener);
                serviceSearchPattern = new DataElement(DataElement.DATSEQ);
                for (int i = 0; i < uuidSet.length; i++) {
                    serviceSearchPattern.addElement(new DataElement(
                            DataElement.UUID, uuidSet[i]));
                }
                parameterLength = rw.getDataSize(serviceSearchPattern) + 2;
            }

            /**
             * Writes transaction-specific parameters into the PDU.
             *
             * @throws IOException when an I/O error occurs
             */
            void writeParameters() throws IOException {
                rw.writeDataElement(serviceSearchPattern);
                rw.writeShort((short)MAX_SERVICE_RECORD_COUNT);
            }

            /**
             * Reads transaction-specific parameters from the PDU.
             *
             * @param length length of PDU's parameters
             * @throws IOException when an I/O error occurs
             */
            void readParameters(int length) throws IOException {
                int totalServiceRecordCount = rw.readShort();
                int currentServiceRecordCount = rw.readShort();
                if (handleList == null && totalServiceRecordCount > 0) {
                    handleList = new int[totalServiceRecordCount];
                }
                for (int i = 0; i < currentServiceRecordCount; i++) {
                    handleList[offset] = rw.readInteger();
                    offset++;
                }
                readContinuationState();
            }

            /**
             * Completes the transaction by calling corresponding listener's
             * method with the data retrieved.
             */
            void complete() {
                listener.serviceSearchResponse(handleList,
                        transactionID);
            }
        }

        /**
         * Provides ServiceAttribute transaction functionality.
         */
        class ServiceAttributeTransaction extends ServiceTransaction {

            /** ServiceRecordHandle (BT Spec 1.2 Vol 3 page 138). */
            int serviceRecordHandle;
            /** AttributeIDList (BT Spec 1.2 Vol 3 page 139). */
            DataElement attributeIDList;
            /** AttributeList (BT Spec 1.2 Vol 3 page 140). */
            byte[] attributes;

            /**
             * Constructs ServiceAttributeTransaction object.
             */
            ServiceAttributeTransaction(int transactionID,
                    SDPResponseListener listener, int recordHandle,
                    int[] attrSet) {
                super(SDP_SERVICE_ATTRIBUTE_REQUEST, transactionID, listener);
                serviceRecordHandle = recordHandle;
                attributeIDList = new DataElement(DataElement.DATSEQ);
                for (int i = 0; i < attrSet.length; i++) {
                    attributeIDList.addElement(new DataElement(
                            DataElement.U_INT_2, attrSet[i]));
                }
                parameterLength = rw.getDataSize(attributeIDList) + 6;
            }

            /**
             * Writes transaction-specific parameters into the PDU.
             *
             * @throws IOException when an I/O error occurs
             */
            void writeParameters() throws IOException {
                rw.writeInteger(serviceRecordHandle);
                rw.writeShort((short)MAX_ATTRIBUTE_BYTE_COUNT);
                rw.writeDataElement(attributeIDList);
            }

            /**
             * Reads transaction-specific parameters from the PDU.
             *
             * @param length length of PDU's parameters
             * @throws IOException when an I/O error occurs
             */
            void readParameters(int length) throws IOException {
                short byteCount = rw.readShort();
                byte[] data = rw.readBytes(byteCount);
                if (attributes == null) {
                    attributes = data;
                } else {
                    byte[] temp = attributes;
                    attributes = new byte[temp.length + byteCount];
                    System.arraycopy(temp, 0, attributes, 0, temp.length);
                    System.arraycopy(data, 0, attributes, temp.length,
                            byteCount);
                }
                readContinuationState();
            }

            /**
             * Completes the transaction by calling corresponding listener's
             * method with the data retrieved.
             */
            void complete() {
                DataElement attrList;
                try {
                    attrList = des.restore(attributes);
                } catch (IOException e) {
                    listener.serviceAttributeResponse(null, null,
                            transactionID);
                    return;
                }
                int size = attrList.getSize() / 2;
                if (size == 0) {
                    listener.serviceAttributeResponse(null, null,
                            transactionID);
                    return;
                }
                Enumeration elements = (Enumeration)attrList.getValue();
                int[] attrIDs = new int[size];
                DataElement[] attrValues = new DataElement[size];
                for (int i = 0; elements.hasMoreElements(); i++) {
                    attrIDs[i] = (int)((DataElement)
                            elements.nextElement()).getLong();
                    attrValues[i] = ((DataElement)
                            elements.nextElement());
                }
                listener.serviceAttributeResponse(attrIDs, attrValues,
                        transactionID);
            }
        }

        /**
         * Provides ServiceSearchAttribute transaction functionality.
         */
        class ServiceSearchAttributeTransaction extends ServiceTransaction {

            /** ServiceSearchPattern (BT Spec 1.2 Vol 3 page 142). */
            DataElement uuidData;
            /** AttributeIDList (BT Spec 1.2 Vol 3 page 142). */
            DataElement attrData;
            /** AttributeLists (BT Spec 1.2 Vol 3 page 143). */
            byte[] attributes;

            /**
             * Constructs ServiceSearchAttributeTransaction object.
             */
            ServiceSearchAttributeTransaction(int transactionID,
                    SDPResponseListener listener, int[] attrSet,
                    UUID[] uuidSet) {
                super(SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST, transactionID,
                        listener);
                attrData = new DataElement(DataElement.DATSEQ);
                uuidData = new DataElement(DataElement.DATSEQ);
                for (int i = 0; i < attrSet.length; i++) {
                    attrData.addElement(new DataElement(DataElement.U_INT_2,
                            attrSet[i]));
                }
                for (int i = 0; i < uuidSet.length; i++) {
                    uuidData.addElement(new DataElement(DataElement.UUID,
                            uuidSet[i]));
                }
                parameterLength = rw.getDataSize(attrData) +
                        rw.getDataSize(uuidData) + 2;
            }

            /**
             * Writes transaction-specific parameters into the PDU.
             *
             * @throws IOException when an I/O error occurs
             */
            void writeParameters() throws IOException {
                rw.writeDataElement(uuidData);
                rw.writeShort((short)MAX_ATTRIBUTE_BYTE_COUNT);
                rw.writeDataElement(attrData);
            }

            /**
             * Reads transaction-specific parameters from the PDU.
             *
             * @param length length of PDU's parameters
             * @throws IOException when an I/O error occurs
             */
            void readParameters(int length) throws IOException {
                short byteCount = rw.readShort();
                byte[] data = rw.readBytes(byteCount);
                if (attributes == null) {
                    attributes = data;
                } else {
                    byte[] temp = attributes;
                    attributes = new byte[temp.length + byteCount];
                    System.arraycopy(temp, 0, attributes, 0, temp.length);
                    System.arraycopy(data, 0, attributes, temp.length,
                                     byteCount);
                }
                readContinuationState();
            }

            /**
             * Completes the transaction by calling corresponding listener's
             * method with the data retrieved.
             */
            void complete() {
                DataElement attrList;
                try {
                    Enumeration attributeLists = (Enumeration)des.
                            restore(attributes).getValue();
                    if (attributeLists.hasMoreElements()) {
                        attrList = (DataElement)attributeLists.nextElement();
                    } else {
                        listener.serviceSearchAttributeResponse(null, null,
                                transactionID);
                        return;
                    }
                } catch (IOException e) {
                    listener.serviceSearchAttributeResponse(null, null,
                            transactionID);
                    return;
                }
                int size = attrList.getSize() / 2;
                if (size == 0) {
                    listener.serviceSearchAttributeResponse(null, null,
                            transactionID);
                    return;
                }
                Enumeration elements = (Enumeration)attrList.getValue();
                int[] attrIDs = new int[size];
                DataElement[] attrValues = new DataElement[size];
                for (int i = 0; elements.hasMoreElements(); i++) {
                    attrIDs[i] = (int)((DataElement)
                            elements.nextElement()).getLong();
                    attrValues[i] = ((DataElement)
                            elements.nextElement());
                }
                listener.serviceSearchAttributeResponse(attrIDs, attrValues,
                        transactionID);
            }
        }

    }
}