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
18package com.android.messaging.mmslib.pdu;
19
20import android.content.ContentResolver;
21import android.content.Context;
22import android.support.v4.util.SimpleArrayMap;
23import android.text.TextUtils;
24
25import java.io.ByteArrayOutputStream;
26import java.io.FileNotFoundException;
27import java.io.IOException;
28import java.io.InputStream;
29import java.util.Arrays;
30
31public 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