Home | History | Annotate | Download | only in pdu
      1 /*
      2  * Copyright (C) 2007-2008 Esmertec AG.
      3  * Copyright (C) 2007-2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.messaging.mmslib.pdu;
     19 
     20 import android.util.Log;
     21 import android.util.SparseArray;
     22 
     23 import com.android.messaging.mmslib.InvalidHeaderValueException;
     24 import com.android.messaging.util.ContentType;
     25 
     26 import java.io.ByteArrayInputStream;
     27 import java.io.ByteArrayOutputStream;
     28 import java.io.UnsupportedEncodingException;
     29 import java.util.Arrays;
     30 
     31 public class PduParser {
     32     /**
     33      * The log tag.
     34      */
     35     private static final String LOG_TAG = "PduParser";
     36 
     37     private static final boolean LOCAL_LOGV = false;
     38 
     39     /**
     40      * The next are WAP values defined in WSP specification.
     41      */
     42     private static final int QUOTE = 127;
     43 
     44     private static final int LENGTH_QUOTE = 31;
     45 
     46     private static final int TEXT_MIN = 32;
     47 
     48     private static final int TEXT_MAX = 127;
     49 
     50     private static final int SHORT_INTEGER_MAX = 127;
     51 
     52     private static final int SHORT_LENGTH_MAX = 30;
     53 
     54     private static final int LONG_INTEGER_LENGTH_MAX = 8;
     55 
     56     private static final int QUOTED_STRING_FLAG = 34;
     57 
     58     private static final int END_STRING_FLAG = 0x00;
     59 
     60     //The next two are used by the interface "parseWapString" to
     61     //distinguish Text-String and Quoted-String.
     62     private static final int TYPE_TEXT_STRING = 0;
     63 
     64     private static final int TYPE_QUOTED_STRING = 1;
     65 
     66     private static final int TYPE_TOKEN_STRING = 2;
     67 
     68     /**
     69      * Specify the part position.
     70      */
     71     private static final int THE_FIRST_PART = 0;
     72 
     73     private static final int THE_LAST_PART = 1;
     74 
     75     /**
     76      * The pdu data.
     77      */
     78     private ByteArrayInputStream mPduDataStream = null;
     79 
     80     /**
     81      * Store pdu headers
     82      */
     83     private PduHeaders mHeaders = null;
     84 
     85     /**
     86      * Store pdu parts.
     87      */
     88     private PduBody mBody = null;
     89 
     90     /**
     91      * Store the "type" parameter in "Content-Type" header field.
     92      */
     93     private static byte[] mTypeParam = null;
     94 
     95     /**
     96      * Store the "start" parameter in "Content-Type" header field.
     97      */
     98     private static byte[] mStartParam = null;
     99 
    100     /**
    101      * Whether to parse content-disposition part header
    102      */
    103     private final boolean mParseContentDisposition;
    104 
    105     /**
    106      * Constructor.
    107      *
    108      * @param pduDataStream pdu data to be parsed
    109      * @param parseContentDisposition whether to parse the Content-Disposition part header
    110      */
    111     public PduParser(byte[] pduDataStream, boolean parseContentDisposition) {
    112         mPduDataStream = new ByteArrayInputStream(pduDataStream);
    113         mParseContentDisposition = parseContentDisposition;
    114     }
    115 
    116     /**
    117      * Parse the pdu.
    118      *
    119      * @return the pdu structure if parsing successfully.
    120      * null if parsing error happened or mandatory fields are not set.
    121      */
    122     public GenericPdu parse() {
    123         if (mPduDataStream == null) {
    124             return null;
    125         }
    126 
    127         /* parse headers */
    128         mHeaders = parseHeaders(mPduDataStream);
    129         if (null == mHeaders) {
    130             // Parse headers failed.
    131             return null;
    132         }
    133 
    134         /* get the message type */
    135         int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
    136 
    137         /* check mandatory header fields */
    138         if (false == checkMandatoryHeader(mHeaders)) {
    139             log("check mandatory headers failed!");
    140             return null;
    141         }
    142         /*
    143          * Get retrieve status. If the header is not there, assuming it is OK status.
    144          * Some carriers may choose to not send this header.
    145          */
    146         int retrieveStatus = mHeaders.hasHeader(PduHeaders.RETRIEVE_STATUS) ?
    147                 mHeaders.getOctet(PduHeaders.RETRIEVE_STATUS) : PduHeaders.RETRIEVE_STATUS_OK;
    148 
    149         if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
    150                 (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType &&
    151                 retrieveStatus == PduHeaders.RETRIEVE_STATUS_OK)) {
    152             /* need to parse the parts */
    153             mBody = parseParts(mPduDataStream);
    154             if (null == mBody) {
    155                 // Parse parts failed.
    156                 return null;
    157             }
    158         }
    159 
    160         switch (messageType) {
    161             case PduHeaders.MESSAGE_TYPE_SEND_REQ:
    162                 if (LOCAL_LOGV) {
    163                     Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ");
    164                 }
    165                 SendReq sendReq = new SendReq(mHeaders, mBody);
    166                 return sendReq;
    167             case PduHeaders.MESSAGE_TYPE_SEND_CONF:
    168                 if (LOCAL_LOGV) {
    169                     Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF");
    170                 }
    171                 SendConf sendConf = new SendConf(mHeaders);
    172                 return sendConf;
    173             case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
    174                 if (LOCAL_LOGV) {
    175                     Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND");
    176                 }
    177                 NotificationInd notificationInd =
    178                         new NotificationInd(mHeaders);
    179                 return notificationInd;
    180             case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
    181                 if (LOCAL_LOGV) {
    182                     Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND");
    183                 }
    184                 NotifyRespInd notifyRespInd =
    185                         new NotifyRespInd(mHeaders);
    186                 return notifyRespInd;
    187             case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
    188                 if (LOCAL_LOGV) {
    189                     Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF");
    190                 }
    191                 RetrieveConf retrieveConf =
    192                         new RetrieveConf(mHeaders, mBody);
    193                 if (retrieveStatus != PduHeaders.RETRIEVE_STATUS_OK) {
    194                     // For failure only no need to check content type
    195                     return retrieveConf;
    196                 }
    197                 byte[] contentType = retrieveConf.getContentType();
    198                 if (null == contentType) {
    199                     return null;
    200                 }
    201                 String ctTypeStr = new String(contentType);
    202                 if (ctTypeStr.equals(ContentType.MMS_MULTIPART_MIXED)
    203                         || ctTypeStr.equals(ContentType.MMS_MULTIPART_RELATED)
    204                         || ctTypeStr.equals(ContentType.MMS_MULTIPART_ALTERNATIVE)) {
    205                     // The MMS content type must be "application/vnd.wap.multipart.mixed"
    206                     // or "application/vnd.wap.multipart.related"
    207                     // or "application/vnd.wap.multipart.alternative"
    208                     return retrieveConf;
    209                 } else if (ctTypeStr.equals(ContentType.MMS_MULTIPART_ALTERNATIVE)) {
    210                     // "application/vnd.wap.multipart.alternative"
    211                     // should take only the first part.
    212                     PduPart firstPart = mBody.getPart(0);
    213                     mBody.removeAll();
    214                     mBody.addPart(0, firstPart);
    215                     return retrieveConf;
    216                 }
    217                 return null;
    218             case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
    219                 if (LOCAL_LOGV) {
    220                     Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND");
    221                 }
    222                 DeliveryInd deliveryInd =
    223                         new DeliveryInd(mHeaders);
    224                 return deliveryInd;
    225             case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
    226                 if (LOCAL_LOGV) {
    227                     Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");
    228                 }
    229                 AcknowledgeInd acknowledgeInd =
    230                         new AcknowledgeInd(mHeaders);
    231                 return acknowledgeInd;
    232             case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
    233                 if (LOCAL_LOGV) {
    234                     Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND");
    235                 }
    236                 ReadOrigInd readOrigInd =
    237                         new ReadOrigInd(mHeaders);
    238                 return readOrigInd;
    239             case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
    240                 if (LOCAL_LOGV) {
    241                     Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND");
    242                 }
    243                 ReadRecInd readRecInd =
    244                         new ReadRecInd(mHeaders);
    245                 return readRecInd;
    246             default:
    247                 log("Parser doesn't support this message type in this version!");
    248                 return null;
    249         }
    250     }
    251 
    252     /**
    253      * Parse pdu headers.
    254      *
    255      * @param pduDataStream pdu data input stream
    256      * @return headers in PduHeaders structure, null when parse fail
    257      */
    258     protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream) {
    259         if (pduDataStream == null) {
    260             return null;
    261         }
    262         boolean keepParsing = true;
    263         PduHeaders headers = new PduHeaders();
    264 
    265         while (keepParsing && (pduDataStream.available() > 0)) {
    266             pduDataStream.mark(1);
    267             int headerField = extractByteValue(pduDataStream);
    268             /* parse custom text header */
    269             if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
    270                 pduDataStream.reset();
    271                 byte[] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
    272                 if (LOCAL_LOGV) {
    273                     Log.v(LOG_TAG, "TextHeader: " + new String(bVal));
    274                 }
    275                 /* we should ignore it at the moment */
    276                 continue;
    277             }
    278             switch (headerField) {
    279                 case PduHeaders.MESSAGE_TYPE: {
    280                     int messageType = extractByteValue(pduDataStream);
    281                     if (LOCAL_LOGV) {
    282                         Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType +
    283                                 " (" + Integer.toHexString(headerField) + ")");
    284                     }
    285                     switch (messageType) {
    286                         // We don't support these kind of messages now.
    287                         case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
    288                         case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
    289                         case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
    290                         case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
    291                         case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
    292                         case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
    293                         case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
    294                         case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
    295                         case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
    296                         case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
    297                         case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
    298                         case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
    299                         case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
    300                         case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
    301                         case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
    302                             return null;
    303                     }
    304                     try {
    305                         headers.setOctet(messageType, headerField);
    306                     } catch (InvalidHeaderValueException e) {
    307                         log("Set invalid Octet value: " + messageType +
    308                                 " into the header filed: " + headerField);
    309                         return null;
    310                     } catch (RuntimeException e) {
    311                         log(headerField + "is not Octet header field!");
    312                         return null;
    313                     }
    314                     break;
    315                 }
    316                 /* Octect value */
    317                 case PduHeaders.REPORT_ALLOWED:
    318                 case PduHeaders.ADAPTATION_ALLOWED:
    319                 case PduHeaders.DELIVERY_REPORT:
    320                 case PduHeaders.DRM_CONTENT:
    321                 case PduHeaders.DISTRIBUTION_INDICATOR:
    322                 case PduHeaders.QUOTAS:
    323                 case PduHeaders.READ_REPORT:
    324                 case PduHeaders.STORE:
    325                 case PduHeaders.STORED:
    326                 case PduHeaders.TOTALS:
    327                 case PduHeaders.SENDER_VISIBILITY:
    328                 case PduHeaders.READ_STATUS:
    329                 case PduHeaders.CANCEL_STATUS:
    330                 case PduHeaders.PRIORITY:
    331                 case PduHeaders.STATUS:
    332                 case PduHeaders.REPLY_CHARGING:
    333                 case PduHeaders.MM_STATE:
    334                 case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
    335                 case PduHeaders.CONTENT_CLASS:
    336                 case PduHeaders.RETRIEVE_STATUS:
    337                 case PduHeaders.STORE_STATUS:
    338                     /**
    339                      * The following field has a different value when
    340                      * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
    341                      * For now we ignore this fact, since we do not support these PDUs
    342                      */
    343                 case PduHeaders.RESPONSE_STATUS: {
    344                     int value = extractByteValue(pduDataStream);
    345                     if (LOCAL_LOGV) {
    346                         Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    347                                 " (" + Integer.toHexString(headerField) + ") Octect value: " +
    348                                 value);
    349                     }
    350 
    351                     try {
    352                         headers.setOctet(value, headerField);
    353                     } catch (InvalidHeaderValueException e) {
    354                         log("Set invalid Octet value: " + value +
    355                                 " into the header filed: " + headerField);
    356                         return null;
    357                     } catch (RuntimeException e) {
    358                         log(headerField + "is not Octet header field!");
    359                         return null;
    360                     }
    361                     break;
    362                 }
    363 
    364                 /* Long-Integer */
    365                 case PduHeaders.DATE:
    366                 case PduHeaders.REPLY_CHARGING_SIZE:
    367                 case PduHeaders.MESSAGE_SIZE: {
    368                     try {
    369                         long value = parseLongInteger(pduDataStream);
    370                         if (LOCAL_LOGV) {
    371                             Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    372                                     " (" + Integer.toHexString(headerField) + ") longint value: " +
    373                                     value);
    374                         }
    375                         headers.setLongInteger(value, headerField);
    376                     } catch (RuntimeException e) {
    377                         log(headerField + "is not Long-Integer header field!");
    378                         return null;
    379                     }
    380                     break;
    381                 }
    382 
    383                 /* Integer-Value */
    384                 case PduHeaders.MESSAGE_COUNT:
    385                 case PduHeaders.START:
    386                 case PduHeaders.LIMIT: {
    387                     try {
    388                         long value = parseIntegerValue(pduDataStream);
    389                         if (LOCAL_LOGV) {
    390                             Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    391                                     " (" + Integer.toHexString(headerField) + ") integer value: " +
    392                                     value);
    393                         }
    394                         headers.setLongInteger(value, headerField);
    395                     } catch (RuntimeException e) {
    396                         log(headerField + "is not Long-Integer header field!");
    397                         return null;
    398                     }
    399                     break;
    400                 }
    401 
    402                 /* Text-String */
    403                 case PduHeaders.TRANSACTION_ID:
    404                 case PduHeaders.REPLY_CHARGING_ID:
    405                 case PduHeaders.AUX_APPLIC_ID:
    406                 case PduHeaders.APPLIC_ID:
    407                 case PduHeaders.REPLY_APPLIC_ID:
    408                     /**
    409                      * The next three header fields are email addresses
    410                      * as defined in RFC2822,
    411                      * not including the characters "<" and ">"
    412                      */
    413                 case PduHeaders.MESSAGE_ID:
    414                 case PduHeaders.REPLACE_ID:
    415                 case PduHeaders.CANCEL_ID:
    416                     /**
    417                      * The following field has a different value when
    418                      * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
    419                      * For now we ignore this fact, since we do not support these PDUs
    420                      */
    421                 case PduHeaders.CONTENT_LOCATION: {
    422                     byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
    423                     if (null != value) {
    424                         try {
    425                             if (LOCAL_LOGV) {
    426                                 Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    427                                         " (" + Integer.toHexString(headerField) + ") string value: "
    428                                         +
    429                                         new String(value));
    430                             }
    431                             headers.setTextString(value, headerField);
    432                         } catch (NullPointerException e) {
    433                             log("null pointer error!");
    434                         } catch (RuntimeException e) {
    435                             log(headerField + "is not Text-String header field!");
    436                             return null;
    437                         }
    438                     }
    439                     break;
    440                 }
    441 
    442                 /* Encoded-string-value */
    443                 case PduHeaders.SUBJECT:
    444                 case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
    445                 case PduHeaders.RETRIEVE_TEXT:
    446                 case PduHeaders.STATUS_TEXT:
    447                 case PduHeaders.STORE_STATUS_TEXT:
    448                     /* the next one is not support
    449                      * M-Mbox-Delete.conf and M-Delete.conf now */
    450                 case PduHeaders.RESPONSE_TEXT: {
    451                     EncodedStringValue value =
    452                             parseEncodedStringValue(pduDataStream);
    453                     if (null != value) {
    454                         try {
    455                             if (LOCAL_LOGV) {
    456                                 Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    457                                         " (" + Integer.toHexString(headerField)
    458                                         + ") encoded string: " +
    459                                         value.getString());
    460                             }
    461                             headers.setEncodedStringValue(value, headerField);
    462                         } catch (NullPointerException e) {
    463                             log("null pointer error!");
    464                         } catch (RuntimeException e) {
    465                             log(headerField + "is not Encoded-String-Value header field!");
    466                             return null;
    467                         }
    468                     }
    469                     break;
    470                 }
    471 
    472                 /* Addressing model */
    473                 case PduHeaders.BCC:
    474                 case PduHeaders.CC:
    475                 case PduHeaders.TO: {
    476                     EncodedStringValue value =
    477                             parseEncodedStringValue(pduDataStream);
    478                     if (null != value) {
    479                         byte[] address = value.getTextString();
    480                         if (null != address) {
    481                             String str = new String(address);
    482                             if (LOCAL_LOGV) {
    483                                 Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField
    484                                         + " value: " + str);
    485                             }
    486                             int endIndex = str.indexOf("/");
    487                             if (endIndex > 0) {
    488                                 str = str.substring(0, endIndex);
    489                             }
    490                             try {
    491                                 value.setTextString(str.getBytes());
    492                             } catch (NullPointerException e) {
    493                                 log("null pointer error!");
    494                                 return null;
    495                             }
    496                         }
    497 
    498                         try {
    499                             headers.appendEncodedStringValue(value, headerField);
    500                         } catch (NullPointerException e) {
    501                             log("null pointer error!");
    502                         } catch (RuntimeException e) {
    503                             log(headerField + "is not Encoded-String-Value header field!");
    504                             return null;
    505                         }
    506                     }
    507                     break;
    508                 }
    509 
    510                 /* Value-length
    511                  * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
    512                 case PduHeaders.DELIVERY_TIME:
    513                 case PduHeaders.EXPIRY:
    514                 case PduHeaders.REPLY_CHARGING_DEADLINE: {
    515                     /* parse Value-length */
    516                     parseValueLength(pduDataStream);
    517 
    518                     /* Absolute-token or Relative-token */
    519                     int token = extractByteValue(pduDataStream);
    520 
    521                     /* Date-value or Delta-seconds-value */
    522                     long timeValue;
    523                     try {
    524                         timeValue = parseLongInteger(pduDataStream);
    525                     } catch (RuntimeException e) {
    526                         log(headerField + "is not Long-Integer header field!");
    527                         return null;
    528                     }
    529                     if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
    530                         /* need to convert the Delta-seconds-value
    531                          * into Date-value */
    532                         timeValue = System.currentTimeMillis() / 1000 + timeValue;
    533                     }
    534 
    535                     try {
    536                         if (LOCAL_LOGV) {
    537                             Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    538                                     " (" + Integer.toHexString(headerField) + ") time value: " +
    539                                     timeValue);
    540                         }
    541                         headers.setLongInteger(timeValue, headerField);
    542                     } catch (RuntimeException e) {
    543                         log(headerField + "is not Long-Integer header field!");
    544                         return null;
    545                     }
    546                     break;
    547                 }
    548 
    549                 case PduHeaders.FROM: {
    550                     /* From-value =
    551                      * Value-length
    552                      * (Address-present-token Encoded-string-value | Insert-address-token)
    553                      */
    554                     EncodedStringValue from = null;
    555                     parseValueLength(pduDataStream); /* parse value-length */
    556 
    557                     /* Address-present-token or Insert-address-token */
    558                     int fromToken = extractByteValue(pduDataStream);
    559 
    560                     /* Address-present-token or Insert-address-token */
    561                     if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
    562                         /* Encoded-string-value */
    563                         from = parseEncodedStringValue(pduDataStream);
    564                         if (null != from) {
    565                             byte[] address = from.getTextString();
    566                             if (null != address) {
    567                                 String str = new String(address);
    568                                 int endIndex = str.indexOf("/");
    569                                 if (endIndex > 0) {
    570                                     str = str.substring(0, endIndex);
    571                                 }
    572                                 try {
    573                                     from.setTextString(str.getBytes());
    574                                 } catch (NullPointerException e) {
    575                                     log("null pointer error!");
    576                                     return null;
    577                                 }
    578                             }
    579                         }
    580                     } else {
    581                         try {
    582                             from = new EncodedStringValue(
    583                                     PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
    584                         } catch (NullPointerException e) {
    585                             log(headerField + "is not Encoded-String-Value header field!");
    586                             return null;
    587                         }
    588                     }
    589 
    590                     try {
    591                         if (LOCAL_LOGV) {
    592                             Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    593                                     " (" + Integer.toHexString(headerField) + ") from address: " +
    594                                     from.getString());
    595                         }
    596                         headers.setEncodedStringValue(from, PduHeaders.FROM);
    597                     } catch (NullPointerException e) {
    598                         log("null pointer error!");
    599                     } catch (RuntimeException e) {
    600                         log(headerField + "is not Encoded-String-Value header field!");
    601                         return null;
    602                     }
    603                     break;
    604                 }
    605 
    606                 case PduHeaders.MESSAGE_CLASS: {
    607                     /* Message-class-value = Class-identifier | Token-text */
    608                     pduDataStream.mark(1);
    609                     int messageClass = extractByteValue(pduDataStream);
    610                     if (LOCAL_LOGV) {
    611                         Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    612                                 " (" + Integer.toHexString(headerField) + ") MESSAGE_CLASS: " +
    613                                 messageClass);
    614                     }
    615 
    616                     if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
    617                         /* Class-identifier */
    618                         try {
    619                             if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
    620                                 headers.setTextString(
    621                                         PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
    622                                         PduHeaders.MESSAGE_CLASS);
    623                             } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
    624                                 headers.setTextString(
    625                                         PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
    626                                         PduHeaders.MESSAGE_CLASS);
    627                             } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
    628                                 headers.setTextString(
    629                                         PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
    630                                         PduHeaders.MESSAGE_CLASS);
    631                             } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
    632                                 headers.setTextString(
    633                                         PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
    634                                         PduHeaders.MESSAGE_CLASS);
    635                             }
    636                         } catch (NullPointerException e) {
    637                             log("null pointer error!");
    638                         } catch (RuntimeException e) {
    639                             log(headerField + "is not Text-String header field!");
    640                             return null;
    641                         }
    642                     } else {
    643                         /* Token-text */
    644                         pduDataStream.reset();
    645                         byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
    646                         if (null != messageClassString) {
    647                             try {
    648                                 headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
    649                             } catch (NullPointerException e) {
    650                                 log("null pointer error!");
    651                             } catch (RuntimeException e) {
    652                                 log(headerField + "is not Text-String header field!");
    653                                 return null;
    654                             }
    655                         }
    656                     }
    657                     break;
    658                 }
    659 
    660                 case PduHeaders.MMS_VERSION: {
    661                     int version = parseShortInteger(pduDataStream);
    662 
    663                     try {
    664                         if (LOCAL_LOGV) {
    665                             Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    666                                     " (" + Integer.toHexString(headerField) + ") MMS_VERSION: " +
    667                                     version);
    668                         }
    669                         headers.setOctet(version, PduHeaders.MMS_VERSION);
    670                     } catch (InvalidHeaderValueException e) {
    671                         log("Set invalid Octet value: " + version +
    672                                 " into the header filed: " + headerField);
    673                         return null;
    674                     } catch (RuntimeException e) {
    675                         log(headerField + "is not Octet header field!");
    676                         return null;
    677                     }
    678                     break;
    679                 }
    680 
    681                 case PduHeaders.PREVIOUSLY_SENT_BY: {
    682                     /* Previously-sent-by-value =
    683                      * Value-length Forwarded-count-value Encoded-string-value */
    684                     /* parse value-length */
    685                     parseValueLength(pduDataStream);
    686 
    687                     /* parse Forwarded-count-value */
    688                     try {
    689                         parseIntegerValue(pduDataStream);
    690                     } catch (RuntimeException e) {
    691                         log(headerField + " is not Integer-Value");
    692                         return null;
    693                     }
    694 
    695                     /* parse Encoded-string-value */
    696                     EncodedStringValue previouslySentBy =
    697                             parseEncodedStringValue(pduDataStream);
    698                     if (null != previouslySentBy) {
    699                         try {
    700                             if (LOCAL_LOGV) {
    701                                 Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    702                                         " (" + Integer.toHexString(headerField) +
    703                                         ") PREVIOUSLY_SENT_BY: " + previouslySentBy.getString());
    704                             }
    705                             headers.setEncodedStringValue(previouslySentBy,
    706                                     PduHeaders.PREVIOUSLY_SENT_BY);
    707                         } catch (NullPointerException e) {
    708                             log("null pointer error!");
    709                         } catch (RuntimeException e) {
    710                             log(headerField + "is not Encoded-String-Value header field!");
    711                             return null;
    712                         }
    713                     }
    714                     break;
    715                 }
    716 
    717                 case PduHeaders.PREVIOUSLY_SENT_DATE: {
    718                     /* Previously-sent-date-value =
    719                      * Value-length Forwarded-count-value Date-value */
    720                     /* parse value-length */
    721                     parseValueLength(pduDataStream);
    722 
    723                     /* parse Forwarded-count-value */
    724                     try {
    725                         parseIntegerValue(pduDataStream);
    726                     } catch (RuntimeException e) {
    727                         log(headerField + " is not Integer-Value");
    728                         return null;
    729                     }
    730 
    731                     /* Date-value */
    732                     try {
    733                         long previouslySentDate = parseLongInteger(pduDataStream);
    734                         if (LOCAL_LOGV) {
    735                             Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    736                                     " (" + Integer.toHexString(headerField) +
    737                                     ") PREVIOUSLY_SENT_DATE: " + previouslySentDate);
    738                         }
    739                         headers.setLongInteger(previouslySentDate,
    740                                 PduHeaders.PREVIOUSLY_SENT_DATE);
    741                     } catch (RuntimeException e) {
    742                         log(headerField + "is not Long-Integer header field!");
    743                         return null;
    744                     }
    745                     break;
    746                 }
    747 
    748                 case PduHeaders.MM_FLAGS: {
    749                     /* MM-flags-value =
    750                      * Value-length
    751                      * ( Add-token | Remove-token | Filter-token )
    752                      * Encoded-string-value
    753                      */
    754                     if (LOCAL_LOGV) {
    755                         Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    756                                 " (" + Integer.toHexString(headerField) + ") MM_FLAGS: " +
    757                                 " NOT REALLY SUPPORTED");
    758                     }
    759 
    760                     /* parse Value-length */
    761                     parseValueLength(pduDataStream);
    762 
    763                     /* Add-token | Remove-token | Filter-token */
    764                     extractByteValue(pduDataStream);
    765 
    766                     /* Encoded-string-value */
    767                     parseEncodedStringValue(pduDataStream);
    768 
    769                     /* not store this header filed in "headers",
    770                      * because now PduHeaders doesn't support it */
    771                     break;
    772                 }
    773 
    774                 /* Value-length
    775                  * (Message-total-token | Size-total-token) Integer-Value */
    776                 case PduHeaders.MBOX_TOTALS:
    777                 case PduHeaders.MBOX_QUOTAS: {
    778                     if (LOCAL_LOGV) {
    779                         Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    780                                 " (" + Integer.toHexString(headerField) + ") MBOX_");
    781                     }
    782                     /* Value-length */
    783                     parseValueLength(pduDataStream);
    784 
    785                     /* Message-total-token | Size-total-token */
    786                     extractByteValue(pduDataStream);
    787 
    788                     /*Integer-Value*/
    789                     try {
    790                         parseIntegerValue(pduDataStream);
    791                     } catch (RuntimeException e) {
    792                         log(headerField + " is not Integer-Value");
    793                         return null;
    794                     }
    795 
    796                     /* not store these headers filed in "headers",
    797                     because now PduHeaders doesn't support them */
    798                     break;
    799                 }
    800 
    801                 case PduHeaders.ELEMENT_DESCRIPTOR: {
    802                     if (LOCAL_LOGV) {
    803                         Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    804                                 " (" + Integer.toHexString(headerField) + ") ELEMENT_DESCRIPTOR");
    805                     }
    806                     parseContentType(pduDataStream, null);
    807 
    808                     /* not store this header filed in "headers",
    809                     because now PduHeaders doesn't support it */
    810                     break;
    811                 }
    812 
    813                 case PduHeaders.CONTENT_TYPE: {
    814                     SparseArray<Object> map = new SparseArray<Object>();
    815                     byte[] contentType =
    816                             parseContentType(pduDataStream, map);
    817 
    818                     if (null != contentType) {
    819                         try {
    820                             if (LOCAL_LOGV) {
    821                                 Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
    822                                         " (" + Integer.toHexString(headerField) + ") CONTENT_TYPE: "
    823                                         + Arrays.toString(contentType));
    824                             }
    825                             headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
    826                         } catch (NullPointerException e) {
    827                             log("null pointer error!");
    828                         } catch (RuntimeException e) {
    829                             log(headerField + "is not Text-String header field!");
    830                             return null;
    831                         }
    832                     }
    833 
    834                     /* get start parameter */
    835                     mStartParam = (byte[]) map.get(PduPart.P_START);
    836 
    837                     /* get charset parameter */
    838                     mTypeParam = (byte[]) map.get(PduPart.P_TYPE);
    839 
    840                     keepParsing = false;
    841                     break;
    842                 }
    843 
    844                 case PduHeaders.CONTENT:
    845                 case PduHeaders.ADDITIONAL_HEADERS:
    846                 case PduHeaders.ATTRIBUTES:
    847                 default: {
    848                     if (LOCAL_LOGV) {
    849                         Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField +
    850                                 " (" + Integer.toHexString(headerField) + ")");
    851                     }
    852                     log("Unknown header");
    853                 }
    854             }
    855         }
    856 
    857         return headers;
    858     }
    859 
    860     /**
    861      * Parse pdu parts.
    862      *
    863      * @param pduDataStream pdu data input stream
    864      * @return parts in PduBody structure
    865      */
    866     protected PduBody parseParts(ByteArrayInputStream pduDataStream) {
    867         if (pduDataStream == null) {
    868             return null;
    869         }
    870 
    871         int count = parseUnsignedInt(pduDataStream); // get the number of parts
    872         PduBody body = new PduBody();
    873 
    874         for (int i = 0; i < count; i++) {
    875             int headerLength = parseUnsignedInt(pduDataStream);
    876             int dataLength = parseUnsignedInt(pduDataStream);
    877             PduPart part = new PduPart();
    878             int startPos = pduDataStream.available();
    879             if (startPos <= 0) {
    880                 // Invalid part.
    881                 return null;
    882             }
    883 
    884             /* parse part's content-type */
    885             SparseArray<Object> map = new SparseArray<Object>();
    886             byte[] contentType = parseContentType(pduDataStream, map);
    887             if (null != contentType) {
    888                 part.setContentType(contentType);
    889             } else {
    890                 part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
    891             }
    892 
    893             /* get name parameter */
    894             byte[] name = (byte[]) map.get(PduPart.P_NAME);
    895             if (null != name) {
    896                 part.setName(name);
    897             }
    898 
    899             /* get charset parameter */
    900             Integer charset = (Integer) map.get(PduPart.P_CHARSET);
    901             if (null != charset) {
    902                 part.setCharset(charset);
    903             }
    904 
    905             /* parse part's headers */
    906             int endPos = pduDataStream.available();
    907             int partHeaderLen = headerLength - (startPos - endPos);
    908             if (partHeaderLen > 0) {
    909                 if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
    910                     // Parse part header faild.
    911                     return null;
    912                 }
    913             } else if (partHeaderLen < 0) {
    914                 // Invalid length of content-type.
    915                 return null;
    916             }
    917 
    918             /* TODO: check content-id, name, filename and content location,
    919              * if not set anyone of them, generate a default content-location
    920              */
    921             if ((null == part.getContentLocation())
    922                     && (null == part.getName())
    923                     && (null == part.getFilename())
    924                     && (null == part.getContentId())) {
    925                 part.setContentLocation(Long.toOctalString(
    926                         System.currentTimeMillis()).getBytes());
    927             }
    928 
    929             /* get part's data */
    930             if (dataLength > 0) {
    931                 byte[] partData = new byte[dataLength];
    932                 String partContentType = new String(part.getContentType());
    933                 pduDataStream.read(partData, 0, dataLength);
    934                 if (partContentType.equalsIgnoreCase(ContentType.MMS_MULTIPART_ALTERNATIVE)) {
    935                     // parse "multipart/vnd.wap.multipart.alternative".
    936                     PduBody childBody = parseParts(new ByteArrayInputStream(partData));
    937                     // take the first part of children.
    938                     part = childBody.getPart(0);
    939                 } else {
    940                     // Check Content-Transfer-Encoding.
    941                     byte[] partDataEncoding = part.getContentTransferEncoding();
    942                     if (null != partDataEncoding) {
    943                         String encoding = new String(partDataEncoding);
    944                         if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
    945                             // Decode "base64" into "binary".
    946                             partData = Base64.decodeBase64(partData);
    947                         } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
    948                             // Decode "quoted-printable" into "binary".
    949                             partData = QuotedPrintable.decodeQuotedPrintable(partData);
    950                         } else {
    951                             // "binary" is the default encoding.
    952                         }
    953                     }
    954                     if (null == partData) {
    955                         log("Decode part data error!");
    956                         return null;
    957                     }
    958                     part.setData(partData);
    959                 }
    960             }
    961 
    962             /* add this part to body */
    963             if (THE_FIRST_PART == checkPartPosition(part)) {
    964                 /* this is the first part */
    965                 body.addPart(0, part);
    966             } else {
    967                 /* add the part to the end */
    968                 body.addPart(part);
    969             }
    970         }
    971 
    972         return body;
    973     }
    974 
    975     /**
    976      * Log status.
    977      *
    978      * @param text log information
    979      */
    980     private static void log(String text) {
    981         if (LOCAL_LOGV) {
    982             Log.v(LOG_TAG, text);
    983         }
    984     }
    985 
    986     /**
    987      * Parse unsigned integer.
    988      *
    989      * @param pduDataStream pdu data input stream
    990      * @return the integer, -1 when failed
    991      */
    992     protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
    993         /**
    994          * From wap-230-wsp-20010705-a.pdf
    995          * The maximum size of a uintvar is 32 bits.
    996          * So it will be encoded in no more than 5 octets.
    997          */
    998         assert (null != pduDataStream);
    999         int result = 0;
   1000         int temp = pduDataStream.read();
   1001         if (temp == -1) {
   1002             return temp;
   1003         }
   1004 
   1005         while ((temp & 0x80) != 0) {
   1006             result = result << 7;
   1007             result |= temp & 0x7F;
   1008             temp = pduDataStream.read();
   1009             if (temp == -1) {
   1010                 return temp;
   1011             }
   1012         }
   1013 
   1014         result = result << 7;
   1015         result |= temp & 0x7F;
   1016 
   1017         return result;
   1018     }
   1019 
   1020     /**
   1021      * Parse value length.
   1022      *
   1023      * @param pduDataStream pdu data input stream
   1024      * @return the integer
   1025      */
   1026     protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
   1027         /**
   1028          * From wap-230-wsp-20010705-a.pdf
   1029          * Value-length = Short-length | (Length-quote Length)
   1030          * Short-length = <Any octet 0-30>
   1031          * Length-quote = <Octet 31>
   1032          * Length = Uintvar-integer
   1033          * Uintvar-integer = 1*5 OCTET
   1034          */
   1035         assert (null != pduDataStream);
   1036         int temp = pduDataStream.read();
   1037         assert (-1 != temp);
   1038         int first = temp & 0xFF;
   1039 
   1040         if (first <= SHORT_LENGTH_MAX) {
   1041             return first;
   1042         } else if (first == LENGTH_QUOTE) {
   1043             return parseUnsignedInt(pduDataStream);
   1044         }
   1045 
   1046         throw new RuntimeException("Value length > LENGTH_QUOTE!");
   1047     }
   1048 
   1049     /**
   1050      * Parse encoded string value.
   1051      *
   1052      * @param pduDataStream pdu data input stream
   1053      * @return the EncodedStringValue
   1054      */
   1055     protected static EncodedStringValue parseEncodedStringValue(
   1056             ByteArrayInputStream pduDataStream) {
   1057         /**
   1058          * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
   1059          * Encoded-string-value = Text-string | Value-length Char-set Text-string
   1060          */
   1061         assert (null != pduDataStream);
   1062         pduDataStream.mark(1);
   1063         EncodedStringValue returnValue = null;
   1064         int charset = 0;
   1065         int temp = pduDataStream.read();
   1066         assert (-1 != temp);
   1067         int first = temp & 0xFF;
   1068         if (first == 0) {
   1069             return null;    //  Blank subject, bail.
   1070         }
   1071 
   1072         pduDataStream.reset();
   1073         if (first < TEXT_MIN) {
   1074             parseValueLength(pduDataStream);
   1075 
   1076             charset = parseShortInteger(pduDataStream); //get the "Charset"
   1077         }
   1078 
   1079         byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1080 
   1081         try {
   1082             if (0 != charset) {
   1083                 returnValue = new EncodedStringValue(charset, textString);
   1084             } else {
   1085                 returnValue = new EncodedStringValue(textString);
   1086             }
   1087         } catch (Exception e) {
   1088             return null;
   1089         }
   1090 
   1091         return returnValue;
   1092     }
   1093 
   1094     /**
   1095      * Parse Text-String or Quoted-String.
   1096      *
   1097      * @param pduDataStream pdu data input stream
   1098      * @param stringType    TYPE_TEXT_STRING or TYPE_QUOTED_STRING
   1099      * @return the string without End-of-string in byte array
   1100      */
   1101     protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
   1102             int stringType) {
   1103         assert (null != pduDataStream);
   1104         /**
   1105          * From wap-230-wsp-20010705-a.pdf
   1106          * Text-string = [Quote] *TEXT End-of-string
   1107          * If the first character in the TEXT is in the range of 128-255,
   1108          * a Quote character must precede it.
   1109          * Otherwise the Quote character must be omitted.
   1110          * The Quote is not part of the contents.
   1111          * Quote = <Octet 127>
   1112          * End-of-string = <Octet 0>
   1113          *
   1114          * Quoted-string = <Octet 34> *TEXT End-of-string
   1115          *
   1116          * Token-text = Token End-of-string
   1117          */
   1118 
   1119         // Mark supposed beginning of Text-string
   1120         // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
   1121         pduDataStream.mark(1);
   1122 
   1123         // Check first char
   1124         int temp = pduDataStream.read();
   1125         assert (-1 != temp);
   1126         if ((TYPE_QUOTED_STRING == stringType) &&
   1127                 (QUOTED_STRING_FLAG == temp)) {
   1128             // Mark again if QUOTED_STRING_FLAG and ignore it
   1129             pduDataStream.mark(1);
   1130         } else if ((TYPE_TEXT_STRING == stringType) &&
   1131                 (QUOTE == temp)) {
   1132             // Mark again if QUOTE and ignore it
   1133             pduDataStream.mark(1);
   1134         } else {
   1135             // Otherwise go back to origin
   1136             pduDataStream.reset();
   1137         }
   1138 
   1139         // We are now definitely at the beginning of string
   1140         /**
   1141          * Return *TOKEN or *TEXT (Text-String without QUOTE,
   1142          * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
   1143          */
   1144         return getWapString(pduDataStream, stringType);
   1145     }
   1146 
   1147     /**
   1148      * Check TOKEN data defined in RFC2616.
   1149      *
   1150      * @param ch checking data
   1151      * @return true when ch is TOKEN, false when ch is not TOKEN
   1152      */
   1153     protected static boolean isTokenCharacter(int ch) {
   1154         /**
   1155          * Token      = 1*<any CHAR except CTLs or separators>
   1156          * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
   1157          *            | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
   1158          *            | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
   1159          *            | "{"(123) | "}"(125) | SP(32) | HT(9)
   1160          * CHAR       = <any US-ASCII character (octets 0 - 127)>
   1161          * CTL        = <any US-ASCII control character
   1162          *            (octets 0 - 31) and DEL (127)>
   1163          * SP         = <US-ASCII SP, space (32)>
   1164          * HT         = <US-ASCII HT, horizontal-tab (9)>
   1165          */
   1166         if ((ch < 33) || (ch > 126)) {
   1167             return false;
   1168         }
   1169 
   1170         switch (ch) {
   1171             case '"': /* '"' */
   1172             case '(': /* '(' */
   1173             case ')': /* ')' */
   1174             case ',': /* ',' */
   1175             case '/': /* '/' */
   1176             case ':': /* ':' */
   1177             case ';': /* ';' */
   1178             case '<': /* '<' */
   1179             case '=': /* '=' */
   1180             case '>': /* '>' */
   1181             case '?': /* '?' */
   1182             case '@': /* '@' */
   1183             case '[': /* '[' */
   1184             case '\\': /* '\' */
   1185             case ']': /* ']' */
   1186             case '{': /* '{' */
   1187             case '}': /* '}' */
   1188                 return false;
   1189         }
   1190 
   1191         return true;
   1192     }
   1193 
   1194     /**
   1195      * Check TEXT data defined in RFC2616.
   1196      *
   1197      * @param ch checking data
   1198      * @return true when ch is TEXT, false when ch is not TEXT
   1199      */
   1200     protected static boolean isText(int ch) {
   1201         /**
   1202          * TEXT = <any OCTET except CTLs,
   1203          *      but including LWS>
   1204          * CTL  = <any US-ASCII control character
   1205          *      (octets 0 - 31) and DEL (127)>
   1206          * LWS  = [CRLF] 1*( SP | HT )
   1207          * CRLF = CR LF
   1208          * CR   = <US-ASCII CR, carriage return (13)>
   1209          * LF   = <US-ASCII LF, linefeed (10)>
   1210          */
   1211         if (((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
   1212             return true;
   1213         }
   1214 
   1215         switch (ch) {
   1216             case '\t': /* '\t' */
   1217             case '\n': /* '\n' */
   1218             case '\r': /* '\r' */
   1219                 return true;
   1220         }
   1221 
   1222         return false;
   1223     }
   1224 
   1225     protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
   1226             int stringType) {
   1227         assert (null != pduDataStream);
   1228         ByteArrayOutputStream out = new ByteArrayOutputStream();
   1229         int temp = pduDataStream.read();
   1230         assert (-1 != temp);
   1231         while ((-1 != temp) && ('\0' != temp)) {
   1232             // check each of the character
   1233             if (stringType == TYPE_TOKEN_STRING) {
   1234                 if (isTokenCharacter(temp)) {
   1235                     out.write(temp);
   1236                 }
   1237             } else {
   1238                 if (isText(temp)) {
   1239                     out.write(temp);
   1240                 }
   1241             }
   1242 
   1243             temp = pduDataStream.read();
   1244             assert (-1 != temp);
   1245         }
   1246 
   1247         if (out.size() > 0) {
   1248             return out.toByteArray();
   1249         }
   1250 
   1251         return null;
   1252     }
   1253 
   1254     /**
   1255      * Extract a byte value from the input stream.
   1256      *
   1257      * @param pduDataStream pdu data input stream
   1258      * @return the byte
   1259      */
   1260     protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
   1261         assert (null != pduDataStream);
   1262         int temp = pduDataStream.read();
   1263         assert (-1 != temp);
   1264         return temp & 0xFF;
   1265     }
   1266 
   1267     /**
   1268      * Parse Short-Integer.
   1269      *
   1270      * @param pduDataStream pdu data input stream
   1271      * @return the byte
   1272      */
   1273     protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
   1274         /**
   1275          * From wap-230-wsp-20010705-a.pdf
   1276          * Short-integer = OCTET
   1277          * Integers in range 0-127 shall be encoded as a one
   1278          * octet value with the most significant bit set to one (1xxx xxxx)
   1279          * and with the value in the remaining least significant bits.
   1280          */
   1281         assert (null != pduDataStream);
   1282         int temp = pduDataStream.read();
   1283         assert (-1 != temp);
   1284         return temp & 0x7F;
   1285     }
   1286 
   1287     /**
   1288      * Parse Long-Integer.
   1289      *
   1290      * @param pduDataStream pdu data input stream
   1291      * @return long integer
   1292      */
   1293     protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
   1294         /**
   1295          * From wap-230-wsp-20010705-a.pdf
   1296          * Long-integer = Short-length Multi-octet-integer
   1297          * The Short-length indicates the length of the Multi-octet-integer
   1298          * Multi-octet-integer = 1*30 OCTET
   1299          * The content octets shall be an unsigned integer value
   1300          * with the most significant octet encoded first (big-endian representation).
   1301          * The minimum number of octets must be used to encode the value.
   1302          * Short-length = <Any octet 0-30>
   1303          */
   1304         assert (null != pduDataStream);
   1305         int temp = pduDataStream.read();
   1306         assert (-1 != temp);
   1307         int count = temp & 0xFF;
   1308 
   1309         if (count > LONG_INTEGER_LENGTH_MAX) {
   1310             throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
   1311         }
   1312 
   1313         long result = 0;
   1314 
   1315         for (int i = 0; i < count; i++) {
   1316             temp = pduDataStream.read();
   1317             assert (-1 != temp);
   1318             result <<= 8;
   1319             result += (temp & 0xFF);
   1320         }
   1321 
   1322         return result;
   1323     }
   1324 
   1325     /**
   1326      * Parse Integer-Value.
   1327      *
   1328      * @param pduDataStream pdu data input stream
   1329      * @return long integer
   1330      */
   1331     protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
   1332         /**
   1333          * From wap-230-wsp-20010705-a.pdf
   1334          * Integer-Value = Short-integer | Long-integer
   1335          */
   1336         assert (null != pduDataStream);
   1337         pduDataStream.mark(1);
   1338         int temp = pduDataStream.read();
   1339         assert (-1 != temp);
   1340         pduDataStream.reset();
   1341         if (temp > SHORT_INTEGER_MAX) {
   1342             return parseShortInteger(pduDataStream);
   1343         } else {
   1344             return parseLongInteger(pduDataStream);
   1345         }
   1346     }
   1347 
   1348     /**
   1349      * To skip length of the wap value.
   1350      *
   1351      * @param pduDataStream pdu data input stream
   1352      * @param length        area size
   1353      * @return the values in this area
   1354      */
   1355     protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
   1356         assert (null != pduDataStream);
   1357         byte[] area = new byte[length];
   1358         int readLen = pduDataStream.read(area, 0, length);
   1359         if (readLen < length) { //The actually read length is lower than the length
   1360             return -1;
   1361         } else {
   1362             return readLen;
   1363         }
   1364     }
   1365 
   1366     /**
   1367      * Parse content type parameters. For now we just support
   1368      * four parameters used in mms: "type", "start", "name", "charset".
   1369      *
   1370      * @param pduDataStream pdu data input stream
   1371      * @param map           to store parameters of Content-Type field
   1372      * @param length        length of all the parameters
   1373      */
   1374     protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
   1375             SparseArray<Object> map, Integer length) {
   1376         /**
   1377          * From wap-230-wsp-20010705-a.pdf
   1378          * Parameter = Typed-parameter | Untyped-parameter
   1379          * Typed-parameter = Well-known-parameter-token Typed-value
   1380          * the actual expected type of the value is implied by the well-known parameter
   1381          * Well-known-parameter-token = Integer-value
   1382          * the code values used for parameters are specified in the Assigned Numbers appendix
   1383          * Typed-value = Compact-value | Text-value
   1384          * In addition to the expected type, there may be no value.
   1385          * If the value cannot be encoded using the expected type, it shall be encoded as text.
   1386          * Compact-value = Integer-value |
   1387          * Date-value | Delta-seconds-value | Q-value | Version-value |
   1388          * Uri-value
   1389          * Untyped-parameter = Token-text Untyped-value
   1390          * the type of the value is unknown, but it shall be encoded as an integer,
   1391          * if that is possible.
   1392          * Untyped-value = Integer-value | Text-value
   1393          */
   1394         assert (null != pduDataStream);
   1395         assert (length > 0);
   1396 
   1397         int startPos = pduDataStream.available();
   1398         int tempPos = 0;
   1399         int lastLen = length;
   1400         while (0 < lastLen) {
   1401             int param = pduDataStream.read();
   1402             assert (-1 != param);
   1403             lastLen--;
   1404 
   1405             switch (param) {
   1406                 /**
   1407                  * From rfc2387, chapter 3.1
   1408                  * The type parameter must be specified and its value is the MIME media
   1409                  * type of the "root" body part. It permits a MIME user agent to
   1410                  * determine the content-type without reference to the enclosed body
   1411                  * part. If the value of the type parameter and the root body part's
   1412                  * content-type differ then the User Agent's behavior is undefined.
   1413                  *
   1414                  * From wap-230-wsp-20010705-a.pdf
   1415                  * type = Constrained-encoding
   1416                  * Constrained-encoding = Extension-Media | Short-integer
   1417                  * Extension-media = *TEXT End-of-string
   1418                  */
   1419                 case PduPart.P_TYPE:
   1420                 case PduPart.P_CT_MR_TYPE:
   1421                     pduDataStream.mark(1);
   1422                     int first = extractByteValue(pduDataStream);
   1423                     pduDataStream.reset();
   1424                     if (first > TEXT_MAX) {
   1425                         // Short-integer (well-known type)
   1426                         int index = parseShortInteger(pduDataStream);
   1427 
   1428                         if (index < PduContentTypes.contentTypes.length) {
   1429                             byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
   1430                             map.put(PduPart.P_TYPE, type);
   1431                         } else {
   1432                             //not support this type, ignore it.
   1433                         }
   1434                     } else {
   1435                         // Text-String (extension-media)
   1436                         byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1437                         if ((null != type) && (null != map)) {
   1438                             map.put(PduPart.P_TYPE, type);
   1439                         }
   1440                     }
   1441 
   1442                     tempPos = pduDataStream.available();
   1443                     lastLen = length - (startPos - tempPos);
   1444                     break;
   1445 
   1446                     /**
   1447                      * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
   1448                      * Start Parameter Referring to Presentation
   1449                      *
   1450                      * From rfc2387, chapter 3.2
   1451                      * The start parameter, if given, is the content-ID of the compound
   1452                      * object's "root". If not present the "root" is the first body part in
   1453                      * the Multipart/Related entity. The "root" is the element the
   1454                      * applications processes first.
   1455                      *
   1456                      * From wap-230-wsp-20010705-a.pdf
   1457                      * start = Text-String
   1458                      */
   1459                 case PduPart.P_START:
   1460                 case PduPart.P_DEP_START:
   1461                     byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1462                     if ((null != start) && (null != map)) {
   1463                         map.put(PduPart.P_START, start);
   1464                     }
   1465 
   1466                     tempPos = pduDataStream.available();
   1467                     lastLen = length - (startPos - tempPos);
   1468                     break;
   1469 
   1470                     /**
   1471                      * From oma-ts-mms-conf-v1_3.pdf
   1472                      * In creation, the character set SHALL be either us-ascii
   1473                      * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
   1474                      * In retrieval, both us-ascii and utf-8 SHALL be supported.
   1475                      *
   1476                      * From wap-230-wsp-20010705-a.pdf
   1477                      * charset = Well-known-charset|Text-String
   1478                      * Well-known-charset = Any-charset | Integer-value
   1479                      * Both are encoded using values from Character Set
   1480                      * Assignments table in Assigned Numbers
   1481                      * Any-charset = <Octet 128>
   1482                      * Equivalent to the special RFC2616 charset value "*"
   1483                      */
   1484                 case PduPart.P_CHARSET:
   1485                     pduDataStream.mark(1);
   1486                     int firstValue = extractByteValue(pduDataStream);
   1487                     pduDataStream.reset();
   1488                     //Check first char
   1489                     if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
   1490                             (END_STRING_FLAG == firstValue)) {
   1491                         //Text-String (extension-charset)
   1492                         byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1493                         try {
   1494                             int charsetInt = CharacterSets.getMibEnumValue(
   1495                                     new String(charsetStr));
   1496                             map.put(PduPart.P_CHARSET, charsetInt);
   1497                         } catch (UnsupportedEncodingException e) {
   1498                             // Not a well-known charset, use "*".
   1499                             Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
   1500                             map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
   1501                         }
   1502                     } else {
   1503                         //Well-known-charset
   1504                         int charset = (int) parseIntegerValue(pduDataStream);
   1505                         if (map != null) {
   1506                             map.put(PduPart.P_CHARSET, charset);
   1507                         }
   1508                     }
   1509 
   1510                     tempPos = pduDataStream.available();
   1511                     lastLen = length - (startPos - tempPos);
   1512                     break;
   1513 
   1514                     /**
   1515                      * From oma-ts-mms-conf-v1_3.pdf
   1516                      * A name for multipart object SHALL be encoded using name-parameter
   1517                      * for Content-Type header in WSP multipart headers.
   1518                      *
   1519                      * From wap-230-wsp-20010705-a.pdf
   1520                      * name = Text-String
   1521                      */
   1522                 case PduPart.P_DEP_NAME:
   1523                 case PduPart.P_NAME:
   1524                     byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1525                     if ((null != name) && (null != map)) {
   1526                         map.put(PduPart.P_NAME, name);
   1527                     }
   1528 
   1529                     tempPos = pduDataStream.available();
   1530                     lastLen = length - (startPos - tempPos);
   1531                     break;
   1532                 default:
   1533                     if (LOCAL_LOGV) {
   1534                         Log.v(LOG_TAG, "Not supported Content-Type parameter");
   1535                     }
   1536                     if (-1 == skipWapValue(pduDataStream, lastLen)) {
   1537                         Log.e(LOG_TAG, "Corrupt Content-Type");
   1538                     } else {
   1539                         lastLen = 0;
   1540                     }
   1541                     break;
   1542             }
   1543         }
   1544 
   1545         if (0 != lastLen) {
   1546             Log.e(LOG_TAG, "Corrupt Content-Type");
   1547         }
   1548     }
   1549 
   1550     /**
   1551      * Parse content type.
   1552      *
   1553      * @param pduDataStream pdu data input stream
   1554      * @param map           to store parameters in Content-Type header field
   1555      * @return Content-Type value
   1556      */
   1557     protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
   1558             SparseArray<Object> map) {
   1559         /**
   1560          * From wap-230-wsp-20010705-a.pdf
   1561          * Content-type-value = Constrained-media | Content-general-form
   1562          * Content-general-form = Value-length Media-type
   1563          * Media-type = (Well-known-media | Extension-Media) *(Parameter)
   1564          */
   1565         assert (null != pduDataStream);
   1566 
   1567         byte[] contentType = null;
   1568         pduDataStream.mark(1);
   1569         int temp = pduDataStream.read();
   1570         assert (-1 != temp);
   1571         pduDataStream.reset();
   1572 
   1573         int cur = (temp & 0xFF);
   1574 
   1575         if (cur < TEXT_MIN) {
   1576             int length = parseValueLength(pduDataStream);
   1577             int startPos = pduDataStream.available();
   1578             pduDataStream.mark(1);
   1579             temp = pduDataStream.read();
   1580             assert (-1 != temp);
   1581             pduDataStream.reset();
   1582             int first = (temp & 0xFF);
   1583 
   1584             if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
   1585                 contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1586             } else if (first > TEXT_MAX) {
   1587                 int index = parseShortInteger(pduDataStream);
   1588 
   1589                 if (index < PduContentTypes.contentTypes.length) { //well-known type
   1590                     contentType = (PduContentTypes.contentTypes[index]).getBytes();
   1591                 } else {
   1592                     pduDataStream.reset();
   1593                     contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1594                 }
   1595             } else {
   1596                 Log.e(LOG_TAG, "Corrupt content-type");
   1597                 return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
   1598             }
   1599 
   1600             int endPos = pduDataStream.available();
   1601             int parameterLen = length - (startPos - endPos);
   1602             if (parameterLen > 0) {//have parameters
   1603                 parseContentTypeParams(pduDataStream, map, parameterLen);
   1604             }
   1605 
   1606             if (parameterLen < 0) {
   1607                 Log.e(LOG_TAG, "Corrupt MMS message");
   1608                 return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
   1609             }
   1610         } else if (cur <= TEXT_MAX) {
   1611             contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1612         } else {
   1613             contentType =
   1614                     (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
   1615         }
   1616 
   1617         return contentType;
   1618     }
   1619 
   1620     /**
   1621      * Parse part's headers.
   1622      *
   1623      * @param pduDataStream pdu data input stream
   1624      * @param part          to store the header informations of the part
   1625      * @param length        length of the headers
   1626      * @return true if parse successfully, false otherwise
   1627      */
   1628     protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
   1629             PduPart part, int length) {
   1630         assert (null != pduDataStream);
   1631         assert (null != part);
   1632         assert (length > 0);
   1633 
   1634         /**
   1635          * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
   1636          * A name for multipart object SHALL be encoded using name-parameter
   1637          * for Content-Type header in WSP multipart headers.
   1638          * In decoding, name-parameter of Content-Type SHALL be used if available.
   1639          * If name-parameter of Content-Type is not available,
   1640          * filename parameter of Content-Disposition header SHALL be used if available.
   1641          * If neither name-parameter of Content-Type header nor filename parameter
   1642          * of Content-Disposition header is available,
   1643          * Content-Location header SHALL be used if available.
   1644          *
   1645          * Within SMIL part the reference to the media object parts SHALL use
   1646          * either Content-ID or Content-Location mechanism [RFC2557]
   1647          * and the corresponding WSP part headers in media object parts
   1648          * contain the corresponding definitions.
   1649          */
   1650         int startPos = pduDataStream.available();
   1651         int tempPos = 0;
   1652         int lastLen = length;
   1653         while (0 < lastLen) {
   1654             int header = pduDataStream.read();
   1655             assert (-1 != header);
   1656             lastLen--;
   1657 
   1658             if (header > TEXT_MAX) {
   1659                 // Number assigned headers.
   1660                 switch (header) {
   1661                     case PduPart.P_CONTENT_LOCATION:
   1662                         /**
   1663                          * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
   1664                          * Content-location-value = Uri-value
   1665                          */
   1666                         byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1667                         if (null != contentLocation) {
   1668                             part.setContentLocation(contentLocation);
   1669                         }
   1670 
   1671                         tempPos = pduDataStream.available();
   1672                         lastLen = length - (startPos - tempPos);
   1673                         break;
   1674                     case PduPart.P_CONTENT_ID:
   1675                         /**
   1676                          * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
   1677                          * Content-ID-value = Quoted-string
   1678                          */
   1679                         byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
   1680                         if (null != contentId) {
   1681                             part.setContentId(contentId);
   1682                         }
   1683 
   1684                         tempPos = pduDataStream.available();
   1685                         lastLen = length - (startPos - tempPos);
   1686                         break;
   1687                     case PduPart.P_DEP_CONTENT_DISPOSITION:
   1688                     case PduPart.P_CONTENT_DISPOSITION:
   1689                         /*
   1690                          * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
   1691                          * Content-disposition-value = Value-length Disposition *(Parameter)
   1692                          * Disposition = Form-data | Attachment | Inline | Token-text
   1693                          * Form-data = <Octet 128>
   1694                          * Attachment = <Octet 129>
   1695                          * Inline = <Octet 130>
   1696                          *
   1697                          * some carrier mmsc servers do not support content_disposition
   1698                          * field correctly
   1699                         */
   1700                         if (mParseContentDisposition) {
   1701                             int len = parseValueLength(pduDataStream);
   1702                             pduDataStream.mark(1);
   1703                             int thisStartPos = pduDataStream.available();
   1704                             int thisEndPos = 0;
   1705                             int value = pduDataStream.read();
   1706 
   1707                             if (value == PduPart.P_DISPOSITION_FROM_DATA) {
   1708                                 part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
   1709                             } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
   1710                                 part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
   1711                             } else if (value == PduPart.P_DISPOSITION_INLINE) {
   1712                                 part.setContentDisposition(PduPart.DISPOSITION_INLINE);
   1713                             } else {
   1714                                 pduDataStream.reset();
   1715                                 /* Token-text */
   1716                                 part.setContentDisposition(parseWapString(pduDataStream
   1717                                         , TYPE_TEXT_STRING));
   1718                             }
   1719 
   1720                             /* get filename parameter and skip other parameters */
   1721                             thisEndPos = pduDataStream.available();
   1722                             if (thisStartPos - thisEndPos < len) {
   1723                                 value = pduDataStream.read();
   1724                                 if (value == PduPart.P_FILENAME) { //filename is text-string
   1725                                     part.setFilename(parseWapString(pduDataStream
   1726                                             , TYPE_TEXT_STRING));
   1727                                 }
   1728 
   1729                                 /* skip other parameters */
   1730                                 thisEndPos = pduDataStream.available();
   1731                                 if (thisStartPos - thisEndPos < len) {
   1732                                     int last = len - (thisStartPos - thisEndPos);
   1733                                     byte[] temp = new byte[last];
   1734                                     pduDataStream.read(temp, 0, last);
   1735                                 }
   1736                             }
   1737 
   1738                             tempPos = pduDataStream.available();
   1739                             lastLen = length - (startPos - tempPos);
   1740                         }
   1741                         break;
   1742                     default:
   1743                         if (LOCAL_LOGV) {
   1744                             Log.v(LOG_TAG, "Not supported Part headers: " + header);
   1745                         }
   1746                         if (-1 == skipWapValue(pduDataStream, lastLen)) {
   1747                             Log.e(LOG_TAG, "Corrupt Part headers");
   1748                             return false;
   1749                         }
   1750                         lastLen = 0;
   1751                         break;
   1752                 }
   1753             } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
   1754                 // Not assigned header.
   1755                 byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1756                 byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
   1757 
   1758                 // Check the header whether it is "Content-Transfer-Encoding".
   1759                 if (true ==
   1760                         PduPart.CONTENT_TRANSFER_ENCODING
   1761                                 .equalsIgnoreCase(new String(tempHeader))) {
   1762                     part.setContentTransferEncoding(tempValue);
   1763                 }
   1764 
   1765                 tempPos = pduDataStream.available();
   1766                 lastLen = length - (startPos - tempPos);
   1767             } else {
   1768                 if (LOCAL_LOGV) {
   1769                     Log.v(LOG_TAG, "Not supported Part headers: " + header);
   1770                 }
   1771                 // Skip all headers of this part.
   1772                 if (-1 == skipWapValue(pduDataStream, lastLen)) {
   1773                     Log.e(LOG_TAG, "Corrupt Part headers");
   1774                     return false;
   1775                 }
   1776                 lastLen = 0;
   1777             }
   1778         }
   1779 
   1780         if (0 != lastLen) {
   1781             Log.e(LOG_TAG, "Corrupt Part headers");
   1782             return false;
   1783         }
   1784 
   1785         return true;
   1786     }
   1787 
   1788     /**
   1789      * Check the position of a specified part.
   1790      *
   1791      * @param part the part to be checked
   1792      * @return part position, THE_FIRST_PART when it's the
   1793      * first one, THE_LAST_PART when it's the last one.
   1794      */
   1795     private static int checkPartPosition(PduPart part) {
   1796         assert (null != part);
   1797         if ((null == mTypeParam) &&
   1798                 (null == mStartParam)) {
   1799             return THE_LAST_PART;
   1800         }
   1801 
   1802         /* check part's content-id */
   1803         if (null != mStartParam) {
   1804             byte[] contentId = part.getContentId();
   1805             if (null != contentId) {
   1806                 if (true == Arrays.equals(mStartParam, contentId)) {
   1807                     return THE_FIRST_PART;
   1808                 }
   1809             }
   1810             // This is not the first part, so append to end (keeping the original order)
   1811             // Check b/19607294 for details of this change
   1812             return THE_LAST_PART;
   1813         }
   1814 
   1815         /* check part's content-type */
   1816         if (null != mTypeParam) {
   1817             byte[] contentType = part.getContentType();
   1818             if (null != contentType) {
   1819                 if (true == Arrays.equals(mTypeParam, contentType)) {
   1820                     return THE_FIRST_PART;
   1821                 }
   1822             }
   1823         }
   1824 
   1825         return THE_LAST_PART;
   1826     }
   1827 
   1828     /**
   1829      * Check mandatory headers of a pdu.
   1830      *
   1831      * @param headers pdu headers
   1832      * @return true if the pdu has all of the mandatory headers, false otherwise.
   1833      */
   1834     protected static boolean checkMandatoryHeader(PduHeaders headers) {
   1835         if (null == headers) {
   1836             return false;
   1837         }
   1838 
   1839         /* get message type */
   1840         int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
   1841 
   1842         /* check Mms-Version field */
   1843         int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
   1844         if (0 == mmsVersion) {
   1845             // Every message should have Mms-Version field.
   1846             return false;
   1847         }
   1848 
   1849         /* check mandatory header fields */
   1850         switch (messageType) {
   1851             case PduHeaders.MESSAGE_TYPE_SEND_REQ:
   1852                 // Content-Type field.
   1853                 byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
   1854                 if (null == srContentType) {
   1855                     return false;
   1856                 }
   1857 
   1858                 // From field.
   1859                 EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
   1860                 if (null == srFrom) {
   1861                     return false;
   1862                 }
   1863 
   1864                 // Transaction-Id field.
   1865                 byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
   1866                 if (null == srTransactionId) {
   1867                     return false;
   1868                 }
   1869 
   1870                 break;
   1871             case PduHeaders.MESSAGE_TYPE_SEND_CONF:
   1872                 // Response-Status field.
   1873                 int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
   1874                 if (0 == scResponseStatus) {
   1875                     return false;
   1876                 }
   1877 
   1878                 // Transaction-Id field.
   1879                 byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
   1880                 if (null == scTransactionId) {
   1881                     return false;
   1882                 }
   1883 
   1884                 break;
   1885             case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
   1886                 // Content-Location field.
   1887                 byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
   1888                 if (null == niContentLocation) {
   1889                     return false;
   1890                 }
   1891 
   1892                 // Expiry field.
   1893                 long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
   1894                 if (-1 == niExpiry) {
   1895                     return false;
   1896                 }
   1897 
   1898                 // Message-Class field.
   1899                 byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
   1900                 if (null == niMessageClass) {
   1901                     return false;
   1902                 }
   1903 
   1904                 // Message-Size field.
   1905                 long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
   1906                 if (-1 == niMessageSize) {
   1907                     return false;
   1908                 }
   1909 
   1910                 // Transaction-Id field.
   1911                 byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
   1912                 if (null == niTransactionId) {
   1913                     return false;
   1914                 }
   1915 
   1916                 break;
   1917             case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
   1918                 // Status field.
   1919                 int nriStatus = headers.getOctet(PduHeaders.STATUS);
   1920                 if (0 == nriStatus) {
   1921                     return false;
   1922                 }
   1923 
   1924                 // Transaction-Id field.
   1925                 byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
   1926                 if (null == nriTransactionId) {
   1927                     return false;
   1928                 }
   1929 
   1930                 break;
   1931             case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
   1932                 // Content-Type field.
   1933                 byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
   1934                 if (null == rcContentType) {
   1935                     return false;
   1936                 }
   1937 
   1938                 // Date field.
   1939                 long rcDate = headers.getLongInteger(PduHeaders.DATE);
   1940                 if (-1 == rcDate) {
   1941                     return false;
   1942                 }
   1943 
   1944                 break;
   1945             case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
   1946                 // Date field.
   1947                 long diDate = headers.getLongInteger(PduHeaders.DATE);
   1948                 if (-1 == diDate) {
   1949                     return false;
   1950                 }
   1951 
   1952                 // Message-Id field.
   1953                 byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
   1954                 if (null == diMessageId) {
   1955                     return false;
   1956                 }
   1957 
   1958                 // Status field.
   1959                 int diStatus = headers.getOctet(PduHeaders.STATUS);
   1960                 if (0 == diStatus) {
   1961                     return false;
   1962                 }
   1963 
   1964                 // To field.
   1965                 EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
   1966                 if (null == diTo) {
   1967                     return false;
   1968                 }
   1969 
   1970                 break;
   1971             case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
   1972                 // Transaction-Id field.
   1973                 byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
   1974                 if (null == aiTransactionId) {
   1975                     return false;
   1976                 }
   1977 
   1978                 break;
   1979             case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
   1980                 // Date field.
   1981                 long roDate = headers.getLongInteger(PduHeaders.DATE);
   1982                 if (-1 == roDate) {
   1983                     return false;
   1984                 }
   1985 
   1986                 // From field.
   1987                 EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
   1988                 if (null == roFrom) {
   1989                     return false;
   1990                 }
   1991 
   1992                 // Message-Id field.
   1993                 byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
   1994                 if (null == roMessageId) {
   1995                     return false;
   1996                 }
   1997 
   1998                 // Read-Status field.
   1999                 int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
   2000                 if (0 == roReadStatus) {
   2001                     return false;
   2002                 }
   2003 
   2004                 // To field.
   2005                 EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
   2006                 if (null == roTo) {
   2007                     return false;
   2008                 }
   2009 
   2010                 break;
   2011             case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
   2012                 // From field.
   2013                 EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
   2014                 if (null == rrFrom) {
   2015                     return false;
   2016                 }
   2017 
   2018                 // Message-Id field.
   2019                 byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
   2020                 if (null == rrMessageId) {
   2021                     return false;
   2022                 }
   2023 
   2024                 // Read-Status field.
   2025                 int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
   2026                 if (0 == rrReadStatus) {
   2027                     return false;
   2028                 }
   2029 
   2030                 // To field.
   2031                 EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
   2032                 if (null == rrTo) {
   2033                     return false;
   2034                 }
   2035 
   2036                 break;
   2037             default:
   2038                 // Parser doesn't support this message type in this version.
   2039                 return false;
   2040         }
   2041 
   2042         return true;
   2043     }
   2044 }
   2045