From e5a7924398b591a0610e2064fa066df2a62259fb Mon Sep 17 00:00:00 2001 From: Vicamo Yang Date: Mon, 4 Jun 2012 13:04:24 +0800 Subject: [PATCH] Bug 744360 - Part 1: add WspPduHelper, r=philikon --- dom/Makefile.in | 1 + dom/mms/Makefile.in | 22 + dom/mms/src/Makefile.in | 22 + dom/mms/src/ril/WspPduHelper.jsm | 1947 ++++++++++++++++++++++++++ dom/mms/src/ril/wap_consts.js | 14 + dom/mms/tests/header_helpers.js | 118 ++ dom/mms/tests/test_wsp_pdu_helper.js | 814 +++++++++++ dom/mms/tests/xpcshell.ini | 5 + testing/xpcshell/xpcshell.ini | 1 + 9 files changed, 2944 insertions(+) create mode 100644 dom/mms/Makefile.in create mode 100644 dom/mms/src/Makefile.in create mode 100644 dom/mms/src/ril/WspPduHelper.jsm create mode 100644 dom/mms/src/ril/wap_consts.js create mode 100644 dom/mms/tests/header_helpers.js create mode 100644 dom/mms/tests/test_wsp_pdu_helper.js create mode 100644 dom/mms/tests/xpcshell.ini diff --git a/dom/Makefile.in b/dom/Makefile.in index f70b86b194b..dc2c1c02845 100644 --- a/dom/Makefile.in +++ b/dom/Makefile.in @@ -55,6 +55,7 @@ DIRS += \ power \ settings \ sms \ + mms \ src \ locales \ network \ diff --git a/dom/mms/Makefile.in b/dom/mms/Makefile.in new file mode 100644 index 00000000000..c1419d7e3ab --- /dev/null +++ b/dom/mms/Makefile.in @@ -0,0 +1,22 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +relativesrcdir = dom/mms + +include $(DEPTH)/config/autoconf.mk + +PARALLEL_DIRS = src + +ifdef MOZ_B2G_RIL +ifdef ENABLE_TESTS +XPCSHELL_TESTS = tests +endif +endif + +include $(topsrcdir)/config/rules.mk diff --git a/dom/mms/src/Makefile.in b/dom/mms/src/Makefile.in new file mode 100644 index 00000000000..6ad0076836d --- /dev/null +++ b/dom/mms/src/Makefile.in @@ -0,0 +1,22 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = \ + $(srcdir) \ + $(NULL) + +include $(DEPTH)/config/autoconf.mk + +ifdef MOZ_B2G_RIL +EXTRA_JS_MODULES = \ + ril/wap_consts.js \ + ril/WspPduHelper.jsm \ + $(NULL) +endif + +include $(topsrcdir)/config/config.mk +include $(topsrcdir)/config/rules.mk diff --git a/dom/mms/src/ril/WspPduHelper.jsm b/dom/mms/src/ril/WspPduHelper.jsm new file mode 100644 index 00000000000..8ef80e7c834 --- /dev/null +++ b/dom/mms/src/ril/WspPduHelper.jsm @@ -0,0 +1,1947 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/wap_consts.js"); + +let DEBUG; // set to true to see debug messages + +// Special ASCII characters +const NUL = 0; +const CR = 13; +const LF = 10; +const SP = 32; +const HT = 9; +const DQUOTE = 34; +const DEL = 127; + +// Special ASCII character ranges +const CTLS = 32; +const ASCIIS = 128; + +/** + * Error class for generic encoding/decoding failures. + */ +function CodeError(message) { + this.name = "CodeError"; + this.message = message || "Invalid format"; +} +CodeError.prototype = new Error(); +CodeError.prototype.constructor = CodeError; + +/** + * Error class for unexpected NUL char at decoding text elements. + * + * @param message [optional] + * A short description for the error. + */ +function NullCharError(message) { + this.name = "NullCharError"; + this.message = message || "Null character found"; +} +NullCharError.prototype = new CodeError(); +NullCharError.prototype.constructor = NullCharError; + +/** + * Error class for fatal encoding/decoding failures. + * + * This error is only raised when expected format isn't met and the parser + * context can't do anything more to either skip it or hand over to other + * alternative encoding/decoding steps. + * + * @param message [optional] + * A short description for the error. + */ +function FatalCodeError(message) { + this.name = "FatalCodeError"; + this.message = message || "Decoding fails"; +} +FatalCodeError.prototype = new Error(); +FatalCodeError.prototype.constructor = FatalCodeError; + +/** + * Error class for undefined well known encoding. + * + * When a encoded header field/parameter has unknown/unsupported value, we may + * never know how to decode the next value. For example, a parameter of + * undefined well known encoding may be followed by a Q-value, which is + * basically a uintvar. However, there is no way you can distiguish an Q-value + * 0.64, encoded as 0x41, from a string begins with 'A', which is also 0x41. + * The `skipValue` will try the latter one, which is not expected. + * + * @param message [optional] + * A short description for the error. + */ +function NotWellKnownEncodingError(message) { + this.name = "NotWellKnownEncodingError"; + this.message = message || "Not well known encoding"; +} +NotWellKnownEncodingError.prototype = new FatalCodeError(); +NotWellKnownEncodingError.prototype.constructor = NotWellKnownEncodingError; + +/** + * Internal helper function to retrieve the value of a property with its name + * specified by `name` inside the object `headers`. + * + * @param headers + * An object that contains parsed header fields. + * @param name + * Header name string to be checked. + * + * @return Value of specified header field. + * + * @throws FatalCodeError if headers[name] is undefined. + */ +function ensureHeader(headers, name) { + let value = headers[name]; + // Header field might have a null value as NoValue + if (value === undefined) { + throw new FatalCodeError("ensureHeader: header " + name + " not defined"); + } + return value; +} + +/** + * Skip field value. + * + * The WSP field values are encoded so that the length of the field value can + * always be determined, even if the detailed format of a specific field value + * is not known. This makes it possible to skip over individual header fields + * without interpreting their content. ... the first octet in all the field + * values can be interpreted as follows: + * + * 0 - 30 | This octet is followed by the indicated number (0 - 30) of data + * octets. + * 31 | This octet is followed by a unitvar, which indicates the number + * of data octets after it. + * 32 - 127 | The value is a text string, terminated by a zero octet (NUL + * character). + * 128 - 255 | It is an encoded 7-bit value; this header has no more data. + * + * @param data + * A wrapped object containing raw PDU data. + * + * @return Skipped value of several possible types like string, integer, or + * an array of octets. + * + * @see WAP-230-WSP-20010705-a clause 8.4.1.2 + */ +function skipValue(data) { + let begin = data.offset; + let value = Octet.decode(data); + if (value <= 31) { + if (value == 31) { + value = UintVar.decode(data); + } + + if (value) { + // `value` can be larger than 30, max length of a multi-octet integer + // here. So we must decode it as an array instead. + value = Octet.decodeMultiple(data, data.offset + value); + } else { + value = null; + } + } else if (value <= 127) { + data.offset = begin; + value = NullTerminatedTexts.decode(data); + } else { + value &= 0x7F; + } + + return value; +} + +/** + * Helper function for decoding multiple alternative forms. + * + * @param data + * A wrapped object containing raw PDU data. + * @param options + * Extra context for decoding. + * + * @return Decoded value. + */ +function decodeAlternatives(data, options) { + let begin = data.offset; + for (let i = 2; i < arguments.length; i++) { + try { + return arguments[i].decode(data, options); + } catch (e) { + // Throw the last exception we get + if (i == (arguments.length - 1)) { + throw e; + } + + data.offset = begin; + } + } +} + +let Octet = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @throws RangeError if no more data is available. + */ + decode: function decode(data) { + if (data.offset >= data.array.length) { + throw new RangeError(); + } + + return data.array[data.offset++]; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * @param end + * An ending offset indicating the end of octet array to read. + * + * @return A decoded array object. + * + * @throws RangeError if no enough data to read. + * @throws TypeError if `data` has neither subarray() nor slice() method. + */ + decodeMultiple: function decodeMultiple(data, end) { + if ((end < data.offset) || (end > data.array.length)) { + throw new RangeError(); + } + if (end == data.offset) { + return null; + } + + let result; + if (data.array.subarray) { + result = data.array.subarray(data.offset, end); + } else if (data.array.slice) { + result = data.array.slice(data.offset, end); + } else { + throw new TypeError(); + } + + data.offset = end; + return result; + }, + + /** + * Internal octet decoding for specific value. + * + * @param data + * A wrapped object containing raw PDU data. + * @param expected + * Expected octet value. + * + * @return Expected octet value. + * + * @throws CodeError if read octet is not equal to expected one. + */ + decodeEqualTo: function decodeEqualTo(data, expected) { + if (this.decode(data) != expected) { + throw new CodeError("Octet - decodeEqualTo: doesn't match " + expected); + } + + return expected; + }, +}; + +/** + * TEXT = + * CTL = + * LWS = [CRLF] 1*(SP|HT) + * CRLF = CR LF + * CR = + * LF = + * SP = + * HT = + * + * @see RFC 2616 clause 2.2 Basic Rules + */ +let Text = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded character. + * + * @throws NullCharError if a NUL character read. + * @throws CodeError if a control character read. + */ + decode: function decode(data) { + let code = Octet.decode(data); + if ((code >= CTLS) && (code != DEL)) { + return String.fromCharCode(code); + } + + if (code == NUL) { + throw new NullCharError(); + } + + if (code != CR) { + throw new CodeError("Text: invalid char code " + code); + } + + // "A CRLF is allowed in the definition of TEXT only as part of a header + // field continuation. It is expected that the folding LWS will be + // replaced with a single SP before interpretation of the TEXT value." + // ~ RFC 2616 clause 2.2 + + let extra; + + // Rethrow everything as CodeError. We had already a successful read above. + try { + extra = Octet.decode(data); + if (extra != LF) { + throw new CodeError("Text: doesn't match LWS sequence"); + } + + extra = Octet.decode(data); + if ((extra != SP) && (extra != HT)) { + throw new CodeError("Text: doesn't match LWS sequence"); + } + } catch (e if e instanceof CodeError) { + throw e; + } catch (e) { + throw new CodeError("Text: doesn't match LWS sequence"); + } + + // Let's eat as many SP|HT as possible. + let begin; + + // Do not throw anything here. We had already matched (SP | HT). + try { + do { + begin = data.offset; + extra = Octet.decode(data); + } while ((extra == SP) || (extra == HT)); + } catch (e) {} + + data.offset = begin; + return " "; + }, +}; + +let NullTerminatedTexts = { + /** + * Decode internal referenced null terminated text string. + * + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded string. + */ + decode: function decode(data) { + let str = ""; + try { + // A End-of-string is also a CTL, which should cause a error. + while (true) { + str += Text.decode(data); + } + } catch (e if e instanceof NullCharError) { + return str; + } + }, +}; + +/** + * TOKEN = 1* + * CHAR = + * SEPARATORS = ()<>@,;:\"/[]?={} SP HT + * + * @see RFC 2616 clause 2.2 Basic Rules + */ +let Token = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded character. + * + * @throws NullCharError if a NUL character read. + * @throws CodeError if an invalid character read. + */ + decode: function decode(data) { + let code = Octet.decode(data); + 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 {} + throw new CodeError("Token: invalid char code " + code); + } + + return String.fromCharCode(code); + } + + if (code == NUL) { + throw new NullCharError(); + } + + throw new CodeError("Token: invalid char code " + code); + }, +}; + +/** + * uric = reserved | unreserved | escaped + * reserved = ;/?:@&=+$, + * unreserved = alphanum | mark + * mark = -_.!~*'() + * escaped = % hex hex + * excluded but used = #% + * + * Or, in decimal, they are: 33,35-59,61,63-90,95,97-122,126 + * + * @see RFC 2396 Uniform Resource Indentifiers (URI) + */ +let URIC = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded character. + * + * @throws NullCharError if a NUL character read. + * @throws CodeError if an invalid character read. + */ + decode: function decode(data) { + let code = Octet.decode(data); + if (code == NUL) { + throw new NullCharError(); + } + + if ((code <= CTLS) || (code >= ASCIIS) || (code == 34) || (code == 60) + || (code == 62) || ((code >= 91) && (code <= 94)) || (code == 96) + || ((code >= 123) && (code <= 125)) || (code == 127)) { + throw new CodeError("URIC: invalid char code " + code); + } + + return String.fromCharCode(code); + }, +}; + +/** + * If the first character in the TEXT is in the range of 128-255, a Quote + * character must precede it. Otherwise the Quote character must be omitted. + * The Quote is not part of the contents. + * + * Text-string = [Quote] *TEXT End-of-string + * Quote = + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 + */ +let TextString = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded string. + */ + decode: function decode(data) { + let begin = data.offset; + let firstCode = Octet.decode(data); + if (firstCode == 127) { + // Quote found, check if first char code is larger-equal than 128. + begin = data.offset; + try { + if (Octet.decode(data) < 128) { + throw new CodeError("Text-string: illegal quote found."); + } + } catch (e if e instanceof CodeError) { + throw e; + } catch (e) { + throw new CodeError("Text-string: unexpected error."); + } + } else if (firstCode >= 128) { + throw new CodeError("Text-string: invalid char code " + firstCode); + } + + data.offset = begin; + return NullTerminatedTexts.decode(data); + }, +}; + +/** + * Token-text = Token End-of-string + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 + */ +let TokenText = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded string. + */ + decode: function decode(data) { + let str = ""; + try { + // A End-of-string is also a CTL, which should cause a error. + while (true) { + str += Token.decode(data); + } + } catch (e if e instanceof NullCharError) { + return str; + } + }, +}; + +/** + * The TEXT encodes an RFC2616 Quoted-string with the enclosing + * quotation-marks <"> removed. + * + * Quoted-string = *TEXT End-of-string + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 + */ +let QuotedString = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded string. + * + * @throws CodeError if first octet read is not 0x34. + */ + decode: function decode(data) { + let value = Octet.decode(data); + if (value != 34) { + throw new CodeError("Quoted-string: not quote " + value); + } + + return NullTerminatedTexts.decode(data); + }, +}; + +/** + * Integers in range 0-127 shall be encoded as a one octet value with the + * most significant bit set to one (1xxx xxxx) and with the value in the + * remaining least significant bits. + * + * Short-integer = OCTET + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 + */ +let ShortInteger = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded integer value. + * + * @throws CodeError if the octet read is less than 0x80. + */ + decode: function decode(data) { + let value = Octet.decode(data); + if (!(value & 0x80)) { + throw new CodeError("Short-integer: invalid value " + value); + } + + return (value & 0x7F); + }, +}; + +/** + * The content octets shall be an unsigned integer value with the most + * significant octet encoded first (big-endian representation). The minimum + * number of octets must be used to encode the value. + * + * Long-integer = Short-length Multi-octet-integer + * Short-length = + * Multi-octet-integer = 1*30 OCTET + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 + */ +let LongInteger = { + /** + * @param data + * A wrapped object containing raw PDU data. + * @param length + * Number of octets to read. + * + * @return A decoded integer value or an octets array of max 30 elements. + */ + decodeMultiOctetInteger: function decodeMultiOctetInteger(data, length) { + if (length < 7) { + // Return a integer instead of an array as possible. For a multi-octet + // integer, there are only maximum 53 bits for integer in javascript. We + // will get an inaccurate one beyond that. We can't neither use bitwise + // operation here, for it will be limited in 32 bits. + let value = 0; + while (length--) { + value = value * 256 + Octet.decode(data); + } + return value; + } + + return Octet.decodeMultiple(data, data.offset + length); + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded integer value or an octets array of max 30 elements. + * + * @throws CodeError if the length read is not in 1..30. + */ + decode: function decode(data) { + let length = Octet.decode(data); + if ((length < 1) || (length > 30)) { + throw new CodeError("Long-integer: invalid length " + length); + } + + return this.decodeMultiOctetInteger(data, length); + }, +}; + +/** + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 + */ +let UintVar = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded integer value. + */ + decode: function decode(data) { + let value = Octet.decode(data); + let result = value & 0x7F; + while (value & 0x80) { + value = Octet.decode(data); + result = result * 128 + (value & 0x7F); + } + + return result; + }, +}; + +/** + * This encoding is used for token values, which have no well-known binary + * encoding, or when the assigned number of the well-known encoding is small + * enough to fit into Short-Integer. + * + * Constrained-encoding = Extension-Media | Short-integer + * Extension-Media = *TEXT End-of-string + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 + */ +let ConstrainedEncoding = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decode integer value or string. + */ + decode: function decode(data) { + return decodeAlternatives(data, null, NullTerminatedTexts, ShortInteger); + }, +}; + +/** + * Value-length = Short-length | (Length-quote Length) + * Short-length = + * Length-quote = + * Length = Uintvar-integer + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.2 + */ +let ValueLength = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded integer value. + * + * @throws CodeError if the first octet read is larger than 31. + */ + decode: function decode(data) { + let value = Octet.decode(data); + if (value <= 30) { + return value; + } + + if (value == 31) { + return UintVar.decode(data); + } + + throw new CodeError("Value-length: invalid value " + value); + }, +}; + +/** + * No-value = + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 + */ +let NoValue = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Always returns null. + */ + decode: function decode(data) { + Octet.decodeEqualTo(data, 0); + return null; + }, +}; + +/** + * Text-value = No-value | Token-text | Quoted-string + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 + */ +let TextValue = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded string or null for No-value. + */ + decode: function decode(data) { + return decodeAlternatives(data, null, NoValue, TokenText, QuotedString); + }, +}; + +/** + * Integer-Value = Short-integer | Long-integer + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 + */ +let IntegerValue = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded integer value or array of octets. + */ + decode: function decode(data) { + return decodeAlternatives(data, null, ShortInteger, LongInteger); + }, +}; + +/** + * The encoding of dates shall be done in number of seconds from + * 1970-01-01, 00:00:00 GMT. + * + * Date-value = Long-integer + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 + */ +let DateValue = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A Date object. + */ + decode: function decode(data) { + let numOrArray = LongInteger.decode(data); + let seconds; + if (typeof numOrArray == "number") { + seconds = numOrArray; + } else { + seconds = 0; + for (let i = 0; i < numOrArray.length; i++) { + seconds = seconds * 256 + numOrArray[i]; + } + } + + return new Date(seconds * 1000); + }, +}; + +/** + * Delta-seconds-value = Integer-value + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 + */ +let DeltaSecondsValue = IntegerValue; + +/** + * Quality factor 0 and quality factors with one or two decimal digits are + * encoded into 1-100; three digits ones into 101-1099. + * + * Q-value = 1*2 OCTET + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 + */ +let QValue = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded integer value of 1..1099. + * + * @throws CodeError if decoded UintVar is not in range 1..1099. + */ + decode: function decode(data) { + let value = UintVar.decode(data); + if (value > 0) { + if (value <= 100) { + return (value - 1) / 100.0; + } + if (value <= 1099) { + return (value - 100) / 1000.0; + } + } + + throw new CodeError("Q-value: invalid value " + value); + }, +}; + +/** + * The three most significant bits of the Short-integer value are interpreted + * to encode a major version number in the range 1-7, and the four least + * significant bits contain a minor version number in the range 0-14. If + * there is only a major version number, this is encoded by placing the value + * 15 in the four least significant bits. + * + * Version-value = Short-integer | Text-string + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 + */ +let VersionValue = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Binary encoded version number. + */ + decode: function decode(data) { + let begin = data.offset; + let value; + try { + value = ShortInteger.decode(data); + if ((value >= 0x10) && (value < 0x80)) { + return value; + } + + throw new CodeError("Version-value: invalid value " + value); + } catch (e) {} + + data.offset = begin; + + let str = TextString.decode(data); + if (!str.match(/^[1-7](\.1?\d)?$/)) { + throw new CodeError("Version-value: invalid value " + str); + } + + let major = str.charCodeAt(0) - 0x30; + let minor = 0x0F; + if (str.length > 1) { + minor = str.charCodeAt(2) - 0x30; + if (str.length > 3) { + minor = 10 + (str.charCodeAt(3) - 0x30); + if (minor > 14) { + throw new CodeError("Version-value: invalid minor " + minor); + } + } + } + + return major << 4 | minor; + }, +}; + +/** + * URI value should be encoded per [RFC2616], but service user may use a + * different format. + * + * Uri-value = Text-string + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 + * @see RFC 2616 clause 2.2 Basic Rules + */ +let UriValue = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded uri string. + */ + decode: function decode(data) { + let str = ""; + try { + // A End-of-string is also a CTL, which should cause a error. + while (true) { + str += URIC.decode(data); + } + } catch (e if e instanceof NullCharError) { + return str; + } + }, +}; + +/** + * Parameter = Typed-parameter | Untyped-parameter + * + * For Typed-parameters, the actual expected type of the value is implied by + * the well-known parameter. In addition to the expected type, there may be no + * value. If the value cannot be encoded using expected type, it shall be + * encoded as text. + * + * Typed-parameter = Well-known-parameter-token Typed-value + * Well-known-parameter-token = Integer-value + * Typed-value = Compact-value | Text-value + * Compact-value = Integer-value | Date-value | Delta-seconds-value | Q-value + * | Version-value | Uri-value + * + * For Untyped-parameters, the type of the value is unknown, but is shall be + * encoded as an integer, if that is possible. + * + * Untyped-parameter = Token-text Untyped-value + * Untyped-value = Integer-value | Text-value + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.4 + */ +let Parameter = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded object containing `name` and `value` properties or null + * if something wrong. The `name` property must be a string, but the + * `value` property can be many different types depending on `name`. + * + * @throws CodeError if decoded IntegerValue is an array. + * @throws NotWellKnownEncodingError if decoded well-known parameter number + * is not registered or supported. + */ + decodeTypedParameter: function decodeTypedParameter(data) { + let numOrArray = IntegerValue.decode(data); + // `decodeIntegerValue` can return a array, which doesn't apply here. + if (typeof numOrArray != "number") { + throw new CodeError("Typed-parameter: invalid integer type"); + } + + let number = numOrArray; + let param = WSP_WELL_KNOWN_PARAMS[number]; + if (!param) { + throw new NotWellKnownEncodingError( + "Typed-parameter: not well known parameter " + number); + } + + let begin = data.offset, value; + try { + // Althought Text-string is not included in BNF of Compact-value, but + // some service provider might still pass a less-strict text form and + // cause a unexpected CodeError raised. For example, the `start` + // parameter expects its value of Text-value, but service provider might + // gives "", which contains illegal characters "<" and ">". + value = decodeAlternatives(data, null, + param.coder, TextValue, TextString); + } catch (e) { + data.offset = begin; + + // Skip current parameter. + value = skipValue(data); + debug("Skip malformed typed parameter: " + + JSON.stringify({name: param.name, value: value})); + + return null; + } + + return { + name: param.name, + value: value, + }; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded object containing `name` and `value` properties or null + * if something wrong. The `name` property must be a string, but the + * `value` property can be many different types depending on `name`. + */ + decodeUntypedParameter: function decodeUntypedParameter(data) { + let name = TokenText.decode(data); + + let begin = data.offset, value; + try { + value = decodeAlternatives(data, null, IntegerValue, TextValue); + } catch (e) { + data.offset = begin; + + // Skip current parameter. + value = skipValue(data); + debug("Skip malformed untyped parameter: " + + JSON.stringify({name: name, value: value})); + + return null; + } + + return { + name: name.toLowerCase(), + value: value, + }; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded object containing `name` and `value` properties or null + * if something wrong. The `name` property must be a string, but the + * `value` property can be many different types depending on `name`. + */ + decode: function decode(data) { + let begin = data.offset; + try { + return this.decodeTypedParameter(data); + } catch (e) { + data.offset = begin; + return this.decodeUntypedParameter(data); + } + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * @param end + * Ending offset of following parameters. + * + * @return An array of decoded objects. + */ + decodeMultiple: function decodeMultiple(data, end) { + let params = null, param; + + while (data.offset < end) { + try { + param = this.decode(data); + } catch (e) { + break; + } + if (param) { + if (!params) { + params = {}; + } + params[param.name] = param.value; + } + } + + return params; + }, +}; + +/** + * Header = Message-header | Shift-sequence + * Message-header = Well-known-header | Application-header + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.6 + */ +let Header = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded object containing `name` and `value` properties or null + * in case of a failed parsing. The `name` property must be a string, + * but the `value` property can be many different types depending on + * `name`. + */ + decodeMessageHeader: function decodeMessageHeader(data) { + return decodeAlternatives(data, null, WellKnownHeader, ApplicationHeader); + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded object containing `name` and `value` properties or null + * in case of a failed parsing. The `name` property must be a string, + * but the `value` property can be many different types depending on + * `name`. + */ + decode: function decode(data) { + // TODO: support header code page shift-sequence + return this.decodeMessageHeader(data); + }, +}; + +/** + * Well-known-header = Well-known-field-name Wap-value + * Well-known-field-name = Short-integer + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.6 + */ +let WellKnownHeader = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded object containing `name` and `value` properties or null + * in case of a failed parsing. The `name` property must be a string, + * but the `value` property can be many different types depending on + * `name`. + * + * @throws NotWellKnownEncodingError if decoded well-known header field + * number is not registered or supported. + */ + decode: function decode(data) { + let index = ShortInteger.decode(data); + + let entry = WSP_HEADER_FIELDS[index]; + if (!entry) { + throw new NotWellKnownEncodingError( + "Well-known-header: not well known header " + index); + } + + let begin = data.offset, value; + try { + value = decodeAlternatives(data, null, entry.coder, TextValue); + } catch (e) { + data.offset = begin; + + value = skipValue(data); + debug("Skip malformed well known header(" + index + "): " + + JSON.stringify({name: entry.name, value: value})); + + return null; + } + + return { + name: entry.name, + value: value, + }; + }, +}; + +/** + * Application-header = Token-text Application-specific-value + * Application-specific-value = Text-string + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.6 + */ +let ApplicationHeader = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded object containing `name` and `value` properties or null + * in case of a failed parsing. The `name` property must be a string, + * but the `value` property can be many different types depending on + * `name`. + */ + decode: function decode(data) { + let name = TokenText.decode(data); + + let begin = data.offset, value; + try { + value = TextString.decode(data); + } catch (e) { + data.offset = begin; + + value = skipValue(data); + debug("Skip malformed application header: " + + JSON.stringify({name: name, value: value})); + + return null; + } + + return { + name: name.toLowerCase(), + value: value, + }; + }, +}; + +/** + * Field-name = Token-text | Well-known-field-name + * Well-known-field-name = Short-integer + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.6 + */ +let FieldName = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A field name string. + * + * @throws NotWellKnownEncodingError if decoded well-known header field + * number is not registered or supported. + */ + decode: function decode(data) { + let begin = data.offset; + try { + return TokenText.decode(data).toLowerCase(); + } catch (e) {} + + data.offset = begin; + + let number = ShortInteger.decode(data); + let entry = WSP_HEADER_FIELDS[number]; + if (!entry) { + throw new NotWellKnownEncodingError( + "Field-name: not well known encoding " + number); + } + + return entry.name; + }, +}; + +/** + * Accept-charset-value = Constrained-charset | Accept-charset-general-form + * Constrained-charset = Any-charset | Constrained-encoding + * Any-charset = + * Accept-charset-general-form = Value-length (Well-known-charset | Token-text) [Q-value] + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.8 + */ +let AcceptCharsetValue = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A object with a property `charset` of string "*". + */ + decodeAnyCharset: function decodeAnyCharset(data) { + Octet.decodeEqualTo(data, 128); + return {charset: "*"}; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A object with a string property `charset` and a optional integer + * property `q`. + * + * @throws NotWellKnownEncodingError if decoded well-known charset number is + * not registered or supported. + */ + decodeConstrainedCharset: function decodeConstrainedCharset(data) { + let begin = data.offset; + try { + return this.decodeAnyCharset(data); + } catch (e) {} + + data.offset = begin; + + let numOrStr = ConstrainedEncoding.decode(data); + if (typeof numOrStr == "string") { + return {charset: numOrStr}; + } + + let charset = numOrStr; + let entry = WSP_WELL_KNOWN_CHARSETS[charset]; + if (!entry) { + throw new NotWellKnownEncodingError( + "Constrained-charset: not well known charset: " + charset); + } + + return {charset: entry.name}; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A object with a string property `charset` and a optional integer + * property `q`. + */ + decodeAcceptCharsetGeneralForm: function decodeAcceptCharsetGeneralForm(data) { + let length = ValueLength.decode(data); + + let begin = data.offset; + let end = begin + length; + + let result; + try { + result = WellKnownCharset.decode(data); + } catch (e) { + data.offset = begin; + + result = {charset: TokenText.decode(data)}; + if (data.offset < end) { + result.q = QValue.decode(data); + } + } + + if (data.offset != end) { + data.offset = end; + } + + return result; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A object with a string property `charset` and a optional integer + * property `q`. + */ + decode: function decode(data) { + let begin = data.offset; + try { + return this.decodeConstrainedCharset(data); + } catch (e) { + data.offset = begin; + return this.decodeAcceptCharsetGeneralForm(data); + } + }, +}; + +/** + * Well-known-charset = Any-charset | Integer-value + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.8 + */ +let WellKnownCharset = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A object with a string property `charset`. + * + * @throws CodeError if decoded charset number is an array. + * @throws NotWellKnownEncodingError if decoded well-known charset number + * is not registered or supported. + */ + decode: function decode(data) { + let begin = data.offset; + + try { + return AcceptCharsetValue.decodeAnyCharset(data); + } catch (e) {} + + data.offset = begin; + + // `IntegerValue.decode` can return a array, which doesn't apply here. + let numOrArray = IntegerValue.decode(data); + if (typeof numOrArray != "number") { + throw new CodeError("Well-known-charset: invalid integer type"); + } + + let charset = numOrArray; + let entry = WSP_WELL_KNOWN_CHARSETS[charset]; + if (!entry) { + throw new NotWellKnownEncodingError( + "Well-known-charset: not well known charset " + charset); + } + + return {charset: entry.name}; + }, +}; + +/** + * The short form of the Content-type-value MUST only be used when the + * well-known media is in the range of 0-127 or a text string. In all other + * cases the general form MUST be used. + * + * Content-type-value = Constrained-media | Content-general-form + * Constrained-media = Constrained-encoding + * Content-general-form = Value-length Media-type + * Media-type = Media *(Parameter) + * Media = Well-known-media | Extension-Media + * Well-known-media = Integer-value + * Extension-Media = *TEXT End-of-string + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.24 + */ +let ContentTypeValue = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded object containing `media` and `params` properties or + * null in case of a failed parsing. The `media` property must be a + * string, and the `params` property is always null. + * + * @throws NotWellKnownEncodingError if decoded well-known content type number + * is not registered or supported. + */ + decodeConstrainedMedia: function decodeConstrainedMedia(data) { + let numOrStr = ConstrainedEncoding.decode(data); + if (typeof numOrStr == "string") { + return { + media: numOrStr.toLowerCase(), + params: null, + }; + } + + let number = numOrStr; + let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number]; + if (!entry) { + throw new NotWellKnownEncodingError( + "Constrained-media: not well known media " + number); + } + + return { + media: entry.type, + params: null, + }; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decode string. + * + * @throws CodeError if decoded content type number is an array. + * @throws NotWellKnownEncodingError if decoded well-known content type + * number is not registered or supported. + */ + decodeMedia: function decodeMedia(data) { + let begin = data.offset, number; + try { + number = IntegerValue.decode(data); + } catch (e) { + data.offset = begin; + return NullTerminatedTexts.decode(data).toLowerCase(); + } + + // `decodeIntegerValue` can return a array, which doesn't apply here. + if (typeof number != "number") { + throw new CodeError("Media: invalid integer type"); + } + + let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number]; + if (!entry) { + throw new NotWellKnownEncodingError("Media: not well known media " + number); + } + + return entry.type; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * @param end + * Ending offset of the Media-type value. + * + * @return A decoded object containing `media` and `params` properties or + * null in case of a failed parsing. The `media` property must be a + * string, and the `params` property is a hash map from a string to + * an value of unspecified type. + */ + decodeMediaType: function decodeMediaType(data, end) { + let media = this.decodeMedia(data); + let params = Parameter.decodeMultiple(data, end); + + return { + media: media, + params: params, + }; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded object containing `media` and `params` properties or + * null in case of a failed parsing. The `media` property must be a + * string, and the `params` property is null or a hash map from a + * string to an value of unspecified type. + */ + decodeContentGeneralForm: function decodeContentGeneralForm(data) { + let length = ValueLength.decode(data); + let end = data.offset + length; + + let value = this.decodeMediaType(data, end); + + if (data.offset != end) { + data.offset = end; + } + + return value; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return A decoded object containing `media` and `params` properties or + * null in case of a failed parsing. The `media` property must be a + * string, and the `params` property is null or a hash map from a + * string to an value of unspecified type. + */ + decode: function decode(data) { + let begin = data.offset; + + try { + return this.decodeConstrainedMedia(data); + } catch (e) { + data.offset = begin; + return this.decodeContentGeneralForm(data); + } + }, +}; + +/** + * Application-id-value = Uri-value | App-assigned-code + * App-assigned-code = Integer-value + * + * @see WAP-230-WSP-20010705-a clause 8.4.2.54 + */ +let ApplicationIdValue = { + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return Decoded string value. + * + * @throws CodeError if decoded application id number is an array. + * @throws NotWellKnownEncodingError if decoded well-known application id + * number is not registered or supported. + */ + decode: function decode(data) { + let begin = data.offset; + try { + return UriValue.decode(data); + } catch (e) {} + + data.offset = begin; + + // `decodeIntegerValue` can return a array, which doesn't apply here. + let numOrArray = IntegerValue.decode(data); + if (typeof numOrArray != "number") { + throw new CodeError("Application-id-value: invalid integer type"); + } + + let id = numOrArray; + let entry = OMNA_PUSH_APPLICATION_IDS[id]; + if (!entry) { + throw new NotWellKnownEncodingError( + "Application-id-value: not well known id: " + id); + } + + return entry.urn; + }, +}; + +let PduHelper = { + /** + * Parse multiple header fields with end mark. + * + * @param data + * A wrapped object containing raw PDU data. + * @param end + * An ending offset indicating the end of headers. + * @param headers [optional] + * An optional object to store parsed header fields. Created + * automatically if undefined. + * + * @return A object containing decoded header fields as its attributes. + */ + parseHeaders: function parseHeaders(data, end, headers) { + if (!headers) { + headers = {}; + } + + let header; + while (data.offset < end) { + try { + header = Header.decode(data); + } catch (e) { + break; + } + if (header) { + headers[header.name] = header.value; + } + } + + if (data.offset != end) { + debug("Parser expects ending in " + end + ", but in " + data.offset); + // Explicitly seek to end in case of skipped header fields. + data.offset = end; + } + + return headers; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * @param msg + * Message object to be populated with decoded header fields. + * + * @see WAP-230-WSP-20010705-a clause 8.2.4 + */ + parsePushHeaders: function parsePushHeaders(data, msg) { + if (!msg.headers) { + msg.headers = {}; + } + + let headersLen = UintVar.decode(data); + let headersEnd = data.offset + headersLen; + + let contentType = ContentTypeValue.decode(data); + msg.headers["content-type"] = contentType; + + msg.headers = this.parseHeaders(data, headersEnd, msg.headers); + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * + * @return An array of objects representing multipart entries or null in case + * of errors found. + * + * @see WAP-230-WSP-20010705-a section 8.5 + */ + parseMultiPart: function parseMultiPart(data) { + let nEntries = UintVar.decode(data); + if (!nEntries) { + return null; + } + + let parts = new Array(nEntries); + for (let i = 0; i < nEntries; i++) { + // Length of the ContentType and Headers fields combined. + let headersLen = UintVar.decode(data); + // Length of the Data field + let contentLen = UintVar.decode(data); + + let headersEnd = data.offset + headersLen; + let contentEnd = headersEnd + contentLen; + + try { + let headers = {}; + + let contentType = ContentTypeValue.decode(data); + headers["content-type"] = contentType; + headers["content-length"] = contentLen; + + headers = this.parseHeaders(data, headersEnd, headers); + + let content = Octet.decodeMultiple(data, contentEnd); + + parts[i] = { + index: i, + headers: headers, + content: content, + }; + } catch (e) { + debug("Failed to parse multipart entry, message: " + e.message); + // Placeholder to keep original index of following entries. + parts[i] = null; + } + + if (data.offset != contentEnd) { + // Seek to entry boundary for next entry. + data.offset = contentEnd; + } + } + + return parts; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * @param isSessionless + * Whether or not the PDU contains a session less WSP PDU. + * @param msg [optional] + * Optional pre-defined PDU object. + * + * @return Parsed WSP PDU object or null in case of errors found. + */ + parse: function parse(data, isSessionless, msg) { + if (!msg) { + msg = { + type: null, + }; + } + + try { + if (isSessionless) { + // "The `transactionId` is used to associate requests with replies in + // the connectionless session service." ~ WAP-230-WSP-20010705-a 8.2.1 + msg.transactionId = Octet.decode(data); + } + + msg.type = Octet.decode(data); + switch (msg.type) { + case WSP_PDU_TYPE_PUSH: + this.parsePushHeaders(data, msg); + break; + } + } catch (e) { + debug("Parse error. Message: " + e.message); + msg = null; + } + + return msg; + }, +}; + +// WSP Header Field Name Assignments +// Note: Items commented out are either deprecated or not implemented. +// Deprecated items should only be supported for backward compatibility +// purpose. +// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. +const WSP_HEADER_FIELDS = (function () { + let names = {}; + function add(name, number, coder) { + let entry = { + name: name, + number: number, + coder: coder, + }; + names[name] = names[number] = entry; + } + + //add("accept", 0x00); + //add("accept-charset", 0x01); Deprecated + //add("accept-encoding", 0x02); Deprecated + //add("accept-language", 0x03); + //add("accept-ranges", 0x04); + add("age", 0x05, DeltaSecondsValue); + //add("allow", 0x06); + //add("authorization", 0x07); + //add("cache-control", 0x08); Deprecated + //add("connection", 0x09); + //add("content-base", 0x0A); Deprecated + //add("content-encoding", 0x0B); + //add("content-language", 0x0C); + add("content-length", 0x0D, IntegerValue); + add("content-location", 0x0E, UriValue); + //add("content-md5", 0x0F); + //add("content-range", 0x10); Deprecated + add("content-type", 0x11, ContentTypeValue); + add("date", 0x12, DateValue); + add("etag", 0x13, TextString); + add("expires", 0x14, DateValue); + add("from", 0x15, TextString); + add("host", 0x16, TextString); + add("if-modified-since", 0x17, DateValue); + add("if-match", 0x18, TextString); + add("if-none-match", 0x19, TextString); + //add("if-range", 0x1A); + add("if-unmodified-since", 0x1B, DateValue); + add("location", 0x1C, UriValue); + add("last-modified", 0x1D, DateValue); + add("max-forwards", 0x1E, IntegerValue); + //add("pragma", 0x1F); + //add("proxy-authenticate", 0x20); + //add("proxy-authentication", 0x21); + //add("public", 0x22); + //add("range", 0x23); + add("referer", 0x24, UriValue); + //add("retry-after", 0x25); + add("server", 0x26, TextString); + //add("transfer-encoding", 0x27); + add("upgrade", 0x28, TextString); + add("user-agent", 0x29, TextString); + //add("vary", 0x2A); + add("via", 0x2B, TextString); + //add("warning", 0x2C); + //add("www-authenticate", 0x2D); + //add("content-disposition", 0x2E); Deprecated + add("x-wap-application-id", 0x2F, ApplicationIdValue); + add("x-wap-content-uri", 0x30, UriValue); + add("x-wap-initiator-uri", 0x31, UriValue); + //add("accept-application", 0x32); + add("bearer-indication", 0x33, IntegerValue); + add("push-flag", 0x34, ShortInteger); + add("profile", 0x35, UriValue); + //add("profile-diff", 0x36); + //add("profile-warning", 0x37); Deprecated + //add("expect", 0x38); + //add("te", 0x39); + //add("trailer", 0x3A); + add("accept-charset", 0x3B, AcceptCharsetValue); + //add("accept-encoding", 0x3C); + //add("cache-control", 0x3D); Deprecated + //add("content-range", 0x3E); + add("x-wap-tod", 0x3F, DateValue); + add("content-id", 0x40, QuotedString); + //add("set-cookie", 0x41); + //add("cookie", 0x42); + //add("encoding-version", 0x43); + //add("profile-warning", 0x44); + //add("content-disposition", 0x45); + //add("x-wap-security", 0x46); + //add("cache-control", 0x47); + + return names; +})(); + +// WSP Content Type Assignments +// @see http://www.wapforum.org/wina +const WSP_WELL_KNOWN_CONTENT_TYPES = (function () { + let types = {}; + + function add(type, number) { + let entry = { + type: type, + number: number, + }; + types[type] = types[number] = entry; + } + + // Well Known Values + add("application/vnd.wap.multipart.related", 0x33); + add("application/vnd.wap.mms-message", 0x3E); + + return types; +})(); + +// WSP Well-Known Parameter Assignments +// Note: Items commented out are either deprecated or not implemented. +// Deprecated items should not be used. +// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. +const WSP_WELL_KNOWN_PARAMS = (function () { + let params = {}; + + function add(name, number, coder) { + let entry = { + name: name, + number: number, + coder: coder, + }; + params[name] = params[number] = entry; + } + + add("q", 0x00, QValue); + add("charset", 0x01, WellKnownCharset); + add("level", 0x02, VersionValue); + add("type", 0x03, IntegerValue); + add("name", 0x05, TextValue); // Deprecated, but used in some carriers, eg. Hinet. + //add("filename", 0x06); Deprecated + add("differences", 0x07, FieldName); + add("padding", 0x08, ShortInteger); + add("type", 0x09, ConstrainedEncoding); + add("start", 0x0A, TextValue); // Deprecated, but used in some carriers, eg. T-Mobile. + //add("start-info", 0x0B); Deprecated + //add("comment", 0x0C); Deprecated + //add("domain", 0x0D); Deprecated + add("max-age", 0x0E, DeltaSecondsValue); + //add("path", 0x0F); Deprecated + add("secure", 0x10, NoValue); + add("sec", 0x11, ShortInteger); + add("mac", 0x12, TextValue); + add("creation-date", 0x13, DateValue); + add("modification-date", 0x14, DateValue); + add("read-date", 0x15, DateValue); + add("size", 0x16, IntegerValue); + add("name", 0x17, TextValue); + add("filename", 0x18, TextValue); + add("start", 0x19, TextValue); + add("start-info", 0x1A, TextValue); + add("comment", 0x1B, TextValue); + add("domain", 0x1C, TextValue); + add("path", 0x1D, TextValue); + + return params; +})(); + +// WSP Character Set Assignments +// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. +// @see http://www.iana.org/assignments/character-sets +const WSP_WELL_KNOWN_CHARSETS = (function () { + let charsets = {}; + + function add(name, number, converter) { + let entry = { + name: name, + number: number, + converter: converter, + }; + + charsets[name] = charsets[number] = entry; + } + + add("ansi_x3.4-1968", 3, null); + add("iso_8859-1:1987", 4, "ISO-8859-1"); + add("utf-8", 106, "UTF-8"); + add("windows-1252", 2252, "windows-1252"); + + return charsets; +})(); + +// OMNA PUSH Application ID +// @see http://www.openmobilealliance.org/tech/omna/omna-push-app-id.aspx +const OMNA_PUSH_APPLICATION_IDS = (function () { + let ids = {}; + + function add(urn, number) { + let entry = { + urn: urn, + number: number, + }; + + ids[urn] = ids[number] = entry; + } + + add("x-wap-application:mms.ua", 0x04); + + return ids; +})(); + +let debug; +if (DEBUG) { + debug = function (s) { + dump("-@- WspPduHelper: " + s + "\n"); + }; +} else { + debug = function (s) {}; +} + +const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([ + // Constant values + "WSP_HEADER_FIELDS", + "WSP_WELL_KNOWN_CONTENT_TYPES", + "WSP_WELL_KNOWN_PARAMS", + "WSP_WELL_KNOWN_CHARSETS", + "OMNA_PUSH_APPLICATION_IDS", + + // Error classes + "CodeError", + "FatalCodeError", + "NotWellKnownEncodingError", + + // Utility functions + "ensureHeader", + "skipValue", + "decodeAlternatives", + + // Decoders + "Octet", + "Text", + "NullTerminatedTexts", + "Token", + "URIC", + "TextString", + "TokenText", + "QuotedString", + "ShortInteger", + "LongInteger", + "UintVar", + "ConstrainedEncoding", + "ValueLength", + "NoValue", + "TextValue", + "IntegerValue", + "DateValue", + "DeltaSecondsValue", + "QValue", + "VersionValue", + "UriValue", + "Parameter", + "Header", + "WellKnownHeader", + "ApplicationHeader", + "FieldName", + "AcceptCharsetValue", + "WellKnownCharset", + "ContentTypeValue", + "ApplicationIdValue", + + // Parser + "PduHelper", +]); + diff --git a/dom/mms/src/ril/wap_consts.js b/dom/mms/src/ril/wap_consts.js new file mode 100644 index 00000000000..be92a8d13ec --- /dev/null +++ b/dom/mms/src/ril/wap_consts.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// WSP PDU Type Assignments +// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. +const WSP_PDU_TYPE_PUSH = 0x06; + +const ALL_CONST_SYMBOLS = Object.keys(this); + +const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS; + diff --git a/dom/mms/tests/header_helpers.js b/dom/mms/tests/header_helpers.js new file mode 100644 index 00000000000..28ff076f8a7 --- /dev/null +++ b/dom/mms/tests/header_helpers.js @@ -0,0 +1,118 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + + +let subscriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); + +/** + * Test whether specified function throws exception with expected + * result. + * + * @param func + * Function to be tested. + * @param exception + * Expected class name of thrown exception. Use null for no throws. + * @param stack + * Optional stack object to be printed. null for Components#stack#caller. + */ +function do_check_throws(func, result, stack) +{ + if (!stack) + stack = Components.stack.caller; + + try { + func(); + } catch (ex) { + if (ex.name == result) { + return; + } + do_throw("expected result " + result + ", caught " + ex, stack); + } + + if (result) { + do_throw("expected result " + result + ", none thrown", stack); + } +} + +/** + * Internal test function for comparing results. + * + * @param func + * A function under test. It should accept an arguement and return the + * result. + * @param data + * Input data for `func`. + * @param expect + * Expected result. + */ +function wsp_test_func(func, data, expect) { + let result_str = JSON.stringify(func(data)); + let expect_str = JSON.stringify(expect); + if (result_str !== expect_str) { + do_throw("expect decoded value: '" + expect_str + "', got '" + result_str + "'"); + } +} + +/** + * Test customized WSP PDU decoding. + * + * @param func + * Decoding func under test. It should return a decoded value if invoked. + * @param input + * Array of octets as test data. + * @param expect + * Expected decoded value, use null if expecting errors instead. + * @param exception + * Expected class name of thrown exception. Use null for no throws. + */ +function wsp_decode_test_ex(func, input, expect, exception) { + let data = {array: input, offset: 0}; + do_check_throws(wsp_test_func.bind(null, func, data, expect), exception); +} + +/** + * Test default WSP PDU decoding. + * + * @param target + * Target decoding object, ie. TextValue. + * @param input + * Array of octets as test data. + * @param expect + * Expected decoded value, use null if expecting errors instead. + * @param exception + * Expected class name of thrown exception. Use null for no throws. + */ +function wsp_decode_test(target, input, expect, exception) { + let func = function decode_func(data) { + return target.decode(data); + }; + + wsp_decode_test_ex(func, input, expect, exception); +} + +/** + * @param str + * A string. + * @param noAppendNull + * True to omit terminating NUL octet. Default false. + * + * @return A number array of char codes of characters in `str`. + */ +function strToCharCodeArray(str, noAppendNull) { + let result = []; + + for (let i = 0; i < str.length; i++) { + result.push(str.charCodeAt(i)); + } + if (!noAppendNull) { + result.push(0); + } + + return result; +} + diff --git a/dom/mms/tests/test_wsp_pdu_helper.js b/dom/mms/tests/test_wsp_pdu_helper.js new file mode 100644 index 00000000000..a8bc1428f69 --- /dev/null +++ b/dom/mms/tests/test_wsp_pdu_helper.js @@ -0,0 +1,814 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let WSP = {}; +subscriptLoader.loadSubScript("resource://gre/modules/WspPduHelper.jsm", WSP); +WSP.debug = do_print; + +function run_test() { + run_next_test(); +} + +// +// Test target: ensureHeader +// + +add_test(function test_ensureHeader() { + do_check_throws(function () { + WSP.ensureHeader({}, "no-such-property"); + }, "FatalCodeError" + ); + + run_next_test(); +}); + +// +// Test target: skipValue() +// + +add_test(function test_skipValue() { + // Test for zero-valued first octet: + wsp_decode_test_ex(function (data) { + return WSP.skipValue(data); + }, [0], null + ); + // Test first octet < 31 + wsp_decode_test_ex(function (data) { + return WSP.skipValue(data); + }, [1, 2], [2] + ); + // Test first octet = 31 + wsp_decode_test_ex(function (data) { + return WSP.skipValue(data); + }, [31, 0], null + ); + wsp_decode_test_ex(function (data) { + return WSP.skipValue(data); + }, [31, 1, 2], [2] + ); + // Test first octet <= 127 + wsp_decode_test_ex(function (data) { + return WSP.skipValue(data); + }, strToCharCodeArray("Hello world!"), "Hello world!" + ); + // Test first octet >= 128 + wsp_decode_test_ex(function (data) { + return WSP.skipValue(data); + }, [0x80 | 0x01], 0x01 + ); + + run_next_test(); +}); + +// +// Test target: Octet +// + +//// Octet.decode //// + +add_test(function test_Octet_decode() { + wsp_decode_test(WSP.Octet, [1], 1); + wsp_decode_test(WSP.Octet, [], null, "RangeError"); + + run_next_test(); +}); + +//// Octet.decodeMultiple //// + +add_test(function test_Octet_decodeMultiple() { + wsp_decode_test_ex(function (data) { + return WSP.Octet.decodeMultiple(data, 3); + }, [0, 1, 2], [0, 1, 2], null + ); + wsp_decode_test_ex(function (data) { + return WSP.Octet.decodeMultiple(data, 3); + }, new Uint8Array([0, 1, 2]), new Uint8Array([0, 1, 2]), null + ); + wsp_decode_test_ex(function (data) { + return WSP.Octet.decodeMultiple(data, 4); + }, [0, 1, 2], null, "RangeError" + ); + + run_next_test(); +}); + +//// Octet.decodeEqualTo //// + +add_test(function test_Octet_decodeEqualTo() { + wsp_decode_test_ex(function (data) { + return WSP.Octet.decodeEqualTo(data, 1); + }, [1], 1, null + ); + wsp_decode_test_ex(function (data) { + return WSP.Octet.decodeEqualTo(data, 2); + }, [1], null, "CodeError" + ); + wsp_decode_test_ex(function (data) { + return WSP.Octet.decodeEqualTo(data, 2); + }, [], null, "RangeError" + ); + + run_next_test(); +}); + +// +// Test target: Text +// + +//// Text.decode //// + +add_test(function test_Text_decode() { + for (let i = 0; i < 256; i++) { + if (i == 0) { + wsp_decode_test(WSP.Text, [0], null, "NullCharError"); + } else if ((i < WSP.CTLS) || (i == WSP.DEL)) { + wsp_decode_test(WSP.Text, [i], null, "CodeError"); + } else { + wsp_decode_test(WSP.Text, [i], String.fromCharCode(i)); + } + } + // Test \r\n(SP|HT)* sequence: + wsp_decode_test(WSP.Text, strToCharCodeArray("\r\n \t \t \t", true), " "); + wsp_decode_test(WSP.Text, strToCharCodeArray("\r\n \t \t \t"), " "); + wsp_decode_test(WSP.Text, strToCharCodeArray("\r\n \t \t \tA"), " "); + + run_next_test(); +}); + +// +// Test target: NullTerminatedTexts +// + +//// NullTerminatedTexts.decode //// + +add_test(function test_NullTerminatedTexts_decode() { + // Test incompleted string: + wsp_decode_test(WSP.NullTerminatedTexts, strToCharCodeArray(" ", true), null, "RangeError"); + // Test control char: + wsp_decode_test(WSP.NullTerminatedTexts, strToCharCodeArray(" \n"), null, "CodeError"); + // Test normal string: + wsp_decode_test(WSP.NullTerminatedTexts, strToCharCodeArray(""), ""); + wsp_decode_test(WSP.NullTerminatedTexts, strToCharCodeArray("oops"), "oops"); + // Test \r\n(SP|HT)* sequence: + wsp_decode_test(WSP.NullTerminatedTexts, strToCharCodeArray("A\r\n \t \t \tB"), "A B"); + + run_next_test(); +}); + +// +// Test target: Token +// + +//// Token.decode //// + +add_test(function test_Token_decode() { + let seps = "()<>@,;:\\\"/[]?={} \t"; + for (let i = 0; i < 256; i++) { + if (i == 0) { + wsp_decode_test(WSP.Token, [i], null, "NullCharError"); + } else if ((i < WSP.CTLS) || (i >= WSP.ASCIIS) + || (seps.indexOf(String.fromCharCode(i)) >= 0)) { + wsp_decode_test(WSP.Token, [i], null, "CodeError"); + } else { + wsp_decode_test(WSP.Token, [i], String.fromCharCode(i)); + } + } + + run_next_test(); +}); + +// +// Test target: URIC +// + +//// URIC.decode //// + +add_test(function test_URIC_decode() { + let uric = "!#$%&'()*+,-./0123456789:;=?@ABCDEFGHIJKLMN" + + "OPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"; + for (let i = 0; i < 256; i++) { + if (i == 0) { + wsp_decode_test(WSP.URIC, [i], null, "NullCharError"); + } else if (uric.indexOf(String.fromCharCode(i)) >= 0) { + wsp_decode_test(WSP.URIC, [i], String.fromCharCode(i)); + } else { + wsp_decode_test(WSP.URIC, [i], null, "CodeError"); + } + } + + run_next_test(); +}); + +// +// Test target: TextString +// + +//// TextString.decode //// + +add_test(function test_TextString_decode() { + // Test quoted string + wsp_decode_test(WSP.TextString, [127, 128, 0], String.fromCharCode(128)); + // Test illegal quoted string + wsp_decode_test(WSP.TextString, [127, 32, 0], null, "CodeError"); + // Test illegal unquoted string + wsp_decode_test(WSP.TextString, [128, 0], null, "CodeError"); + // Test normal string + wsp_decode_test(WSP.TextString, [32, 0], " "); + + run_next_test(); +}); + +// +// Test target: TokenText +// + +//// TokenText.decode //// + +add_test(function test_TokenText_decode() { + wsp_decode_test(WSP.TokenText, [65], null, "RangeError"); + wsp_decode_test(WSP.TokenText, [0], ""); + wsp_decode_test(WSP.TokenText, [65, 0], "A"); + + run_next_test(); +}); + +// +// Test target: QuotedString +// + +//// QuotedString.decode //// + +add_test(function test_QuotedString_decode() { + // Test non-quoted string + wsp_decode_test(WSP.QuotedString, [32, 0], null, "CodeError"); + // Test incompleted string + wsp_decode_test(WSP.QuotedString, [34, 32], null, "RangeError"); + wsp_decode_test(WSP.QuotedString, [34, 32, 0], " "); + + run_next_test(); +}); + +// +// Test target: ShortInteger +// + +//// ShortInteger.decode //// + +add_test(function test_ShortInteger_decode() { + for (let i = 0; i < 256; i++) { + if (i & 0x80) { + wsp_decode_test(WSP.ShortInteger, [i], i & 0x7F); + } else { + wsp_decode_test(WSP.ShortInteger, [i], null, "CodeError"); + } + } + + run_next_test(); +}); + +// +// Test target: LongInteger +// + +//// LongInteger.decode //// + +function LongInteger_testcases(target) { + // Test LongInteger of zero octet + wsp_decode_test(target, [0, 0], null, "CodeError"); + wsp_decode_test(target, [1, 0x80], 0x80); + wsp_decode_test(target, [2, 0x80, 2], 0x8002); + wsp_decode_test(target, [3, 0x80, 2, 3], 0x800203); + wsp_decode_test(target, [4, 0x80, 2, 3, 4], 0x80020304); + wsp_decode_test(target, [5, 0x80, 2, 3, 4, 5], 0x8002030405); + wsp_decode_test(target, [6, 0x80, 2, 3, 4, 5, 6], 0x800203040506); + // Test LongInteger of more than 6 octets + wsp_decode_test(target, [7, 0x80, 2, 3, 4, 5, 6, 7], [0x80, 2, 3, 4, 5, 6, 7]); + // Test LongInteger of more than 30 octets + wsp_decode_test(target, [31], null, "CodeError"); +} +add_test(function test_LongInteger_decode() { + LongInteger_testcases(WSP.LongInteger); + + run_next_test(); +}); + +// +// Test target: UintVar +// + +//// UintVar.decode //// + +add_test(function test_UintVar_decode() { + wsp_decode_test(WSP.UintVar, [0x80], null, "RangeError"); + // Test up to max 53 bits integer + wsp_decode_test(WSP.UintVar, [0x7F], 0x7F); + wsp_decode_test(WSP.UintVar, [0xFF, 0x7F], 0x3FFF); + wsp_decode_test(WSP.UintVar, [0xFF, 0xFF, 0x7F], 0x1FFFFF); + wsp_decode_test(WSP.UintVar, [0xFF, 0xFF, 0xFF, 0x7F], 0xFFFFFFF); + wsp_decode_test(WSP.UintVar, [0xFF, 0xFF, 0xFF, 0xFF, 0x7F], 0x7FFFFFFFF); + wsp_decode_test(WSP.UintVar, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F], 0x3FFFFFFFFFF); + wsp_decode_test(WSP.UintVar, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F], 0x1FFFFFFFFFFFF); + wsp_decode_test(WSP.UintVar, [0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F], 0x1FFFFFFFFFFFFF); + wsp_decode_test(WSP.UintVar, [0x01, 0x02], 1); + wsp_decode_test(WSP.UintVar, [0x80, 0x01, 0x02], 1); + wsp_decode_test(WSP.UintVar, [0x80, 0x80, 0x80, 0x01, 0x2], 1); + + run_next_test(); +}); + +// +// Test target: ConstrainedEncoding (decodeAlternatives) +// + +//// ConstrainedEncoding.decode //// + +add_test(function test_ConstrainedEncoding_decode() { + wsp_decode_test(WSP.ConstrainedEncoding, [0x80], 0); + wsp_decode_test(WSP.ConstrainedEncoding, [32, 0], " "); + + run_next_test(); +}); + +// +// Test target: ValueLength +// + +//// ValueLength.decode //// + +add_test(function test_ValueLength_decode() { + for (let i = 0; i < 256; i++) { + if (i < 31) { + wsp_decode_test(WSP.ValueLength, [i, 0x8F, 0x7F], i); + } else if (i == 31) { + wsp_decode_test(WSP.ValueLength, [i, 0x8F, 0x7F], 0x7FF); + } else { + wsp_decode_test(WSP.ValueLength, [i, 0x8F, 0x7F], null, "CodeError"); + } + } + + run_next_test(); +}); + +// +// Test target: NoValue +// + +//// NoValue.decode //// + +add_test(function test_NoValue_decode() { + wsp_decode_test(WSP.NoValue, [0], null); + for (let i = 1; i < 256; i++) { + wsp_decode_test(WSP.NoValue, [i], null, "CodeError"); + } + + run_next_test(); +}); + +// +// Test target: TextValue +// + +//// TextValue.decode //// + +add_test(function test_TextValue_decode() { + wsp_decode_test(WSP.TextValue, [0], null); + wsp_decode_test(WSP.TextValue, [65, 0], "A"); + wsp_decode_test(WSP.TextValue, [32, 0], null, "CodeError"); + wsp_decode_test(WSP.TextValue, [34, 32, 0], " "); + + run_next_test(); +}); + +// +// Test target: IntegerValue +// + +//// IntegerValue.decode //// + +add_test(function test_IntegerValue_decode() { + for (let i = 128; i < 256; i++) { + wsp_decode_test(WSP.IntegerValue, [i], i & 0x7F); + } + + LongInteger_testcases(WSP.IntegerValue); + + run_next_test(); +}); + +// +// Test target: DateValue +// + +//// DateValue.decode //// + +add_test(function test_DateValue_decode() { + wsp_decode_test(WSP.DateValue, [0, 0], null, "CodeError"); + wsp_decode_test(WSP.DateValue, [1, 0x80], new Date(0x80 * 1000)); + wsp_decode_test(WSP.DateValue, [31], null, "CodeError"); + + run_next_test(); +}); + +// +// Test target: DeltaSecondsValue +// + // DeltaSecondsValue is only an alias of IntegerValue. + +// +// Test target: QValue +// + +//// QValue.decode //// + +add_test(function test_QValue_decode() { + wsp_decode_test(WSP.QValue, [0], null, "CodeError"); + wsp_decode_test(WSP.QValue, [1], 0); + wsp_decode_test(WSP.QValue, [100], 0.99); + wsp_decode_test(WSP.QValue, [101], 0.001); + wsp_decode_test(WSP.QValue, [0x88, 0x4B], 0.999); + wsp_decode_test(WSP.QValue, [0x88, 0x4C], null, "CodeError"); + + run_next_test(); +}); + +// +// Test target: VersionValue +// + +//// VersionValue.decode //// + +add_test(function test_VersionValue_decode() { + for (let major = 1; major < 8; major++) { + let version = (major << 4) | 0x0F; + wsp_decode_test(WSP.VersionValue, [0x80 | version], version); + wsp_decode_test(WSP.VersionValue, [major + 0x30, 0], version); + + for (let minor = 0; minor < 15; minor++) { + version = (major << 4) | minor; + wsp_decode_test(WSP.VersionValue, [0x80 | version], version); + if (minor >= 10) { + wsp_decode_test(WSP.VersionValue, [major + 0x30, 0x2E, 0x31, (minor - 10) + 0x30, 0], version); + } else { + wsp_decode_test(WSP.VersionValue, [major + 0x30, 0x2E, minor + 0x30, 0], version); + } + } + } + + run_next_test(); +}); + +// +// Test target: UriValue +// + +//// UriValue.decode //// + +add_test(function test_UriValue_decode() { + wsp_decode_test(WSP.UriValue, [97], null, "RangeError"); + wsp_decode_test(WSP.UriValue, [0], ""); + wsp_decode_test(WSP.UriValue, [65, 0], "A"); + + run_next_test(); +}); + +// +// Test target: Parameter +// + +//// Parameter.decodeTypedParameter //// + +add_test(function test_Parameter_decodeTypedParameter() { + // Test for array-typed return value from IntegerValue + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeTypedParameter(data); + }, [7, 0, 0, 0, 0, 0, 0, 0], null, "CodeError" + ); + // Test for number-typed return value from IntegerValue + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeTypedParameter(data); + }, [1, 0, 0], {name: "q", value: null} + ); + // Test for NotWellKnownEncodingError + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeTypedParameter(data); + }, [1, 0xFF], null, "NotWellKnownEncodingError" + ); + // Test for parameter specific decoder + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeTypedParameter(data); + }, [1, 0, 100], {name: "q", value: 0.99} + ); + // Test for TextValue + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeTypedParameter(data); + }, [1, 0x10, 48, 46, 57, 57, 0], {name: "secure", value: "0.99"} + ); + // Test for TextString + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeTypedParameter(data); + }, [1, 0x19, 60, 115, 109, 105, 108, 62, 0], {name: "start", value: ""} + ); + // Test for skipValue + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeTypedParameter(data); + }, [1, 0x19, 128], null + ); + + run_next_test(); +}); + +//// Parameter.decodeUntypedParameter //// + +add_test(function test_Parameter_decodeUntypedParameter() { + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeUntypedParameter(data); + }, [1], null, "CodeError" + ); + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeUntypedParameter(data); + }, [65, 0, 0], {name: "a", value: null} + ); + // Test for IntegerValue + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeUntypedParameter(data); + }, [65, 0, 1, 0], {name: "a", value: 0} + ); + // Test for TextValue + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeUntypedParameter(data); + }, [65, 0, 66, 0], {name: "a", value: "B"} + ); + + run_next_test(); +}); + +//// Parameter.decode //// + +add_test(function test_Parameter_decode() { + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decode(data); + }, [1, 0x19, 60, 115, 109, 105, 108, 62, 0], {name: "start", value: ""} + ); + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decode(data); + }, [65, 0, 66, 0], {name: "a", value: "B"} + ); + + run_next_test(); +}); + +//// Parameter.decodeMultiple //// + +add_test(function test_Parameter_decodeMultiple() { + wsp_decode_test_ex(function (data) { + return WSP.Parameter.decodeMultiple(data, 13); + }, [1, 0x19, 60, 115, 109, 105, 108, 62, 0, 65, 0, 66, 0], {start: "", a: "B"} + ); + + run_next_test(); +}); + +// +// Test target: Header +// + +//// Header.decode //// + +add_test(function test_Header_decode() { + wsp_decode_test(WSP.Header, [0x34 | 0x80, 0x80], {name: "push-flag", value: 0}); + wsp_decode_test(WSP.Header, [65, 0, 66, 0], {name: "a", value: "B"}); + + run_next_test(); +}); + +// +// Test target: WellKnownHeader +// + +//// WellKnownHeader.decode //// + +add_test(function test_WellKnownHeader_decode() { + wsp_decode_test(WSP.WellKnownHeader, [0xFF], null, "NotWellKnownEncodingError"); + let (entry = WSP.WSP_HEADER_FIELDS["push-flag"]) { + // Test for Short-Integer + wsp_decode_test(WSP.WellKnownHeader, [entry.number | 0x80, 0x80], + {name: entry.name, value: 0}); + // Test for NoValue + wsp_decode_test(WSP.WellKnownHeader, [entry.number | 0x80, 0], + {name: entry.name, value: null}); + // Test for TokenText + wsp_decode_test(WSP.WellKnownHeader, [entry.number | 0x80, 65, 0], + {name: entry.name, value: "A"}); + // Test for QuotedString + wsp_decode_test(WSP.WellKnownHeader, [entry.number | 0x80, 34, 128, 0], + {name: entry.name, value: String.fromCharCode(128)}); + // Test for skipValue + wsp_decode_test(WSP.WellKnownHeader, [entry.number | 0x80, 2, 0, 0], null); + } + + run_next_test(); +}); + +// +// Test target: ApplicationHeader +// + +//// ApplicationHeader.decode //// + +add_test(function test_ApplicationHeader_decode() { + wsp_decode_test(WSP.ApplicationHeader, [5, 0, 66, 0], null, "CodeError"); + wsp_decode_test(WSP.ApplicationHeader, [65, 0, 66, 0], {name: "a", value: "B"}); + // Test for skipValue + wsp_decode_test(WSP.ApplicationHeader, [65, 0, 2, 0, 0], null); + + run_next_test(); +}); + +// +// Test target: FieldName +// + +//// FieldName.decode //// + +add_test(function test_FieldName_decode() { + wsp_decode_test(WSP.FieldName, [0], ""); + wsp_decode_test(WSP.FieldName, [65, 0], "a"); + wsp_decode_test(WSP.FieldName, [97, 0], "a"); + let (entry = WSP.WSP_HEADER_FIELDS["content-length"]) { + wsp_decode_test(WSP.FieldName, [entry.number | 0x80], entry.name); + } + wsp_decode_test(WSP.FieldName, [0xFF], null, "NotWellKnownEncodingError"); + + run_next_test(); +}); + +// +// Test target: AcceptCharsetValue +// + +//// AcceptCharsetValue.decode //// + +add_test(function test_AcceptCharsetValue_decode() { + wsp_decode_test(WSP.AcceptCharsetValue, [0xFF], null, "CodeError"); + // Test for Any-Charset + wsp_decode_test(WSP.AcceptCharsetValue, [128], {charset: "*"}); + // Test for Constrained-Charset + wsp_decode_test(WSP.AcceptCharsetValue, [65, 0], {charset: "A"}); + let (entry = WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"]) { + wsp_decode_test(WSP.AcceptCharsetValue, [entry.number | 0x80], {charset: entry.name}); + } + // Test for Accept-Charset-General-Form + wsp_decode_test(WSP.AcceptCharsetValue, [1, 128], {charset: "*"}); + let (entry = WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"]) { + wsp_decode_test(WSP.AcceptCharsetValue, [2, 1, entry.number], {charset: entry.name}); + wsp_decode_test(WSP.AcceptCharsetValue, [1, entry.number | 0x80], {charset: entry.name}); + } + wsp_decode_test(WSP.AcceptCharsetValue, [3, 65, 0, 100], {charset: "A", q: 0.99}); + + run_next_test(); +}); + +// +// Test target: WellKnownCharset +// + +//// WellKnownCharset.decode //// + +add_test(function test_WellKnownCharset_decode() { + wsp_decode_test(WSP.WellKnownCharset, [0xFF], null, "NotWellKnownEncodingError"); + // Test for Any-Charset + wsp_decode_test(WSP.WellKnownCharset, [128], {charset: "*"}); + // Test for number-typed return value from IntegerValue + wsp_decode_test(WSP.WellKnownCharset, [1, 3], {charset: "ansi_x3.4-1968"}); + // Test for array-typed return value from IntegerValue + wsp_decode_test(WSP.WellKnownCharset, [7, 0, 0, 0, 0, 0, 0, 0, 3], null, "CodeError"); + + run_next_test(); +}); + +// +// Test target: ContentTypeValue +// + +//// ContentTypeValue.decodeConstrainedMedia //// + +add_test(function test_ContentTypeValue_decodeConstrainedMedia() { + // Test for string-typed return value from ConstrainedEncoding + wsp_decode_test_ex(function (data) { + return WSP.ContentTypeValue.decodeConstrainedMedia(data); + }, [65, 0], {media: "a", params: null} + ); + // Test for number-typed return value from ConstrainedEncoding + wsp_decode_test_ex(function (data) { + return WSP.ContentTypeValue.decodeConstrainedMedia(data); + }, [0x33 | 0x80], {media: "application/vnd.wap.multipart.related", params: null} + ); + // Test for NotWellKnownEncodingError + wsp_decode_test_ex(function (data) { + return WSP.ContentTypeValue.decodeConstrainedMedia(data); + }, [0x80], null, "NotWellKnownEncodingError" + ); + + run_next_test(); +}); + +//// ContentTypeValue.decodeMedia //// + +add_test(function test_ContentTypeValue_decodeMedia() { + // Test for NullTerminatedTexts + wsp_decode_test_ex(function (data) { + return WSP.ContentTypeValue.decodeMedia(data); + }, [65, 0], "a" + ); + // Test for IntegerValue + wsp_decode_test_ex(function (data) { + return WSP.ContentTypeValue.decodeMedia(data); + }, [0x3E | 0x80], "application/vnd.wap.mms-message" + ); + wsp_decode_test_ex(function (data) { + return WSP.ContentTypeValue.decodeMedia(data); + }, [0x80], null, "NotWellKnownEncodingError" + ); + + run_next_test(); +}); + +//// ContentTypeValue.decodeMediaType //// + +add_test(function test_ContentTypeValue_decodeMediaType() { + wsp_decode_test_ex(function (data) { + return WSP.ContentTypeValue.decodeMediaType(data, 1); + }, [0x3E | 0x80], + {media: "application/vnd.wap.mms-message", params: null} + ); + wsp_decode_test_ex(function (data) { + return WSP.ContentTypeValue.decodeMediaType(data, 14); + }, [0x3E | 0x80, 1, 0x19, 60, 115, 109, 105, 108, 62, 0, 65, 0, 66, 0], + {media: "application/vnd.wap.mms-message", params: {start: "", a: "B"}} + ); + + run_next_test(); +}); + +//// ContentTypeValue.decodeContentGeneralForm //// + +add_test(function test_ContentTypeValue_decodeContentGeneralForm() { + wsp_decode_test_ex(function (data) { + return WSP.ContentTypeValue.decodeContentGeneralForm(data); + }, [14, 0x3E | 0x80, 1, 0x19, 60, 115, 109, 105, 108, 62, 0, 65, 0, 66, 0], + {media: "application/vnd.wap.mms-message", params: {start: "", a: "B"}} + ); + + run_next_test(); +}); + +//// ContentTypeValue.decode //// + +add_test(function test_ContentTypeValue_decode() { + wsp_decode_test(WSP.ContentTypeValue, + [14, 0x3E | 0x80, 1, 0x19, 60, 115, 109, 105, 108, 62, 0, 65, 0, 66, 0], + {media: "application/vnd.wap.mms-message", params: {start: "", a: "B"}} + ); + + wsp_decode_test(WSP.ContentTypeValue, [0x33 | 0x80], + {media: "application/vnd.wap.multipart.related", params: null} + ); + + run_next_test(); +}); + +// +// Test target: ApplicationIdValue +// + +//// ApplicationIdValue.decode //// + +add_test(function test_ApplicationIdValue_decode() { + wsp_decode_test(WSP.ApplicationIdValue, [0], ""); + wsp_decode_test(WSP.ApplicationIdValue, [65, 0], "A"); + wsp_decode_test(WSP.ApplicationIdValue, [97, 0], "a"); + let (entry = WSP.OMNA_PUSH_APPLICATION_IDS["x-wap-application:mms.ua"]) { + wsp_decode_test(WSP.ApplicationIdValue, [entry.number | 0x80], entry.urn); + wsp_decode_test(WSP.ApplicationIdValue, [1, entry.number], entry.urn); + } + wsp_decode_test(WSP.ApplicationIdValue, [0xFF], null, "NotWellKnownEncodingError"); + + run_next_test(); +}); + +// +// Test target: PduHelper +// + +//// PduHelper.parseHeaders //// + +add_test(function test_PduHelper_parseHeaders() { + wsp_decode_test_ex(function (data) { + return WSP.PduHelper.parseHeaders(data, data.array.length); + }, [0x80 | 0x05, 2, 0x23, 0x28, 0x80 | 0x2F, 0x80 | 0x04], + {"age": 9000, "x-wap-application-id": "x-wap-application:mms.ua"} + ); + + run_next_test(); +}); + diff --git a/dom/mms/tests/xpcshell.ini b/dom/mms/tests/xpcshell.ini new file mode 100644 index 00000000000..c7e24a07873 --- /dev/null +++ b/dom/mms/tests/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head = header_helpers.js +tail = + +[test_wsp_pdu_helper.js] diff --git a/testing/xpcshell/xpcshell.ini b/testing/xpcshell/xpcshell.ini index 9233ac09752..a2ded891bda 100644 --- a/testing/xpcshell/xpcshell.ini +++ b/testing/xpcshell/xpcshell.ini @@ -11,6 +11,7 @@ [include:image/test/unit/xpcshell.ini] [include:dom/plugins/test/unit/xpcshell.ini] [include:dom/sms/tests/xpcshell.ini] +[include:dom/mms/tests/xpcshell.ini] [include:dom/src/json/test/unit/xpcshell.ini] [include:dom/system/gonk/tests/xpcshell.ini] [include:dom/tests/unit/xpcshell.ini]