dom/mobilemessage/src/gonk/MmsPduHelper.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
     9 let WSP = {};
    10 Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP);
    12 Cu.import("resource://gre/modules/mms_consts.js");
    14 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
    16 let DEBUG; // set to true to see debug messages
    18 this.MMS_VERSION = (function() {
    19   Cu.import("resource://gre/modules/Services.jsm");
    21   try {
    22     return Services.prefs.getIntPref("dom.mms.version");
    23   } catch(ex) {}
    25   return MMS_VERSION_1_3;
    26 })();
    28 this.translatePduErrorToStatus = function translatePduErrorToStatus(error) {
    29   if (error == MMS_PDU_ERROR_OK) {
    30     return MMS_PDU_STATUS_RETRIEVED;
    31   }
    33   if ((error >= MMS_PDU_ERROR_TRANSIENT_FAILURE)
    34       && (error < MMS_PDU_ERROR_PERMANENT_FAILURE)) {
    35     return MMS_PDU_STATUS_DEFERRED;
    36   }
    38   return MMS_PDU_STATUS_UNRECOGNISED;
    39 }
    41 function defineLazyRegExp(obj, name, pattern) {
    42   obj.__defineGetter__(name, function() {
    43     delete obj[name];
    44     return obj[name] = new RegExp(pattern);
    45   });
    46 }
    48 function RangedValue(name, min, max) {
    49   this.name = name;
    50   this.min = min;
    51   this.max = max;
    52 }
    53 RangedValue.prototype = {
    54   name: null,
    55   min: null,
    56   max: null,
    58   /**
    59    * @param data
    60    *        A wrapped object containing raw PDU data.
    61    *
    62    * @return A decoded integer.
    63    *
    64    * @throws CodeError if decoded value is not in the range [this.min, this.max].
    65    */
    66   decode: function(data) {
    67     let value = WSP.Octet.decode(data);
    68     if ((value >= this.min) && (value <= this.max)) {
    69       return value;
    70     }
    72     throw new WSP.CodeError(this.name + ": invalid value " + value);
    73   },
    75   /**
    76    * @param data
    77    *        A wrapped object to store encoded raw data.
    78    * @param value
    79    *        An integer value within thr range [this.min, this.max].
    80    */
    81   encode: function(data, value) {
    82     if ((value < this.min) || (value > this.max)) {
    83       throw new WSP.CodeError(this.name + ": invalid value " + value);
    84     }
    86     WSP.Octet.encode(data, value);
    87   },
    88 };
    90 /**
    91  * Internal decoding function for boolean values.
    92  *
    93  * Boolean-value = Yes | No
    94  * Yes = <Octet 128>
    95  * No = <Octet 129>
    96  */
    97 this.BooleanValue = {
    98   /**
    99    * @param data
   100    *        A wrapped object containing raw PDU data.
   101    *
   102    * @return Boolean true or false.
   103    *
   104    * @throws CodeError if read octet equals to neither 128 nor 129.
   105    */
   106   decode: function(data) {
   107     let value = WSP.Octet.decode(data);
   108     if ((value != 128) && (value != 129)) {
   109       throw new WSP.CodeError("Boolean-value: invalid value " + value);
   110     }
   112     return value == 128;
   113   },
   115   /**
   116    * @param data
   117    *        A wrapped object to store encoded raw data.
   118    * @param value
   119    *        A boolean value to be encoded.
   120    */
   121   encode: function(data, value) {
   122     WSP.Octet.encode(data, value ? 128 : 129);
   123   },
   124 };
   126 /**
   127  * MMS Address
   128  *
   129  * address = email | device-address | alphanum-shortcode | num-shortcode
   130  *
   131  * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 8
   132  */
   133 this.Address = {
   134   /**
   135    * @param data
   136    *        A wrapped object to store encoded raw data.
   137    *
   138    * @return An object of two string-typed attributes: address and type.
   139    */
   140   decode: function(data) {
   141     let str = EncodedStringValue.decode(data);
   143     let result;
   144     if (((result = str.match(this.REGEXP_DECODE_PLMN)) != null)
   145         || ((result = str.match(this.REGEXP_DECODE_IPV4)) != null)
   146         || ((result = str.match(this.REGEXP_DECODE_IPV6)) != null)
   147         || ((result = str.match(this.REGEXP_DECODE_CUSTOM)) != null)) {
   148       return {address: result[1], type: result[2]};
   149     }
   151     let type;
   152     if (str.match(this.REGEXP_NUM)) {
   153       type = "num";
   154     } else if (str.match(this.REGEXP_ALPHANUM)) {
   155       type = "alphanum";
   156     } else if (str.indexOf("@") > 0) {
   157       // E-mail should match the definition of `mailbox` as described in section
   158       // 3.4 of RFC2822, but excluding the obsolete definitions as indicated by
   159       // the "obs-" prefix. Here we match only a `@` character.
   160       type = "email";
   161     } else {
   162       throw new WSP.CodeError("Address: invalid address");
   163     }
   165     return {address: str, type: type};
   166   },
   168   /**
   169    * @param data
   170    *        A wrapped object to store encoded raw data.
   171    * @param value
   172    *        An object of two string-typed attributes: address and type.
   173    */
   174   encode: function(data, value) {
   175     if (!value || !value.type || !value.address) {
   176       throw new WSP.CodeError("Address: invalid value");
   177     }
   179     let str;
   180     switch (value.type) {
   181       case "email":
   182         if (value.address.indexOf("@") > 0) {
   183           str = value.address;
   184         }
   185         break;
   186       case "num":
   187         if (value.address.match(this.REGEXP_NUM)) {
   188           str = value.address;
   189         }
   190         break;
   191       case "alphanum":
   192         if (value.address.match(this.REGEXP_ALPHANUM)) {
   193           str = value.address;
   194         }
   195         break;
   196       case "IPv4":
   197         if (value.address.match(this.REGEXP_ENCODE_IPV4)) {
   198           str = value.address + "/TYPE=IPv4";
   199         }
   200         break;
   201       case "IPv6":
   202         if (value.address.match(this.REGEXP_ENCODE_IPV6)) {
   203           str = value.address + "/TYPE=IPv6";
   204         }
   205         break;
   206       case "PLMN":
   207         if (value.address.match(this.REGEXP_ENCODE_PLMN)) {
   208           str = value.address + "/TYPE=PLMN";
   209         }
   210         break;
   211       default:
   212         if (value.type.match(this.REGEXP_ENCODE_CUSTOM_TYPE)
   213             && value.address.match(this.REGEXP_ENCODE_CUSTOM_ADDR)) {
   214           str = value.address + "/TYPE=" + value.type;
   215 	}
   216         break;
   217     }
   219     if (!str) {
   220       throw new WSP.CodeError("Address: invalid value: " + JSON.stringify(value));
   221     }
   223     EncodedStringValue.encode(data, str);
   224   },
   226   /**
   227    * @param address
   228    *        Address string which want to find the type.
   229    *
   230    * @return Address type.
   231    */
   232   resolveType: function(address) {
   233     if (address.match(this.REGEXP_EMAIL)) {
   234       return "email";
   235     }
   237     if (address.match(this.REGEXP_IPV4)) {
   238       return "IPv4";
   239     }
   241     if (address.match(this.REGEXP_IPV6)) {
   242       return "IPv6";
   243     }
   245     let normalizedAddress = PhoneNumberUtils.normalize(address, false);
   246     if (PhoneNumberUtils.isPlainPhoneNumber(normalizedAddress)) {
   247       return "PLMN";
   248     }
   250     return "Others";
   251   },
   252 };
   254 defineLazyRegExp(Address, "REGEXP_DECODE_PLMN",        "^(\\+?[\\d.-]+)\\/TYPE=(PLMN)$");
   255 defineLazyRegExp(Address, "REGEXP_DECODE_IPV4",        "^(\\d{1,3}(?:\\.\\d{1,3}){3})\\/TYPE=(IPv4)$");
   256 defineLazyRegExp(Address, "REGEXP_DECODE_IPV6",        "^([\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7})\\/TYPE=(IPv6)$");
   257 defineLazyRegExp(Address, "REGEXP_DECODE_CUSTOM",      "^([\\w\\+\\-.%]+)\\/TYPE=(\\w+)$");
   258 defineLazyRegExp(Address, "REGEXP_ENCODE_PLMN",        "^\\+?[\\d.-]+$");
   259 defineLazyRegExp(Address, "REGEXP_ENCODE_IPV4",        "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
   260 defineLazyRegExp(Address, "REGEXP_ENCODE_IPV6",        "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
   261 defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_TYPE", "^\\w+$");
   262 defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_ADDR", "^[\\w\\+\\-.%]+$");
   263 defineLazyRegExp(Address, "REGEXP_NUM",                "^[\\+*#]\\d+$");
   264 defineLazyRegExp(Address, "REGEXP_ALPHANUM",           "^\\w+$");
   265 defineLazyRegExp(Address, "REGEXP_PLMN",               "^\\?[\\d.-]$");
   266 defineLazyRegExp(Address, "REGEXP_IPV4",               "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
   267 defineLazyRegExp(Address, "REGEXP_IPV6",               "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
   268 defineLazyRegExp(Address, "REGEXP_EMAIL",              "@");
   270 /**
   271  * Header-field = MMS-header | Application-header
   272  *
   273  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2
   274  */
   275 this.HeaderField = {
   276   /**
   277    * @param data
   278    *        A wrapped object containing raw PDU data.
   279    * @param options
   280    *        Extra context for decoding.
   281    *
   282    * @return A decoded object containing `name` and `value` properties or null
   283    *         in case of a failed parsing. The `name` property must be a string,
   284    *         but the `value` property can be many different types depending on
   285    *         `name`.
   286    */
   287   decode: function(data, options) {
   288     return WSP.decodeAlternatives(data, options,
   289                                   MmsHeader, WSP.ApplicationHeader);
   290   },
   292   /**
   293    * @param data
   294    *        A wrapped object to store encoded raw data.
   295    * @param octet
   296    *        Octet value to be encoded.
   297    * @param options
   298    *        Extra context for encoding.
   299    */
   300   encode: function(data, value, options) {
   301     WSP.encodeAlternatives(data, value, options,
   302                            MmsHeader, WSP.ApplicationHeader);
   303   },
   304 };
   306 /**
   307  * MMS-header = MMS-field-name MMS-value
   308  * MMS-field-name = Short-integer
   309  *
   310  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2
   311  */
   312 this.MmsHeader = {
   313   /**
   314    * @param data
   315    *        A wrapped object containing raw PDU data.
   316    * @param options
   317    *        Extra context for decoding.
   318    *
   319    * @return A decoded object containing `name` and `value` properties or null
   320    *         in case of a failed parsing. The `name` property must be a string,
   321    *         but the `value` property can be many different types depending on
   322    *         `name`.
   323    *
   324    * @throws NotWellKnownEncodingError if decoded well-known header field
   325    *         number is not registered or supported.
   326    */
   327   decode: function(data, options) {
   328     let index = WSP.ShortInteger.decode(data);
   330     let entry = MMS_HEADER_FIELDS[index];
   331     if (!entry) {
   332       throw new WSP.NotWellKnownEncodingError(
   333         "MMS-header: not well known header " + index);
   334     }
   336     let cur = data.offset, value;
   337     try {
   338       value = entry.coder.decode(data, options);
   339     } catch (e) {
   340       data.offset = cur;
   342       value = WSP.skipValue(data);
   343       debug("Skip malformed well known header: "
   344             + JSON.stringify({name: entry.name, value: value}));
   346       return null;
   347     }
   349     return {
   350       name: entry.name,
   351       value: value,
   352     };
   353   },
   355   /**
   356    * @param data
   357    *        A wrapped object to store encoded raw data.
   358    * @param header
   359    *        An object containing two attributes: a string-typed `name` and a
   360    *        `value` of arbitrary type.
   361    *
   362    * @throws CodeError if got an empty header name.
   363    * @throws NotWellKnownEncodingError if the well-known header field number is
   364    *         not registered or supported.
   365    */
   366   encode: function(data, header) {
   367     if (!header.name) {
   368       throw new WSP.CodeError("MMS-header: empty header name");
   369     }
   371     let entry = MMS_HEADER_FIELDS[header.name.toLowerCase()];
   372     if (!entry) {
   373       throw new WSP.NotWellKnownEncodingError(
   374         "MMS-header: not well known header " + header.name);
   375     }
   377     WSP.ShortInteger.encode(data, entry.number);
   378     entry.coder.encode(data, header.value);
   379   },
   380 };
   382 /**
   383  * Cancel-status-value = Cancel Request Successfully received |
   384  *                       Cancel Request corrupted
   385  *
   386  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.7
   387  */
   388 this.CancelStatusValue = new RangedValue("Cancel-status-value", 128, 129);
   390 /**
   391  * Content-class-value = text | image-basic| image-rich | video-basic |
   392  *                       video-rich | megapixel | content-basic | content-rich
   393  *
   394  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.9
   395  */
   396 this.ContentClassValue = new RangedValue("Content-class-value", 128, 135);
   398 /**
   399  * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
   400  *
   401  *   Content-location-value = Uri-value
   402  *
   403  * When used in the M-Mbox-Delete.conf and M-Delete.conf PDU:
   404  *
   405  *   Content-location-Del-value = Value-length Status-count-value Content-location-value
   406  *   Status-count-value = Integer-value
   407  *
   408  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.10
   409  */
   410 this.ContentLocationValue = {
   411   /**
   412    * @param data
   413    *        A wrapped object containing raw PDU data.
   414    * @param options
   415    *        Extra context for decoding.
   416    *
   417    * @return A decoded object containing `uri` and conditional `statusCount`
   418    *         properties.
   419    */
   420   decode: function(data, options) {
   421     let type = WSP.ensureHeader(options, "x-mms-message-type");
   423     let result = {};
   424     if ((type == MMS_PDU_TYPE_MBOX_DELETE_CONF)
   425         || (type == MMS_PDU_TYPE_DELETE_CONF)) {
   426       let length = WSP.ValueLength.decode(data);
   427       let end = data.offset + length;
   429       result.statusCount = WSP.IntegerValue.decode(data);
   430       result.uri = WSP.UriValue.decode(data);
   432       if (data.offset != end) {
   433         data.offset = end;
   434       }
   435     } else {
   436       result.uri = WSP.UriValue.decode(data);
   437     }
   439     return result;
   440   },
   441 };
   443 /**
   444  * Element-Descriptor-value = Value-length Content-Reference-value *(Parameter)
   445  * Content-Reference-value = Text-string
   446  *
   447  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18
   448  */
   449 this.ElementDescriptorValue = {
   450   /**
   451    * @param data
   452    *        A wrapped object containing raw PDU data.
   453    *
   454    * @return A decoded object containing a string property `contentReference`
   455    *         and an optinal `params` name-value map.
   456    */
   457   decode: function(data) {
   458     let length = WSP.ValueLength.decode(data);
   459     let end = data.offset + length;
   461     let result = {};
   462     result.contentReference = WSP.TextString.decode(data);
   463     if (data.offset < end) {
   464       result.params = Parameter.decodeMultiple(data, end);
   465     }
   467     if (data.offset != end) {
   468       // Explicitly seek to end in case of skipped parameters.
   469       data.offset = end;
   470     }
   472     return result;
   473   },
   474 };
   476 /**
   477  * OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18:
   478  * `For well-known parameter names binary tokens MUST be used as defined in
   479  * Table 27.` So we can't reuse that of WSP.
   480  *
   481  *   Parameter = Parameter-name Parameter-value
   482  *   Parameter-name = Short-integer | Text-string
   483  *   Parameter-value = Constrained-encoding | Text-string
   484  *
   485  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18
   486  */
   487 this.Parameter = {
   488   /**
   489    * @param data
   490    *        A wrapped object containing raw PDU data.
   491    *
   492    * @return A decoded string.
   493    *
   494    * @throws NotWellKnownEncodingError if decoded well-known parameter number
   495    *         is not registered or supported.
   496    */
   497   decodeParameterName: function(data) {
   498     let begin = data.offset;
   499     let number;
   500     try {
   501       number = WSP.ShortInteger.decode(data);
   502     } catch (e) {
   503       data.offset = begin;
   504       return WSP.TextString.decode(data).toLowerCase();
   505     }
   507     let entry = MMS_WELL_KNOWN_PARAMS[number];
   508     if (!entry) {
   509       throw new WSP.NotWellKnownEncodingError(
   510         "Parameter-name: not well known parameter " + number);
   511     }
   513     return entry.name;
   514   },
   516   /**
   517    * @param data
   518    *        A wrapped object containing raw PDU data.
   519    *
   520    * @return A decoded object containing `name` and `value` properties or null
   521    *         in case of a failed parsing. The `name` property must be a string,
   522    *         but the `value` property can be many different types depending on
   523    *         `name`.
   524    */
   525   decode: function(data) {
   526     let name = this.decodeParameterName(data);
   527     let value = WSP.decodeAlternatives(data, null,
   528                                        WSP.ConstrainedEncoding, WSP.TextString);
   529     return {
   530       name: name,
   531       value: value,
   532     };
   533   },
   535   /**
   536    * @param data
   537    *        A wrapped object containing raw PDU data.
   538    * @param end
   539    *        Ending offset of following parameters.
   540    *
   541    * @return An array of decoded objects.
   542    */
   543   decodeMultiple: function(data, end) {
   544     let params, param;
   546     while (data.offset < end) {
   547       try {
   548         param = this.decode(data);
   549       } catch (e) {
   550         break;
   551       }
   552       if (param) {
   553         if (!params) {
   554           params = {};
   555         }
   556         params[param.name] = param.value;
   557       }
   558     }
   560     return params;
   561   },
   563   /**
   564    * @param data
   565    *        A wrapped object to store encoded raw data.
   566    * @param param
   567    *        An object containing two attributes: `name` and `value`.
   568    * @param options
   569    *        Extra context for encoding.
   570    */
   571   encode: function(data, param, options) {
   572     if (!param || !param.name) {
   573       throw new WSP.CodeError("Parameter-name: empty param name");
   574     }
   576     let entry = MMS_WELL_KNOWN_PARAMS[param.name.toLowerCase()];
   577     if (entry) {
   578       WSP.ShortInteger.encode(data, entry.number);
   579     } else {
   580       WSP.TextString.encode(data, param.name);
   581     }
   583     WSP.encodeAlternatives(data, param.value, options,
   584                            WSP.ConstrainedEncoding, WSP.TextString);
   585   },
   586 };
   588 /**
   589  * The Char-set values are registered by IANA as MIBEnum value and SHALL be
   590  * encoded as Integer-value.
   591  *
   592  *   Encoded-string-value = Text-string | Value-length Char-set Text-string
   593  *
   594  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.19
   595  * @see OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1
   596  */
   597 this.EncodedStringValue = {
   598   /**
   599    * @param data
   600    *        A wrapped object containing raw PDU data.
   601    *
   602    * @return Decoded string.
   603    *
   604    * @throws CodeError if the raw octets cannot be converted.
   605    * @throws NotWellKnownEncodingError if decoded well-known charset number is
   606    *         not registered or supported.
   607    */
   608   decodeCharsetEncodedString: function(data) {
   609     let length = WSP.ValueLength.decode(data);
   610     let end = data.offset + length;
   612     let charset = WSP.IntegerValue.decode(data);
   613     let entry = WSP.WSP_WELL_KNOWN_CHARSETS[charset];
   614     if (!entry) {
   615       throw new WSP.NotWellKnownEncodingError(
   616         "Charset-encoded-string: not well known charset " + charset);
   617     }
   619     let str;
   620     if (entry.converter) {
   621       // Read a possible string quote(<Octet 127>).
   622       let begin = data.offset;
   623       if (WSP.Octet.decode(data) != 127) {
   624         data.offset = begin;
   625       }
   627       let raw = WSP.Octet.decodeMultiple(data, end - 1);
   628       // Read NUL character.
   629       WSP.Octet.decodeEqualTo(data, 0);
   631       if (!raw) {
   632         str = "";
   633       } else {
   634         let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   635                    .createInstance(Ci.nsIScriptableUnicodeConverter);
   636         conv.charset = entry.converter;
   637         try {
   638           str = conv.convertFromByteArray(raw, raw.length);
   639         } catch (e) {
   640           throw new WSP.CodeError("Charset-encoded-string: " + e.message);
   641         }
   642       }
   643     } else {
   644       str = WSP.TextString.decode(data);
   645     }
   647     if (data.offset != end) {
   648       data.offset = end;
   649     }
   651     return str;
   652   },
   654   /**
   655    * @param data
   656    *        A wrapped object containing raw PDU data.
   657    *
   658    * @return Decoded string.
   659    */
   660   decode: function(data) {
   661     let begin = data.offset;
   662     try {
   663       return WSP.TextString.decode(data);
   664     } catch (e) {
   665       data.offset = begin;
   666       return this.decodeCharsetEncodedString(data);
   667     }
   668   },
   670   /**
   671    * Always encode target string with UTF-8 encoding.
   672    *
   673    * @param data
   674    *        A wrapped object to store encoded raw data.
   675    * @param str
   676    *        A string.
   677    */
   678   encodeCharsetEncodedString: function(data, str) {
   679     let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   680                .createInstance(Ci.nsIScriptableUnicodeConverter);
   681     // `When the text string cannot be represented as us-ascii, the character
   682     // set SHALL be encoded as utf-8(IANA MIBenum 106) which has unique byte
   683     // ordering.` ~ OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1
   684     conv.charset = "UTF-8";
   686     let raw;
   687     try {
   688       raw = conv.convertToByteArray(str);
   689     } catch (e) {
   690       throw new WSP.CodeError("Charset-encoded-string: " + e.message);
   691     }
   693     let length = raw.length + 2; // Charset number and NUL character
   694     // Prepend <Octet 127> if necessary.
   695     if (raw[0] >= 128) {
   696       ++length;
   697     }
   699     WSP.ValueLength.encode(data, length);
   701     let entry = WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"];
   702     WSP.IntegerValue.encode(data, entry.number);
   704     if (raw[0] >= 128) {
   705       WSP.Octet.encode(data, 127);
   706     }
   707     WSP.Octet.encodeMultiple(data, raw);
   708     WSP.Octet.encode(data, 0);
   709   },
   711   /**
   712    * @param data
   713    *        A wrapped object to store encoded raw data.
   714    * @param str
   715    *        A string.
   716    */
   717   encode: function(data, str) {
   718     let begin = data.offset;
   719     try {
   720       // Quoted from OMA-TS-MMS-CONF-V1_3-20110913-A:
   721       // Some of the MMS headers have been defined as "Encoded-string-value".
   722       // The character set IANA MIBEnum value in these headers SHALL be
   723       // encoded as Integer-value ([WAPWSP] section 8.4.2.3). The character
   724       // set us-ascii (IANA MIBenum 3) SHALL always be accepted. If the
   725       // character set is not specified (simple Text-string encoding) the
   726       // character set SHALL be identified as us-ascii (lower half of ISO
   727       // 8859-1 [ISO8859-1]). When the text string cannot be represented as
   728       // us-ascii, the character set SHALL be encoded as utf-8 (IANA MIBenum
   729       // 106) which has unique byte ordering.
   730       WSP.TextString.encode(data, str, true);
   731     } catch (e) {
   732       data.offset = begin;
   733       this.encodeCharsetEncodedString(data, str);
   734     }
   735   },
   736 };
   738 /**
   739  * Expiry-value = Value-length (Absolute-token Date-value | Relative-token Delta-seconds-value)
   740  * Absolute-token = <Octet 128>
   741  * Relative-token = <Octet 129>
   742  *
   743  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.20
   744  */
   745 this.ExpiryValue = {
   746   /**
   747    * @param data
   748    *        A wrapped object containing raw PDU data.
   749    *
   750    * @return A Date object for absolute expiry or an integer for relative one.
   751    *
   752    * @throws CodeError if decoded token equals to neither 128 nor 129.
   753    */
   754   decode: function(data) {
   755     let length = WSP.ValueLength.decode(data);
   756     let end = data.offset + length;
   758     let token = WSP.Octet.decode(data);
   759     if ((token != 128) && (token != 129)) {
   760       throw new WSP.CodeError("Expiry-value: invalid token " + token);
   761     }
   763     let result;
   764     if (token == 128) {
   765       result = WSP.DateValue.decode(data);
   766     } else {
   767       result = WSP.DeltaSecondsValue.decode(data);
   768     }
   770     if (data.offset != end) {
   771       data.offset = end;
   772     }
   774     return result;
   775   },
   777   /**
   778    * @param data
   779    *        A wrapped object to store encoded raw data.
   780    * @param value
   781    *        A Date object for absolute expiry or an integer for relative one.
   782    */
   783   encode: function(data, value) {
   784     let isDate, begin = data.offset;
   785     if (value instanceof Date) {
   786       isDate = true;
   787       WSP.DateValue.encode(data, value);
   788     } else if (typeof value == "number") {
   789       isDate = false;
   790       WSP.DeltaSecondsValue.encode(data, value);
   791     } else {
   792       throw new CodeError("Expiry-value: invalid value type");
   793     }
   795     // Calculate how much octets will be written and seek back.
   796     // TODO: use memmove, see bug 730873
   797     let len = data.offset - begin;
   798     data.offset = begin;
   800     WSP.ValueLength.encode(data, len + 1);
   801     if (isDate) {
   802       WSP.Octet.encode(data, 128);
   803       WSP.DateValue.encode(data, value);
   804     } else {
   805       WSP.Octet.encode(data, 129);
   806       WSP.DeltaSecondsValue.encode(data, value);
   807     }
   808   },
   809 };
   811 /**
   812  * From-value = Value-length (Address-present-token Address | Insert-address-token)
   813  * Address-present-token = <Octet 128>
   814  * Insert-address-token = <Octet 129>
   815  *
   816  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.21
   817  */
   818 this.FromValue = {
   819   /**
   820    * @param data
   821    *        A wrapped object containing raw PDU data.
   822    *
   823    * @return A decoded Address-value or null for MMS Proxy-Relay Insert-Address
   824    *         mode.
   825    *
   826    * @throws CodeError if decoded token equals to neither 128 nor 129.
   827    */
   828   decode: function(data) {
   829     let length = WSP.ValueLength.decode(data);
   830     let end = data.offset + length;
   832     let token = WSP.Octet.decode(data);
   833     if ((token != 128) && (token != 129)) {
   834       throw new WSP.CodeError("From-value: invalid token " + token);
   835     }
   837     let result = null;
   838     if (token == 128) {
   839       result = Address.decode(data);
   840     }
   842     if (data.offset != end) {
   843       data.offset = end;
   844     }
   846     return result;
   847   },
   849   /**
   850    * @param data
   851    *        A wrapped object to store encoded raw data.
   852    * @param value
   853    *        A Address-value or null for MMS Proxy-Relay Insert-Address mode.
   854    */
   855   encode: function(data, value) {
   856     if (!value) {
   857       WSP.ValueLength.encode(data, 1);
   858       WSP.Octet.encode(data, 129);
   859       return;
   860     }
   862     // Calculate how much octets will be written and seek back.
   863     // TODO: use memmove, see bug 730873
   864     let begin = data.offset;
   865     Address.encode(data, value);
   866     let len = data.offset - begin;
   867     data.offset = begin;
   869     WSP.ValueLength.encode(data, len + 1);
   870     WSP.Octet.encode(data, 128);
   871     Address.encode(data, value);
   872   },
   873 };
   875 /**
   876  * Previously-sent-by-value = Value-length Forwarded-count-value Address
   877  * Forwarded-count-value = Integer-value
   878  *
   879  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23
   880  */
   881 this.PreviouslySentByValue = {
   882   /**
   883    * @param data
   884    *        A wrapped object containing raw PDU data.
   885    *
   886    * @return Decoded object containing an integer `forwardedCount` and an
   887    *         string-typed `originator` attributes.
   888    */
   889   decode: function(data) {
   890     let length = WSP.ValueLength.decode(data);
   891     let end = data.offset + length;
   893     let result = {};
   894     result.forwardedCount = WSP.IntegerValue.decode(data);
   895     result.originator = Address.decode(data);
   897     if (data.offset != end) {
   898       data.offset = end;
   899     }
   901     return result;
   902   },
   903 };
   905 /**
   906  * Previously-sent-date-value = Value-length Forwarded-count-value Date-value
   907  *
   908  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23
   909  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.24
   910  */
   911 this.PreviouslySentDateValue = {
   912   /**
   913    * @param data
   914    *        A wrapped object containing raw PDU data.
   915    *
   916    * @return Decoded object containing an integer `forwardedCount` and an
   917    *         Date-typed `timestamp` attributes.
   918    */
   919   decode: function(data) {
   920     let length = WSP.ValueLength.decode(data);
   921     let end = data.offset + length;
   923     let result = {};
   924     result.forwardedCount = WSP.IntegerValue.decode(data);
   925     result.timestamp = WSP.DateValue.decode(data);
   927     if (data.offset != end) {
   928       data.offset = end;
   929     }
   931     return result;
   932   },
   933 };
   935 /**
   936  * Message-class-value = Class-identifier | Token-text
   937  * Class-identifier = Personal | Advertisement | Informational | Auto
   938  * Personal = <Octet 128>
   939  * Advertisement = <Octet 129>
   940  * Informational = <Octet 130>
   941  * Auto = <Octet 131>
   942  *
   943  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.27
   944  */
   945 this.MessageClassValue = {
   946   WELL_KNOWN_CLASSES: ["personal", "advertisement", "informational", "auto"],
   948   /**
   949    * @param data
   950    *        A wrapped object containing raw PDU data.
   951    *
   952    * @return A decoded string.
   953    *
   954    * @throws CodeError if decoded value is not in the range 128..131.
   955    */
   956   decodeClassIdentifier: function(data) {
   957     let value = WSP.Octet.decode(data);
   958     if ((value >= 128) && (value < (128 + this.WELL_KNOWN_CLASSES.length))) {
   959       return this.WELL_KNOWN_CLASSES[value - 128];
   960     }
   962     throw new WSP.CodeError("Class-identifier: invalid id " + value);
   963   },
   965   /**
   966    * @param data
   967    *        A wrapped object containing raw PDU data.
   968    *
   969    * @return A decoded string.
   970    */
   971   decode: function(data) {
   972     let begin = data.offset;
   973     try {
   974       return this.decodeClassIdentifier(data);
   975     } catch (e) {
   976       data.offset = begin;
   977       return WSP.TokenText.decode(data);
   978     }
   979   },
   981   /**
   982    * @param data
   983    *        A wrapped object to store encoded raw data.
   984    * @param klass
   985    */
   986   encode: function(data, klass) {
   987     let index = this.WELL_KNOWN_CLASSES.indexOf(klass.toLowerCase());
   988     if (index >= 0) {
   989       WSP.Octet.encode(data, index + 128);
   990     } else {
   991       WSP.TokenText.encode(data, klass);
   992     }
   993   },
   994 };
   996  /**
   997  * Message-type-value = <Octet 128..151>
   998  *
   999  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.30
  1000  */
  1001 this.MessageTypeValue = new RangedValue("Message-type-value", 128, 151);
  1003 /**
  1004  * MM-flags-value = Value-length ( Add-token | Remove-token | Filter-token ) Encoded-string-value
  1005  * Add-token = <Octet 128>
  1006  * Remove-token = <Octet 129>
  1007  * Filter-token = <Octet 130>
  1009  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.32
  1010  */
  1011 this.MmFlagsValue = {
  1012   /**
  1013    * @param data
  1014    *        A wrapped object containing raw PDU data.
  1016    * @return Decoded object containing an integer `type` and an string-typed
  1017    *         `text` attributes.
  1019    * @throws CodeError if decoded value is not in the range 128..130.
  1020    */
  1021   decode: function(data) {
  1022     let length = WSP.ValueLength.decode(data);
  1023     let end = data.offset + length;
  1025     let result = {};
  1026     result.type = WSP.Octet.decode(data);
  1027     if ((result.type < 128) || (result.type > 130)) {
  1028       throw new WSP.CodeError("MM-flags-value: invalid type " + result.type);
  1030     result.text = EncodedStringValue.decode(data);
  1032     if (data.offset != end) {
  1033       data.offset = end;
  1036     return result;
  1037   },
  1039   /**
  1040    * @param data
  1041    *        A wrapped object to store encoded raw data.
  1042    * @param value
  1043    *        An object containing an integer `type` and an string-typed
  1044    *        `text` attributes.
  1045    */
  1046   encode: function(data, value) {
  1047     if ((value.type < 128) || (value.type > 130)) {
  1048       throw new WSP.CodeError("MM-flags-value: invalid type " + value.type);
  1051     // Calculate how much octets will be written and seek back.
  1052     // TODO: use memmove, see bug 730873
  1053     let begin = data.offset;
  1054     EncodedStringValue.encode(data, value.text);
  1055     let len = data.offset - begin;
  1056     data.offset = begin;
  1058     WSP.ValueLength.encode(data, len + 1);
  1059     WSP.Octet.encode(data, value.type);
  1060     EncodedStringValue.encode(data, value.text);
  1061   },
  1062 };
  1064 /**
  1065  * MM-state-value = Draft | Sent | New | Retrieved | Forwarded
  1066  * Draft = <Octet 128>
  1067  * Sent = <Octet 129>
  1068  * New = <Octet 130>
  1069  * Retrieved = <Octet 131>
  1070  * Forwarded = <Octet 132>
  1072  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.33
  1073  */
  1074 this.MmStateValue = new RangedValue("MM-state-value", 128, 132);
  1076 /**
  1077  * Priority-value = Low | Normal | High
  1078  * Low = <Octet 128>
  1079  * Normal = <Octet 129>
  1080  * High = <Octet 130>
  1082  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.35
  1083  */
  1084 this.PriorityValue = new RangedValue("Priority-value", 128, 130);
  1086 /**
  1087  * Read-status-value = Read | Deleted without being read
  1089  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.38
  1090  */
  1091 this.ReadStatusValue = new RangedValue("Read-status-value", 128, 129);
  1093 /**
  1094  * Recommended-Retrieval-Mode-value = Manual
  1095  * Manual = <Octet 128>
  1097  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.39
  1098  */
  1099 this.RecommendedRetrievalModeValue = {
  1100   /**
  1101    * @param data
  1102    *        A wrapped object containing raw PDU data.
  1104    * @return A decoded integer.
  1105    */
  1106   decode: function(data) {
  1107     return WSP.Octet.decodeEqualTo(data, 128);
  1108   },
  1109 };
  1111 /**
  1112  * Reply-charging-value = Requested | Requested text only | Accepted |
  1113  *                        Accepted text only
  1114  * Requested = <Octet 128>
  1115  * Requested text only = <Octet 129>
  1116  * Accepted = <Octet 130>
  1117  * Accepted text only = <Octet 131>
  1119  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.43
  1120  */
  1121 this.ReplyChargingValue = new RangedValue("Reply-charging-value", 128, 131);
  1123 /**
  1124  * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
  1126  *   Response-text-value = Encoded-string-value
  1128  * When used in the M-Mbox-Delete.conf and M-Delete.conf PDUs:
  1130  *   Response-text-Del-value = Value-length Status-count-value Response-text-value
  1132  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.49
  1133  */
  1134 this.ResponseText = {
  1135   /**
  1136    * @param data
  1137    *        A wrapped object containing raw PDU data.
  1138    * @param options
  1139    *        Extra context for decoding.
  1141    * @return An object containing a string-typed `text` attribute and a
  1142    *         integer-typed `statusCount` one.
  1143    */
  1144   decode: function(data, options) {
  1145     let type = WSP.ensureHeader(options, "x-mms-message-type");
  1147     let result = {};
  1148     if ((type == MMS_PDU_TYPE_MBOX_DELETE_CONF)
  1149         || (type == MMS_PDU_TYPE_DELETE_CONF)) {
  1150       let length = WSP.ValueLength.decode(data);
  1151       let end = data.offset + length;
  1153       result.statusCount = WSP.IntegerValue.decode(data);
  1154       result.text = EncodedStringValue.decode(data);
  1156       if (data.offset != end) {
  1157         data.offset = end;
  1159     } else {
  1160       result.text = EncodedStringValue.decode(data);
  1163     return result;
  1164   },
  1165 };
  1167 /**
  1168  * Retrieve-status-value = Ok | Error-transient-failure |
  1169  *                         Error-transient-message-not-found |
  1170  *                         Error-transient-network-problem |
  1171  *                         Error-permanent-failure |
  1172  *                         Error-permanent-service-denied |
  1173  *                         Error-permanent-message-not-found |
  1174  *                         Error-permanent-content-unsupported
  1175  * Ok = <Octet 128>
  1176  * Error-transient-failure = <Octet 192>
  1177  * Error-transient-message-not-found = <Octet 193>
  1178  * Error-transient-network-problem = <Octet 194>
  1179  * Error-permanent-failure = <Octet 224>
  1180  * Error-permanent-service-denied = <Octet 225>
  1181  * Error-permanent-message-not-found = <Octet 226>
  1182  * Error-permanent-content-unsupported = <Octet 227>
  1184  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.50
  1185  */
  1186 this.RetrieveStatusValue = {
  1187   /**
  1188    * @param data
  1189    *        A wrapped object containing raw PDU data.
  1191    * @return A decoded integer.
  1192    */
  1193   decode: function(data) {
  1194     let value = WSP.Octet.decode(data);
  1195     if (value == MMS_PDU_ERROR_OK) {
  1196       return value;
  1199     if ((value >= MMS_PDU_ERROR_TRANSIENT_FAILURE) && (value < 256)) {
  1200       return value;
  1203     // Any other values SHALL NOT be used. They are reserved for future use.
  1204     // An MMS Client that receives such a reserved value MUST react the same
  1205     // as it does to the value 224 (Error-permanent-failure).
  1206     return MMS_PDU_ERROR_PERMANENT_FAILURE;
  1207   },
  1208 };
  1210 /**
  1211  * Sender-visibility-value = Hide | Show
  1213  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.52
  1214  */
  1215 this.SenderVisibilityValue = new RangedValue("Sender-visibility-value", 128, 129);
  1217 /**
  1218  * Status-value = Expired | Retrieved | Rejected | Deferred | Unrecognised |
  1219  *                Indeterminate | Forwarded | Unreachable
  1220  * Expired = <Octet 128>
  1221  * Retrieved = <Octet 129>
  1222  * Rejected = <Octet 130>
  1223  * Deferred = <Octet 131>
  1224  * Unrecognised = <Octet 132>
  1225  * Indeterminate = <Octet 133>
  1226  * Forwarded = <Octet 134>
  1227  * Unreachable = <Octet 135>
  1229  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.54
  1230  */
  1231 this.StatusValue = new RangedValue("Status-value", 128, 135);
  1233 this.PduHelper = {
  1234   /**
  1235    * @param data
  1236    *        A wrapped object containing raw PDU data.
  1237    * @param headers
  1238    *        An optional object to store parsed header fields. Created
  1239    *        automatically if undefined.
  1241    * @return A boolean value indicating whether it's followed by message body.
  1242    */
  1243   parseHeaders: function(data, headers) {
  1244     if (!headers) {
  1245       headers = {};
  1248     let header;
  1249     while (data.offset < data.array.length) {
  1250       // There is no `header length` information in MMS PDU. If we just got
  1251       // something wrong in parsing header fields, we might not be able to
  1252       // determine the correct header-content boundary.
  1253       header = HeaderField.decode(data, headers);
  1255       if (header) {
  1256         let orig = headers[header.name];
  1257         if (Array.isArray(orig)) {
  1258           headers[header.name].push(header.value);
  1259         } else if (orig) {
  1260           headers[header.name] = [orig, header.value];
  1261         } else {
  1262           headers[header.name] = header.value;
  1264         if (header.name == "content-type") {
  1265           // `... if the PDU contains a message body the Content Type MUST be
  1266           // the last header field, followed by message body.` See
  1267           // OMA-TS-MMS_ENC-V1_3-20110913-A section 7.
  1268           break;
  1273     return headers;
  1274   },
  1276   /**
  1277    * @param data
  1278    *        A wrapped object containing raw PDU data.
  1279    * @param msg
  1280    *        A message object to store decoded multipart or octet array content.
  1281    */
  1282   parseContent: function(data, msg) {
  1283     let contentType = msg.headers["content-type"].media;
  1284     if ((contentType == "application/vnd.wap.multipart.related")
  1285         || (contentType == "application/vnd.wap.multipart.mixed")) {
  1286       msg.parts = WSP.PduHelper.parseMultiPart(data);
  1287       return;
  1290     if (data.offset >= data.array.length) {
  1291       return;
  1294     msg.content = WSP.Octet.decodeMultiple(data, data.array.length);
  1295     if (false) {
  1296       for (let begin = 0; begin < msg.content.length; begin += 20) {
  1297         debug("content: " + JSON.stringify(msg.content.subarray(begin, begin + 20)));
  1300   },
  1302   /**
  1303    * Check existences of all mandatory fields of a MMS message. Also sets `type`
  1304    * for convenient access.
  1306    * @param msg
  1307    *        A MMS message object.
  1309    * @return The corresponding entry in MMS_PDU_TYPES;
  1311    * @throws FatalCodeError if the PDU type is not supported yet.
  1312    */
  1313   checkMandatoryFields: function(msg) {
  1314     let type = WSP.ensureHeader(msg.headers, "x-mms-message-type");
  1315     let entry = MMS_PDU_TYPES[type];
  1316     if (!entry) {
  1317       throw new WSP.FatalCodeError(
  1318         "checkMandatoryFields: unsupported message type " + type);
  1321     entry.mandatoryFields.forEach(function(name) {
  1322       WSP.ensureHeader(msg.headers, name);
  1323     });
  1325     // Setup convenient alias that referenced frequently.
  1326     msg.type = type;
  1328     return entry;
  1329   },
  1331   /**
  1332    * @param data
  1333    *        A wrapped object containing raw PDU data.
  1334    * @param msg [optional]
  1335    *        Optional target object for decoding.
  1337    * @return A MMS message object or null in case of errors found.
  1338    */
  1339   parse: function(data, msg) {
  1340     if (!msg) {
  1341       msg = {};
  1344     try {
  1345       msg.headers = this.parseHeaders(data, msg.headers);
  1347       // Validity checks
  1348       let typeinfo = this.checkMandatoryFields(msg);
  1349       if (typeinfo.hasContent) {
  1350         this.parseContent(data, msg);
  1352     } catch (e) {
  1353       debug("Failed to parse MMS message, error message: " + e.message);
  1354       return null;
  1357     return msg;
  1358   },
  1360   /**
  1361    * @param data
  1362    *        A wrapped object to store encoded raw data.
  1363    * @param headers
  1364    *        A dictionary object containing multiple name/value mapping.
  1365    * @param name
  1366    *        Name of the header field to be encoded.
  1367    */
  1368   encodeHeader: function(data, headers, name) {
  1369     let value = headers[name];
  1370     if (Array.isArray(value)) {
  1371       for (let i = 0; i < value.length; i++) {
  1372         HeaderField.encode(data, {name: name, value: value[i]}, headers);
  1374     } else {
  1375       HeaderField.encode(data, {name: name, value: value}, headers);
  1377   },
  1379   /**
  1380    * @param data
  1381    *        A wrapped object to store encoded raw data.
  1382    * @param headers
  1383    *        A dictionary object containing multiple name/value mapping.
  1384    */
  1385   encodeHeaderIfExists: function(data, headers, name) {
  1386     // Header value could be zero or null.
  1387     if (headers[name] !== undefined) {
  1388       this.encodeHeader(data, headers, name);
  1390   },
  1392   /**
  1393    * @param data [optional]
  1394    *        A wrapped object to store encoded raw data. Created if undefined.
  1395    * @param headers
  1396    *        A dictionary object containing multiple name/value mapping.
  1398    * @return the passed data parameter or a created one.
  1399    */
  1400   encodeHeaders: function(data, headers) {
  1401     if (!data) {
  1402       data = {array: [], offset: 0};
  1405     // `In the encoding of the header fields, the order of the fields is not
  1406     // significant, except that X-Mms-Message-Type, X-Mms-Transaction-ID (when
  1407     // present) and X-Mms-MMS-Version MUST be at the beginning of the message
  1408     // headers, in that order, and if the PDU contains a message body the
  1409     // Content Type MUST be the last header field, followed by message body.`
  1410     // ~ OMA-TS-MMS_ENC-V1_3-20110913-A section 7
  1411     this.encodeHeader(data, headers, "x-mms-message-type");
  1412     this.encodeHeaderIfExists(data, headers, "x-mms-transaction-id");
  1413     this.encodeHeaderIfExists(data, headers, "x-mms-mms-version");
  1415     for (let key in headers) {
  1416       if ((key == "x-mms-message-type")
  1417           || (key == "x-mms-transaction-id")
  1418           || (key == "x-mms-mms-version")
  1419           || (key == "content-type")) {
  1420         continue;
  1422       this.encodeHeader(data, headers, key);
  1425     this.encodeHeaderIfExists(data, headers, "content-type");
  1427     return data;
  1428   },
  1430   /**
  1431    * @param multiStream
  1432    *        An exsiting nsIMultiplexInputStream.
  1433    * @param msg
  1434    *        A MMS message object.
  1436    * @return An instance of nsIMultiplexInputStream or null in case of errors.
  1437    */
  1438   compose: function(multiStream, msg) {
  1439     if (!multiStream) {
  1440       multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"]
  1441                     .createInstance(Ci.nsIMultiplexInputStream);
  1444     try {
  1445       // Validity checks
  1446       let typeinfo = this.checkMandatoryFields(msg);
  1448       let data = this.encodeHeaders(null, msg.headers);
  1449       debug("Composed PDU Header: " + JSON.stringify(data.array));
  1450       WSP.PduHelper.appendArrayToMultiStream(multiStream, data.array, data.offset);
  1452       if (msg.content) {
  1453         WSP.PduHelper.appendArrayToMultiStream(multiStream, msg.content, msg.content.length);
  1454       } else if (msg.parts) {
  1455         WSP.PduHelper.composeMultiPart(multiStream, msg.parts);
  1456       } else if (typeinfo.hasContent) {
  1457         throw new WSP.CodeError("Missing message content");
  1460       return multiStream;
  1461     } catch (e) {
  1462       debug("Failed to compose MMS message, error message: " + e.message);
  1463       return null;
  1465   },
  1466 };
  1468 const MMS_PDU_TYPES = (function() {
  1469   let pdus = {};
  1470   function add(number, hasContent, mandatoryFields) {
  1471     pdus[number] = {
  1472       number: number,
  1473       hasContent: hasContent,
  1474       mandatoryFields: mandatoryFields,
  1475     };
  1478   add(MMS_PDU_TYPE_SEND_REQ, true, ["x-mms-message-type",
  1479                                     "x-mms-transaction-id",
  1480                                     "x-mms-mms-version",
  1481                                     "from",
  1482                                     "content-type"]);
  1483   add(MMS_PDU_TYPE_SEND_CONF, false, ["x-mms-message-type",
  1484                                       "x-mms-transaction-id",
  1485                                       "x-mms-mms-version",
  1486                                       "x-mms-response-status"]);
  1487   add(MMS_PDU_TYPE_NOTIFICATION_IND, false, ["x-mms-message-type",
  1488                                              "x-mms-transaction-id",
  1489                                              "x-mms-mms-version",
  1490                                              "x-mms-message-class",
  1491                                              "x-mms-message-size",
  1492                                              "x-mms-expiry",
  1493                                              "x-mms-content-location"]);
  1494   add(MMS_PDU_TYPE_RETRIEVE_CONF, true, ["x-mms-message-type",
  1495                                          "x-mms-mms-version",
  1496                                          "date",
  1497                                          "content-type"]);
  1498   add(MMS_PDU_TYPE_NOTIFYRESP_IND, false, ["x-mms-message-type",
  1499                                            "x-mms-transaction-id",
  1500                                            "x-mms-mms-version",
  1501                                            "x-mms-status"]);
  1502   add(MMS_PDU_TYPE_DELIVERY_IND, false, ["x-mms-message-type",
  1503                                          "x-mms-mms-version",
  1504                                          "message-id",
  1505                                          "to",
  1506                                          "date",
  1507                                          "x-mms-status"]);
  1508   add(MMS_PDU_TYPE_ACKNOWLEDGE_IND, false, ["x-mms-message-type",
  1509                                             "x-mms-transaction-id",
  1510                                             "x-mms-mms-version"]);
  1511   add(MMS_PDU_TYPE_READ_REC_IND, false, ["x-mms-message-type",
  1512                                          "message-id",
  1513                                          "x-mms-mms-version",
  1514                                          "to",
  1515                                          "from",
  1516                                          "x-mms-read-status"]);
  1517   add(MMS_PDU_TYPE_READ_ORIG_IND, false, ["x-mms-message-type",
  1518                                           "x-mms-mms-version",
  1519                                           "message-id",
  1520                                           "to",
  1521                                           "from",
  1522                                           "date",
  1523                                           "x-mms-read-status"]);
  1525   return pdus;
  1526 })();
  1528 /**
  1529  * Header field names and assigned numbers.
  1531  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.4
  1532  */
  1533 const MMS_HEADER_FIELDS = (function() {
  1534   let names = {};
  1535   function add(name, number, coder) {
  1536     let entry = {
  1537       name: name,
  1538       number: number,
  1539       coder: coder,
  1540     };
  1541     names[name] = names[number] = entry;
  1544   add("bcc",                                     0x01, Address);
  1545   add("cc",                                      0x02, Address);
  1546   add("x-mms-content-location",                  0x03, ContentLocationValue);
  1547   add("content-type",                            0x04, WSP.ContentTypeValue);
  1548   add("date",                                    0x05, WSP.DateValue);
  1549   add("x-mms-delivery-report",                   0x06, BooleanValue);
  1550   add("x-mms-delivery-time",                     0x07, ExpiryValue);
  1551   add("x-mms-expiry",                            0x08, ExpiryValue);
  1552   add("from",                                    0x09, FromValue);
  1553   add("x-mms-message-class",                     0x0A, MessageClassValue);
  1554   add("message-id",                              0x0B, WSP.TextString);
  1555   add("x-mms-message-type",                      0x0C, MessageTypeValue);
  1556   add("x-mms-mms-version",                       0x0D, WSP.ShortInteger);
  1557   add("x-mms-message-size",                      0x0E, WSP.LongInteger);
  1558   add("x-mms-priority",                          0x0F, PriorityValue);
  1559   add("x-mms-read-report",                       0x10, BooleanValue);
  1560   add("x-mms-report-allowed",                    0x11, BooleanValue);
  1561   add("x-mms-response-status",                   0x12, RetrieveStatusValue);
  1562   add("x-mms-response-text",                     0x13, ResponseText);
  1563   add("x-mms-sender-visibility",                 0x14, SenderVisibilityValue);
  1564   add("x-mms-status",                            0x15, StatusValue);
  1565   add("subject",                                 0x16, EncodedStringValue);
  1566   add("to",                                      0x17, Address);
  1567   add("x-mms-transaction-id",                    0x18, WSP.TextString);
  1568   add("x-mms-retrieve-status",                   0x19, RetrieveStatusValue);
  1569   add("x-mms-retrieve-text",                     0x1A, EncodedStringValue);
  1570   add("x-mms-read-status",                       0x1B, ReadStatusValue);
  1571   add("x-mms-reply-charging",                    0x1C, ReplyChargingValue);
  1572   add("x-mms-reply-charging-deadline",           0x1D, ExpiryValue);
  1573   add("x-mms-reply-charging-id",                 0x1E, WSP.TextString);
  1574   add("x-mms-reply-charging-size",               0x1F, WSP.LongInteger);
  1575   add("x-mms-previously-sent-by",                0x20, PreviouslySentByValue);
  1576   add("x-mms-previously-sent-date",              0x21, PreviouslySentDateValue);
  1577   add("x-mms-store",                             0x22, BooleanValue);
  1578   add("x-mms-mm-state",                          0x23, MmStateValue);
  1579   add("x-mms-mm-flags",                          0x24, MmFlagsValue);
  1580   add("x-mms-store-status",                      0x25, RetrieveStatusValue);
  1581   add("x-mms-store-status-text",                 0x26, EncodedStringValue);
  1582   add("x-mms-stored",                            0x27, BooleanValue);
  1583   //add("x-mms-attributes", 0x28);
  1584   add("x-mms-totals",                            0x29, BooleanValue);
  1585   //add("x-mms-mbox-totals", 0x2A);
  1586   add("x-mms-quotas",                            0x2B, BooleanValue);
  1587   //add("x-mms-mbox-quotas", 0x2C);
  1588   add("x-mms-message-count",                     0x2D, WSP.IntegerValue);
  1589   //add("content", 0x2E);
  1590   add("x-mms-start",                             0x2F, WSP.IntegerValue);
  1591   //add("additional-headers", 0x30);
  1592   add("x-mms-distribution-indicator",            0x31, BooleanValue);
  1593   add("x-mms-element-descriptor",                0x32, ElementDescriptorValue);
  1594   add("x-mms-limit",                             0x33, WSP.IntegerValue);
  1595   add("x-mms-recommended-retrieval-mode",        0x34, RecommendedRetrievalModeValue);
  1596   add("x-mms-recommended-retrieval-mode-text",   0x35, EncodedStringValue);
  1597   //add("x-mms-status-text", 0x36);
  1598   add("x-mms-applic-id",                         0x37, WSP.TextString);
  1599   add("x-mms-reply-applic-id",                   0x38, WSP.TextString);
  1600   add("x-mms-aux-applic-id",                     0x39, WSP.TextString);
  1601   add("x-mms-content-class",                     0x3A, ContentClassValue);
  1602   add("x-mms-drm-content",                       0x3B, BooleanValue);
  1603   add("x-mms-adaptation-allowed",                0x3C, BooleanValue);
  1604   add("x-mms-replace-id",                        0x3D, WSP.TextString);
  1605   add("x-mms-cancel-id",                         0x3E, WSP.TextString);
  1606   add("x-mms-cancel-status",                     0x3F, CancelStatusValue);
  1608   return names;
  1609 })();
  1611 // @see OMA-TS-MMS_ENC-V1_3-20110913-A Table 27: Parameter Name Assignments
  1612 const MMS_WELL_KNOWN_PARAMS = (function() {
  1613   let params = {};
  1615   function add(name, number, coder) {
  1616     let entry = {
  1617       name: name,
  1618       number: number,
  1619       coder: coder,
  1620     };
  1621     params[name] = params[number] = entry;
  1624   // Encoding Version: 1.2
  1625   add("type", 0x02, WSP.TypeValue);
  1627   return params;
  1628 })();
  1630 let debug;
  1631 if (DEBUG) {
  1632   debug = function(s) {
  1633     dump("-$- MmsPduHelper: " + s + "\n");
  1634   };
  1635 } else {
  1636   debug = function(s) {};
  1639 this.EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([
  1640   // Constant values
  1641   "MMS_VERSION",
  1643   // Utility functions
  1644   "translatePduErrorToStatus",
  1646   // Decoders
  1647   "BooleanValue",
  1648   "Address",
  1649   "HeaderField",
  1650   "MmsHeader",
  1651   "CancelStatusValue",
  1652   "ContentClassValue",
  1653   "ContentLocationValue",
  1654   "ElementDescriptorValue",
  1655   "Parameter",
  1656   "EncodedStringValue",
  1657   "ExpiryValue",
  1658   "FromValue",
  1659   "PreviouslySentByValue",
  1660   "PreviouslySentDateValue",
  1661   "MessageClassValue",
  1662   "MessageTypeValue",
  1663   "MmFlagsValue",
  1664   "MmStateValue",
  1665   "PriorityValue",
  1666   "ReadStatusValue",
  1667   "RecommendedRetrievalModeValue",
  1668   "ReplyChargingValue",
  1669   "ResponseText",
  1670   "RetrieveStatusValue",
  1671   "SenderVisibilityValue",
  1672   "StatusValue",
  1674   // Parser
  1675   "PduHelper",
  1676 ]);

mercurial