1 /* 2 * Copyright (C) 2007-2008 Esmertec AG. 3 * Copyright (C) 2007-2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.messaging.mmslib.pdu; 19 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.support.v4.util.SimpleArrayMap; 23 import android.text.TextUtils; 24 25 import java.io.ByteArrayOutputStream; 26 import java.io.FileNotFoundException; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.util.Arrays; 30 31 public class PduComposer { 32 /** 33 * Address type. 34 */ 35 private static final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1; 36 private static final int PDU_EMAIL_ADDRESS_TYPE = 2; 37 private static final int PDU_IPV4_ADDRESS_TYPE = 3; 38 private static final int PDU_IPV6_ADDRESS_TYPE = 4; 39 private static final int PDU_UNKNOWN_ADDRESS_TYPE = 5; 40 41 /** 42 * Address regular expression string. 43 */ 44 static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+"; 45 46 static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" + 47 "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}"; 48 49 static final String REGEXP_IPV6_ADDRESS_TYPE = 50 "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" + 51 "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" + 52 "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}"; 53 54 static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" + 55 "[0-9]{1,3}\\.{1}[0-9]{1,3}"; 56 57 /** 58 * The postfix strings of address. 59 */ 60 static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN"; 61 static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4"; 62 static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6"; 63 64 /** 65 * Error values. 66 */ 67 private static final int PDU_COMPOSE_SUCCESS = 0; 68 private static final int PDU_COMPOSE_CONTENT_ERROR = 1; 69 private static final int PDU_COMPOSE_FIELD_NOT_SET = 2; 70 private static final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3; 71 72 /** 73 * WAP values defined in WSP spec. 74 */ 75 private static final int QUOTED_STRING_FLAG = 34; 76 private static final int END_STRING_FLAG = 0; 77 private static final int LENGTH_QUOTE = 31; 78 private static final int TEXT_MAX = 127; 79 private static final int SHORT_INTEGER_MAX = 127; 80 private static final int LONG_INTEGER_LENGTH_MAX = 8; 81 82 /** 83 * Block size when read data from InputStream. 84 */ 85 private static final int PDU_COMPOSER_BLOCK_SIZE = 1024; 86 87 /** 88 * The output message. 89 */ 90 protected ByteArrayOutputStream mMessage = null; 91 92 /** 93 * The PDU. 94 */ 95 private GenericPdu mPdu = null; 96 97 /** 98 * Current visiting position of the mMessage. 99 */ 100 protected int mPosition = 0; 101 102 /** 103 * Message compose buffer stack. 104 */ 105 private BufferStack mStack = null; 106 107 /** 108 * Content resolver. 109 */ 110 private final ContentResolver mResolver; 111 112 /** 113 * Header of this pdu. 114 */ 115 private PduHeaders mPduHeader = null; 116 117 /** 118 * Map of all content type 119 */ 120 private static SimpleArrayMap<String, Integer> mContentTypeMap = null; 121 122 static { 123 mContentTypeMap = new SimpleArrayMap<String, Integer>(); 124 125 int i; 126 for (i = 0; i < PduContentTypes.contentTypes.length; i++) { 127 mContentTypeMap.put(PduContentTypes.contentTypes[i], i); 128 } 129 } 130 131 /** 132 * Constructor. 133 * 134 * @param context the context 135 * @param pdu the pdu to be composed 136 */ 137 public PduComposer(final Context context, final GenericPdu pdu) { 138 mPdu = pdu; 139 mResolver = context.getContentResolver(); 140 mPduHeader = pdu.getPduHeaders(); 141 mStack = new BufferStack(); 142 mMessage = new ByteArrayOutputStream(); 143 mPosition = 0; 144 } 145 146 /** 147 * Make the message. No need to check whether mandatory fields are set, 148 * because the constructors of outgoing pdus are taking care of this. 149 * 150 * @return OutputStream of maked message. Return null if 151 * the PDU is invalid. 152 */ 153 public byte[] make() { 154 // Get Message-type. 155 final int type = mPdu.getMessageType(); 156 157 /* make the message */ 158 switch (type) { 159 case PduHeaders.MESSAGE_TYPE_SEND_REQ: 160 if (makeSendReqPdu() != PDU_COMPOSE_SUCCESS) { 161 return null; 162 } 163 break; 164 case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: 165 if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) { 166 return null; 167 } 168 break; 169 case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: 170 if (makeAckInd() != PDU_COMPOSE_SUCCESS) { 171 return null; 172 } 173 break; 174 case PduHeaders.MESSAGE_TYPE_READ_REC_IND: 175 if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) { 176 return null; 177 } 178 break; 179 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 180 if (makeNotificationInd() != PDU_COMPOSE_SUCCESS) { 181 return null; 182 } 183 break; 184 default: 185 return null; 186 } 187 188 return mMessage.toByteArray(); 189 } 190 191 /** 192 * Copy buf to mMessage. 193 */ 194 protected void arraycopy(final byte[] buf, final int pos, final int length) { 195 mMessage.write(buf, pos, length); 196 mPosition = mPosition + length; 197 } 198 199 /** 200 * Append a byte to mMessage. 201 */ 202 protected void append(final int value) { 203 mMessage.write(value); 204 mPosition++; 205 } 206 207 /** 208 * Append short integer value to mMessage. 209 * This implementation doesn't check the validity of parameter, since it 210 * assumes that the values are validated in the GenericPdu setter methods. 211 */ 212 protected void appendShortInteger(final int value) { 213 /* 214 * From WAP-230-WSP-20010705-a: 215 * Short-integer = OCTET 216 * ; Integers in range 0-127 shall be encoded as a one octet value 217 * ; with the most significant bit set to one (1xxx xxxx) and with 218 * ; the value in the remaining least significant bits. 219 * In our implementation, only low 7 bits are stored and otherwise 220 * bits are ignored. 221 */ 222 append((value | 0x80) & 0xff); 223 } 224 225 /** 226 * Append an octet number between 128 and 255 into mMessage. 227 * NOTE: 228 * A value between 0 and 127 should be appended by using appendShortInteger. 229 * This implementation doesn't check the validity of parameter, since it 230 * assumes that the values are validated in the GenericPdu setter methods. 231 */ 232 protected void appendOctet(final int number) { 233 append(number); 234 } 235 236 /** 237 * Append a short length into mMessage. 238 * This implementation doesn't check the validity of parameter, since it 239 * assumes that the values are validated in the GenericPdu setter methods. 240 */ 241 protected void appendShortLength(final int value) { 242 /* 243 * From WAP-230-WSP-20010705-a: 244 * Short-length = <Any octet 0-30> 245 */ 246 append(value); 247 } 248 249 /** 250 * Append long integer into mMessage. it's used for really long integers. 251 * This implementation doesn't check the validity of parameter, since it 252 * assumes that the values are validated in the GenericPdu setter methods. 253 */ 254 protected void appendLongInteger(final long longInt) { 255 /* 256 * From WAP-230-WSP-20010705-a: 257 * Long-integer = Short-length Multi-octet-integer 258 * ; The Short-length indicates the length of the Multi-octet-integer 259 * Multi-octet-integer = 1*30 OCTET 260 * ; The content octets shall be an unsigned integer value with the 261 * ; most significant octet encoded first (big-endian representation). 262 * ; The minimum number of octets must be used to encode the value. 263 */ 264 int size; 265 long temp = longInt; 266 267 // Count the length of the long integer. 268 for (size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) { 269 temp = (temp >>> 8); 270 } 271 272 // Set Length. 273 appendShortLength(size); 274 275 // Count and set the long integer. 276 int i; 277 int shift = (size - 1) * 8; 278 279 for (i = 0; i < size; i++) { 280 append((int) ((longInt >>> shift) & 0xff)); 281 shift = shift - 8; 282 } 283 } 284 285 /** 286 * Append text string into mMessage. 287 * This implementation doesn't check the validity of parameter, since it 288 * assumes that the values are validated in the GenericPdu setter methods. 289 */ 290 protected void appendTextString(final byte[] text) { 291 /* 292 * From WAP-230-WSP-20010705-a: 293 * Text-string = [Quote] *TEXT End-of-string 294 * ; If the first character in the TEXT is in the range of 128-255, 295 * ; a Quote character must precede it. Otherwise the Quote character 296 * ;must be omitted. The Quote is not part of the contents. 297 */ 298 if (((text[0]) & 0xff) > TEXT_MAX) { // No need to check for <= 255 299 append(TEXT_MAX); 300 } 301 302 arraycopy(text, 0, text.length); 303 append(0); 304 } 305 306 /** 307 * Append text string into mMessage. 308 * This implementation doesn't check the validity of parameter, since it 309 * assumes that the values are validated in the GenericPdu setter methods. 310 */ 311 protected void appendTextString(final String str) { 312 /* 313 * From WAP-230-WSP-20010705-a: 314 * Text-string = [Quote] *TEXT End-of-string 315 * ; If the first character in the TEXT is in the range of 128-255, 316 * ; a Quote character must precede it. Otherwise the Quote character 317 * ;must be omitted. The Quote is not part of the contents. 318 */ 319 appendTextString(str.getBytes()); 320 } 321 322 /** 323 * Append encoded string value to mMessage. 324 * This implementation doesn't check the validity of parameter, since it 325 * assumes that the values are validated in the GenericPdu setter methods. 326 */ 327 protected void appendEncodedString(final EncodedStringValue enStr) { 328 /* 329 * From OMA-TS-MMS-ENC-V1_3-20050927-C: 330 * Encoded-string-value = Text-string | Value-length Char-set Text-string 331 */ 332 assert (enStr != null); 333 334 final int charset = enStr.getCharacterSet(); 335 final byte[] textString = enStr.getTextString(); 336 if (null == textString) { 337 return; 338 } 339 340 /* 341 * In the implementation of EncodedStringValue, the charset field will 342 * never be 0. It will always be composed as 343 * Encoded-string-value = Value-length Char-set Text-string 344 */ 345 mStack.newbuf(); 346 final PositionMarker start = mStack.mark(); 347 348 appendShortInteger(charset); 349 appendTextString(textString); 350 351 final int len = start.getLength(); 352 mStack.pop(); 353 appendValueLength(len); 354 mStack.copy(); 355 } 356 357 /** 358 * Append uintvar integer into mMessage. 359 * This implementation doesn't check the validity of parameter, since it 360 * assumes that the values are validated in the GenericPdu setter methods. 361 */ 362 protected void appendUintvarInteger(final long value) { 363 /* 364 * From WAP-230-WSP-20010705-a: 365 * To encode a large unsigned integer, split it into 7-bit fragments 366 * and place them in the payloads of multiple octets. The most significant 367 * bits are placed in the first octets with the least significant bits 368 * ending up in the last octet. All octets MUST set the Continue bit to 1 369 * except the last octet, which MUST set the Continue bit to 0. 370 */ 371 int i; 372 long max = SHORT_INTEGER_MAX; 373 374 for (i = 0; i < 5; i++) { 375 if (value < max) { 376 break; 377 } 378 379 max = (max << 7) | 0x7fL; 380 } 381 382 while (i > 0) { 383 long temp = value >>> (i * 7); 384 temp = temp & 0x7f; 385 386 append((int) ((temp | 0x80) & 0xff)); 387 388 i--; 389 } 390 391 append((int) (value & 0x7f)); 392 } 393 394 /** 395 * Append date value into mMessage. 396 * This implementation doesn't check the validity of parameter, since it 397 * assumes that the values are validated in the GenericPdu setter methods. 398 */ 399 protected void appendDateValue(final long date) { 400 /* 401 * From OMA-TS-MMS-ENC-V1_3-20050927-C: 402 * Date-value = Long-integer 403 */ 404 appendLongInteger(date); 405 } 406 407 /** 408 * Append value length to mMessage. 409 * This implementation doesn't check the validity of parameter, since it 410 * assumes that the values are validated in the GenericPdu setter methods. 411 */ 412 protected void appendValueLength(final long value) { 413 /* 414 * From WAP-230-WSP-20010705-a: 415 * Value-length = Short-length | (Length-quote Length) 416 * ; Value length is used to indicate the length of the value to follow 417 * Short-length = <Any octet 0-30> 418 * Length-quote = <Octet 31> 419 * Length = Uintvar-integer 420 */ 421 if (value < LENGTH_QUOTE) { 422 appendShortLength((int) value); 423 return; 424 } 425 426 append(LENGTH_QUOTE); 427 appendUintvarInteger(value); 428 } 429 430 /** 431 * Append quoted string to mMessage. 432 * This implementation doesn't check the validity of parameter, since it 433 * assumes that the values are validated in the GenericPdu setter methods. 434 */ 435 protected void appendQuotedString(final byte[] text) { 436 /* 437 * From WAP-230-WSP-20010705-a: 438 * Quoted-string = <Octet 34> *TEXT End-of-string 439 * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing 440 * ;quotation-marks <"> removed. 441 */ 442 append(QUOTED_STRING_FLAG); 443 arraycopy(text, 0, text.length); 444 append(END_STRING_FLAG); 445 } 446 447 /** 448 * Append quoted string to mMessage. 449 * This implementation doesn't check the validity of parameter, since it 450 * assumes that the values are validated in the GenericPdu setter methods. 451 */ 452 protected void appendQuotedString(final String str) { 453 /* 454 * From WAP-230-WSP-20010705-a: 455 * Quoted-string = <Octet 34> *TEXT End-of-string 456 * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing 457 * ;quotation-marks <"> removed. 458 */ 459 appendQuotedString(str.getBytes()); 460 } 461 462 private EncodedStringValue appendAddressType(final EncodedStringValue address) { 463 EncodedStringValue temp = null; 464 465 try { 466 final int addressType = checkAddressType(address.getString()); 467 temp = EncodedStringValue.copy(address); 468 if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) { 469 // Phone number. 470 temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes()); 471 } else if (PDU_IPV4_ADDRESS_TYPE == addressType) { 472 // Ipv4 address. 473 temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes()); 474 } else if (PDU_IPV6_ADDRESS_TYPE == addressType) { 475 // Ipv6 address. 476 temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes()); 477 } 478 } catch (final NullPointerException e) { 479 return null; 480 } 481 482 return temp; 483 } 484 485 /** 486 * Append header to mMessage. 487 */ 488 private int appendHeader(final int field) { 489 switch (field) { 490 case PduHeaders.MMS_VERSION: 491 appendOctet(field); 492 493 final int version = mPduHeader.getOctet(field); 494 if (0 == version) { 495 appendShortInteger(PduHeaders.CURRENT_MMS_VERSION); 496 } else { 497 appendShortInteger(version); 498 } 499 500 break; 501 502 case PduHeaders.MESSAGE_ID: 503 case PduHeaders.TRANSACTION_ID: 504 case PduHeaders.CONTENT_LOCATION: 505 final byte[] textString = mPduHeader.getTextString(field); 506 if (null == textString) { 507 return PDU_COMPOSE_FIELD_NOT_SET; 508 } 509 510 appendOctet(field); 511 appendTextString(textString); 512 break; 513 514 case PduHeaders.TO: 515 case PduHeaders.BCC: 516 case PduHeaders.CC: 517 final EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field); 518 519 if (null == addr) { 520 return PDU_COMPOSE_FIELD_NOT_SET; 521 } 522 523 EncodedStringValue temp; 524 for (int i = 0; i < addr.length; i++) { 525 temp = appendAddressType(addr[i]); 526 if (temp == null) { 527 return PDU_COMPOSE_CONTENT_ERROR; 528 } 529 530 appendOctet(field); 531 appendEncodedString(temp); 532 } 533 break; 534 535 case PduHeaders.FROM: 536 // Value-length (Address-present-token Encoded-string-value | Insert-address-token) 537 appendOctet(field); 538 539 final EncodedStringValue from = mPduHeader.getEncodedStringValue(field); 540 if ((from == null) 541 || TextUtils.isEmpty(from.getString()) 542 || new String(from.getTextString()).equals( 543 PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) { 544 // Length of from = 1 545 append(1); 546 // Insert-address-token = <Octet 129> 547 append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN); 548 } else { 549 mStack.newbuf(); 550 final PositionMarker fstart = mStack.mark(); 551 552 // Address-present-token = <Octet 128> 553 append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN); 554 555 temp = appendAddressType(from); 556 if (temp == null) { 557 return PDU_COMPOSE_CONTENT_ERROR; 558 } 559 560 appendEncodedString(temp); 561 562 final int flen = fstart.getLength(); 563 mStack.pop(); 564 appendValueLength(flen); 565 mStack.copy(); 566 } 567 break; 568 569 case PduHeaders.READ_STATUS: 570 case PduHeaders.STATUS: 571 case PduHeaders.REPORT_ALLOWED: 572 case PduHeaders.PRIORITY: 573 case PduHeaders.DELIVERY_REPORT: 574 case PduHeaders.READ_REPORT: 575 final int octet = mPduHeader.getOctet(field); 576 if (0 == octet) { 577 return PDU_COMPOSE_FIELD_NOT_SET; 578 } 579 580 appendOctet(field); 581 appendOctet(octet); 582 break; 583 584 case PduHeaders.DATE: 585 final long date = mPduHeader.getLongInteger(field); 586 if (-1 == date) { 587 return PDU_COMPOSE_FIELD_NOT_SET; 588 } 589 590 appendOctet(field); 591 appendDateValue(date); 592 break; 593 594 case PduHeaders.SUBJECT: 595 final EncodedStringValue enString = 596 mPduHeader.getEncodedStringValue(field); 597 if (null == enString) { 598 return PDU_COMPOSE_FIELD_NOT_SET; 599 } 600 601 appendOctet(field); 602 appendEncodedString(enString); 603 break; 604 605 case PduHeaders.MESSAGE_CLASS: 606 final byte[] messageClass = mPduHeader.getTextString(field); 607 if (null == messageClass) { 608 return PDU_COMPOSE_FIELD_NOT_SET; 609 } 610 611 appendOctet(field); 612 if (Arrays.equals(messageClass, 613 PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) { 614 appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT); 615 } else if (Arrays.equals(messageClass, 616 PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) { 617 appendOctet(PduHeaders.MESSAGE_CLASS_AUTO); 618 } else if (Arrays.equals(messageClass, 619 PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) { 620 appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL); 621 } else if (Arrays.equals(messageClass, 622 PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) { 623 appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL); 624 } else { 625 appendTextString(messageClass); 626 } 627 break; 628 629 case PduHeaders.EXPIRY: 630 case PduHeaders.MESSAGE_SIZE: 631 final long value = mPduHeader.getLongInteger(field); 632 if (-1 == value) { 633 return PDU_COMPOSE_FIELD_NOT_SET; 634 } 635 636 appendOctet(field); 637 638 mStack.newbuf(); 639 final PositionMarker valueStart = mStack.mark(); 640 641 append(PduHeaders.VALUE_RELATIVE_TOKEN); 642 appendLongInteger(value); 643 644 final int valueLength = valueStart.getLength(); 645 mStack.pop(); 646 appendValueLength(valueLength); 647 mStack.copy(); 648 break; 649 650 default: 651 return PDU_COMPOSE_FIELD_NOT_SUPPORTED; 652 } 653 654 return PDU_COMPOSE_SUCCESS; 655 } 656 657 /** 658 * Make ReadRec.Ind. 659 */ 660 private int makeReadRecInd() { 661 if (mMessage == null) { 662 mMessage = new ByteArrayOutputStream(); 663 mPosition = 0; 664 } 665 666 // X-Mms-Message-Type 667 appendOctet(PduHeaders.MESSAGE_TYPE); 668 appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND); 669 670 // X-Mms-MMS-Version 671 if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { 672 return PDU_COMPOSE_CONTENT_ERROR; 673 } 674 675 // Message-ID 676 if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) { 677 return PDU_COMPOSE_CONTENT_ERROR; 678 } 679 680 // To 681 if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) { 682 return PDU_COMPOSE_CONTENT_ERROR; 683 } 684 685 // From 686 if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) { 687 return PDU_COMPOSE_CONTENT_ERROR; 688 } 689 690 // Date Optional 691 appendHeader(PduHeaders.DATE); 692 693 // X-Mms-Read-Status 694 if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) { 695 return PDU_COMPOSE_CONTENT_ERROR; 696 } 697 698 // X-Mms-Applic-ID Optional(not support) 699 // X-Mms-Reply-Applic-ID Optional(not support) 700 // X-Mms-Aux-Applic-Info Optional(not support) 701 702 return PDU_COMPOSE_SUCCESS; 703 } 704 705 /** 706 * Make NotifyResp.Ind. 707 */ 708 private int makeNotifyResp() { 709 if (mMessage == null) { 710 mMessage = new ByteArrayOutputStream(); 711 mPosition = 0; 712 } 713 714 // X-Mms-Message-Type 715 appendOctet(PduHeaders.MESSAGE_TYPE); 716 appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND); 717 718 // X-Mms-Transaction-ID 719 if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) { 720 return PDU_COMPOSE_CONTENT_ERROR; 721 } 722 723 // X-Mms-MMS-Version 724 if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { 725 return PDU_COMPOSE_CONTENT_ERROR; 726 } 727 728 // X-Mms-Status 729 if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) { 730 return PDU_COMPOSE_CONTENT_ERROR; 731 } 732 733 // X-Mms-Report-Allowed Optional (not support) 734 return PDU_COMPOSE_SUCCESS; 735 } 736 737 /** 738 * Make Acknowledge.Ind. 739 */ 740 private int makeAckInd() { 741 if (mMessage == null) { 742 mMessage = new ByteArrayOutputStream(); 743 mPosition = 0; 744 } 745 746 // X-Mms-Message-Type 747 appendOctet(PduHeaders.MESSAGE_TYPE); 748 appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND); 749 750 // X-Mms-Transaction-ID 751 if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) { 752 return PDU_COMPOSE_CONTENT_ERROR; 753 } 754 755 // X-Mms-MMS-Version 756 if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { 757 return PDU_COMPOSE_CONTENT_ERROR; 758 } 759 760 // X-Mms-Report-Allowed Optional 761 appendHeader(PduHeaders.REPORT_ALLOWED); 762 763 return PDU_COMPOSE_SUCCESS; 764 } 765 766 /** 767 * Make Acknowledge.Ind. 768 */ 769 private int makeNotificationInd() { 770 if (mMessage == null) { 771 mMessage = new ByteArrayOutputStream(); 772 mPosition = 0; 773 } 774 775 // X-Mms-Message-Type 776 appendOctet(PduHeaders.MESSAGE_TYPE); 777 appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); 778 779 // X-Mms-Transaction-ID 780 if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) { 781 return PDU_COMPOSE_CONTENT_ERROR; 782 } 783 784 // X-Mms-MMS-Version 785 if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { 786 return PDU_COMPOSE_CONTENT_ERROR; 787 } 788 789 // From 790 if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) { 791 return PDU_COMPOSE_CONTENT_ERROR; 792 } 793 794 // Subject Optional 795 appendHeader(PduHeaders.SUBJECT); 796 797 // Expiry 798 if (appendHeader(PduHeaders.MESSAGE_CLASS) != PDU_COMPOSE_SUCCESS) { 799 return PDU_COMPOSE_CONTENT_ERROR; 800 } 801 802 // Expiry 803 if (appendHeader(PduHeaders.MESSAGE_SIZE) != PDU_COMPOSE_SUCCESS) { 804 return PDU_COMPOSE_CONTENT_ERROR; 805 } 806 807 // Expiry 808 if (appendHeader(PduHeaders.EXPIRY) != PDU_COMPOSE_SUCCESS) { 809 return PDU_COMPOSE_CONTENT_ERROR; 810 } 811 812 // X-Mms-Content-Location 813 if (appendHeader(PduHeaders.CONTENT_LOCATION) != PDU_COMPOSE_SUCCESS) { 814 return PDU_COMPOSE_CONTENT_ERROR; 815 } 816 817 return PDU_COMPOSE_SUCCESS; 818 } 819 820 /** 821 * Make Send.req. 822 */ 823 private int makeSendReqPdu() { 824 if (mMessage == null) { 825 mMessage = new ByteArrayOutputStream(); 826 mPosition = 0; 827 } 828 829 // X-Mms-Message-Type 830 appendOctet(PduHeaders.MESSAGE_TYPE); 831 appendOctet(PduHeaders.MESSAGE_TYPE_SEND_REQ); 832 833 // X-Mms-Transaction-ID 834 appendOctet(PduHeaders.TRANSACTION_ID); 835 836 final byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID); 837 if (trid == null) { 838 // Transaction-ID should be set(by Transaction) before make(). 839 throw new IllegalArgumentException("Transaction-ID is null."); 840 } 841 appendTextString(trid); 842 843 // X-Mms-MMS-Version 844 if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { 845 return PDU_COMPOSE_CONTENT_ERROR; 846 } 847 848 // Date Date-value Optional. 849 appendHeader(PduHeaders.DATE); 850 851 // From 852 if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) { 853 return PDU_COMPOSE_CONTENT_ERROR; 854 } 855 856 boolean recipient = false; 857 858 // To 859 if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) { 860 recipient = true; 861 } 862 863 // Cc 864 if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) { 865 recipient = true; 866 } 867 868 // Bcc 869 if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) { 870 recipient = true; 871 } 872 873 // Need at least one of "cc", "bcc" and "to". 874 if (false == recipient) { 875 return PDU_COMPOSE_CONTENT_ERROR; 876 } 877 878 // Subject Optional 879 appendHeader(PduHeaders.SUBJECT); 880 881 // X-Mms-Message-Class Optional 882 // Message-class-value = Class-identifier | Token-text 883 appendHeader(PduHeaders.MESSAGE_CLASS); 884 885 // X-Mms-Expiry Optional 886 appendHeader(PduHeaders.EXPIRY); 887 888 // X-Mms-Priority Optional 889 appendHeader(PduHeaders.PRIORITY); 890 891 // X-Mms-Delivery-Report Optional 892 appendHeader(PduHeaders.DELIVERY_REPORT); 893 894 // X-Mms-Read-Report Optional 895 appendHeader(PduHeaders.READ_REPORT); 896 897 // Content-Type 898 appendOctet(PduHeaders.CONTENT_TYPE); 899 900 // Message body 901 return makeMessageBody(); 902 } 903 904 /** 905 * Make message body. 906 */ 907 private int makeMessageBody() { 908 // 1. add body informations 909 mStack.newbuf(); // Switching buffer because we need to 910 911 final PositionMarker ctStart = mStack.mark(); 912 913 // This contentTypeIdentifier should be used for type of attachment... 914 final String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE)); 915 final Integer contentTypeIdentifier = mContentTypeMap.get(contentType); 916 if (contentTypeIdentifier == null) { 917 // content type is mandatory 918 return PDU_COMPOSE_CONTENT_ERROR; 919 } 920 921 appendShortInteger(contentTypeIdentifier.intValue()); 922 923 // content-type parameter: start 924 final PduBody body = ((SendReq) mPdu).getBody(); 925 if (null == body || body.getPartsNum() == 0) { 926 // empty message 927 appendUintvarInteger(0); 928 mStack.pop(); 929 mStack.copy(); 930 return PDU_COMPOSE_SUCCESS; 931 } 932 933 PduPart part; 934 try { 935 part = body.getPart(0); 936 937 final byte[] start = part.getContentId(); 938 if (start != null) { 939 appendOctet(PduPart.P_DEP_START); 940 if (('<' == start[0]) && ('>' == start[start.length - 1])) { 941 appendTextString(start); 942 } else { 943 appendTextString("<" + new String(start) + ">"); 944 } 945 } 946 947 // content-type parameter: type 948 appendOctet(PduPart.P_CT_MR_TYPE); 949 appendTextString(part.getContentType()); 950 } catch (final ArrayIndexOutOfBoundsException e) { 951 e.printStackTrace(); 952 } 953 954 final int ctLength = ctStart.getLength(); 955 mStack.pop(); 956 appendValueLength(ctLength); 957 mStack.copy(); 958 959 // 3. add content 960 final int partNum = body.getPartsNum(); 961 appendUintvarInteger(partNum); 962 for (int i = 0; i < partNum; i++) { 963 part = body.getPart(i); 964 mStack.newbuf(); // Leaving space for header lengh and data length 965 final PositionMarker attachment = mStack.mark(); 966 967 mStack.newbuf(); // Leaving space for Content-Type length 968 final PositionMarker contentTypeBegin = mStack.mark(); 969 970 final byte[] partContentType = part.getContentType(); 971 972 if (partContentType == null) { 973 // content type is mandatory 974 return PDU_COMPOSE_CONTENT_ERROR; 975 } 976 977 // content-type value 978 final Integer partContentTypeIdentifier = 979 mContentTypeMap.get(new String(partContentType)); 980 if (partContentTypeIdentifier == null) { 981 appendTextString(partContentType); 982 } else { 983 appendShortInteger(partContentTypeIdentifier.intValue()); 984 } 985 986 /* Content-type parameter : name. 987 * The value of name, filename, content-location is the same. 988 * Just one of them is enough for this PDU. 989 */ 990 byte[] name = part.getName(); 991 992 if (null == name) { 993 name = part.getFilename(); 994 995 if (null == name) { 996 name = part.getContentLocation(); 997 998 if (null == name) { 999 /* at lease one of name, filename, Content-location 1000 * should be available. 1001 */ 1002 // I found that an mms received from tmomail.net will include a SMIL part 1003 // that has no name. That would cause the code here to return 1004 // PDU_COMPOSE_CONTENT_ERROR when a user tried to forward the message. The 1005 // message would never send and the user would be stuck in a retry 1006 // situation. Simply jam in any old name here to fix the problem. 1007 name = "smil.xml".getBytes(); 1008 } 1009 } 1010 } 1011 appendOctet(PduPart.P_DEP_NAME); 1012 appendTextString(name); 1013 1014 // content-type parameter : charset 1015 final int charset = part.getCharset(); 1016 if (charset != 0) { 1017 appendOctet(PduPart.P_CHARSET); 1018 appendShortInteger(charset); 1019 } 1020 1021 final int contentTypeLength = contentTypeBegin.getLength(); 1022 mStack.pop(); 1023 appendValueLength(contentTypeLength); 1024 mStack.copy(); 1025 1026 // content id 1027 final byte[] contentId = part.getContentId(); 1028 1029 if (null != contentId) { 1030 appendOctet(PduPart.P_CONTENT_ID); 1031 if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) { 1032 appendQuotedString(contentId); 1033 } else { 1034 appendQuotedString("<" + new String(contentId) + ">"); 1035 } 1036 } 1037 1038 // content-location 1039 final byte[] contentLocation = part.getContentLocation(); 1040 if (null != contentLocation) { 1041 appendOctet(PduPart.P_CONTENT_LOCATION); 1042 appendTextString(contentLocation); 1043 } 1044 1045 // content 1046 final int headerLength = attachment.getLength(); 1047 1048 int dataLength = 0; // Just for safety... 1049 final byte[] partData = part.getData(); 1050 1051 if (partData != null) { 1052 arraycopy(partData, 0, partData.length); 1053 dataLength = partData.length; 1054 } else { 1055 InputStream cr = null; 1056 try { 1057 final byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE]; 1058 cr = mResolver.openInputStream(part.getDataUri()); 1059 int len = 0; 1060 while ((len = cr.read(buffer)) != -1) { 1061 mMessage.write(buffer, 0, len); 1062 mPosition += len; 1063 dataLength += len; 1064 } 1065 } catch (final FileNotFoundException e) { 1066 return PDU_COMPOSE_CONTENT_ERROR; 1067 } catch (final IOException e) { 1068 return PDU_COMPOSE_CONTENT_ERROR; 1069 } catch (final RuntimeException e) { 1070 return PDU_COMPOSE_CONTENT_ERROR; 1071 } finally { 1072 if (cr != null) { 1073 try { 1074 cr.close(); 1075 } catch (final IOException e) { 1076 // Nothing to do 1077 } 1078 } 1079 } 1080 } 1081 1082 if (dataLength != (attachment.getLength() - headerLength)) { 1083 throw new RuntimeException("BUG: Length sanity check failed"); 1084 } 1085 1086 mStack.pop(); 1087 appendUintvarInteger(headerLength); 1088 appendUintvarInteger(dataLength); 1089 mStack.copy(); 1090 } 1091 1092 return PDU_COMPOSE_SUCCESS; 1093 } 1094 1095 /** 1096 * Record current message informations. 1097 */ 1098 private static class LengthRecordNode { 1099 1100 ByteArrayOutputStream currentMessage = null; 1101 1102 public int currentPosition = 0; 1103 1104 public LengthRecordNode next = null; 1105 } 1106 1107 /** 1108 * Mark current message position and stact size. 1109 */ 1110 private class PositionMarker { 1111 1112 private int c_pos; // Current position 1113 1114 private int currentStackSize; // Current stack size 1115 1116 int getLength() { 1117 // If these assert fails, likely that you are finding the 1118 // size of buffer that is deep in BufferStack you can only 1119 // find the length of the buffer that is on top 1120 if (currentStackSize != mStack.stackSize) { 1121 throw new RuntimeException("BUG: Invalid call to getLength()"); 1122 } 1123 1124 return mPosition - c_pos; 1125 } 1126 } 1127 1128 /** 1129 * This implementation can be OPTIMIZED to use only 1130 * 2 buffers. This optimization involves changing BufferStack 1131 * only... Its usage (interface) will not change. 1132 */ 1133 private class BufferStack { 1134 1135 private LengthRecordNode stack = null; 1136 1137 private LengthRecordNode toCopy = null; 1138 1139 int stackSize = 0; 1140 1141 /** 1142 * Create a new message buffer and push it into the stack. 1143 */ 1144 void newbuf() { 1145 // You can't create a new buff when toCopy != null 1146 // That is after calling pop() and before calling copy() 1147 // If you do, it is a bug 1148 if (toCopy != null) { 1149 throw new RuntimeException("BUG: Invalid newbuf() before copy()"); 1150 } 1151 1152 final LengthRecordNode temp = new LengthRecordNode(); 1153 1154 temp.currentMessage = mMessage; 1155 temp.currentPosition = mPosition; 1156 1157 temp.next = stack; 1158 stack = temp; 1159 1160 stackSize = stackSize + 1; 1161 1162 mMessage = new ByteArrayOutputStream(); 1163 mPosition = 0; 1164 } 1165 1166 /** 1167 * Pop the message before and record current message in the stack. 1168 */ 1169 void pop() { 1170 final ByteArrayOutputStream currentMessage = mMessage; 1171 final int currentPosition = mPosition; 1172 1173 mMessage = stack.currentMessage; 1174 mPosition = stack.currentPosition; 1175 1176 toCopy = stack; 1177 // Re using the top element of the stack to avoid memory allocation 1178 1179 stack = stack.next; 1180 stackSize = stackSize - 1; 1181 1182 toCopy.currentMessage = currentMessage; 1183 toCopy.currentPosition = currentPosition; 1184 } 1185 1186 /** 1187 * Append current message to the message before. 1188 */ 1189 void copy() { 1190 arraycopy(toCopy.currentMessage.toByteArray(), 0, 1191 toCopy.currentPosition); 1192 1193 toCopy = null; 1194 } 1195 1196 /** 1197 * Mark current message position 1198 */ 1199 PositionMarker mark() { 1200 final PositionMarker m = new PositionMarker(); 1201 1202 m.c_pos = mPosition; 1203 m.currentStackSize = stackSize; 1204 1205 return m; 1206 } 1207 } 1208 1209 /** 1210 * Check address type. 1211 * 1212 * @param address address string without the postfix stinng type, 1213 * such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4" 1214 * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number, 1215 * PDU_EMAIL_ADDRESS_TYPE if it is email address, 1216 * PDU_IPV4_ADDRESS_TYPE if it is ipv4 address, 1217 * PDU_IPV6_ADDRESS_TYPE if it is ipv6 address, 1218 * PDU_UNKNOWN_ADDRESS_TYPE if it is unknown. 1219 */ 1220 protected static int checkAddressType(final String address) { 1221 /** 1222 * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8. 1223 * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode) 1224 * e-mail = mailbox; to the definition of mailbox as described in 1225 * section 3.4 of [RFC2822], but excluding the 1226 * obsolete definitions as indicated by the "obs-" prefix. 1227 * device-address = ( global-phone-number "/TYPE=PLMN" ) 1228 * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" ) 1229 * / ( escaped-value "/TYPE=" address-type ) 1230 * 1231 * global-phone-number = ["+"] 1*( DIGIT / written-sep ) 1232 * written-sep =("-"/".") 1233 * 1234 * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value 1235 * 1236 * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373 1237 */ 1238 1239 if (null == address) { 1240 return PDU_UNKNOWN_ADDRESS_TYPE; 1241 } 1242 1243 if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) { 1244 // Ipv4 address. 1245 return PDU_IPV4_ADDRESS_TYPE; 1246 } else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) { 1247 // Phone number. 1248 return PDU_PHONE_NUMBER_ADDRESS_TYPE; 1249 } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) { 1250 // Email address. 1251 return PDU_EMAIL_ADDRESS_TYPE; 1252 } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) { 1253 // Ipv6 address. 1254 return PDU_IPV6_ADDRESS_TYPE; 1255 } else { 1256 // Unknown address. 1257 return PDU_UNKNOWN_ADDRESS_TYPE; 1258 } 1259 } 1260 } 1261