Home | History | Annotate | Download | only in obex
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  * Copyright (C) 2015 Samsung LSI
      4  * Copyright (c) 2008-2009, Motorola, Inc.
      5  *
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions are met:
     10  *
     11  * - Redistributions of source code must retain the above copyright notice,
     12  * this list of conditions and the following disclaimer.
     13  *
     14  * - Redistributions in binary form must reproduce the above copyright notice,
     15  * this list of conditions and the following disclaimer in the documentation
     16  * and/or other materials provided with the distribution.
     17  *
     18  * - Neither the name of the Motorola, Inc. nor the names of its contributors
     19  * may be used to endorse or promote products derived from this software
     20  * without specific prior written permission.
     21  *
     22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     23  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     25  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
     26  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     32  * POSSIBILITY OF SUCH DAMAGE.
     33  */
     34 
     35 package javax.obex;
     36 
     37 import java.io.ByteArrayOutputStream;
     38 import java.io.IOException;
     39 import java.io.UnsupportedEncodingException;
     40 import java.security.MessageDigest;
     41 import java.security.NoSuchAlgorithmException;
     42 import java.util.Calendar;
     43 import java.util.Date;
     44 import java.util.TimeZone;
     45 
     46 import android.util.Log;
     47 
     48 /**
     49  * This class defines a set of helper methods for the implementation of Obex.
     50  * @hide
     51  */
     52 public final class ObexHelper {
     53 
     54     private static final String TAG = "ObexHelper";
     55     public static final boolean VDBG = false;
     56     /**
     57      * Defines the basic packet length used by OBEX. Every OBEX packet has the
     58      * same basic format:<BR>
     59      * Byte 0: Request or Response Code Byte 1&2: Length of the packet.
     60      */
     61     public static final int BASE_PACKET_LENGTH = 3;
     62 
     63     /** Prevent object construction of helper class */
     64     private ObexHelper() {
     65     }
     66 
     67     /**
     68      * The maximum packet size for OBEX packets that this client can handle. At
     69      * present, this must be changed for each port. TODO: The max packet size
     70      * should be the Max incoming MTU minus TODO: L2CAP package headers and
     71      * RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO:
     72      * LocalDevice.getProperty().
     73      * NOTE: This value must be larger than or equal to the L2CAP SDU
     74      */
     75     /*
     76      * android note set as 0xFFFE to match remote MPS
     77      */
     78     public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
     79 
     80     // The minimum allowed max packet size is 255 according to the OBEX specification
     81     public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255;
     82 
     83     // The length of OBEX Byte Sequency Header Id according to the OBEX specification
     84     public static final int OBEX_BYTE_SEQ_HEADER_LEN = 0x03;
     85 
     86     /**
     87      * Temporary workaround to be able to push files to Windows 7.
     88      * TODO: Should be removed as soon as Microsoft updates their driver.
     89      */
     90     public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00;
     91 
     92     public static final int OBEX_OPCODE_FINAL_BIT_MASK = 0x80;
     93 
     94     public static final int OBEX_OPCODE_CONNECT = 0x80;
     95 
     96     public static final int OBEX_OPCODE_DISCONNECT = 0x81;
     97 
     98     public static final int OBEX_OPCODE_PUT = 0x02;
     99 
    100     public static final int OBEX_OPCODE_PUT_FINAL = 0x82;
    101 
    102     public static final int OBEX_OPCODE_GET = 0x03;
    103 
    104     public static final int OBEX_OPCODE_GET_FINAL = 0x83;
    105 
    106     public static final int OBEX_OPCODE_RESERVED = 0x04;
    107 
    108     public static final int OBEX_OPCODE_RESERVED_FINAL = 0x84;
    109 
    110     public static final int OBEX_OPCODE_SETPATH = 0x85;
    111 
    112     public static final int OBEX_OPCODE_ABORT = 0xFF;
    113 
    114     public static final int OBEX_AUTH_REALM_CHARSET_ASCII = 0x00;
    115 
    116     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_1 = 0x01;
    117 
    118     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_2 = 0x02;
    119 
    120     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_3 = 0x03;
    121 
    122     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_4 = 0x04;
    123 
    124     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_5 = 0x05;
    125 
    126     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_6 = 0x06;
    127 
    128     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_7 = 0x07;
    129 
    130     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_8 = 0x08;
    131 
    132     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_9 = 0x09;
    133 
    134     public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF;
    135 
    136     public static final byte OBEX_SRM_ENABLE         = 0x01; // For BT we only need enable/disable
    137     public static final byte OBEX_SRM_DISABLE        = 0x00;
    138     public static final byte OBEX_SRM_SUPPORT        = 0x02; // Unused for now
    139 
    140     public static final byte OBEX_SRMP_WAIT          = 0x01; // Only SRMP value used by BT
    141 
    142     /**
    143      * Updates the HeaderSet with the headers received in the byte array
    144      * provided. Invalid headers are ignored.
    145      * <P>
    146      * The first two bits of an OBEX Header specifies the type of object that is
    147      * being sent. The table below specifies the meaning of the high bits.
    148      * <TABLE>
    149      * <TR>
    150      * <TH>Bits 8 and 7</TH>
    151      * <TH>Value</TH>
    152      * <TH>Description</TH>
    153      * </TR>
    154      * <TR>
    155      * <TD>00</TD>
    156      * <TD>0x00</TD>
    157      * <TD>Null Terminated Unicode text, prefixed with 2 byte unsigned integer</TD>
    158      * </TR>
    159      * <TR>
    160      * <TD>01</TD>
    161      * <TD>0x40</TD>
    162      * <TD>Byte Sequence, length prefixed with 2 byte unsigned integer</TD>
    163      * </TR>
    164      * <TR>
    165      * <TD>10</TD>
    166      * <TD>0x80</TD>
    167      * <TD>1 byte quantity</TD>
    168      * </TR>
    169      * <TR>
    170      * <TD>11</TD>
    171      * <TD>0xC0</TD>
    172      * <TD>4 byte quantity - transmitted in network byte order (high byte first</TD>
    173      * </TR>
    174      * </TABLE>
    175      * This method uses the information in this table to determine the type of
    176      * Java object to create and passes that object with the full header to
    177      * setHeader() to update the HeaderSet object. Invalid headers will cause an
    178      * exception to be thrown. When it is thrown, it is ignored.
    179      * @param header the HeaderSet to update
    180      * @param headerArray the byte array containing headers
    181      * @return the result of the last start body or end body header provided;
    182      *         the first byte in the result will specify if a body or end of
    183      *         body is received
    184      * @throws IOException if an invalid header was found
    185      */
    186     public static byte[] updateHeaderSet(HeaderSet header, byte[] headerArray) throws IOException {
    187         int index = 0;
    188         int length = 0;
    189         int headerID;
    190         byte[] value = null;
    191         byte[] body = null;
    192         HeaderSet headerImpl = header;
    193         try {
    194             while (index < headerArray.length) {
    195                 headerID = 0xFF & headerArray[index];
    196                 switch (headerID & (0xC0)) {
    197 
    198                     /*
    199                      * 0x00 is a unicode null terminate string with the first
    200                      * two bytes after the header identifier being the length
    201                      */
    202                     case 0x00:
    203                         // Fall through
    204                         /*
    205                          * 0x40 is a byte sequence with the first
    206                          * two bytes after the header identifier being the length
    207                          */
    208                     case 0x40:
    209                         boolean trimTail = true;
    210                         index++;
    211                         length = ((0xFF & headerArray[index]) << 8) +
    212                                  (0xFF & headerArray[index + 1]);
    213                         index += 2;
    214                         if (length <= OBEX_BYTE_SEQ_HEADER_LEN) {
    215                             Log.e(TAG, "Remote sent an OBEX packet with " +
    216                                   "incorrect header length = " + length);
    217                             break;
    218                         }
    219                         length -= OBEX_BYTE_SEQ_HEADER_LEN;
    220                         value = new byte[length];
    221                         System.arraycopy(headerArray, index, value, 0, length);
    222                         if (length == 0 || (length > 0 && (value[length - 1] != 0))) {
    223                             trimTail = false;
    224                         }
    225                         switch (headerID) {
    226                             case HeaderSet.TYPE:
    227                                 try {
    228                                     // Remove trailing null
    229                                     if (trimTail == false) {
    230                                         headerImpl.setHeader(headerID, new String(value, 0,
    231                                                 value.length, "ISO8859_1"));
    232                                     } else {
    233                                         headerImpl.setHeader(headerID, new String(value, 0,
    234                                                 value.length - 1, "ISO8859_1"));
    235                                     }
    236                                 } catch (UnsupportedEncodingException e) {
    237                                     throw e;
    238                                 }
    239                                 break;
    240 
    241                             case HeaderSet.AUTH_CHALLENGE:
    242                                 headerImpl.mAuthChall = new byte[length];
    243                                 System.arraycopy(headerArray, index, headerImpl.mAuthChall, 0,
    244                                         length);
    245                                 break;
    246 
    247                             case HeaderSet.AUTH_RESPONSE:
    248                                 headerImpl.mAuthResp = new byte[length];
    249                                 System.arraycopy(headerArray, index, headerImpl.mAuthResp, 0,
    250                                         length);
    251                                 break;
    252 
    253                             case HeaderSet.BODY:
    254                                 /* Fall Through */
    255                             case HeaderSet.END_OF_BODY:
    256                                 body = new byte[length + 1];
    257                                 body[0] = (byte)headerID;
    258                                 System.arraycopy(headerArray, index, body, 1, length);
    259                                 break;
    260 
    261                             case HeaderSet.TIME_ISO_8601:
    262                                 try {
    263                                     String dateString = new String(value, "ISO8859_1");
    264                                     Calendar temp = Calendar.getInstance();
    265                                     if ((dateString.length() == 16)
    266                                             && (dateString.charAt(15) == 'Z')) {
    267                                         temp.setTimeZone(TimeZone.getTimeZone("UTC"));
    268                                     }
    269                                     temp.set(Calendar.YEAR, Integer.parseInt(dateString.substring(
    270                                             0, 4)));
    271                                     temp.set(Calendar.MONTH, Integer.parseInt(dateString.substring(
    272                                             4, 6)));
    273                                     temp.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateString
    274                                             .substring(6, 8)));
    275                                     temp.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateString
    276                                             .substring(9, 11)));
    277                                     temp.set(Calendar.MINUTE, Integer.parseInt(dateString
    278                                             .substring(11, 13)));
    279                                     temp.set(Calendar.SECOND, Integer.parseInt(dateString
    280                                             .substring(13, 15)));
    281                                     headerImpl.setHeader(HeaderSet.TIME_ISO_8601, temp);
    282                                 } catch (UnsupportedEncodingException e) {
    283                                     throw e;
    284                                 }
    285                                 break;
    286 
    287                             default:
    288                                 if ((headerID & 0xC0) == 0x00) {
    289                                     headerImpl.setHeader(headerID, ObexHelper.convertToUnicode(
    290                                             value, true));
    291                                 } else {
    292                                     headerImpl.setHeader(headerID, value);
    293                                 }
    294                         }
    295 
    296                         index += length;
    297                         break;
    298 
    299                     /*
    300                      * 0x80 is a byte header.  The only valid byte headers are
    301                      * the 16 user defined byte headers.
    302                      */
    303                     case 0x80:
    304                         index++;
    305                         try {
    306                             headerImpl.setHeader(headerID, Byte.valueOf(headerArray[index]));
    307                         } catch (Exception e) {
    308                             // Not a valid header so ignore
    309                         }
    310                         index++;
    311                         break;
    312 
    313                     /*
    314                      * 0xC0 is a 4 byte unsigned integer header and with the
    315                      * exception of TIME_4_BYTE will be converted to a Long
    316                      * and added.
    317                      */
    318                     case 0xC0:
    319                         index++;
    320                         value = new byte[4];
    321                         System.arraycopy(headerArray, index, value, 0, 4);
    322                         try {
    323                             if (headerID != HeaderSet.TIME_4_BYTE) {
    324                                 // Determine if it is a connection ID.  These
    325                                 // need to be handled differently
    326                                 if (headerID == HeaderSet.CONNECTION_ID) {
    327                                     headerImpl.mConnectionID = new byte[4];
    328                                     System.arraycopy(value, 0, headerImpl.mConnectionID, 0, 4);
    329                                 } else {
    330                                     headerImpl.setHeader(headerID, Long
    331                                             .valueOf(convertToLong(value)));
    332                                 }
    333                             } else {
    334                                 Calendar temp = Calendar.getInstance();
    335                                 temp.setTime(new Date(convertToLong(value) * 1000L));
    336                                 headerImpl.setHeader(HeaderSet.TIME_4_BYTE, temp);
    337                             }
    338                         } catch (Exception e) {
    339                             // Not a valid header so ignore
    340                             throw new IOException("Header was not formatted properly", e);
    341                         }
    342                         index += 4;
    343                         break;
    344                 }
    345 
    346             }
    347         } catch (IOException e) {
    348             throw new IOException("Header was not formatted properly", e);
    349         }
    350 
    351         return body;
    352     }
    353 
    354     /**
    355      * Creates the header part of OBEX packet based on the header provided.
    356      * TODO: Could use getHeaderList() to get the array of headers to include
    357      * and then use the high two bits to determine the the type of the object
    358      * and construct the byte array from that. This will make the size smaller.
    359      * @param head the header used to construct the byte array
    360      * @param nullOut <code>true</code> if the header should be set to
    361      *        <code>null</code> once it is added to the array or
    362      *        <code>false</code> if it should not be nulled out
    363      * @return the header of an OBEX packet
    364      */
    365     public static byte[] createHeader(HeaderSet head, boolean nullOut) {
    366         Long intHeader = null;
    367         String stringHeader = null;
    368         Calendar dateHeader = null;
    369         Byte byteHeader = null;
    370         StringBuffer buffer = null;
    371         byte[] value = null;
    372         byte[] result = null;
    373         byte[] lengthArray = new byte[2];
    374         int length;
    375         HeaderSet headImpl = null;
    376         ByteArrayOutputStream out = new ByteArrayOutputStream();
    377         headImpl = head;
    378 
    379         try {
    380             /*
    381              * Determine if there is a connection ID to send.  If there is,
    382              * then it should be the first header in the packet.
    383              */
    384             if ((headImpl.mConnectionID != null) && (headImpl.getHeader(HeaderSet.TARGET) == null)) {
    385 
    386                 out.write((byte)HeaderSet.CONNECTION_ID);
    387                 out.write(headImpl.mConnectionID);
    388             }
    389 
    390             // Count Header
    391             intHeader = (Long)headImpl.getHeader(HeaderSet.COUNT);
    392             if (intHeader != null) {
    393                 out.write((byte)HeaderSet.COUNT);
    394                 value = ObexHelper.convertToByteArray(intHeader.longValue());
    395                 out.write(value);
    396                 if (nullOut) {
    397                     headImpl.setHeader(HeaderSet.COUNT, null);
    398                 }
    399             }
    400 
    401             // Name Header
    402             stringHeader = (String)headImpl.getHeader(HeaderSet.NAME);
    403             if (stringHeader != null) {
    404                 out.write((byte)HeaderSet.NAME);
    405                 value = ObexHelper.convertToUnicodeByteArray(stringHeader);
    406                 length = value.length + 3;
    407                 lengthArray[0] = (byte)(0xFF & (length >> 8));
    408                 lengthArray[1] = (byte)(0xFF & length);
    409                 out.write(lengthArray);
    410                 out.write(value);
    411                 if (nullOut) {
    412                     headImpl.setHeader(HeaderSet.NAME, null);
    413                 }
    414             } else if (headImpl.getEmptyNameHeader()) {
    415                 out.write((byte) HeaderSet.NAME);
    416                 lengthArray[0] = (byte) 0x00;
    417                 lengthArray[1] = (byte) 0x03;
    418                 out.write(lengthArray);
    419             }
    420 
    421             // Type Header
    422             stringHeader = (String)headImpl.getHeader(HeaderSet.TYPE);
    423             if (stringHeader != null) {
    424                 out.write((byte)HeaderSet.TYPE);
    425                 try {
    426                     value = stringHeader.getBytes("ISO8859_1");
    427                 } catch (UnsupportedEncodingException e) {
    428                     throw e;
    429                 }
    430 
    431                 length = value.length + 4;
    432                 lengthArray[0] = (byte)(255 & (length >> 8));
    433                 lengthArray[1] = (byte)(255 & length);
    434                 out.write(lengthArray);
    435                 out.write(value);
    436                 out.write(0x00);
    437                 if (nullOut) {
    438                     headImpl.setHeader(HeaderSet.TYPE, null);
    439                 }
    440             }
    441 
    442             // Length Header
    443             intHeader = (Long)headImpl.getHeader(HeaderSet.LENGTH);
    444             if (intHeader != null) {
    445                 out.write((byte)HeaderSet.LENGTH);
    446                 value = ObexHelper.convertToByteArray(intHeader.longValue());
    447                 out.write(value);
    448                 if (nullOut) {
    449                     headImpl.setHeader(HeaderSet.LENGTH, null);
    450                 }
    451             }
    452 
    453             // Time ISO Header
    454             dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_ISO_8601);
    455             if (dateHeader != null) {
    456 
    457                 /*
    458                  * The ISO Header should take the form YYYYMMDDTHHMMSSZ.  The
    459                  * 'Z' will only be included if it is a UTC time.
    460                  */
    461                 buffer = new StringBuffer();
    462                 int temp = dateHeader.get(Calendar.YEAR);
    463                 for (int i = temp; i < 1000; i = i * 10) {
    464                     buffer.append("0");
    465                 }
    466                 buffer.append(temp);
    467                 temp = dateHeader.get(Calendar.MONTH);
    468                 if (temp < 10) {
    469                     buffer.append("0");
    470                 }
    471                 buffer.append(temp);
    472                 temp = dateHeader.get(Calendar.DAY_OF_MONTH);
    473                 if (temp < 10) {
    474                     buffer.append("0");
    475                 }
    476                 buffer.append(temp);
    477                 buffer.append("T");
    478                 temp = dateHeader.get(Calendar.HOUR_OF_DAY);
    479                 if (temp < 10) {
    480                     buffer.append("0");
    481                 }
    482                 buffer.append(temp);
    483                 temp = dateHeader.get(Calendar.MINUTE);
    484                 if (temp < 10) {
    485                     buffer.append("0");
    486                 }
    487                 buffer.append(temp);
    488                 temp = dateHeader.get(Calendar.SECOND);
    489                 if (temp < 10) {
    490                     buffer.append("0");
    491                 }
    492                 buffer.append(temp);
    493 
    494                 if (dateHeader.getTimeZone().getID().equals("UTC")) {
    495                     buffer.append("Z");
    496                 }
    497 
    498                 try {
    499                     value = buffer.toString().getBytes("ISO8859_1");
    500                 } catch (UnsupportedEncodingException e) {
    501                     throw e;
    502                 }
    503 
    504                 length = value.length + 3;
    505                 lengthArray[0] = (byte)(255 & (length >> 8));
    506                 lengthArray[1] = (byte)(255 & length);
    507                 out.write(HeaderSet.TIME_ISO_8601);
    508                 out.write(lengthArray);
    509                 out.write(value);
    510                 if (nullOut) {
    511                     headImpl.setHeader(HeaderSet.TIME_ISO_8601, null);
    512                 }
    513             }
    514 
    515             // Time 4 Byte Header
    516             dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_4_BYTE);
    517             if (dateHeader != null) {
    518                 out.write(HeaderSet.TIME_4_BYTE);
    519 
    520                 /*
    521                  * Need to call getTime() twice.  The first call will return
    522                  * a java.util.Date object.  The second call returns the number
    523                  * of milliseconds since January 1, 1970.  We need to convert
    524                  * it to seconds since the TIME_4_BYTE expects the number of
    525                  * seconds since January 1, 1970.
    526                  */
    527                 value = ObexHelper.convertToByteArray(dateHeader.getTime().getTime() / 1000L);
    528                 out.write(value);
    529                 if (nullOut) {
    530                     headImpl.setHeader(HeaderSet.TIME_4_BYTE, null);
    531                 }
    532             }
    533 
    534             // Description Header
    535             stringHeader = (String)headImpl.getHeader(HeaderSet.DESCRIPTION);
    536             if (stringHeader != null) {
    537                 out.write((byte)HeaderSet.DESCRIPTION);
    538                 value = ObexHelper.convertToUnicodeByteArray(stringHeader);
    539                 length = value.length + 3;
    540                 lengthArray[0] = (byte)(255 & (length >> 8));
    541                 lengthArray[1] = (byte)(255 & length);
    542                 out.write(lengthArray);
    543                 out.write(value);
    544                 if (nullOut) {
    545                     headImpl.setHeader(HeaderSet.DESCRIPTION, null);
    546                 }
    547             }
    548 
    549             // Target Header
    550             value = (byte[])headImpl.getHeader(HeaderSet.TARGET);
    551             if (value != null) {
    552                 out.write((byte)HeaderSet.TARGET);
    553                 length = value.length + 3;
    554                 lengthArray[0] = (byte)(255 & (length >> 8));
    555                 lengthArray[1] = (byte)(255 & length);
    556                 out.write(lengthArray);
    557                 out.write(value);
    558                 if (nullOut) {
    559                     headImpl.setHeader(HeaderSet.TARGET, null);
    560                 }
    561             }
    562 
    563             // HTTP Header
    564             value = (byte[])headImpl.getHeader(HeaderSet.HTTP);
    565             if (value != null) {
    566                 out.write((byte)HeaderSet.HTTP);
    567                 length = value.length + 3;
    568                 lengthArray[0] = (byte)(255 & (length >> 8));
    569                 lengthArray[1] = (byte)(255 & length);
    570                 out.write(lengthArray);
    571                 out.write(value);
    572                 if (nullOut) {
    573                     headImpl.setHeader(HeaderSet.HTTP, null);
    574                 }
    575             }
    576 
    577             // Who Header
    578             value = (byte[])headImpl.getHeader(HeaderSet.WHO);
    579             if (value != null) {
    580                 out.write((byte)HeaderSet.WHO);
    581                 length = value.length + 3;
    582                 lengthArray[0] = (byte)(255 & (length >> 8));
    583                 lengthArray[1] = (byte)(255 & length);
    584                 out.write(lengthArray);
    585                 out.write(value);
    586                 if (nullOut) {
    587                     headImpl.setHeader(HeaderSet.WHO, null);
    588                 }
    589             }
    590 
    591             // Connection ID Header
    592             value = (byte[])headImpl.getHeader(HeaderSet.APPLICATION_PARAMETER);
    593             if (value != null) {
    594                 out.write((byte)HeaderSet.APPLICATION_PARAMETER);
    595                 length = value.length + 3;
    596                 lengthArray[0] = (byte)(255 & (length >> 8));
    597                 lengthArray[1] = (byte)(255 & length);
    598                 out.write(lengthArray);
    599                 out.write(value);
    600                 if (nullOut) {
    601                     headImpl.setHeader(HeaderSet.APPLICATION_PARAMETER, null);
    602                 }
    603             }
    604 
    605             // Object Class Header
    606             value = (byte[])headImpl.getHeader(HeaderSet.OBJECT_CLASS);
    607             if (value != null) {
    608                 out.write((byte)HeaderSet.OBJECT_CLASS);
    609                 length = value.length + 3;
    610                 lengthArray[0] = (byte)(255 & (length >> 8));
    611                 lengthArray[1] = (byte)(255 & length);
    612                 out.write(lengthArray);
    613                 out.write(value);
    614                 if (nullOut) {
    615                     headImpl.setHeader(HeaderSet.OBJECT_CLASS, null);
    616                 }
    617             }
    618 
    619             // Check User Defined Headers
    620             for (int i = 0; i < 16; i++) {
    621 
    622                 //Unicode String Header
    623                 stringHeader = (String)headImpl.getHeader(i + 0x30);
    624                 if (stringHeader != null) {
    625                     out.write((byte)i + 0x30);
    626                     value = ObexHelper.convertToUnicodeByteArray(stringHeader);
    627                     length = value.length + 3;
    628                     lengthArray[0] = (byte)(255 & (length >> 8));
    629                     lengthArray[1] = (byte)(255 & length);
    630                     out.write(lengthArray);
    631                     out.write(value);
    632                     if (nullOut) {
    633                         headImpl.setHeader(i + 0x30, null);
    634                     }
    635                 }
    636 
    637                 // Byte Sequence Header
    638                 value = (byte[])headImpl.getHeader(i + 0x70);
    639                 if (value != null) {
    640                     out.write((byte)i + 0x70);
    641                     length = value.length + 3;
    642                     lengthArray[0] = (byte)(255 & (length >> 8));
    643                     lengthArray[1] = (byte)(255 & length);
    644                     out.write(lengthArray);
    645                     out.write(value);
    646                     if (nullOut) {
    647                         headImpl.setHeader(i + 0x70, null);
    648                     }
    649                 }
    650 
    651                 // Byte Header
    652                 byteHeader = (Byte)headImpl.getHeader(i + 0xB0);
    653                 if (byteHeader != null) {
    654                     out.write((byte)i + 0xB0);
    655                     out.write(byteHeader.byteValue());
    656                     if (nullOut) {
    657                         headImpl.setHeader(i + 0xB0, null);
    658                     }
    659                 }
    660 
    661                 // Integer header
    662                 intHeader = (Long)headImpl.getHeader(i + 0xF0);
    663                 if (intHeader != null) {
    664                     out.write((byte)i + 0xF0);
    665                     out.write(ObexHelper.convertToByteArray(intHeader.longValue()));
    666                     if (nullOut) {
    667                         headImpl.setHeader(i + 0xF0, null);
    668                     }
    669                 }
    670             }
    671 
    672             // Add the authentication challenge header
    673             if (headImpl.mAuthChall != null) {
    674                 out.write((byte)HeaderSet.AUTH_CHALLENGE);
    675                 length = headImpl.mAuthChall.length + 3;
    676                 lengthArray[0] = (byte)(255 & (length >> 8));
    677                 lengthArray[1] = (byte)(255 & length);
    678                 out.write(lengthArray);
    679                 out.write(headImpl.mAuthChall);
    680                 if (nullOut) {
    681                     headImpl.mAuthChall = null;
    682                 }
    683             }
    684 
    685             // Add the authentication response header
    686             if (headImpl.mAuthResp != null) {
    687                 out.write((byte)HeaderSet.AUTH_RESPONSE);
    688                 length = headImpl.mAuthResp.length + 3;
    689                 lengthArray[0] = (byte)(255 & (length >> 8));
    690                 lengthArray[1] = (byte)(255 & length);
    691                 out.write(lengthArray);
    692                 out.write(headImpl.mAuthResp);
    693                 if (nullOut) {
    694                     headImpl.mAuthResp = null;
    695                 }
    696             }
    697 
    698             // TODO:
    699             // If the SRM and SRMP header is in use, they must be send in the same OBEX packet
    700             // But the current structure of the obex code cannot handle this, and therefore
    701             // it makes sense to put them in the tail of the headers, since we then reduce the
    702             // chance of enabling SRM to soon. The down side is that SRM cannot be used while
    703             // transferring non-body headers
    704 
    705             // Add the SRM header
    706             byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
    707             if (byteHeader != null) {
    708                 out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE);
    709                 out.write(byteHeader.byteValue());
    710                 if (nullOut) {
    711                     headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, null);
    712                 }
    713             }
    714 
    715             // Add the SRM parameter header
    716             byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
    717             if (byteHeader != null) {
    718                 out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
    719                 out.write(byteHeader.byteValue());
    720                 if (nullOut) {
    721                     headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
    722                 }
    723             }
    724 
    725         } catch (IOException e) {
    726         } finally {
    727             result = out.toByteArray();
    728             try {
    729                 out.close();
    730             } catch (Exception ex) {
    731             }
    732         }
    733 
    734         return result;
    735 
    736     }
    737 
    738     /**
    739      * Determines where the maximum divide is between headers. This method is
    740      * used by put and get operations to separate headers to a size that meets
    741      * the max packet size allowed.
    742      * @param headerArray the headers to separate
    743      * @param start the starting index to search
    744      * @param maxSize the maximum size of a packet
    745      * @return the index of the end of the header block to send or -1 if the
    746      *         header could not be divided because the header is too large
    747      */
    748     public static int findHeaderEnd(byte[] headerArray, int start, int maxSize) {
    749 
    750         int fullLength = 0;
    751         int lastLength = -1;
    752         int index = start;
    753         int length = 0;
    754 
    755         // TODO: Ensure SRM and SRMP headers are not split into two OBEX packets
    756 
    757         while ((fullLength < maxSize) && (index < headerArray.length)) {
    758             int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]);
    759             lastLength = fullLength;
    760 
    761             switch (headerID & (0xC0)) {
    762 
    763                 case 0x00:
    764                     // Fall through
    765                 case 0x40:
    766 
    767                     index++;
    768                     length = (headerArray[index] < 0 ? headerArray[index] + 256
    769                             : headerArray[index]);
    770                     length = length << 8;
    771                     index++;
    772                     length += (headerArray[index] < 0 ? headerArray[index] + 256
    773                             : headerArray[index]);
    774                     length -= 3;
    775                     index++;
    776                     index += length;
    777                     fullLength += length + 3;
    778                     break;
    779 
    780                 case 0x80:
    781 
    782                     index++;
    783                     index++;
    784                     fullLength += 2;
    785                     break;
    786 
    787                 case 0xC0:
    788 
    789                     index += 5;
    790                     fullLength += 5;
    791                     break;
    792 
    793             }
    794 
    795         }
    796 
    797         /*
    798          * Determine if this is the last header or not
    799          */
    800         if (lastLength == 0) {
    801             /*
    802              * Since this is the last header, check to see if the size of this
    803              * header is less then maxSize.  If it is, return the length of the
    804              * header, otherwise return -1.  The length of the header is
    805              * returned since it would be the start of the next header
    806              */
    807             if (fullLength < maxSize) {
    808                 return headerArray.length;
    809             } else {
    810                 return -1;
    811             }
    812         } else {
    813             return lastLength + start;
    814         }
    815     }
    816 
    817     /**
    818      * Converts the byte array to a long.
    819      * @param b the byte array to convert to a long
    820      * @return the byte array as a long
    821      */
    822     public static long convertToLong(byte[] b) {
    823         long result = 0;
    824         long value = 0;
    825         long power = 0;
    826 
    827         for (int i = (b.length - 1); i >= 0; i--) {
    828             value = b[i];
    829             if (value < 0) {
    830                 value += 256;
    831             }
    832 
    833             result = result | (value << power);
    834             power += 8;
    835         }
    836 
    837         return result;
    838     }
    839 
    840     /**
    841      * Converts the long to a 4 byte array. The long must be non negative.
    842      * @param l the long to convert
    843      * @return a byte array that is the same as the long
    844      */
    845     public static byte[] convertToByteArray(long l) {
    846         byte[] b = new byte[4];
    847 
    848         b[0] = (byte)(255 & (l >> 24));
    849         b[1] = (byte)(255 & (l >> 16));
    850         b[2] = (byte)(255 & (l >> 8));
    851         b[3] = (byte)(255 & l);
    852 
    853         return b;
    854     }
    855 
    856     /**
    857      * Converts the String to a UNICODE byte array. It will also add the ending
    858      * null characters to the end of the string.
    859      * @param s the string to convert
    860      * @return the unicode byte array of the string
    861      */
    862     public static byte[] convertToUnicodeByteArray(String s) {
    863         if (s == null) {
    864             return null;
    865         }
    866 
    867         char c[] = s.toCharArray();
    868         byte[] result = new byte[(c.length * 2) + 2];
    869         for (int i = 0; i < c.length; i++) {
    870             result[(i * 2)] = (byte)(c[i] >> 8);
    871             result[((i * 2) + 1)] = (byte)c[i];
    872         }
    873 
    874         // Add the UNICODE null character
    875         result[result.length - 2] = 0;
    876         result[result.length - 1] = 0;
    877 
    878         return result;
    879     }
    880 
    881     /**
    882      * Retrieves the value from the byte array for the tag value specified. The
    883      * array should be of the form Tag - Length - Value triplet.
    884      * @param tag the tag to retrieve from the byte array
    885      * @param triplet the byte sequence containing the tag length value form
    886      * @return the value of the specified tag
    887      */
    888     public static byte[] getTagValue(byte tag, byte[] triplet) {
    889 
    890         int index = findTag(tag, triplet);
    891         if (index == -1) {
    892             return null;
    893         }
    894 
    895         index++;
    896         int length = triplet[index] & 0xFF;
    897 
    898         byte[] result = new byte[length];
    899         index++;
    900         System.arraycopy(triplet, index, result, 0, length);
    901 
    902         return result;
    903     }
    904 
    905     /**
    906      * Finds the index that starts the tag value pair in the byte array provide.
    907      * @param tag the tag to look for
    908      * @param value the byte array to search
    909      * @return the starting index of the tag or -1 if the tag could not be found
    910      */
    911     public static int findTag(byte tag, byte[] value) {
    912         int length = 0;
    913 
    914         if (value == null) {
    915             return -1;
    916         }
    917 
    918         int index = 0;
    919 
    920         while ((index < value.length) && (value[index] != tag)) {
    921             length = value[index + 1] & 0xFF;
    922             index += length + 2;
    923         }
    924 
    925         if (index >= value.length) {
    926             return -1;
    927         }
    928 
    929         return index;
    930     }
    931 
    932     /**
    933      * Converts the byte array provided to a unicode string.
    934      * @param b the byte array to convert to a string
    935      * @param includesNull determine if the byte string provided contains the
    936      *        UNICODE null character at the end or not; if it does, it will be
    937      *        removed
    938      * @return a Unicode string
    939      * @throws IllegalArgumentException if the byte array has an odd length
    940      */
    941     public static String convertToUnicode(byte[] b, boolean includesNull) {
    942         if (b == null || b.length == 0) {
    943             return null;
    944         }
    945         int arrayLength = b.length;
    946         if (!((arrayLength % 2) == 0)) {
    947             throw new IllegalArgumentException("Byte array not of a valid form");
    948         }
    949         arrayLength = (arrayLength >> 1);
    950         if (includesNull) {
    951             arrayLength -= 1;
    952         }
    953 
    954         char[] c = new char[arrayLength];
    955         for (int i = 0; i < arrayLength; i++) {
    956             int upper = b[2 * i];
    957             int lower = b[(2 * i) + 1];
    958             if (upper < 0) {
    959                 upper += 256;
    960             }
    961             if (lower < 0) {
    962                 lower += 256;
    963             }
    964             // If upper and lower both equal 0, it should be the end of string.
    965             // Ignore left bytes from array to avoid potential issues
    966             if (upper == 0 && lower == 0) {
    967                 return new String(c, 0, i);
    968             }
    969 
    970             c[i] = (char)((upper << 8) | lower);
    971         }
    972 
    973         return new String(c);
    974     }
    975 
    976     /**
    977      * Compute the MD5 hash of the byte array provided. Does not accumulate
    978      * input.
    979      * @param in the byte array to hash
    980      * @return the MD5 hash of the byte array
    981      */
    982     public static byte[] computeMd5Hash(byte[] in) {
    983         try {
    984             MessageDigest md5 = MessageDigest.getInstance("MD5");
    985             return md5.digest(in);
    986         } catch (NoSuchAlgorithmException e) {
    987             throw new RuntimeException(e);
    988         }
    989     }
    990 
    991     /**
    992      * Computes an authentication challenge header.
    993      * @param nonce the challenge that will be provided to the peer; the
    994      *        challenge must be 16 bytes long
    995      * @param realm a short description that describes what password to use
    996      * @param access if <code>true</code> then full access will be granted if
    997      *        successful; if <code>false</code> then read only access will be
    998      *        granted if successful
    999      * @param userID if <code>true</code>, a user ID is required in the reply;
   1000      *        if <code>false</code>, no user ID is required
   1001      * @throws IllegalArgumentException if the challenge is not 16 bytes long;
   1002      *         if the realm can not be encoded in less then 255 bytes
   1003      * @throws IOException if the encoding scheme ISO 8859-1 is not supported
   1004      */
   1005     public static byte[] computeAuthenticationChallenge(byte[] nonce, String realm, boolean access,
   1006             boolean userID) throws IOException {
   1007         byte[] authChall = null;
   1008 
   1009         if (nonce.length != 16) {
   1010             throw new IllegalArgumentException("Nonce must be 16 bytes long");
   1011         }
   1012 
   1013         /*
   1014          * The authentication challenge is a byte sequence of the following form
   1015          * byte 0: 0x00 - the tag for the challenge
   1016          * byte 1: 0x10 - the length of the challenge; must be 16
   1017          * byte 2-17: the authentication challenge
   1018          * byte 18: 0x01 - the options tag; this is optional in the spec, but
   1019          *                 we are going to include it in every message
   1020          * byte 19: 0x01 - length of the options; must be 1
   1021          * byte 20: the value of the options; bit 0 is set if user ID is
   1022          *          required; bit 1 is set if access mode is read only
   1023          * byte 21: 0x02 - the tag for authentication realm; only included if
   1024          *                 an authentication realm is specified
   1025          * byte 22: the length of the authentication realm; only included if
   1026          *          the authentication realm is specified
   1027          * byte 23: the encoding scheme of the authentication realm; we will use
   1028          *          the ISO 8859-1 encoding scheme since it is part of the KVM
   1029          * byte 24 & up: the realm if one is specified.
   1030          */
   1031         if (realm == null) {
   1032             authChall = new byte[21];
   1033         } else {
   1034             if (realm.length() >= 255) {
   1035                 throw new IllegalArgumentException("Realm must be less then 255 bytes");
   1036             }
   1037             authChall = new byte[24 + realm.length()];
   1038             authChall[21] = 0x02;
   1039             authChall[22] = (byte)(realm.length() + 1);
   1040             authChall[23] = 0x01; // ISO 8859-1 Encoding
   1041             System.arraycopy(realm.getBytes("ISO8859_1"), 0, authChall, 24, realm.length());
   1042         }
   1043 
   1044         // Include the nonce field in the header
   1045         authChall[0] = 0x00;
   1046         authChall[1] = 0x10;
   1047         System.arraycopy(nonce, 0, authChall, 2, 16);
   1048 
   1049         // Include the options header
   1050         authChall[18] = 0x01;
   1051         authChall[19] = 0x01;
   1052         authChall[20] = 0x00;
   1053 
   1054         if (!access) {
   1055             authChall[20] = (byte)(authChall[20] | 0x02);
   1056         }
   1057         if (userID) {
   1058             authChall[20] = (byte)(authChall[20] | 0x01);
   1059         }
   1060 
   1061         return authChall;
   1062     }
   1063 
   1064     /**
   1065      * Return the maximum allowed OBEX packet to transmit.
   1066      * OBEX packets transmitted must be smaller than this value.
   1067      * @param transport Reference to the ObexTransport in use.
   1068      * @return the maximum allowed OBEX packet to transmit
   1069      */
   1070     public static int getMaxTxPacketSize(ObexTransport transport) {
   1071         int size = transport.getMaxTransmitPacketSize();
   1072         return validateMaxPacketSize(size);
   1073     }
   1074 
   1075     /**
   1076      * Return the maximum allowed OBEX packet to receive - used in OBEX connect.
   1077      * @param transport
   1078      * @return he maximum allowed OBEX packet to receive
   1079      */
   1080     public static int getMaxRxPacketSize(ObexTransport transport) {
   1081         int size = transport.getMaxReceivePacketSize();
   1082         return validateMaxPacketSize(size);
   1083     }
   1084 
   1085     private static int validateMaxPacketSize(int size) {
   1086         if(VDBG && (size > MAX_PACKET_SIZE_INT)) Log.w(TAG,
   1087                 "The packet size supported for the connection (" + size + ") is larger"
   1088                 + " than the configured OBEX packet size: " + MAX_PACKET_SIZE_INT);
   1089         if(size != -1) {
   1090             if(size < LOWER_LIMIT_MAX_PACKET_SIZE) {
   1091                 throw new IllegalArgumentException(size + " is less that the lower limit: "
   1092                         + LOWER_LIMIT_MAX_PACKET_SIZE);
   1093             }
   1094             return size;
   1095         }
   1096         return MAX_PACKET_SIZE_INT;
   1097     }
   1098 }
   1099