diff --git a/dom/mms/src/ril/MmsPduHelper.jsm b/dom/mms/src/ril/MmsPduHelper.jsm index 957902bfdc9..6c692574398 100644 --- a/dom/mms/src/ril/MmsPduHelper.jsm +++ b/dom/mms/src/ril/MmsPduHelper.jsm @@ -26,6 +26,13 @@ function translatePduErrorToStatus(error) { return MMS_PDU_STATUS_UNRECOGNISED; } +function defineLazyRegExp(obj, name, pattern) { + obj.__defineGetter__(name, function() { + delete obj[name]; + return obj[name] = new RegExp(pattern); + }); +} + /** * Internal decoding function for boolean values. * @@ -80,17 +87,17 @@ let Address = { 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)) { + if (((result = str.match(this.REGEXP_DECODE_PLMN)) != null) + || ((result = str.match(this.REGEXP_DECODE_IPV4)) != null) + || ((result = str.match(this.REGEXP_DECODE_IPV6)) != null) + || ((result = str.match(this.REGEXP_DECODE_CUSTOM)) != null)) { return {address: result[1], type: result[2]}; } let type; - if (str.match(/^[\+*#]\d+$/)) { + if (str.match(this.REGEXP_NUM)) { type = "num"; - } else if (str.match(/^\w+$/)) { + } else if (str.match(this.REGEXP_ALPHANUM)) { type = "alphanum"; } else if (str.indexOf("@") > 0) { // E-mail should match the definition of `mailbox` as described in section @@ -103,8 +110,78 @@ let Address = { return {address: str, type: type}; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An object of two string-typed attributes: address and type. + */ + encode: function encode(data, value) { + if (!value || !value.type || !value.address) { + throw new WSP.CodeError("Address: invalid value"); + } + + let str; + switch (value.type) { + case "email": + if (value.address.indexOf("@") > 0) { + str = value.address; + } + break; + case "num": + if (value.address.match(this.REGEXP_NUM)) { + str = value.address; + } + break; + case "alphanum": + if (value.address.match(this.REGEXP_ALPHANUM)) { + str = value.address; + } + break; + case "IPv4": + if (value.address.match(this.REGEXP_ENCODE_IPV4)) { + str = value.address + "/TYPE=IPv4"; + } + break; + case "IPv6": + if (value.address.match(this.REGEXP_ENCODE_IPV6)) { + str = value.address + "/TYPE=IPv6"; + } + break; + case "PLMN": + if (value.address.match(this.REGEXP_ENCODE_PLMN)) { + str = value.address + "/TYPE=PLMN"; + } + break; + default: + if (value.type.match(this.REGEXP_ENCODE_CUSTOM_TYPE) + && value.address.match(this.REGEXP_ENCODE_CUSTOM_ADDR)) { + str = value.address + "/TYPE=" + value.type; + } + break; + } + + if (!str) { + throw new WSP.CodeError("Address: invalid value: " + JSON.stringify(value)); + } + + EncodedStringValue.encode(data, str); + }, }; +defineLazyRegExp(Address, "REGEXP_DECODE_PLMN", "^(\\+?[\\d.-]+)\\/TYPE=(PLMN)$"); +defineLazyRegExp(Address, "REGEXP_DECODE_IPV4", "^(\\d{1,3}(?:\\.\\d{1,3}){3})\\/TYPE=(IPv4)$"); +defineLazyRegExp(Address, "REGEXP_DECODE_IPV6", "^([\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7})\\/TYPE=(IPv6)$"); +defineLazyRegExp(Address, "REGEXP_DECODE_CUSTOM", "^([\\w\\+\\-.%]+)\\/TYPE=(\\w+)$"); +defineLazyRegExp(Address, "REGEXP_ENCODE_PLMN", "^\\+?[\\d.-]+$"); +defineLazyRegExp(Address, "REGEXP_ENCODE_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$"); +defineLazyRegExp(Address, "REGEXP_ENCODE_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$"); +defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_TYPE", "^\\w+$"); +defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_ADDR", "^[\\w\\+\\-.%]+$"); +defineLazyRegExp(Address, "REGEXP_NUM", "^[\\+*#]\\d+$"); +defineLazyRegExp(Address, "REGEXP_ALPHANUM", "^\\w+$"); + /** * Header-field = MMS-header | Application-header * @@ -240,6 +317,20 @@ let ContentClassValue = { throw new WSP.CodeError("Content-class-value: invalid class " + value); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * A numeric content class value to be encoded. + */ + encode: function encode(data, value) { + if ((value < 128) || (value > 135)) { + throw new WSP.CodeError("Content-class-value: invalid class " + value); + } + + WSP.Octet.encode(data, value); + }, }; /** @@ -406,6 +497,30 @@ let Parameter = { return params; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param param + * An object containing two attributes: `name` and `value`. + * @param options + * Extra context for encoding. + */ + encode: function encode(data, param, options) { + if (!param || !param.name) { + throw new WSP.CodeError("Parameter-name: empty param name"); + } + + let entry = MMS_WELL_KNOWN_PARAMS[param.name.toLowerCase()]; + if (entry) { + WSP.ShortInteger.encode(data, entry.number); + } else { + WSP.TextString.encode(data, param.name); + } + + WSP.encodeAlternatives(data, param.value, options, + WSP.ConstrainedEncoding, WSP.TextString); + }, }; /** @@ -489,6 +604,63 @@ let EncodedStringValue = { return this.decodeCharsetEncodedString(data); } }, + + /** + * Always encode target string with UTF-8 encoding. + * + * @param data + * A wrapped object to store encoded raw data. + * @param str + * A string. + */ + encodeCharsetEncodedString: function encodeCharsetEncodedString(data, str) { + let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Ci.nsIScriptableUnicodeConverter); + // `When the text string cannot be represented as us-ascii, the character + // set SHALL be encoded as utf-8(IANA MIBenum 106) which has unique byte + // ordering.` ~ OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1 + conv.charset = "UTF-8"; + + let raw; + try { + raw = conv.convertToByteArray(str); + } catch (e) { + throw new WSP.CodeError("Charset-encoded-string: " + e.message); + } + + let length = raw.length + 2; // Charset number and NUL character + // Prepend if necessary. + if (raw[0] >= 128) { + ++length; + } + + WSP.ValueLength.encode(data, length); + + let entry = WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"]; + WSP.IntegerValue.encode(data, entry.number); + + if (raw[0] >= 128) { + WSP.Octet.encode(data, 127); + } + WSP.Octet.encodeMultiple(data, raw); + WSP.Octet.encode(data, 0); + }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param str + * A string. + */ + encode: function encode(data, str) { + let begin = data.offset; + try { + WSP.TextString.encode(data, str); + } catch (e) { + data.offset = begin; + this.encodeCharsetEncodedString(data, str); + } + }, }; /** @@ -529,6 +701,39 @@ let ExpiryValue = { return result; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * A Date object for absolute expiry or an integer for relative one. + */ + encode: function encode(data, value) { + let isDate, begin = data.offset; + if (value instanceof Date) { + isDate = true; + WSP.DateValue.encode(data, value); + } else if (typeof value == "number") { + isDate = false; + WSP.DeltaSecondsValue.encode(data, value); + } else { + throw new CodeError("Expiry-value: invalid value type"); + } + + // Calculate how much octets will be written and seek back. + // TODO: use memmove, see bug 730873 + let len = data.offset - begin; + data.offset = begin; + + WSP.ValueLength.encode(data, len + 1); + if (isDate) { + WSP.Octet.encode(data, 128); + WSP.DateValue.encode(data, value); + } else { + WSP.Octet.encode(data, 129); + WSP.DeltaSecondsValue.encode(data, value); + } + }, }; /** @@ -568,6 +773,31 @@ let FromValue = { return result; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * A Address-value or null for MMS Proxy-Relay Insert-Address mode. + */ + encode: function encode(data, value) { + if (!value) { + WSP.ValueLength.encode(data, 1); + WSP.Octet.encode(data, 129); + return; + } + + // Calculate how much octets will be written and seek back. + // TODO: use memmove, see bug 730873 + let begin = data.offset; + Address.encode(data, value); + let len = data.offset - begin; + data.offset = begin; + + WSP.ValueLength.encode(data, len + 1); + WSP.Octet.encode(data, 128); + Address.encode(data, value); + }, }; /** @@ -641,6 +871,8 @@ let PreviouslySentDateValue = { * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.27 */ let MessageClassValue = { + WELL_KNOWN_CLASSES: ["personal", "advertisement", "informational", "auto"], + /** * @param data * A wrapped object containing raw PDU data. @@ -651,11 +883,8 @@ let MessageClassValue = { */ decodeClassIdentifier: function decodeClassIdentifier(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"; + if ((value >= 128) && (value < (128 + this.WELL_KNOWN_CLASSES.length))) { + return this.WELL_KNOWN_CLASSES[value - 128]; } throw new WSP.CodeError("Class-identifier: invalid id " + value); @@ -676,6 +905,20 @@ let MessageClassValue = { return WSP.TokenText.decode(data); } }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param klass + */ + encode: function encode(data, klass) { + let index = this.WELL_KNOWN_CLASSES.indexOf(klass.toLowerCase()); + if (index >= 0) { + WSP.Octet.encode(data, index + 128); + } else { + WSP.TokenText.encode(data, klass); + } + }, }; /** @@ -753,6 +996,30 @@ let MmFlagsValue = { return result; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An object containing an integer `type` and an string-typed + * `text` attributes. + */ + encode: function encode(data, value) { + if ((value.type < 128) || (value.type > 130)) { + throw new WSP.CodeError("MM-flags-value: invalid type " + value.type); + } + + // Calculate how much octets will be written and seek back. + // TODO: use memmove, see bug 730873 + let begin = data.offset; + EncodedStringValue.encode(data, value.text); + let len = data.offset - begin; + data.offset = begin; + + WSP.ValueLength.encode(data, len + 1); + WSP.Octet.encode(data, value.type); + EncodedStringValue.encode(data, value.text); + }, }; /** @@ -782,6 +1049,22 @@ let MmStateValue = { throw new WSP.CodeError("MM-state-value: invalid state " + state); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param state + * A numeric state value to be encoded. + * + * @throws CodeError if state is not in the range 128..132. + */ + encode: function encode(data, state) { + if ((state < 128) || (state > 132)) { + throw new WSP.CodeError("MM-state-value: invalid state " + state); + } + + WSP.Octet.encode(data, state); + }, }; /** @@ -809,6 +1092,20 @@ let PriorityValue = { throw new WSP.CodeError("Priority-value: invalid priority " + priority); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param priority + * A numeric priority value to be encoded. + */ + encode: function encode(data, priority) { + if ((priority < 128) || (priority > 130)) { + throw new WSP.CodeError("Priority-value: invalid priority " + priority); + } + + WSP.Octet.encode(data, priority); + }, }; /** @@ -856,6 +1153,64 @@ let ReplyChargingValue = { throw new WSP.CodeError("Reply-charging-value: invalid value " + value); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An integer value within thr range 128..131. + */ + encode: function encode(data, value) { + if ((value < 128) || (value > 131)) { + throw new WSP.CodeError("Reply-charging-value: invalid value " + value); + } + + WSP.Octet.encode(data, value); + }, +}; + +/** + * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf: + * + * Response-text-value = Encoded-string-value + * + * When used in the M-Mbox-Delete.conf and M-Delete.conf PDUs: + * + * Response-text-Del-value = Value-length Status-count-value Response-text-value + * + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.49 + */ +let ResponseText = { + /** + * @param data + * A wrapped object containing raw PDU data. + * @param options + * Extra context for decoding. + * + * @return An object containing a string-typed `text` attribute and a + * integer-typed `statusCount` one. + */ + 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.text = EncodedStringValue.decode(data); + + if (data.offset != end) { + data.offset = end; + } + } else { + result.text = EncodedStringValue.decode(data); + } + + return result; + }, }; /** @@ -1078,20 +1433,35 @@ let PduHelper = { }, /** - * Convert javascript Array to an nsIInputStream. + * @param data + * A wrapped object to store encoded raw data. + * @param headers + * A dictionary object containing multiple name/value mapping. + * @param name + * Name of the header field to be encoded. */ - convertArrayToInputStream: function convertDataToInputStream(array) { - let storageStream = Cc["@mozilla.org/storagestream;1"] - .createInstance(Ci.nsIStorageStream); - storageStream.init(4096, array.length, null); + encodeHeader: function encodeHeader(data, headers, name) { + let value = headers[name]; + if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + HeaderField.encode(data, {name: name, value: value[i]}, headers); + } + } else { + HeaderField.encode(data, {name: name, value: value}, headers); + } + }, - let boStream = Cc["@mozilla.org/binaryoutputstream;1"] - .createInstance(Ci.nsIBinaryOutputStream); - boStream.setOutputStream(storageStream.getOutputStream(0)); - boStream.writeByteArray(array, array.length) - boStream.close(); - - return storageStream.newInputStream(0); + /** + * @param data + * A wrapped object to store encoded raw data. + * @param headers + * A dictionary object containing multiple name/value mapping. + */ + encodeHeaderIfExists: function encodeHeaderIfExists(data, headers, name) { + // Header value could be zero or null. + if (headers[name] !== undefined) { + this.encodeHeader(data, headers, name); + } }, /** @@ -1107,26 +1477,15 @@ let PduHelper = { data = {array: [], offset: 0}; } - function encodeHeader(name) { - HeaderField.encode(data, {name: name, value: headers[name]}); - } - - function encodeHeaderIfExists(name) { - // Header value could be zero or null. - if (headers[name] !== undefined) { - encodeHeader(name); - } - } - // `In the encoding of the header fields, the order of the fields is not // significant, except that X-Mms-Message-Type, X-Mms-Transaction-ID (when // present) and X-Mms-MMS-Version MUST be at the beginning of the message // headers, in that order, and if the PDU contains a message body the // Content Type MUST be the last header field, followed by message body.` // ~ OMA-TS-MMS_ENC-V1_3-20110913-A section 7 - encodeHeader("x-mms-message-type"); - encodeHeaderIfExists("x-mms-transaction-id"); - encodeHeaderIfExists("x-mms-mms-version"); + this.encodeHeader(data, headers, "x-mms-message-type"); + this.encodeHeaderIfExists(data, headers, "x-mms-transaction-id"); + this.encodeHeaderIfExists(data, headers, "x-mms-mms-version"); for (let key in headers) { if ((key == "x-mms-message-type") @@ -1135,15 +1494,10 @@ let PduHelper = { || (key == "content-type")) { continue; } - encodeHeader(key); + this.encodeHeader(data, headers, key); } - encodeHeaderIfExists("content-type"); - - // Remove extra space consumed during encoding. - while (data.array.length > data.offset) { - data.array.pop(); - } + this.encodeHeaderIfExists(data, headers, "content-type"); return data; }, @@ -1168,8 +1522,15 @@ let PduHelper = { let data = this.encodeHeaders(null, msg.headers); debug("Composed PDU Header: " + JSON.stringify(data.array)); - let headerStream = this.convertArrayToInputStream(data.array); - multiStream.appendStream(headerStream); + WSP.PduHelper.appendArrayToMultiStream(multiStream, data.array, data.offset); + + if (msg.content) { + WSP.PduHelper.appendArrayToMultiStream(multiStream, msg.content, msg.content.length); + } else if (msg.parts) { + WSP.PduHelper.composeMultiPart(multiStream, msg.parts); + } else if (typeinfo.hasContent) { + throw new WSP.CodeError("Missing message content"); + } return multiStream; } catch (e) { @@ -1230,7 +1591,7 @@ const MMS_HEADER_FIELDS = (function () { 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-delivery-time", 0x07, ExpiryValue); add("x-mms-expiry", 0x08, ExpiryValue); add("from", 0x09, FromValue); add("x-mms-message-class", 0x0A, MessageClassValue); @@ -1241,9 +1602,9 @@ const MMS_HEADER_FIELDS = (function () { 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-response-status", 0x12, RetrieveStatusValue); + add("x-mms-response-text", 0x13, ResponseText); + add("x-mms-sender-visibility", 0x14, BooleanValue); add("x-mms-status", 0x15, StatusValue); add("subject", 0x16, EncodedStringValue); add("to", 0x17, Address); @@ -1260,8 +1621,8 @@ const MMS_HEADER_FIELDS = (function () { 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-store-status", 0x25, RetrieveStatusValue); + add("x-mms-store-status-text", 0x26, EncodedStringValue); add("x-mms-stored", 0x27, BooleanValue); //add("x-mms-attributes", 0x28); add("x-mms-totals", 0x29, BooleanValue); @@ -1344,6 +1705,7 @@ const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([ "PriorityValue", "RecommendedRetrievalModeValue", "ReplyChargingValue", + "ResponseText", "RetrieveStatusValue", "StatusValue", diff --git a/dom/mms/src/ril/WspPduHelper.jsm b/dom/mms/src/ril/WspPduHelper.jsm index 730013ff8ef..83216767075 100644 --- a/dom/mms/src/ril/WspPduHelper.jsm +++ b/dom/mms/src/ril/WspPduHelper.jsm @@ -289,6 +289,18 @@ let Octet = { data.array[data.offset++] = octet; } }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param octet + * An octet array object. + */ + encodeMultiple: function encodeMultiple(data, array) { + for (let i = 0; i < array.length; i++) { + this.encode(data, array[i]); + } + }, }; /** @@ -659,6 +671,17 @@ let QuotedString = { return NullTerminatedTexts.decode(data); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param str + * A String to be encoded. + */ + encode: function encode(data, str) { + Octet.encode(data, 34); + NullTerminatedTexts.encode(data, str); + }, }; /** @@ -757,6 +780,42 @@ let LongInteger = { return this.decodeMultiOctetInteger(data, length); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param numOrArray + * An octet array of less-equal than 30 elements or an integer + * greater-equal than 128. + */ + encode: function encode(data, numOrArray) { + if (typeof numOrArray === "number") { + let num = numOrArray; + if (num >= 0x1000000000000) { + throw new CodeError("Long-integer: number too large " + num); + } + + let stack = []; + do { + stack.push(Math.floor(num % 256)); + num = Math.floor(num / 256); + } while (num); + + Octet.encode(data, stack.length); + while (stack.length) { + Octet.encode(data, stack.pop()); + } + return; + } + + let array = numOrArray; + if ((array.length < 1) || (array.length > 30)) { + throw new CodeError("Long-integer: invalid length " + array.length); + } + + Octet.encode(data, array.length); + Octet.encodeMultiple(data, array); + }, }; /** @@ -779,6 +838,30 @@ let UintVar = { return result; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An integer value. + */ + encode: function encode(data, value) { + if (value < 0) { + throw new CodeError("UintVar: invalid value " + value); + } + + let stack = []; + while (value >= 128) { + stack.push(Math.floor(value % 128)); + value = Math.floor(value / 128); + } + + while (stack.length) { + Octet.encode(data, value | 0x80); + value = stack.pop(); + } + Octet.encode(data, value); + }, }; /** @@ -801,6 +884,20 @@ let ConstrainedEncoding = { decode: function decode(data) { return decodeAlternatives(data, null, NullTerminatedTexts, ShortInteger); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An integer or a string value. + */ + encode: function encode(data, value) { + if (typeof value == "number") { + ShortInteger.encode(data, value); + } else { + NullTerminatedTexts.encode(data, value); + } + }, }; /** @@ -832,6 +929,20 @@ let ValueLength = { throw new CodeError("Value-length: invalid value " + value); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + */ + encode: function encode(data, value) { + if (value <= 30) { + Octet.encode(data, value); + } else { + Octet.encode(data, 31); + UintVar.encode(data, value); + } + }, }; /** @@ -850,6 +961,19 @@ let NoValue = { Octet.decodeEqualTo(data, 0); return null; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * A null or undefined value. + */ + encode: function encode(data, value) { + if (value != null) { + throw new CodeError("No-value: invalid value " + value); + } + Octet.encode(data, 0); + }, }; /** @@ -867,6 +991,16 @@ let TextValue = { decode: function decode(data) { return decodeAlternatives(data, null, NoValue, TokenText, QuotedString); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param text + * A null or undefined or text string. + */ + encode: function encode(data, text) { + encodeAlternatives(data, text, null, NoValue, TokenText, QuotedString); + }, }; /** @@ -884,6 +1018,22 @@ let IntegerValue = { decode: function decode(data) { return decodeAlternatives(data, null, ShortInteger, LongInteger); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An integer value or an octet array of less-equal than 31 elements. + */ + encode: function encode(data, value) { + if (typeof value === "number") { + encodeAlternatives(data, value, null, ShortInteger, LongInteger); + } else if (Array.isArray(value) || (value instanceof Uint8Array)) { + LongInteger.encode(data, value); + } else { + throw new CodeError("Integer-Value: invalid value type"); + } + }, }; /** @@ -915,6 +1065,21 @@ let DateValue = { return new Date(seconds * 1000); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param date + * A Date object. + */ + encode: function encode(data, date) { + let seconds = date.getTime() / 1000; + if (seconds < 0) { + throw new CodeError("Date-value: negative seconds " + seconds); + } + + LongInteger.encode(data, seconds); + }, }; /** @@ -954,6 +1119,27 @@ let QValue = { throw new CodeError("Q-value: invalid value " + value); }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An integer within the range 1..1099. + */ + encode: function encode(data, value) { + if ((value < 0) || (value >= 1)) { + throw new CodeError("Q-value: invalid value " + value); + } + + value *= 1000; + if ((value % 10) == 0) { + // Two digits only. + UintVar.encode(data, Math.floor(value / 10 + 1)); + } else { + // Three digits. + UintVar.encode(data, Math.floor(value + 100)); + } + }, }; /** @@ -1007,6 +1193,20 @@ let VersionValue = { return major << 4 | minor; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param version + * A binary encoded version number. + */ + encode: function encode(data, version) { + if ((version < 0x10) || (version >= 0x80)) { + throw new CodeError("Version-value: invalid version " + version); + } + + ShortInteger.encode(data, version); + }, }; /** @@ -1067,6 +1267,21 @@ let TypeValue = { return entry.type; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param type + * A content type string. + */ + encode: function encode(data, type) { + let entry = WSP_WELL_KNOWN_CONTENT_TYPES[type.toLowerCase()]; + if (entry) { + ShortInteger.encode(data, entry.number); + } else { + NullTerminatedTexts.encode(data, type); + } + }, }; /** @@ -1220,6 +1435,63 @@ let Parameter = { return params; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param param + * An object containing `name` and `value` properties. + */ + encodeTypedParameter: function decodeTypedParameter(data, param) { + let entry = WSP_WELL_KNOWN_PARAMS[param.name.toLowerCase()]; + if (!entry) { + throw new NotWellKnownEncodingError( + "Typed-parameter: not well known parameter " + param.name); + } + + IntegerValue.encode(data, entry.number); + encodeAlternatives(data, param.value, null, + entry.coder, TextValue, TextString); + }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param param + * An object containing `name` and `value` properties. + */ + encodeUntypedParameter: function encodeUntypedParameter(data, param) { + TokenText.encode(data, param.name); + encodeAlternatives(data, param.value, null, IntegerValue, TextValue); + }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param param + * An array of parameter objects. + */ + encodeMultiple: function encodeMultiple(data, params) { + for (let name in params) { + this.encode(data, {name: name, value: params[name]}); + } + }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param param + * An object containing `name` and `value` properties. + */ + encode: function encode(data, param) { + let begin = data.offset; + try { + this.encodeTypedParameter(data, param); + } catch (e) { + data.offset = begin; + this.encodeUntypedParameter(data, param); + } + }, }; /** @@ -1255,6 +1527,22 @@ let Header = { // TODO: support header code page shift-sequence return this.decodeMessageHeader(data); }, + + encodeMessageHeader: function encodeMessageHeader(data, header) { + encodeAlternatives(data, header, null, WellKnownHeader, ApplicationHeader); + }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param header + * An object containing two attributes: a string-typed `name` and a + * `value` of arbitrary type. + */ + encode: function encode(data, header) { + // TODO: support header code page shift-sequence + this.encodeMessageHeader(data, header); + }, }; /** @@ -1303,6 +1591,24 @@ let WellKnownHeader = { value: value, }; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param header + * An object containing two attributes: a string-typed `name` and a + * `value` of arbitrary type. + */ + encode: function encode(data, header) { + let entry = WSP_HEADER_FIELDS[header.name.toLowerCase()]; + if (!entry) { + throw new NotWellKnownEncodingError( + "Well-known-header: not well known header " + header.name); + } + + ShortInteger.encode(data, entry.number); + encodeAlternatives(data, header.value, null, entry.coder, TextValue); + }, }; /** @@ -1395,6 +1701,21 @@ let FieldName = { return entry.name; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param name + * A field name string. + */ + encode: function encode(data, name) { + let entry = WSP_HEADER_FIELDS[name.toLowerCase()]; + if (entry) { + ShortInteger.encode(data, entry.number); + } else { + TokenText.encode(data, name); + } + }, }; /** @@ -1498,6 +1819,21 @@ let AcceptCharsetValue = { return this.decodeAcceptCharsetGeneralForm(data); } }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An object with a string property `charset`. + */ + encodeAnyCharset: function encodeAnyCharset(data, value) { + if (!value || !value.charset || (value.charset === "*")) { + Octet.encode(data, 128); + return; + } + + throw new CodeError("Any-charset: invalid value " + value); + }, }; /** @@ -1540,6 +1876,28 @@ let WellKnownCharset = { return {charset: entry.name}; }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + */ + encode: function encode(data, value) { + let begin = data.offset; + try { + AcceptCharsetValue.encodeAnyCharset(data, value); + return; + } catch (e) {} + + data.offset = begin; + let entry = WSP_WELL_KNOWN_CHARSETS[value.charset.toLowerCase()]; + if (!entry) { + throw new NotWellKnownEncodingError( + "Well-known-charset: not well known charset " + value.charset); + } + + IntegerValue.encode(data, entry.number); + }, }; /** @@ -1670,6 +2028,73 @@ let ContentTypeValue = { return this.decodeContentGeneralForm(data); } }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An object containing `media` and `params` properties. + */ + encodeConstrainedMedia: function encodeConstrainedMedia(data, value) { + if (value.params) { + throw new CodeError("Constrained-media: should use general form instread"); + } + + TypeValue.encode(data, value.media); + }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An object containing `media` and `params` properties. + */ + encodeMediaType: function encodeMediaType(data, value) { + let entry = WSP_WELL_KNOWN_CONTENT_TYPES[value.media.toLowerCase()]; + if (entry) { + IntegerValue.encode(data, entry.number); + } else { + NullTerminatedTexts.encode(data, value.media); + } + + Parameter.encodeMultiple(data, value.params); + }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An object containing `media` and `params` properties. + */ + encodeContentGeneralForm: function encodeContentGeneralForm(data, value) { + let begin = data.offset; + this.encodeMediaType(data, value); + + // Calculate how much octets will be written and seek back. + // TODO: use memmove, see bug 730873 + let len = data.offset - begin; + data.offset = begin; + + ValueLength.encode(data, len); + this.encodeMediaType(data, value); + }, + + /** + * @param data + * A wrapped object to store encoded raw data. + * @param value + * An object containing `media` and `params` properties. + */ + encode: function encode(data, value) { + let begin = data.offset; + + try { + this.encodeConstrainedMedia(data, value); + } catch (e) { + data.offset = begin; + this.encodeContentGeneralForm(data, value); + } + }, }; /** @@ -1869,6 +2294,85 @@ let PduHelper = { return msg; }, + + /** + * @param multiStream + * An exsiting nsIMultiplexInputStream. + * @param array + * An octet array. + * @param length + * Max number of octets to be coverted into an input stream. + */ + appendArrayToMultiStream: function appendArrayToMultiStream(multiStream, array, length) { + let storageStream = Cc["@mozilla.org/storagestream;1"] + .createInstance(Ci.nsIStorageStream); + storageStream.init(4096, length, null); + + let boStream = Cc["@mozilla.org/binaryoutputstream;1"] + .createInstance(Ci.nsIBinaryOutputStream); + boStream.setOutputStream(storageStream.getOutputStream(0)); + boStream.writeByteArray(array, length); + boStream.close(); + + multiStream.appendStream(storageStream.newInputStream(0)); + }, + + /** + * @param multiStream + * An exsiting nsIMultiplexInputStream. + * @param parts + * An array of objects representing multipart entries. + * + * @see WAP-230-WSP-20010705-a section 8.5 + */ + composeMultiPart: function composeMultiPart(multiStream, parts) { + // Encode multipart header + { + let data = {array: [], offset: 0}; + UintVar.encode(data, parts.length); + debug("Encoded multipart header: " + JSON.stringify(data.array)); + this.appendArrayToMultiStream(multiStream, data.array, data.offset); + } + + // Encode each part + for (let i = 0; i < parts.length; i++) { + let part = parts[i]; + let data = {array: [], offset: 0}; + + // Encode Content-Type + let contentType = part.headers["content-type"]; + ContentTypeValue.encode(data, contentType); + + // Encode other headers + if (Object.keys(part).length > 1) { + // Remove Content-Type temporarily + delete part.headers["content-type"]; + + for (let name in part.headers) { + Header.encode(data, {name: name, value: part.headers[name]}); + } + + // Restore Content-Type back + part.headers["content-type"] = contentType; + } + + // Encode headersLen, DataLen + let headersLen = data.offset; + UintVar.encode(data, headersLen); + UintVar.encode(data, part.content.length); + + // Move them to the beginning of encoded octet array. + let slice1 = data.array.slice(headersLen); + let slice2 = data.array.slice(0, headersLen); + data.array = slice1.concat(slice2); + debug("Encoded per-part header: " + JSON.stringify(data.array)); + + // Append per-part header + this.appendArrayToMultiStream(multiStream, data.array, data.offset); + // Append part content + this.appendArrayToMultiStream(multiStream, part.content, part.content.length); + } + }, }; // WSP Header Field Name Assignments diff --git a/dom/mms/src/ril/mms_consts.js b/dom/mms/src/ril/mms_consts.js index 313d4000cea..1ee179e8b41 100644 --- a/dom/mms/src/ril/mms_consts.js +++ b/dom/mms/src/ril/mms_consts.js @@ -38,14 +38,51 @@ const MMS_PDU_ERROR_OK = 128; const MMS_PDU_ERROR_TRANSIENT_FAILURE = 192; const MMS_PDU_ERROR_PERMANENT_FAILURE = 224; +// X-Mms-Response-Status values +// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.48 +// @see OMA-TS-MMS_ENC-V1_3-20110913-A Table 28, 29, 30 +//const MMS_PDU_RESPONSE_ERROR_UNSPECIFIED = 129; (obsolete) +//const MMS_PDU_RESPONSE_ERROR_SERVICE_DENIED = 130; (obsolete) +//const MMS_PDU_RESPONSE_ERROR_MESSAGE_FORMAT_CORRUPT = 131; (obsolete) +//const MMS_PDU_RESPONSE_ERROR_SENDING_ADDRESS_UNRESOLVED = 132; (obsolete) +//const MMS_PDU_RESPONSE_ERROR_MESSAGE_NOT_FOUND = 133; (obsolete) +//const MMS_PDU_RESPONSE_ERROR_NETWORK_PROBLEM = 134; (obsolete) +//const MMS_PDU_RESPONSE_ERROR_CONTENT_NOT_ACCEPTED = 135; (obsolete) +const MMS_PDU_RESPONSE_ERROR_UNSUPPORTED_MESSAGE = 136; +const MMS_PDU_RESPONSE_ERROR_TRANSIENT_SENDING_ADDRESS_UNRESOLVED = 193; +const MMS_PDU_RESPONSE_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 194; +const MMS_PDU_RESPONSE_ERROR_TRANSIENT_NETWORK_PROBLEM = 195; +const MMS_PDU_RESPONSE_ERROR_TRANSIENT_PARTIAL_SUCCESS = 196; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_SERVICE_DENIED = 225; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 226; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED = 227; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 228; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED = 229; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET = 230; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 231; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED = 232; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED = 233; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED = 234; +const MMS_PDU_RESPONSE_ERROR_PERMANENT_LACK_OF_PREPAID = 235; + // X-Mms-Retrieve-Status values // @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.50 +// @see OMA-TS-MMS_ENC-V1_3-20110913-A Table 31 const MMS_PDU_RETRIEVE_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 193; const MMS_PDU_RETRIEVE_ERROR_TRANSIENT_NETWORK_PROBLEM = 194; const MMS_PDU_RETRIEVE_ERROR_PERMANENT_SERVICE_DENIED = 225; const MMS_PDU_RETRIEVE_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 226; const MMS_PDU_RETRIEVE_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 227; +// X-Mms-Store-Status values +// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.58 +// @see OMA-TS-MMS_ENC-V1_3-20110913-A Table 35 +const MMS_PDU_STORE_ERROR_TRANSIENT_NETWORK_PROBLEM = 193; +const MMS_PDU_STORE_ERROR_PERMANENT_SERVICE_DENIED = 225; +const MMS_PDU_STORE_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 226; +const MMS_PDU_STORE_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 227; +const MMS_PDU_STORE_ERROR_PERMANENT_MMBOX_FULL = 228; + // X-Mms-Status values // @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.54 const MMS_PDU_STATUS_EXPIRED = 128; diff --git a/dom/mms/tests/test_mms_pdu_helper.js b/dom/mms/tests/test_mms_pdu_helper.js index 837b2fb1361..dc46080066f 100644 --- a/dom/mms/tests/test_mms_pdu_helper.js +++ b/dom/mms/tests/test_mms_pdu_helper.js @@ -81,6 +81,42 @@ add_test(function test_Address_decode() { run_next_test(); }); +//// Address.encode //// + +add_test(function test_Address_encode() { + // Test for PLMN address + wsp_encode_test(MMS.Address, {address: "+123.456-789", type: "PLMN"}, + strToCharCodeArray("+123.456-789/TYPE=PLMN")); + wsp_encode_test(MMS.Address, {address: "123456789", type: "PLMN"}, + strToCharCodeArray("123456789/TYPE=PLMN")); + // Test for IPv4 + wsp_encode_test(MMS.Address, {address: "1.2.3.4", type: "IPv4"}, + strToCharCodeArray("1.2.3.4/TYPE=IPv4")); + // Test for IPv6 + wsp_encode_test(MMS.Address, + {address: "1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000", type: "IPv6"}, + strToCharCodeArray("1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000/TYPE=IPv6") + ); + // Test for other device-address + wsp_encode_test(MMS.Address, {address: "+H-e.l%l_o", type: "W0r1d_"}, + strToCharCodeArray("+H-e.l%l_o/TYPE=W0r1d_")); + // Test for num-shortcode + wsp_encode_test(MMS.Address, {address: "+123", type: "num"}, + strToCharCodeArray("+123")); + wsp_encode_test(MMS.Address, {address: "*123", type: "num"}, + strToCharCodeArray("*123")); + wsp_encode_test(MMS.Address, {address: "#123", type: "num"}, + strToCharCodeArray("#123")); + // Test for alphanum-shortcode + wsp_encode_test(MMS.Address, {address: "H0wD0Y0uTurnTh1s0n", type: "alphanum"}, + strToCharCodeArray("H0wD0Y0uTurnTh1s0n")); + // Test for email address + wsp_encode_test(MMS.Address, {address: "Joe User ", type: "email"}, + strToCharCodeArray("Joe User ")); + + run_next_test(); +}); + // // Test target: HeaderField // @@ -159,6 +195,20 @@ add_test(function test_ContentClassValue_decode() { run_next_test(); }); +//// ContentClassValue.encode //// + +add_test(function test_ContentClassValue_encode() { + for (let i = 0; i < 256; i++) { + if ((i >= 128) && (i <= 135)) { + wsp_encode_test(MMS.ContentClassValue, i, [i]); + } else { + wsp_encode_test(MMS.ContentClassValue, i, null, "CodeError"); + } + } + + run_next_test(); +}); + // // Test target: ContentLocationValue // @@ -266,6 +316,24 @@ add_test(function test_Parameter_decodeMultiple() { run_next_test(); }); +//// Parameter.encode //// + +add_test(function test_Parameter_encode() { + // Test for invalid parameter value + wsp_encode_test(MMS.Parameter, null, null, "CodeError"); + wsp_encode_test(MMS.Parameter, undefined, null, "CodeError"); + wsp_encode_test(MMS.Parameter, {}, null, "CodeError"); + // Test for case-insensitive parameter name + wsp_encode_test(MMS.Parameter, {name: "TYPE", value: 0}, [130, 128]); + wsp_encode_test(MMS.Parameter, {name: "type", value: 0}, [130, 128]); + // Test for non-well-known parameter name + wsp_encode_test(MMS.Parameter, {name: "name", value: 0}, [110, 97, 109, 101, 0, 128]); + // Test for constrained encoding value + wsp_encode_test(MMS.Parameter, {name: "type", value: "0"}, [130, 48, 0]); + + run_next_test(); +}); + // // Test target: EncodedStringValue // @@ -294,6 +362,28 @@ add_test(function test_EncodedStringValue_decode() { run_next_test(); }); +//// EncodedStringValue.encode //// + +add_test(function test_EncodedStringValue_encode() { + // Test for normal TextString + wsp_encode_test(MMS.EncodedStringValue, "Hello", strToCharCodeArray("Hello")); + // 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 = conv.convertToByteArray(str).concat([0]); + wsp_encode_test(MMS.EncodedStringValue, str, + [raw.length + 2, 0x80 | entry.number, 127].concat(raw)); + } + + run_next_test(); +}); + // // Test target: ExpiryValue // @@ -309,6 +399,17 @@ add_test(function test_ExpiryValue_decode() { run_next_test(); }); +//// ExpiryValue.encode //// + +add_test(function test_ExpiryValue_encode() { + // Test for Absolute-token Date-value + wsp_encode_test(MMS.ExpiryValue, new Date(0x80 * 1000), [3, 128, 1, 0x80]); + // Test for Relative-token Delta-seconds-value + wsp_encode_test(MMS.ExpiryValue, 0, [2, 129, 0x80]); + + run_next_test(); +}); + // // Test target: PreviouslySentByValue // @@ -354,6 +455,20 @@ add_test(function test_FromValue_decode() { run_next_test(); }); +//// FromValue.encode //// + +add_test(function test_FromValue_encode() { + // Test for Insert-address-token: + wsp_encode_test(MMS.FromValue, null, [1, 129]); + // Test for Address-present-token: + let (addr = strToCharCodeArray("+123/TYPE=PLMN")) { + wsp_encode_test(MMS.FromValue, {address: "+123", type: "PLMN"}, + [addr.length + 1, 128].concat(addr)); + } + + run_next_test(); +}); + // // Test target: MessageClassValue // @@ -389,6 +504,18 @@ add_test(function test_MessageClassValue_decode() { run_next_test(); }); +//// MessageClassValue.encode //// + +add_test(function test_MessageClassValue_encode() { + wsp_encode_test(MMS.MessageClassValue, "personal", [128]); + wsp_encode_test(MMS.MessageClassValue, "advertisement", [129]); + wsp_encode_test(MMS.MessageClassValue, "informational", [130]); + wsp_encode_test(MMS.MessageClassValue, "auto", [131]); + wsp_encode_test(MMS.MessageClassValue, "A", [65, 0]); + + run_next_test(); +}); + // // Test target: MessageTypeValue // @@ -439,6 +566,20 @@ add_test(function test_MmFlagsValue_decode() { run_next_test(); }); +//// MmFlagsValue.encode //// + +add_test(function test_MmFlagsValue_encode() { + for (let i = 0; i < 256; i++) { + if ((i >= 128) && (i <= 130)) { + wsp_encode_test(MMS.MmFlagsValue, {type: i, text: "A"}, [3, i, 65, 0]); + } else { + wsp_encode_test(MMS.MmFlagsValue, {type: i, text: "A"}, null, "CodeError"); + } + } + + run_next_test(); +}); + // // Test target: MmStateValue // @@ -457,6 +598,20 @@ add_test(function test_MmStateValue_decode() { run_next_test(); }); +//// MmStateValue.encode //// + +add_test(function test_MmStateValue_encode() { + for (let i = 0; i < 256; i++) { + if ((i >= 128) && (i <= 132)) { + wsp_encode_test(MMS.MmStateValue, i, [i]); + } else { + wsp_encode_test(MMS.MmStateValue, i, null, "CodeError"); + } + } + + run_next_test(); +}); + // // Test target: PriorityValue // @@ -475,6 +630,20 @@ add_test(function test_PriorityValue_decode() { run_next_test(); }); +//// PriorityValue.encode //// + +add_test(function test_PriorityValue_encode() { + for (let i = 0; i < 256; i++) { + if ((i >= 128) && (i <= 130)) { + wsp_encode_test(MMS.PriorityValue, i, [i]); + } else { + wsp_encode_test(MMS.PriorityValue, i, null, "CodeError"); + } + } + + run_next_test(); +}); + // // Test target: RecommendedRetrievalModeValue // @@ -511,6 +680,58 @@ add_test(function test_ReplyChargingValue_decode() { run_next_test(); }); +//// ReplyChargingValue.encode //// + +add_test(function test_ReplyChargingValue_encode() { + for (let i = 0; i < 256; i++) { + if ((i >= 128) && (i <= 131)) { + wsp_encode_test(MMS.ReplyChargingValue, i, [i]); + } else { + wsp_encode_test(MMS.ReplyChargingValue, i, null, "CodeError"); + } + } + + run_next_test(); +}); + +// +// Test target: ResponseText +// + +//// ResponseText.decode //// + +add_test(function test_ResponseText_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_PDU_TYPE_MBOX_DELETE_CONF; + return MMS.ResponseText.decode(data, options); + }, [0, 0x80 | 0x00].concat(strToCharCodeArray("http://no.such.com/path")), + {statusCount: 0, text: "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_PDU_TYPE_DELETE_CONF; + return MMS.ResponseText.decode(data, options); + }, [0, 0x80 | 0x00].concat(strToCharCodeArray("http://no.such.com/path")), + {statusCount: 0, text: "http://no.such.com/path"} + ); + // Test for other situations + wsp_decode_test_ex(function (data) { + let options = {}; + options["x-mms-message-type"] = MMS_PDU_TYPE_SEND_REQ; + return MMS.ResponseText.decode(data, options); + }, strToCharCodeArray("http://no.such.com/path"), + {text: "http://no.such.com/path"} + ); + + run_next_test(); +}); + // // Test target: RetrieveStatusValue // @@ -585,7 +806,7 @@ add_test(function test_PduHelper_parseHeaders() { }; parse([0x80 | 0x0D, 0x80 | MMS_VERSION, // X-Mms-Mms-Version: 1.3 0x80 | 0x04, 0x80 | 0x33, // Content-Type: application/vnd.wap.multipart.related - 0x80 | 0x0C, MMS_PDU_TYPE_SEND_REQ // X-Mms-Message-Type: M-Send.req + 0x80 | 0x0C, MMS_PDU_TYPE_SEND_REQ // X-Mms-Message-Type: M-Send.req ], expect); // Parse header fields with multiple entries @@ -606,3 +827,83 @@ add_test(function test_PduHelper_parseHeaders() { run_next_test(); }); + +//// PduHelper.encodeHeader //// + +add_test(function test_PduHelper_encodeHeader() { + function func(name, data, headers) { + MMS.PduHelper.encodeHeader(data, headers, name); + + // Remove extra space consumed during encoding. + while (data.array.length > data.offset) { + data.array.pop(); + } + + return data.array; + } + + // Encode header fields with multiple entries + let headers = { + to: [ + { address: "+123", type: "PLMN" }, + { address: "+456", type: "num" }, + ], + }; + wsp_encode_test_ex(func.bind(null, "to"), headers, + Array.concat([0x80 | 0x17]).concat(strToCharCodeArray("+123/TYPE=PLMN")) + .concat([0x80 | 0x17]).concat(strToCharCodeArray("+456"))); + + run_next_test(); +}); + +//// PduHelper.encodeHeaderIfExists //// + +add_test(function test_PduHelper_encodeHeaderIfExists() { + function func(name, data, headers) { + MMS.PduHelper.encodeHeaderIfExists(data, headers, name); + + // Remove extra space consumed during encoding. + while (data.array.length > data.offset) { + data.array.pop(); + } + + return data.array; + } + + wsp_encode_test_ex(func.bind(null, "to"), {}, []); + + run_next_test(); +}); + +//// PduHelper.encodeHeaders //// + +add_test(function test_PduHelper_encodeHeaders() { + function func(data, headers) { + MMS.PduHelper.encodeHeaders(data, headers); + + // Remove extra space consumed during encoding. + while (data.array.length > data.offset) { + data.array.pop(); + } + + return data.array; + } + + let headers = {}; + headers["x-mms-message-type"] = MMS_PDU_TYPE_SEND_REQ; + headers["x-mms-mms-version"] = MMS_VERSION; + headers["x-mms-transaction-id"] = "asdf"; + headers["to"] = { address: "+123", type: "PLMN" }; + headers["content-type"] = { + media: "application/vnd.wap.multipart.related", + }; + wsp_encode_test_ex(func, headers, + Array.concat([0x80 | 0x0C, MMS_PDU_TYPE_SEND_REQ]) + .concat([0x80 | 0x18]).concat(strToCharCodeArray(headers["x-mms-transaction-id"])) + .concat([0x80 | 0x0D, 0x80 | MMS_VERSION]) + .concat([0x80 | 0x17]).concat(strToCharCodeArray("+123/TYPE=PLMN")) + .concat([0x80 | 0x04, 0x80 | 0x33])); + + run_next_test(); +}); + diff --git a/dom/mms/tests/test_wsp_pdu_helper.js b/dom/mms/tests/test_wsp_pdu_helper.js index f487357c7c9..7cba250ca40 100644 --- a/dom/mms/tests/test_wsp_pdu_helper.js +++ b/dom/mms/tests/test_wsp_pdu_helper.js @@ -107,6 +107,17 @@ add_test(function test_Octet_encode() { run_next_test(); }); +//// Octet.encodeMultiple //// + +add_test(function test_Octet_encodeMultiple() { + wsp_encode_test_ex(function (data, input) { + WSP.Octet.encodeMultiple(data, input); + return data.array; + }, [0, 1, 2, 3], [0, 1, 2, 3]); + + run_next_test(); +}); + // // Test target: Text // @@ -303,6 +314,14 @@ add_test(function test_QuotedString_decode() { run_next_test(); }); +//// QuotedString.encode //// + +add_test(function test_QuotedString_encode() { + wsp_encode_test(WSP.QuotedString, "B2G", [34].concat(strToCharCodeArray("B2G"))); + + run_next_test(); +}); + // // Test target: ShortInteger // @@ -341,7 +360,7 @@ add_test(function test_ShortInteger_encode() { //// LongInteger.decode //// -function LongInteger_testcases(target) { +function LongInteger_decode_testcases(target) { // Test LongInteger of zero octet wsp_decode_test(target, [0, 0], null, "CodeError"); wsp_decode_test(target, [1, 0x80], 0x80); @@ -356,7 +375,38 @@ function LongInteger_testcases(target) { wsp_decode_test(target, [31], null, "CodeError"); } add_test(function test_LongInteger_decode() { - LongInteger_testcases(WSP.LongInteger); + LongInteger_decode_testcases(WSP.LongInteger); + + run_next_test(); +}); + +//// LongInteger.encode //// + +function LongInteger_encode_testcases(target) { + wsp_encode_test(target, 0x80, [1, 0x80]); + wsp_encode_test(target, 0x8002, [2, 0x80, 2]); + wsp_encode_test(target, 0x800203, [3, 0x80, 2, 3]); + wsp_encode_test(target, 0x80020304, [4, 0x80, 2, 3, 4]); + wsp_encode_test(target, 0x8002030405, [5, 0x80, 2, 3, 4, 5]); + wsp_encode_test(target, 0x800203040506, [6, 0x80, 2, 3, 4, 5, 6]); + // Test LongInteger of more than 6 octets + wsp_encode_test(target, 0x1000000000000, null, "CodeError"); + // Test input empty array + wsp_encode_test(target, [], null, "CodeError"); + // Test input octets array of length 1..30 + let array = []; + for (let i = 1; i <= 30; i++) { + array.push(i); + wsp_encode_test(target, array, [i].concat(array)); + } + // Test input octets array of 31 elements. + array.push(31); + wsp_encode_test(target, array, null, "CodeError"); +} +add_test(function test_LongInteger_encode() { + wsp_encode_test(WSP.LongInteger, 0, [1, 0]); + + LongInteger_encode_testcases(WSP.LongInteger); run_next_test(); }); @@ -385,8 +435,25 @@ add_test(function test_UintVar_decode() { run_next_test(); }); +//// UintVar.encode //// + +add_test(function test_UintVar_encode() { + // Test up to max 53 bits integer + wsp_encode_test(WSP.UintVar, 0, [0]); + wsp_encode_test(WSP.UintVar, 0x7F, [0x7F]); + wsp_encode_test(WSP.UintVar, 0x3FFF, [0xFF, 0x7F]); + wsp_encode_test(WSP.UintVar, 0x1FFFFF, [0xFF, 0xFF, 0x7F]); + wsp_encode_test(WSP.UintVar, 0xFFFFFFF, [0xFF, 0xFF, 0xFF, 0x7F]); + wsp_encode_test(WSP.UintVar, 0x7FFFFFFFF, [0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); + wsp_encode_test(WSP.UintVar, 0x3FFFFFFFFFF, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); + wsp_encode_test(WSP.UintVar, 0x1FFFFFFFFFFFF, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); + wsp_encode_test(WSP.UintVar, 0x1FFFFFFFFFFFFF, [0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); + + run_next_test(); +}); + // -// Test target: ConstrainedEncoding (decodeAlternatives) +// Test target: ConstrainedEncoding // //// ConstrainedEncoding.decode //// @@ -398,6 +465,15 @@ add_test(function test_ConstrainedEncoding_decode() { run_next_test(); }); +//// ConstrainedEncoding.encode //// + +add_test(function test_ConstrainedEncoding_encode() { + wsp_encode_test(WSP.ConstrainedEncoding, 0, [0x80 | 0]); + wsp_encode_test(WSP.ConstrainedEncoding, "A", [65, 0]); + + run_next_test(); +}); + // // Test target: ValueLength // @@ -418,6 +494,22 @@ add_test(function test_ValueLength_decode() { run_next_test(); }); +//// ValueLength.encode //// + +add_test(function test_ValueLength_encode() { + for (let i = 0; i < 256; i++) { + if (i < 31) { + wsp_encode_test(WSP.ValueLength, i, [i]); + } else if (i < 128) { + wsp_encode_test(WSP.ValueLength, i, [31, i]); + } else { + wsp_encode_test(WSP.ValueLength, i, [31, (0x80 | (i / 128)), i % 128]); + } + } + + run_next_test(); +}); + // // Test target: NoValue // @@ -433,6 +525,19 @@ add_test(function test_NoValue_decode() { run_next_test(); }); +//// NoValue.encode //// + +add_test(function test_NoValue_encode() { + wsp_encode_test(WSP.NoValue, undefined, [0]); + wsp_encode_test(WSP.NoValue, null, [0]); + wsp_encode_test(WSP.NoValue, 0, null, "CodeError"); + wsp_encode_test(WSP.NoValue, "", null, "CodeError"); + wsp_encode_test(WSP.NoValue, [], null, "CodeError"); + wsp_encode_test(WSP.NoValue, {}, null, "CodeError"); + + run_next_test(); +}); + // // Test target: TextValue // @@ -448,6 +553,18 @@ add_test(function test_TextValue_decode() { run_next_test(); }); +//// TextValue.encode //// + +add_test(function test_TextValue_encode() { + wsp_encode_test(WSP.TextValue, undefined, [0]); + wsp_encode_test(WSP.TextValue, null, [0]); + wsp_encode_test(WSP.TextValue, "", [0]); + wsp_encode_test(WSP.TextValue, "A", [65, 0]); + wsp_encode_test(WSP.TextValue, "\x80", [34, 128, 0]); + + run_next_test(); +}); + // // Test target: IntegerValue // @@ -459,7 +576,19 @@ add_test(function test_IntegerValue_decode() { wsp_decode_test(WSP.IntegerValue, [i], i & 0x7F); } - LongInteger_testcases(WSP.IntegerValue); + LongInteger_decode_testcases(WSP.IntegerValue); + + run_next_test(); +}); + +//// IntegerValue.decode //// + +add_test(function test_IntegerValue_encode() { + for (let i = 0; i < 128; i++) { + wsp_encode_test(WSP.IntegerValue, i, [0x80 | i]); + } + + LongInteger_encode_testcases(WSP.IntegerValue); run_next_test(); }); @@ -478,6 +607,14 @@ add_test(function test_DateValue_decode() { run_next_test(); }); +//// DateValue.encode //// + +add_test(function test_DateValue_encode() { + wsp_encode_test(WSP.DateValue, new Date(0x80 * 1000), [1, 0x80]); + + run_next_test(); +}); + // // Test target: DeltaSecondsValue // @@ -500,6 +637,18 @@ add_test(function test_QValue_decode() { run_next_test(); }); +//// QValue.encode //// + +add_test(function test_QValue_encode() { + wsp_encode_test(WSP.QValue, 0, [1]); + wsp_encode_test(WSP.QValue, 0.99, [100]); + wsp_encode_test(WSP.QValue, 0.001, [101]); + wsp_encode_test(WSP.QValue, 0.999, [0x88, 0x4B]); + wsp_encode_test(WSP.QValue, 1, null, "CodeError"); + + run_next_test(); +}); + // // Test target: VersionValue // @@ -526,6 +675,22 @@ add_test(function test_VersionValue_decode() { run_next_test(); }); +//// VersionValue.encode //// + +add_test(function test_VersionValue_encode() { + for (let major = 1; major < 8; major++) { + let version = (major << 4) | 0x0F; + wsp_encode_test(WSP.VersionValue, version, [0x80 | version]); + + for (let minor = 0; minor < 15; minor++) { + version = (major << 4) | minor; + wsp_encode_test(WSP.VersionValue, version, [0x80 | version]); + } + } + + run_next_test(); +}); + // // Test target: UriValue // @@ -558,6 +723,17 @@ add_test(function test_TypeValue_decode() { run_next_test(); }); +//// TypeValue.encode //// + +add_test(function test_TypeValue_encode() { + wsp_encode_test(WSP.TypeValue, "no/such.type", + [110, 111, 47, 115, 117, 99, 104, 46, 116, 121, 112, 101, 0]); + wsp_encode_test(WSP.TypeValue, "application/vnd.wap.multipart.related", + [0x33 | 0x80]); + + run_next_test(); +}); + // // Test target: Parameter // @@ -627,6 +803,59 @@ add_test(function test_Parameter_decodeMultiple() { run_next_test(); }); +//// Parameter.encodeTypedParameter //// + +add_test(function test_Parameter_encodeTypedParameter() { + function func(data, input) { + WSP.Parameter.encodeTypedParameter(data, input); + return data.array; + } + + // Test for NotWellKnownEncodingError + wsp_encode_test_ex(func, {name: "xxx", value: 0}, null, "NotWellKnownEncodingError"); + wsp_encode_test_ex(func, {name: "q", value: 0}, [0x80, 1]); + wsp_encode_test_ex(func, {name: "name", value: "A"}, [0x85, 65, 0]); + + run_next_test(); +}); + +//// Parameter.encodeUntypedParameter //// + +add_test(function test_Parameter_encodeUntypedParameter() { + function func(data, input) { + WSP.Parameter.encodeUntypedParameter(data, input); + return data.array; + } + + wsp_encode_test_ex(func, {name: "q", value: 0}, [113, 0, 0x80]); + wsp_encode_test_ex(func, {name: "name", value: "A"}, [110, 97, 109, 101, 0, 65, 0]); + + run_next_test(); +}); + +//// Parameter.encodeMultiple //// + +add_test(function test_Parameter_encodeMultiple() { + function func(data, input) { + WSP.Parameter.encodeMultiple(data, input); + return data.array; + } + + wsp_encode_test_ex(func, {q: 0, n: "A"}, [0x80, 1, 110, 0, 65, 0]); + + run_next_test(); +}); + +//// Parameter.encode //// + +add_test(function test_Parameter_encode() { + + wsp_encode_test(WSP.Parameter, {name: "q", value: 0}, [0x80, 1]); + wsp_encode_test(WSP.Parameter, {name: "n", value: "A"}, [110, 0, 65, 0]); + + run_next_test(); +}); + // // Test target: Header // @@ -722,6 +951,15 @@ add_test(function test_FieldName_decode() { run_next_test(); }); +//// FieldName.encode //// + +add_test(function test_FieldName_encode() { + wsp_encode_test(WSP.FieldName, "", [0]); + wsp_encode_test(WSP.FieldName, "date", [0x92]); + + run_next_test(); +}); + // // Test target: AcceptCharsetValue // @@ -748,6 +986,24 @@ add_test(function test_AcceptCharsetValue_decode() { run_next_test(); }); +//// AcceptCharsetValue.encodeAnyCharset //// + +add_test(function test_AcceptCharsetValue_encodeAnyCharset() { + function func(data, input) { + WSP.AcceptCharsetValue.encodeAnyCharset(data, input); + return data.array; + } + + wsp_encode_test_ex(func, null, [0x80]); + wsp_encode_test_ex(func, undefined, [0x80]); + wsp_encode_test_ex(func, {}, [0x80]); + wsp_encode_test_ex(func, {charset: null}, [0x80]); + wsp_encode_test_ex(func, {charset: "*"}, [0x80]); + wsp_encode_test_ex(func, {charset: "en"}, null, "CodeError"); + + run_next_test(); +}); + // // Test target: WellKnownCharset // @@ -766,6 +1022,16 @@ add_test(function test_WellKnownCharset_decode() { run_next_test(); }); +//// WellKnownCharset.encode //// + +add_test(function test_WellKnownCharset_encode() { + // Test for Any-charset + wsp_encode_test(WSP.WellKnownCharset, {charset: "*"}, [0x80]); + wsp_encode_test(WSP.WellKnownCharset, {charset: "UTF-8"}, [128 + 106]); + + run_next_test(); +}); + // // Test target: ContentTypeValue // @@ -848,6 +1114,70 @@ add_test(function test_ContentTypeValue_decode() { run_next_test(); }); +//// ContentTypeValue.encodeConstrainedMedia //// + +add_test(function test_ContentTypeValue_encodeConstrainedMedia() { + function func(data, input) { + WSP.ContentTypeValue.encodeConstrainedMedia(data, input); + return data.array; + } + + // Test media type with additional parameters. + wsp_encode_test_ex(func, {media: "a", params: [{a: "b"}]}, null, "CodeError"); + wsp_encode_test_ex(func, {media: "no/such.type"}, + [110, 111, 47, 115, 117, 99, 104, 46, 116, 121, 112, 101, 0]); + wsp_encode_test_ex(func, {media: "application/vnd.wap.multipart.related"}, + [0x33 | 0x80]); + + run_next_test(); +}); + +//// ContentTypeValue.encodeMediaType //// + +add_test(function test_ContentTypeValue_encodeMediaType() { + function func(data, input) { + WSP.ContentTypeValue.encodeMediaType(data, input); + return data.array; + } + + wsp_encode_test_ex(func, {media: "no/such.type"}, + [110, 111, 47, 115, 117, 99, 104, 46, 116, 121, 112, 101, 0]); + wsp_encode_test_ex(func, {media: "application/vnd.wap.multipart.related"}, + [0x33 | 0x80]); + wsp_encode_test_ex(func, {media: "a", params: {b: "c", q: 0}}, + [97, 0, 98, 0, 99, 0, 128, 1]); + + run_next_test(); +}); + +//// ContentTypeValue.encodeContentGeneralForm //// + +add_test(function test_ContentTypeValue_encodeContentGeneralForm() { + function func(data, input) { + WSP.ContentTypeValue.encodeContentGeneralForm(data, input); + return data.array; + } + + wsp_encode_test_ex(func, {media: "a", params: {b: "c", q: 0}}, + [8, 97, 0, 98, 0, 99, 0, 128, 1]); + + run_next_test(); +}); + +//// ContentTypeValue.encode //// + +add_test(function test_ContentTypeValue_encode() { + wsp_encode_test(WSP.ContentTypeValue, {media: "no/such.type"}, + [110, 111, 47, 115, 117, 99, 104, 46, 116, 121, 112, 101, 0]); + wsp_encode_test(WSP.ContentTypeValue, + {media: "application/vnd.wap.multipart.related"}, + [0x33 | 0x80]); + wsp_encode_test(WSP.ContentTypeValue, {media: "a", params: {b: "c", q: 0}}, + [8, 97, 0, 98, 0, 99, 0, 128, 1]); + + run_next_test(); +}); + // // Test target: ApplicationIdValue //