/** * BlueCove BlueZ module - Java library for Bluetooth on Linux * Copyright (C) 2009 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. * * * @version $Id$ */ package com.intel.bluetooth; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Vector; import javax.bluetooth.DataElement; import javax.bluetooth.ServiceRecord; import javax.bluetooth.UUID; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * Read XML representation of Service records to JAR-82 format records. * */ class BlueZServiceRecordXML { private static final Map<String, Integer> integerXMLtypes = new HashMap<String, Integer>(); private static final Map<Integer, String> allXMLtypes = new HashMap<Integer, String>(); static { integerXMLtypes.put("uint8", DataElement.U_INT_1); integerXMLtypes.put("uint16", DataElement.U_INT_2); integerXMLtypes.put("uint32", DataElement.U_INT_4); integerXMLtypes.put("int8", DataElement.INT_1); integerXMLtypes.put("int16", DataElement.INT_2); integerXMLtypes.put("int32", DataElement.INT_4); integerXMLtypes.put("int64", DataElement.INT_8); allXMLtypes.put(DataElement.U_INT_1, "uint8"); allXMLtypes.put(DataElement.U_INT_2, "uint16"); allXMLtypes.put(DataElement.U_INT_4, "uint32"); allXMLtypes.put(DataElement.U_INT_8, "uint64"); allXMLtypes.put(DataElement.U_INT_16, "uint128"); allXMLtypes.put(DataElement.INT_1, "int8"); allXMLtypes.put(DataElement.INT_2, "int16"); allXMLtypes.put(DataElement.INT_4, "int32"); allXMLtypes.put(DataElement.INT_8, "int64"); allXMLtypes.put(DataElement.INT_16, "int128"); allXMLtypes.put(DataElement.BOOL, "boolean"); allXMLtypes.put(DataElement.UUID, "uuid"); allXMLtypes.put(DataElement.STRING, "text"); allXMLtypes.put(DataElement.URL, "url"); allXMLtypes.put(DataElement.NULL, "nil"); allXMLtypes.put(DataElement.DATALT, "alternate"); allXMLtypes.put(DataElement.DATSEQ, "sequence"); } private static String toHexStringSigned(long l) { if (l >= 0) { return "0x" + Long.toHexString(l); } else { return "-0x" + Long.toHexString(-l); } } private static void appendUUID(StringBuffer buf, UUID uuid) { String str = uuid.toString().toUpperCase(); int shortIdx = str.indexOf(BluetoothConsts.SHORT_UUID_BASE); if ((shortIdx != -1) && (shortIdx + BluetoothConsts.SHORT_UUID_BASE.length() == str.length())) { // This is short 16-bit or 32-bit UUID buf.append("0x").append(str.substring(0, shortIdx)); } else { //Make it in format 12345678-1234-1234-1234-123456789012 buf.append(str.substring(0, 8)).append('-').append(str.substring(8, 12)).append('-'); buf.append(str.substring(12, 16)).append('-').append(str.substring(16, 20)).append('-').append(str.substring(20)); } } @SuppressWarnings("unchecked") static void appendDataElement(StringBuffer buf, DataElement d) { int valueType = d.getDataType(); String type = allXMLtypes.get(valueType); buf.append("<").append(type); switch (valueType) { case DataElement.U_INT_1: case DataElement.U_INT_2: case DataElement.U_INT_4: buf.append(" value=\"0x").append(Long.toHexString(d.getLong())).append("\">"); break; case DataElement.INT_1: case DataElement.INT_2: case DataElement.INT_4: case DataElement.INT_8: buf.append(" value=\"").append(toHexStringSigned(d.getLong())).append("\">"); break; case DataElement.BOOL: buf.append(" value=\"").append(d.getBoolean()?"true":"false").append("\">"); break; case DataElement.URL: case DataElement.STRING: //TODO add encoding buf.append(" value=\"").append(d.getValue()).append("\">"); break; case DataElement.UUID: buf.append(" value=\""); appendUUID(buf, ((UUID) d.getValue())); buf.append("\">"); break; case DataElement.U_INT_8: byte[] b8 = (byte[]) d.getValue(); buf.append(" value=\"0x"); for (int i = 0; i < b8.length; i++) { buf.append(Integer.toHexString(b8[i] >> 4 & 0xf)); buf.append(Integer.toHexString(b8[i] & 0xf)); } buf.append("\">"); break; case DataElement.U_INT_16: case DataElement.INT_16: byte[] b16 = (byte[]) d.getValue(); buf.append(" value=\""); for (int i = b16.length - 1; i >= 0; i--) { buf.append(Integer.toHexString(b16[i] >> 4 & 0xf)); buf.append(Integer.toHexString(b16[i] & 0xf)); } buf.append("\">"); break; case DataElement.DATALT: case DataElement.DATSEQ: buf.append(">\n"); for (Enumeration en = (Enumeration) (d.getValue()); en.hasMoreElements();) { appendDataElement(buf, (DataElement)en.nextElement()); } break; case DataElement.NULL: buf.append(">\n"); break; default: throw new IllegalArgumentException("DataElement type " + valueType ); } buf.append("</").append(type).append(">\n"); } public static String exportXMLRecord(ServiceRecord serviceRecord) { StringBuffer b = new StringBuffer(); b.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"); b.append("<record>\n"); int[] ids = serviceRecord.getAttributeIDs(); Vector<Integer> sorted = new Vector<Integer>(); for (int i = 0; i < ids.length; i++) { sorted.addElement(new Integer(ids[i])); } Collections.sort(sorted); for(Integer id : sorted) { b.append("<attribute id=\"0x").append(Long.toHexString(id)).append("\" >\n"); appendDataElement(b, serviceRecord.getAttributeValue(id)); b.append("</attribute>\n"); } b.append("</record>"); return b.toString(); } public static Map<Integer, DataElement> parsXMLRecord(String xml) throws IOException { try { DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = builder.parse(new InputSource(new ByteArrayInputStream(xml.getBytes()))); Element root = doc.getDocumentElement(); if (!"record".equals(root.getTagName())) { throw new IOException("SDP xml record expected, got " + root.getTagName()); } Map<Integer, DataElement> elements = new HashMap<Integer, DataElement>(); NodeList nodes = root.getElementsByTagName("attribute"); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); Node idNode = node.getAttributes().getNamedItem("id"); int id = parsInt(idNode.getNodeValue()); NodeList children = node.getChildNodes(); for (int j = 0; (children != null) && (j < children.getLength()); j++) { Node child = children.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { elements.put(id, parsDataElement(child)); break; } } } return elements; } catch (ParserConfigurationException e) { throw (IOException) UtilsJavaSE.initCause(new IOException(e.getMessage()), e); } catch (SAXException e) { throw (IOException) UtilsJavaSE.initCause(new IOException(e.getMessage()), e); } } private static int parsInt(String value) { if (value.startsWith("0x")) { return Integer.valueOf(value.substring(2), 16).intValue(); } else { return Integer.valueOf(value).intValue(); } } private static long parsLong(String value) { if (value.startsWith("0x")) { return Long.valueOf(value.substring(2), 16).longValue(); } else if (value.startsWith("-0x")) { return - Long.valueOf(value.substring(3), 16).longValue(); } else { return Long.valueOf(value).longValue(); } } private static long getLongValue(Node node) throws IOException { Node valueNode = node.getAttributes().getNamedItem("value"); if (valueNode == null) { throw new IOException("value attribute expected in " + node.getNodeName()); } else { return parsLong(valueNode.getNodeValue()); } } private static boolean getBoolValue(Node node) throws IOException { Node valueNode = node.getAttributes().getNamedItem("value"); if (valueNode == null) { throw new IOException("value attribute expected in " + node.getNodeName()); } else { return "true".equals(valueNode.getNodeValue()); } } private static UUID getUUIDValue(Node node) throws IOException { Node valueNode = node.getAttributes().getNamedItem("value"); if (valueNode == null) { throw new IOException("value attribute expected in " + node.getNodeName()); } String value = valueNode.getNodeValue(); if (value.length() == 32) { return new UUID(value.replace("-", ""), false); } else if (value.startsWith("0x")) { return new UUID(Long.valueOf(value.substring(2), 16).longValue()); } else { String value2 = value.replace("-", ""); if (value2.length() == 32) { return new UUID(value2, false); } throw new IOException("Unknown UUID format " + value); } } private static byte[] getByteArrayValue(Node node, int length) throws IOException { Node valueNode = node.getAttributes().getNamedItem("value"); if (valueNode == null) { throw new IOException("value attribute expected in " + node.getNodeName()); } String value = valueNode.getNodeValue(); if (length != value.length() / 2) { throw new IOException("value attribute invalid length " + value.length()); } byte[] result = new byte[length]; for (int i = 0; i < length; i++) { result[(length - 1) - i] = (byte)(Integer.parseInt(value.substring(i * 2, i * 2 + 2), 16) & 0xFF); } return result; } private static byte[] getByteArrayUINT8(Node node, int length) throws IOException { Node valueNode = node.getAttributes().getNamedItem("value"); if (valueNode == null) { throw new IOException("value attribute expected in " + node.getNodeName()); } String value = valueNode.getNodeValue(); int radix = 10; if (value.startsWith("0x")) { value = value.substring(2); radix = 16; } if (length != value.length() / 2) { throw new IOException("value attribute invalid length " + value.length()); } byte[] result = new byte[length]; for (int i = 0; i < length; i++) { result[i] = (byte)(Integer.parseInt(value.substring(i * 2, i * 2 + 2), radix) & 0xFF); } return result; } private static String getTextValue(Node node) throws IOException { Node valueNode = node.getAttributes().getNamedItem("value"); if (valueNode == null) { throw new IOException("value attribute expected in " + node.getNodeName()); } else { Node encodingNode = node.getAttributes().getNamedItem("encoding"); if (encodingNode == null) { return valueNode.getNodeValue(); } if ("hex".equals(encodingNode.getNodeValue())) { StringBuffer b = new StringBuffer(); String value = valueNode.getNodeValue(); for (int i = 0; i < value.length() / 2; i++) { b.append((char) Integer.parseInt(value.substring(i * 2, i * 2 + 2), 16)); } return b.toString(); } else { throw new IOException("Unknown text encoding " + encodingNode.getNodeValue()); } } } private static DataElement parsDataElement(Node node) throws IOException { String name = node.getNodeName(); Integer intValueType = integerXMLtypes.get(name); if (intValueType != null) { return new DataElement(intValueType.intValue(), getLongValue(node)); } else if ("sequence".equals(name)) { DataElement seq = new DataElement(DataElement.DATSEQ); NodeList children = node.getChildNodes(); for (int j = 0; (children != null) && (j < children.getLength()); j++) { Node child = children.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { seq.addElement(parsDataElement(child)); } } return seq; } else if ("alternate".equals(name)) { DataElement seq = new DataElement(DataElement.DATALT); NodeList children = node.getChildNodes(); for (int j = 0; (children != null) && (j < children.getLength()); j++) { Node child = children.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { seq.addElement(parsDataElement(child)); } } return seq; } else if ("uuid".equals(name)) { return new DataElement(DataElement.UUID, getUUIDValue(node)); } else if ("text".equals(name)) { return new DataElement(DataElement.STRING, getTextValue(node)); } else if ("url".equals(name)) { return new DataElement(DataElement.URL, getTextValue(node)); } else if ("nil".equals(name)) { return new DataElement(DataElement.NULL); } else if ("boolean".equals(name)) { return new DataElement(getBoolValue(node)); } else if ("uint64".equals(name)) { return new DataElement(DataElement.U_INT_8, getByteArrayUINT8(node, 8)); } else if ("int128".equals(name)) { return new DataElement(DataElement.INT_16, getByteArrayValue(node, 16)); } else if ("uint128".equals(name)) { return new DataElement(DataElement.U_INT_16, getByteArrayValue(node, 16)); } else { throw new IOException("Unrecognized DataElement " + name); } } }