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