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