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