summaryrefslogtreecommitdiffstats
path: root/obex/javax/obex/ObexHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'obex/javax/obex/ObexHelper.java')
-rw-r--r--obex/javax/obex/ObexHelper.java990
1 files changed, 990 insertions, 0 deletions
diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java
new file mode 100644
index 0000000..e6ade7b
--- /dev/null
+++ b/obex/javax/obex/ObexHelper.java
@@ -0,0 +1,990 @@
+/*
+ * 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 android.security.Md5MessageDigest;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * This class defines a set of helper methods for the implementation of Obex.
+ * @hide
+ */
+public final class ObexHelper {
+
+ /**
+ * Defines the basic packet length used by OBEX. Every OBEX packet has the
+ * same basic format:<BR>
+ * Byte 0: Request or Response Code Byte 1&2: Length of the packet.
+ */
+ public static final int BASE_PACKET_LENGTH = 3;
+
+ /** Prevent object construction of helper class */
+ private ObexHelper() {
+ }
+
+ /**
+ * The maximum packet size for OBEX packets that this client can handle. At
+ * present, this must be changed for each port. TODO: The max packet size
+ * should be the Max incoming MTU minus TODO: L2CAP package headers and
+ * RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO:
+ * LocalDevice.getProperty().
+ */
+ /*
+ * android note set as 0xFFFE to match remote MPS
+ */
+ public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
+
+ public static final int OBEX_OPCODE_CONNECT = 0x80;
+
+ public static final int OBEX_OPCODE_DISCONNECT = 0x81;
+
+ public static final int OBEX_OPCODE_PUT = 0x02;
+
+ public static final int OBEX_OPCODE_PUT_FINAL = 0x82;
+
+ public static final int OBEX_OPCODE_GET = 0x03;
+
+ public static final int OBEX_OPCODE_GET_FINAL = 0x83;
+
+ public static final int OBEX_OPCODE_RESERVED = 0x04;
+
+ public static final int OBEX_OPCODE_RESERVED_FINAL = 0x84;
+
+ public static final int OBEX_OPCODE_SETPATH = 0x85;
+
+ public static final int OBEX_OPCODE_ABORT = 0xFF;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_ASCII = 0x00;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_1 = 0x01;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_2 = 0x02;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_3 = 0x03;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_4 = 0x04;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_5 = 0x05;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_6 = 0x06;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_7 = 0x07;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_8 = 0x08;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_9 = 0x09;
+
+ public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF;
+
+ /**
+ * 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
+ * @throws 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 e;
+ }
+ break;
+
+ case HeaderSet.AUTH_CHALLENGE:
+ headerImpl.mAuthChall = new byte[length];
+ System.arraycopy(headerArray, index, headerImpl.mAuthChall, 0,
+ length);
+ break;
+
+ case HeaderSet.AUTH_RESPONSE:
+ headerImpl.mAuthResp = new byte[length];
+ System.arraycopy(headerArray, index, headerImpl.mAuthResp, 0,
+ length);
+ break;
+
+ case HeaderSet.BODY:
+ /* Fall Through */
+ case HeaderSet.END_OF_BODY:
+ 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 e;
+ }
+ break;
+
+ default:
+ if ((headerID & 0xC0) == 0x00) {
+ headerImpl.setHeader(headerID, ObexHelper.convertToUnicode(
+ value, true));
+ } else {
+ headerImpl.setHeader(headerID, value);
+ }
+ }
+
+ 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 == HeaderSet.CONNECTION_ID) {
+ headerImpl.mConnectionID = new byte[4];
+ System.arraycopy(value, 0, headerImpl.mConnectionID, 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.
+ * TODO: Could use getHeaderList() to get the array of headers to include
+ * and then use the high two bits to determine the the type of the object
+ * and construct the byte array from 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();
+ 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.mConnectionID != null) && (headImpl.getHeader(HeaderSet.TARGET) == null)) {
+
+ out.write((byte)HeaderSet.CONNECTION_ID);
+ out.write(headImpl.mConnectionID);
+ }
+
+ // 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)(0xFF & (length >> 8));
+ lengthArray[1] = (byte)(0xFF & 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 e;
+ }
+
+ 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 e;
+ }
+
+ 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.mAuthChall != null) {
+ out.write((byte)HeaderSet.AUTH_CHALLENGE);
+ length = headImpl.mAuthChall.length + 3;
+ lengthArray[0] = (byte)(255 & (length >> 8));
+ lengthArray[1] = (byte)(255 & length);
+ out.write(lengthArray);
+ out.write(headImpl.mAuthChall);
+ if (nullOut) {
+ headImpl.mAuthChall = null;
+ }
+ }
+
+ // Add the authentication response header
+ if (headImpl.mAuthResp != null) {
+ out.write((byte)HeaderSet.AUTH_RESPONSE);
+ length = headImpl.mAuthResp.length + 3;
+ lengthArray[0] = (byte)(255 & (length >> 8));
+ lengthArray[1] = (byte)(255 & length);
+ out.write(lengthArray);
+ out.write(headImpl.mAuthResp);
+ if (nullOut) {
+ headImpl.mAuthResp = 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
+ * @throws 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);
+ }
+
+ /**
+ * Compute the MD5 hash of the byte array provided. Does not accumulate
+ * input.
+ * @param in the byte array to hash
+ * @return the MD5 hash of the byte array
+ */
+ public static byte[] computeMd5Hash(byte[] in) {
+ Md5MessageDigest md5 = new Md5MessageDigest();
+ return md5.digest(in);
+ }
+
+ /**
+ * 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
+ * @throws IllegalArgumentException if the challenge is not 16 bytes long;
+ * if the realm can not be encoded in less then 255 bytes
+ * @throws 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;
+ }
+}