| /* |
| * 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.util.Calendar; |
| import java.security.SecureRandom; |
| |
| /** |
| * This class implements the javax.obex.HeaderSet interface for OBEX over |
| * RFCOMM. |
| * @hide |
| */ |
| public final class HeaderSet { |
| |
| /** |
| * Represents the OBEX Count header. This allows the connection statement to |
| * tell the server how many objects it plans to send or retrieve. |
| * <P> |
| * The value of <code>COUNT</code> is 0xC0 (192). |
| */ |
| public static final int COUNT = 0xC0; |
| |
| /** |
| * Represents the OBEX Name header. This specifies the name of the object. |
| * <P> |
| * The value of <code>NAME</code> is 0x01 (1). |
| */ |
| public static final int NAME = 0x01; |
| |
| /** |
| * Represents the OBEX Type header. This allows a request to specify the |
| * type of the object (e.g. text, html, binary, etc.). |
| * <P> |
| * The value of <code>TYPE</code> is 0x42 (66). |
| */ |
| public static final int TYPE = 0x42; |
| |
| /** |
| * Represents the OBEX Length header. This is the length of the object in |
| * bytes. |
| * <P> |
| * The value of <code>LENGTH</code> is 0xC3 (195). |
| */ |
| public static final int LENGTH = 0xC3; |
| |
| /** |
| * Represents the OBEX Time header using the ISO 8601 standards. This is the |
| * preferred time header. |
| * <P> |
| * The value of <code>TIME_ISO_8601</code> is 0x44 (68). |
| */ |
| public static final int TIME_ISO_8601 = 0x44; |
| |
| /** |
| * Represents the OBEX Time header using the 4 byte representation. This is |
| * only included for backwards compatibility. It represents the number of |
| * seconds since January 1, 1970. |
| * <P> |
| * The value of <code>TIME_4_BYTE</code> is 0xC4 (196). |
| */ |
| public static final int TIME_4_BYTE = 0xC4; |
| |
| /** |
| * Represents the OBEX Description header. This is a text description of the |
| * object. |
| * <P> |
| * The value of <code>DESCRIPTION</code> is 0x05 (5). |
| */ |
| public static final int DESCRIPTION = 0x05; |
| |
| /** |
| * Represents the OBEX Target header. This is the name of the service an |
| * operation is targeted to. |
| * <P> |
| * The value of <code>TARGET</code> is 0x46 (70). |
| */ |
| public static final int TARGET = 0x46; |
| |
| /** |
| * Represents the OBEX HTTP header. This allows an HTTP 1.X header to be |
| * included in a request or reply. |
| * <P> |
| * The value of <code>HTTP</code> is 0x47 (71). |
| */ |
| public static final int HTTP = 0x47; |
| |
| /** |
| * Represents the OBEX BODY header. |
| * <P> |
| * The value of <code>BODY</code> is 0x48 (72). |
| */ |
| public static final int BODY = 0x48; |
| |
| /** |
| * Represents the OBEX End of BODY header. |
| * <P> |
| * The value of <code>BODY</code> is 0x49 (73). |
| */ |
| public static final int END_OF_BODY = 0x49; |
| |
| /** |
| * Represents the OBEX Who header. Identifies the OBEX application to |
| * determine if the two peers are talking to each other. |
| * <P> |
| * The value of <code>WHO</code> is 0x4A (74). |
| */ |
| public static final int WHO = 0x4A; |
| |
| /** |
| * Represents the OBEX Connection ID header. Identifies used for OBEX |
| * connection multiplexing. |
| * <P> |
| * The value of <code>CONNECTION_ID</code> is 0xCB (203). |
| */ |
| |
| public static final int CONNECTION_ID = 0xCB; |
| |
| /** |
| * Represents the OBEX Application Parameter header. This header specifies |
| * additional application request and response information. |
| * <P> |
| * The value of <code>APPLICATION_PARAMETER</code> is 0x4C (76). |
| */ |
| public static final int APPLICATION_PARAMETER = 0x4C; |
| |
| /** |
| * Represents the OBEX authentication digest-challenge. |
| * <P> |
| * The value of <code>AUTH_CHALLENGE</code> is 0x4D (77). |
| */ |
| public static final int AUTH_CHALLENGE = 0x4D; |
| |
| /** |
| * Represents the OBEX authentication digest-response. |
| * <P> |
| * The value of <code>AUTH_RESPONSE</code> is 0x4E (78). |
| */ |
| public static final int AUTH_RESPONSE = 0x4E; |
| |
| /** |
| * Represents the OBEX Object Class header. This header specifies the OBEX |
| * object class of the object. |
| * <P> |
| * The value of <code>OBJECT_CLASS</code> is 0x4F (79). |
| */ |
| public static final int OBJECT_CLASS = 0x4F; |
| |
| private Long mCount; // 4 byte unsigned integer |
| |
| private String mName; // null terminated Unicode text string |
| |
| private boolean mEmptyName; |
| |
| private String mType; // null terminated ASCII text string |
| |
| private Long mLength; // 4 byte unsigend integer |
| |
| private Calendar mIsoTime; // String of the form YYYYMMDDTHHMMSSZ |
| |
| private Calendar mByteTime; // 4 byte unsigned integer |
| |
| private String mDescription; // null terminated Unicode text String |
| |
| private byte[] mTarget; // byte sequence |
| |
| private byte[] mHttpHeader; // byte sequence |
| |
| private byte[] mWho; // length prefixed byte sequence |
| |
| private byte[] mAppParam; // byte sequence of the form tag length value |
| |
| private byte[] mObjectClass; // byte sequence |
| |
| private String[] mUnicodeUserDefined; //null terminated unicode string |
| |
| private byte[][] mSequenceUserDefined; // byte sequence user defined |
| |
| private Byte[] mByteUserDefined; // 1 byte |
| |
| private Long[] mIntegerUserDefined; // 4 byte unsigned integer |
| |
| private final SecureRandom mRandom; |
| |
| /*package*/ byte[] nonce; |
| |
| public byte[] mAuthChall; // The authentication challenge header |
| |
| public byte[] mAuthResp; // The authentication response header |
| |
| public byte[] mConnectionID; // THe connection ID |
| |
| public int responseCode; |
| |
| /** |
| * Creates new <code>HeaderSet</code> object. |
| * @param size the max packet size for this connection |
| */ |
| public HeaderSet() { |
| mUnicodeUserDefined = new String[16]; |
| mSequenceUserDefined = new byte[16][]; |
| mByteUserDefined = new Byte[16]; |
| mIntegerUserDefined = new Long[16]; |
| responseCode = -1; |
| mRandom = new SecureRandom(); |
| } |
| |
| /** |
| * Sets flag for special "value" of NAME header which should be empty. This |
| * is not the same as NAME header with empty string in which case it will |
| * have length of 5 bytes. It should be 3 bytes with only header id and |
| * length field. |
| */ |
| public void setEmptyNameHeader() { |
| mName = null; |
| mEmptyName = true; |
| } |
| |
| /** |
| * Gets flag for special "value" of NAME header which should be empty. See |
| * above. |
| */ |
| public boolean getEmptyNameHeader() { |
| return mEmptyName; |
| } |
| |
| /** |
| * Sets the value of the header identifier to the value provided. The type |
| * of object must correspond to the Java type defined in the description of |
| * this interface. If <code>null</code> is passed as the |
| * <code>headerValue</code> then the header will be removed from the set of |
| * headers to include in the next request. |
| * @param headerID the identifier to include in the message |
| * @param headerValue the value of the header identifier |
| * @throws IllegalArgumentException if the header identifier provided is not |
| * one defined in this interface or a user-defined header; if the |
| * type of <code>headerValue</code> is not the correct Java type as |
| * defined in the description of this interface\ |
| */ |
| public void setHeader(int headerID, Object headerValue) { |
| long temp = -1; |
| |
| switch (headerID) { |
| case COUNT: |
| if (!(headerValue instanceof Long)) { |
| if (headerValue == null) { |
| mCount = null; |
| break; |
| } |
| throw new IllegalArgumentException("Count must be a Long"); |
| } |
| temp = ((Long)headerValue).longValue(); |
| if ((temp < 0L) || (temp > 0xFFFFFFFFL)) { |
| throw new IllegalArgumentException("Count must be between 0 and 0xFFFFFFFF"); |
| } |
| mCount = (Long)headerValue; |
| break; |
| case NAME: |
| if ((headerValue != null) && (!(headerValue instanceof String))) { |
| throw new IllegalArgumentException("Name must be a String"); |
| } |
| mEmptyName = false; |
| mName = (String)headerValue; |
| break; |
| case TYPE: |
| if ((headerValue != null) && (!(headerValue instanceof String))) { |
| throw new IllegalArgumentException("Type must be a String"); |
| } |
| mType = (String)headerValue; |
| break; |
| case LENGTH: |
| if (!(headerValue instanceof Long)) { |
| if (headerValue == null) { |
| mLength = null; |
| break; |
| } |
| throw new IllegalArgumentException("Length must be a Long"); |
| } |
| temp = ((Long)headerValue).longValue(); |
| if ((temp < 0L) || (temp > 0xFFFFFFFFL)) { |
| throw new IllegalArgumentException("Length must be between 0 and 0xFFFFFFFF"); |
| } |
| mLength = (Long)headerValue; |
| break; |
| case TIME_ISO_8601: |
| if ((headerValue != null) && (!(headerValue instanceof Calendar))) { |
| throw new IllegalArgumentException("Time ISO 8601 must be a Calendar"); |
| } |
| mIsoTime = (Calendar)headerValue; |
| break; |
| case TIME_4_BYTE: |
| if ((headerValue != null) && (!(headerValue instanceof Calendar))) { |
| throw new IllegalArgumentException("Time 4 Byte must be a Calendar"); |
| } |
| mByteTime = (Calendar)headerValue; |
| break; |
| case DESCRIPTION: |
| if ((headerValue != null) && (!(headerValue instanceof String))) { |
| throw new IllegalArgumentException("Description must be a String"); |
| } |
| mDescription = (String)headerValue; |
| break; |
| case TARGET: |
| if (headerValue == null) { |
| mTarget = null; |
| } else { |
| if (!(headerValue instanceof byte[])) { |
| throw new IllegalArgumentException("Target must be a byte array"); |
| } else { |
| mTarget = new byte[((byte[])headerValue).length]; |
| System.arraycopy(headerValue, 0, mTarget, 0, mTarget.length); |
| } |
| } |
| break; |
| case HTTP: |
| if (headerValue == null) { |
| mHttpHeader = null; |
| } else { |
| if (!(headerValue instanceof byte[])) { |
| throw new IllegalArgumentException("HTTP must be a byte array"); |
| } else { |
| mHttpHeader = new byte[((byte[])headerValue).length]; |
| System.arraycopy(headerValue, 0, mHttpHeader, 0, mHttpHeader.length); |
| } |
| } |
| break; |
| case WHO: |
| if (headerValue == null) { |
| mWho = null; |
| } else { |
| if (!(headerValue instanceof byte[])) { |
| throw new IllegalArgumentException("WHO must be a byte array"); |
| } else { |
| mWho = new byte[((byte[])headerValue).length]; |
| System.arraycopy(headerValue, 0, mWho, 0, mWho.length); |
| } |
| } |
| break; |
| case OBJECT_CLASS: |
| if (headerValue == null) { |
| mObjectClass = null; |
| } else { |
| if (!(headerValue instanceof byte[])) { |
| throw new IllegalArgumentException("Object Class must be a byte array"); |
| } else { |
| mObjectClass = new byte[((byte[])headerValue).length]; |
| System.arraycopy(headerValue, 0, mObjectClass, 0, mObjectClass.length); |
| } |
| } |
| break; |
| case APPLICATION_PARAMETER: |
| if (headerValue == null) { |
| mAppParam = null; |
| } else { |
| if (!(headerValue instanceof byte[])) { |
| throw new IllegalArgumentException( |
| "Application Parameter must be a byte array"); |
| } else { |
| mAppParam = new byte[((byte[])headerValue).length]; |
| System.arraycopy(headerValue, 0, mAppParam, 0, mAppParam.length); |
| } |
| } |
| break; |
| default: |
| // Verify that it was not a Unicode String user Defined |
| if ((headerID >= 0x30) && (headerID <= 0x3F)) { |
| if ((headerValue != null) && (!(headerValue instanceof String))) { |
| throw new IllegalArgumentException( |
| "Unicode String User Defined must be a String"); |
| } |
| mUnicodeUserDefined[headerID - 0x30] = (String)headerValue; |
| |
| break; |
| } |
| // Verify that it was not a byte sequence user defined value |
| if ((headerID >= 0x70) && (headerID <= 0x7F)) { |
| |
| if (headerValue == null) { |
| mSequenceUserDefined[headerID - 0x70] = null; |
| } else { |
| if (!(headerValue instanceof byte[])) { |
| throw new IllegalArgumentException( |
| "Byte Sequence User Defined must be a byte array"); |
| } else { |
| mSequenceUserDefined[headerID - 0x70] = new byte[((byte[])headerValue).length]; |
| System.arraycopy(headerValue, 0, mSequenceUserDefined[headerID - 0x70], |
| 0, mSequenceUserDefined[headerID - 0x70].length); |
| } |
| } |
| break; |
| } |
| // Verify that it was not a Byte user Defined |
| if ((headerID >= 0xB0) && (headerID <= 0xBF)) { |
| if ((headerValue != null) && (!(headerValue instanceof Byte))) { |
| throw new IllegalArgumentException("ByteUser Defined must be a Byte"); |
| } |
| mByteUserDefined[headerID - 0xB0] = (Byte)headerValue; |
| |
| break; |
| } |
| // Verify that is was not the 4 byte unsigned integer user |
| // defined header |
| if ((headerID >= 0xF0) && (headerID <= 0xFF)) { |
| if (!(headerValue instanceof Long)) { |
| if (headerValue == null) { |
| mIntegerUserDefined[headerID - 0xF0] = null; |
| break; |
| } |
| throw new IllegalArgumentException("Integer User Defined must be a Long"); |
| } |
| temp = ((Long)headerValue).longValue(); |
| if ((temp < 0L) || (temp > 0xFFFFFFFFL)) { |
| throw new IllegalArgumentException( |
| "Integer User Defined must be between 0 and 0xFFFFFFFF"); |
| } |
| mIntegerUserDefined[headerID - 0xF0] = (Long)headerValue; |
| break; |
| } |
| throw new IllegalArgumentException("Invalid Header Identifier"); |
| } |
| } |
| |
| /** |
| * Retrieves the value of the header identifier provided. The type of the |
| * Object returned is defined in the description of this interface. |
| * @param headerID the header identifier whose value is to be returned |
| * @return the value of the header provided or <code>null</code> if the |
| * header identifier specified is not part of this |
| * <code>HeaderSet</code> object |
| * @throws IllegalArgumentException if the <code>headerID</code> is not one |
| * defined in this interface or any of the user-defined headers |
| * @throws IOException if an error occurred in the transport layer during |
| * the operation or if the connection has been closed |
| */ |
| public Object getHeader(int headerID) throws IOException { |
| |
| switch (headerID) { |
| case COUNT: |
| return mCount; |
| case NAME: |
| return mName; |
| case TYPE: |
| return mType; |
| case LENGTH: |
| return mLength; |
| case TIME_ISO_8601: |
| return mIsoTime; |
| case TIME_4_BYTE: |
| return mByteTime; |
| case DESCRIPTION: |
| return mDescription; |
| case TARGET: |
| return mTarget; |
| case HTTP: |
| return mHttpHeader; |
| case WHO: |
| return mWho; |
| case CONNECTION_ID: |
| return mConnectionID; |
| case OBJECT_CLASS: |
| return mObjectClass; |
| case APPLICATION_PARAMETER: |
| return mAppParam; |
| default: |
| // Verify that it was not a Unicode String user Defined |
| if ((headerID >= 0x30) && (headerID <= 0x3F)) { |
| return mUnicodeUserDefined[headerID - 0x30]; |
| } |
| // Verify that it was not a byte sequence user defined header |
| if ((headerID >= 0x70) && (headerID <= 0x7F)) { |
| return mSequenceUserDefined[headerID - 0x70]; |
| } |
| // Verify that it was not a byte user defined header |
| if ((headerID >= 0xB0) && (headerID <= 0xBF)) { |
| return mByteUserDefined[headerID - 0xB0]; |
| } |
| // Verify that it was not a integer user defined header |
| if ((headerID >= 0xF0) && (headerID <= 0xFF)) { |
| return mIntegerUserDefined[headerID - 0xF0]; |
| } |
| throw new IllegalArgumentException("Invalid Header Identifier"); |
| } |
| } |
| |
| /** |
| * Retrieves the list of headers that may be retrieved via the |
| * <code>getHeader</code> method that will not return <code>null</code>. In |
| * other words, this method returns all the headers that are available in |
| * this object. |
| * @see #getHeader |
| * @return the array of headers that are set in this object or |
| * <code>null</code> if no headers are available |
| * @throws IOException if an error occurred in the transport layer during |
| * the operation or the connection has been closed |
| */ |
| public int[] getHeaderList() throws IOException { |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| |
| if (mCount != null) { |
| out.write(COUNT); |
| } |
| if (mName != null) { |
| out.write(NAME); |
| } |
| if (mType != null) { |
| out.write(TYPE); |
| } |
| if (mLength != null) { |
| out.write(LENGTH); |
| } |
| if (mIsoTime != null) { |
| out.write(TIME_ISO_8601); |
| } |
| if (mByteTime != null) { |
| out.write(TIME_4_BYTE); |
| } |
| if (mDescription != null) { |
| out.write(DESCRIPTION); |
| } |
| if (mTarget != null) { |
| out.write(TARGET); |
| } |
| if (mHttpHeader != null) { |
| out.write(HTTP); |
| } |
| if (mWho != null) { |
| out.write(WHO); |
| } |
| if (mAppParam != null) { |
| out.write(APPLICATION_PARAMETER); |
| } |
| if (mObjectClass != null) { |
| out.write(OBJECT_CLASS); |
| } |
| |
| for (int i = 0x30; i < 0x40; i++) { |
| if (mUnicodeUserDefined[i - 0x30] != null) { |
| out.write(i); |
| } |
| } |
| |
| for (int i = 0x70; i < 0x80; i++) { |
| if (mSequenceUserDefined[i - 0x70] != null) { |
| out.write(i); |
| } |
| } |
| |
| for (int i = 0xB0; i < 0xC0; i++) { |
| if (mByteUserDefined[i - 0xB0] != null) { |
| out.write(i); |
| } |
| } |
| |
| for (int i = 0xF0; i < 0x100; i++) { |
| if (mIntegerUserDefined[i - 0xF0] != null) { |
| out.write(i); |
| } |
| } |
| |
| byte[] headers = out.toByteArray(); |
| out.close(); |
| |
| if ((headers == null) || (headers.length == 0)) { |
| return null; |
| } |
| |
| int[] result = new int[headers.length]; |
| for (int i = 0; i < headers.length; i++) { |
| // Convert the byte to a positive integer. That is, an integer |
| // between 0 and 256. |
| result[i] = headers[i] & 0xFF; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Sets the authentication challenge header. The <code>realm</code> will be |
| * encoded based upon the default encoding scheme used by the implementation |
| * to encode strings. Therefore, the encoding scheme used to encode the |
| * <code>realm</code> is application dependent. |
| * @param realm a short description that describes what password to use; if |
| * <code>null</code> no realm will be sent in the authentication |
| * challenge header |
| * @param userID if <code>true</code>, a user ID is required in the reply; |
| * if <code>false</code>, no user ID is required |
| * @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 |
| * @throws IOException |
| */ |
| public void createAuthenticationChallenge(String realm, boolean userID, boolean access) |
| throws IOException { |
| |
| nonce = new byte[16]; |
| for (int i = 0; i < 16; i++) { |
| nonce[i] = (byte)mRandom.nextInt(); |
| } |
| |
| mAuthChall = ObexHelper.computeAuthenticationChallenge(nonce, realm, access, userID); |
| } |
| |
| /** |
| * Returns the response code received from the server. Response codes are |
| * defined in the <code>ResponseCodes</code> class. |
| * @see ResponseCodes |
| * @return the response code retrieved from the server |
| * @throws IOException if an error occurred in the transport layer during |
| * the transaction; if this method is called on a |
| * <code>HeaderSet</code> object created by calling |
| * <code>createHeaderSet()</code> in a <code>ClientSession</code> |
| * object; if this object was created by an OBEX server |
| */ |
| public int getResponseCode() throws IOException { |
| if (responseCode == -1) { |
| throw new IOException("May not be called on a server"); |
| } else { |
| return responseCode; |
| } |
| } |
| } |