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