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

SDPServer.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.L2CAPConnectionNotifier;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import java.util.Enumeration;
import java.util.Vector;
import java.io.IOException;

/**
 * Represents Servive Discovery Protocol Server.
 */
public class SDPServer {

    /** Set to false in RR version - then the javac skip the code. */
    private static final boolean DEBUG = false;

    /**
     *  notifier - a connection used by the server
     * to wait for a client connection
     */
    private L2CAPConnectionNotifier conNotifier = null;

    /** Collects connections to the server. */
    private Vector connections;

    /** Requests acceptor, it is Runnable and works in its own thread. */
    private Acceptor acceptor;

    /** Shows if acceptor thread is running. */
    private boolean acceptorStarted = false;

    /** SDP UUID. */
    private static final int SDP_UUID = 0x0001;

    /** SDP_ErrorResponse PDU ID. */
    private static final int SDP_ERROR_RESPONSE = 0x01;

    /** SDP_ServiceSearchRequest PDU ID. */
    private static final int SDP_SERVICE_SEARCH_REQUEST = 0x02;

    /** SDP_ServiceSearchResponse PDU ID. */
    private static final int SDP_SERVICE_SEARCH_RESPONSE = 0x03;

    /** SDP_ServiceAttributeRequest PDU ID. */
    private static final int SDP_SERVICE_ATTRIBUTE_REQUEST = 0x04;

    /** SDP_ServiceAttributeResponse PDU ID. */
    private static final int SDP_SERVICE_ATTRIBUTE_RESPONSE = 0x05;

    /** SDP_ServiceSearchAttributeResponse PDU ID. */
    private static final int SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST = 0x06;

    /** SDP_ServiceSearchAttributeResponse PDU ID. */
    private static final int SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE = 0x07;

    /** Error code for SDP_ErrorResponse: Invalid/unsupported SDP version. */
    private static final int SDP_INVALID_VERSION = 0x01;

    /** Error code for SDP_ErrorResponse: Invalid Service Record Handle. */
    private static final int SDP_INVALID_SR_HANDLE = 0x02;

    /** Error code for SDP_ErrorResponse: Invalid request syntax. */
    private static final int SDP_INVALID_SYNTAX = 0x03;

    /*
     * Note: The following constants aren't used by emulator
     * but can be used in real device
     * private static final int SDP_INVALID_PDU_SIZE = 0x04;
     * private static final int SDP_INVALID_CONTINUATION_STATE = 0x05;
     * private static final int SDP_INSUFFICIENT_RESOURCES = 0x06;
     */

    /**
     * Constructs <code>SDPServer</code> instance.
     */
    public SDPServer() {
        connections = new Vector();
        acceptor = new Acceptor();
    }

    /**
     * Starts this SDP server if not started.
     */
    public synchronized void start() {
        if (acceptorStarted) {
            return;
        }

        requestPSM();
        UUID sdpUUID = new UUID(SDP_UUID);
        try {
            conNotifier = (L2CAPConnectionNotifier)
                    SDP.getL2CAPConnection("//localhost:"
                    + SDP.UUID + ";name=SDPServer");
        } catch (IOException ioe) {
            // ignore
        }

        if (conNotifier != null) {
            acceptorStarted = true;
            (new Thread(acceptor)).start();
        }
    }

    /**
     * Notifies native emulation code that next PSM requested
     * is for SDP server.
     */
    private native void requestPSM();

    /**
     * Stops this server closing all the connections to it.
     */
    synchronized void stop() {
        try {
            conNotifier.close();
        } catch (IOException ioe) {
            // ignore
        }

        for (int i = connections.size(); i >= 0; i--) {
            L2CAPConnection con = (L2CAPConnection) connections.elementAt(i);

            try {
                synchronized (con) {
                    con.close();
                }
            } catch (IOException ioe) {
                // ignore
            }
        }
        connections.removeAllElements();
    }

