/**
 *  BlueCove - Java library for Bluetooth
 *  Copyright (C) 2006-2007 Eric Wagner
 *  Copyright (C) 2006-2008 Vlad Skarzhevskyy
 *
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 *  @version $Id: BluetoothStackOSX.java 2557 2008-12-11 08:19:30Z skarzhevskyy $
 */
package com.intel.bluetooth;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.bluetooth.BluetoothConnectionException;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.ServiceRegistrationException;
import javax.bluetooth.UUID;

class BluetoothStackOSX implements BluetoothStack {

    public static final boolean debug = false;

    private static BluetoothStackOSX singleInstance = null;

    // TODO what is the real number for Attributes retrivable ?
    private final static int ATTR_RETRIEVABLE_MAX = 256;

    private Vector deviceDiscoveryListeners = new Vector/* <DiscoveryListener> */();

    private Hashtable deviceDiscoveryListenerReportedDevices = new Hashtable();

    private int receive_mtu_max = -1;

    private int localDeviceSupportedSoftwareVersion;

    private long lastDeviceDiscoveryTime = 0;

    private int localDeviceServiceClasses = 0;

    private Thread localDeviceServiceClassMaintainer = null;

    private static final int BLUETOOTH_SOFTWARE_VERSION_2_0_0 = 20000;

    BluetoothStackOSX() {

    }

    // ---------------------- Library initialization

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#isNativeCodeLoaded()
     */
    public native boolean isNativeCodeLoaded();

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#requireNativeLibraries()
     */
    public LibraryInformation[] requireNativeLibraries() {
        return LibraryInformation.library(BlueCoveImpl.NATIVE_LIB_OSX);
    }

    public String getStackID() {
        return BlueCoveImpl.STACK_OSX;
    }

