/** * BlueCove - Java library for Bluetooth * Copyright (C) 2006-2007 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. * * @author vlads * @version $Id$ */ package net.sf.bluecove; import java.util.Enumeration; import java.util.Vector; import javax.bluetooth.BluetoothStateException; import javax.bluetooth.DataElement; import javax.bluetooth.LocalDevice; import javax.bluetooth.ServiceRecord; import javax.bluetooth.UUID; import org.bluecove.tester.log.Logger; import org.bluecove.tester.util.RuntimeDetect; import junit.framework.Assert; import junit.framework.AssertionFailedError; import net.sf.bluecove.util.BluetoothTypesInfo; import net.sf.bluecove.util.CollectionUtils; /** * */ public class ServiceRecordTester { public static final int ServiceClassIDList = 0x0001; private static Vector allTestServiceAttributes = new Vector(); static { buildAllTestServiceAttributes(); } public static boolean hasServiceClassUUID(ServiceRecord servRecord, UUID uuid) { DataElement attrDataElement = servRecord.getAttributeValue(ServiceClassIDList); if ((attrDataElement == null) || (attrDataElement.getDataType() != DataElement.DATSEQ) || attrDataElement.getSize() == 0) { Logger.warn("Bogus ServiceClassIDList"); if (RuntimeDetect.isBlueCove) { return false; } // Avetana version 3.17 if ((attrDataElement != null) && (attrDataElement.getDataType() == DataElement.UUID)) { return uuid.equals(attrDataElement.getValue()); } return false; } // Logger.debug("test ServiceClassIDList:" + // BluetoothTypesInfo.toString(attrDataElement)); Object value = attrDataElement.getValue(); if ((value == null) || (!(value instanceof Enumeration))) { Logger.warn("Bogus Value in DATSEQ"); if (value != null) { Logger.error("DATSEQ class " + value.getClass().getName()); } return false; } for (Enumeration e = (Enumeration) value; e.hasMoreElements();) { Object element = e.nextElement(); if (!(element instanceof DataElement)) { Logger.warn("Bogus element in DATSEQ, " + value.getClass().getName()); continue; } DataElement dataElement = (DataElement) element; if ((dataElement.getDataType() == DataElement.UUID)) { if (uuid.equals(dataElement.getValue())) { return true; } else { // Logger.debug("not same " + // BluetoothTypesInfo.toString((UUID)dataElement.getValue())); } } else { // Logger.debug("test not UUID:" + // BluetoothTypesInfo.toString(dataElement)); } } return false; } public static boolean hasServiceClassBlieCoveUUID(ServiceRecord servRecord) { return hasServiceClassUUID(servRecord, Configuration.blueCoveUUID()) || hasServiceClassUUID(servRecord, Configuration.blueCoveL2CAPUUID()); } public static boolean equals(DataElement de1, DataElement de2) { if ((de1 == null) || (de2 == null)) { return false; } try { if (de1.getDataType() != de2.getDataType()) { return false; } switch (de1.getDataType()) { case DataElement.U_INT_1: case DataElement.U_INT_2: case DataElement.U_INT_4: case DataElement.INT_1: case DataElement.INT_2: case DataElement.INT_4: case DataElement.INT_8: return (de1.getLong() == de2.getLong()); case DataElement.URL: case DataElement.STRING: case DataElement.UUID: return de1.getValue().equals(de2.getValue()); case DataElement.INT_16: case DataElement.U_INT_8: case DataElement.U_INT_16: byte[] byteAray1 = (byte[]) de1.getValue(); byte[] byteAray2 = (byte[]) de2.getValue(); if (byteAray1.length != byteAray2.length) { return false; } for (int k = 0; k < byteAray1.length; k++) { if (byteAray1[k] != byteAray2[k]) { return false; } } return true; case DataElement.NULL: return true; case DataElement.BOOL: return (de1.getBoolean() == de2.getBoolean()); case DataElement.DATSEQ: case DataElement.DATALT: Enumeration en1 = (Enumeration) de1.getValue(); Enumeration en2 = (Enumeration) de2.getValue(); for (; en1.hasMoreElements() && en2.hasMoreElements();) { DataElement d1 = (DataElement) en1.nextElement(); DataElement d2 = (DataElement) en2.nextElement(); if (!equals(d1, d2)) { return false; } } if (en1.hasMoreElements() || en2.hasMoreElements()) { return false; } return true; default: return false; } } catch (Throwable e) { Logger.error("DataElement equals", e); return false; } } public static boolean testServiceAttributes(ServiceRecord servRecord, String servicesOnDeviceName, String servicesOnDeviceAddress) { boolean isBlueCoveTestService = false; boolean hadError = false; long variableData = 0; if (!Configuration.testServiceAttributes.booleanValue() || ("0".equals(LocalDevice.getProperty("bluetooth.sd.attr.retrievable.max")))) { return hasServiceClassBlieCoveUUID(servRecord); } boolean canTestLong = true; DataElement flagDataElement = servRecord.getAttributeValue(Consts.TEST_SERVICE_ATTRIBUTE_INT_ID); if (flagDataElement != null) { if (flagDataElement.getLong() == Consts.TEST_SERVICE_ATTRIBUTE_INT_VALUE) { canTestLong = false; } } if (canTestLong && Configuration.testAllServiceAttributes.booleanValue()) { isBlueCoveTestService = hasServiceClassBlieCoveUUID(servRecord); if (isBlueCoveTestService) { compareAllServiceAttributes(servRecord, servicesOnDeviceName); } else { Logger.debug("NOT a BlueCove service"); } return isBlueCoveTestService; } if (!canTestLong && Configuration.testAllServiceAttributes.booleanValue()) { Logger.info("can't test all service Attributes"); } try { int[] attributeIDs = servRecord.getAttributeIDs(); // Logger.debug("attributes " + attributeIDs.length); boolean foundName = false; boolean foundInt = false; boolean foundStr = false; boolean foundUrl = false; boolean foundLong = false; boolean foundBytes = false; boolean foundIntOK = false; boolean foundUrlOK = false; boolean foundBytesOK = false; for (int j = 0; j < attributeIDs.length; j++) { int id = attributeIDs[j]; try { DataElement attrDataElement = servRecord.getAttributeValue(id); Assert.assertNotNull("attrValue null", attrDataElement); switch (id) { case BluetoothTypesInfo.ServiceClassIDList: if (!hasServiceClassBlieCoveUUID(servRecord)) { TestResponderClient.failure.addFailure("ServiceClassUUID not found on " + servicesOnDeviceName); } else { isBlueCoveTestService = true; if (Configuration.useServiceClassExtUUID.booleanValue() && !hasServiceClassUUID(servRecord, Consts.uuidSrvClassExt)) { Logger.warn("srv SrvClassExt attr. not found"); TestResponderClient.failure.addFailure("SrvClassExt UUID not found on " + servicesOnDeviceName); } } break; case 0x0100: foundName = true; if (!Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) { String nameValue = (String) attrDataElement.getValue(); Assert.assertTrue("name [" + nameValue + "]", nameValue .startsWith((Consts.RESPONDER_SERVERNAME))); isBlueCoveTestService = true; } break; case Consts.TEST_SERVICE_ATTRIBUTE_INT_ID: foundInt = true; Assert.assertEquals("int type", Consts.TEST_SERVICE_ATTRIBUTE_INT_TYPE, attrDataElement .getDataType()); Assert.assertEquals("int", Consts.TEST_SERVICE_ATTRIBUTE_INT_VALUE, attrDataElement.getLong()); isBlueCoveTestService = true; foundIntOK = true; break; case Consts.TEST_SERVICE_ATTRIBUTE_LONG_ID: foundLong = true; Assert.assertEquals("long type", Consts.TEST_SERVICE_ATTRIBUTE_LONG_TYPE, attrDataElement .getDataType()); if (!Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) { Assert.assertEquals("long", Consts.TEST_SERVICE_ATTRIBUTE_LONG_VALUE, attrDataElement .getLong()); isBlueCoveTestService = true; } break; case Consts.TEST_SERVICE_ATTRIBUTE_STR_ID: foundStr = true; Assert.assertEquals("str type", DataElement.STRING, attrDataElement.getDataType()); if (!Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) { Assert.assertEquals("str", Consts.TEST_SERVICE_ATTRIBUTE_STR_VALUE, attrDataElement .getValue()); isBlueCoveTestService = true; } break; case Consts.TEST_SERVICE_ATTRIBUTE_URL_ID: foundUrl = true; int urlType = attrDataElement.getDataType(); // URL is String on Widcomm Assert.assertTrue("url type", (DataElement.URL == urlType) || (DataElement.STRING == urlType)); if (DataElement.URL != urlType) { Logger.warn("attr URL decoded as STRING"); } Assert.assertEquals("url", Consts.TEST_SERVICE_ATTRIBUTE_URL_VALUE, attrDataElement.getValue()); isBlueCoveTestService = true; foundUrlOK = true; break; case Consts.TEST_SERVICE_ATTRIBUTE_BYTES_ID: foundBytes = true; String byteArrayTypeName = BluetoothTypesInfo .toStringDataElementType(Consts.TEST_SERVICE_ATTRIBUTE_BYTES_TYPE); Assert.assertEquals("byte[] " + byteArrayTypeName + " type", Consts.TEST_SERVICE_ATTRIBUTE_BYTES_TYPE, attrDataElement.getDataType()); byte[] byteAray; try { byteAray = (byte[]) attrDataElement.getValue(); } catch (Throwable e) { Logger.warn("attr " + byteArrayTypeName + " " + id + " " + e.getMessage()); hadError = true; break; } Assert.assertEquals("byteAray.len of " + byteArrayTypeName, Consts.TEST_SERVICE_ATTRIBUTE_BYTES_VALUE.length, byteAray.length); for (int k = 0; k < byteAray.length; k++) { if (Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue() && Configuration.stackWIDCOMM && k >= 4) { // INT_16 are truncated in discovery break; } Assert.assertEquals("byte[" + k + "] of " + byteArrayTypeName, Consts.TEST_SERVICE_ATTRIBUTE_BYTES_VALUE[k], byteAray[k]); } isBlueCoveTestService = true; foundBytesOK = true; break; case Consts.VARIABLE_SERVICE_ATTRIBUTE_BYTES_ID: Assert.assertEquals("var U_INT_4 type", DataElement.U_INT_4, attrDataElement.getDataType()); try { variableData = attrDataElement.getLong(); // Logger.debug("Var info:" + variableData); } catch (Throwable e) { Logger.warn("attr " + id + " " + e.getMessage()); hadError = true; } break; case Consts.SERVICE_ATTRIBUTE_BYTES_SERVER_INFO: Logger.debug("Server info:" + attrDataElement.getValue()); try { Assert.assertEquals("BTAddress", servicesOnDeviceAddress.toUpperCase(), getAddressFromBTSystemInfo(attrDataElement.getValue().toString())); } catch (AssertionFailedError e) { Logger.error("Wrong SR on " + servicesOnDeviceName, e); TestResponderClient.failure.addFailure("Wrong SR on " + servicesOnDeviceName, e); return false; } break; default: if (!Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) { Logger.debug("attribute " + id + " " + BluetoothTypesInfo.toStringDataElementType(attrDataElement.getDataType())); } } } catch (AssertionFailedError e) { Logger.warn("attr " + id + " " + e.getMessage()); // countFailure++; hadError = true; } } if ((!Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) && (!foundName)) { Logger.warn("srv name attr. not found"); TestResponderClient.failure.addFailure("srv name attr. not found on " + servicesOnDeviceName); } if (!foundInt) { Logger.warn("srv INT attr. not found"); TestResponderClient.failure.addFailure("srv INT attr. not found on " + servicesOnDeviceName); } if ((!Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) && (!foundLong)) { Logger.warn("srv long attr. not found"); TestResponderClient.failure.addFailure("srv long attr. not found on " + servicesOnDeviceName); } if ((!Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) && (!foundStr)) { Logger.warn("srv STR attr. not found"); TestResponderClient.failure.addFailure("srv STR attr. not found on " + servicesOnDeviceName); } if (!foundUrl) { Logger.warn("srv URL attr. not found"); TestResponderClient.failure.addFailure("srv URL attr. not found on " + servicesOnDeviceName); } if (!foundBytes) { Logger.warn("srv byte[] attr. not found"); TestResponderClient.failure.addFailure("srv byte[] attr. not found on " + servicesOnDeviceName); } // if (variableData == 0) { // Logger.warn("srv var data attr. not found"); // TestResponderClient.failure.addFailure("srv var data attr. not // found on " + servicesOnDeviceName); // } if (foundName && foundUrl && foundInt && foundStr && foundLong && foundBytes && !hadError) { Logger.info("all service Attr OK"); TestResponderClient.countSuccess++; } else if ((Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) && foundUrl && foundInt && foundBytes && !hadError) { Logger.info("service Attr found"); TestResponderClient.countSuccess++; } if (foundIntOK && foundUrlOK && foundBytesOK) { Logger.info("Common Service Attr OK"); } } catch (Throwable e) { Logger.error("attrs", e); } if (isBlueCoveTestService) { RemoteDeviceInfo.deviceServiceFound(servRecord.getHostDevice(), variableData); } return isBlueCoveTestService; } private static void buildAllTestServiceAttributes() { try { final boolean testIntTypes = true; final boolean testSimpleSequence = true; final boolean extraTestNULL = false; final boolean extraTestInt = false; final boolean extraTestInt16 = false; final boolean extraTestUUIDTypes = false; final boolean extraTestComplextSequence = false; final boolean extraTestLargeSequence = false; if (extraTestNULL) { allTestServiceAttributes.addElement(new DataElement(DataElement.NULL)); } if (testIntTypes) { // Just some arbitrary number the same on client and server. allTestServiceAttributes.addElement(new DataElement(DataElement.U_INT_1, 0xBC)); allTestServiceAttributes.addElement(new DataElement(DataElement.U_INT_2, 0xABCD)); allTestServiceAttributes.addElement(new DataElement(DataElement.U_INT_4, 0xABCDEF40l)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_1, -0x1E)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_2, -0x7EFD)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_4, -0x2BC7EF35l)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_8, -0x7F893012AB39FB72l)); } if (extraTestInt16) { allTestServiceAttributes.addElement(new DataElement(DataElement.U_INT_8, new byte[] { 1, -2, 3, 4, -5, 6, 7, -8 })); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_16, new byte[] { 11, -22, 33, 44, -5, 6, 77, 88, 9, -10, 11, 12, -13, 14, 15, 16 })); allTestServiceAttributes.addElement(new DataElement(DataElement.U_INT_16, new byte[] { 21, -32, 43, 54, -65, 76, 87, 98, 11, -110, 111, 112, -113, 114, 115, 16 })); } // There are limit on number of attributes we can test on WIDCOMM if (extraTestInt) { allTestServiceAttributes.addElement(new DataElement(DataElement.U_INT_1, 0)); allTestServiceAttributes.addElement(new DataElement(DataElement.U_INT_2, 0)); allTestServiceAttributes.addElement(new DataElement(DataElement.U_INT_4, 0)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_1, 0)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_1, 0x4C)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_2, 0)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_2, 0x5BCD)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_4, 0)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_4, 0x1BCDEF35l)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_8, 0)); allTestServiceAttributes.addElement(new DataElement(DataElement.INT_8, 0x3eC6EF355892EA8Cl)); } allTestServiceAttributes.addElement(new DataElement(DataElement.UUID, new UUID( "E10C0FE1121111A11111161911110003", false))); if (extraTestUUIDTypes) { allTestServiceAttributes.addElement(new DataElement(DataElement.UUID, new UUID( "0000110500001000800000805f9b34fb", false))); allTestServiceAttributes.addElement(new DataElement(DataElement.UUID, new UUID(0x1105))); allTestServiceAttributes.addElement(new DataElement(DataElement.UUID, new UUID(0x21301107))); allTestServiceAttributes.addElement(new DataElement(DataElement.UUID, new UUID( "2130110800001000800000805f9b34fb", false))); } if (testSimpleSequence) { allTestServiceAttributes.addElement(new DataElement(DataElement.STRING, "BlueCove-2007")); allTestServiceAttributes .addElement(new DataElement(DataElement.STRING, CommunicationData.stringUTFData)); DataElement seq1 = new DataElement(DataElement.DATSEQ); seq1.addElement(new DataElement(DataElement.STRING, "BlueCove-seq1")); seq1.addElement(new DataElement(DataElement.U_INT_1, 0x12)); allTestServiceAttributes.addElement(seq1); allTestServiceAttributes.addElement(new DataElement(true)); } if (extraTestComplextSequence) { DataElement seq2 = new DataElement(DataElement.DATSEQ); DataElement seq21 = new DataElement(DataElement.DATSEQ); seq21.addElement(new DataElement(DataElement.STRING, "BlueCove-seq2.1")); seq21.addElement(new DataElement(DataElement.U_INT_1, 0x22)); seq2.addElement(seq21); DataElement seq22 = new DataElement(DataElement.DATSEQ); seq22.addElement(new DataElement(DataElement.STRING, "BlueCove-seq2.2")); seq22.addElement(new DataElement(DataElement.U_INT_2, 0x2)); seq22.addElement(new DataElement(DataElement.U_INT_2, 0x3)); // This do not work on WIDCOMM // DataElement seq23 = new DataElement(DataElement.DATSEQ); // seq23.addElement(new DataElement(DataElement.STRING, // "BlueCove-seq2.3")); // seq22.addElement(seq23); seq2.addElement(seq22); // This do not work on WIDCOMM // seq2.addElement(new DataElement(DataElement.U_INT_1, 0x44)); allTestServiceAttributes.addElement(seq2); } if (extraTestLargeSequence) { DataElement seqLong = new DataElement(DataElement.DATSEQ); for (int i = 0; i < 100; i++) { seqLong.addElement(new DataElement(DataElement.STRING, "BlueCove-long-seq " + i)); } allTestServiceAttributes.addElement(seqLong); } } catch (Throwable e) { Logger.error("attrs create", e); } } public static void addAllTestServiceAttributes(ServiceRecord servRecord) { servRecord.setAttributeValue(Consts.TEST_SERVICE_ATTRIBUTE_INT_ID, new DataElement( Consts.TEST_SERVICE_ATTRIBUTE_INT_TYPE, Consts.TEST_SERVICE_ATTRIBUTE_INT_VALUE_TEST_ALL)); for (int i = 0; i < allTestServiceAttributes.size(); i++) { DataElement de = (DataElement) allTestServiceAttributes.elementAt(i); servRecord.setAttributeValue(Consts.SERVICE_ATTRIBUTE_ALL_START + i, de); } } public static int allTestServiceAttributesSize() { return allTestServiceAttributes.size(); } public static void compareAllServiceAttributes(ServiceRecord servRecord, String servicesOnDeviceName) { int[] ids = servRecord.getAttributeIDs(); if (ids == null) { String errorText = "attributes are NULL"; Logger.error(errorText); TestResponderClient.failure.addFailure(errorText + " on " + servicesOnDeviceName); return; } if (ids.length == 0) { String errorText = "not attributes"; Logger.error(errorText); TestResponderClient.failure.addFailure(errorText + " on " + servicesOnDeviceName); return; } int countError = 0; int countSuccess = 0; int countFound = 0; boolean[] found = new boolean[allTestServiceAttributes.size()]; Vector sorted = new Vector(); for (int i = 0; i < ids.length; i++) { sorted.addElement(new Integer(ids[i])); } CollectionUtils.sort(sorted); for (Enumeration en = sorted.elements(); en.hasMoreElements();) { int id = ((Integer) en.nextElement()).intValue(); int index = id - Consts.SERVICE_ATTRIBUTE_ALL_START; if ((index < 0) || (index > allTestServiceAttributes.size())) { continue; } found[index] = true; countFound++; DataElement deGot = servRecord.getAttributeValue(id); DataElement deExpect = (DataElement) allTestServiceAttributes.elementAt(index); if (equals(deGot, deExpect)) { Logger.debug("ServAttr OK " + BluetoothTypesInfo.toString(deGot)); countSuccess += 1; } else { countError += 1; Logger.error("ServAttr " + id + " expected " + BluetoothTypesInfo.toString(deExpect)); Logger.error("ServAttr " + id + " received " + BluetoothTypesInfo.toString(deGot)); } } if (countSuccess != allTestServiceAttributes.size()) { if (countFound != allTestServiceAttributes.size()) { String errorText = "missing attributes, found " + countFound + " expect " + allTestServiceAttributes.size(); Logger.error(errorText); TestResponderClient.failure.addFailure(errorText + " on " + servicesOnDeviceName); } if (countSuccess != 0) { for (int i = 0; i < allTestServiceAttributes.size(); i++) { if (found[i]) { continue; } DataElement de = (DataElement) allTestServiceAttributes.elementAt(i); Logger.error("ServAttr missing " + BluetoothTypesInfo.toString(de)); } } } else { Logger.info("All Service Attr found - OK"); TestResponderClient.countSuccess++; } } private static final String ADDRESS = "address:"; public static String getBTSystemInfo() { try { LocalDevice localDevice = LocalDevice.getLocalDevice(); StringBuffer buf = new StringBuffer(); buf.append(ADDRESS).append(localDevice.getBluetoothAddress()).append(";"); buf.append(" name:").append(localDevice.getFriendlyName()).append(";"); return buf.toString(); } catch (BluetoothStateException e) { return "error"; } } public static String getAddressFromBTSystemInfo(String sysInfoAttr) { if ((sysInfoAttr == null) || (sysInfoAttr.length() < 6)) { return null; } int startIndex = sysInfoAttr.indexOf(ADDRESS); if (startIndex == -1) { return null; } startIndex += ADDRESS.length(); int endIndex = sysInfoAttr.indexOf(';', startIndex); if (endIndex == -1) { return null; } return sysInfoAttr.substring(startIndex, endIndex).toUpperCase(); } }