001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.kvem.jsr082.bluetooth;
027:
028: import java.io.IOException;
029: import java.util.Enumeration;
030: import java.util.Hashtable;
031: import javax.bluetooth.BluetoothStateException;
032: import javax.bluetooth.DataElement;
033: import javax.bluetooth.LocalDevice;
034: import javax.bluetooth.RemoteDevice;
035: import javax.bluetooth.ServiceRecord;
036: import javax.bluetooth.UUID;
037: import com.sun.midp.io.BluetoothUrl;
038:
039: /**
040: * Service record implementation.
041: */
042: public final class ServiceRecordImpl implements ServiceRecord {
043:
044: /** Maxumum quantity of attributes in one request */
045: static final int RETRIEVABLE_MAX;
046:
047: /**
048: * Maximum number of concurrent service searches that can
049: * exist at any one time.
050: */
051: static final int TRANS_MAX;
052:
053: /** Remote device service provided by. */
054: private RemoteDevice remoteDevice = null;
055:
056: /** Service notifier. */
057: private BluetoothNotifier notifier = null;
058:
059: /** Attribues of the record. */
060: private Hashtable attributesTable = null;
061:
062: /** Bit scale that keeps service classes. */
063: private int serviceClasses = 0;
064:
065: /** Mask to identify attribute IDs out of range. */
066: private static final int MASK_OVERFLOW = 0xffff0000;
067:
068: /** Mask of incorrect class bits. */
069: private static final int MASK_INCORRECT_CLASS = 0xff003fff;
070:
071: /** ServiceRecordHandle attribute ID. */
072: public static final int SERVICE_RECORD_HANDLE = 0x0000;
073:
074: /** ProtocolDescriptorList attribute ID. */
075: public static final int PROTOCOL_DESCRIPTOR_LIST = 0x0004;
076:
077: /** Service class attribute id. */
078: public static final int SERVICE_CLASS_ATTR_ID = 0x0001;
079:
080: /** Name attribute id. */
081: public static final int NAME_ATTR_ID = 0x0100;
082:
083: /** Protocol type. */
084: private int protocol = BluetoothUrl.UNKNOWN;
085:
086: /** Bluetooth address of device service record came from. */
087: private String btaddr = null;
088:
089: /** PSM or channel id. */
090: private int port = -1;
091:
092: static {
093: int retrievableMax = 5; // default value
094: try {
095: retrievableMax = Integer.parseInt(LocalDevice
096: .getProperty("bluetooth.sd.attr.retrievable.max"));
097: } catch (NumberFormatException e) {
098: System.err.println("Internal error: ServiceRecordImpl: "
099: + "improper retrievable.max value");
100: }
101: RETRIEVABLE_MAX = retrievableMax;
102:
103: int transMax = 10; // default value
104: try {
105: transMax = Integer.parseInt(LocalDevice
106: .getProperty("bluetooth.sd.trans.max"));
107: } catch (NumberFormatException e) {
108: System.err.println("Internal error: ServiceRecordImpl: "
109: + "improper trans.max value");
110: }
111: TRANS_MAX = transMax;
112: }
113:
114: /**
115: * Creates service records on client device.
116: *
117: * @param device server device
118: * @param attrIDs attributes IDs
119: * @param attrValues attributes values
120: */
121: protected ServiceRecordImpl(RemoteDevice device, int[] attrIDs,
122: DataElement[] attrValues) {
123: init(attrIDs, attrValues);
124: remoteDevice = device;
125: }
126:
127: /**
128: * Creates service records for the given notifier.
129: *
130: * @param notifier notifier to be associated with this service record
131: * @param attrIDs attributes IDs
132: * @param attrValues attributes values
133: */
134: public ServiceRecordImpl(BluetoothNotifier notifier, int[] attrIDs,
135: DataElement[] attrValues) {
136: init(attrIDs, attrValues);
137: this .notifier = notifier;
138: }
139:
140: /**
141: * Creates a copy of this record. The copy recieves new instances of
142: * attributes values which are of types <code>DataElement.DATSEQ</code>
143: * or <code>DataElement.DATALT</code> (the only data element types that
144: * can be modified after creation).
145: *
146: * @return new instance, a copy of this one.
147: */
148: public synchronized ServiceRecordImpl copy() {
149: int count = attributesTable.size();
150: int[] attrIDs = new int[count];
151: DataElement[] attrValues = new DataElement[count];
152:
153: Enumeration ids = attributesTable.keys();
154: Enumeration values = attributesTable.elements();
155:
156: for (int i = 0; i < count; i++) {
157: attrIDs[i] = ((Integer) ids.nextElement()).intValue();
158: // no nedd to copy elements here; service record constructor
159: // performs the copying
160: attrValues[i] = (DataElement) values.nextElement();
161: }
162:
163: ServiceRecordImpl servRec = new ServiceRecordImpl(notifier,
164: attrIDs, attrValues);
165: servRec.serviceClasses = serviceClasses;
166: return servRec;
167: }
168:
169: /**
170: * Returns service record handle.
171: *
172: * @return service record handle, or 0 if the record is not in SDDB.
173: */
174: public int getHandle() {
175: DataElement handle = getAttributeValue(SERVICE_RECORD_HANDLE);
176: return handle != null ? (int) handle.getLong() : 0;
177: }
178:
179: /**
180: * Sets service record handle.
181: *
182: * @param handle new service record handle value
183: */
184: public void setHandle(int handle) {
185: Integer attrID = new Integer(SERVICE_RECORD_HANDLE);
186: attributesTable.remove(attrID);
187: attributesTable.put(attrID, new DataElement(
188: DataElement.U_INT_4, handle));
189: }
190:
191: /**
192: * Returns notifier that has created this record.
193: * @return corresponding notifier.
194: */
195: public BluetoothNotifier getNotifier() {
196: return notifier;
197: }
198:
199: /**
200: * Creates attributes table and fills it up by values given.
201: * @param attrIDs attributes IDs
202: * @param attrValues attributes values
203: */
204: private void init(int[] attrIDs, DataElement[] attrValues) {
205: attributesTable = new Hashtable(attrIDs.length + 1);
206: attrsInit(attrIDs, attrValues);
207: }
208:
209: /**
210: * Fills up attributes table by values given.
211: * @param attrIDs attributes IDs
212: * @param attrValues attributes values
213: */
214: private void attrsInit(int[] attrIDs, DataElement[] attrValues) {
215: for (int i = 0; i < attrIDs.length; i++) {
216: attributesTable.put(new Integer(attrIDs[i]),
217: dataElementCopy(attrValues[i]));
218: }
219: DataElement handle = getAttributeValue(SERVICE_RECORD_HANDLE);
220: if (handle == null) {
221: attributesTable.put(new Integer(SERVICE_RECORD_HANDLE),
222: new DataElement(DataElement.U_INT_4, 0));
223: }
224: }
225:
226: /**
227: * Creates a copy of DataElement if it's necessary.
228: * @param original data element to be copied if its type
229: * allows value modification
230: * @return copy of data element
231: */
232: private DataElement dataElementCopy(DataElement original) {
233: if ((original.getDataType() == DataElement.DATSEQ)
234: || (original.getDataType() == DataElement.DATALT)) {
235: DataElement copy = new DataElement(original.getDataType());
236: Enumeration elements = (Enumeration) original.getValue();
237:
238: while (elements.hasMoreElements()) {
239: copy.addElement(dataElementCopy((DataElement) elements
240: .nextElement()));
241: }
242: return copy;
243: } else {
244: return original;
245: }
246: }
247:
248: // JAVADOC COMMENT ELIDED
249: public DataElement getAttributeValue(int attrID) {
250: if ((attrID & MASK_OVERFLOW) != 0) {
251: throw new IllegalArgumentException(
252: "attrID isn't a 16-bit unsigned integer");
253: }
254: DataElement attrValue = (DataElement) attributesTable
255: .get(new Integer(attrID));
256:
257: if (attrValue == null) {
258: return null;
259: } else {
260: return dataElementCopy(attrValue);
261: }
262: }
263:
264: // JAVADOC COMMENT ELIDED
265: public RemoteDevice getHostDevice() {
266: return remoteDevice;
267: }
268:
269: // JAVADOC COMMENT ELIDED
270: public synchronized int[] getAttributeIDs() {
271: int[] attrIDs = new int[attributesTable.size()];
272: Enumeration e = attributesTable.keys();
273:
274: for (int i = 0; i < attrIDs.length; i++) {
275: attrIDs[i] = ((Integer) e.nextElement()).intValue();
276: }
277: return attrIDs;
278: }
279:
280: // JAVADOC COMMENT ELIDED
281: public synchronized boolean populateRecord(int[] attrIDs)
282: throws IOException {
283: Hashtable dupChecker = new Hashtable();
284: Object checkObj = new Object();
285:
286: if (remoteDevice == null) {
287: throw new RuntimeException("local ServiceRecord");
288: }
289:
290: if (attrIDs.length == 0) {
291: throw new IllegalArgumentException("attrIDs size is zero");
292: }
293:
294: if (attrIDs.length > RETRIEVABLE_MAX) {
295: throw new IllegalArgumentException(
296: "attrIDs size exceeds retrievable.max");
297: }
298:
299: for (int i = 0; i < attrIDs.length; i++) {
300: if ((attrIDs[i] & MASK_OVERFLOW) != 0) {
301: throw new IllegalArgumentException(
302: "attrID does not represent "
303: + "a 16-bit unsigned integer");
304: }
305:
306: // check attribute ID duplication
307: if (dupChecker.put(new Integer(attrIDs[i]), checkObj) != null) {
308: throw new IllegalArgumentException(
309: "duplicated attribute ID");
310: }
311: }
312:
313: // obtains transaction ID for request
314: short transactionID = SDPClient.newTransactionID();
315:
316: // SDP connection and listener. They are initialized in try blok.
317: SDPClient sdp = null;
318: SRSDPListener listener = null;
319:
320: try {
321: // prepare data for request
322: DataElement handleEl = (DataElement) attributesTable
323: .get(new Integer(SERVICE_RECORD_HANDLE));
324: int handle = (int) handleEl.getLong();
325:
326: // create and prepare SDP listner
327: listener = new SRSDPListener();
328:
329: // create SDP connection and ..
330: sdp = new SDPClient(remoteDevice.getBluetoothAddress());
331:
332: // ... and make request
333: sdp.serviceAttributeRequest(handle, attrIDs, transactionID,
334: listener);
335:
336: synchronized (listener) {
337: if ((listener.ioExcpt == null)
338: && (listener.attrValues == null)) {
339: try {
340: listener.wait();
341: } catch (InterruptedException ie) {
342: // ignore (breake waiting)
343: }
344: }
345: }
346: } finally {
347:
348: // Closes SDP connection and frees transaction ID in any case
349: SDPClient.freeTransactionID(transactionID);
350:
351: // if connection was created try to close it
352: if (sdp != null) {
353: try {
354: sdp.close();
355: } catch (IOException ioe) {
356: // ignore
357: }
358: }
359: }
360:
361: if (listener.ioExcpt != null) {
362: throw listener.ioExcpt;
363: } else if (listener.attrValues == null) {
364: return false;
365: } else if (listener.attrValues.length == 0) {
366: return false;
367: } else {
368: attrsInit(listener.attrIDs, listener.attrValues);
369: return true;
370: }
371: }
372:
373: // JAVADOC COMMENT ELIDED
374: public synchronized String getConnectionURL(int requiredSecurity,
375: boolean mustBeMaster) {
376: // protocol, btaddr, port
377: retrieveUrlCommonParams();
378: BluetoothUrl url = BluetoothUrl.createClientUrl(protocol,
379: btaddr, port);
380:
381: if (mustBeMaster) {
382: url.master = true;
383: } else {
384: url.master = false;
385: }
386:
387: switch (requiredSecurity) {
388: case NOAUTHENTICATE_NOENCRYPT:
389: break;
390: case AUTHENTICATE_ENCRYPT:
391: url.encrypt = true;
392: case AUTHENTICATE_NOENCRYPT:
393: url.authenticate = true;
394: break;
395: default:
396: throw new IllegalArgumentException(
397: "unsupported security type: " + requiredSecurity);
398: }
399:
400: return url.toString();
401: }
402:
403: /**
404: * Retrieves service protocol, device address and port (PSM or channel)
405: * from service record attributes. Results are set to
406: * <code>protocol</code>, <code>btaddr</code> and <code>port</code>
407: * variables correspondingly.
408: */
409: private void retrieveUrlCommonParams() {
410: if (protocol != BluetoothUrl.UNKNOWN) {
411: // already retrieved
412: return;
413: }
414:
415: if (remoteDevice != null) {
416: btaddr = remoteDevice.getBluetoothAddress();
417: } else {
418: try {
419: btaddr = LocalDevice.getLocalDevice()
420: .getBluetoothAddress();
421: } catch (BluetoothStateException bse) {
422: throw new IllegalArgumentException(
423: "cannot generate url");
424: }
425: }
426:
427: /*
428: * There are three protocols supported -
429: * they are obex or rfcomm or l2cap. So, if obex is
430: * found in ProtocolDescriptorList, the protocol is btgoep,
431: * if RFCOMM is found (and no obex) - the btspp, otherwise
432: * the protocol is btl2cap.
433: */
434: DataElement protocolList = getAttributeValue(PROTOCOL_DESCRIPTOR_LIST);
435: Enumeration val = (Enumeration) protocolList.getValue();
436: int type = -1; // 0 = l2cap, 1 = spp, 2 = obex
437: final UUID L2CAP_UUID = new UUID(0x0100);
438: final UUID RFCOMM_UUID = new UUID(0x0003);
439: final UUID OBEX_UUID = new UUID(0x0008);
440:
441: // go through all of the protocols in the protocols list
442: while (val.hasMoreElements()) {
443: DataElement protoDE = (DataElement) val.nextElement();
444:
445: // application adds a garbage in protocolList - ignore
446: if (protoDE.getDataType() != DataElement.DATSEQ) {
447: continue;
448: }
449: Enumeration protoEnum = (Enumeration) protoDE.getValue();
450: int tmpPort = -1;
451: int tmpType = -1;
452:
453: // look on protocol details
454: while (protoEnum.hasMoreElements()) {
455: DataElement de = (DataElement) protoEnum.nextElement();
456:
457: // may be PSM or channel id
458: if (de.getDataType() == DataElement.U_INT_1
459: || de.getDataType() == DataElement.U_INT_2) {
460: tmpPort = (int) de.getLong();
461: } else if (de.getDataType() == DataElement.UUID) {
462: UUID protoUUID = (UUID) de.getValue();
463:
464: if (protoUUID.equals(L2CAP_UUID)) {
465: tmpType = 0;
466: } else if (protoUUID.equals(RFCOMM_UUID)) {
467: tmpType = 1;
468: } else if (protoUUID.equals(OBEX_UUID)) {
469: tmpType = 2;
470: }
471: }
472: }
473:
474: /*
475: * ok, new protocol has been parsed - let's check if it
476: * is over the previous one or not.
477: *
478: * Note, that OBEX protocol may appear before the RFCOMM
479: * one - in this case the port (channel id) is not set -
480: * need to check this case separately.
481: */
482: if (tmpType > type) {
483: type = tmpType;
484:
485: // no "port" for obex type (obex = 2)
486: if (tmpType != 2) {
487: port = tmpPort;
488: }
489: } else if (tmpType == 1) {
490: port = tmpPort;
491: }
492: }
493:
494: switch (type) {
495: case 0:
496: protocol = BluetoothUrl.L2CAP;
497: break;
498: case 1:
499: protocol = BluetoothUrl.RFCOMM;
500: break;
501: case 2:
502: protocol = BluetoothUrl.OBEX;
503: break;
504: default:
505: throw new IllegalArgumentException("wrong protocol list");
506: }
507: }
508:
509: /**
510: * Retrieve service classes bits provided by corresponing service
511: * at local device.
512: *
513: * @return an integer that keeps the service classes bits
514: */
515: public int getDeviceServiceClasses() {
516: if (remoteDevice != null) {
517: throw new RuntimeException(
518: "This ServiceRecord was created by a call to "
519: + "DiscoveryAgent.searchServices()");
520: }
521:
522: // it's necessary to improve these code
523: return serviceClasses;
524: }
525:
526: // JAVADOC COMMENT ELIDED
527: public synchronized void setDeviceServiceClasses(int classes) {
528: // checks that it's service record from remote device
529: if (remoteDevice != null) {
530: throw new RuntimeException("This ServiceRecord was created"
531: + " by a call to DiscoveryAgent.searchServices()");
532: }
533:
534: // checks correction of set classbits
535: if ((classes & MASK_INCORRECT_CLASS) != 0) {
536: throw new IllegalArgumentException(
537: "attempt to set incorrect bits");
538: }
539: serviceClasses = classes;
540: }
541:
542: // JAVADOC COMMENT ELIDED
543: public synchronized boolean setAttributeValue(int attrID,
544: DataElement attrValue) {
545:
546: if ((attrID & MASK_OVERFLOW) != 0) {
547: throw new IllegalArgumentException(
548: "attrID does not represent a 16-bit unsigned integer");
549: }
550:
551: if (attrID == SERVICE_RECORD_HANDLE) {
552: throw new IllegalArgumentException(
553: "attrID is the value of ServiceRecordHandle (0x0000)");
554: }
555:
556: if (remoteDevice != null) {
557: throw new RuntimeException(
558: "can't update ServiceRecord of the RemoteDevice");
559: }
560: Object key = new Integer(attrID);
561:
562: if (attrValue == null) {
563: return attributesTable.remove(key) != null;
564: } else {
565: attributesTable.put(key, dataElementCopy(attrValue));
566: return true;
567: }
568: }
569:
570: /**
571: * SDP responce listener that is used within <code>populateRecord()</code>
572: * processing.
573: */
574: class SRSDPListener implements SDPResponseListener {
575: /** Attributes values retrieved form remote device. */
576: DataElement[] attrValues = null;
577: /** Keeps an IOException to be thrown. */
578: IOException ioExcpt = null;
579: /** IDs of attributes to be retrieved. */
580: int[] attrIDs = null;
581:
582: /**
583: * Receives error response.
584: * @param errorCode error code
585: * @param info error information
586: * @param transactionID transaction ID
587: */
588: public void errorResponse(int errorCode, String info,
589: int transactionID) {
590: synchronized (this ) {
591: ioExcpt = new IOException(info);
592: notify();
593: }
594: }
595:
596: /**
597: * Implements required SDPResponseListener method,
598: * must not be called.
599: * @param handleList no matter
600: * @param transactionID no matter
601: */
602: public void serviceSearchResponse(int[] handleList,
603: int transactionID) {
604: throw new RuntimeException("unexpected call");
605: }
606:
607: /**
608: * Receives arrays of service record attributes and their values.
609: * @param attributeIDs list of attributes whose values were requested
610: * from server.
611: * @param attributeValues values returned by server within.
612: * @param transactionID ID of transaction response recieved within.
613: */
614: public void serviceAttributeResponse(int[] attributeIDs,
615: DataElement[] attributeValues, int transactionID) {
616: synchronized (this ) {
617: attrIDs = attributeIDs;
618: attrValues = attributeValues;
619: notify();
620: }
621: }
622:
623: /**
624: * Implements required SDPResponseListener method,
625: * must not be called.
626: * @param attrIDs no matter
627: * @param attributeValues no matter
628: * @param transactionID no matter
629: */
630: public void serviceSearchAttributeResponse(int[] attrIDs,
631: DataElement[] attributeValues, int transactionID) {
632: throw new RuntimeException("unexpected call");
633: }
634: }
635: }
|