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