blob: 0a06709548fe04c8f95d955085eab84e603e75c8 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
* 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:<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;
/**
* 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.
* <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);
}
} else if (headImpl.getEmptyNameHeader()) {
out.write((byte) HeaderSet.NAME);
lengthArray[0] = (byte) 0x00;
lengthArray[1] = (byte) 0x03;
out.write(lengthArray);
}
// 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 <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;
}
}