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 javax.bluetooth.DataElement;
029: import javax.bluetooth.L2CAPConnection;
030: import javax.bluetooth.UUID;
031: import java.util.Enumeration;
032: import java.util.Hashtable;
033: import java.io.IOException;
034:
035: /**
036: * SDPClient class provides a client side of SDP connection as described in
037: * Bluetooth Specification version 1.2.
038: */
039: class SDPClient {
040:
041: /** Max retrievable service record handles. Maybe defined via property. */
042: private static final int MAX_SERVICE_RECORD_COUNT = 0x0fff;
043:
044: /** Max total size of retrievable attributes. Maybe defined via property. */
045: private static final int MAX_ATTRIBUTE_BYTE_COUNT = 0xffff;
046:
047: /**
048: * The lowest possible value of transaction ID.
049: * The number must be positive.
050: */
051: private static final int firstTransactionID = 0x0001;
052:
053: /** The maximum possible value of transaction ID. */
054: private static final int maxTransactionID = 0xffff;
055:
056: /** Next transaction ID. */
057: private static int effectiveTransactionID = firstTransactionID;
058:
059: /** Maps Bluetooth addresses to transport connections. */
060: private static Hashtable transportStorage = new Hashtable();
061:
062: /** Transport connection for this client. */
063: private SDPTransport transport;
064:
065: /** Bluetooth address this client is connected to. */
066: private String address = null;
067:
068: /**
069: * Constructs an <code>SDPClient<code> object and opens SDP connection
070: * to the remote device with the specified Bluetooth address.
071: *
072: * @param bluetoothAddress bluetooth address of SDP server
073: */
074: SDPClient(String bluetoothAddress) throws IOException {
075: address = bluetoothAddress;
076: synchronized (transportStorage) {
077: transport = (SDPTransport) transportStorage.get(address);
078: if (transport == null) {
079: transport = new SDPTransport(address);
080: transportStorage.put(address, transport);
081: }
082: }
083: transport.addRef();
084: }
085:
086: /**
087: * Retrieves next new transaction ID.
088: *
089: * @return new transaction ID.
090: */
091: static synchronized short newTransactionID() {
092: int transactionID = effectiveTransactionID++;
093: if (effectiveTransactionID > maxTransactionID) {
094: // strictly speaking, this is not quite safe,
095: // need revisit : if we have a pending
096: // transaction after 64K of subsequent calls
097: effectiveTransactionID = firstTransactionID;
098: }
099: return (short) transactionID;
100: }
101:
102: /**
103: * Frees transaction ID.
104: *
105: * @param transactionID the ID to free.
106: */
107: static synchronized void freeTransactionID(short transactionID) {
108: // empty in this implementation
109: }
110:
111: /**
112: * Closes connection of this client to the specified server.
113: *
114: * @throws IOException if no connection is open
115: */
116: void close() throws IOException {
117: if (transport == null) {
118: throw new IOException("Connection is already closed.");
119: }
120: transport.release();
121: transport = null;
122: }
123:
124: /**
125: * Initiates ServiceSearch transaction that is used to search for
126: * services that have all the UUIDs specified on a server.
127: */
128: void serviceSearchRequest(UUID[] uuidSet, int transactionID,
129: SDPResponseListener listener) throws IOException {
130: try {
131: transport.serviceSearchRequest(uuidSet, transactionID,
132: listener);
133: } catch (IOException ioe) {
134: transport.cancelAll(SDPResponseListener.IO_ERROR);
135: throw ioe;
136: }
137: }
138:
139: /**
140: * Initiates ServiceAttribute transaction that retrieves
141: * specified attribute values from a specific service record.
142: */
143: void serviceAttributeRequest(int serviceRecordHandle,
144: int[] attrSet, int transactionID,
145: SDPResponseListener listener) throws IOException {
146: try {
147: transport.serviceAttributeRequest(serviceRecordHandle,
148: attrSet, transactionID, listener);
149: } catch (IOException e) {
150: transport.cancelAll(SDPResponseListener.IO_ERROR);
151: throw e;
152: }
153: }
154:
155: /**
156: * Initiates ServiceSearchAttribute transaction that searches for services
157: * on a server by UUIDs specified and retrieves values of specified
158: * parameters for service records found.
159: */
160: void serviceSearchAttributeRequest(int[] attrSet, UUID[] uuidSet,
161: int transactionID, SDPResponseListener listener)
162: throws IOException {
163: try {
164: transport.serviceSearchAttributeRequest(attrSet, uuidSet,
165: transactionID, listener);
166: } catch (IOException e) {
167: transport.cancelAll(SDPResponseListener.IO_ERROR);
168: throw e;
169: }
170: }
171:
172: /**
173: * Cancels transaction with given ID.
174: */
175: boolean cancelServiceSearch(int transactionID) {
176: return transport.cancelServiceSearch(transactionID);
177: }
178:
179: /**
180: * SDP transport connection.
181: */
182: private static class SDPTransport {
183:
184: /** Bluetooth address of the server. */
185: private String address;
186:
187: /**
188: * Reference counter keeps the number of SDP connections which
189: * use this transport. When this value reaches zero, the L2CAP
190: * connection is closed and the transport is removed from the global
191: * SDPClient.transportStorage hashtable.
192: */
193: private int refCount = 0;
194:
195: /** The L2CAP (logical link) connection. */
196: private L2CAPConnection connection;
197:
198: /**
199: * Object that performs reading from and writing to L2CAP connection.
200: */
201: private DataL2CAPReaderWriter rw;
202:
203: /** Maps transaction IDs to ServiceTransaction objects. */
204: private Hashtable transactions = new Hashtable();
205:
206: /** Response receiver. */
207: private Receiver receiver = new Receiver();
208:
209: /** Lock for synchronizing reading from connection. */
210: private Object readLock = new Object();
211:
212: /** Lock for synchronizing writing to connection. */
213: private Object writeLock = new Object();
214:
215: /**
216: * Helper object which serializes and restores
217: * <code>DataElement</code>s.
218: */
219: private static DataElementSerializer des = new DataElementSerializer();
220:
221: /** ID of SDP_ErrorResponse protocol data unit. */
222: private static final int SDP_ERROR_RESPONSE = 0x01;
223:
224: /** ID of SDP_ServiceSearchRequest protocol data unit. */
225: private static final int SDP_SERVICE_SEARCH_REQUEST = 0x02;
226:
227: /** ID of SDP_ServiceSearchResponse protocol data unit. */
228: private static final int SDP_SERVICE_SEARCH_RESPONSE = 0x03;
229:
230: /** ID of SDP_ServiceAttributeRequest protocol data unit. */
231: private static final int SDP_SERVICE_ATTRIBUTE_REQUEST = 0x04;
232:
233: /** ID of SDP_ServiceAttributeResponse protocol data unit. */
234: private static final int SDP_SERVICE_ATTRIBUTE_RESPONSE = 0x05;
235:
236: /** ID of SDP_ServiceSearchAttributeRequest protocol data unit. */
237: private static final int SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST = 0x06;
238:
239: /** ID of SDP_ServiceSearchAttributeResponse protocol data unit. */
240: private static final int SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE = 0x07;
241:
242: /** L2CAP URL starting string. */
243: private static final String SDP_L2CAP_URL_BEGIN = "//";
244:
245: /** L2CAP URL trailing string. */
246: private static final String SDP_L2CAP_URL_END = ":0001";
247:
248: /**
249: * Constricts <code>SDPTransport</code> instance that reflects transport
250: * connections to the specified server.
251: *
252: * @param bluetoothAddress bluetooth address of the server
253: */
254: private SDPTransport(String bluetoothAddress)
255: throws IOException {
256: address = bluetoothAddress;
257: connection = (L2CAPConnection) SDP
258: .getL2CAPConnection(SDP_L2CAP_URL_BEGIN
259: + bluetoothAddress + SDP_L2CAP_URL_END);
260: rw = new DataL2CAPReaderWriter(connection);
261: }
262:
263: /**
264: * Increases reference counter. This object and the underlying L2CAP
265: * connection will live while the counter is positive.
266: */
267: private synchronized void addRef() {
268: refCount++;
269: }
270:
271: /**
272: * Decreases reference counter. If the counter becomes equal to zero,
273: * L2CAP connection is closed and the transport is removed from the
274: * global SDPClient.transportStorage hashtable.
275: */
276: private synchronized void release() {
277: refCount--;
278: if (refCount == 0) {
279: try {
280: connection.close();
281: } catch (IOException e) {
282: // just ignore it, we're done with this object anyway
283: }
284: receiver.cancel();
285: synchronized (transportStorage) {
286: transportStorage.remove(address);
287: }
288: }
289: }
290:
291: /**
292: * Cancels a transaction with specified ID.
293: */
294: boolean cancelTransaction(int transactionID, int reason) {
295: // key for transactions hastable is effectiveTransactionID not
296: // transactionID so we need to iterate over the hashatble looking
297: // for the transactionID
298: Enumeration e = transactions.elements();
299: while (e.hasMoreElements()) {
300: ServiceTransaction trans = (ServiceTransaction) e
301: .nextElement();
302: if (trans != null
303: && trans.transactionID == transactionID) {
304: transactions.remove(new Integer(
305: trans.effectiveTransactionID));
306: // the transaction is removed from transactions hashtable by
307: // ServiceTransaction.end() called from
308: // ServiceTransaction.cancel(). The cancel() reports to the
309: // listener what transaction is terminated.
310: trans.cancel(reason);
311: return true;
312: }
313: }
314: return false;
315: }
316:
317: /**
318: * Cancels all current transactions. Called in case of I/O failure
319: * in the underlying L2CAP connection.
320: */
321: synchronized void cancelAll(int reason) {
322: // force actual release of resources
323: refCount = 1;
324: release();
325: Enumeration e = transactions.elements();
326: while (e.hasMoreElements()) {
327: ServiceTransaction trans = (ServiceTransaction) e
328: .nextElement();
329: trans.listener.errorResponse(reason, "",
330: trans.transactionID);
331: }
332: transactions.clear();
333: }
334:
335: /**
336: * Starts ServiceSearch transaction.
337: */
338: void serviceSearchRequest(UUID[] uuidSet, int transactionID,
339: SDPResponseListener listener) throws IOException {
340: new ServiceSearchTransaction(transactionID, listener,
341: uuidSet).start();
342: }
343:
344: /**
345: * Starts ServiceAttribute transaction.
346: */
347: void serviceAttributeRequest(int serviceRecordHandle,
348: int[] attrSet, int transactionID,
349: SDPResponseListener listener) throws IOException {
350: new ServiceAttributeTransaction(transactionID, listener,
351: serviceRecordHandle, attrSet).start();
352: }
353:
354: /**
355: * Starts ServiceSearchAttribute transaction.
356: */
357: void serviceSearchAttributeRequest(int[] attrSet,
358: UUID[] uuidSet, int transactionID,
359: SDPResponseListener listener) throws IOException {
360: new ServiceSearchAttributeTransaction(transactionID,
361: listener, attrSet, uuidSet).start();
362: }
363:
364: /**
365: * Cancels a transaction with specified ID with reason
366: * <code>SDPResponseListener.TERMINATED</code>.
367: */
368: boolean cancelServiceSearch(int transactionID) {
369: return cancelTransaction(transactionID,
370: SDPResponseListener.TERMINATED);
371: }
372:
373: /**
374: * Gets next SDP server response, if any, and passes it to the
375: * corresponding listener. If a response is received, the transaction
376: * it belongs to is stopped.
377: *
378: * @throws IOException if an I/O error occurs
379: */
380: void processResponse() throws IOException {
381: ServiceTransaction trans;
382: synchronized (readLock) {
383: byte pduID = rw.readByte();
384: short transID = rw.readShort();
385: short length = rw.readShort();
386: // System.out.println("received pdu " + pduID +
387: // " trans " + transID);
388:
389: trans = (ServiceTransaction) transactions
390: .get(new Integer(transID));
391:
392: if (trans == null) {
393: // System.out.println("not found");
394: // transaction we are not aware of; skip this pdu
395: rw.readBytes(length);
396: return;
397: }
398: if (pduID == SDP_ERROR_RESPONSE) {
399: trans.error(length);
400: return;
401: }
402: trans.readParameters(length);
403: }
404: trans.process();
405: }
406:
407: /**
408: * SDP response receiver.
409: */
410: private class Receiver implements Runnable {
411: /** A counter to identify if the receiver has clients. */
412: private int startCounter = 0;
413:
414: /** Identifies if receiving is cancelled. */
415: private boolean canceled = false;
416:
417: /**
418: * Identifies if receiving thread is running (false) or not (true).
419: */
420: private boolean stopped = true;
421:
422: /**
423: * Starts receiving thread if it is not running.
424: */
425: synchronized void start() {
426: if (startCounter == 0) {
427: if (stopped) {
428: stopped = false;
429: new Thread(this ).start();
430: }
431: }
432: startCounter++;
433: }
434:
435: /** Decrements number of receiving thread clients. */
436: synchronized void stop() {
437: startCounter--;
438: }
439:
440: /** Cancels receiving responses. */
441: synchronized void cancel() {
442: canceled = true;
443: }
444:
445: /**
446: * The <code>run()</code> method.
447: *
448: * @see java.lang.Runnable
449: */
450: public void run() {
451: while (true) {
452: synchronized (this ) {
453: if ((startCounter <= 0) || canceled) {
454: startCounter = 0;
455: stopped = true;
456: break;
457: }
458: }
459: try {
460: processResponse();
461: } catch (IOException ioe) {
462: synchronized (this ) {
463: if (startCounter <= 0 || canceled) {
464: startCounter = 0;
465: stopped = true;
466: break;
467: }
468: }
469: cancelAll(SDPResponseListener.IO_ERROR);
470: }
471: }
472: }
473: }
474:
475: /**
476: * This abstract class provides base functionality for all SDP
477: * transactions.
478: */
479: private abstract class ServiceTransaction {
480:
481: /** PDU ID (see Bluetooth Specification 1.2 Vol 3 page 131) */
482: byte pduID;
483: /** Transcation ID used to identify this transaction. */
484: int transactionID;
485: /** Effective transaction ID. */
486: int effectiveTransactionID;
487: /** Length of all parameters. */
488: long parameterLength;
489: /** Continuation state used with partial responses. */
490: byte[] continuationState;
491: /** Listener to report request result to. */
492: SDPResponseListener listener;
493:
494: /**
495: * Class constructor.
496: *
497: * @param pduID protocol data unit ID
498: * @param transactionID transaction ID of the first request
499: * @param listener listener object which will receive
500: * completion and error notifications
501: */
502: ServiceTransaction(int pduID, int transactionID,
503: SDPResponseListener listener) {
504: this .pduID = (byte) pduID;
505: this .transactionID = transactionID;
506: effectiveTransactionID = newTransactionID();
507: this .listener = listener;
508: }
509:
510: /**
511: * Updates the effective transaction ID with a new value.
512: */
513: final void updateTransactionID() {
514: transactions
515: .remove(new Integer(effectiveTransactionID));
516: effectiveTransactionID = newTransactionID();
517: transactions.put(new Integer(effectiveTransactionID),
518: this );
519: }
520:
521: /**
522: * Starts this transaction.
523: *
524: * @throws IOException when an I/O error occurs
525: */
526: final void start() throws IOException {
527: addRef();
528: receiver.start();
529:
530: transactions.put(new Integer(effectiveTransactionID),
531: this );
532:
533: try {
534: synchronized (writeLock) {
535: rw.writeByte(pduID);
536: rw.writeShort((short) effectiveTransactionID);
537: rw.writeShort((short) (parameterLength + 1));
538: writeParameters();
539: rw.writeByte((byte) 0x00);
540: rw.flush();
541: }
542: } catch (IOException e) {
543: end();
544: throw e;
545: }
546: }
547:
548: /**
549: * Re-submits the original request with continuation state
550: * data received with the incomplete response.
551: *
552: * @throws IOException when an I/O error occurs
553: */
554: final void proceed() throws IOException {
555: updateTransactionID();
556: synchronized (writeLock) {
557: rw.writeByte(pduID);
558: rw.writeShort((short) effectiveTransactionID);
559: rw
560: .writeShort((short) (parameterLength + 1 + continuationState.length));
561: writeParameters();
562: rw.writeByte((byte) continuationState.length);
563: rw.writeBytes(continuationState);
564: rw.flush();
565: }
566: }
567:
568: /**
569: * Extracts continuation state parameter.
570: *
571: * @return true if the continuation state is present,
572: * false otherwise
573: * @throws IOException when an I/O error occurs
574: */
575: final boolean readContinuationState() throws IOException {
576: byte infoLength = rw.readByte();
577: if (infoLength == 0) {
578: continuationState = null;
579: return false;
580: }
581: continuationState = rw.readBytes(infoLength);
582: return true;
583: }
584:
585: /**
586: * Terminates this transaction and reports error to the listener.
587: *
588: * @param reason error code which will be reported
589: */
590: final void cancel(int reason) {
591: end();
592: listener.errorResponse(reason, "", transactionID);
593: }
594:
595: /**
596: * Ends this transaction by unregistering it in the outer class.
597: */
598: final void end() {
599: transactions
600: .remove(new Integer(effectiveTransactionID));
601: receiver.stop();
602: release();
603: }
604:
605: /**
606: * Reads error PDU, ends this transaction and reports listener the
607: * error code retrieved.
608: *
609: * @param length length of PDU's parameters
610: */
611: final void error(int length) throws IOException {
612: short errorCode = rw.readShort();
613: byte[] infoBytes = rw.readBytes(length - 2);
614: end();
615: listener.errorResponse(errorCode,
616: new String(infoBytes), transactionID);
617: }
618:
619: /**
620: * Processes this transaction by either re-submitting the original
621: * request if the last response was incomplete, or providing the
622: * listener with the results if the transaction was completed.
623: *
624: * @throws IOException when an I/O error occurs
625: */
626: final void process() throws IOException {
627: if (continuationState != null) {
628: try {
629: proceed();
630: } catch (IOException e) {
631: end();
632: throw e;
633: }
634: } else {
635: end();
636: complete();
637: }
638: }
639:
640: /**
641: * Writes transaction-specific parameters into the PDU.
642: *
643: * @throws IOException when an I/O error occurs
644: */
645: abstract void writeParameters() throws IOException;
646:
647: /**
648: * Reads transaction-specific parameters from the PDU.
649: *
650: * @param length length of PDU's parameters
651: * @throws IOException when an I/O error occurs
652: */
653: abstract void readParameters(int length) throws IOException;
654:
655: /**
656: * Completes the transaction by calling corresponding listener's
657: * method with the data retrieved.
658: */
659: abstract void complete();
660:
661: }
662:
663: /**
664: * Provides ServiceSearch transaction functionality.
665: */
666: class ServiceSearchTransaction extends ServiceTransaction {
667:
668: /** ServiceSearchPattern (BT Spec 1.2 Vol 3 page 135). */
669: DataElement serviceSearchPattern;
670: /** Acquired service record handles. */
671: int[] handleList;
672: /** Current position in the handleList. */
673: int offset;
674:
675: /**
676: * Constructs ServiceSearchTransaction object.
677: */
678: ServiceSearchTransaction(int transactionID,
679: SDPResponseListener listener, UUID[] uuidSet) {
680: super (SDP_SERVICE_SEARCH_REQUEST, transactionID,
681: listener);
682: serviceSearchPattern = new DataElement(
683: DataElement.DATSEQ);
684: for (int i = 0; i < uuidSet.length; i++) {
685: serviceSearchPattern.addElement(new DataElement(
686: DataElement.UUID, uuidSet[i]));
687: }
688: parameterLength = rw.getDataSize(serviceSearchPattern) + 2;
689: }
690:
691: /**
692: * Writes transaction-specific parameters into the PDU.
693: *
694: * @throws IOException when an I/O error occurs
695: */
696: void writeParameters() throws IOException {
697: rw.writeDataElement(serviceSearchPattern);
698: rw.writeShort((short) MAX_SERVICE_RECORD_COUNT);
699: }
700:
701: /**
702: * Reads transaction-specific parameters from the PDU.
703: *
704: * @param length length of PDU's parameters
705: * @throws IOException when an I/O error occurs
706: */
707: void readParameters(int length) throws IOException {
708: int totalServiceRecordCount = rw.readShort();
709: int currentServiceRecordCount = rw.readShort();
710: if (handleList == null && totalServiceRecordCount > 0) {
711: handleList = new int[totalServiceRecordCount];
712: }
713: for (int i = 0; i < currentServiceRecordCount; i++) {
714: handleList[offset] = rw.readInteger();
715: offset++;
716: }
717: readContinuationState();
718: }
719:
720: /**
721: * Completes the transaction by calling corresponding listener's
722: * method with the data retrieved.
723: */
724: void complete() {
725: listener.serviceSearchResponse(handleList,
726: transactionID);
727: }
728: }
729:
730: /**
731: * Provides ServiceAttribute transaction functionality.
732: */
733: class ServiceAttributeTransaction extends ServiceTransaction {
734:
735: /** ServiceRecordHandle (BT Spec 1.2 Vol 3 page 138). */
736: int serviceRecordHandle;
737: /** AttributeIDList (BT Spec 1.2 Vol 3 page 139). */
738: DataElement attributeIDList;
739: /** AttributeList (BT Spec 1.2 Vol 3 page 140). */
740: byte[] attributes;
741:
742: /**
743: * Constructs ServiceAttributeTransaction object.
744: */
745: ServiceAttributeTransaction(int transactionID,
746: SDPResponseListener listener, int recordHandle,
747: int[] attrSet) {
748: super (SDP_SERVICE_ATTRIBUTE_REQUEST, transactionID,
749: listener);
750: serviceRecordHandle = recordHandle;
751: attributeIDList = new DataElement(DataElement.DATSEQ);
752: for (int i = 0; i < attrSet.length; i++) {
753: attributeIDList.addElement(new DataElement(
754: DataElement.U_INT_2, attrSet[i]));
755: }
756: parameterLength = rw.getDataSize(attributeIDList) + 6;
757: }
758:
759: /**
760: * Writes transaction-specific parameters into the PDU.
761: *
762: * @throws IOException when an I/O error occurs
763: */
764: void writeParameters() throws IOException {
765: rw.writeInteger(serviceRecordHandle);
766: rw.writeShort((short) MAX_ATTRIBUTE_BYTE_COUNT);
767: rw.writeDataElement(attributeIDList);
768: }
769:
770: /**
771: * Reads transaction-specific parameters from the PDU.
772: *
773: * @param length length of PDU's parameters
774: * @throws IOException when an I/O error occurs
775: */
776: void readParameters(int length) throws IOException {
777: short byteCount = rw.readShort();
778: byte[] data = rw.readBytes(byteCount);
779: if (attributes == null) {
780: attributes = data;
781: } else {
782: byte[] temp = attributes;
783: attributes = new byte[temp.length + byteCount];
784: System.arraycopy(temp, 0, attributes, 0,
785: temp.length);
786: System.arraycopy(data, 0, attributes, temp.length,
787: byteCount);
788: }
789: readContinuationState();
790: }
791:
792: /**
793: * Completes the transaction by calling corresponding listener's
794: * method with the data retrieved.
795: */
796: void complete() {
797: DataElement attrList;
798: try {
799: attrList = des.restore(attributes);
800: } catch (IOException e) {
801: listener.serviceAttributeResponse(null, null,
802: transactionID);
803: return;
804: }
805: int size = attrList.getSize() / 2;
806: if (size == 0) {
807: listener.serviceAttributeResponse(null, null,
808: transactionID);
809: return;
810: }
811: Enumeration elements = (Enumeration) attrList
812: .getValue();
813: int[] attrIDs = new int[size];
814: DataElement[] attrValues = new DataElement[size];
815: for (int i = 0; elements.hasMoreElements(); i++) {
816: attrIDs[i] = (int) ((DataElement) elements
817: .nextElement()).getLong();
818: attrValues[i] = ((DataElement) elements
819: .nextElement());
820: }
821: listener.serviceAttributeResponse(attrIDs, attrValues,
822: transactionID);
823: }
824: }
825:
826: /**
827: * Provides ServiceSearchAttribute transaction functionality.
828: */
829: class ServiceSearchAttributeTransaction extends
830: ServiceTransaction {
831:
832: /** ServiceSearchPattern (BT Spec 1.2 Vol 3 page 142). */
833: DataElement uuidData;
834: /** AttributeIDList (BT Spec 1.2 Vol 3 page 142). */
835: DataElement attrData;
836: /** AttributeLists (BT Spec 1.2 Vol 3 page 143). */
837: byte[] attributes;
838:
839: /**
840: * Constructs ServiceSearchAttributeTransaction object.
841: */
842: ServiceSearchAttributeTransaction(int transactionID,
843: SDPResponseListener listener, int[] attrSet,
844: UUID[] uuidSet) {
845: super (SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST,
846: transactionID, listener);
847: attrData = new DataElement(DataElement.DATSEQ);
848: uuidData = new DataElement(DataElement.DATSEQ);
849: for (int i = 0; i < attrSet.length; i++) {
850: attrData.addElement(new DataElement(
851: DataElement.U_INT_2, attrSet[i]));
852: }
853: for (int i = 0; i < uuidSet.length; i++) {
854: uuidData.addElement(new DataElement(
855: DataElement.UUID, uuidSet[i]));
856: }
857: parameterLength = rw.getDataSize(attrData)
858: + rw.getDataSize(uuidData) + 2;
859: }
860:
861: /**
862: * Writes transaction-specific parameters into the PDU.
863: *
864: * @throws IOException when an I/O error occurs
865: */
866: void writeParameters() throws IOException {
867: rw.writeDataElement(uuidData);
868: rw.writeShort((short) MAX_ATTRIBUTE_BYTE_COUNT);
869: rw.writeDataElement(attrData);
870: }
871:
872: /**
873: * Reads transaction-specific parameters from the PDU.
874: *
875: * @param length length of PDU's parameters
876: * @throws IOException when an I/O error occurs
877: */
878: void readParameters(int length) throws IOException {
879: short byteCount = rw.readShort();
880: byte[] data = rw.readBytes(byteCount);
881: if (attributes == null) {
882: attributes = data;
883: } else {
884: byte[] temp = attributes;
885: attributes = new byte[temp.length + byteCount];
886: System.arraycopy(temp, 0, attributes, 0,
887: temp.length);
888: System.arraycopy(data, 0, attributes, temp.length,
889: byteCount);
890: }
891: readContinuationState();
892: }
893:
894: /**
895: * Completes the transaction by calling corresponding listener's
896: * method with the data retrieved.
897: */
898: void complete() {
899: DataElement attrList;
900: try {
901: Enumeration attributeLists = (Enumeration) des
902: .restore(attributes).getValue();
903: if (attributeLists.hasMoreElements()) {
904: attrList = (DataElement) attributeLists
905: .nextElement();
906: } else {
907: listener.serviceSearchAttributeResponse(null,
908: null, transactionID);
909: return;
910: }
911: } catch (IOException e) {
912: listener.serviceSearchAttributeResponse(null, null,
913: transactionID);
914: return;
915: }
916: int size = attrList.getSize() / 2;
917: if (size == 0) {
918: listener.serviceSearchAttributeResponse(null, null,
919: transactionID);
920: return;
921: }
922: Enumeration elements = (Enumeration) attrList
923: .getValue();
924: int[] attrIDs = new int[size];
925: DataElement[] attrValues = new DataElement[size];
926: for (int i = 0; elements.hasMoreElements(); i++) {
927: attrIDs[i] = (int) ((DataElement) elements
928: .nextElement()).getLong();
929: attrValues[i] = ((DataElement) elements
930: .nextElement());
931: }
932: listener.serviceSearchAttributeResponse(attrIDs,
933: attrValues, transactionID);
934: }
935: }
936:
937: }
938: }
|