Bug 749856 - Part 2: Add MmsPduHelper, r=philikon
authorVicamo Yang <vyang@mozilla.com>
Mon, 04 Jun 2012 13:04:44 +0800
changeset 99784 8d6c0457e630bda1675f6849afe62d5f6a041cf4
parent 99783 71140763abea347a63d930ba3137930fbf2cb01f
child 99785 abb9036641e2f31f9b270bce23e03fabb7b98102
push id1116
push userlsblakk@mozilla.com
push dateMon, 16 Jul 2012 19:38:18 +0000
treeherdermozilla-beta@95f959a8b4dc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilikon
bugs749856
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 749856 - Part 2: Add MmsPduHelper, r=philikon
dom/mms/src/Makefile.in
dom/mms/src/ril/MmsPduHelper.jsm
dom/mms/src/ril/mms_consts.js
dom/mms/tests/test_mms_pdu_helper.js
dom/mms/tests/xpcshell.ini
--- a/dom/mms/src/Makefile.in
+++ b/dom/mms/src/Makefile.in
@@ -8,16 +8,18 @@ srcdir           = @srcdir@
 VPATH            = \
   $(srcdir) \
   $(NULL)
 
 include $(DEPTH)/config/autoconf.mk
 
 ifdef MOZ_B2G_RIL
 EXTRA_JS_MODULES = \
