/* * * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.jsr082.bluetooth; import java.io.IOException; import java.util.Enumeration; import java.util.Stack; import javax.bluetooth.DataElement; import javax.bluetooth.ServiceRecord; import javax.bluetooth.UUID; /* * Serializes and restores DataElement objects. */ public class DataElementSerializer { /* NULL data header. */ private static final byte NULL_DATA = 0x00; /* Boolean data header. */ private static final byte BOOLEAN_DATA = 0x28; /* 1-byte signed integer header. */ private static final byte INT1_SIGNED = 0x10; /* 2-byte signed integer header. */ private static final byte INT2_SIGNED = 0x11; /* 4-byte signed integer header. */ private static final byte INT4_SIGNED = 0x12; /* 8-byte signed integer header. */ private static final byte INT8_SIGNED = 0x13; /* 16-byte signed integer header. */ private static final byte INT16_SIGNED = 0x14; /* 1-byte unsigned integer header. */ private static final byte INT1_UNSIGNED = 0x08; /* 2-byte unsigned integer header. */ private static final byte INT2_UNSIGNED = 0x09; /* 4-byte unsigned integer header. */ private static final byte INT4_UNSIGNED = 0x0a; /* 8-byte unsigned integer header. */ private static final byte INT8_UNSIGNED = 0x0b; /* 16-byte unsigned integer header. */ private static final byte INT16_UNSIGNED = 0x0c; /* 16-bit UUID header. */ private static final byte UUID_2 = 0x19; /* 32-bit UUID header. */ private static final byte UUID_4 = 0x1a; /* 128-bit UUID header. */ private static final byte UUID_16 = 0x1c; /* Mask to get type tag from header. */ private static final byte TYPE_MASK = ((byte)0xf8); /* Mask to get size of data size field from header. */ private static final byte SIZE_MASK = 0x07; /* Tag for string type. */ private static final byte STRING_TYPE = 0x20; /* Tag for URL type. */ private static final byte URL_TYPE = 0x40; /* Tag for sequence type. */ private static final byte SEQUENCE_TYPE = 0x30; /* Tag for an alternative type. */ private static final byte ALTERNATIVE_TYPE = 0x38; /* Tag that identifies that size of data size field is 2 bytes. */ private static final byte SHORT_SIZE = 0x05; /* Tag that identifies that size of data size field is 4 bytes. */ private static final byte NORMAL_SIZE = 0x06; /* Tag that identifies that size of data size field is 8 bytes. */ private static final byte LONG_SIZE = 0x07; /* Destination buffer which collects binary data of a data element. */ protected byte[] writeBuffer = null; /* Source buffer which contains binary data of a data element. */ protected byte[] readBuffer = null; /* Current position at the destination buffer. */ protected long writePos = 0; /* Current position at the source buffer. */ protected long readPos = 0; /* Allows to store and retrieve positions at the source buffer. */ private Stack readPosStack = new Stack(); /* * Constructs the serializer object. */ public DataElementSerializer() { } /* * Serializes given DataElement object, i.e. creates an array of bytes * representing DataElement as described in Bluetooth Specification * Version 1.2, vol 3, page 127. * * @param data the data element to serialize * @return an array containing the serialized data element * @throws IOException if an I/O error occurs */ public synchronized byte[] serialize(DataElement data) throws IOException { writeBuffer = new byte[(int)getDataSize(data)]; writePos = 0; writeDataElement(data); byte[] result = writeBuffer; writeBuffer = null; return result; } /* * Constructs DataElement from byte array containing the element in * serialized form. * * @param data byte array containing the element in serialized form * @return DataElement constructed from the binary data * @throws IOException if an I/O error occurs */ public synchronized DataElement restore(byte[] data) throws IOException { readBuffer = data; readPos = 0; DataElement result = readDataElement(); readBuffer = null; return result; } /* * Returns the size of DataElement with service information * to get the total size required to work with this DataElement. * * @param data the data element to get packet size for * @return number of bytes needed to store given data element */ public long getDataSize(DataElement data) { int type = data.getDataType(); long size = getPureDataSize(data); if ((type == DataElement.NULL) || (type == DataElement.BOOL) || (type == DataElement.INT_1) || (type == DataElement.U_INT_1) || (type == DataElement.INT_2) || (type == DataElement.U_INT_2) || (type == DataElement.INT_4) || (type == DataElement.U_INT_4) || (type == DataElement.INT_8) || (type == DataElement.U_INT_8) || (type == DataElement.INT_16) || (type == DataElement.U_INT_16) || (type == DataElement.UUID)) { return size + 1; } else if ((type == DataElement.DATSEQ) || (type == DataElement.DATALT) || (type == DataElement.STRING) || (type == DataElement.URL)) { if (size <= 0xffL) { return size + 2; } else if (size <= 0xffffL) { return size + 3; } else if (size <= 0xffffffffL) { return size + 5; } else { throw new RuntimeException("Data size is too large."); } } else { throw new RuntimeException("Unexpected data type."); } } /* * Returns the size of DataElement without service information. * * @param data the data element to get pure data size for * @return pure data size in bytes */ public long getPureDataSize(DataElement data) { switch (data.getDataType()) { case DataElement.NULL: return 0; case DataElement.BOOL: case DataElement.INT_1: case DataElement.U_INT_1: return 1; case DataElement.INT_2: case DataElement.U_INT_2: return 2; case DataElement.INT_4: case DataElement.U_INT_4: return 4; case DataElement.INT_8: case DataElement.U_INT_8: return 8; case DataElement.INT_16: case DataElement.U_INT_16: return 16; case DataElement.DATSEQ: case DataElement.DATALT: long size = 0; Enumeration elements = (Enumeration)data.getValue(); while (elements.hasMoreElements()) { size += getDataSize((DataElement)elements.nextElement()); } return size; case DataElement.STRING: case DataElement.URL: return ((String)data.getValue()).length(); case DataElement.UUID: return 16; default: throw new RuntimeException("Unknown data type."); } } /* * Writes given data element into the write buffer. * * @param data the data element to write * @throws IOException if an I/O error occurs */ public void writeDataElement(DataElement data) throws IOException { long size = getPureDataSize(data); int type = data.getDataType(); byte typeBits = 0x00; if ((type == DataElement.NULL) || (type == DataElement.BOOL) || (type == DataElement.INT_1) || (type == DataElement.U_INT_1) || (type == DataElement.INT_2) || (type == DataElement.U_INT_2) || (type == DataElement.INT_4) || (type == DataElement.U_INT_4) || (type == DataElement.INT_8) || (type == DataElement.U_INT_8) || (type == DataElement.INT_16) || (type == DataElement.U_INT_16)) { switch (type) { case DataElement.NULL: writeByte(NULL_DATA); break; case DataElement.BOOL: writeByte(BOOLEAN_DATA); writeBoolean(data.getBoolean()); break; case DataElement.INT_1: writeByte(INT1_SIGNED); writeByte((byte)data.getLong()); break; case DataElement.U_INT_1: writeByte(INT1_UNSIGNED); writeByte((byte)data.getLong()); break; case DataElement.INT_2: writeByte(INT2_SIGNED); writeShort((short)data.getLong()); break; case DataElement.U_INT_2: writeByte(INT2_UNSIGNED); writeShort((short)data.getLong()); break; case DataElement.INT_4: writeByte(INT4_SIGNED); writeInteger((int)data.getLong()); break; case DataElement.U_INT_4: writeByte(INT4_UNSIGNED); writeInteger((int)data.getLong()); break; case DataElement.INT_8: writeByte(INT8_SIGNED); writeLong(data.getLong()); break; case DataElement.U_INT_8: writeByte(INT8_UNSIGNED); writeBytes((byte[])data.getValue()); break; case DataElement.INT_16: writeByte(INT16_SIGNED); writeBytes((byte[])data.getValue()); break; case DataElement.U_INT_16: writeByte(INT16_UNSIGNED); writeBytes((byte[])data.getValue()); break; } } else if ((type == DataElement.DATSEQ) || (type == DataElement.DATALT) || (type == DataElement.STRING) || (type == DataElement.URL)) { switch (type) { case DataElement.DATSEQ: typeBits = (TYPE_MASK & SEQUENCE_TYPE); break; case DataElement.DATALT: typeBits = (TYPE_MASK & ALTERNATIVE_TYPE); break; case DataElement.STRING: typeBits = (TYPE_MASK & STRING_TYPE); break; case DataElement.URL: typeBits = (TYPE_MASK & URL_TYPE); break; } if (size <= 0xff) { writeByte(typeBits | (SIZE_MASK & SHORT_SIZE)); writeByte((byte)size); } else if (size <= 0xffff) { writeByte(typeBits | (SIZE_MASK & NORMAL_SIZE)); writeShort((short)size); } else { writeByte(typeBits | (SIZE_MASK & LONG_SIZE)); writeInteger((int)size); } if ((type == DataElement.DATSEQ) || (type == DataElement.DATALT)) { Enumeration elements = (Enumeration) data.getValue(); while (elements.hasMoreElements()) { writeDataElement((DataElement)elements.nextElement()); } } else { writeBytes(((String)data.getValue()).getBytes()); } } else if (type == DataElement.UUID) { writeByte(UUID_16); String uuid = ((UUID)data.getValue()).toString(); while (uuid.length() < 32) { uuid = '0' + uuid; } for (int i = 0; i < 16; i++) { writeByte(Integer.parseInt( uuid.substring(i * 2, i * 2 + 2), 16)); } } else { throw new RuntimeException("Unknown data type."); } } /* * Creates a data element from the binary data in the read buffer. * * @return <code>DataElement</code> read * @throws IOException if an I/O error occurs */ public DataElement readDataElement() throws IOException { byte header = readByte(); if ((header == NULL_DATA) || (header == BOOLEAN_DATA) || (header == INT1_SIGNED) || (header == INT1_UNSIGNED) || (header == INT2_SIGNED) || (header == INT2_UNSIGNED) || (header == INT4_SIGNED) || (header == INT4_UNSIGNED) || (header == INT8_SIGNED) || (header == INT8_UNSIGNED) || (header == INT16_SIGNED) || (header == INT16_UNSIGNED)) { switch (header) { case NULL_DATA: return new DataElement(DataElement.NULL); case BOOLEAN_DATA: return new DataElement(readBoolean()); case INT1_SIGNED: return new DataElement(DataElement.INT_1, readByte()); case INT1_UNSIGNED: return new DataElement(DataElement.U_INT_1, (readByte() & 0xffL)); case INT2_SIGNED: return new DataElement(DataElement.INT_2, readShort()); case INT2_UNSIGNED: return new DataElement(DataElement.U_INT_2, (readShort() & 0xffffL)); case INT4_SIGNED: return new DataElement(DataElement.INT_4, readInteger()); case INT4_UNSIGNED: return new DataElement(DataElement.U_INT_4, (readInteger() & 0xffffffffL)); case INT8_SIGNED: return new DataElement(DataElement.INT_8, readLong()); case INT8_UNSIGNED: return new DataElement(DataElement.U_INT_8, readBytes(8)); case INT16_SIGNED: return new DataElement(DataElement.INT_16, readBytes(16)); case INT16_UNSIGNED: return new DataElement(DataElement.U_INT_16, readBytes(16)); } } else if (((header & TYPE_MASK) == STRING_TYPE) || ((header & TYPE_MASK) == URL_TYPE) || ((header & TYPE_MASK) == SEQUENCE_TYPE) || ((header & TYPE_MASK) == ALTERNATIVE_TYPE)) { long size = 0; if ((header & SIZE_MASK) == SHORT_SIZE) { size = readByte() & 0xffL; } else if ((header & SIZE_MASK) == NORMAL_SIZE) { size = readShort() & 0xffffL; } else if ((header & SIZE_MASK) == LONG_SIZE) { size = readInteger() & 0xffffffffL; } else { System.err.println("Unknown size mask."); } if ((header & TYPE_MASK) == STRING_TYPE) { return new DataElement(DataElement.STRING, new String(readBytes((int)size))); } else if ((header & TYPE_MASK) == URL_TYPE) { return new DataElement(DataElement.URL, new String(readBytes((int)size))); } else { DataElement data = null; DataElement dataElement = null; long dataPos = 0; if ((header & TYPE_MASK) == SEQUENCE_TYPE) { data = new DataElement(DataElement.DATSEQ); } else { data = new DataElement(DataElement.DATALT); } while (dataPos < size) { pushReadPos(); dataElement = readDataElement(); dataPos += readPos - popReadPos(); data.addElement(dataElement); } return data; } } else if (header == UUID_2) { return new DataElement(DataElement.UUID, readUUID(2)); } else if (header == UUID_4) { return new DataElement(DataElement.UUID, readUUID(4)); } else if (header == UUID_16) { return new DataElement(DataElement.UUID, readUUID(16)); } else { throw new RuntimeException("Unknown data type."); } return null; } /* * Writes boolean data to the buffer. * Writes only value given itself. Note that boolean data header * should be written before. * * @param data boolean value to write. * @throws IOException if an I/O error occurs */ public void writeBoolean(boolean data) throws IOException { writeByte(data ? 1 : 0); } /* * Writes 1-byte data to the buffer. * * @param data byte value to write. * @throws IOException if an I/O error occurs */ public void writeByte(long data) throws IOException { if (writePos < 0 || writePos >= writeBuffer.length) { throw new IndexOutOfBoundsException(); } writeBuffer[(int)writePos++] = (byte)data; } /* * Writes 2-byte data to the buffer. * * @param data 2-byte value to write. * @throws IOException if an I/O error occurs */ public void writeShort(short data) throws IOException { writeByte((byte)((data >>> 8) & 0xff)); writeByte((byte)((data >>> 0) & 0xff)); } /* * Writes 4-byte data to the connection. * * @param data 4-byte value to write. * @throws IOException if an I/O error occurs */ public void writeInteger(int data) throws IOException { writeShort((short)((data >>> 16) & 0xffff)); writeShort((short)((data >>> 0) & 0xffff)); } /* * Writes 8-byte data to the connection. * * @param data 8-byte value to write. * @throws IOException if an I/O error occurs */ public void writeLong(long data) throws IOException { writeInteger((int)((data >>> 32) & 0xffffffff)); writeInteger((int)((data >>> 0) & 0xffffffff)); } /* * Writes given data to the connection. * * @param data bytes to write. * @throws IOException if an I/O error occurs */ public void writeBytes(byte[] data) throws IOException { if (writePos < 0 || writePos + data.length > writeBuffer.length) { throw new IndexOutOfBoundsException(); } System.arraycopy(data, 0, writeBuffer, (int)writePos, data.length); writePos += data.length; } /* * Reads boolean value from the connection. * * @return boolean value recieved. * @throws IOException if an I/O error occurs */ public boolean readBoolean() throws IOException { return (readByte() != 0); } /* * Reads 1-byte value from the connection. * * @return byte recieved. * @throws IOException if an I/O error occurs */ public byte readByte() throws IOException { if (readPos < 0 || readPos >= readBuffer.length) { throw new IndexOutOfBoundsException(); } return readBuffer[(int)readPos++]; } /* * Reads 2-byte value from the connection. * * @return short which is the 2 bytes read. * @throws IOException if an I/O error occurs */ public short readShort() throws IOException { int data1 = ((int)readByte()) & 0xff; int data2 = ((int)readByte()) & 0xff; return (short)((data1 << 8) + (data2 << 0)); } /* * Reads 4-byte value from the connection. * * @return int which is the 4 bytes read. * @throws IOException if an I/O error occurs */ public int readInteger() throws IOException { int data1 = ((int)readShort()) & 0xffff; int data2 = ((int)readShort()) & 0xffff; return ((data1 << 16) + (data2 << 0)); } /* * Reads 8-byte value from the connection. * * @return long which is the 8 bytes read. * @throws IOException if an I/O error occurs */ public long readLong() throws IOException { long data1 = ((long)readInteger()) & 0xffffffffL; long data2 = ((long)readInteger()) & 0xffffffffL; return ((data1 << 32) + (data2 << 0)); } /* * Reads given number of bytes from the connection. * * @param size number of bytes to read. * @return array of bytes read. * @throws IOException if an I/O error occurs */ public byte[] readBytes(int size) throws IOException { byte[] data = new byte[size]; int dataPos = 0; if (readPos < 0 || readPos + data.length > readBuffer.length) { throw new IndexOutOfBoundsException(); } System.arraycopy(readBuffer, (int)readPos, data, 0, data.length); readPos += data.length; return data; } /* * Reads UUID of a given size. * * @param len number of bytes to read * @return UUID created from <code>len</code> bytes * @throws IOException if an I/O error occurs */ public UUID readUUID(int len) throws IOException { String uuid = ""; for (int i = 0; i < len; i++) { String digit = Integer.toHexString(readByte() & 0xff); if (digit.length() == 1) digit = '0' + digit; uuid += digit; } return new UUID(uuid, len < 16); } /* Saves the current read position. */ private void pushReadPos() { readPosStack.push(new Long(readPos)); } /* Extracts saved read position. */ private long popReadPos() { return ((Long)readPosStack.pop()).longValue(); } }