Home | History | Annotate | Download | only in pdu
      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