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