Bug 749856 - Part 6: add WSP/MMS encoding, r=philikon

This commit is contained in:
Vicamo Yang 2012-06-01 16:48:57 +08:00
Родитель dcd6c15524
Коммит 85a268fce9
5 изменённых файлов: 581 добавлений и 3 удалений

Просмотреть файл

@ -50,6 +50,16 @@ let BooleanValue = {
return value == 128; return value == 128;
}, },
/**
* @param data
* A wrapped object to store encoded raw data.
* @param value
* A boolean value to be encoded.
*/
encode: function encode(data, value) {
WSP.Octet.encode(data, value ? 128 : 129);
},
}; };
/** /**
@ -105,6 +115,19 @@ let HeaderField = {
return WSP.decodeAlternatives(data, options, return WSP.decodeAlternatives(data, options,
MmsHeader, WSP.ApplicationHeader); MmsHeader, WSP.ApplicationHeader);
}, },
/**
* @param data
* A wrapped object to store encoded raw data.
* @param octet
* Octet value to be encoded.
* @param options
* Extra context for encoding.
*/
encode: function encode(data, value, options) {
WSP.encodeAlternatives(data, value, options,
MmsHeader, WSP.ApplicationHeader);
},
}; };
/** /**
@ -155,6 +178,32 @@ let MmsHeader = {
value: value, 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.
*
* @throws CodeError if got an empty header name.
* @throws NotWellKnownEncodingError if the well-known header field number is
* not registered or supported.
*/
encode: function encode(data, header) {
if (!header.name) {
throw new WSP.CodeError("MMS-header: empty header name");
}
let entry = MMS_HEADER_FIELDS[header.name.toLowerCase()];
if (!entry) {
throw new WSP.NotWellKnownEncodingError(
"MMS-header: not well known header " + header.name);
}
WSP.ShortInteger.encode(data, entry.number);
entry.coder.encode(data, header.value);
},
}; };
/** /**
@ -639,6 +688,22 @@ let MessageTypeValue = {
throw new WSP.CodeError("Message-type-value: invalid type " + type); throw new WSP.CodeError("Message-type-value: invalid type " + type);
}, },
/**
* @param data
* A wrapped object to store encoded raw data.
* @param type
* A numeric message type value to be encoded.
*
* @throws CodeError if the value is not in the range 128..151.
*/
encode: function encode(data, type) {
if ((type < 128) || (type > 151)) {
throw new WSP.CodeError("Message-type-value: invalid type " + type);
}
WSP.Octet.encode(data, type);
},
}; };
/** /**
@ -866,6 +931,22 @@ let StatusValue = {
throw new WSP.CodeError("Status-value: invalid status " + status); throw new WSP.CodeError("Status-value: invalid status " + status);
}, },
/**
* @param data
* A wrapped object to store encoded raw data.
* @param value
* A numeric status value to be encoded.
*
* @throws CodeError if the value is not in the range 128..135.
*/
encode: function encode(data, value) {
if ((value < 128) || (value > 135)) {
throw new WSP.CodeError("Status-value: invalid status " + value);
}
WSP.Octet.encode(data, value);
},
}; };
let PduHelper = { let PduHelper = {
@ -985,6 +1066,107 @@ let PduHelper = {
return msg; return msg;
}, },
/**
* Convert javascript Array to an nsIInputStream.
*/
convertArrayToInputStream: function convertDataToInputStream(array) {
let storageStream = Cc["@mozilla.org/storagestream;1"]
.createInstance(Ci.nsIStorageStream);
storageStream.init(4096, array.length, null);
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 [optional]
* A wrapped object to store encoded raw data. Created if undefined.
* @param headers
* A dictionary object containing multiple name/value mapping.
*
* @return the passed data parameter or a created one.
*/
encodeHeaders: function encodeHeaders(data, headers) {
if (!data) {
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");
for (let key in headers) {
if ((key == "x-mms-message-type")
|| (key == "x-mms-transaction-id")
|| (key == "x-mms-mms-version")
|| (key == "content-type")) {
continue;
}
encodeHeader(key);
}
encodeHeaderIfExists("content-type");
// Remove extra space consumed during encoding.
while (data.array.length > data.offset) {
data.array.pop();
}
return data;
},
/**
* @param multiStream
* An exsiting nsIMultiplexInputStream.
* @param msg
* A MMS message object.
*
* @return An instance of nsIMultiplexInputStream or null in case of errors.
*/
compose: function compose(multiStream, msg) {
if (!multiStream) {
multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"]
.createInstance(Ci.nsIMultiplexInputStream);
}
try {
// Validity checks
this.checkMandatoryFields(msg);
let data = this.encodeHeaders(null, msg.headers);
debug("Composed PDU Header: " + JSON.stringify(data.array));
let headerStream = this.convertArrayToInputStream(data.array);
multiStream.appendStream(headerStream);
return multiStream;
} catch (e) {
debug("Failed to compose MMS message, error message: " + e.message);
return null;
}
},
}; };
const MMS_PDU_TYPES = (function () { const MMS_PDU_TYPES = (function () {

Просмотреть файл

@ -181,6 +181,33 @@ function decodeAlternatives(data, options) {
} }
} }
/**
* Helper function for encoding multiple alternative forms.
*
* @param data
* A wrapped object to store encoded raw data.
* @param value
* Object value of arbitrary type to be encoded.
* @param options
* Extra context for encoding.
*/
function encodeAlternatives(data, value, options) {
let begin = data.offset;
for (let i = 3; i < arguments.length; i++) {
try {
arguments[i].encode(data, value, options);
return;
} catch (e) {
// Throw the last exception we get
if (i == (arguments.length - 1)) {
throw e;
}
data.offset = begin;
}
}
}
let Octet = { let Octet = {
/** /**
* @param data * @param data
@ -247,6 +274,21 @@ let Octet = {
return expected; return expected;
}, },
/**
* @param data
* A wrapped object to store encoded raw data.
* @param octet
* Octet value to be encoded.
*/
encode: function encode(data, octet) {
if (data.offset >= data.array.length) {
data.array.push(octet);
data.offset++;
} else {
data.array[data.offset++] = octet;
}
},
}; };
/** /**
@ -323,6 +365,26 @@ let Text = {
data.offset = begin; data.offset = begin;
return " "; return " ";
}, },
/**
* @param data
* A wrapped object to store encoded raw data.
* @param text
* String text of one character to be encoded.
*
* @throws CodeError if a control character got.
*/
encode: function encode(data, text) {
if (!text) {
throw new CodeError("Text: empty string");
}
let code = text.charCodeAt(0);
if ((code < CTLS) || (code == DEL) || (code > 255)) {
throw new CodeError("Text: invalid char code " + code);
}
Octet.encode(data, code);
},
}; };
let NullTerminatedTexts = { let NullTerminatedTexts = {
@ -345,6 +407,21 @@ let NullTerminatedTexts = {
return str; return str;
} }
}, },
/**
* @param data
* A wrapped object to store encoded raw data.
* @param str
* A String to be encoded.
*/
encode: function encode(data, str) {
if (str) {
for (let i = 0; i < str.length; i++) {
Text.encode(data, str.charAt(i));
}
}
Octet.encode(data, 0);
},
}; };
/** /**
@ -385,6 +462,37 @@ let Token = {
throw new CodeError("Token: invalid char code " + code); throw new CodeError("Token: invalid char code " + code);
}, },
/**
* @param data
* A wrapped object to store encoded raw data.
* @param token
* String text of one character to be encoded.
*
* @throws CodeError if an invalid character got.
*/
encode: function encode(data, token) {
if (!token) {
throw new CodeError("Token: empty string");
}
let code = token.charCodeAt(0);
if ((code < ASCIIS) && (code >= CTLS)) {
if ((code == HT) || (code == SP)
|| (code == 34) || (code == 40) || (code == 41) // ASCII "()
|| (code == 44) || (code == 47) // ASCII ,/
|| ((code >= 58) && (code <= 64)) // ASCII :;<=>?@
|| ((code >= 91) && (code <= 93)) // ASCII [\]
|| (code == 123) || (code == 125)) { // ASCII {}
// Fallback to throw CodeError
} else {
Octet.encode(data, token.charCodeAt(0));
return;
}
}
throw new CodeError("Token: invalid char code " + code);
},
}; };
/** /**
@ -464,6 +572,26 @@ let TextString = {
data.offset = begin; data.offset = begin;
return NullTerminatedTexts.decode(data); 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) {
if (!str) {
Octet.encode(data, 0);
return;
}
let firstCharCode = str.charCodeAt(0);
if (firstCharCode >= 128) {
Octet.encode(data, 127);
}
NullTerminatedTexts.encode(data, str);
},
}; };
/** /**
@ -489,6 +617,21 @@ let TokenText = {
return str; return str;
} }
}, },
/**
* @param data
* A wrapped object to store encoded raw data.
* @param str
* A String to be encoded.
*/
encode: function encode(data, str) {
if (str) {
for (let i = 0; i < str.length; i++) {
Token.encode(data, str.charAt(i));
}
}
Octet.encode(data, 0);
},
}; };
/** /**
@ -544,6 +687,22 @@ let ShortInteger = {
return (value & 0x7F); return (value & 0x7F);
}, },
/**
* @param data
* A wrapped object to store encoded raw data.
* @param value
* A numeric value to be encoded.
*
* @throws CodeError if the octet read is larger-equal than 0x80.
*/
encode: function encode(data, value) {
if (value & 0x80) {
throw new CodeError("Short-integer: invalid value " + value);
}
Octet.encode(data, value | 0x80);
},
}; };
/** /**
@ -1152,6 +1311,24 @@ let ApplicationHeader = {
value: value, 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.
*
* @throws CodeError if got an empty header name.
*/
encode: function encode(data, header) {
if (!header.name) {
throw new CodeError("Application-header: empty header name");
}
TokenText.encode(data, header.name);
TextString.encode(data, header.value);
},
}; };
/** /**
@ -1908,6 +2085,7 @@ const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([
"ensureHeader", "ensureHeader",
"skipValue", "skipValue",
"decodeAlternatives", "decodeAlternatives",
"encodeAlternatives",
// Decoders // Decoders
"Octet", "Octet",

Просмотреть файл

@ -95,6 +95,52 @@ function wsp_decode_test(target, input, expect, exception) {
wsp_decode_test_ex(func, input, expect, exception); wsp_decode_test_ex(func, input, expect, exception);
} }
/**
* Test customized WSP PDU encoding.
*
* @param func
* Encoding func under test. It should return an encoded octet array if
* invoked.
* @param input
* An object to be encoded.
* @param expect
* Expected encoded octet array, use null if expecting errors instead.
* @param exception
* Expected class name of thrown exception. Use null for no throws.
*/
function wsp_encode_test_ex(func, input, expect, exception) {
let data = {array: [], offset: 0};
do_check_throws(wsp_test_func.bind(null, func.bind(null, data), input,
expect), exception);
}
/**
* Test default WSP PDU encoding.
*
* @param target
* Target decoding object, ie. TextValue.
* @param input
* An object to be encoded.
* @param expect
* Expected encoded octet array, use null if expecting errors instead.
* @param exception
* Expected class name of thrown exception. Use null for no throws.
*/
function wsp_encode_test(target, input, expect, exception) {
let func = function encode_func(data, input) {
target.encode(data, input);
// Remove extra space consumed during encoding.
while (data.array.length > data.offset) {
data.array.pop();
}
return data.array;
}
wsp_encode_test_ex(func, input, expect, exception);
}
/** /**
* @param str * @param str
* A string. * A string.

Просмотреть файл

@ -29,6 +29,15 @@ add_test(function test_BooleanValue_decode() {
run_next_test(); run_next_test();
}); });
//// BooleanValue.encode ////
add_test(function test_BooleanValue_encode() {
wsp_encode_test(MMS.BooleanValue, true, [128]);
wsp_encode_test(MMS.BooleanValue, false, [129]);
run_next_test();
});
// //
// Test target: Address // Test target: Address
// //
@ -83,6 +92,19 @@ add_test(function test_HeaderField_decode() {
run_next_test(); run_next_test();
}); });
//// HeaderField.encode ////
add_test(function test_HeaderField_encode() {
// Test for MmsHeader
wsp_encode_test(MMS.HeaderField, {name: "X-Mms-Message-Type",
value: MMS.MMS_PDU_TYPE_SEND_REQ},
[0x80 | 0x0C, MMS.MMS_PDU_TYPE_SEND_REQ]);
// Test for ApplicationHeader
wsp_encode_test(MMS.HeaderField, {name: "a", value: "B"}, [97, 0, 66, 0]);
run_next_test();
});
// //
// Test target: MmsHeader // Test target: MmsHeader
// //
@ -98,6 +120,24 @@ add_test(function test_MmsHeader_decode() {
run_next_test(); run_next_test();
}); });
//// MmsHeader.encode ////
add_test(function test_MmsHeader_encode() {
// Test for empty header name:
wsp_encode_test(MMS.MmsHeader, {name: undefined, value: null}, null, "CodeError");
wsp_encode_test(MMS.MmsHeader, {name: null, value: null}, null, "CodeError");
wsp_encode_test(MMS.MmsHeader, {name: "", value: null}, null, "CodeError");
// Test for non-well-known header name:
wsp_encode_test(MMS.MmsHeader, {name: "X-No-Such-Field", value: null},
null, "NotWellKnownEncodingError");
// Test for normal header
wsp_encode_test(MMS.MmsHeader, {name: "X-Mms-Message-Type",
value: MMS.MMS_PDU_TYPE_SEND_REQ},
[0x80 | 0x0C, MMS.MMS_PDU_TYPE_SEND_REQ]);
run_next_test();
});
// //
// Test target: ContentClassValue // Test target: ContentClassValue
// //
@ -225,7 +265,7 @@ add_test(function test_EncodedStringValue_decode() {
let raw; let raw;
try { try {
let raw = conv.convertToByteArray(str); let raw = conv.convertToByteArray(str).concat([0]);
if (raw[0] >= 128) { if (raw[0] >= 128) {
wsp_decode_test(MMS.EncodedStringValue, wsp_decode_test(MMS.EncodedStringValue,
[raw.length + 2, 0x80 | entry.number, 127].concat(raw), str); [raw.length + 2, 0x80 | entry.number, 127].concat(raw), str);
@ -352,6 +392,20 @@ add_test(function test_MessageTypeValue_decode() {
run_next_test(); run_next_test();
}); });
//// MessageTypeValue.encode ////
add_test(function test_MessageTypeValue_encode() {
for (let i = 0; i < 256; i++) {
if ((i >= 128) && (i <= 151)) {
wsp_encode_test(MMS.MessageTypeValue, i, [i]);
} else {
wsp_encode_test(MMS.MessageTypeValue, i, null, "CodeError");
}
}
run_next_test();
});
// //
// Test target: MmFlagsValue // Test target: MmFlagsValue
// //
@ -482,3 +536,17 @@ add_test(function test_StatusValue_decode() {
run_next_test(); run_next_test();
}); });
//// StatusValue.encode ////
add_test(function test_StatusValue_encode() {
for (let i = 0; i < 256; i++) {
if ((i >= 128) && (i <= 135)) {
wsp_encode_test(MMS.StatusValue, i, [i]);
} else {
wsp_encode_test(MMS.StatusValue, i, null, "CodeError");
}
}
run_next_test();
});

Просмотреть файл

@ -111,6 +111,16 @@ add_test(function test_Octet_decodeEqualTo() {
run_next_test(); run_next_test();
}); });
//// Octet.encode ////
add_test(function test_Octet_encode() {
for (let i = 0; i < 256; i++) {
wsp_encode_test(WSP.Octet, i, [i]);
}
run_next_test();
});
// //
// Test target: Text // Test target: Text
// //
@ -135,6 +145,20 @@ add_test(function test_Text_decode() {
run_next_test(); run_next_test();
}); });
//// Text.encode ////
add_test(function test_Text_encode() {
for (let i = 0; i < 256; i++) {
if ((i < WSP.CTLS) || (i == WSP.DEL)) {
wsp_encode_test(WSP.Text, String.fromCharCode(i), null, "CodeError");
} else {
wsp_encode_test(WSP.Text, String.fromCharCode(i), [i]);
}
}
run_next_test();
});
// //
// Test target: NullTerminatedTexts // Test target: NullTerminatedTexts
// //
@ -155,19 +179,30 @@ add_test(function test_NullTerminatedTexts_decode() {
run_next_test(); run_next_test();
}); });
//// NullTerminatedTexts.encode ////
add_test(function test_NullTerminatedTexts_encode() {
wsp_encode_test(WSP.NullTerminatedTexts, "", [0]);
wsp_encode_test(WSP.NullTerminatedTexts, "Hello, World!",
strToCharCodeArray("Hello, World!"));
run_next_test();
});
// //
// Test target: Token // Test target: Token
// //
let TOKEN_SEPS = "()<>@,;:\\\"/[]?={} \t";
//// Token.decode //// //// Token.decode ////
add_test(function test_Token_decode() { add_test(function test_Token_decode() {
let seps = "()<>@,;:\\\"/[]?={} \t";
for (let i = 0; i < 256; i++) { for (let i = 0; i < 256; i++) {
if (i == 0) { if (i == 0) {
wsp_decode_test(WSP.Token, [i], null, "NullCharError"); wsp_decode_test(WSP.Token, [i], null, "NullCharError");
} else if ((i < WSP.CTLS) || (i >= WSP.ASCIIS) } else if ((i < WSP.CTLS) || (i >= WSP.ASCIIS)
|| (seps.indexOf(String.fromCharCode(i)) >= 0)) { || (TOKEN_SEPS.indexOf(String.fromCharCode(i)) >= 0)) {
wsp_decode_test(WSP.Token, [i], null, "CodeError"); wsp_decode_test(WSP.Token, [i], null, "CodeError");
} else { } else {
wsp_decode_test(WSP.Token, [i], String.fromCharCode(i)); wsp_decode_test(WSP.Token, [i], String.fromCharCode(i));
@ -177,6 +212,21 @@ add_test(function test_Token_decode() {
run_next_test(); run_next_test();
}); });
//// Token.encode ////
add_test(function test_Token_encode() {
for (let i = 0; i < 256; i++) {
if ((i < WSP.CTLS) || (i >= WSP.ASCIIS)
|| (TOKEN_SEPS.indexOf(String.fromCharCode(i)) >= 0)) {
wsp_encode_test(WSP.Token, String.fromCharCode(i), null, "CodeError");
} else {
wsp_encode_test(WSP.Token, String.fromCharCode(i), [i]);
}
}
run_next_test();
});
// //
// Test target: URIC // Test target: URIC
// //
@ -218,6 +268,17 @@ add_test(function test_TextString_decode() {
run_next_test(); run_next_test();
}); });
//// TextString.encode ////
add_test(function test_TextString_encode() {
// Test quoted string
wsp_encode_test(WSP.TextString, String.fromCharCode(128), [127, 128, 0]);
// Test normal string
wsp_encode_test(WSP.TextString, "Mozilla", strToCharCodeArray("Mozilla"));
run_next_test();
});
// //
// Test target: TokenText // Test target: TokenText
// //
@ -232,6 +293,14 @@ add_test(function test_TokenText_decode() {
run_next_test(); run_next_test();
}); });
//// TokenText.encode ////
add_test(function test_TokenText_encode() {
wsp_encode_test(WSP.TokenText, "B2G", strToCharCodeArray("B2G"));
run_next_test();
});
// //
// Test target: QuotedString // Test target: QuotedString
// //
@ -266,6 +335,20 @@ add_test(function test_ShortInteger_decode() {
run_next_test(); run_next_test();
}); });
//// ShortInteger.encode ////
add_test(function test_ShortInteger_encode() {
for (let i = 0; i < 256; i++) {
if (i & 0x80) {
wsp_encode_test(WSP.ShortInteger, i, null, "CodeError");
} else {
wsp_encode_test(WSP.ShortInteger, i, [0x80 | i]);
}
}
run_next_test();
});
// //
// Test target: LongInteger // Test target: LongInteger
// //
@ -624,6 +707,27 @@ add_test(function test_ApplicationHeader_decode() {
run_next_test(); run_next_test();
}); });
//// ApplicationHeader.encode ////
add_test(function test_ApplicationHeader_encode() {
// Test invalid header name string:
wsp_encode_test(WSP.ApplicationHeader, {name: undefined, value: "asdf"}, null, "CodeError");
wsp_encode_test(WSP.ApplicationHeader, {name: null, value: "asdf"}, null, "CodeError");
wsp_encode_test(WSP.ApplicationHeader, {name: "", value: "asdf"}, null, "CodeError");
wsp_encode_test(WSP.ApplicationHeader, {name: "a b", value: "asdf"}, null, "CodeError");
// Test value string:
wsp_encode_test(WSP.ApplicationHeader, {name: "asdf", value: undefined},
strToCharCodeArray("asdf").concat([0]));
wsp_encode_test(WSP.ApplicationHeader, {name: "asdf", value: null},
strToCharCodeArray("asdf").concat([0]));
wsp_encode_test(WSP.ApplicationHeader, {name: "asdf", value: ""},
strToCharCodeArray("asdf").concat([0]));
wsp_encode_test(WSP.ApplicationHeader, {name: "asdf", value: "fdsa"},
strToCharCodeArray("asdf").concat(strToCharCodeArray("fdsa")));
run_next_test();
});
// //
// Test target: FieldName // Test target: FieldName
// //