/* * Copyright 1990-2009 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.jsr082.bluetooth; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import javax.bluetooth.BluetoothStateException; import javax.bluetooth.DataElement; import javax.bluetooth.LocalDevice; import javax.bluetooth.RemoteDevice; import javax.bluetooth.ServiceRecord; import javax.bluetooth.UUID; import com.sun.jsr082.bluetooth.SDPClient; /* * Service record implementation. */ public final class ServiceRecordImpl implements ServiceRecord { /* Maxumum quantity of attributes in one request */ static final int RETRIEVABLE_MAX; /* * Maximum number of concurrent service searches that can * exist at any one time. */ static final int TRANS_MAX; /* Remote device service provided by. */ private RemoteDevice remoteDevice = null; /* Service notifier. */ private BluetoothNotifier notifier = null; /* Attribues of the record. */ private Hashtable attributesTable = null; /* Bit scale that keeps service classes. */ private int serviceClasses = 0; /* Mask to identify attribute IDs out of range. */ private static final int MASK_OVERFLOW = 0xffff0000; /* Mask of incorrect class bits. */ private static final int MASK_INCORRECT_CLASS = 0xff003fff; /* ServiceRecordHandle attribute ID. */ public static final int SERVICE_RECORD_HANDLE = 0x0000; /* ProtocolDescriptorList attribute ID. */ public static final int PROTOCOL_DESCRIPTOR_LIST = 0x0004; /* Service class attribute id. */ public static final int SERVICE_CLASS_ATTR_ID = 0x0001; /* Name attribute id. */ public static final int NAME_ATTR_ID = 0x0100; /* Protocol type. */ private int protocol = BluetoothUrl.UNKNOWN; /* Bluetooth address of device service record came from. */ private String btaddr = null; /* PSM or channel id. */ private int port = -1; /* Record handle */ private int recHandle = 0; /* SDPClient from where this ServiceRecord is created */ public SDPClient sdpClient = null; static { int retrievableMax = 5; // default value try { retrievableMax = Integer.parseInt(LocalDevice.getProperty( "bluetooth.sd.attr.retrievable.max")); } catch (NumberFormatException e) { System.err.println("Internal error: ServiceRecordImpl: " + "improper retrievable.max value"); } RETRIEVABLE_MAX = retrievableMax; int transMax = 10; // default value try { transMax = Integer.parseInt(LocalDevice.getProperty( "bluetooth.sd.trans.max")); } catch (NumberFormatException e) { System.err.println("Internal error: ServiceRecordImpl: " + "improper trans.max value"); } TRANS_MAX = transMax; } /* * Creates service records on client device. * * @param device server device * @param attrIDs attributes IDs * @param attrValues attributes values */ public ServiceRecordImpl(RemoteDevice device, int[] attrIDs, DataElement[] attrValues) { init(attrIDs, attrValues); remoteDevice = device; } /* * Creates service records for the given notifier. * * @param notifier notifier to be associated with this service record * @param attrIDs attributes IDs * @param attrValues attributes values */ public ServiceRecordImpl(BluetoothNotifier notifier, int[] attrIDs, DataElement[] attrValues) { init(attrIDs, attrValues); this.notifier = notifier; } /* * Creates a copy of this record. The copy recieves new instances of * attributes values which are of types <code>DataElement.DATSEQ</code> * or <code>DataElement.DATALT</code> (the only data element types that * can be modified after creation). * * @return new instance, a copy of this one. */ public synchronized ServiceRecordImpl copy() { int count = attributesTable.size(); int[] attrIDs = new int[count]; DataElement[] attrValues = new DataElement[count]; Enumeration ids = attributesTable.keys(); Enumeration values = attributesTable.elements(); for (int i = 0; i < count; i++) { attrIDs[i] = ((Integer)ids.nextElement()).intValue(); // no nedd to copy elements here; service record constructor // performs the copying attrValues[i] = (DataElement)values.nextElement(); } ServiceRecordImpl servRec = new ServiceRecordImpl(notifier, attrIDs, attrValues); servRec.serviceClasses = serviceClasses; return servRec; } /* * Returns service record handle. * * @return service record handle, or 0 if the record is not in SDDB. */ public int getHandle() { DataElement handle = getAttributeValue(SERVICE_RECORD_HANDLE); return handle != null ? (int)handle.getLong() : 0; } /* * Sets service record handle. * * @param handle new service record handle value */ public void setHandle(int handle) { Integer attrID = new Integer(SERVICE_RECORD_HANDLE); attributesTable.remove(attrID); attributesTable.put(attrID, new DataElement( DataElement.U_INT_4, handle)); recHandle = handle; } /* * Returns notifier that has created this record. * @return corresponding notifier. */ public BluetoothNotifier getNotifier() { return notifier; } /* * Creates attributes table and fills it up by values given. * @param attrIDs attributes IDs * @param attrValues attributes values */ private void init(int[] attrIDs, DataElement[] attrValues) { attributesTable = new Hashtable(attrIDs.length + 1); attrsInit(attrIDs, attrValues); } /* * Fills up attributes table by values given. * @param attrIDs attributes IDs * @param attrValues attributes values */ private void attrsInit(int[] attrIDs, DataElement[] attrValues) { for (int i = 0; i < attrIDs.length; i++) { attributesTable.put(new Integer(attrIDs[i]), dataElementCopy(attrValues[i])); } } /* * Creates a copy of DataElement if it's necessary. * @param original data element to be copied if its type * allows value modification * @return copy of data element */ private DataElement dataElementCopy(DataElement original) { if ((original.getDataType() == DataElement.DATSEQ) || (original.getDataType() == DataElement.DATALT)) { DataElement copy = new DataElement(original.getDataType()); Enumeration elements = (Enumeration) original.getValue(); while (elements.hasMoreElements()) { copy.addElement(dataElementCopy((DataElement) elements.nextElement())); } return copy; } else { return original; } } // JAVADOC COMMENT ELIDED public DataElement getAttributeValue(int attrID) { if ((attrID & MASK_OVERFLOW) != 0) { throw new IllegalArgumentException( "attrID isn't a 16-bit unsigned integer"); } DataElement attrValue = (DataElement) attributesTable.get(new Integer(attrID)); if (attrValue == null) { return null; } else { return dataElementCopy(attrValue); } } // JAVADOC COMMENT ELIDED public RemoteDevice getHostDevice() { return remoteDevice; } // JAVADOC COMMENT ELIDED public synchronized int[] getAttributeIDs() { int[] attrIDs = new int[attributesTable.size()]; Enumeration e = attributesTable.keys(); for (int i = 0; i < attrIDs.length; i++) { attrIDs[i] = ((Integer) e.nextElement()).intValue(); } return attrIDs; } // JAVADOC COMMENT ELIDED public synchronized boolean populateRecord(int[] attrIDs) throws IOException { Hashtable dupChecker = new Hashtable(); Object checkObj = new Object(); if (remoteDevice == null) { throw new RuntimeException("local ServiceRecord"); } if (attrIDs.length == 0) { throw new IllegalArgumentException("attrIDs size is zero"); } if (attrIDs.length > RETRIEVABLE_MAX) { throw new IllegalArgumentException( "attrIDs size exceeds retrievable.max"); } for (int i = 0; i < attrIDs.length; i++) { if ((attrIDs[i] & MASK_OVERFLOW) != 0) { throw new IllegalArgumentException("attrID does not represent " + "a 16-bit unsigned integer"); } // check attribute ID duplication if (dupChecker.put(new Integer(attrIDs[i]), checkObj) != null) { throw new IllegalArgumentException( "duplicated attribute ID"); } } // obtains transaction ID for request short transactionID = SDPClientTransactionBase.newTransactionID(); // SDP connection and listener. They are initialized in try blok. SDPClient sdp = null; SRSDPListener listener = null; try { // prepare data for request DataElement handleEl = (DataElement) attributesTable.get( new Integer(SERVICE_RECORD_HANDLE)); int handle = (int) handleEl.getLong(); // create and prepare SDP listner listener = new SRSDPListener(); // create SDP connection and .. if (sdpClient == null) { sdp = ServiceDiscovererFactory.getServiceDiscoverer(). getSDPClient(remoteDevice.getBluetoothAddress()); } else { sdp = sdpClient; } // ... and make request sdp.serviceAttributeRequest(handle, attrIDs, transactionID, listener); synchronized (listener) { if ((listener.ioExcpt == null) && (listener.attrValues == null)) { try { listener.wait(); } catch (InterruptedException ie) { // ignore (breake waiting) } } } } finally { // Closes SDP connection and frees transaction ID in any case SDPClientTransactionBase.freeTransactionID(transactionID); // if connection was created try to close it if (sdp != null) { try { sdp.close(); } catch (IOException ioe) { // ignore } } } if (listener.ioExcpt != null) { throw listener.ioExcpt; } else if (listener.attrValues == null) { return false; } else if (listener.attrValues.length == 0) { return false; } else { attrsInit(listener.attrIDs, listener.attrValues); return true; } } // JAVADOC COMMENT ELIDED public synchronized String getConnectionURL(int requiredSecurity, boolean mustBeMaster) { // protocol, btaddr, port retrieveUrlCommonParams(); if (protocol == BluetoothUrl.UNKNOWN) { return null; } BluetoothUrl url = BluetoothUrl.createClientUrl( protocol, btaddr, port); if (mustBeMaster) { url.master = true; } else { url.master = false; } switch (requiredSecurity) { case NOAUTHENTICATE_NOENCRYPT: break; case AUTHENTICATE_ENCRYPT: url.encrypt = true; case AUTHENTICATE_NOENCRYPT: url.authenticate = true; break; default: throw new IllegalArgumentException("unsupported security type: " + requiredSecurity); } return url.toString(); } /* * Retrieves service protocol, device address and port (PSM or channel) * from service record attributes. Results are set to * <code>protocol</code>, <code>btaddr</code> and <code>port</code> * variables correspondingly. */ private void retrieveUrlCommonParams() { if (protocol != BluetoothUrl.UNKNOWN) { // already retrieved return; } if (remoteDevice != null) { btaddr = remoteDevice.getBluetoothAddress(); } else { try { btaddr = LocalDevice.getLocalDevice().getBluetoothAddress(); } catch (BluetoothStateException bse) { throw new IllegalArgumentException("cannot generate url"); } } /* * There are three protocols supported - * they are obex or rfcomm or l2cap. So, if obex is * found in ProtocolDescriptorList, the protocol is btgoep, * if RFCOMM is found (and no obex) - the btspp, otherwise * the protocol is btl2cap. */ DataElement protocolList = getAttributeValue(PROTOCOL_DESCRIPTOR_LIST); if (protocolList == null) { return; } Enumeration val = (Enumeration) protocolList.getValue(); int type = -1; // 0 = l2cap, 1 = spp, 2 = obex final UUID L2CAP_UUID = new UUID(0x0100); final UUID RFCOMM_UUID = new UUID(0x0003); final UUID OBEX_UUID = new UUID(0x0008); // go through all of the protocols in the protocols list while (val.hasMoreElements()) { DataElement protoDE = (DataElement) val.nextElement(); // application adds a garbage in protocolList - ignore if (protoDE.getDataType() != DataElement.DATSEQ) { continue; } Enumeration protoEnum = (Enumeration) protoDE.getValue(); int tmpPort = -1; int tmpType = -1; // look on protocol details while (protoEnum.hasMoreElements()) { DataElement de = (DataElement) protoEnum.nextElement(); // may be PSM or channel id if (de.getDataType() == DataElement.U_INT_1 || de.getDataType() == DataElement.U_INT_2) { tmpPort = (int) de.getLong(); } else if (de.getDataType() == DataElement.UUID) { UUID protoUUID = (UUID) de.getValue(); if (protoUUID.equals(L2CAP_UUID)) { tmpType = 0; } else if (protoUUID.equals(RFCOMM_UUID)) { tmpType = 1; } else if (protoUUID.equals(OBEX_UUID)) { tmpType = 2; } } } /* * ok, new protocol has been parsed - let's check if it * is over the previous one or not. * * Note, that OBEX protocol may appear before the RFCOMM * one - in this case the port (channel id) is not set - * need to check this case separately. */ if (tmpType > type) { type = tmpType; // no "port" for obex type (obex = 2) if (tmpType != 2) { port = tmpPort; } } else if (tmpType == 1) { port = tmpPort; } } switch (type) { case 0: protocol = BluetoothUrl.L2CAP; break; case 1: protocol = BluetoothUrl.RFCOMM; break; case 2: protocol = BluetoothUrl.OBEX; break; default: throw new IllegalArgumentException("wrong protocol list"); } } /* * Retrieve service classes bits provided by corresponing service * at local device. * * @return an integer that keeps the service classes bits */ public int getDeviceServiceClasses() { if (remoteDevice != null) { throw new RuntimeException( "This ServiceRecord was created by a call to " + "DiscoveryAgent.searchServices()"); } // it's necessary to improve these code return serviceClasses; } // JAVADOC COMMENT ELIDED public synchronized void setDeviceServiceClasses(int classes) { // checks that it's service record from remote device if (remoteDevice != null) { throw new RuntimeException("This ServiceRecord was created" + " by a call to DiscoveryAgent.searchServices()"); } // checks correction of set classbits if ((classes & MASK_INCORRECT_CLASS) != 0) { throw new IllegalArgumentException("attempt to set incorrect bits"); } serviceClasses = classes; } // JAVADOC COMMENT ELIDED public synchronized boolean setAttributeValue( int attrID, DataElement attrValue) { if ((attrID & MASK_OVERFLOW) != 0) { throw new IllegalArgumentException( "attrID does not represent a 16-bit unsigned integer"); } if (attrID == SERVICE_RECORD_HANDLE) { throw new IllegalArgumentException( "attrID is the value of ServiceRecordHandle (0x0000)"); } if (remoteDevice != null) { throw new RuntimeException( "can't update ServiceRecord of the RemoteDevice"); } Object key = new Integer(attrID); if (attrValue == null) { return attributesTable.remove(key) != null; } else { attributesTable.put(key, dataElementCopy(attrValue)); return true; } } /* * SDP responce listener that is used within <code>populateRecord()</code> * processing. */ class SRSDPListener implements SDPResponseListener { /* Attributes values retrieved form remote device. */ DataElement[] attrValues = null; /* Keeps an IOException to be thrown. */ IOException ioExcpt = null; /* IDs of attributes to be retrieved. */ int[] attrIDs = null; /* * Receives error response. * @param errorCode error code * @param info error information * @param transactionID transaction ID */ public void errorResponse(int errorCode, String info, int transactionID) { synchronized (this) { ioExcpt = new IOException(info); notify(); } } /* * Implements required SDPResponseListener method, * must not be called. * @param handleList no matter * @param transactionID no matter */ public void serviceSearchResponse(int[] handleList, int transactionID) { throw new RuntimeException("unexpected call"); } /* * Receives arrays of service record attributes and their values. * @param attributeIDs list of attributes whose values were requested * from server. * @param attributeValues values returned by server within. * @param transactionID ID of transaction response recieved within. */ public void serviceAttributeResponse(int[] attributeIDs, DataElement[] attributeValues, int transactionID) { synchronized (this) { attrIDs = attributeIDs; attrValues = attributeValues; notify(); } } /* * Implements required SDPResponseListener method, * must not be called. * @param attrIDs no matter * @param attributeValues no matter * @param transactionID no matter */ public void serviceSearchAttributeResponse(int[] attrIDs, DataElement[] attributeValues, int transactionID) { throw new RuntimeException("unexpected call"); } } }