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