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