    /**
     * Retrieves next PDU from a connection and processes it.
     *
     * @param rw <code>DataL2CAPReaderWriter</code> instance that represents
     *        desired connection and provides RW utilities.
     *
     * @throws IOException if a processing error occured.
     */
    private void processRequest(DataL2CAPReaderWriter rw)
            throws IOException {
        byte requestType = rw.readByte();
        short transactionID = rw.readShort();
        short length = rw.readShort();

        if (requestType == SDP_SERVICE_SEARCH_REQUEST) {
            processServiceSearch(rw, transactionID);
        } else if (requestType == SDP_SERVICE_ATTRIBUTE_REQUEST) {
            processServiceAttribute(rw, transactionID);
        } else if (requestType == SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST) {
            processServiceSearchAttribute(rw, transactionID);
        } else {
            writeErrorResponce(rw, transactionID, SDP_INVALID_SYNTAX,
                    "Invalid Type of Request");
            System.err.println("WARNING: Unsupported SDP request");
        }
    }

    /**
     * Retrieves SDP_ServiceSearchRequest parameters from given connection,
     * processes the requests and sends a respond.
     *
     * @param rw <code>DataL2CAPReaderWriter</code> instance that represents
     *        desired connection and provides RW utilities.
     * @param transactionID ID of transaction the request is recieved in.
     * @throws IOException if a processing error occured.
     */
    private void processServiceSearch(DataL2CAPReaderWriter rw,
            short transactionID) throws IOException {
        DataElement uuidSet = rw.readDataElement();
        short maximimSRCount = rw.readShort();

        // IMPL_NOTE: ContinuationState isn't used, but should on real device
        int continuationState = rw.readByte();

        if (continuationState != 0) {
            writeErrorResponce(rw, transactionID, SDP_INVALID_VERSION,
                    "Current implementation don't support continuation state");
            return;
        }

        Vector currentHandles = new Vector();
        int[] handles = SDDB.getInstance().getHandles();
        for (int i = 0; i < handles.length; i++) {
            ServiceRecord sr = SDDB.getInstance().getServiceRecord(handles[i]);

            if (findUUIDs((ServiceRecordImpl) sr, uuidSet)) {
                currentHandles.addElement(new Integer(handles[i]));
            }
        }
        rw.writeByte((byte) SDP_SERVICE_SEARCH_RESPONSE);
        rw.writeShort(transactionID);
        rw.writeShort((short) (currentHandles.size() + 5));

        // Total and current counts are the same for all the results are
        // sent in one response PDU
        rw.writeShort((short) currentHandles.size());
        rw.writeShort((short) currentHandles.size());

        for (int i = 0; i < currentHandles.size(); i++) {
            if (i > maximimSRCount) {
                break;
            }
            int h = ((Integer) currentHandles.elementAt(i)).intValue();
            rw.writeInteger(h);
        }

        // IMPL_NOTE: ContinuationState isn't used, but should on real device
        rw.writeByte((byte) 0x00);
        rw.flush();
    }

    /**
     * Retrieves SDP_ServiceAttribute parameters from given connection,
     * processes the requests and sends a respond.
     *
     * @param rw <code>DataL2CAPReaderWriter</code> instance that represents
     *        desired connection and provides RW utilities.
     * @param transactionID ID of transaction the request is recieved in.
     *
     * @throws IOException if a processing error occured.
     */
    private void processServiceAttribute(DataL2CAPReaderWriter rw,
            short transactionID) throws IOException {
        int handle = rw.readInteger();

        // IMPL_NOTE: Add checking for real device
        short maximimSize = rw.readShort();
        DataElement attrSet = rw.readDataElement();

        // IMPL_NOTE: ContinuationState isn't used, but should on real device
        int continuationState = rw.readByte();

        if (continuationState != 0) {
            writeErrorResponce(rw, transactionID, SDP_INVALID_VERSION,
                    "Current implementation don't support continuation state");
            return;
        }

        ServiceRecord sr = SDDB.getInstance().getServiceRecord(handle);

        // if service record not found process it
        if (sr == null) {
            writeErrorResponce(rw, transactionID, SDP_INVALID_SR_HANDLE,
                    "Servicce Record with specified ID not found");
            return;
        }

        DataElement attrIDValues = new DataElement(DataElement.DATSEQ);
        Enumeration e = (Enumeration) attrSet.getValue();

        while (e.hasMoreElements()) {
            DataElement attrID = (DataElement) e.nextElement();
            int attr = (int) attrID.getLong();
            DataElement attrValue = sr.getAttributeValue(attr);

            if (attrValue != null) {
                attrIDValues.addElement(attrID);
                attrIDValues.addElement(attrValue);
            }
        }
        int length = (int) rw.getDataSize(attrIDValues);
        rw.writeByte((byte) SDP_SERVICE_ATTRIBUTE_RESPONSE);
        rw.writeShort(transactionID);
        rw.writeShort((short) (length + 3));
        rw.writeShort((short) length);
        rw.writeDataElement(attrIDValues);

        // IMPL_NOTE: ContinuationState isn't used, but should on real device
        rw.writeByte((byte) 0x00);
        rw.flush();
    }

