content: /*
   * Copyright (C) 2015 The Android Open Source Project
   * Copyright (C) 2015 Samsung LSI
   * Copyright (c) 2008-2009, Motorola, Inc.
   *
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * - Redistributions of source code must retain the above copyright notice,
   * this list of conditions and the following disclaimer.
   *
   * - Redistributions in binary form must reproduce the above copyright notice,
   * this list of conditions and the following disclaimer in the documentation
   * and/or other materials provided with the distribution.
   *
   * - Neither the name of the Motorola, Inc. nor the names of its contributors
   * may be used to endorse or promote products derived from this software
   * without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   */
  
  package javax.obex;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.Calendar;
  import java.util.Date;
  import java.util.TimeZone;
  
  import android.util.Log;
  
  /**
   * This class defines a set of helper methods for the implementation of Obex.
   * @hide
   */
  public final class ObexHelper {
  
      private static final String TAG = "ObexHelper";
      public static final boolean VDBG = false;
      /**
       * Defines the basic packet length used by OBEX. Every OBEX packet has the
       * same basic format:<BR>
       * Byte 0: Request or Response Code Byte 1&2: Length of the packet.
       */
      public static final int BASE_PACKET_LENGTH = 3;
  
      /** Prevent object construction of helper class */
      private ObexHelper() {
      }
  
      /**
       * The maximum packet size for OBEX packets that this client can handle. At
       * present, this must be changed for each port. TODO: The max packet size
       * should be the Max incoming MTU minus TODO: L2CAP package headers and
       * RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO:
       * LocalDevice.getProperty().
       * NOTE: This value must be larger than or equal to the L2CAP SDU
       */
      /*
       * android note set as 0xFFFE to match remote MPS
       */
      public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
  
      // The minimum allowed max packet size is 255 according to the OBEX specification
      public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255;
  
      // The length of OBEX Byte Sequency Header Id according to the OBEX specification
      public static final int OBEX_BYTE_SEQ_HEADER_LEN = 0x03;
  
      /**
       * Temporary workaround to be able to push files to Windows 7.
       * TODO: Should be removed as soon as Microsoft updates their driver.
       */
      public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00;
  
      public static final int OBEX_OPCODE_FINAL_BIT_MASK = 0x80;
  
      public static final int OBEX_OPCODE_CONNECT = 0x80;
  
      public static final int OBEX_OPCODE_DISCONNECT = 0x81;
  
      public static final int OBEX_OPCODE_PUT = 0x02;
  
      public static final int OBEX_OPCODE_PUT_FINAL = 0x82;
  
      public static final int OBEX_OPCODE_GET = 0x03;
  
      public static final int OBEX_OPCODE_GET_FINAL = 0x83;
  
      public static final int OBEX_OPCODE_RESERVED = 0x04;
  
      public static final int OBEX_OPCODE_RESERVED_FINAL = 0x84;
  
      public static final int OBEX_OPCODE_SETPATH = 0x85;
  
      public static final int OBEX_OPCODE_ABORT = 0xFF;
  
      public static final int OBEX_AUTH_REALM_CHARSET_ASCII = 0x00;
  
      public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_1 = 0x01;
  
      public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_2 = 0x02;
  
      public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_3 = 0x03;
  
      public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_4 = 0x04;
  
      public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_5 = 0x05;
  
      public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_6 = 0x06;
  
      public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_7 = 0x07;
  
      public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_8 = 0x08;
  
      public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_9 = 0x09;
  
      public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF;
  
      public static final byte OBEX_SRM_ENABLE         = 0x01; // For BT we only need enable/disable
      public static final byte OBEX_SRM_DISABLE        = 0x00;
      public static final byte OBEX_SRM_SUPPORT        = 0x02; // Unused for now
  
      public static final byte OBEX_SRMP_WAIT          = 0x01; // Only SRMP value used by BT
  
      /**
       * Updates the HeaderSet with the headers received in the byte array
       * provided. Invalid headers are ignored.
       * <P>
       * The first two bits of an OBEX Header specifies the type of object that is
       * being sent. The table below specifies the meaning of the high bits.
       * <TABLE>
       * <TR>
       * <TH>Bits 8 and 7</TH>
       * <TH>Value</TH>
       * <TH>Description</TH>
       * </TR>
       * <TR>
       * <TD>00</TD>
       * <TD>0x00</TD>
       * <TD>Null Terminated Unicode text, prefixed with 2 byte unsigned integer</TD>
       * </TR>
       * <TR>
       * <TD>01</TD>
       * <TD>0x40</TD>
       * <TD>Byte Sequence, length prefixed with 2 byte unsigned integer</TD>
       * </TR>
       * <TR>
       * <TD>10</TD>
       * <TD>0x80</TD>
       * <TD>1 byte quantity</TD>
       * </TR>
       * <TR>
       * <TD>11</TD>
       * <TD>0xC0</TD>
       * <TD>4 byte quantity - transmitted in network byte order (high byte first</TD>
       * </TR>
       * </TABLE>
       * This method uses the information in this table to determine the type of
       * Java object to create and passes that object with the full header to
       * setHeader() to update the HeaderSet object. Invalid headers will cause an
       * exception to be thrown. When it is thrown, it is ignored.
       * @param header the HeaderSet to update
       * @param headerArray the byte array containing headers
       * @return the result of the last start body or end body header provided;
       *         the first byte in the result will specify if a body or end of
       *         body is received
       * @throws IOException if an invalid header was found
       */
      public static byte[] updateHeaderSet(HeaderSet header, byte[] headerArray) throws IOException {
          int index = 0;
          int length = 0;
          int headerID;
          byte[] value = null;
          byte[] body = null;
          HeaderSet headerImpl = header;
          try {
              while (index < headerArray.length) {
                  headerID = 0xFF & headerArray[index];
                  switch (headerID & (0xC0)) {
  
                      /*
                       * 0x00 is a unicode null terminate string with the first
                       * two bytes after the header identifier being the length
                       */
                      case 0x00:
                          // Fall through
                          /*
                           * 0x40 is a byte sequence with the first
                           * two bytes after the header identifier being the length
                           */
                      case 0x40:
                          boolean trimTail = true;
                          index++;
                          length = ((0xFF & headerArray[index]) << 8) +
                                   (0xFF & headerArray[index + 1]);
                          index += 2;
                          if (length <= OBEX_BYTE_SEQ_HEADER_LEN) {
                              Log.e(TAG, "Remote sent an OBEX packet with " +
                                    "incorrect header length = " + length);
                              break;
                          }
                          length -= OBEX_BYTE_SEQ_HEADER_LEN;
                          value = new byte[length];
                          System.arraycopy(headerArray, index, value, 0, length);
                          if (length == 0 || (length > 0 && (value[length - 1] != 0))) {
                              trimTail = false;
                          }
                          switch (headerID) {
                              case HeaderSet.TYPE:
                                  try {
                                      // Remove trailing null
                                      if (trimTail == false) {
                                          headerImpl.setHeader(headerID, new String(value, 0,
                                                  value.length, "ISO8859_1"));
                                      } else {
                                          headerImpl.setHeader(headerID, new String(value, 0,
                                                  value.length - 1, "ISO8859_1"));
                                      }
                                  } catch (UnsupportedEncodingException e) {
                                      throw e;
                                  }
                                  break;
  
                              case HeaderSet.AUTH_CHALLENGE:
                                  headerImpl.mAuthChall = new byte[length];
                                  System.arraycopy(headerArray, index, headerImpl.mAuthChall, 0,
                                          length);
                                  break;
  
                              case HeaderSet.AUTH_RESPONSE:
                                  headerImpl.mAuthResp = new byte[length];
                                  System.arraycopy(headerArray, index, headerImpl.mAuthResp, 0,
                                          length);
                                  break;
  
                              case HeaderSet.BODY:
                                  /* Fall Through */
                              case HeaderSet.END_OF_BODY:
                                  body = new byte[length + 1];
                                  body[0] = (byte)headerID;
                                  System.arraycopy(headerArray, index, body, 1, length);
                                  break;
  
                              case HeaderSet.TIME_ISO_8601:
                                  try {
                                      String dateString = new String(value, "ISO8859_1");
                                      Calendar temp = Calendar.getInstance();
                                      if ((dateString.length() == 16)
                                              && (dateString.charAt(15) == 'Z')) {
                                          temp.setTimeZone(TimeZone.getTimeZone("UTC"));
                                      }
                                      temp.set(Calendar.YEAR, Integer.parseInt(dateString.substring(
                                              0, 4)));
                                      temp.set(Calendar.MONTH, Integer.parseInt(dateString.substring(
                                              4, 6)));
                                      temp.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateString
                                              .substring(6, 8)));
                                      temp.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateString
                                              .substring(9, 11)));
                                      temp.set(Calendar.MINUTE, Integer.parseInt(dateString
                                              .substring(11, 13)));
                                      temp.set(Calendar.SECOND, Integer.parseInt(dateString
                                              .substring(13, 15)));
                                      headerImpl.setHeader(HeaderSet.TIME_ISO_8601, temp);
                                  } catch (UnsupportedEncodingException e) {
                                      throw e;
                                  }
                                  break;
  
                              default:
                                  if ((headerID & 0xC0) == 0x00) {
                                      headerImpl.setHeader(headerID, ObexHelper.convertToUnicode(
                                              value, true));
                                  } else {
                                      headerImpl.setHeader(headerID, value);
                                  }
                          }
  
                          index += length;
                          break;
  
                      /*
                       * 0x80 is a byte header.  The only valid byte headers are
                       * the 16 user defined byte headers.
                       */
                      case 0x80:
                          index++;
                          try {
                              headerImpl.setHeader(headerID, Byte.valueOf(headerArray[index]));
                          } catch (Exception e) {
                              // Not a valid header so ignore
                          }
                          index++;
                          break;
  
                      /*
                       * 0xC0 is a 4 byte unsigned integer header and with the
                       * exception of TIME_4_BYTE will be converted to a Long
                       * and added.
                       */
                      case 0xC0:
                          index++;
                          value = new byte[4];
                          System.arraycopy(headerArray, index, value, 0, 4);
                          try {
                              if (headerID != HeaderSet.TIME_4_BYTE) {
                                  // Determine if it is a connection ID.  These
                                  // need to be handled differently
                                  if (headerID == HeaderSet.CONNECTION_ID) {
                                      headerImpl.mConnectionID = new byte[4];
                                      System.arraycopy(value, 0, headerImpl.mConnectionID, 0, 4);
                                  } else {
                                      headerImpl.setHeader(headerID, Long
                                              .valueOf(convertToLong(value)));
                                  }
                              } else {
                                  Calendar temp = Calendar.getInstance();
                                  temp.setTime(new Date(convertToLong(value) * 1000L));
                                  headerImpl.setHeader(HeaderSet.TIME_4_BYTE, temp);
                              }
                          } catch (Exception e) {
                              // Not a valid header so ignore
                              throw new IOException("Header was not formatted properly", e);
                          }
                          index += 4;
                          break;
                  }
  
              }
          } catch (IOException e) {
              throw new IOException("Header was not formatted properly", e);
          }
  
          return body;
      }
  
      /**
       * Creates the header part of OBEX packet based on the header provided.
       * TODO: Could use getHeaderList() to get the array of headers to include
       * and then use the high two bits to determine the the type of the object
       * and construct the byte array from that. This will make the size smaller.
       * @param head the header used to construct the byte array
       * @param nullOut <code>true</code> if the header should be set to
       *        <code>null</code> once it is added to the array or
       *        <code>false</code> if it should not be nulled out
       * @return the header of an OBEX packet
       */
      public static byte[] createHeader(HeaderSet head, boolean nullOut) {
          Long intHeader = null;
          String stringHeader = null;
          Calendar dateHeader = null;
          Byte byteHeader = null;
          StringBuffer buffer = null;
          byte[] value = null;
          byte[] result = null;
          byte[] lengthArray = new byte[2];
          int length;
          HeaderSet headImpl = null;
          ByteArrayOutputStream out = new ByteArrayOutputStream();
          headImpl = head;
  
          try {
              /*
               * Determine if there is a connection ID to send.  If there is,
               * then it should be the first header in the packet.
               */
              if ((headImpl.mConnectionID != null) && (headImpl.getHeader(HeaderSet.TARGET) == null)) {
  
                  out.write((byte)HeaderSet.CONNECTION_ID);
                  out.write(headImpl.mConnectionID);
              }
  
              // Count Header
              intHeader = (Long)headImpl.getHeader(HeaderSet.COUNT);
              if (intHeader != null) {
                  out.write((byte)HeaderSet.COUNT);
                  value = ObexHelper.convertToByteArray(intHeader.longValue());
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.COUNT, null);
                  }
              }
  
              // Name Header
              stringHeader = (String)headImpl.getHeader(HeaderSet.NAME);
              if (stringHeader != null) {
                  out.write((byte)HeaderSet.NAME);
                  value = ObexHelper.convertToUnicodeByteArray(stringHeader);
                  length = value.length + 3;
                  lengthArray[0] = (byte)(0xFF & (length >> 8));
                  lengthArray[1] = (byte)(0xFF & length);
                  out.write(lengthArray);
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.NAME, null);
                  }
              } else if (headImpl.getEmptyNameHeader()) {
                  out.write((byte) HeaderSet.NAME);
                  lengthArray[0] = (byte) 0x00;
                  lengthArray[1] = (byte) 0x03;
                  out.write(lengthArray);
              }
  
              // Type Header
              stringHeader = (String)headImpl.getHeader(HeaderSet.TYPE);
              if (stringHeader != null) {
                  out.write((byte)HeaderSet.TYPE);
                  try {
                      value = stringHeader.getBytes("ISO8859_1");
                  } catch (UnsupportedEncodingException e) {
                      throw e;
                  }
  
                  length = value.length + 4;
                  lengthArray[0] = (byte)(255 & (length >> 8));
                  lengthArray[1] = (byte)(255 & length);
                  out.write(lengthArray);
                  out.write(value);
                  out.write(0x00);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.TYPE, null);
                  }
              }
  
              // Length Header
              intHeader = (Long)headImpl.getHeader(HeaderSet.LENGTH);
              if (intHeader != null) {
                  out.write((byte)HeaderSet.LENGTH);
                  value = ObexHelper.convertToByteArray(intHeader.longValue());
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.LENGTH, null);
                  }
              }
  
              // Time ISO Header
              dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_ISO_8601);
              if (dateHeader != null) {
  
                  /*
                   * The ISO Header should take the form YYYYMMDDTHHMMSSZ.  The
                   * 'Z' will only be included if it is a UTC time.
                   */
                  buffer = new StringBuffer();
                  int temp = dateHeader.get(Calendar.YEAR);
                  for (int i = temp; i < 1000; i = i * 10) {
                      buffer.append("0");
                  }
                  buffer.append(temp);
                  temp = dateHeader.get(Calendar.MONTH);
                  if (temp < 10) {
                      buffer.append("0");
                  }
                  buffer.append(temp);
                  temp = dateHeader.get(Calendar.DAY_OF_MONTH);
                  if (temp < 10) {
                      buffer.append("0");
                  }
                  buffer.append(temp);
                  buffer.append("T");
                  temp = dateHeader.get(Calendar.HOUR_OF_DAY);
                  if (temp < 10) {
                      buffer.append("0");
                  }
                  buffer.append(temp);
                  temp = dateHeader.get(Calendar.MINUTE);
                  if (temp < 10) {
                      buffer.append("0");
                  }
                  buffer.append(temp);
                  temp = dateHeader.get(Calendar.SECOND);
                  if (temp < 10) {
                      buffer.append("0");
                  }
                  buffer.append(temp);
  
                  if (dateHeader.getTimeZone().getID().equals("UTC")) {
                      buffer.append("Z");
                  }
  
                  try {
                      value = buffer.toString().getBytes("ISO8859_1");
                  } catch (UnsupportedEncodingException e) {
                      throw e;
                  }
  
                  length = value.length + 3;
                  lengthArray[0] = (byte)(255 & (length >> 8));
                  lengthArray[1] = (byte)(255 & length);
                  out.write(HeaderSet.TIME_ISO_8601);
                  out.write(lengthArray);
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.TIME_ISO_8601, null);
                  }
              }
  
              // Time 4 Byte Header
              dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_4_BYTE);
              if (dateHeader != null) {
                  out.write(HeaderSet.TIME_4_BYTE);
  
                  /*
                   * Need to call getTime() twice.  The first call will return
                   * a java.util.Date object.  The second call returns the number
                   * of milliseconds since January 1, 1970.  We need to convert
                   * it to seconds since the TIME_4_BYTE expects the number of
                   * seconds since January 1, 1970.
                   */
                  value = ObexHelper.convertToByteArray(dateHeader.getTime().getTime() / 1000L);
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.TIME_4_BYTE, null);
                  }
              }
  
              // Description Header
              stringHeader = (String)headImpl.getHeader(HeaderSet.DESCRIPTION);
              if (stringHeader != null) {
                  out.write((byte)HeaderSet.DESCRIPTION);
                  value = ObexHelper.convertToUnicodeByteArray(stringHeader);
                  length = value.length + 3;
                  lengthArray[0] = (byte)(255 & (length >> 8));
                  lengthArray[1] = (byte)(255 & length);
                  out.write(lengthArray);
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.DESCRIPTION, null);
                  }
              }
  
              // Target Header
              value = (byte[])headImpl.getHeader(HeaderSet.TARGET);
              if (value != null) {
                  out.write((byte)HeaderSet.TARGET);
                  length = value.length + 3;
                  lengthArray[0] = (byte)(255 & (length >> 8));
                  lengthArray[1] = (byte)(255 & length);
                  out.write(lengthArray);
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.TARGET, null);
                  }
              }
  
              // HTTP Header
              value = (byte[])headImpl.getHeader(HeaderSet.HTTP);
              if (value != null) {
                  out.write((byte)HeaderSet.HTTP);
                  length = value.length + 3;
                  lengthArray[0] = (byte)(255 & (length >> 8));
                  lengthArray[1] = (byte)(255 & length);
                  out.write(lengthArray);
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.HTTP, null);
                  }
              }
  
              // Who Header
              value = (byte[])headImpl.getHeader(HeaderSet.WHO);
              if (value != null) {
                  out.write((byte)HeaderSet.WHO);
                  length = value.length + 3;
                  lengthArray[0] = (byte)(255 & (length >> 8));
                  lengthArray[1] = (byte)(255 & length);
                  out.write(lengthArray);
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.WHO, null);
                  }
              }
  
              // Connection ID Header
              value = (byte[])headImpl.getHeader(HeaderSet.APPLICATION_PARAMETER);
              if (value != null) {
                  out.write((byte)HeaderSet.APPLICATION_PARAMETER);
                  length = value.length + 3;
                  lengthArray[0] = (byte)(255 & (length >> 8));
                  lengthArray[1] = (byte)(255 & length);
                  out.write(lengthArray);
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.APPLICATION_PARAMETER, null);
                  }
              }
  
              // Object Class Header
              value = (byte[])headImpl.getHeader(HeaderSet.OBJECT_CLASS);
              if (value != null) {
                  out.write((byte)HeaderSet.OBJECT_CLASS);
                  length = value.length + 3;
                  lengthArray[0] = (byte)(255 & (length >> 8));
                  lengthArray[1] = (byte)(255 & length);
                  out.write(lengthArray);
                  out.write(value);
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.OBJECT_CLASS, null);
                  }
              }
  
              // Check User Defined Headers
              for (int i = 0; i < 16; i++) {
  
                  //Unicode String Header
                  stringHeader = (String)headImpl.getHeader(i + 0x30);
                  if (stringHeader != null) {
                      out.write((byte)i + 0x30);
                      value = ObexHelper.convertToUnicodeByteArray(stringHeader);
                      length = value.length + 3;
                      lengthArray[0] = (byte)(255 & (length >> 8));
                      lengthArray[1] = (byte)(255 & length);
                      out.write(lengthArray);
                      out.write(value);
                      if (nullOut) {
                          headImpl.setHeader(i + 0x30, null);
                      }
                  }
  
                  // Byte Sequence Header
                  value = (byte[])headImpl.getHeader(i + 0x70);
                  if (value != null) {
                      out.write((byte)i + 0x70);
                      length = value.length + 3;
                      lengthArray[0] = (byte)(255 & (length >> 8));
                      lengthArray[1] = (byte)(255 & length);
                      out.write(lengthArray);
                      out.write(value);
                      if (nullOut) {
                          headImpl.setHeader(i + 0x70, null);
                      }
                  }
  
                  // Byte Header
                  byteHeader = (Byte)headImpl.getHeader(i + 0xB0);
                  if (byteHeader != null) {
                      out.write((byte)i + 0xB0);
                      out.write(byteHeader.byteValue());
                      if (nullOut) {
                          headImpl.setHeader(i + 0xB0, null);
                      }
                  }
  
                  // Integer header
                  intHeader = (Long)headImpl.getHeader(i + 0xF0);
                  if (intHeader != null) {
                      out.write((byte)i + 0xF0);
                      out.write(ObexHelper.convertToByteArray(intHeader.longValue()));
                      if (nullOut) {
                          headImpl.setHeader(i + 0xF0, null);
                      }
                  }
              }
  
              // Add the authentication challenge header
              if (headImpl.mAuthChall != null) {
                  out.write((byte)HeaderSet.AUTH_CHALLENGE);
                  length = headImpl.mAuthChall.length + 3;
                  lengthArray[0] = (byte)(255 & (length >> 8));
                  lengthArray[1] = (byte)(255 & length);
                  out.write(lengthArray);
                  out.write(headImpl.mAuthChall);
                  if (nullOut) {
                      headImpl.mAuthChall = null;
                  }
              }
  
              // Add the authentication response header
              if (headImpl.mAuthResp != null) {
                  out.write((byte)HeaderSet.AUTH_RESPONSE);
                  length = headImpl.mAuthResp.length + 3;
                  lengthArray[0] = (byte)(255 & (length >> 8));
                  lengthArray[1] = (byte)(255 & length);
                  out.write(lengthArray);
                  out.write(headImpl.mAuthResp);
                  if (nullOut) {
                      headImpl.mAuthResp = null;
                  }
              }
  
              // TODO:
              // If the SRM and SRMP header is in use, they must be send in the same OBEX packet
              // But the current structure of the obex code cannot handle this, and therefore
              // it makes sense to put them in the tail of the headers, since we then reduce the
              // chance of enabling SRM to soon. The down side is that SRM cannot be used while
              // transferring non-body headers
  
              // Add the SRM header
              byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
              if (byteHeader != null) {
                  out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE);
                  out.write(byteHeader.byteValue());
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, null);
                  }
              }
  
              // Add the SRM parameter header
              byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
              if (byteHeader != null) {
                  out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
                  out.write(byteHeader.byteValue());
                  if (nullOut) {
                      headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
                  }
              }
  
          } catch (IOException e) {
          } finally {
              result = out.toByteArray();
              try {
                  out.close();
              } catch (Exception ex) {
              }
          }
  
          return result;
  
      }
  
      /**
       * Determines where the maximum divide is between headers. This method is
       * used by put and get operations to separate headers to a size that meets
       * the max packet size allowed.
       * @param headerArray the headers to separate
       * @param start the starting index to search
       * @param maxSize the maximum size of a packet
       * @return the index of the end of the header block to send or -1 if the
       *         header could not be divided because the header is too large
       */
      public static int findHeaderEnd(byte[] headerArray, int start, int maxSize) {
  
          int fullLength = 0;
          int lastLength = -1;
          int index = start;
          int length = 0;
  
          // TODO: Ensure SRM and SRMP headers are not split into two OBEX packets
  
          while ((fullLength < maxSize) && (index < headerArray.length)) {
              int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]);
              lastLength = fullLength;
  
              switch (headerID & (0xC0)) {
  
                  case 0x00:
                      // Fall through
                  case 0x40:
  
                      index++;
                      length = (headerArray[index] < 0 ? headerArray[index] + 256
                              : headerArray[index]);
                      length = length << 8;
                      index++;
                      length += (headerArray[index] < 0 ? headerArray[index] + 256
                              : headerArray[index]);
                      length -= 3;
                      index++;
                      index += length;
                      fullLength += length + 3;
                      break;
  
                  case 0x80:
  
                      index++;
                      index++;
                      fullLength += 2;
                      break;
  
                  case 0xC0:
  
                      index += 5;
                      fullLength += 5;
                      break;
  
              }
  
          }
  
          /*
           * Determine if this is the last header or not
           */
          if (lastLength == 0) {
              /*
               * Since this is the last header, check to see if the size of this
               * header is less then maxSize.  If it is, return the length of the
               * header, otherwise return -1.  The length of the header is
               * returned since it would be the start of the next header
               */
              if (fullLength < maxSize) {
                  return headerArray.length;
              } else {
                  return -1;
              }
          } else {
              return lastLength + start;
          }
      }
  
      /**
       * Converts the byte array to a long.
       * @param b the byte array to convert to a long
       * @return the byte array as a long
       */
      public static long convertToLong(byte[] b) {
          long result = 0;
          long value = 0;
          long power = 0;
  
          for (int i = (b.length - 1); i >= 0; i--) {
              value = b[i];
              if (value < 0) {
                  value += 256;
              }
  
              result = result | (value << power);
              power += 8;
          }
  
          return result;
      }
  
      /**
       * Converts the long to a 4 byte array. The long must be non negative.
       * @param l the long to convert
       * @return a byte array that is the same as the long
       */
      public static byte[] convertToByteArray(long l) {
          byte[] b = new byte[4];
  
          b[0] = (byte)(255 & (l >> 24));
          b[1] = (byte)(255 & (l >> 16));
          b[2] = (byte)(255 & (l >> 8));
          b[3] = (byte)(255 & l);
  
          return b;
      }
  
      /**
       * Converts the String to a UNICODE byte array. It will also add the ending
       * null characters to the end of the string.
       * @param s the string to convert
       * @return the unicode byte array of the string
       */
      public static byte[] convertToUnicodeByteArray(String s) {
          if (s == null) {
              return null;
          }
  
          char c[] = s.toCharArray();
          byte[] result = new byte[(c.length * 2) + 2];
          for (int i = 0; i < c.length; i++) {
              result[(i * 2)] = (byte)(c[i] >> 8);
              result[((i * 2) + 1)] = (byte)c[i];
          }
  
          // Add the UNICODE null character
          result[result.length - 2] = 0;
          result[result.length - 1] = 0;
  
          return result;
      }
  
      /**
       * Retrieves the value from the byte array for the tag value specified. The
       * array should be of the form Tag - Length - Value triplet.
       * @param tag the tag to retrieve from the byte array
       * @param triplet the byte sequence containing the tag length value form
       * @return the value of the specified tag
       */
      public static byte[] getTagValue(byte tag, byte[] triplet) {
  
          int index = findTag(tag, triplet);
          if (index == -1) {
              return null;
          }
  
          index++;
          int length = triplet[index] & 0xFF;
  
          byte[] result = new byte[length];
          index++;
          System.arraycopy(triplet, index, result, 0, length);
  
          return result;
      }
  
      /**
       * Finds the index that starts the tag value pair in the byte array provide.
       * @param tag the tag to look for
       * @param value the byte array to search
       * @return the starting index of the tag or -1 if the tag could not be found
       */
      public static int findTag(byte tag, byte[] value) {
          int length = 0;
  
          if (value == null) {
              return -1;
          }
  
          int index = 0;
  
          while ((index < value.length) && (value[index] != tag)) {
              length = value[index + 1] & 0xFF;
              index += length + 2;
          }
  
          if (index >= value.length) {
              return -1;
          }
  
          return index;
      }
  
      /**
       * Converts the byte array provided to a unicode string.
       * @param b the byte array to convert to a string
       * @param includesNull determine if the byte string provided contains the
       *        UNICODE null character at the end or not; if it does, it will be
       *        removed
       * @return a Unicode string
       * @throws IllegalArgumentException if the byte array has an odd length
       */
      public static String convertToUnicode(byte[] b, boolean includesNull) {
          if (b == null || b.length == 0) {
              return null;
          }
          int arrayLength = b.length;
          if (!((arrayLength % 2) == 0)) {
              throw new IllegalArgumentException("Byte array not of a valid form");
          }
          arrayLength = (arrayLength >> 1);
          if (includesNull) {
              arrayLength -= 1;
          }
  
          char[] c = new char[arrayLength];
          for (int i = 0; i < arrayLength; i++) {
              int upper = b[2 * i];
              int lower = b[(2 * i) + 1];
              if (upper < 0) {
                  upper += 256;
              }
              if (lower < 0) {
                  lower += 256;
              }
              // If upper and lower both equal 0, it should be the end of string.
              // Ignore left bytes from array to avoid potential issues
              if (upper == 0 && lower == 0) {
                  return new String(c, 0, i);
              }
  
              c[i] = (char)((upper << 8) | lower);
          }
  
          return new String(c);
      }
  
      /**
       * Compute the MD5 hash of the byte array provided. Does not accumulate
       * input.
       * @param in the byte array to hash
       * @return the MD5 hash of the byte array
       */
      public static byte[] computeMd5Hash(byte[] in) {
          try {
              MessageDigest md5 = MessageDigest.getInstance("MD5");
              return md5.digest(in);
          } catch (NoSuchAlgorithmException e) {
              throw new RuntimeException(e);
          }
      }
  
      /**
       * Computes an authentication challenge header.
       * @param nonce the challenge that will be provided to the peer; the
       *        challenge must be 16 bytes long
       * @param realm a short description that describes what password to use
       * @param access if <code>true</code> then full access will be granted if
       *        successful; if <code>false</code> then read only access will be
       *        granted if successful
       * @param userID if <code>true</code>, a user ID is required in the reply;
       *        if <code>false</code>, no user ID is required
       * @throws IllegalArgumentException if the challenge is not 16 bytes long;
       *         if the realm can not be encoded in less then 255 bytes
       * @throws IOException if the encoding scheme ISO 8859-1 is not supported
       */
      public static byte[] computeAuthenticationChallenge(byte[] nonce, String realm, boolean access,
              boolean userID) throws IOException {
          byte[] authChall = null;
  
          if (nonce.length != 16) {
              throw new IllegalArgumentException("Nonce must be 16 bytes long");
          }
  
          /*
           * The authentication challenge is a byte sequence of the following form
           * byte 0: 0x00 - the tag for the challenge
           * byte 1: 0x10 - the length of the challenge; must be 16
           * byte 2-17: the authentication challenge
           * byte 18: 0x01 - the options tag; this is optional in the spec, but
           *                 we are going to include it in every message
           * byte 19: 0x01 - length of the options; must be 1
           * byte 20: the value of the options; bit 0 is set if user ID is
           *          required; bit 1 is set if access mode is read only
           * byte 21: 0x02 - the tag for authentication realm; only included if
           *                 an authentication realm is specified
           * byte 22: the length of the authentication realm; only included if
           *          the authentication realm is specified
           * byte 23: the encoding scheme of the authentication realm; we will use
           *          the ISO 8859-1 encoding scheme since it is part of the KVM
           * byte 24 & up: the realm if one is specified.
           */
          if (realm == null) {
              authChall = new byte[21];
          } else {
              if (realm.length() >= 255) {
                  throw new IllegalArgumentException("Realm must be less then 255 bytes");
              }
              authChall = new byte[24 + realm.length()];
              authChall[21] = 0x02;
              authChall[22] = (byte)(realm.length() + 1);
              authChall[23] = 0x01; // ISO 8859-1 Encoding
              System.arraycopy(realm.getBytes("ISO8859_1"), 0, authChall, 24, realm.length());
          }
  
          // Include the nonce field in the header
          authChall[0] = 0x00;
          authChall[1] = 0x10;
          System.arraycopy(nonce, 0, authChall, 2, 16);
  
          // Include the options header
          authChall[18] = 0x01;
          authChall[19] = 0x01;
          authChall[20] = 0x00;
  
          if (!access) {
              authChall[20] = (byte)(authChall[20] | 0x02);
          }
          if (userID) {
              authChall[20] = (byte)(authChall[20] | 0x01);
          }
  
          return authChall;
      }
  
      /**
       * Return the maximum allowed OBEX packet to transmit.
       * OBEX packets transmitted must be smaller than this value.
       * @param transport Reference to the ObexTransport in use.
       * @return the maximum allowed OBEX packet to transmit
       */
      public static int getMaxTxPacketSize(ObexTransport transport) {
          int size = transport.getMaxTransmitPacketSize();
          return validateMaxPacketSize(size);
      }
  
      /**
       * Return the maximum allowed OBEX packet to receive - used in OBEX connect.
       * @param transport
       * @return he maximum allowed OBEX packet to receive
       */
      public static int getMaxRxPacketSize(ObexTransport transport) {
          int size = transport.getMaxReceivePacketSize();
          return validateMaxPacketSize(size);
      }
  
      private static int validateMaxPacketSize(int size) {
          if(VDBG && (size > MAX_PACKET_SIZE_INT)) Log.w(TAG,
                  "The packet size supported for the connection (" + size + ") is larger"
                  + " than the configured OBEX packet size: " + MAX_PACKET_SIZE_INT);
          if(size != -1) {
              if(size < LOWER_LIMIT_MAX_PACKET_SIZE) {
                  throw new IllegalArgumentException(size + " is less that the lower limit: "
                          + LOWER_LIMIT_MAX_PACKET_SIZE);
              }
              return size;
          }
          return MAX_PACKET_SIZE_INT;
      }
  }
  