fc6032f106
max allowed packet size Tag: #feature Bug: 187174891 Test: Manual Change-Id: I80422e03d14958f2ea95327f95d010c747948e2c
1101 lines
42 KiB
Java
1101 lines
42 KiB
Java
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
* Copyright (C) 2015 Samsung LSI
|
|
* 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.util.Log;
|
|
|
|
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 {
|
|
|
|
private static final String TAG = "ObexHelper";
|
|
public static final boolean VDBG = false;
|
|
/**
|
|
* 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().
|
|
* NOTE: This value must be larger than or equal to the L2CAP SDU
|
|
*/
|
|
/*
|
|
* android note set as 0xFFFE to match remote MPS
|
|
*/
|
|
public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
|
|
|
|
// The minimum allowed max packet size is 255 according to the OBEX specification
|
|
public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255;
|
|
|
|
// The length of OBEX Byte Sequency Header Id according to the OBEX specification
|
|
public static final int OBEX_BYTE_SEQ_HEADER_LEN = 0x03;
|
|
|
|
/**
|
|
* 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_FINAL_BIT_MASK = 0x80;
|
|
|
|
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;
|
|
|
|
public static final byte OBEX_SRM_ENABLE = 0x01; // For BT we only need enable/disable
|
|
public static final byte OBEX_SRM_DISABLE = 0x00;
|
|
public static final byte OBEX_SRM_SUPPORT = 0x02; // Unused for now
|
|
|
|
public static final byte OBEX_SRMP_WAIT = 0x01; // Only SRMP value used by BT
|
|
|
|
/**
|
|
* 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]) << 8) +
|
|
(0xFF & headerArray[index + 1]);
|
|
index += 2;
|
|
if (length <= OBEX_BYTE_SEQ_HEADER_LEN) {
|
|
Log.e(TAG, "Remote sent an OBEX packet with " +
|
|
"incorrect header length = " + length);
|
|
break;
|
|
}
|
|
length -= OBEX_BYTE_SEQ_HEADER_LEN;
|
|
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", e);
|
|
}
|
|
index += 4;
|
|
break;
|
|
}
|
|
|
|
}
|
|
} catch (IOException e) {
|
|
throw new IOException("Header was not formatted properly", e);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// TODO:
|
|
// If the SRM and SRMP header is in use, they must be send in the same OBEX packet
|
|
// But the current structure of the obex code cannot handle this, and therefore
|
|
// it makes sense to put them in the tail of the headers, since we then reduce the
|
|
// chance of enabling SRM to soon. The down side is that SRM cannot be used while
|
|
// transferring non-body headers
|
|
|
|
// Add the SRM header
|
|
byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
|
|
if (byteHeader != null) {
|
|
out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE);
|
|
out.write(byteHeader.byteValue());
|
|
if (nullOut) {
|
|
headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, null);
|
|
}
|
|
}
|
|
|
|
// Add the SRM parameter header
|
|
byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
|
|
if (byteHeader != null) {
|
|
out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
|
|
out.write(byteHeader.byteValue());
|
|
if (nullOut) {
|
|
headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, 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;
|
|
|
|
// TODO: Ensure SRM and SRMP headers are not split into two OBEX packets
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Return the maximum allowed OBEX packet to transmit.
|
|
* OBEX packets transmitted must be smaller than this value.
|
|
* @param transport Reference to the ObexTransport in use.
|
|
* @return the maximum allowed OBEX packet to transmit
|
|
*/
|
|
public static int getMaxTxPacketSize(ObexTransport transport) {
|
|
int size = transport.getMaxTransmitPacketSize();
|
|
return validateMaxPacketSize(size);
|
|
}
|
|
|
|
/**
|
|
* Return the maximum allowed OBEX packet to receive - used in OBEX connect.
|
|
* @param transport
|
|
* @return he maximum allowed OBEX packet to receive
|
|
*/
|
|
public static int getMaxRxPacketSize(ObexTransport transport) {
|
|
int size = transport.getMaxReceivePacketSize();
|
|
return validateMaxPacketSize(size);
|
|
}
|
|
|
|
private static int validateMaxPacketSize(int size) {
|
|
if (VDBG && (size > MAX_PACKET_SIZE_INT)) {
|
|
Log.w(TAG, "The packet size supported for the connection (" + size + ") is larger"
|
|
+ " than the configured OBEX packet size: " + MAX_PACKET_SIZE_INT);
|
|
}
|
|
if (size != -1 && size < MAX_PACKET_SIZE_INT) {
|
|
if (size < LOWER_LIMIT_MAX_PACKET_SIZE) {
|
|
throw new IllegalArgumentException(size + " is less that the lower limit: "
|
|
+ LOWER_LIMIT_MAX_PACKET_SIZE);
|
|
}
|
|
return size;
|
|
}
|
|
return MAX_PACKET_SIZE_INT;
|
|
}
|
|
}
|