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