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