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