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 androidx.collection.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++) {
mContentTypeMap.put(PduContentTypes.contentTypes[i], 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      */
PduComposer(final Context context, final GenericPdu pdu)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      */
make()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      */
arraycopy(final byte[] buf, final int pos, final int length)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      */
append(final int value)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      */
appendShortInteger(final int value)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      */
appendOctet(final int number)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      */
appendShortLength(final int value)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      */
appendLongInteger(final long longInt)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      */
appendTextString(final byte[] text)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      */
appendTextString(final String str)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      */
appendEncodedString(final EncodedStringValue enStr)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      */
appendUintvarInteger(final long value)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      */
appendDateValue(final long date)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      */
appendValueLength(final long value)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      */
appendQuotedString(final byte[] text)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      */
appendQuotedString(final String str)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 
appendAddressType(final EncodedStringValue address)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      */
appendHeader(final int field)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      */
makeReadRecInd()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      */
makeNotifyResp()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      */
makeAckInd()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      */
makeNotificationInd()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      */
makeSendReqPdu()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      */
makeMessageBody()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 
getLength()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          */
newbuf()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          */
pop()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          */
copy()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          */
mark()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      */
checkAddressType(final String address)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