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