    public String toString() {
        return getStackID();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#getFeatureSet()
     */
    public int getFeatureSet() {
        if (localDeviceSupportedSoftwareVersion >= BLUETOOTH_SOFTWARE_VERSION_2_0_0) {
            return FEATURE_L2CAP | FEATURE_SERVICE_ATTRIBUTES | FEATURE_SET_DEVICE_SERVICE_CLASSES;
        } else {
            return FEATURE_L2CAP | FEATURE_SERVICE_ATTRIBUTES;
        }
    }

    public native int getLibraryVersion();

    public native int detectBluetoothStack();

    private native boolean initializeImpl();

    public void initialize() throws BluetoothStateException {
        if (singleInstance != null) {
            throw new BluetoothStateException("Only one instance of " + getStackID() + " stack supported");
        }
        localDeviceSupportedSoftwareVersion = getLocalDeviceSupportedSoftwareVersion();
        DebugLog.debug("localDeviceSupportedSoftwareVersion", localDeviceSupportedSoftwareVersion);
        if (!initializeImpl()) {
            throw new BluetoothStateException("OS X BluetoothStack not found");
        }
        singleInstance = this;
    }

    public void destroy() {
        if (localDeviceSupportedSoftwareVersion >= BLUETOOTH_SOFTWARE_VERSION_2_0_0) {
            setLocalDeviceServiceClassesImpl(0);
        }
        singleInstance = null;
    }

    public native void enableNativeDebug(Class nativeDebugCallback, boolean on);

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.intel.bluetooth.BluetoothStack#isCurrentThreadInterruptedCallback()
     */
    public boolean isCurrentThreadInterruptedCallback() {
        return UtilsJavaSE.isCurrentThreadInterrupted();
    }

    // ---------------------- LocalDevice

    public native String getLocalDeviceBluetoothAddress() throws BluetoothStateException;

    public native String getLocalDeviceName();

    private native int getDeviceClassImpl();

    public DeviceClass getLocalDeviceClass() {
        return new DeviceClass(getDeviceClassImpl());
    }

    private native boolean setLocalDeviceServiceClassesImpl(int classOfDevice);

    private class MaintainDeviceServiceClassesThread extends Thread {

        MaintainDeviceServiceClassesThread() {
            super("MaintainDeviceServiceClassesThread");
        }

        public void run() {
            boolean updated = true;
            while (true) {
                try {
                    int delay = 1000 * 120;
                    if (!updated) {
                        delay = 1000;
                    }
                    Thread.sleep(delay);
                } catch (InterruptedException e) {
                    break;
                }
                if (localDeviceServiceClasses != 0) {
                    updated = setLocalDeviceServiceClassesImpl(localDeviceServiceClasses);
                } else if (!updated) {
                    updated = true;
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#setLocalDeviceServiceClasses(int)
     */
    public synchronized void setLocalDeviceServiceClasses(int classOfDevice) {
        if (localDeviceSupportedSoftwareVersion < BLUETOOTH_SOFTWARE_VERSION_2_0_0) {
            return;
        }
        if (classOfDevice != localDeviceServiceClasses) {
            setLocalDeviceServiceClassesImpl(classOfDevice);
        }
        localDeviceServiceClasses = classOfDevice;
        if ((classOfDevice != 0) && (localDeviceServiceClassMaintainer == null)) {
            localDeviceServiceClassMaintainer = new MaintainDeviceServiceClassesThread();
            UtilsJavaSE.threadSetDaemon(localDeviceServiceClassMaintainer);
            localDeviceServiceClassMaintainer.start();
        }
    }

    public native boolean isLocalDevicePowerOn();

    private native boolean isLocalDeviceFeatureSwitchRoles();

    private native boolean isLocalDeviceFeatureParkMode();

    private native int getLocalDeviceL2CAPMTUMaximum();

    private native int getLocalDeviceSupportedSoftwareVersion();

    private native String getLocalDeviceSoftwareVersionInfo();

    private native int getLocalDeviceManufacturer();

    private native String getLocalDeviceVersion();

    public String getLocalDeviceProperty(String property) {
        if (BluetoothConsts.PROPERTY_BLUETOOTH_CONNECTED_DEVICES_MAX.equals(property)) {
            return isLocalDeviceFeatureParkMode() ? "255" : "7";
        }
        if (BluetoothConsts.PROPERTY_BLUETOOTH_SD_TRANS_MAX.equals(property)) {
            return "7";
        }
        if (BluetoothConsts.PROPERTY_BLUETOOTH_CONNECTED_INQUIRY_SCAN.equals(property)) {
            return BlueCoveImpl.TRUE;
        }
        if (BluetoothConsts.PROPERTY_BLUETOOTH_CONNECTED_PAGE_SCAN.equals(property)) {
            return BlueCoveImpl.TRUE;
        }
        if (BluetoothConsts.PROPERTY_BLUETOOTH_CONNECTED_INQUIRY.equals(property)) {
            return BlueCoveImpl.TRUE;
        }
        if (BluetoothConsts.PROPERTY_BLUETOOTH_CONNECTED_PAGE.equals(property)) {
            return BlueCoveImpl.TRUE;
        }

        if (BluetoothConsts.PROPERTY_BLUETOOTH_SD_ATTR_RETRIEVABLE_MAX.equals(property)) {
            return String.valueOf(ATTR_RETRIEVABLE_MAX);
        }
        if (BluetoothConsts.PROPERTY_BLUETOOTH_MASTER_SWITCH.equals(property)) {
            // return isLocalDeviceFeatureSwitchRoles() ? TRUE : FALSE;
            return BlueCoveImpl.FALSE;
        }
        if (BluetoothConsts.PROPERTY_BLUETOOTH_L2CAP_RECEIVEMTU_MAX.equals(property)) {
            return String.valueOf(receiveMTUMAX());
        }

        if ("bluecove.radio.version".equals(property)) {
            return getLocalDeviceVersion();
        }
        if ("bluecove.radio.manufacturer".equals(property)) {
            return String.valueOf(getLocalDeviceManufacturer());
        }
        if ("bluecove.stack.version".equals(property)) {
            return getLocalDeviceSoftwareVersionInfo();
        }

        return null;
    }

    private int receiveMTUMAX() {
        if (receive_mtu_max < 0) {
            receive_mtu_max = getLocalDeviceL2CAPMTUMaximum();
        }
        return receive_mtu_max;
    }

    private native boolean getLocalDeviceDiscoverableImpl();

    public int getLocalDeviceDiscoverable() {
        if (getLocalDeviceDiscoverableImpl()) {
            return DiscoveryAgent.GIAC;
        } else {
            return DiscoveryAgent.NOT_DISCOVERABLE;
        }
    }

    /**
     * There are no functions to set OS X stack Discoverable status.
     */
    public boolean setLocalDeviceDiscoverable(int mode) throws BluetoothStateException {
        if (getLocalDeviceDiscoverable() == mode) {
            return true;
        }
        return false;
    }

    private void verifyDeviceReady() throws BluetoothStateException {
        if (!isLocalDevicePowerOn()) {
            throw new BluetoothStateException("Bluetooth Device is not ready");
        }
    }

    public RemoteDevice[] retrieveDevices(int option) {
        return null;
    }

    public Boolean isRemoteDeviceTrusted(long address) {
        return null;
    }

    public Boolean isRemoteDeviceAuthenticated(long address) {
        return null;
    }

    // ---------------------- Remote Device authentication

    public boolean authenticateRemoteDevice(long address) throws IOException {
        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#authenticateRemoteDevice(long,
     * java.lang.String)
     */
    public boolean authenticateRemoteDevice(long address, String passkey) throws IOException {
        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.intel.bluetooth.BluetoothStack#removeAuthenticationWithRemoteDevice
     * (long)
     */
    public void removeAuthenticationWithRemoteDevice(long address) throws IOException {
        throw new NotSupportedIOException(getStackID());
    }

    // ---------------------- Device Inquiry

    public native String getRemoteDeviceFriendlyName(long address) throws IOException;

    private native int runDeviceInquiryImpl(DeviceInquiryRunnable inquiryRunnable, DeviceInquiryThread startedNotify, int accessCode, int duration, DiscoveryListener listener)
            throws BluetoothStateException;

    public boolean startInquiry(int accessCode, DiscoveryListener listener) throws BluetoothStateException {
        // Inquiries are throttled if they are called too quickly in succession.
        // e.g. JSR-82 TCK
        long sinceDiscoveryLast = System.currentTimeMillis() - lastDeviceDiscoveryTime;
        long acceptableInterval = 7 * 1000;
        if (sinceDiscoveryLast < acceptableInterval) {
            try {
                Thread.sleep(acceptableInterval - sinceDiscoveryLast);
            } catch (InterruptedException e) {
                throw new BluetoothStateException();
            }
        }

        deviceDiscoveryListeners.addElement(listener);
        deviceDiscoveryListenerReportedDevices.put(listener, new Vector());
        DeviceInquiryRunnable inquiryRunnable = new DeviceInquiryRunnable() {

            public int runDeviceInquiry(DeviceInquiryThread startedNotify, int accessCode, DiscoveryListener listener) throws BluetoothStateException {
                try {
                    return runDeviceInquiryImpl(this, startedNotify, accessCode, DeviceInquiryThread.getConfigDeviceInquiryDuration(), listener);
                } finally {
                    lastDeviceDiscoveryTime = System.currentTimeMillis();
                    deviceDiscoveryListeners.removeElement(listener);
                    deviceDiscoveryListenerReportedDevices.remove(listener);
                }
            }

            public void deviceDiscoveredCallback(DiscoveryListener listener, long deviceAddr, int deviceClass, String deviceName, boolean paired) {
                if (!deviceDiscoveryListeners.contains(listener)) {
                    return;
                }
                // Update name if name retrieved
                RemoteDevice remoteDevice = RemoteDeviceHelper.createRemoteDevice(BluetoothStackOSX.this, deviceAddr, deviceName, paired);
                Vector reported = (Vector) deviceDiscoveryListenerReportedDevices.get(listener);
                if (reported == null || (reported.contains(remoteDevice))) {
                    return;
                }
                reported.addElement(remoteDevice);
                DeviceClass cod = new DeviceClass(deviceClass);
                DebugLog.debug("deviceDiscoveredCallback address", remoteDevice.getBluetoothAddress());
                DebugLog.debug("deviceDiscoveredCallback deviceClass", cod);
                listener.deviceDiscovered(remoteDevice, cod);
            }
        };
        return DeviceInquiryThread.startInquiry(this, inquiryRunnable, accessCode, listener);
    }

    private native boolean deviceInquiryCancelImpl();

    public boolean cancelInquiry(DiscoveryListener listener) {
        // no further deviceDiscovered() events will occur for this inquiry
        if (!deviceDiscoveryListeners.removeElement(listener)) {
            return false;
        }
        return deviceInquiryCancelImpl();
    }

    // ---------------------- Service search

    private native int runSearchServicesImpl(long address, int transID) throws BluetoothStateException, SearchServicesException;

    public int searchServices(int[] attrSet, UUID[] uuidSet, RemoteDevice device, DiscoveryListener listener) throws BluetoothStateException {

        SearchServicesRunnable searchRunnable = new SearchServicesRunnable() {

            public int runSearchServices(SearchServicesThread sst, int[] attrSet, UUID[] uuidSet, RemoteDevice device, DiscoveryListener listener)
                    throws BluetoothStateException {
                // OS X will retrieve all Records, we filter here in Java
                sst.searchServicesStartedCallback();
                int recordsSize;
                try {
                    recordsSize = runSearchServicesImpl(RemoteDeviceHelper.getAddress(device), sst.getTransID());
                } catch (SearchServicesDeviceNotReachableException e) {
                    return DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE;
                } catch (SearchServicesTerminatedException e) {
                    return DiscoveryListener.SERVICE_SEARCH_TERMINATED;
                } catch (SearchServicesException e) {
                    return DiscoveryListener.SERVICE_SEARCH_ERROR;
                }
                if (sst.isTerminated()) {
                    return DiscoveryListener.SERVICE_SEARCH_TERMINATED;
                }
                if (recordsSize == 0) {
                    return DiscoveryListener.SERVICE_SEARCH_NO_RECORDS;
                }
                Vector records = new Vector();
                int[] uuidFilerAttrIDs = new int[] { BluetoothConsts.ServiceClassIDList, BluetoothConsts.ProtocolDescriptorList };
                int[] requiredAttrIDs = new int[] { BluetoothConsts.ServiceRecordHandle, BluetoothConsts.ServiceRecordState, BluetoothConsts.ServiceID };
                nextRecord: for (int i = 0; i < recordsSize; i++) {
                    ServiceRecordImpl sr = new ServiceRecordImpl(BluetoothStackOSX.this, device, i);
                    try {
                        sr.populateRecord(uuidFilerAttrIDs);
                        // Apply JSR-82 filter, all UUID should be present
                        for (int u = 0; u < uuidSet.length; u++) {
                            if (!((sr.hasServiceClassUUID(uuidSet[u]) || sr.hasProtocolClassUUID(uuidSet[u])))) {
                                if (debug) {
                                    DebugLog.debug("filtered ServiceRecord (" + i + ")", sr);
                                }
                                continue nextRecord;
                            }
                        }
                        if (debug) {
                            DebugLog.debug("accepted ServiceRecord (" + i + ")", sr);
                        }
                        records.addElement(sr);
                        sr.populateRecord(requiredAttrIDs);
                        if (attrSet != null) {
                            sr.populateRecord(attrSet);
                        }
                        DebugLog.debug("ServiceRecord (" + i + ")", sr);
                    } catch (Exception e) {
                        DebugLog.debug("populateRecord error", e);
                    }

                    if (sst.isTerminated()) {
                        DebugLog.debug("SERVICE_SEARCH_TERMINATED " + sst.getTransID());
                        return DiscoveryListener.SERVICE_SEARCH_TERMINATED;
                    }
                }
                if (records.size() != 0) {
                    DebugLog.debug("SERVICE_SEARCH_COMPLETED " + sst.getTransID());
                    ServiceRecord[] fileteredRecords = (ServiceRecord[]) Utils.vector2toArray(records, new ServiceRecord[records.size()]);
                    listener.servicesDiscovered(sst.getTransID(), fileteredRecords);
                    return DiscoveryListener.SERVICE_SEARCH_COMPLETED;
                } else {
                    return DiscoveryListener.SERVICE_SEARCH_NO_RECORDS;
                }
            }

        };
        return SearchServicesThread.startSearchServices(this, searchRunnable, attrSet, uuidSet, device, listener);
    }

    private native void cancelServiceSearchImpl(int transID);

    public boolean cancelServiceSearch(int transID) {
        SearchServicesThread sst = SearchServicesThread.getServiceSearchThread(transID);
        if (sst != null) {
            synchronized (this) {
                if (!sst.isTerminated()) {
                    sst.setTerminated();
                    cancelServiceSearchImpl(transID);
                    return true;
                }
            }
        }
        return false;
    }

    private native byte[] getServiceAttributeImpl(long address, long serviceRecordIndex, int attrID);

    public boolean populateServicesRecordAttributeValues(ServiceRecordImpl serviceRecord, int[] attrIDs) throws IOException {
        if (attrIDs.length > ATTR_RETRIEVABLE_MAX) {
            throw new IllegalArgumentException();
        }
        boolean anyRetrived = false;
        long address = RemoteDeviceHelper.getAddress(serviceRecord.getHostDevice());
        for (int i = 0; i < attrIDs.length; i++) {
            int id = attrIDs[i];
            try {
                byte[] blob = getServiceAttributeImpl(address, serviceRecord.getHandle(), id);
                if (blob != null) {
                    DataElement element = (new SDPInputStream(new ByteArrayInputStream(blob))).readElement();
                    serviceRecord.populateAttributeValue(id, element);
                    anyRetrived = true;
                    if (debug) {
                        DebugLog.debug("data for attribute " + id + " Ox" + Integer.toHexString(id) + " " + element);
                    }
                } else {
                    if (debug) {
                        DebugLog.debug("no data for attribute " + id + " Ox" + Integer.toHexString(id));
                    }
                }
            } catch (Throwable e) {
                if (debug) {
                    DebugLog.error("error populate attribute " + id + " Ox" + Integer.toHexString(id), e);
                }
            }
        }
        return anyRetrived;
    }

    // ---------------------- Client RFCOMM connections

    private native long connectionRfOpenClientConnectionImpl(long address, int channel, boolean authenticate, boolean encrypt, int timeout) throws IOException;

    public long connectionRfOpenClientConnection(BluetoothConnectionParams params) throws IOException {
        if (params.encrypt) {
            throw new BluetoothConnectionException(BluetoothConnectionException.SECURITY_BLOCK, "encrypt mode not supported");
        }
        Object lock = RemoteDeviceHelper.createRemoteDevice(this, params.address, null, false);
        synchronized (lock) {
            return connectionRfOpenClientConnectionImpl(params.address, params.channel, params.authenticate, params.encrypt, params.timeout);
        }
    }

    public native void connectionRfCloseClientConnection(long handle) throws IOException;

    public native int rfGetSecurityOpt(long handle, int expected) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2Encrypt(long,long,boolean)
     */
    public boolean rfEncrypt(long address, long handle, boolean on) throws IOException {
        return false;
    }

    // ---------------------- Server RFCOMM connections

    private native long rfServerCreateImpl(byte[] uuidValue, boolean obexSrv, String name, boolean authenticate, boolean encrypt) throws IOException;

    private native int rfServerGetChannelID(long handle) throws IOException;

    private native void rfServerCloseImpl(long handle) throws IOException;

    public long rfServerOpen(BluetoothConnectionNotifierParams params, ServiceRecordImpl serviceRecord) throws IOException {
        verifyDeviceReady();
        if (params.encrypt) {
            throw new BluetoothConnectionException(BluetoothConnectionException.SECURITY_BLOCK, "encrypt mode not supported");
        }
        byte[] uuidValue = Utils.UUIDToByteArray(params.uuid);
        long handle = rfServerCreateImpl(uuidValue, params.obex, params.name, params.authenticate, params.encrypt);
        boolean success = false;
        try {
            int channel = rfServerGetChannelID(handle);
            serviceRecord.populateRFCOMMAttributes(handle, channel, params.uuid, params.name, params.obex);
            success = true;
        } finally {
            if (!success) {
                rfServerCloseImpl(handle);
            }
        }
        return handle;
    }

    public void rfServerClose(long handle, ServiceRecordImpl serviceRecord) throws IOException {
        rfServerCloseImpl(handle);
    }

    public void rfServerUpdateServiceRecord(long handle, ServiceRecordImpl serviceRecord, boolean acceptAndOpen) throws ServiceRegistrationException {
        sdpServiceUpdateServiceRecord(handle, 'R', serviceRecord);
    }

    public native long rfServerAcceptAndOpenRfServerConnection(long handle) throws IOException;

    public void connectionRfCloseServerConnection(long handle) throws IOException {
        connectionRfCloseClientConnection(handle);
    }

    private native void sdpServiceUpdateServiceRecordPublish(long handle, char handleType) throws ServiceRegistrationException;

    private native void sdpServiceAddAttribute(long handle, char handleType, int attrID, int attrType, long numberValue, byte[] arrayValue)
            throws ServiceRegistrationException;

    private native void sdpServiceSequenceAttributeStart(long handle, char handleType, int attrID, int attrType) throws ServiceRegistrationException;

    private native void sdpServiceSequenceAttributeEnd(long handle, char handleType, int attrID) throws ServiceRegistrationException;

    private void sdpServiceAddAttribute(long handle, char handleType, int attrID, DataElement element) throws ServiceRegistrationException {
        int type = element.getDataType();
        switch (type) {
        case DataElement.NULL:
            sdpServiceAddAttribute(handle, handleType, attrID, type, 0, null);
            break;
        case DataElement.BOOL:
            sdpServiceAddAttribute(handle, handleType, attrID, type, element.getBoolean() ? 1 : 0, null);
            break;
        case DataElement.U_INT_1:
        case DataElement.INT_1:
        case DataElement.U_INT_2:
        case DataElement.INT_2:
        case DataElement.U_INT_4:
        case DataElement.INT_4:
        case DataElement.INT_8:
            sdpServiceAddAttribute(handle, handleType, attrID, type, element.getLong(), null);
            break;
        case DataElement.U_INT_8:
        case DataElement.U_INT_16:
        case DataElement.INT_16:
            sdpServiceAddAttribute(handle, handleType, attrID, type, 0, (byte[]) element.getValue());
            break;
        case DataElement.UUID:
            sdpServiceAddAttribute(handle, handleType, attrID, type, 0, Utils.UUIDToByteArray((UUID) element.getValue()));
            break;
        case DataElement.STRING:
            byte[] bs = Utils.getUTF8Bytes((String) element.getValue());
            sdpServiceAddAttribute(handle, handleType, attrID, type, 0, bs);
            break;
        case DataElement.URL:
            byte[] bu = Utils.getASCIIBytes((String) element.getValue());
            sdpServiceAddAttribute(handle, handleType, attrID, type, 0, bu);
            break;
        case DataElement.DATSEQ:
        case DataElement.DATALT:
            sdpServiceSequenceAttributeStart(handle, handleType, attrID, type);
            for (Enumeration e = (Enumeration) element.getValue(); e.hasMoreElements();) {
                DataElement child = (DataElement) e.nextElement();
                sdpServiceAddAttribute(handle, handleType, -1, child);
            }
            sdpServiceSequenceAttributeEnd(handle, handleType, attrID);
            break;
        default:
            throw new IllegalArgumentException();
        }
    }

    private void sdpServiceUpdateServiceRecord(long handle, char handleType, ServiceRecordImpl serviceRecord) throws ServiceRegistrationException {
        int[] ids = serviceRecord.getAttributeIDs();
        if ((ids == null) || (ids.length == 0)) {
            return;
        }
        for (int i = 0; i < ids.length; i++) {
            int attrID = ids[i];
            switch (attrID) {
            case BluetoothConsts.ServiceRecordHandle:
                continue;
            case BluetoothConsts.ProtocolDescriptorList:
            case BluetoothConsts.AttributeIDServiceName:
                continue;
            }
            sdpServiceAddAttribute(handle, handleType, attrID, serviceRecord.getAttributeValue(attrID));
        }
        sdpServiceUpdateServiceRecordPublish(handle, handleType);
    }

    // ---------------------- Shared Client and Server RFCOMM connections

    public void connectionRfFlush(long handle) throws IOException {
        // TODO Auto-generated method stub
    }

    public native int connectionRfRead(long handle) throws IOException;

    public native int connectionRfRead(long handle, byte[] b, int off, int len) throws IOException;

    public native int connectionRfReadAvailable(long handle) throws IOException;

    public void connectionRfWrite(long handle, int b) throws IOException {
        byte buf[] = new byte[1];
        buf[0] = (byte) (b & 0xFF);
        connectionRfWrite(handle, buf, 0, 1);
    }

    public native void connectionRfWrite(long handle, byte[] b, int off, int len) throws IOException;

    public native long getConnectionRfRemoteAddress(long handle) throws IOException;

    // ---------------------- Client and Server L2CAP connections

    private void validateMTU(int receiveMTU, int transmitMTU) {
        if (receiveMTU > receiveMTUMAX()) {
            throw new IllegalArgumentException("invalid ReceiveMTU value " + receiveMTU);
        }
    }

    private native long l2OpenClientConnectionImpl(long address, int channel, boolean authenticate, boolean encrypt, int receiveMTU, int transmitMTU,
            int timeout) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.intel.bluetooth.BluetoothStack#l2OpenClientConnection(com.intel.bluetooth
     * .BluetoothConnectionParams, int, int)
     */
    public long l2OpenClientConnection(BluetoothConnectionParams params, int receiveMTU, int transmitMTU) throws IOException {
        validateMTU(receiveMTU, transmitMTU);
        if (params.encrypt) {
            throw new BluetoothConnectionException(BluetoothConnectionException.SECURITY_BLOCK, "encrypt mode not supported");
        }
        Object lock = RemoteDeviceHelper.createRemoteDevice(this, params.address, null, false);
        synchronized (lock) {
            return l2OpenClientConnectionImpl(params.address, params.channel, params.authenticate, params.encrypt, receiveMTU, transmitMTU, params.timeout);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2CloseClientConnection(long)
     */
    public native void l2CloseClientConnection(long handle) throws IOException;

    private native long l2ServerOpenImpl(byte[] uuidValue, boolean authenticate, boolean encrypt, String name, int receiveMTU, int transmitMTU, int assignPsm)
            throws IOException;

    public native int l2ServerPSM(long handle) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @seecom.intel.bluetooth.BluetoothStack#l2ServerOpen(com.intel.bluetooth.
     * BluetoothConnectionNotifierParams, int, int,
     * com.intel.bluetooth.ServiceRecordImpl)
     */
    public long l2ServerOpen(BluetoothConnectionNotifierParams params, int receiveMTU, int transmitMTU, ServiceRecordImpl serviceRecord) throws IOException {
        verifyDeviceReady();
        validateMTU(receiveMTU, transmitMTU);
        if (params.encrypt) {
            throw new BluetoothConnectionException(BluetoothConnectionException.SECURITY_BLOCK, "encrypt mode not supported");
        }
        byte[] uuidValue = Utils.UUIDToByteArray(params.uuid);
        long handle = l2ServerOpenImpl(uuidValue, params.authenticate, params.encrypt, params.name, receiveMTU, transmitMTU, params.bluecove_ext_psm);

        int channel = l2ServerPSM(handle);

        int serviceRecordHandle = (int) handle;

        serviceRecord.populateL2CAPAttributes(serviceRecordHandle, channel, params.uuid, params.name);

        return handle;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2ServerUpdateServiceRecord(long,
     * com.intel.bluetooth.ServiceRecordImpl, boolean)
     */
    public void l2ServerUpdateServiceRecord(long handle, ServiceRecordImpl serviceRecord, boolean acceptAndOpen) throws ServiceRegistrationException {
        sdpServiceUpdateServiceRecord(handle, 'L', serviceRecord);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.intel.bluetooth.BluetoothStack#l2ServerAcceptAndOpenServerConnection
     * (long)
     */
    public native long l2ServerAcceptAndOpenServerConnection(long handle) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2CloseServerConnection(long)
     */
    public void l2CloseServerConnection(long handle) throws IOException {
        l2CloseClientConnection(handle);
    }

    private native void l2ServerCloseImpl(long handle) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2ServerClose(long,
     * com.intel.bluetooth.ServiceRecordImpl)
     */
    public void l2ServerClose(long handle, ServiceRecordImpl serviceRecord) throws IOException {
        l2ServerCloseImpl(handle);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2GetSecurityOpt(long, int)
     */
    public native int l2GetSecurityOpt(long handle, int expected) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2Ready(long)
     */
    public native boolean l2Ready(long handle) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2receive(long, byte[])
     */
    public native int l2Receive(long handle, byte[] inBuf) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2send(long, byte[])
     */
    public native void l2Send(long handle, byte[] data) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2GetReceiveMTU(long)
     */
    public native int l2GetReceiveMTU(long handle) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2GetTransmitMTU(long)
     */
    public native int l2GetTransmitMTU(long handle) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2RemoteAddress(long)
     */
    public native long l2RemoteAddress(long handle) throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.intel.bluetooth.BluetoothStack#l2Encrypt(long,long,boolean)
     */
    public boolean l2Encrypt(long address, long handle, boolean on) throws IOException {
        return false;
    }
}
