/* * 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.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; 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:
* 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; /** * Temporary workaround to be able to push files to Windows 7. * TODO: Should be removed as soon as Microsoft updates their driver. */ public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00; 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. *

* 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. * * * * * * * * * * * * * * * * * * * * * * * * * * *
Bits 8 and 7ValueDescription
000x00Null Terminated Unicode text, prefixed with 2 byte unsigned integer
010x40Byte Sequence, length prefixed with 2 byte unsigned integer
100x801 byte quantity
110xC04 byte quantity - transmitted in network byte order (high byte first
* 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 true if the header should be set to * null once it is added to the array or * false 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 || b.length == 0) { 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; } // If upper and lower both equal 0, it should be the end of string. // Ignore left bytes from array to avoid potential issues if (upper == 0 && lower == 0) { return new String(c, 0, i); } 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) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5.digest(in); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } /** * 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 true then full access will be granted if * successful; if false then read only access will be * granted if successful * @param userID if true, a user ID is required in the reply; * if false, 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; } }