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