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