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