+  ril/mms_consts.js \
+  ril/MmsPduHelper.jsm \
   ril/wap_consts.js \
   ril/WapPushManager.js \
   ril/WspPduHelper.jsm \
   $(NULL)
 endif
 
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/MmsPduHelper.jsm
@@ -0,0 +1,1135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+let WSP = {};
+Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP);
+
+Cu.import("resource://gre/modules/mms_consts.js");
+
+let DEBUG; // set to true to see debug messages
+
+/**
+ * Internal decoding function for boolean values.
+ *
+ * Boolean-value = Yes | No
+ * Yes = <Octet 128>
+ * No = <Octet 129>
+ */
+let BooleanValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Boolean true or false.
+   *
+   * @throws CodeError if read octet equals to neither 128 nor 129.
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    if ((value != 128) && (value != 129)) {
+      throw new WSP.CodeError("Boolean-value: invalid value " + value);
+    }
+
+    return value == 128;
+  },
+};
+
+/**
+ * MMS Address
+ *
+ * address = email | device-address | alphanum-shortcode | num-shortcode
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 8
+ */
+let Address = {
+  decode: function (data) {
+    let str = EncodedStringValue.decode(data);
+
+    let result;
+    if (((result = str.match(/^(\+?[\d.-]+)\/TYPE=(PLMN)$/)) != null)
+        || ((result = str.match(/^(\d{1,3}(?:\.\d{1,3}){3})\/TYPE=(IPv4)$/)) != null)
+        || ((result = str.match(/^([\da-fA-F]{4}(?::[\da-fA-F]{4}){7})\/TYPE=(IPv6)$/)) != null)
+        || ((result = str.match(/^([\w\+\-.%]+)\/TYPE=(\w+)$/)) != null)) {
+      return {address: result[1], type: result[2]};
+    }
+
+    let type;
+    if (str.match(/^[\+*#]\d+$/)) {
+      type = "num";
+    } else if (str.match(/^\w+$/)) {
+      type = "alphanum";
+    } else {
+      type = "unknown";
+    }
+
+    return {address: str, type: type};
+  },
+};
+
+/**
+ * Header-field = MMS-header | Application-header
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2
+ */
+let HeaderField = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param options
+   *        Extra context for decoding.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         in case of a failed parsing. The `name` property must be a string,
+   *         but the `value` property can be many different types depending on
+   *         `name`.
+   */
+  decode: function decode(data, options) {
+    return WSP.decodeAlternatives(data, options,
+                                  MmsHeader, WSP.ApplicationHeader);
+  },
+};
+
+/**
+ * MMS-header = MMS-field-name MMS-value
+ * MMS-field-name = Short-integer
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2
+ */
+let MmsHeader = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param options
+   *        Extra context for decoding.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         in case of a failed parsing. The `name` property must be a string,
+   *         but the `value` property can be many different types depending on
+   *         `name`.
+   *
+   * @throws NotWellKnownEncodingError if decoded well-known header field
+   *         number is not registered or supported.
+   */
+  decode: function decode(data, options) {
+    let index = WSP.ShortInteger.decode(data);
+
+    let entry = MMS_HEADER_FIELDS[index];
+    if (!entry) {
+      throw new WSP.NotWellKnownEncodingError(
+        "MMS-header: not well known header " + index);
+    }
+
+    let cur = data.offset, value;
+    try {
+      value = entry.coder.decode(data, options);
+    } catch (e) {
+      data.offset = cur;
+
+      value = WSP.skipValue(data);
+      debug("Skip malformed well known header: "
+            + JSON.stringify({name: entry.name, value: value}));
+
+      return null;
+    }
+
+    return {
+      name: entry.name,
+      value: value,
+    };
+  },
+};
+
+/**
+ * Content-class-value = text | image-basic| image-rich | video-basic |
+ *                       video-rich | megapixel | content-basic | content-rich
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.9
+ */
+let ContentClassValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A integer value for each class.
+   *
+   * @throws CodeError if decoded value is not in range 128..135.
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    if ((value >= 128) && (value <= 135)) {
+      return value;
+    }
+
+    throw new WSP.CodeError("Content-class-value: invalid class " + value);
+  },
+};
+
+/**
+ * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
+ *
+ *   Content-location-value = Uri-value
+ *
+ * When used in the M-Mbox-Delete.conf and M-Delete.conf PDU:
+ *
+ *   Content-location-Del-value = Value-length Status-count-value Content-location-value
+ *   Status-count-value = Integer-value
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.10
+ */
+let ContentLocationValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param options
+   *        Extra context for decoding.
+   *
+   * @return A decoded object containing `uri` and conditional `statusCount`
+   *         properties.
+   */
+  decode: function decode(data, options) {
+    let type = WSP.ensureHeader(options, "x-mms-message-type");
+
+    let result = {};
+    if ((type == MMS_PDU_TYPE_MBOX_DELETE_CONF)
+        || (type == MMS_PDU_TYPE_DELETE_CONF)) {
+      let length = WSP.ValueLength.decode(data);
+      let end = data.offset + length;
+
+      result.statusCount = WSP.IntegerValue.decode(data);
+      result.uri = WSP.UriValue.decode(data);
+
+      if (data.offset != end) {
+        data.offset = end;
+      }
+    } else {
+      result.uri = WSP.UriValue.decode(data);
+    }
+
+    return result;
+  },
+};
+
+/**
+ * Element-Descriptor-value = Value-length Content-Reference-value *(Parameter)
+ * Content-Reference-value = Text-string
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18
+ */
+let ElementDescriptorValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing a string property `contentReference`
+   *         and an optinal `params` name-value map.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let result = {};
+    result.contentReference = WSP.TextString.decode(data);
+    if (data.offset < end) {
+      result.params = Parameter.decodeMultiple(data, end);
+    }
+
+    if (data.offset != end) {
+      // Explicitly seek to end in case of skipped parameters.
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18:
+ * `For well-known parameter names binary tokens MUST be used as defined in
+ * Table 27.` So we can't reuse that of WSP.
+ *
+ *   Parameter = Parameter-name Parameter-value
+ *   Parameter-name = Short-integer | Text-string
+ *   Parameter-value = Constrained-encoding | Text-string
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18
+ */
+let Parameter = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded string.
+   *
+   * @throws NotWellKnownEncodingError if decoded well-known parameter number
+   *         is not registered or supported.
+   */
+  decodeParameterName: function decodeParameterName(data) {
+    let begin = data.offset;
+    let number;
+    try {
+      number = WSP.ShortInteger.decode(data);
+    } catch (e) {
+      data.offset = begin;
+      return WSP.TextString.decode(data).toLowerCase();
+    }
+
+    let entry = MMS_WELL_KNOWN_PARAMS[number];
+    if (!entry) {
+      throw new WSP.NotWellKnownEncodingError(
+        "Parameter-name: not well known parameter " + number);
+    }
+
+    return entry.name;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         in case of a failed parsing. The `name` property must be a string,
+   *         but the `value` property can be many different types depending on
+   *         `name`.
+   */
+  decode: function decode(data) {
+    let name = this.decodeParameterName(data);
+    let value = WSP.decodeAlternatives(data, null,
+                                       WSP.ConstrainedEncoding, WSP.TextString);
+    return {
+      name: name,
+      value: value,
+    };
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param end
+   *        Ending offset of following parameters.
+   *
+   * @return An array of decoded objects.
+   */
+  decodeMultiple: function decodeMultiple(data, end) {
+    let params, param;
+
+    while (data.offset < end) {
+      try {
+        param = this.decode(data);
+      } catch (e) {
+        break;
+      }
+      if (param) {
+        if (!params) {
+          params = {};
+        }
+        params[param.name] = param.value;
+      }
+    }
+
+    return params;
+  },
+};
+
+/**
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ * The Char-set values are registered by IANA as MIBEnum value.
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.19
+ */
+let EncodedStringValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string.
+   */
+  decode: function decode(data) {
+    return WSP.decodeAlternatives(data, null,
+                                  WSP.TextString, CharsetEncodedString);
+  },
+};
+
+/**
+ * Charset-encoded-string = Value-length Char-set Text-string
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.19
+ */
+let CharsetEncodedString = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string.
+   *
+   * @throws CodeError if the raw octets cannot be converted.
+   * @throws NotWellKnownEncodingError if decoded well-known charset number is
+   *         not registered or supported.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let charset = WSP.ShortInteger.decode(data);
+    let entry = WSP.WSP_WELL_KNOWN_CHARSETS[charset];
+    if (!entry) {
+      throw new WSP.NotWellKnownEncodingError(
+        "Charset-encoded-string: not well known charset " + charset);
+    }
+
+    let str;
+    if (entry.converter) {
+      // Read a possible string quote(<Octet 127>).
+      let begin = data.offset;
+      if (WSP.Octet.decode(data) != 127) {
+        data.offset = begin;
+      }
+
+      let raw = WSP.Octet.decodeMultiple(data, end - 1);
+      // Read NUL character.
+      WSP.Octet.decodeEqualTo(data, 0);
+
+      if (!raw) {
+        str = "";
+      } else {
+        let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                   .createInstance(Ci.nsIScriptableUnicodeConverter);
+        conv.charset = entry.converter;
+        try {
+          str = conv.convertFromByteArray(raw, raw.length);
+        } catch (e) {
+          throw new WSP.CodeError("Charset-encoded-string: " + e.message);
+        }
+      }
+    } else {
+      str = WSP.TextString.decode(data);
+    }
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return str;
+  },
+};
+
+/**
+ * Expiry-value = Value-length (Absolute-token Date-value | Relative-token Delta-seconds-value)
+ * Address-token = <Octet 128>
+ * Relative-token = <Octet 129>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.20
+ */
+let ExpiryValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A Date object for absolute expiry or an integer for relative one.
+   *
+   * @throws CodeError if decoded token equals to neither 128 nor 129.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let token = WSP.Octet.decode(data);
+    if ((token != 128) && (token != 129)) {
+      throw new WSP.CodeError("Expiry-value: invalid token " + token);
+    }
+
+    let result;
+    if (token == 128) {
+      result = WSP.DateValue.decode(data);
+    } else {
+      result = WSP.DeltaSecondsValue.decode(data);
+    }
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * From-value = Value-length (Address-present-token Address | Insert-address-token)
+ * Address-present-token = <Octet 128>
+ * Insert-address-token = <Octet 129>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.21
+ */
+let FromValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string or null for MMS Proxy-Relay Insert-Address mode.
+   *
+   * @throws CodeError if decoded token equals to neither 128 nor 129.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let token = WSP.Octet.decode(data);
+    if ((token != 128) && (token != 129)) {
+      throw new WSP.CodeError("From-value: invalid token " + token);
+    }
+
+    let result = null;
+    if (token == 128) {
+      result = Address.decode(data);
+    }
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * Previously-sent-by-value = Value-length Forwarded-count-value Address
+ * Forwarded-count-value = Integer-value
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23
+ */
+let PreviouslySentByValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded object containing an integer `forwardedCount` and an
+   *         string-typed `originator` attributes.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let result = {};
+    result.forwardedCount = WSP.IntegerValue.decode(data);
+    result.originator = Address.decode(data);
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * Previously-sent-date-value = Value-length Forwarded-count-value Date-value
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.24
+ */
+let PreviouslySentDateValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded object containing an integer `forwardedCount` and an
+   *         Date-typed `timestamp` attributes.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let result = {};
+    result.forwardedCount = WSP.IntegerValue.decode(data);
+    result.timestamp = WSP.DateValue.decode(data);
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * Message-class-value = Class-identifier | Token-text
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.27
+ */
+let MessageClassValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded string.
+   */
+  decode: function decode(data) {
+    return WSP.decodeAlternatives(data, null,
+                                  ClassIdentifier, WSP.TokenText);
+  },
+};
+
+/**
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ * Personal = <Octet 128>
+ * Advertisement = <Octet 129>
+ * Informational = <Octet 130>
+ * Auto = <Octet 131>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.27
+ */
+let ClassIdentifier = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded string.
+   *
+   * @throws CodeError if decoded value is not in the range 128..131.
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    switch (value) {
+      case 128: return "personal";
+      case 129: return "advertisement";
+      case 130: return "informational";
+      case 131: return "auto";
+    }
+
+    throw new WSP.CodeError("Class-identifier: invalid id " + value);
+  },
+};
+
+ /**
+ * Message-type-value = <Octet 128..151>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.30
+ */
+let MessageTypeValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range 128..151.
+   */
+  decode: function decode(data) {
+    let type = WSP.Octet.decode(data);
+    if ((type >= 128) && (type <= 151)) {
+      return type;
+    }
+
+    throw new WSP.CodeError("Message-type-value: invalid type " + type);
+  },
+};
+
+/**
+ * MM-flags-value = Value-length ( Add-token | Remove-token | Filter-token ) Encoded-string-value
+ * Add-token = <Octet 128>
+ * Remove-token = <Octet 129>
+ * Filter-token = <Octet 130>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.32
+ */
+let MmFlagsValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded object containing an integer `type` and an string-typed
+   *         `address` attributes.
+   *
+   * @throws CodeError if decoded value is not in the range 128..130.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let result = {};
+    result.type = WSP.Octet.decode(data);
+    if ((result.type < 128) || (result.type > 130)) {
+      throw new WSP.CodeError("MM-flags-value: invalid type " + result.type);
+    }
+    result.text = EncodedStringValue.decode(data);
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * MM-state-value = Draft | Sent | New | Retrieved | Forwarded
+ * Draft = <Octet 128>
+ * Sent = <Octet 129>
+ * New = <Octet 130>
+ * Retrieved = <Octet 131>
+ * Forwarded = <Octet 132>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.33
+ */
+let MmStateValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range 128..132.
+   */
+  decode: function decode(data) {
+    let state = WSP.Octet.decode(data);
+    if ((state >= 128) && (state <= 132)) {
+      return state;
+    }
+
+    throw new WSP.CodeError("MM-state-value: invalid state " + state);
+  },
+};
+
+/**
+ * Priority-value = Low | Normal | High
+ * Low = <Octet 128>
+ * Normal = <Octet 129>
+ * High = <Octet 130>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.35
+ */
+let PriorityValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range 128..130.
+   */
+  decode: function decode(data) {
+    let priority = WSP.Octet.decode(data);
+    if ((priority >= 128) && (priority <= 130)) {
+      return priority;
+    }
+
+    throw new WSP.CodeError("Priority-value: invalid priority " + priority);
+  },
+};
+
+/**
+ * Recommended-Retrieval-Mode-value = Manual
+ * Manual = <Octet 128>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.39
+ */
+let RecommendedRetrievalModeValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   */
+  decode: function decode(data) {
+    return WSP.Octet.decodeEqualTo(data, 128);
+  },
+};
+
+/**
+ * Reply-charging-value = Requested | Requested text only | Accepted |
+ *                        Accepted text only
+ * Requested = <Octet 128>
+ * Requested text only = <Octet 129>
+ * Accepted = <Octet 130>
+ * Accepted text only = <Octet 131>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.43
+ */
+let ReplyChargingValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range 128..131.
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    if ((value >= 128) && (value <= 131)) {
+      return value;
+    }
+
+    throw new WSP.CodeError("Reply-charging-value: invalid value " + value);
+  },
+};
+
+/**
+ * Retrieve-status-value = Ok | Error-transient-failure |
+ *                         Error-transient-message-not-found |
+ *                         Error-transient-network-problem |
+ *                         Error-permanent-failure |
+ *                         Error-permanent-service-denied |
+ *                         Error-permanent-message-not-found |
+ *                         Error-permanent-content-unsupported
+ * Ok = <Octet 128>
+ * Error-transient-failure = <Octet 192>
+ * Error-transient-message-not-found = <Octet 193>
+ * Error-transient-network-problem = <Octet 194>
+ * Error-permanent-failure = <Octet 224>
+ * Error-permanent-service-denied = <Octet 225>
+ * Error-permanent-message-not-found = <Octet 226>
+ * Error-permanent-content-unsupported = <Octet 227>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.50
+ */
+let RetrieveStatusValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    if ((value == 128)
+        || ((value >= 192) && (value <= 194))
+        || ((value >= 224) && (value <= 227))) {
+      return value;
+    }
+
+    if ((value >= 195) && (value <= 223)) {
+      // The values 195 through 223 are reserved for future use to indicate
+      // other transient failures. An MMS Client MUST react the same to a value
+      // in range 195 to 223 as it does to the value 192
+      // (Error-transient-failure).
+      return MMS_PDU_ERROR_TRANSIENT_FAILURE;
+    }
+
+    // The values 228 through 255 are reserved for future use to indicate
+    // other permanent failures. An MMS Client MUST react the same to a value
+    // in range 228 to 255 as it does to the value 224
+    // (Error-permanent-failure).
+
+    // Any other values SHALL NOT be used. They are reserved for future use.
+    // An MMS Client that receives such a reserved value MUST react the same
+    // as it does to the value 224 (Error-permanent-failure).
+    return MMS_PDU_ERROR_PERMANENT_FAILURE;
+  },
+};
+
+/**
+ * Status-value = Expired | Retrieved | Rejected | Deferred | Unrecognised |
+ *                Indeterminate | Forwarded | Unreachable
+ * Expired = <Octet 128>
+ * Retrieved = <Octet 129>
+ * Rejected = <Octet 130>
+ * Deferred = <Octet 131>
+ * Unrecognised = <Octet 132>
+ * Indeterminate = <Octet 133>
+ * Forwarded = <Octet 134>
+ * Unreachable = <Octet 135>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.54
+ */
+let StatusValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range 128..135.
+   */
+  decode: function decode(data) {
+    let status = WSP.Octet.decode(data);
+    if ((status >= 128) && (status <= 135)) {
+      return status;
+    }
+
+    throw new WSP.CodeError("Status-value: invalid status " + status);
+  },
+};
+
+let PduHelper = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param headers
+   *        An optional object to store parsed header fields. Created
+   *        automatically if undefined.
+   *
+   * @return A boolean value indicating whether it's followed by message body.
+   */
+  parseHeaders: function parseHeaders(data, headers) {
+    if (!headers) {
+      headers = {};
+    }
+
+    let header;
+    while (data.offset < data.array.length) {
+      // There is no `header length` information in MMS PDU. If we just got
+      // something wrong in parsing header fields, we might not be able to
+      // determine the correct header-content boundary.
+      header = HeaderField.decode(data, headers);
+
+      if (header) {
+        headers[header.name] = header.value;
+        if (header.name == "content-type") {
+          // `... if the PDU contains a message body the Content Type MUST be
+          // the last header field, followed by message body.` See
+          // OMA-TS-MMS_ENC-V1_3-20110913-A section 7.
+          break;
+        }
+      }
+    }
+
+    return headers;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param msg
+   *        A message object to store decoded multipart or octet array content.
+   */
+  parseContent: function parseContent(data, msg) {
+    let contentType = msg.headers["content-type"].media;
+    if ((contentType == "application/vnd.wap.multipart.related")
+        || (contentType == "application/vnd.wap.multipart.mixed")) {
+      msg.parts = WSP.PduHelper.parseMultiPart(data);
+      return;
+    }
+
+    if (data.offset >= data.array.length) {
+      return;
+    }
+
+    msg.content = WSP.Octet.decodeMultiple(data, data.array.length);
+    if (false) {
+      for (let begin = 0; begin < msg.content.length; begin += 20) {
+        debug("content: " + JSON.stringify(msg.content.subarray(begin, begin + 20)));
+      }
+    }
+  },
+
+  /**
+   * Check existences of all mandatory fields of a MMS message. Also sets `type`
+   * and `typeinfo` for convient access.
+   *
+   * @param msg
+   *        A MMS message object.
+   *
+   * @throws FatalCodeError if the PDU type is not supported yet.
+   */
+  checkMandatoryFields: function checkMandatoryFields(msg) {
+    let type = WSP.ensureHeader(msg.headers, "x-mms-message-type");
+    let entry = MMS_PDU_TYPES[type];
+    if (!entry) {
+      throw new WSP.FatalCodeError(
+        "checkMandatoryFields: unsupported message type " + type);
+    }
+
+    entry.mandatoryFields.forEach(function (name) {
+      WSP.ensureHeader(msg.headers, name);
+    });
+
+    // Setup convient alias that referenced frequently.
+    msg.type = type;
+    msg.typeinfo = entry;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param msg [optional]
+   *        Optional target object for decoding.
+   *
+   * @return A MMS message object or null in case of errors found.
+   */
+  parse: function parse(data, msg) {
+    if (!msg) {
+      msg = {};
+    }
+
+    try {
+      msg.headers = this.parseHeaders(data, msg.headers);
+
+      // Validity checks
+      this.checkMandatoryFields(msg);
+
+      if (msg.typeinfo.hasContent) {
+        this.parseContent(data, msg);
+      }
+    } catch (e) {
+      debug("Failed to parse MMS message, error message: " + e.message);
+      return null;
+    }
+
+    return msg;
+  },
+};
+
+const MMS_PDU_TYPES = (function () {
+  let pdus = {};
+  function add(number, hasContent, mandatoryFields) {
+    pdus[number] = {
+      number: number,
+      hasContent: hasContent,
+      mandatoryFields: mandatoryFields,
+    };
+  }
+
+  add(MMS_PDU_TYPE_NOTIFICATION_IND, false, ["x-mms-message-type",
+                                             "x-mms-transaction-id",
+                                             "x-mms-mms-version",
+                                             "x-mms-message-class",
+                                             "x-mms-message-size",
+                                             "x-mms-expiry",
+                                             "x-mms-content-location"]);
+  add(MMS_PDU_TYPE_RETRIEVE_CONF, true, ["x-mms-message-type",
+                                         "x-mms-mms-version",
+                                         "content-type"]);
+
+  return pdus;
+})();
+
+/**
+ * Header field names and assigned numbers.
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.4
+ */
+const MMS_HEADER_FIELDS = (function () {
+  let names = {};
+  function add(name, number, coder) {
+    let entry = {
+      name: name,
+      number: number,
+      coder: coder,
+    };
+    names[name] = names[number] = entry;
+  }
+
+  add("bcc",                                     0x01, Address);
+  add("cc",                                      0x02, Address);
+  add("x-mms-content-location",                  0x03, ContentLocationValue);
+  add("content-type",                            0x04, WSP.ContentTypeValue);
+  add("date",                                    0x05, WSP.DateValue);
+  add("x-mms-delivery-report",                   0x06, BooleanValue);
+  //add("x-mms-delivery-time", 0x07);
+  add("x-mms-expiry",                            0x08, ExpiryValue);
+  add("from",                                    0x09, FromValue);
+  add("x-mms-message-class",                     0x0A, MessageClassValue);
+  add("message-id",                              0x0B, WSP.TextString);
+  add("x-mms-message-type",                      0x0C, MessageTypeValue);
+  add("x-mms-mms-version",                       0x0D, WSP.ShortInteger);
+  add("x-mms-message-size",                      0x0E, WSP.LongInteger);
+  add("x-mms-priority",                          0x0F, PriorityValue);
+  add("x-mms-read-report",                       0x10, BooleanValue);
+  add("x-mms-report-allowed",                    0x11, BooleanValue);
+  //add("x-mms-response-status", 0x12);
+  //add("x-mms-response-text", 0x13);
+  //add("x-mms-sender-visibility", 0x14);
+  add("x-mms-status",                            0x15, StatusValue);
+  add("subject",                                 0x16, EncodedStringValue);
+  add("to",                                      0x17, Address);
+  add("x-mms-transaction-id",                    0x18, WSP.TextString);
+  add("x-mms-retrieve-status",                   0x19, RetrieveStatusValue);
+  add("x-mms-retrieve-text",                     0x1A, EncodedStringValue);
+  //add("x-mms-read-status", 0x1B);
+  add("x-mms-reply-charging",                    0x1C, WSP.ReplyChargingValue);
+  add("x-mms-reply-charging-deadline",           0x1D, ExpiryValue);
+  add("x-mms-reply-charging-id",                 0x1E, WSP.TextString);
+  add("x-mms-reply-charging-size",               0x1F, WSP.LongInteger);
+  add("x-mms-previously-sent-by",                0x20, PreviouslySentByValue);
+  add("x-mms-previously-sent-date",              0x21, PreviouslySentDateValue);
+  add("x-mms-store",                             0x22, BooleanValue);
+  add("x-mms-mm-state",                          0x23, MmStateValue);
+  add("x-mms-mm-flags",                          0x24, MmFlagsValue);
+  //add("x-mms-store-status", 0x25);
+  //add("x-mms-store-status-text", 0x26);
+  add("x-mms-stored",                            0x27, BooleanValue);
+  //add("x-mms-attributes", 0x28);
+  add("x-mms-totals",                            0x29, BooleanValue);
+  //add("x-mms-mbox-totals", 0x2A);
+  add("x-mms-quotas",                            0x2B, BooleanValue);
+  //add("x-mms-mbox-quotas", 0x2C);
+  add("x-mms-message-count",                     0x2D, WSP.IntegerValue);
+  //add("content", 0x2E);
+  add("x-mms-start",                             0x2F, WSP.IntegerValue);
+  //add("additional-headers", 0x30);
+  add("x-mms-distribution-indicator",            0x31, BooleanValue);
+  add("x-mms-element-descriptor",                0x32, ElementDescriptorValue);
+  add("x-mms-limit",                             0x33, WSP.IntegerValue);
+  add("x-mms-recommended-retrieval-mode",        0x34, RecommendedRetrievalModeValue);
+  add("x-mms-recommended-retrieval-mode-text",   0x35, EncodedStringValue);
+  //add("x-mms-status-text", 0x36);
+  add("x-mms-applic-id",                         0x37, WSP.TextString);
+  add("x-mms-reply-applic-id",                   0x38, WSP.TextString);
+  add("x-mms-aux-applic-id",                     0x39, WSP.TextString);
+  add("x-mms-content-class",                     0x3A, ContentClassValue);
+  add("x-mms-drm-content",                       0x3B, BooleanValue);
+  add("x-mms-adaptation-allowed",                0x3C, BooleanValue);
+  add("x-mms-replace-id",                        0x3D, WSP.TextString);
+  add("x-mms-cancel-id",                         0x3E, WSP.TextString);
+  add("x-mms-cancel-status",                     0x3F, BooleanValue);
+
+  return names;
+})();
+
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A Table 27: Parameter Name Assignments
+const MMS_WELL_KNOWN_PARAMS = (function () {
+  let params = {};
+
+  function add(name, number, coder) {
+    let entry = {
+      name: name,
+      number: number,
+      coder: coder,
+    };
+    params[name] = params[number] = entry;
+  }
+
+  add("type", 0x02, WSP.ConstrainedEncoding);
+
+  return params;
+})();
+
+let debug;
+if (DEBUG) {
+  debug = function (s) {
+    dump("-$- MmsPduHelper: " + s + "\n");
+  };
+} else {
+  debug = function (s) {};
+}
+
+const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([
+  // Decoders
+  "BooleanValue",
+  "Address",
+  "HeaderField",
+  "MmsHeader",
+  "ContentClassValue",
+  "ContentLocationValue",
+  "ElementDescriptorValue",
+  "Parameter",
+  "EncodedStringValue",
+  "ExpiryValue",
+  "FromValue",
+  "MessageClassValue",
+  "ClassIdentifier",
+  "MessageTypeValue",
+  "PriorityValue",
+  "RecommendedRetrievalModeValue",
+  "ReplyChargingValue",
+  "StatusValue",
+
+  // Parser
+  "PduHelper",
+]);
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/mms_consts.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Encoded X-Mms-Message-Type values
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.30
+const MMS_PDU_TYPE_SEND_REQ = 128;
+const MMS_PDU_TYPE_SEND_CONF = 129;
+const MMS_PDU_TYPE_NOTIFICATION_IND = 130;
+const MMS_PDU_TYPE_NOTIFYRESP_IND = 131;
+const MMS_PDU_TYPE_RETRIEVE_CONF = 132;
+const MMS_PDU_TYPE_ACKNOWLEDGE_IND = 133;
+const MMS_PDU_TYPE_DELIVERY_IND = 134;
+const MMS_PDU_TYPE_READ_REC_IND = 135;
+const MMS_PDU_TYPE_READ_ORIG_IND = 136;
+const MMS_PDU_TYPE_FORWARD_REQ = 137;
+const MMS_PDU_TYPE_FORWARD_CONF = 138;
+const MMS_PDU_TYPE_MBOX_STORE_REQ = 139;
+const MMS_PDU_TYPE_MBOX_STORE_CONF = 140;
+const MMS_PDU_TYPE_MBOX_VIEW_REQ = 141;
+const MMS_PDU_TYPE_MBOX_VIEW_CONF = 142;
+const MMS_PDU_TYPE_MBOX_UPLOAD_REQ = 143;
+const MMS_PDU_TYPE_MBOX_UPLOAD_CONF = 144;
+const MMS_PDU_TYPE_MBOX_DELETE_REQ = 145;
+const MMS_PDU_TYPE_MBOX_DELETE_CONF = 146;
+const MMS_PDU_TYPE_MBOX_DESCR = 147;
+const MMS_PDU_TYPE_DELETE_REQ = 148;
+const MMS_PDU_TYPE_DELETE_CONF = 149;
+const MMS_PDU_TYPE_CANCEL_REQ = 150;
+const MMS_PDU_TYPE_CANCEL_CONF = 151;
+
+// X-Mms-Retrieve-Status values
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.50
+const MMS_PDU_ERROR_OK                            = 128;
+const MMS_PDU_ERROR_TRANSIENT_FAILURE             = 192;
+const MMS_PDU_ERROR_TRANSIENT_MESSAGE_NOT_FOUND   = 193;
+const MMS_PDU_ERROR_TRANSIENT_NETWORK_PROBLEM     = 194;
+const MMS_PDU_ERROR_PERMANENT_FAILURE             = 224;
+const MMS_PDU_ERROR_PERMANENT_SERVICE_DENIED      = 225;
+const MMS_PDU_ERROR_PERMANENT_MESSAGE_NOT_FOUND   = 226;
+const MMS_PDU_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 227;
+
+const ALL_CONST_SYMBOLS = Object.keys(this);
+
+// Allow this file to be imported via Components.utils.import().
+const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS;
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/tests/test_mms_pdu_helper.js
@@ -0,0 +1,484 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let MMS = {};
+subscriptLoader.loadSubScript("resource://gre/modules/MmsPduHelper.jsm", MMS);
+MMS.debug = do_print;
+
+function run_test() {
+  run_next_test();
+}
+
+//
+// Test target: BooleanValue
+//
+
+//// BooleanValue.decode ////
+
+add_test(function test_BooleanValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if (i == 128) {
+      wsp_decode_test(MMS.BooleanValue, [128], true);
+    } else if (i == 129) {
+      wsp_decode_test(MMS.BooleanValue, [129], false);
+    } else {
+      wsp_decode_test(MMS.BooleanValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: Address
+//
+
+//// Address.decode ////
+
+add_test(function test_Address_decode() {
+  // Test for PLMN address
+  wsp_decode_test(MMS.Address, strToCharCodeArray("+123.456-789/TYPE=PLMN"),
+                      {address: "+123.456-789", type: "PLMN"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("123456789/TYPE=PLMN"),
+                      {address: "123456789", type: "PLMN"});
+  // Test for IPv4
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3.4/TYPE=IPv4"),
+                      {address: "1.2.3.4", type: "IPv4"});
+  // Test for IPv6
+  wsp_decode_test(MMS.Address,
+    strToCharCodeArray("1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000/TYPE=IPv6"),
+    {address: "1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000", type: "IPv6"}
+  );
+  // Test for other device-address
+  wsp_decode_test(MMS.Address, strToCharCodeArray("+H-e.l%l_o/TYPE=W0r1d_"),
+                      {address: "+H-e.l%l_o", type: "W0r1d_"});
+  // Test for num-shortcode
+  wsp_decode_test(MMS.Address, strToCharCodeArray("+123"),
+                      {address: "+123", type: "num"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("*123"),
+                      {address: "*123", type: "num"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("#123"),
+                      {address: "#123", type: "num"});
+  // Test for alphanum-shortcode
+  wsp_decode_test(MMS.Address, strToCharCodeArray("H0wD0Y0uTurnTh1s0n"),
+                      {address: "H0wD0Y0uTurnTh1s0n", type: "alphanum"});
+  // Test for other unknown typed sequence
+  wsp_decode_test(MMS.Address, strToCharCodeArray("Joe User <joe@user.org>"),
+                      {address: "Joe User <joe@user.org>", type: "unknown"});
+
+  run_next_test();
+});
+
+//
+// Test target: HeaderField
+//
+
+//// HeaderField.decode ////
+
+add_test(function test_HeaderField_decode() {
+  wsp_decode_test(MMS.HeaderField, [65, 0, 66, 0], {name: "a", value: "B"});
+  wsp_decode_test(MMS.HeaderField, [0x80 | 0x27, 128],
+                      {name: "x-mms-stored", value: true});
+
+  run_next_test();
+});
+
+//
+// Test target: MmsHeader
+//
+
+//// MmsHeader.decode ////
+
+add_test(function test_MmsHeader_decode() {
+  wsp_decode_test(MMS.MmsHeader, [0x80 | 0x00], null, "NotWellKnownEncodingError");
+  wsp_decode_test(MMS.MmsHeader, [0x80 | 0x27, 128],
+                      {name: "x-mms-stored", value: true});
+  wsp_decode_test(MMS.MmsHeader, [0x80 | 0x27, 255], null);
+
+  run_next_test();
+});
+
+//
+// Test target: ContentClassValue
+//
+
+//// ContentClassValue.decode ////
+
+add_test(function test_ContentClassValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 135)) {
+      wsp_decode_test(MMS.ContentClassValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.ContentClassValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: ContentLocationValue
+//
+
+//// ContentLocationValue.decode ////
+
+add_test(function test_ContentLocationValue_decode() {
+  // Test for MMS_PDU_TYPE_MBOX_DELETE_CONF & MMS_PDU_TYPE_DELETE_CONF
+  wsp_decode_test_ex(function (data) {
+      data.array[0] = data.array.length - 1;
+
+      let options = {};
+      options["x-mms-message-type"] = /*MMS.MMS_PDU_TYPE_MBOX_DELETE_CONF*/146;
+      return MMS.ContentLocationValue.decode(data, options);
+    }, [0, 0x80 | 0x00].concat(strToCharCodeArray("http://no.such.com/path")),
+    {statusCount: 0, uri: "http://no.such.com/path"}
+  );
+  wsp_decode_test_ex(function (data) {
+      data.array[0] = data.array.length - 1;
+
+      let options = {};
+      options["x-mms-message-type"] = /*MMS.MMS_PDU_TYPE_DELETE_CONF*/149;
+      return MMS.ContentLocationValue.decode(data, options);
+    }, [0, 0x80 | 0x00].concat(strToCharCodeArray("http://no.such.com/path")),
+    {statusCount: 0, uri: "http://no.such.com/path"}
+  );
+
+  run_next_test();
+});
+
+//
+// Test target: ElementDescriptorValue
+//
+
+//// ElementDescriptorValue.decode ////
+
+add_test(function test_ElementDescriptorValue_decode() {
+  wsp_decode_test(MMS.ElementDescriptorValue, [2, 97, 0], {contentReference: "a"});
+  wsp_decode_test(MMS.ElementDescriptorValue, [4, 97, 0, 0x80 | 0x02, 0x80],
+                      {contentReference: "a", params: {type: 0}});
+
+  run_next_test();
+});
+
+//
+// Test target: Parameter
+//
+
+//// Parameter.decodeParameterName ////
+
+add_test(function test_Parameter_decodeParameterName() {
+  wsp_decode_test_ex(function (data) {
+      return MMS.Parameter.decodeParameterName(data);
+    }, [0x80 | 0x02], "type"
+  );
+  wsp_decode_test_ex(function (data) {
+      return MMS.Parameter.decodeParameterName(data);
+    }, strToCharCodeArray("type"), "type"
+  );
+
+  run_next_test();
+});
+
+//// Parameter.decode ////
+
+add_test(function test_Parameter_decode() {
+  wsp_decode_test(MMS.Parameter, [0x80 | 0x02, 0x80 | 0x00], {name: "type", value: 0});
+
+  run_next_test();
+});
+
+//// Parameter.decodeMultiple ////
+
+add_test(function test_Parameter_decodeMultiple() {
+  // FIXME: The following test case falls because Parameter-value decoding of
+  //        "type" parameters utilies WSP.ConstrainedEncoding, which in turn
+  //        utilies WSP.TextString, and TextString is not matual exclusive to
+  //        each other.
+  //wsp_decode_test_ex(function (data) {
+  //    return MMS.Parameter.decodeMultiple(data, data.array.length);
+  //  }, [0x80 | 0x02, 0x80 | 0x00].concat(strToCharCodeArray("good")).concat([0x80 | 0x01]),
+  //  {type: 0, good: 1}
+  //);
+
+  run_next_test();
+});
+
+//
+// Test target: EncodedStringValue
+//
+
+//// EncodedStringValue.decode ////
+
+add_test(function test_EncodedStringValue_decode() {
+  // Test for normal TextString
+  wsp_decode_test(MMS.EncodedStringValue, strToCharCodeArray("Hello"), "Hello");
+  // Test for non-well-known charset
+  wsp_decode_test(MMS.EncodedStringValue, [1, 0x80], null, "NotWellKnownEncodingError");
+  // Test for utf-8
+  let (entry = MMS.WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"]) {
+    // "Mozilla" in full width.
+    let str = "\uff2d\uff4f\uff5a\uff49\uff4c\uff4c\uff41";
+
+    let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+               .createInstance(Ci.nsIScriptableUnicodeConverter);
+    conv.charset = entry.converter;
+
+    let raw;
+    try {
+      let raw = conv.convertToByteArray(str);
+      if (raw[0] >= 128) {
+        wsp_decode_test(MMS.EncodedStringValue,
+                            [raw.length + 2, 0x80 | entry.number, 127].concat(raw), str);
+      } else {
+        wsp_decode_test(MMS.EncodedStringValue,
+                            [raw.length + 1, 0x80 | entry.number].concat(raw), str);
+      }
+    } catch (e) {
+      do_print("Can't convert test string to byte array with " + entry.converter);
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: ExpiryValue
+//
+
+//// ExpiryValue.decode ////
+
+add_test(function test_ExpiryValue_decode() {
+  // Test for Absolute-token Date-value
+  wsp_decode_test(MMS.ExpiryValue, [3, 128, 1, 0x80], new Date(0x80 * 1000));
+  // Test for Relative-token Delta-seconds-value
+  wsp_decode_test(MMS.ExpiryValue, [3, 129, 0x80], 0);
+
+  run_next_test();
+});
+
+//
+// Test target: PreviouslySentByValue
+//
+
+//// PreviouslySentByValue.decode ////
+
+add_test(function test_PreviouslySentByValue_decode() {
+  wsp_decode_test(MMS.PreviouslySentByValue, [3, 0x80 | 0x03, 65, 0],
+                      {forwardedCount: 3, originator: {address: "A",
+                                                       type: "alphanum"}});
+
+  run_next_test();
+});
+
+//
+// Test target: PreviouslySentDateValue
+//
+
+//// PreviouslySentDateValue.decode ////
+
+add_test(function test_PreviouslySentDateValue_decode() {
+  wsp_decode_test(MMS.PreviouslySentDateValue, [3, 0x80 | 0x03, 1, 4],
+                      {forwardedCount: 3, timestamp: new Date(4 * 1000)});
+
+  run_next_test();
+});
+
+//
+// Test target: FromValue
+//
+
+//// FromValue.decode ////
+
+add_test(function test_FromValue_decode() {
+  // Test for Insert-address-token:
+  wsp_decode_test(MMS.FromValue, [1, 129], null);
+  // Test for Address-present-token:
+  let (addr = strToCharCodeArray("+123/TYPE=PLMN")) {
+    wsp_decode_test(MMS.FromValue, [addr.length + 2, 128].concat(addr),
+                        {address: "+123", type: "PLMN"});
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: MessageClassValue
+//
+
+//// MessageClassValue.decode ////
+
+add_test(function test_MessageClassValue_decode() {
+  wsp_decode_test(MMS.MessageClassValue, [65, 0], "A");
+  wsp_decode_test(MMS.MessageClassValue, [128], "personal");
+
+  run_next_test();
+});
+
+//
+// Test target: ClassIdentifier
+//
+
+//// ClassIdentifier.decode ////
+
+add_test(function test_ClassIdentifier_decode() {
+  let (IDs = ["personal", "advertisement", "informational", "auto"]) {
+    for (let i = 0; i < 256; i++) {
+      if ((i >= 128) && (i <= 131)) {
+        wsp_decode_test(MMS.ClassIdentifier, [i], IDs[i - 128]);
+      } else {
+        wsp_decode_test(MMS.ClassIdentifier, [i], null, "CodeError");
+      }
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: MessageTypeValue
+//
+
+//// MessageTypeValue.decode ////
+
+add_test(function test_MessageTypeValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 151)) {
+      wsp_decode_test(MMS.MessageTypeValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.MessageTypeValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: MmFlagsValue
+//
+
+//// MmFlagsValue.decode ////
+
+add_test(function test_MmFlagsValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 130)) {
+      wsp_decode_test(MMS.MmFlagsValue, [3, i, 65, 0], {type: i, text: "A"});
+    } else {
+      wsp_decode_test(MMS.MmFlagsValue, [3, i, 65, 0], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: MmStateValue
+//
+
+//// MmStateValue.decode ////
+
+add_test(function test_MmStateValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 132)) {
+      wsp_decode_test(MMS.MmStateValue, [i, 0], i);
+    } else {
+      wsp_decode_test(MMS.MmStateValue, [i, 0], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: PriorityValue
+//
+
+//// PriorityValue.decode ////
+
+add_test(function test_PriorityValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 130)) {
+      wsp_decode_test(MMS.PriorityValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.PriorityValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: RecommendedRetrievalModeValue
+//
+
+//// RecommendedRetrievalModeValue.decode ////
+
+add_test(function test_RecommendedRetrievalModeValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if (i == 128) {
+      wsp_decode_test(MMS.RecommendedRetrievalModeValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.RecommendedRetrievalModeValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: ReplyChargingValue
+//
+
+//// ReplyChargingValue.decode ////
+
+add_test(function test_ReplyChargingValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 131)) {
+      wsp_decode_test(MMS.ReplyChargingValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.ReplyChargingValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: RetrieveStatusValue
+//
+
+//// RetrieveStatusValue.decode ////
+
+add_test(function test_RetrieveStatusValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i == 128)
+        || ((i >= 192) && (i <= 194))
+        || ((i >= 224) && (i <= 227))) {
+      wsp_decode_test(MMS.RetrieveStatusValue, [i], i);
+    } else if ((i >= 195) && (i <= 223)) {
+      wsp_decode_test(MMS.RetrieveStatusValue, [i], 192);
+    } else {
+      wsp_decode_test(MMS.RetrieveStatusValue, [i], 224);
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: StatusValue
+//
+
+//// StatusValue.decode ////
+
+add_test(function test_StatusValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 135)) {
+      wsp_decode_test(MMS.StatusValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.StatusValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
--- a/dom/mms/tests/xpcshell.ini
+++ b/dom/mms/tests/xpcshell.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 head = header_helpers.js
 tail =
 
 [test_wsp_pdu_helper.js]
+[test_mms_pdu_helper.js]