diff options
Diffstat (limited to 'obex/javax/obex/OBEXHelper.java')
-rw-r--r-- | obex/javax/obex/OBEXHelper.java | 1326 |
1 files changed, 1326 insertions, 0 deletions
diff --git a/obex/javax/obex/OBEXHelper.java b/obex/javax/obex/OBEXHelper.java new file mode 100644 index 0000000..5c413af --- /dev/null +++ b/obex/javax/obex/OBEXHelper.java @@ -0,0 +1,1326 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package javax.obex; + +import java.io.*; +import java.util.*; + +/** + * This class defines a set of helper methods for the implementation of OBEX. + * + * @version 0.3 November 28, 2008 + */ +public class OBEXHelper { + + /** + * Creates new OBEXHelper + */ + private OBEXHelper() { + } + + private static final String TAG = "OBEXHelper"; + + /** + * Updates the HeaderSet with the headers received in the byte array + * provided. Invalid headers are ignored. + * <P> + * The first two bits of an OBEX Header specifies the type of object that + * is being sent. The table below specifies the meaning of the high + * bits. + * <TABLE> + * <TR><TH>Bits 8 and 7</TH><TH>Value</TH><TH>Description</TH></TR> + * <TR><TD>00</TD><TD>0x00</TD><TD>Null Terminated Unicode text, prefixed + * with 2 byte unsigned integer</TD></TR> + * <TR><TD>01</TD><TD>0x40</TD><TD>Byte Sequence, length prefixed with + * 2 byte unsigned integer</TD></TR> + * <TR><TD>10</TD><TD>0x80</TD><TD>1 byte quantity</TD></TR> + * <TR><TD>11</TD><TD>0xC0</TD><TD>4 byte quantity - transmitted in + * network byte order (high byte first</TD></TR> + * </TABLE> + * This method uses the information in this table to determine the type of + * Java object to create and passes that object with the full header + * to setHeader() to update the HeaderSet object. Invalid headers will + * cause an exception to be thrown. When it is thrown, it is ignored. + * + * @param header the HeaderSet to update + * + * @param headerArray the byte array containing headers + * + * @return the result of the last start body or end body header provided; + * the first byte in the result will specify if a body or end of body is + * received + * + * @exception IOException if an invalid header was found + */ + public static byte[] updateHeaderSet(HeaderSet header, byte[] headerArray) throws IOException { + int index = 0; + int length = 0; + int headerID; + byte[] value = null; + byte[] body = null; + HeaderSet headerImpl = header; + try { + while (index < headerArray.length) { + headerID = 0xFF & headerArray[index]; + switch (headerID & (0xC0)) { + + /* + * 0x00 is a unicode null terminate string with the first + * two bytes after the header identifier being the length + */ + case 0x00: + // Fall through + /* + * 0x40 is a byte sequence with the first + * two bytes after the header identifier being the length + */ + case 0x40: + boolean trimTail = true; + index++; + length = 0xFF & headerArray[index]; + length = length << 8; + index++; + length += 0xFF & headerArray[index]; + length -= 3; + index++; + value = new byte[length]; + System.arraycopy(headerArray, index, value, 0, length); + if (length == 0 || (length > 0 && (value[length - 1] != 0))) { + trimTail = false; + } + switch (headerID) { + case HeaderSet.TYPE: + try { + // Remove trailing null + if (trimTail == false) { + headerImpl.setHeader(headerID, new String(value, 0, + value.length, "ISO8859_1")); + } else { + headerImpl.setHeader(headerID, new String(value, 0, + value.length - 1, "ISO8859_1")); + } + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("ISO8859_1 is not supported" + + e.getMessage()); + } + break; + + // This is the constant for the authentication challenge header + // This header does not have a constant defined in the Java + // OBEX API + case 0x4D: + headerImpl.authChall = new byte[length]; + System.arraycopy(headerArray, index, headerImpl.authChall, 0, + length); + break; + + // This is the constant for the authentication response header + // This header does not have a constant defined in the Java + // OBEX API + case 0x4E: + headerImpl.authResp = new byte[length]; + System + .arraycopy(headerArray, index, headerImpl.authResp, 0, + length); + break; + + /* + * These two case statements are for the body (0x48) + * and end of body (0x49) headers. + */ + case 0x48: + /* Fall Through */ + case 0x49: + body = new byte[length + 1]; + body[0] = (byte)headerID; + System.arraycopy(headerArray, index, body, 1, length); + break; + + case HeaderSet.TIME_ISO_8601: + try { + String dateString = new String(value, "ISO8859_1"); + Calendar temp = Calendar.getInstance(); + if ((dateString.length() == 16) + && (dateString.charAt(15) == 'Z')) { + temp.setTimeZone(TimeZone.getTimeZone("UTC")); + } + temp.set(Calendar.YEAR, Integer.parseInt(dateString.substring( + 0, 4))); + temp.set(Calendar.MONTH, Integer.parseInt(dateString.substring( + 4, 6))); + temp.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateString + .substring(6, 8))); + temp.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateString + .substring(9, 11))); + temp.set(Calendar.MINUTE, Integer.parseInt(dateString + .substring(11, 13))); + temp.set(Calendar.SECOND, Integer.parseInt(dateString + .substring(13, 15))); + headerImpl.setHeader(HeaderSet.TIME_ISO_8601, temp); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("ISO8859_1 is not supported" + + e.getMessage()); + } catch (Exception e) { + throw new IOException( + "Time Header does not follow ISO 8601 standard"); + } + break; + + default: + try { + if ((headerID & 0xC0) == 0x00) { + headerImpl.setHeader(headerID, OBEXHelper.convertToUnicode( + value, true)); + } else { + headerImpl.setHeader(headerID, value); + } + } catch (Exception e) { + // Not a valid header so ignore + } + } + + index += length; + break; + + /* + * 0x80 is a byte header. The only valid byte headers are + * the 16 user defined byte headers. + */ + case 0x80: + index++; + try { + headerImpl.setHeader(headerID, Byte.valueOf(headerArray[index])); + } catch (Exception e) { + // Not a valid header so ignore + } + index++; + break; + + /* + * 0xC0 is a 4 byte unsigned integer header and with the + * exception of TIME_4_BYTE will be converted to a Long + * and added. + */ + case 0xC0: + index++; + value = new byte[4]; + System.arraycopy(headerArray, index, value, 0, 4); + try { + if (headerID != HeaderSet.TIME_4_BYTE) { + // Determine if it is a connection ID. These + // need to be handled differently + if (headerID == 0xCB) { + headerImpl.connectionID = new byte[4]; + System.arraycopy(value, 0, headerImpl.connectionID, 0, 4); + } else { + headerImpl.setHeader(headerID, Long + .valueOf(convertToLong(value))); + } + } else { + Calendar temp = Calendar.getInstance(); + temp.setTime(new Date(convertToLong(value) * 1000L)); + headerImpl.setHeader(HeaderSet.TIME_4_BYTE, temp); + } + } catch (Exception e) { + // Not a valid header so ignore + throw new IOException("Header was not formatted properly"); + } + index += 4; + break; + } + + } + } catch (IOException e) { + throw new IOException("Header was not formatted properly"); + } + + return body; + } + + /** + * Creates the header part of OBEX packet based on the header provided. + * + * OPTIMIZATION: Could use getHeaderList() to get the array of headers to + * OPTIMIZATION: include and then use the high two bits to determine the + * OPTIMIZATION: the type of the object and construct the byte array from + * OPTIMIZATION: that. This will make the size smaller. + * + * @param head the header used to construct the byte array + * + * @param nullOut <code>true</code> if the header should be set to + * <code>null</code> once it is added to the array or <code>false</code> + * if it should not be nulled out + * + * @return the header of an OBEX packet + */ + public static byte[] createHeader(HeaderSet head, boolean nullOut) { + Long intHeader = null; + String stringHeader = null; + Calendar dateHeader = null; + Byte byteHeader = null; + StringBuffer buffer = null; + byte[] value = null; + byte[] result = null; + byte[] lengthArray = new byte[2]; + int length; + HeaderSet headImpl = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + if (!(head instanceof HeaderSet)) { + throw new IllegalArgumentException("Header not created by createHeaderSet"); + } + headImpl = head; + + try { + /* + * Determine if there is a connection ID to send. If there is, + * then it should be the first header in the packet. + */ + if ((headImpl.connectionID != null) && (headImpl.getHeader(HeaderSet.TARGET) == null)) { + + out.write((byte)0xCB); + out.write(headImpl.connectionID); + } + + // Count Header + intHeader = (Long)headImpl.getHeader(HeaderSet.COUNT); + if (intHeader != null) { + out.write((byte)HeaderSet.COUNT); + value = OBEXHelper.convertToByteArray(intHeader.longValue()); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.COUNT, null); + } + } + + // Name Header + stringHeader = (String)headImpl.getHeader(HeaderSet.NAME); + if (stringHeader != null) { + out.write((byte)HeaderSet.NAME); + value = OBEXHelper.convertToUnicodeByteArray(stringHeader); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.NAME, null); + } + } + + // Type Header + stringHeader = (String)headImpl.getHeader(HeaderSet.TYPE); + if (stringHeader != null) { + out.write((byte)HeaderSet.TYPE); + try { + value = stringHeader.getBytes("ISO8859_1"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Unsupported Encoding Scheme: " + e.getMessage()); + } + + length = value.length + 4; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + out.write(0x00); + if (nullOut) { + headImpl.setHeader(HeaderSet.TYPE, null); + } + } + + // Length Header + intHeader = (Long)headImpl.getHeader(HeaderSet.LENGTH); + if (intHeader != null) { + out.write((byte)HeaderSet.LENGTH); + value = OBEXHelper.convertToByteArray(intHeader.longValue()); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.LENGTH, null); + } + } + + // Time ISO Header + dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_ISO_8601); + if (dateHeader != null) { + + /* + * The ISO Header should take the form YYYYMMDDTHHMMSSZ. The + * 'Z' will only be included if it is a UTC time. + */ + buffer = new StringBuffer(); + int temp = dateHeader.get(Calendar.YEAR); + for (int i = temp; i < 1000; i = i * 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.MONTH); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.DAY_OF_MONTH); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + buffer.append("T"); + temp = dateHeader.get(Calendar.HOUR_OF_DAY); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.MINUTE); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.SECOND); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + + if (dateHeader.getTimeZone().getID().equals("UTC")) { + buffer.append("Z"); + } + + try { + value = buffer.toString().getBytes("ISO8859_1"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UnsupportedEncodingException: " + e.getMessage()); + } + + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(HeaderSet.TIME_ISO_8601); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.TIME_ISO_8601, null); + } + } + + // Time 4 Byte Header + dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_4_BYTE); + if (dateHeader != null) { + out.write(HeaderSet.TIME_4_BYTE); + + /* + * Need to call getTime() twice. The first call will return + * a java.util.Date object. The second call returns the number + * of milliseconds since January 1, 1970. We need to convert + * it to seconds since the TIME_4_BYTE expects the number of + * seconds since January 1, 1970. + */ + value = OBEXHelper.convertToByteArray(dateHeader.getTime().getTime() / 1000L); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.TIME_4_BYTE, null); + } + } + + // Description Header + stringHeader = (String)headImpl.getHeader(HeaderSet.DESCRIPTION); + if (stringHeader != null) { + out.write((byte)HeaderSet.DESCRIPTION); + value = OBEXHelper.convertToUnicodeByteArray(stringHeader); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.DESCRIPTION, null); + } + } + + // Target Header + value = (byte[])headImpl.getHeader(HeaderSet.TARGET); + if (value != null) { + out.write((byte)HeaderSet.TARGET); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.TARGET, null); + } + } + + // HTTP Header + value = (byte[])headImpl.getHeader(HeaderSet.HTTP); + if (value != null) { + out.write((byte)HeaderSet.HTTP); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.HTTP, null); + } + } + + // Who Header + value = (byte[])headImpl.getHeader(HeaderSet.WHO); + if (value != null) { + out.write((byte)HeaderSet.WHO); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.WHO, null); + } + } + + // Connection ID Header + value = (byte[])headImpl.getHeader(HeaderSet.APPLICATION_PARAMETER); + if (value != null) { + out.write((byte)HeaderSet.APPLICATION_PARAMETER); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.APPLICATION_PARAMETER, null); + } + } + + // Object Class Header + value = (byte[])headImpl.getHeader(HeaderSet.OBJECT_CLASS); + if (value != null) { + out.write((byte)HeaderSet.OBJECT_CLASS); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.OBJECT_CLASS, null); + } + } + + // Check User Defined Headers + for (int i = 0; i < 16; i++) { + + //Unicode String Header + stringHeader = (String)headImpl.getHeader(i + 0x30); + if (stringHeader != null) { + out.write((byte)i + 0x30); + value = OBEXHelper.convertToUnicodeByteArray(stringHeader); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(i + 0x30, null); + } + } + + // Byte Sequence Header + value = (byte[])headImpl.getHeader(i + 0x70); + if (value != null) { + out.write((byte)i + 0x70); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(i + 0x70, null); + } + } + + // Byte Header + byteHeader = (Byte)headImpl.getHeader(i + 0xB0); + if (byteHeader != null) { + out.write((byte)i + 0xB0); + out.write(byteHeader.byteValue()); + if (nullOut) { + headImpl.setHeader(i + 0xB0, null); + } + } + + // Integer header + intHeader = (Long)headImpl.getHeader(i + 0xF0); + if (intHeader != null) { + out.write((byte)i + 0xF0); + out.write(OBEXHelper.convertToByteArray(intHeader.longValue())); + if (nullOut) { + headImpl.setHeader(i + 0xF0, null); + } + } + } + + // Add the authentication challenge header + if (headImpl.authChall != null) { + out.write((byte)0x4D); + length = headImpl.authChall.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(headImpl.authChall); + if (nullOut) { + headImpl.authChall = null; + } + } + + // Add the authentication response header + if (headImpl.authResp != null) { + out.write((byte)0x4E); + length = headImpl.authResp.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(headImpl.authResp); + if (nullOut) { + headImpl.authResp = null; + } + } + + } catch (IOException e) { + } finally { + result = out.toByteArray(); + try { + out.close(); + } catch (Exception ex) { + } + } + + return result; + + } + + /** + * Determines where the maximum divide is between headers. This method is + * used by put and get operations to separate headers to a size that meets + * the max packet size allowed. + * + * @param headerArray the headers to separate + * + * @param start the starting index to search + * + * @param maxSize the maximum size of a packet + * + * @return the index of the end of the header block to send or -1 if the + * header could not be divided because the header is too large + */ + public static int findHeaderEnd(byte[] headerArray, int start, int maxSize) { + + int fullLength = 0; + int lastLength = -1; + int index = start; + int length = 0; + + while ((fullLength < maxSize) && (index < headerArray.length)) { + int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]); + lastLength = fullLength; + + switch (headerID & (0xC0)) { + + case 0x00: + // Fall through + case 0x40: + + index++; + length = (headerArray[index] < 0 ? headerArray[index] + 256 + : headerArray[index]); + length = length << 8; + index++; + length += (headerArray[index] < 0 ? headerArray[index] + 256 + : headerArray[index]); + length -= 3; + index++; + index += length; + fullLength += length + 3; + break; + + case 0x80: + + index++; + index++; + fullLength += 2; + break; + + case 0xC0: + + index += 5; + fullLength += 5; + break; + + } + + } + + /* + * Determine if this is the last header or not + */ + if (lastLength == 0) { + /* + * Since this is the last header, check to see if the size of this + * header is less then maxSize. If it is, return the length of the + * header, otherwise return -1. The length of the header is + * returned since it would be the start of the next header + */ + if (fullLength < maxSize) { + return headerArray.length; + } else { + return -1; + } + } else { + return lastLength + start; + } + } + + /** + * Converts the byte array to a long. + * + * @param b the byte array to convert to a long + * + * @return the byte array as a long + */ + public static long convertToLong(byte[] b) { + long result = 0; + long value = 0; + long power = 0; + + for (int i = (b.length - 1); i >= 0; i--) { + value = b[i]; + if (value < 0) { + value += 256; + } + + result = result | (value << power); + power += 8; + } + + return result; + } + + /** + * Converts the long to a 4 byte array. The long must be non negative. + * + * @param l the long to convert + * + * @return a byte array that is the same as the long + */ + public static byte[] convertToByteArray(long l) { + byte[] b = new byte[4]; + + b[0] = (byte)(255 & (l >> 24)); + b[1] = (byte)(255 & (l >> 16)); + b[2] = (byte)(255 & (l >> 8)); + b[3] = (byte)(255 & l); + + return b; + } + + /** + * Converts the String to a UNICODE byte array. It will also add the ending + * null characters to the end of the string. + * + * @param s the string to convert + * + * @return the unicode byte array of the string + */ + public static byte[] convertToUnicodeByteArray(String s) { + if (s == null) { + return null; + } + + char c[] = s.toCharArray(); + byte[] result = new byte[(c.length * 2) + 2]; + for (int i = 0; i < c.length; i++) { + result[(i * 2)] = (byte)(c[i] >> 8); + result[((i * 2) + 1)] = (byte)c[i]; + } + + // Add the UNICODE null character + result[result.length - 2] = 0; + result[result.length - 1] = 0; + + return result; + } + + /** + * Retrieves the value from the byte array for the tag value specified. The + * array should be of the form Tag - Length - Value triplet. + * + * @param tag the tag to retrieve from the byte array + * + * @param triplet the byte sequence containing the tag length value form + * + * @return the value of the specified tag + */ + public static byte[] getTagValue(byte tag, byte[] triplet) { + + int index = findTag(tag, triplet); + if (index == -1) { + return null; + } + + index++; + int length = triplet[index] & 0xFF; + + byte[] result = new byte[length]; + index++; + System.arraycopy(triplet, index, result, 0, length); + + return result; + } + + /** + * Finds the index that starts the tag value pair in the byte array provide. + * + * @param tag the tag to look for + * + * @param value the byte array to search + * + * @return the starting index of the tag or -1 if the tag could not be found + */ + public static int findTag(byte tag, byte[] value) { + int length = 0; + + if (value == null) { + return -1; + } + + int index = 0; + + while ((index < value.length) && (value[index] != tag)) { + length = value[index + 1] & 0xFF; + index += length + 2; + } + + if (index >= value.length) { + return -1; + } + + return index; + } + + /** + * Converts the byte array provided to a unicode string. + * + * @param b the byte array to convert to a string + * + * @param includesNull determine if the byte string provided contains the + * UNICODE null character at the end or not; if it does, it will be + * removed + * + * @return a Unicode string + * + * @param IllegalArgumentException if the byte array has an odd length + */ + public static String convertToUnicode(byte[] b, boolean includesNull) { + if (b == null) { + return null; + } + int arrayLength = b.length; + if (!((arrayLength % 2) == 0)) { + throw new IllegalArgumentException("Byte array not of a valid form"); + } + arrayLength = (arrayLength >> 1); + if (includesNull) { + arrayLength -= 1; + } + + char[] c = new char[arrayLength]; + for (int i = 0; i < arrayLength; i++) { + int upper = b[2 * i]; + int lower = b[(2 * i) + 1]; + if (upper < 0) { + upper += 256; + } + if (lower < 0) { + lower += 256; + } + + c[i] = (char)((upper << 8) | lower); + } + + return new String(c); + } + + /** + * Computes the MD5 hash algorithm on the byte array provided. This + * implementation of MD5 is optimized for OBEX in that it provides for a + * single entry and exist and thus does not build up the input before + * applying the hash. + * + * OPTIMIZATION: Embedd MD5 algorithm in this method. This will make the + * OPTIMIZATION: size smaller. + * + * @param in the byte array to hash + * + * @return the MD5 hash of the byte array + */ + public static byte[] computeMD5Hash(byte[] in) { + + MD5Hash hash = new MD5Hash(in); + return hash.computeDigest(); + } + + /** + * Computes an authentication challenge header. + * + * + * @param nonce the challenge that will be provided to the peer; the + * challenge must be 16 bytes long + * + * @param realm a short description that describes what password to use + * + * @param access if <code>true</code> then full access will be granted if + * successful; if <code>false</code> then read only access will be granted + * if successful + * + * @param userID if <code>true</code>, a user ID is required in the reply; + * if <code>false</code>, no user ID is required + * + * @exception IllegalArgumentException if the challenge is not 16 bytes + * long; if the realm can not be encoded in less then 255 bytes + * + * @exception IOException if the encoding scheme ISO 8859-1 is not supported + */ + public static byte[] computeAuthenticationChallenge(byte[] nonce, String realm, boolean access, + boolean userID) throws IOException { + byte[] authChall = null; + + if (nonce.length != 16) { + throw new IllegalArgumentException("Nonce must be 16 bytes long"); + } + + /* + * The authentication challenge is a byte sequence of the following form + * byte 0: 0x00 - the tag for the challenge + * byte 1: 0x10 - the length of the challenge; must be 16 + * byte 2-17: the authentication challenge + * byte 18: 0x01 - the options tag; this is optional in the spec, but + * we are going to include it in every message + * byte 19: 0x01 - length of the options; must be 1 + * byte 20: the value of the options; bit 0 is set if user ID is + * required; bit 1 is set if access mode is read only + * byte 21: 0x02 - the tag for authentication realm; only included if + * an authentication realm is specified + * byte 22: the length of the authentication realm; only included if + * the authentication realm is specified + * byte 23: the encoding scheme of the authentication realm; we will use + * the ISO 8859-1 encoding scheme since it is part of the KVM + * byte 24 & up: the realm if one is specified. + */ + if (realm == null) { + authChall = new byte[21]; + } else { + if (realm.length() >= 255) { + throw new IllegalArgumentException("Realm must be less then 255 bytes"); + } + authChall = new byte[24 + realm.length()]; + authChall[21] = 0x02; + authChall[22] = (byte)(realm.length() + 1); + authChall[23] = 0x01; // ISO 8859-1 Encoding + System.arraycopy(realm.getBytes("ISO8859_1"), 0, authChall, 24, realm.length()); + } + + // Include the nonce field in the header + authChall[0] = 0x00; + authChall[1] = 0x10; + System.arraycopy(nonce, 0, authChall, 2, 16); + + // Include the options header + authChall[18] = 0x01; + authChall[19] = 0x01; + authChall[20] = 0x00; + + if (!access) { + authChall[20] = (byte)(authChall[20] | 0x02); + } + if (userID) { + authChall[20] = (byte)(authChall[20] | 0x01); + } + + return authChall; + } +} + +/** + * This class will complete an MD5 hash of the buffer provided. + */ +class MD5Hash { + + // Constants for MD5Transform routine. + private static final int A1 = 7; + + private static final int A2 = 12; + + private static final int A3 = 17; + + private static final int A4 = 22; + + private static final int B1 = 5; + + private static final int B2 = 9; + + private static final int B3 = 14; + + private static final int B4 = 20; + + private static final int C1 = 4; + + private static final int C2 = 11; + + private static final int C3 = 16; + + private static final int C4 = 23; + + private static final int D1 = 6; + + private static final int D2 = 10; + + private static final int D3 = 15; + + private static final int D4 = 21; + + private int state[]; + + private int count[]; + + /** + * Keeps the present digest + */ + private byte buffer[]; + + /** + * Completes a hash on the data provided. + * + * @param data the byte array to hash + */ + public MD5Hash(byte data[]) { + buffer = new byte[64]; + state = new int[4]; + count = new int[2]; + + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; + + count[0] = 0; + count[1] = 0; + + MD5Update(data, 0, data.length); + } + + /** + * Updates the MD5 hash buffer. + * + * @param input byte array of data + * + * @param offset offset into the array to start the digest calculation + * + * @param inputLen the length of the byte array + */ + private void MD5Update(byte input[], int offset, int inputLen) { + int i, index, partLen; + + // Compute number of bytes mod 64 + index = (count[0] >>> 3) & 0x3F; + + // Update number of bits + int slen = inputLen << 3; + if ((count[0] += slen) < slen) { + count[1]++; + } + + count[1] += (inputLen >>> 29); + + partLen = 64 - index; + // Transform as many times as possible. + if (inputLen >= partLen) { + copy(index, input, offset, partLen); + + MD5Transform(buffer, 0, 0); + + for (i = partLen; i + 63 < inputLen; i += 64) { + MD5Transform(input, offset, i); + } + + index = 0; + } else { + i = 0; + } + + copy(index, input, i + offset, inputLen - i); + } + + /** + * Computes the final MD5 digest + * + * @return the MD5 digest + */ + public byte[] computeDigest() { + byte bits[]; + byte digest[]; + int index; + int length; + + // Save number of bits + bits = MD5Encode(count, 8); + + // Pad out to 56 mod 64. + index = ((count[0] >>> 3) & 0x3f); + length = (index < 56) ? (56 - index) : (120 - index); + + // build padding buffer. + if (length > 0) { + byte padding[] = new byte[length]; + padding[0] = (byte)0x80; + for (int i = 1; i < length; i++) { + padding[i] = 0; + } + + MD5Update(padding, 0, length); + } + + // Append length + MD5Update(bits, 0, 8); + + // Store state in digest + digest = MD5Encode(state, 16); + + return digest; + } + + /** + * Copies the input array into the local objects buffer. It performs a + * check to verify that data is available to copy. + * + * @param startIndex the offset into the local buffer to copy + * + * @param input the array to copy into the local buffer + * + * @param index the index to start copying from + * + * @param length the amount of data to copy + */ + private void copy(int startIndex, byte input[], int index, int length) { + if (index == input.length) + return; + System.arraycopy(input, index, buffer, startIndex, length); + } + + /** + * Rotates the bytes in <code>x</code> <code>n</code> times to the left. + * The rotation wraps the bits around. + * + * @param x the integer to rotate + * + * @param n the number of bits to rotate + * + * @return <code>x</code> rotated to the left <code>n</code> times + */ + private int rotate(int x, int n) { + return (((x) << (n)) | ((x) >>> (32 - (n)))); + } + + /** + * Completes a single step in the MD5 hash algorithm. + */ + private int MD5Step(int a, int b, int c, int d, int x, int s, int ac, int round) { + switch (round) { + case 1: + a += (((b) & (c)) | ((~b) & (d))) + (x) + (ac); + break; + case 2: + a += (((b) & (d)) | ((c) & (~d))) + (x) + (ac); + break; + case 3: + a += ((b) ^ (c) ^ (d)) + (x) + (ac); + break; + case 4: + a += ((c) ^ ((b) | (~d))) + (x) + (ac); + break; + } + a = rotate(a, (s)); + a += (b); + return a; + } + + /** + * Performs the core MD5 algorithm. This method will add the data provided + * and recompute the hash. + * + * @param data the block of data to add + * + * @param index the starting index into the data to start processing + * + * @param length the length of the byte array to process + */ + private void MD5Transform(byte data[], int index, int length) { + int a = state[0]; + int b = state[1]; + int c = state[2]; + int d = state[3]; + int x[]; + + x = MD5Decode(data, index, length, 64); + + // Round 1 + a = MD5Step(a, b, c, d, x[0], A1, 0xd76aa478, 1); + d = MD5Step(d, a, b, c, x[1], A2, 0xe8c7b756, 1); + c = MD5Step(c, d, a, b, x[2], A3, 0x242070db, 1); + b = MD5Step(b, c, d, a, x[3], A4, 0xc1bdceee, 1); + a = MD5Step(a, b, c, d, x[4], A1, 0xf57c0faf, 1); + d = MD5Step(d, a, b, c, x[5], A2, 0x4787c62a, 1); + c = MD5Step(c, d, a, b, x[6], A3, 0xa8304613, 1); + b = MD5Step(b, c, d, a, x[7], A4, 0xfd469501, 1); + a = MD5Step(a, b, c, d, x[8], A1, 0x698098d8, 1); + d = MD5Step(d, a, b, c, x[9], A2, 0x8b44f7af, 1); + c = MD5Step(c, d, a, b, x[10], A3, 0xffff5bb1, 1); + b = MD5Step(b, c, d, a, x[11], A4, 0x895cd7be, 1); + a = MD5Step(a, b, c, d, x[12], A1, 0x6b901122, 1); + d = MD5Step(d, a, b, c, x[13], A2, 0xfd987193, 1); + c = MD5Step(c, d, a, b, x[14], A3, 0xa679438e, 1); + b = MD5Step(b, c, d, a, x[15], A4, 0x49b40821, 1); + + // Round 2 + a = MD5Step(a, b, c, d, x[1], B1, 0xf61e2562, 2); + d = MD5Step(d, a, b, c, x[6], B2, 0xc040b340, 2); + c = MD5Step(c, d, a, b, x[11], B3, 0x265e5a51, 2); + b = MD5Step(b, c, d, a, x[0], B4, 0xe9b6c7aa, 2); + a = MD5Step(a, b, c, d, x[5], B1, 0xd62f105d, 2); + d = MD5Step(d, a, b, c, x[10], B2, 0x2441453, 2); + c = MD5Step(c, d, a, b, x[15], B3, 0xd8a1e681, 2); + b = MD5Step(b, c, d, a, x[4], B4, 0xe7d3fbc8, 2); + a = MD5Step(a, b, c, d, x[9], B1, 0x21e1cde6, 2); + d = MD5Step(d, a, b, c, x[14], B2, 0xc33707d6, 2); + c = MD5Step(c, d, a, b, x[3], B3, 0xf4d50d87, 2); + b = MD5Step(b, c, d, a, x[8], B4, 0x455a14ed, 2); + a = MD5Step(a, b, c, d, x[13], B1, 0xa9e3e905, 2); + d = MD5Step(d, a, b, c, x[2], B2, 0xfcefa3f8, 2); + c = MD5Step(c, d, a, b, x[7], B3, 0x676f02d9, 2); + b = MD5Step(b, c, d, a, x[12], B4, 0x8d2a4c8a, 2); + + // Round 3 + a = MD5Step(a, b, c, d, x[5], C1, 0xfffa3942, 3); + d = MD5Step(d, a, b, c, x[8], C2, 0x8771f681, 3); + c = MD5Step(c, d, a, b, x[11], C3, 0x6d9d6122, 3); + b = MD5Step(b, c, d, a, x[14], C4, 0xfde5380c, 3); + a = MD5Step(a, b, c, d, x[1], C1, 0xa4beea44, 3); + d = MD5Step(d, a, b, c, x[4], C2, 0x4bdecfa9, 3); + c = MD5Step(c, d, a, b, x[7], C3, 0xf6bb4b60, 3); + b = MD5Step(b, c, d, a, x[10], C4, 0xbebfbc70, 3); + a = MD5Step(a, b, c, d, x[13], C1, 0x289b7ec6, 3); + d = MD5Step(d, a, b, c, x[0], C2, 0xeaa127fa, 3); + c = MD5Step(c, d, a, b, x[3], C3, 0xd4ef3085, 3); + b = MD5Step(b, c, d, a, x[6], C4, 0x4881d05, 3); + a = MD5Step(a, b, c, d, x[9], C1, 0xd9d4d039, 3); + d = MD5Step(d, a, b, c, x[12], C2, 0xe6db99e5, 3); + c = MD5Step(c, d, a, b, x[15], C3, 0x1fa27cf8, 3); + b = MD5Step(b, c, d, a, x[2], C4, 0xc4ac5665, 3); + + // Round 4 + a = MD5Step(a, b, c, d, x[0], D1, 0xf4292244, 4); + d = MD5Step(d, a, b, c, x[7], D2, 0x432aff97, 4); + c = MD5Step(c, d, a, b, x[14], D3, 0xab9423a7, 4); + b = MD5Step(b, c, d, a, x[5], D4, 0xfc93a039, 4); + a = MD5Step(a, b, c, d, x[12], D1, 0x655b59c3, 4); + d = MD5Step(d, a, b, c, x[3], D2, 0x8f0ccc92, 4); + c = MD5Step(c, d, a, b, x[10], D3, 0xffeff47d, 4); + b = MD5Step(b, c, d, a, x[1], D4, 0x85845dd1, 4); + a = MD5Step(a, b, c, d, x[8], D1, 0x6fa87e4f, 4); + d = MD5Step(d, a, b, c, x[15], D2, 0xfe2ce6e0, 4); + c = MD5Step(c, d, a, b, x[6], D3, 0xa3014314, 4); + b = MD5Step(b, c, d, a, x[13], D4, 0x4e0811a1, 4); + a = MD5Step(a, b, c, d, x[4], D1, 0xf7537e82, 4); + d = MD5Step(d, a, b, c, x[11], D2, 0xbd3af235, 4); + c = MD5Step(c, d, a, b, x[2], D3, 0x2ad7d2bb, 4); + b = MD5Step(b, c, d, a, x[9], D4, 0xeb86d391, 4); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + } + + /** + * Encodes the input array. <code>input</code> must be a multiple of + * four. + * + * @param input the array to encode + * + * @param length the length of the array to encode + */ + private byte[] MD5Encode(int input[], int length) { + int i, j; + byte output[] = new byte[length]; + + for (i = 0, j = 0; j < length; i++, j += 4) { + output[j] = (byte)(input[i] & 0xff); + output[j + 1] = (byte)((input[i] >>> 8) & 0xff); + output[j + 2] = (byte)((input[i] >>> 16) & 0xff); + output[j + 3] = (byte)((input[i] >>> 24) & 0xff); + } + + return output; + } + + /** + * Decodes the array. The <code>input</code> array must be a multiple of + * four. Results are undefined if this is not true. + * + * @param input the array to decode + * + * @param index the starting position into the input array to start decoding + * + * @param posn + * + * @param length + */ + private int[] MD5Decode(byte input[], int offset, int posn, int length) { + int output[] = new int[length / 4]; + int i, j; + int limit = length + posn + offset; + + for (i = 0, j = offset + posn; j < limit; i++, j += 4) + output[i] = ((input[j]) & 0xff) | (((input[j + 1]) & 0xff) << 8) + | (((input[j + 2]) & 0xff) << 16) | (((input[j + 3]) & 0xff) << 24); + + return output; + } +} |