    /**
     * Retrieves SDP_ServiceSearchAttribute parameters from given connection,
     * processes the requests and sends a respond.
     *
     * @param rw <code>DataL2CAPReaderWriter</code> instance that represents
     *        desired connection and provides RW utilities.
     * @param transactionID ID of transaction the request is recieved in.
     *
     * @throws IOException if a processing error occured.
     */
    private void processServiceSearchAttribute(DataL2CAPReaderWriter rw,
            short transactionID) throws IOException {
        DataElement uuidSet = rw.readDataElement();

        // IMPL_NOTE: Add checking for real device
        short maximimSize = rw.readShort();
        DataElement attrSet = rw.readDataElement();

        // IMPL_NOTE: ContinuationState isn't used, but should on real device
        int continuationState = rw.readByte();

        if (continuationState != 0) {
            writeErrorResponce(rw, transactionID, SDP_INVALID_VERSION,
                    "Current implementation don't support continuation state");
            return;
        }

        int[] handles = SDDB.getInstance().getHandles();
        int handle = -1;

        for (int i = 0; i < handles.length; i++) {
            ServiceRecord sr = SDDB.getInstance().getServiceRecord(handles[i]);

            if (findUUIDs((ServiceRecordImpl) sr, uuidSet)) {
                handle = handles[i];
            }
        }
        ServiceRecord sr = SDDB.getInstance().getServiceRecord(handle);

        // if service record not found process it
        if (sr == null) {
            writeErrorResponce(rw, transactionID, SDP_INVALID_SR_HANDLE,
                    "Servicce Record with specified ID not found");
            return;
        }

        DataElement attributeLists = new DataElement(DataElement.DATSEQ);
        DataElement attrIDValues = new DataElement(DataElement.DATSEQ);
        Enumeration e = (Enumeration) attrSet.getValue();

        while (e.hasMoreElements()) {
            DataElement attrID = (DataElement) e.nextElement();
            int attr = (int) attrID.getLong();
            DataElement attrValue = sr.getAttributeValue(attr);

            if (attrValue != null) {
                attrIDValues.addElement(attrID);
                attrIDValues.addElement(attrValue);
            }
        }

        attributeLists.addElement(attrIDValues);
        int length = (int) rw.getDataSize(attributeLists);

        rw.writeByte((byte) SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE);
        rw.writeShort(transactionID);
        rw.writeShort((short) (length + 3));
        rw.writeShort((short) length);
        rw.writeDataElement(attributeLists);

        // IMPL_NOTE: ContinuationState isn't used, but should on real device
        rw.writeByte((byte) 0x00);
        rw.flush();
    }

    /**
     * Sends SDP_ErrorResponse PDU.
     *
     * @param rw <code>DataL2CAPReaderWriter</code> instance that represents
     *        connection to send to and provides RW utilities.
     * @param transactionID ID of transaction to send response within.
     * @param errorCode error code.
     * @param info error details.
     *
     * @throws IOException if a processing error occured.
     */
    private void writeErrorResponce(DataL2CAPReaderWriter rw,
            short transactionID, int errorCode, String info)
            throws IOException  {
        byte[] infoBytes = info.getBytes();
        int length = infoBytes.length + 2;
        rw.writeByte((byte) SDP_ERROR_RESPONSE);
        rw.writeShort(transactionID);
        rw.writeShort((short) length);
        rw.writeShort((short) errorCode);
        rw.writeBytes(infoBytes);
        rw.flush();
    }

