gecko/dom/mobilemessage/gonk/SmsSegmentHelper.jsm

426 lines
14 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
let RIL = {};
Cu.import("resource://gre/modules/ril_consts.js", RIL);
/**
* SmsSegmentHelper
*/
this.SmsSegmentHelper = {
/**
* Get valid SMS concatenation reference number.
*/
_segmentRef: 0,
get nextSegmentRef() {
let ref = this._segmentRef++;
this._segmentRef %= (this.segmentRef16Bit ? 65535 : 255);
// 0 is not a valid SMS concatenation reference number.
return ref + 1;
},
/**
* Calculate encoded length using specified locking/single shift table
*
* @param aMessage
* message string to be encoded.
* @param aLangTable
* locking shift table string.
* @param aLangShiftTable
* single shift table string.
* @param aStrict7BitEncoding [Optional]
* Enable Latin characters replacement with corresponding
* ones in GSM SMS 7-bit default alphabet.
*
* @return encoded length in septets.
*
* @note The algorithm used in this function must match exactly with
* GsmPDUHelper#writeStringAsSeptets.
*/
countGsm7BitSeptets: function(aMessage, aLangTable, aLangShiftTable, aStrict7BitEncoding) {
let length = 0;
for (let msgIndex = 0; msgIndex < aMessage.length; msgIndex++) {
let c = aMessage.charAt(msgIndex);
if (aStrict7BitEncoding) {
c = RIL.GSM_SMS_STRICT_7BIT_CHARMAP[c] || c;
}
let septet = aLangTable.indexOf(c);
// According to 3GPP TS 23.038, section 6.1.1 General notes, "The
// characters marked '1)' are not used but are displayed as a space."
if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) {
continue;
}
if (septet >= 0) {
length++;
continue;
}
septet = aLangShiftTable.indexOf(c);
if (septet < 0) {
if (!aStrict7BitEncoding) {
return -1;
}
// Bug 816082, when aStrict7BitEncoding is enabled, we should replace
// characters that can't be encoded with GSM 7-Bit alphabets with '*'.
c = "*";
if (aLangTable.indexOf(c) >= 0) {
length++;
} else if (aLangShiftTable.indexOf(c) >= 0) {
length += 2;
} else {
// We can't even encode a '*' character with current configuration.
return -1;
}
continue;
}
// According to 3GPP TS 23.038 B.2, "This code represents a control
// character and therefore must not be used for language specific
// characters."
if (septet == RIL.PDU_NL_RESERVED_CONTROL) {
continue;
}
// The character is not found in locking shfit table, but could be
// encoded as <escape><char> with single shift table. Note that it's
// still possible for septet to has the value of PDU_NL_EXTENDED_ESCAPE,
// but we can display it as a space in this case as said in previous
// comment.
length += 2;
}
return length;
},
/**
* Calculate user data length of specified message string encoded in GSM 7Bit
* alphabets.
*
* @param aMessage
* a message string to be encoded.
* @param aStrict7BitEncoding [Optional]
* Enable Latin characters replacement with corresponding
* ones in GSM SMS 7-bit default alphabet.
*
* @return null or an options object with attributes `dcs`,
* `userDataHeaderLength`, `encodedFullBodyLength`, `langIndex`,
* `langShiftIndex`, `segmentMaxSeq` set.
*
* @see #calculateUserDataLength().
*
* |enabledGsmTableTuples|:
* List of tuples of national language identifier pairs.
* TODO: Support static/runtime settings, see bug 733331.
* |segmentRef16Bit|:
* Use 16-bit reference number for concatenated outgoint messages.
* TODO: Support static/runtime settings, see bug 733331.
*/
enabledGsmTableTuples: [
[RIL.PDU_NL_IDENTIFIER_DEFAULT, RIL.PDU_NL_IDENTIFIER_DEFAULT],
],
segmentRef16Bit: false,
calculateUserDataLength7Bit: function(aMessage, aStrict7BitEncoding) {
let options = null;
let minUserDataSeptets = Number.MAX_VALUE;
for (let i = 0; i < this.enabledGsmTableTuples.length; i++) {
let [langIndex, langShiftIndex] = this.enabledGsmTableTuples[i];
const langTable = RIL.PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
const langShiftTable = RIL.PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
let bodySeptets = this.countGsm7BitSeptets(aMessage,
langTable,
langShiftTable,
aStrict7BitEncoding);
if (bodySeptets < 0) {
continue;
}
let headerLen = 0;
if (langIndex != RIL.PDU_NL_IDENTIFIER_DEFAULT) {
headerLen += 3; // IEI + len + langIndex
}
if (langShiftIndex != RIL.PDU_NL_IDENTIFIER_DEFAULT) {
headerLen += 3; // IEI + len + langShiftIndex
}
// Calculate full user data length, note the extra byte is for header len
let headerSeptets = Math.ceil((headerLen ? headerLen + 1 : 0) * 8 / 7);
let segmentSeptets = RIL.PDU_MAX_USER_DATA_7BIT;
if ((bodySeptets + headerSeptets) > segmentSeptets) {
headerLen += this.segmentRef16Bit ? 6 : 5;
headerSeptets = Math.ceil((headerLen + 1) * 8 / 7);
segmentSeptets -= headerSeptets;
}
let segments = Math.ceil(bodySeptets / segmentSeptets);
let userDataSeptets = bodySeptets + headerSeptets * segments;
if (userDataSeptets >= minUserDataSeptets) {
continue;
}
minUserDataSeptets = userDataSeptets;
options = {
dcs: RIL.PDU_DCS_MSG_CODING_7BITS_ALPHABET,
encodedFullBodyLength: bodySeptets,
userDataHeaderLength: headerLen,
langIndex: langIndex,
langShiftIndex: langShiftIndex,
segmentMaxSeq: segments,
segmentChars: segmentSeptets,
};
}
return options;
},
/**
* Calculate user data length of specified message string encoded in UCS2.
*
* @param aMessage
* a message string to be encoded.
*
* @return an options object with attributes `dcs`, `userDataHeaderLength`,
* `encodedFullBodyLength`, `segmentMaxSeq` set.
*
* @see #calculateUserDataLength().
*/
calculateUserDataLengthUCS2: function(aMessage) {
let bodyChars = aMessage.length;
let headerLen = 0;
let headerChars = Math.ceil((headerLen ? headerLen + 1 : 0) / 2);
let segmentChars = RIL.PDU_MAX_USER_DATA_UCS2;
if ((bodyChars + headerChars) > segmentChars) {
headerLen += this.segmentRef16Bit ? 6 : 5;
headerChars = Math.ceil((headerLen + 1) / 2);
segmentChars -= headerChars;
}
let segments = Math.ceil(bodyChars / segmentChars);
return {
dcs: RIL.PDU_DCS_MSG_CODING_16BITS_ALPHABET,
encodedFullBodyLength: bodyChars * 2,
userDataHeaderLength: headerLen,
segmentMaxSeq: segments,
segmentChars: segmentChars,
};
},
/**
* Calculate user data length and its encoding.
*
* @param aMessage
* a message string to be encoded.
* @param aStrict7BitEncoding [Optional]
* Enable Latin characters replacement with corresponding
* ones in GSM SMS 7-bit default alphabet.
*
* @return an options object with some or all of following attributes set:
*
* @param dcs
* Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
* constants.
* @param userDataHeaderLength
* Length of embedded user data header, in bytes. The whole header
* size will be userDataHeaderLength + 1; 0 for no header.
* @param encodedFullBodyLength
* Length of the message body when encoded with the given DCS. For
* UCS2, in bytes; for 7-bit, in septets.
* @param langIndex
* Table index used for normal 7-bit encoded character lookup.
* @param langShiftIndex
* Table index used for escaped 7-bit encoded character lookup.
* @param segmentMaxSeq
* Max sequence number of a multi-part messages, or 1 for single one.
* This number might not be accurate for a multi-part message until
* it's processed by #fragmentText() again.
*/
calculateUserDataLength: function(aMessage, aStrict7BitEncoding) {
let options = this.calculateUserDataLength7Bit(aMessage, aStrict7BitEncoding);
if (!options) {
options = this.calculateUserDataLengthUCS2(aMessage);
}
return options;
},
/**
* Fragment GSM 7-Bit encodable string for transmission.
*
* @param aText
* text string to be fragmented.
* @param aLangTable
* locking shift table string.
* @param aLangShiftTable
* single shift table string.
* @param aSegmentSeptets
* Number of available spetets per segment.
* @param aStrict7BitEncoding [Optional]
* Enable Latin characters replacement with corresponding
* ones in GSM SMS 7-bit default alphabet.
*
* @return an array of objects. See #fragmentText() for detailed definition.
*/
fragmentText7Bit: function(aText, aLangTable, aLangShiftTable, aSegmentSeptets, aStrict7BitEncoding) {
let ret = [];
let body = "", len = 0;
// If the message is empty, we only push the empty message to ret.
if (aText.length === 0) {
ret.push({
body: aText,
encodedBodyLength: aText.length,
});
return ret;
}
for (let i = 0, inc = 0; i < aText.length; i++) {
let c = aText.charAt(i);
if (aStrict7BitEncoding) {
c = RIL.GSM_SMS_STRICT_7BIT_CHARMAP[c] || c;
}
let septet = aLangTable.indexOf(c);
if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) {
continue;
}
if (septet >= 0) {
inc = 1;
} else {
septet = aLangShiftTable.indexOf(c);
if (septet == RIL.PDU_NL_RESERVED_CONTROL) {
continue;
}
inc = 2;
if (septet < 0) {
if (!aStrict7BitEncoding) {
throw new Error("Given text cannot be encoded with GSM 7-bit Alphabet!");
}
// Bug 816082, when aStrict7BitEncoding is enabled, we should replace
// characters that can't be encoded with GSM 7-Bit alphabets with '*'.
c = "*";
if (aLangTable.indexOf(c) >= 0) {
inc = 1;
}
}
}
if ((len + inc) > aSegmentSeptets) {
ret.push({
body: body,
encodedBodyLength: len,
});
body = c;
len = inc;
} else {
body += c;
len += inc;
}
}
if (len) {
ret.push({
body: body,
encodedBodyLength: len,
});
}
return ret;
},
/**
* Fragment UCS2 encodable string for transmission.
*
* @param aText
* text string to be fragmented.
* @param aSegmentChars
* Number of available characters per segment.
*
* @return an array of objects. See #fragmentText() for detailed definition.
*/
fragmentTextUCS2: function(aText, aSegmentChars) {
let ret = [];
// If the message is empty, we only push the empty message to ret.
if (aText.length === 0) {
ret.push({
body: aText,
encodedBodyLength: aText.length,
});
return ret;
}
for (let offset = 0; offset < aText.length; offset += aSegmentChars) {
let str = aText.substr(offset, aSegmentChars);
ret.push({
body: str,
encodedBodyLength: str.length * 2,
});
}
return ret;
},
/**
* Fragment string for transmission.
*
* Fragment input text string into an array of objects that contains
* attributes `body`, substring for this segment, `encodedBodyLength`,
* length of the encoded segment body in septets.
*
* @param aText
* Text string to be fragmented.
* @param aOptions [Optional]
* Optional pre-calculated option object. The output array will be
* stored at aOptions.segments if there are multiple segments.
* @param aStrict7BitEncoding [Optional]
* Enable Latin characters replacement with corresponding
* ones in GSM SMS 7-bit default alphabet.
*
* @return Populated options object.
*/
fragmentText: function(aText, aOptions, aStrict7BitEncoding) {
if (!aOptions) {
aOptions = this.calculateUserDataLength(aText, aStrict7BitEncoding);
}
if (aOptions.dcs == RIL.PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
const langTable = RIL.PDU_NL_LOCKING_SHIFT_TABLES[aOptions.langIndex];
const langShiftTable = RIL.PDU_NL_SINGLE_SHIFT_TABLES[aOptions.langShiftIndex];
aOptions.segments = this.fragmentText7Bit(aText,
langTable, langShiftTable,
aOptions.segmentChars,
aStrict7BitEncoding);
} else {
aOptions.segments = this.fragmentTextUCS2(aText,
aOptions.segmentChars);
}
// Re-sync aOptions.segmentMaxSeq with actual length of returning array.
aOptions.segmentMaxSeq = aOptions.segments.length;
if (aOptions.segmentMaxSeq > 1) {
aOptions.segmentRef16Bit = this.segmentRef16Bit;
aOptions.segmentRef = this.nextSegmentRef;
}
return aOptions;
}
};
this.EXPORTED_SYMBOLS = [ 'SmsSegmentHelper' ];