1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package com.intel.bluetooth;
25
26 import java.io.ByteArrayInputStream;
27 import java.io.ByteArrayOutputStream;
28 import java.io.IOException;
29 import java.util.Enumeration;
30 import java.util.Hashtable;
31
32 import javax.bluetooth.BluetoothStateException;
33 import javax.bluetooth.DataElement;
34 import javax.bluetooth.LocalDevice;
35 import javax.bluetooth.RemoteDevice;
36 import javax.bluetooth.ServiceRecord;
37 import javax.bluetooth.UUID;
38
39 import com.intel.bluetooth.BluetoothConsts.DeviceClassConsts;
40
41 class ServiceRecordImpl implements ServiceRecord {
42
43 private BluetoothStack bluetoothStack;
44
45 private RemoteDevice device;
46
47 private long handle;
48
49 Hashtable attributes;
50
51 protected boolean attributeUpdated;
52
53 int deviceServiceClasses;
54
55 int deviceServiceClassesRegistered;
56
57 ServiceRecordImpl(BluetoothStack bluetoothStack, RemoteDevice device, long handle) {
58
59 this.bluetoothStack = bluetoothStack;
60
61 this.device = device;
62
63 this.handle = handle;
64
65 this.deviceServiceClassesRegistered = 0;
66
67 this.attributes = new Hashtable();
68 }
69
70 byte[] toByteArray() throws IOException {
71 DataElement element = new DataElement(DataElement.DATSEQ);
72
73 final boolean sort = true;
74 if (sort) {
75 int[] sortIDs = new int[attributes.size()];
76 int k = 0;
77 for (Enumeration e = attributes.keys(); e.hasMoreElements();) {
78 Integer key = (Integer) e.nextElement();
79 sortIDs[k] = key.intValue();
80 k++;
81 }
82
83
84 for (int i = 0; i < sortIDs.length; i++) {
85 for (int j = 0; j < sortIDs.length - i - 1; j++) {
86 if (sortIDs[j] > sortIDs[j + 1]) {
87 int temp = sortIDs[j];
88 sortIDs[j] = sortIDs[j + 1];
89 sortIDs[j + 1] = temp;
90 }
91 }
92 }
93
94
95 for (int i = 0; i < sortIDs.length; i++) {
96 element.addElement(new DataElement(DataElement.U_INT_2, sortIDs[i]));
97 element.addElement(getAttributeValue(sortIDs[i]));
98 }
99 } else {
100 for (Enumeration e = attributes.keys(); e.hasMoreElements();) {
101 Integer key = (Integer) e.nextElement();
102
103 element.addElement(new DataElement(DataElement.U_INT_2, key.intValue()));
104 element.addElement((DataElement) attributes.get(key));
105 }
106 }
107
108 ByteArrayOutputStream out = new ByteArrayOutputStream();
109
110 (new SDPOutputStream(out)).writeElement(element);
111
112 return out.toByteArray();
113 }
114
115 void loadByteArray(byte data[]) throws IOException {
116 DataElement element = (new SDPInputStream(new ByteArrayInputStream(data))).readElement();
117 if (element.getDataType() != DataElement.DATSEQ) {
118 throw new IOException("DATSEQ expected instead of " + element.getDataType());
119 }
120 Enumeration en = (Enumeration) element.getValue();
121 while (en.hasMoreElements()) {
122 DataElement id = (DataElement) en.nextElement();
123 if (id.getDataType() != DataElement.U_INT_2) {
124 throw new IOException("U_INT_2 expected instead of " + id.getDataType());
125 }
126 DataElement value = (DataElement) en.nextElement();
127 this.populateAttributeValue((int) id.getLong(), value);
128 }
129 }
130
131
132
133
134
135
136
137
138
139
140 public DataElement getAttributeValue(int attrID) {
141 if (attrID < 0x0000 || attrID > 0xffff) {
142 throw new IllegalArgumentException();
143 }
144
145 return (DataElement) attributes.get(new Integer(attrID));
146 }
147
148
149
150
151
152
153
154
155
156
157 public RemoteDevice getHostDevice() {
158 return device;
159 }
160
161
162
163
164
165
166
167
168
169
170 public int[] getAttributeIDs() {
171 int[] attrIDs = new int[attributes.size()];
172
173 int i = 0;
174
175 for (Enumeration e = attributes.keys(); e.hasMoreElements();) {
176 attrIDs[i++] = ((Integer) e.nextElement()).intValue();
177 }
178
179 return attrIDs;
180 }
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214 public boolean populateRecord(int[] attrIDs) throws IOException {
215
216
217
218 if (device == null) {
219 throw new RuntimeException("This is local device service record");
220 }
221
222 if (attrIDs == null) {
223 throw new NullPointerException("attrIDs is null");
224 }
225
226
227
228 if (attrIDs.length == 0) {
229 throw new IllegalArgumentException();
230 }
231
232
233
234
235
236 for (int i = 0; i < attrIDs.length; i++) {
237 if (attrIDs[i] < 0x0000 || attrIDs[i] > 0xffff) {
238 throw new IllegalArgumentException();
239 }
240 }
241
242
243
244
245
246
247 int[] sortIDs = new int[attrIDs.length];
248 System.arraycopy(attrIDs, 0, sortIDs, 0, attrIDs.length);
249 for (int i = 0; i < sortIDs.length; i++) {
250 for (int j = 0; j < sortIDs.length - i - 1; j++) {
251 if (sortIDs[j] > sortIDs[j + 1]) {
252 int temp = sortIDs[j];
253 sortIDs[j] = sortIDs[j + 1];
254 sortIDs[j + 1] = temp;
255 }
256 }
257 }
258
259
260
261 for (int i = 0; i < sortIDs.length - 1; i++) {
262 if (sortIDs[i] == sortIDs[i + 1]) {
263 throw new IllegalArgumentException();
264 }
265 DebugLog.debug0x("srvRec query for attr", sortIDs[i]);
266 }
267 DebugLog.debug0x("srvRec query for attr", sortIDs[sortIDs.length - 1]);
268
269 return this.bluetoothStack.populateServicesRecordAttributeValues(this, sortIDs);
270 }
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299 public String getConnectionURL(int requiredSecurity, boolean mustBeMaster) {
300
301 int commChannel = -1;
302
303 DataElement protocolDescriptor = getAttributeValue(BluetoothConsts.ProtocolDescriptorList);
304 if ((protocolDescriptor == null) || (protocolDescriptor.getDataType() != DataElement.DATSEQ)) {
305 return null;
306 }
307
308
309
310
311
312
313 boolean isL2CAP = false;
314 boolean isRFCOMM = false;
315 boolean isOBEX = false;
316
317 for (Enumeration protocolsSeqEnum = (Enumeration) protocolDescriptor.getValue(); protocolsSeqEnum
318 .hasMoreElements();) {
319 DataElement elementSeq = (DataElement) protocolsSeqEnum.nextElement();
320
321 if (elementSeq.getDataType() == DataElement.DATSEQ) {
322 Enumeration elementSeqEnum = (Enumeration) elementSeq.getValue();
323
324 if (elementSeqEnum.hasMoreElements()) {
325 DataElement protocolElement = (DataElement) elementSeqEnum.nextElement();
326 if (protocolElement.getDataType() != DataElement.UUID) {
327 continue;
328 }
329 Object uuid = protocolElement.getValue();
330 if (BluetoothConsts.OBEX_PROTOCOL_UUID.equals(uuid)) {
331 isOBEX = true;
332 isRFCOMM = false;
333 isL2CAP = false;
334 } else if (elementSeqEnum.hasMoreElements() && (BluetoothConsts.RFCOMM_PROTOCOL_UUID.equals(uuid))) {
335
336 DataElement protocolPSMElement = (DataElement) elementSeqEnum.nextElement();
337
338 switch (protocolPSMElement.getDataType()) {
339 case DataElement.U_INT_1:
340 case DataElement.U_INT_2:
341 case DataElement.U_INT_4:
342 case DataElement.INT_1:
343 case DataElement.INT_2:
344 case DataElement.INT_4:
345 case DataElement.INT_8:
346 long val = protocolPSMElement.getLong();
347 if ((val >= BluetoothConsts.RFCOMM_CHANNEL_MIN)
348 && (val <= BluetoothConsts.RFCOMM_CHANNEL_MAX)) {
349 commChannel = (int) val;
350 isRFCOMM = true;
351 isL2CAP = false;
352 }
353 break;
354 }
355 } else if (elementSeqEnum.hasMoreElements() && (BluetoothConsts.L2CAP_PROTOCOL_UUID.equals(uuid))) {
356 DataElement protocolPSMElement = (DataElement) elementSeqEnum.nextElement();
357 switch (protocolPSMElement.getDataType()) {
358 case DataElement.U_INT_1:
359 case DataElement.U_INT_2:
360 case DataElement.U_INT_4:
361 case DataElement.INT_1:
362 case DataElement.INT_2:
363 case DataElement.INT_4:
364 case DataElement.INT_8:
365 long pcm = protocolPSMElement.getLong();
366 if ((pcm >= BluetoothConsts.L2CAP_PSM_MIN) && (pcm <= BluetoothConsts.L2CAP_PSM_MAX)) {
367 commChannel = (int) pcm;
368 isL2CAP = true;
369 }
370 break;
371 }
372 }
373 }
374 }
375 }
376
377 if (commChannel == -1) {
378 return null;
379 }
380
381
382
383
384 StringBuffer buf = new StringBuffer();
385 if (isOBEX) {
386 buf.append(BluetoothConsts.PROTOCOL_SCHEME_BT_OBEX);
387 } else if (isRFCOMM) {
388 buf.append(BluetoothConsts.PROTOCOL_SCHEME_RFCOMM);
389 } else if (isL2CAP) {
390 buf.append(BluetoothConsts.PROTOCOL_SCHEME_L2CAP);
391 } else {
392 return null;
393 }
394 buf.append("://");
395
396 if (device == null) {
397 try {
398 Object saveID = BlueCoveImpl.getCurrentThreadBluetoothStackID();
399 try {
400 BlueCoveImpl.setThreadBluetoothStack(bluetoothStack);
401 buf.append(LocalDevice.getLocalDevice().getBluetoothAddress());
402 } finally {
403 if (saveID != null) {
404 BlueCoveImpl.setThreadBluetoothStackID(saveID);
405 }
406 }
407 } catch (BluetoothStateException bse) {
408 DebugLog.error("can't read LocalAddress", bse);
409 buf.append("localhost");
410 }
411 } else {
412 buf.append(getHostDevice().getBluetoothAddress());
413 }
414
415 buf.append(":");
416 if (isL2CAP) {
417 String hex = Integer.toHexString(commChannel);
418 for (int i = hex.length(); i < 4; i++) {
419 buf.append('0');
420 }
421 buf.append(hex);
422 } else {
423 buf.append(commChannel);
424 }
425
426 switch (requiredSecurity) {
427 case NOAUTHENTICATE_NOENCRYPT:
428 buf.append(";authenticate=false;encrypt=false");
429 break;
430 case AUTHENTICATE_NOENCRYPT:
431 buf.append(";authenticate=true;encrypt=false");
432 break;
433 case AUTHENTICATE_ENCRYPT:
434 buf.append(";authenticate=true;encrypt=true");
435 break;
436 default:
437 throw new IllegalArgumentException();
438 }
439
440 if (mustBeMaster) {
441 buf.append(";master=true");
442 } else {
443 buf.append(";master=false");
444 }
445
446 return buf.toString();
447 }
448
449 int getChannel(UUID protocolUUID) {
450
451 int channel = -1;
452
453 DataElement protocolDescriptor = getAttributeValue(BluetoothConsts.ProtocolDescriptorList);
454 if ((protocolDescriptor == null) || (protocolDescriptor.getDataType() != DataElement.DATSEQ)) {
455 return -1;
456 }
457
458
459
460
461
462
463 for (Enumeration protocolsSeqEnum = (Enumeration) protocolDescriptor.getValue(); protocolsSeqEnum
464 .hasMoreElements();) {
465 DataElement elementSeq = (DataElement) protocolsSeqEnum.nextElement();
466
467 if (elementSeq.getDataType() == DataElement.DATSEQ) {
468 Enumeration elementSeqEnum = (Enumeration) elementSeq.getValue();
469
470 if (elementSeqEnum.hasMoreElements()) {
471 DataElement protocolElement = (DataElement) elementSeqEnum.nextElement();
472 if (protocolElement.getDataType() != DataElement.UUID) {
473 continue;
474 }
475 Object uuid = protocolElement.getValue();
476 if (elementSeqEnum.hasMoreElements() && (protocolUUID.equals(uuid))) {
477
478 DataElement protocolPSMElement = (DataElement) elementSeqEnum.nextElement();
479
480 switch (protocolPSMElement.getDataType()) {
481 case DataElement.U_INT_1:
482 case DataElement.U_INT_2:
483 case DataElement.U_INT_4:
484 case DataElement.INT_1:
485 case DataElement.INT_2:
486 case DataElement.INT_4:
487 case DataElement.INT_8:
488 channel = (int) protocolPSMElement.getLong();
489 break;
490 }
491 }
492 }
493 }
494 }
495 return channel;
496 }
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543 public void setDeviceServiceClasses(int classes) {
544 if (device != null) {
545 throw new RuntimeException("Service record obtained from a remote device");
546 }
547 if ((classes & (0xff000000 | DeviceClassConsts.LIMITED_DISCOVERY_SERVICE | DeviceClassConsts.FORMAT_VERSION_MASK)) != 0) {
548 throw new IllegalArgumentException();
549 }
550 if ((classes & (DeviceClassConsts.MAJOR_MASK | DeviceClassConsts.MINOR_MASK)) != 0) {
551 throw new IllegalArgumentException();
552 }
553
554 if ((bluetoothStack.getFeatureSet() & BluetoothStack.FEATURE_SET_DEVICE_SERVICE_CLASSES) == 0) {
555 throw new NotSupportedRuntimeException(bluetoothStack.getStackID());
556 }
557
558 this.deviceServiceClasses = classes;
559 }
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590 public boolean setAttributeValue(int attrID, DataElement attrValue) {
591
592
593
594
595 if (device != null) {
596 throw new IllegalArgumentException();
597 }
598
599 if (attrID < 0x0000 || attrID > 0xffff) {
600 throw new IllegalArgumentException();
601 }
602
603 if (attrID == BluetoothConsts.ServiceRecordHandle) {
604 throw new IllegalArgumentException();
605 }
606
607
608
609
610
611 attributeUpdated = true;
612 if (attrValue == null) {
613 return (attributes.remove(new Integer(attrID)) != null);
614 } else {
615 attributes.put(new Integer(attrID), attrValue);
616 return true;
617 }
618 }
619
620
621
622
623 void populateAttributeValue(int attrID, DataElement attrValue) {
624 if (attrID < 0x0000 || attrID > 0xffff) {
625 throw new IllegalArgumentException();
626 }
627 if (attrValue == null) {
628 attributes.remove(new Integer(attrID));
629 } else {
630 attributes.put(new Integer(attrID), attrValue);
631 }
632 }
633
634 public String toString() {
635
636 StringBuffer buf = new StringBuffer("{\n");
637
638 for (Enumeration e = attributes.keys(); e.hasMoreElements();) {
639 Integer i = (Integer) e.nextElement();
640
641 buf.append("0x");
642 buf.append(Integer.toHexString(i.intValue()));
643 buf.append(":\n\t");
644
645 DataElement d = (DataElement) attributes.get(i);
646
647 buf.append(d);
648 buf.append("\n");
649 }
650
651 buf.append("}");
652
653 return buf.toString();
654 }
655
656
657
658
659 long getHandle() {
660 return this.handle;
661 }
662
663
664
665
666 void setHandle(long handle) {
667 this.handle = handle;
668 }
669
670
671
672
673 boolean hasServiceClassUUID(UUID uuid) {
674 DataElement attrDataElement = getAttributeValue(BluetoothConsts.ServiceClassIDList);
675 if ((attrDataElement == null) || (attrDataElement.getDataType() != DataElement.DATSEQ)
676 || attrDataElement.getSize() == 0) {
677
678 return false;
679 }
680
681 Object value = attrDataElement.getValue();
682 if ((value == null) || (!(value instanceof Enumeration))) {
683 DebugLog.debug("Bogus Value in DATSEQ");
684 if (value != null) {
685 DebugLog.error("DATSEQ class " + value.getClass().getName());
686 }
687 return false;
688 }
689 for (Enumeration e = (Enumeration) value; e.hasMoreElements();) {
690 Object element = e.nextElement();
691 if (!(element instanceof DataElement)) {
692 DebugLog.debug("Bogus element in DATSEQ, " + value.getClass().getName());
693 continue;
694 }
695 DataElement dataElement = (DataElement) element;
696 if ((dataElement.getDataType() == DataElement.UUID) && (uuid.equals(dataElement.getValue()))) {
697 return true;
698 }
699 }
700
701 return false;
702 }
703
704 boolean hasProtocolClassUUID(UUID uuid) {
705 DataElement protocolDescriptor = getAttributeValue(BluetoothConsts.ProtocolDescriptorList);
706 if ((protocolDescriptor == null) || (protocolDescriptor.getDataType() != DataElement.DATSEQ)) {
707
708 return false;
709 }
710
711 for (Enumeration protocolsSeqEnum = (Enumeration) protocolDescriptor.getValue(); protocolsSeqEnum
712 .hasMoreElements();) {
713 DataElement elementSeq = (DataElement) protocolsSeqEnum.nextElement();
714
715 if (elementSeq.getDataType() == DataElement.DATSEQ) {
716 Enumeration elementSeqEnum = (Enumeration) elementSeq.getValue();
717 if (elementSeqEnum.hasMoreElements()) {
718 DataElement protocolElement = (DataElement) elementSeqEnum.nextElement();
719 if (protocolElement.getDataType() != DataElement.UUID) {
720 continue;
721 }
722 if (uuid.equals(protocolElement.getValue())) {
723 return true;
724 }
725 }
726 }
727 }
728 return false;
729 }
730
731 DataElement clone(DataElement de) {
732 DataElement c = null;
733
734 switch (de.getDataType()) {
735 case DataElement.U_INT_1:
736 case DataElement.U_INT_2:
737 case DataElement.U_INT_4:
738 case DataElement.INT_1:
739 case DataElement.INT_2:
740 case DataElement.INT_4:
741 c = new DataElement(de.getDataType(), de.getLong());
742 break;
743 case DataElement.URL:
744 case DataElement.STRING:
745 case DataElement.UUID:
746 case DataElement.INT_16:
747 case DataElement.INT_8:
748 case DataElement.U_INT_16:
749 c = new DataElement(de.getDataType(), de.getValue());
750 break;
751 case DataElement.NULL:
752 c = new DataElement(de.getDataType());
753 break;
754 case DataElement.BOOL:
755 c = new DataElement(de.getBoolean());
756 break;
757 case DataElement.DATSEQ:
758 case DataElement.DATALT:
759 c = new DataElement(de.getDataType());
760 for (Enumeration en = (Enumeration) de.getValue(); en.hasMoreElements();) {
761 DataElement dataElement = (DataElement) en.nextElement();
762 c.addElement(clone(dataElement));
763 }
764 }
765
766 return c;
767 }
768
769
770
771
772 void populateRFCOMMAttributes(long handle, int channel, UUID uuid, String name, boolean obex) {
773
774 this.populateAttributeValue(BluetoothConsts.ServiceRecordHandle, new DataElement(DataElement.U_INT_4, handle));
775
776
777
778
779
780 DataElement serviceClassIDList = new DataElement(DataElement.DATSEQ);
781 serviceClassIDList.addElement(new DataElement(DataElement.UUID, uuid));
782 if (!obex) {
783 serviceClassIDList.addElement(new DataElement(DataElement.UUID, BluetoothConsts.SERIAL_PORT_UUID));
784 }
785
786 this.populateAttributeValue(BluetoothConsts.ServiceClassIDList, serviceClassIDList);
787
788
789
790
791
792 DataElement protocolDescriptorList = new DataElement(DataElement.DATSEQ);
793
794 DataElement L2CAPDescriptor = new DataElement(DataElement.DATSEQ);
795 L2CAPDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.L2CAP_PROTOCOL_UUID));
796 protocolDescriptorList.addElement(L2CAPDescriptor);
797
798 DataElement RFCOMMDescriptor = new DataElement(DataElement.DATSEQ);
799 RFCOMMDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.RFCOMM_PROTOCOL_UUID));
800 RFCOMMDescriptor.addElement(new DataElement(DataElement.U_INT_1, channel));
801 protocolDescriptorList.addElement(RFCOMMDescriptor);
802
803 if (obex) {
804 DataElement OBEXDescriptor = new DataElement(DataElement.DATSEQ);
805 OBEXDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.OBEX_PROTOCOL_UUID));
806 protocolDescriptorList.addElement(OBEXDescriptor);
807 }
808
809 this.populateAttributeValue(BluetoothConsts.ProtocolDescriptorList, protocolDescriptorList);
810
811 if (name != null) {
812 this.populateAttributeValue(BluetoothConsts.AttributeIDServiceName, new DataElement(DataElement.STRING,
813 name));
814 }
815 }
816
817 void populateL2CAPAttributes(int handle, int channel, UUID uuid, String name) {
818
819 this.populateAttributeValue(BluetoothConsts.ServiceRecordHandle, new DataElement(DataElement.U_INT_4, handle));
820
821
822
823
824
825 DataElement serviceClassIDList = new DataElement(DataElement.DATSEQ);
826 serviceClassIDList.addElement(new DataElement(DataElement.UUID, uuid));
827
828 this.populateAttributeValue(BluetoothConsts.ServiceClassIDList, serviceClassIDList);
829
830
831
832
833 DataElement protocolDescriptorList = new DataElement(DataElement.DATSEQ);
834
835 DataElement L2CAPDescriptor = new DataElement(DataElement.DATSEQ);
836 L2CAPDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.L2CAP_PROTOCOL_UUID));
837 L2CAPDescriptor.addElement(new DataElement(DataElement.U_INT_2, channel));
838 protocolDescriptorList.addElement(L2CAPDescriptor);
839
840 this.populateAttributeValue(BluetoothConsts.ProtocolDescriptorList, protocolDescriptorList);
841
842 if (name != null) {
843 this.populateAttributeValue(BluetoothConsts.AttributeIDServiceName, new DataElement(DataElement.STRING,
844 name));
845 }
846 }
847 }