    /**
     * Checks if the specified service record contains all of the
     * UUID with values from specified 'uuids' list.
     *
     * Note, that according to spec clarification from spec lead
     * such a search is done over all of the service attribues
     * (not just ServiceClassIDList and ProtocolDescriptorList).
     *
     * @param sr service record to check.
     * @param uuids list of UUIDs to check record for.
     *
     * @return true if the record given contains all the UUIDs, false
     * otherwise.
     */
    private boolean findUUIDs(ServiceRecordImpl sr, DataElement uuids) {
        int[] attrs = sr.getAttributeIDs();
        Enumeration e = (Enumeration) uuids.getValue();

    NEXT_UUID:
        while (e.hasMoreElements()) {
            UUID uuid = (UUID) ((DataElement) e.nextElement()).getValue();
            for (int i = 0; i < attrs.length; i++) {
                DataElement attr = sr.getAttributeValue(attrs[i]);

                if (containsUUID(attr, uuid)) {
                    continue NEXT_UUID;
                }
            }
            return false;
        }
        return true;
    }

    /**
     * Checks if specified 'attr' contains (or equals) to specified 'uuid.
     *
     * @param attr data element to check if it equals to desired UUID or
     *        contains it.
     * @param uuid the UUID to compare with.
     *
     * @return true if data element given represents either specified UUID
     * or a sequence that contains it, false otherwise.
     */
    private boolean containsUUID(DataElement attr, UUID uuid) {
        if (attr.getDataType() == DataElement.UUID) {
            return uuid.equals((UUID) attr.getValue());
        }

        if (attr.getDataType() != DataElement.DATSEQ) {
            return false;
        }
        Enumeration e = (Enumeration) attr.getValue();

        while (e.hasMoreElements()) {
            DataElement de = (DataElement) e.nextElement();

            if (containsUUID(de, uuid)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Requests acceptor, it is Runnable to be launched in a separate thread.
     */
    class Acceptor implements Runnable {
        /**
         * The <code>run()</code> method, see interface
         * {@link java.lang.Runnable Runnable}.
         */
        public void run() {
            while (true) {
                try {
                    L2CAPConnection con = conNotifier.acceptAndOpen();
                    DataL2CAPReaderWriter rw = new DataL2CAPReaderWriter(con);

                    synchronized (SDPServer.this) {
                        connections.addElement(con);
                    }
                    Sender sender = new Sender(con, rw);
                    new Thread(sender).start();
                } catch (IOException ioe) {
                    if (DEBUG) {
                        ioe.printStackTrace();
                    }
                    // connection was closed
                    break;
                }
            }

            synchronized (SDPServer.this) {
                acceptorStarted = false;
                try {
                    conNotifier.close();
                } catch (IOException e) {
                    // no matter
                }
                conNotifier = null;
            }
        }

        /** Cleans possible blockings and extra references up. */
        private void finalize() {
            if (conNotifier != null) {
                acceptorStarted = false;
                try {
                    conNotifier.close();
                } catch (IOException e) {
                    // no matter
                }
                conNotifier = null;
            }
        }
    }

    /**
     * Responses sender, it is Runnable to be launched in a separate thread.
     */
    class Sender implements Runnable {
        /** Connection to send to. */
        L2CAPConnection connection;

        /** Utility object to write to L2CAP connection. */
        DataL2CAPReaderWriter readerWriter;

        /**
         * Constructs sender to send that sends to given connection using
         * specified writer.
         *
         * @param connection L2CAP connection to send to.
         * @param readerWriter read/write utility object to use for sending.
         */
        Sender(L2CAPConnection connection,
                DataL2CAPReaderWriter readerWriter) {
            this.connection = connection;
            this.readerWriter = readerWriter;
        }

        /**
         * The <code>run()</code> method, see interface
         * {@link java.lang.Runnable Runnable}.
         */
        public void run() {
            while (true) {
                try {

                    /*
                     * If this call returns sucessfully, the
                     * next request will be processed (in the
                     * next 'while' loop). IOException means
                     * the remote end-point is closed - no
                     * more requests to be processed.
                     */
                    processRequest(readerWriter);
                } catch (IOException ioe) {

                    // this means the process is done
                    if (DEBUG) {
                        ioe.printStackTrace();
                    }

                    synchronized (SDPServer.this) {
                        connections.removeElement(connection);
                    }

                    try {
                        connection.close();
                    } catch (IOException ioe1) {}
                    break;
                }

                // process is done successfully - go to next one
            }
        }
    }
}