Bug 761057 - Part 2: Add required encoders for M-Send.req/M-Send.conf PDU, r=philikon

This commit is contained in:
Vicamo Yang 2012-07-26 12:11:40 +08:00
parent f6f2cd1a53
commit a39061d0de
5 changed files with 1591 additions and 57 deletions

View File

@ -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 <Octet 127> 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",

View File

@ -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

View File

@ -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;

View File

@ -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 <joe@user.org>", type: "email"},
strToCharCodeArray("Joe User <joe@user.org>"));
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();
});

View File

@ -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
//