Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
4 MARIONETTE_TIMEOUT = 30000;
6 const PDU_DCS_CODING_GROUP_BITS = 0xF0;
7 const PDU_DCS_MSG_CODING_7BITS_ALPHABET = 0x00;
8 const PDU_DCS_MSG_CODING_8BITS_ALPHABET = 0x04;
9 const PDU_DCS_MSG_CODING_16BITS_ALPHABET = 0x08;
11 const PDU_DCS_MSG_CLASS_BITS = 0x03;
12 const PDU_DCS_MSG_CLASS_NORMAL = 0xFF;
13 const PDU_DCS_MSG_CLASS_0 = 0x00;
14 const PDU_DCS_MSG_CLASS_ME_SPECIFIC = 0x01;
15 const PDU_DCS_MSG_CLASS_SIM_SPECIFIC = 0x02;
16 const PDU_DCS_MSG_CLASS_TE_SPECIFIC = 0x03;
17 const PDU_DCS_MSG_CLASS_USER_1 = 0x04;
18 const PDU_DCS_MSG_CLASS_USER_2 = 0x05;
20 const GECKO_SMS_MESSAGE_CLASSES = {};
21 GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL] = "normal";
22 GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0] = "class-0";
23 GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_ME_SPECIFIC] = "class-1";
24 GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_SIM_SPECIFIC] = "class-2";
25 GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_TE_SPECIFIC] = "class-3";
26 GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_USER_1] = "user-1";
27 GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_USER_2] = "user-2";
29 const CB_MESSAGE_SIZE_GSM = 88;
31 const CB_GSM_MESSAGEID_ETWS_BEGIN = 0x1100;
32 const CB_GSM_MESSAGEID_ETWS_END = 0x1107;
34 const CB_GSM_GEOGRAPHICAL_SCOPE_NAMES = [
35 "cell-immediate",
36 "plmn",
37 "location-area",
38 "cell"
39 ];
41 const CB_ETWS_WARNING_TYPE_NAMES = [
42 "earthquake",
43 "tsunami",
44 "earthquake-tsunami",
45 "test",
46 "other"
47 ];
49 const CB_DCS_LANG_GROUP_1 = [
50 "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi",
51 "no", "el", "tr", "hu", "pl", null
52 ];
53 const CB_DCS_LANG_GROUP_2 = [
54 "cs", "he", "ar", "ru", "is", null, null, null, null, null,
55 null, null, null, null, null, null
56 ];
58 const CB_MAX_CONTENT_7BIT = Math.floor((CB_MESSAGE_SIZE_GSM - 6) * 8 / 7);
59 const CB_MAX_CONTENT_UCS2 = Math.floor((CB_MESSAGE_SIZE_GSM - 6) / 2);
61 const BODY_7BITS = "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
62 + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
63 + "@@@@@@@@@@@@@"; // 93 ascii chars.
64 const BODY_7BITS_IND = BODY_7BITS.substr(3);
65 const BODY_UCS2 = "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
66 + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
67 + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
68 + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
69 + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
70 + "\u0000"; // 41 unicode chars.
71 const BODY_UCS2_IND = BODY_UCS2.substr(1);
73 SpecialPowers.addPermission("cellbroadcast", true, document);
74 SpecialPowers.addPermission("mobileconnection", true, document);
76 is(BODY_7BITS.length, CB_MAX_CONTENT_7BIT, "BODY_7BITS.length");
77 is(BODY_7BITS_IND.length, CB_MAX_CONTENT_7BIT - 3, "BODY_7BITS_IND.length");
78 is(BODY_UCS2.length, CB_MAX_CONTENT_UCS2, "BODY_UCS2.length");
79 is(BODY_UCS2_IND.length, CB_MAX_CONTENT_UCS2 - 1, "BODY_UCS2_IND.length")
81 let cbs = window.navigator.mozCellBroadcast;
82 ok(cbs instanceof window.MozCellBroadcast,
83 "mozCellBroadcast is instanceof " + cbs.constructor);
85 let pendingEmulatorCmdCount = 0;
86 function sendCellBroadcastMessage(pdu, callback) {
87 pendingEmulatorCmdCount++;
89 let cmd = "cbs pdu " + pdu;
90 runEmulatorCmd(cmd, function(result) {
91 pendingEmulatorCmdCount--;
93 is(result[0], "OK", "Emulator response");
95 if (callback) {
96 window.setTimeout(callback, 0);
97 }
98 });
99 }
101 function buildHexStr(n, numSemiOctets) {
102 let str = n.toString(16);
103 ok(str.length <= numSemiOctets);
104 while (str.length < numSemiOctets) {
105 str = "0" + str;
106 }
107 return str;
108 }
110 function seq(end, begin) {
111 let result = [];
112 for (let i = begin || 0; i < end; i++) {
113 result.push(i);
114 }
115 return result;
116 }
118 function repeat(func, array, oncomplete) {
119 (function do_call(index) {
120 let next = index < (array.length - 1) ? do_call.bind(null, index + 1) : oncomplete;
121 func.apply(null, [array[index], next]);
122 })(0);
123 }
125 function doTestHelper(pdu, nextTest, checkFunc) {
126 cbs.addEventListener("received", function onreceived(event) {
127 cbs.removeEventListener("received", onreceived);
129 checkFunc(event.message);
131 window.setTimeout(nextTest, 0);
132 });
134 if (Array.isArray(pdu)) {
135 repeat(sendCellBroadcastMessage, pdu);
136 } else {
137 sendCellBroadcastMessage(pdu);
138 }
139 }
141 /**
142 * Tests receiving Cell Broadcast messages, event instance type, all attributes
143 * of CellBroadcastMessage exist.
144 */
145 function testGsmMessageAttributes() {
146 log("Test GSM Cell Broadcast message attributes");
148 cbs.addEventListener("received", function onreceived(event) {
149 cbs.removeEventListener("received", onreceived);
151 // Bug 838542: following check throws an exception and fails this case.
152 // ok(event instanceof MozCellBroadcastEvent,
153 // "event is instanceof " + event.constructor)
154 ok(event, "event is valid");
156 let message = event.message;
157 ok(message, "event.message is valid");
159 // Attributes other than `language` and `body` should always be assigned.
160 ok(message.gsmGeographicalScope != null, "message.gsmGeographicalScope");
161 ok(message.messageCode != null, "message.messageCode");
162 ok(message.messageId != null, "message.messageId");
163 ok(message.language != null, "message.language");
164 ok(message.body != null, "message.body");
165 ok(message.messageClass != null, "message.messageClass");
166 ok(message.timestamp != null, "message.timestamp");
167 ok('etws' in message, "message.etws");
168 if (message.etws) {
169 ok('warningType' in message.etws, "message.etws.warningType");
170 ok(message.etws.emergencyUserAlert != null, "message.etws.emergencyUserAlert");
171 ok(message.etws.popup != null, "message.etws.popup");
172 }
173 ok(message.cdmaServiceCategory != null, "message.cdmaServiceCategory");
175 window.setTimeout(testReceiving_GSM_GeographicalScope, 0);
176 });
178 // Here we use a simple GSM message for test.
179 let pdu = buildHexStr(0, CB_MESSAGE_SIZE_GSM * 2);
180 sendCellBroadcastMessage(pdu);
181 }
183 function testReceiving_GSM_GeographicalScope() {
184 log("Test receiving GSM Cell Broadcast - Geographical Scope");
186 function do_test(gs, nextTest) {
187 let pdu = buildHexStr(((gs & 0x03) << 14), 4)
188 + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 2) * 2);
190 doTestHelper(pdu, nextTest, function(message) {
191 is(message.gsmGeographicalScope, CB_GSM_GEOGRAPHICAL_SCOPE_NAMES[gs],
192 "message.gsmGeographicalScope");
193 });
194 }
196 repeat(do_test, seq(CB_GSM_GEOGRAPHICAL_SCOPE_NAMES.length),
197 testReceiving_GSM_MessageCode);
198 }
200 function testReceiving_GSM_MessageCode() {
201 log("Test receiving GSM Cell Broadcast - Message Code");
203 // Message Code has 10 bits, and is ORed into a 16 bits 'serial' number. Here
204 // we test every single bit to verify the operation doesn't go wrong.
205 let messageCodesToTest = [
206 0x000, 0x001, 0x002, 0x004, 0x008, 0x010, 0x020, 0x040,
207 0x080, 0x100, 0x200, 0x251
208 ];
210 function do_test(messageCode, nextTest) {
211 let pdu = buildHexStr(((messageCode & 0x3FF) << 4), 4)
212 + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 2) * 2);
214 doTestHelper(pdu, nextTest, function(message) {
215 is(message.messageCode, messageCode, "message.messageCode");
216 });
217 }
219 repeat(do_test, messageCodesToTest, testReceiving_GSM_MessageId);
220 }
222 function testReceiving_GSM_MessageId() {
223 log("Test receiving GSM Cell Broadcast - Message Identifier");
225 // Message Identifier has 16 bits, but no bitwise operation is needed.
226 // Test some selected values only.
227 let messageIdsToTest = [
228 0x0000, 0x0001, 0x0010, 0x0100, 0x1000, 0x1111, 0x8888, 0x8811,
229 ];
231 function do_test(messageId, nextTest) {
232 let pdu = buildHexStr(0, 4)
233 + buildHexStr((messageId & 0xFFFF), 4)
234 + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 4) * 2);
236 doTestHelper(pdu, nextTest, function(message) {
237 is(message.messageId, messageId, "message.messageId");
238 ok(message.etws == null, "message.etws");
239 });
240 }
242 repeat(do_test, messageIdsToTest, testReceiving_GSM_Language_and_Body);
243 }
245 // Copied from GsmPDUHelper.readCbDataCodingScheme
246 function decodeDataCodingScheme(dcs) {
247 let language = null;
248 let hasLanguageIndicator = false;
249 let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
250 let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
252 switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
253 case 0x00: // 0000
254 language = CB_DCS_LANG_GROUP_1[dcs & 0x0F];
255 break;
257 case 0x10: // 0001
258 switch (dcs & 0x0F) {
259 case 0x00:
260 hasLanguageIndicator = true;
261 break;
262 case 0x01:
263 encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
264 hasLanguageIndicator = true;
265 break;
266 }
267 break;
269 case 0x20: // 0010
270 language = CB_DCS_LANG_GROUP_2[dcs & 0x0F];
271 break;
273 case 0x40: // 01xx
274 case 0x50:
275 //case 0x60:
276 //case 0x70:
277 case 0x90: // 1001
278 encoding = (dcs & 0x0C);
279 if (encoding == 0x0C) {
280 encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
281 }
282 messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS);
283 break;
285 case 0xF0:
286 encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET
287 : PDU_DCS_MSG_CODING_7BITS_ALPHABET;
288 switch(dcs & PDU_DCS_MSG_CLASS_BITS) {
289 case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break;
290 case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break;
291 case 0x03: messageClass = PDU_DCS_MSG_CLASS_TE_SPECIFIC; break;
292 }
293 break;
295 case 0x30: // 0011 (Reserved)
296 case 0x80: // 1000 (Reserved)
297 case 0xA0: // 1010..1100 (Reserved)
298 case 0xB0:
299 case 0xC0:
300 break;
301 default:
302 throw new Error("Unsupported CBS data coding scheme: " + dcs);
303 }
305 return [encoding, language, hasLanguageIndicator,
306 GECKO_SMS_MESSAGE_CLASSES[messageClass]];
307 }
309 function testReceiving_GSM_Language_and_Body() {
310 log("Test receiving GSM Cell Broadcast - Language & Body");
312 function do_test(dcs) {
313 let encoding, language, indicator, messageClass;
314 try {
315 [encoding, language, indicator, messageClass] = decodeDataCodingScheme(dcs);
316 } catch (e) {
317 // Unsupported coding group, skip.
318 let nextGroup = (dcs & PDU_DCS_CODING_GROUP_BITS) + 0x10;
319 window.setTimeout(do_test.bind(null, nextGroup), 0);
320 return;
321 }
323 let pdu = buildHexStr(0, 8)
324 + buildHexStr(dcs, 2)
325 + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 5) * 2);
327 let nextTest = (dcs < 0xFF) ? do_test.bind(null, dcs + 1)
328 : testReceiving_GSM_Timestamp;
329 doTestHelper(pdu, nextTest, function(message) {
330 if (language) {
331 is(message.language, language, "message.language");
332 } else if (indicator) {
333 is(message.language, "@@", "message.language");
334 } else {
335 ok(message.language == null, "message.language");
336 }
338 switch (encoding) {
339 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
340 is(message.body, indicator ? BODY_7BITS_IND : BODY_7BITS, "message.body");
341 break;
342 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
343 ok(message.body == null, "message.body");
344 break;
345 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
346 is(message.body, indicator ? BODY_UCS2_IND : BODY_UCS2, "message.body");
347 break;
348 }
350 is(message.messageClass, messageClass, "message.messageClass");
351 });
352 }
354 do_test(0);
355 }
357 function testReceiving_GSM_Timestamp() {
358 log("Test receiving GSM Cell Broadcast - Timestamp");
360 let pdu = buildHexStr(0, CB_MESSAGE_SIZE_GSM * 2);
361 doTestHelper(pdu, testReceiving_GSM_WarningType, function(message) {
362 // Cell Broadcast messages do not contain a timestamp field (however, ETWS
363 // does). We only check the timestamp doesn't go too far (60 seconds) here.
364 let msMessage = message.timestamp.getTime();
365 let msNow = Date.now();
366 ok(Math.abs(msMessage - msNow) < (1000 * 60), "message.timestamp");
367 });
368 }
370 function testReceiving_GSM_WarningType() {
371 log("Test receiving GSM Cell Broadcast - Warning Type");
373 let messageIdsToTest = [];
374 for (let i = CB_GSM_MESSAGEID_ETWS_BEGIN; i <= CB_GSM_MESSAGEID_ETWS_END; i++) {
375 messageIdsToTest.push(i);
376 }
378 function do_test(messageId, nextTest) {
379 let pdu = buildHexStr(0, 4)
380 + buildHexStr((messageId & 0xFFFF), 4)
381 + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 4) * 2);
383 doTestHelper(pdu, nextTest, function(message) {
384 is(message.messageId, messageId, "message.messageId");
385 ok(message.etws != null, "message.etws");
387 let offset = messageId - CB_GSM_MESSAGEID_ETWS_BEGIN;
388 if (offset < CB_ETWS_WARNING_TYPE_NAMES.length) {
389 is(message.etws.warningType, CB_ETWS_WARNING_TYPE_NAMES[offset],
390 "message.etws.warningType");
391 } else {
392 ok(message.etws.warningType == null, "message.etws.warningType");
393 }
394 });
395 }
397 repeat(do_test, messageIdsToTest, testReceiving_GSM_EmergencyUserAlert);
398 }
400 function doTestEmergencyUserAlert_or_Popup(name, mask, nextTest) {
401 let pdu = buildHexStr(mask, 4)
402 + buildHexStr(CB_GSM_MESSAGEID_ETWS_BEGIN, 4)
403 + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 4) * 2);
405 doTestHelper(pdu, nextTest, function(message) {
406 is(message.messageId, CB_GSM_MESSAGEID_ETWS_BEGIN, "message.messageId");
407 ok(message.etws != null, "message.etws");
408 is(message.etws[name], mask != 0, "message.etws." + name);
409 });
410 }
412 function testReceiving_GSM_EmergencyUserAlert() {
413 log("Test receiving GSM Cell Broadcast - Emergency User Alert");
415 repeat(doTestEmergencyUserAlert_or_Popup.bind(null, "emergencyUserAlert"),
416 [0x2000, 0x0000], testReceiving_GSM_Popup);
417 }
419 function testReceiving_GSM_Popup() {
420 log("Test receiving GSM Cell Broadcast - Popup");
422 repeat(doTestEmergencyUserAlert_or_Popup.bind(null, "popup"),
423 [0x1000, 0x0000], testReceiving_GSM_Multipart);
424 }
426 function testReceiving_GSM_Multipart() {
427 log("Test receiving GSM Cell Broadcast - Multipart Messages");
429 function do_test(numParts, nextTest) {
430 let pdus = [];
431 for (let i = 1; i <= numParts; i++) {
432 let pdu = buildHexStr(0, 10)
433 + buildHexStr((i << 4) + numParts, 2)
434 + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 6) * 2);
435 pdus.push(pdu);
436 }
438 doTestHelper(pdus, nextTest, function(message) {
439 is(message.body.length, (numParts * CB_MAX_CONTENT_7BIT),
440 "message.body");
441 });
442 }
444 repeat(do_test, seq(16, 1), testReceiving_GSM_ServiceCategory);
445 }
447 function testReceiving_GSM_ServiceCategory() {
448 log("Test receiving GSM Cell Broadcast - Service Category");
450 cbs.addEventListener("received", function onreceived(event) {
451 cbs.removeEventListener("received", onreceived);
453 let message = event.message;
455 // Bug 910091
456 // "Service Category" is not defined in GSM. We should always get '0' here.
457 is(message.cdmaServiceCategory, 0, "message.cdmaServiceCategory");
459 window.setTimeout(cleanUp, 0);
460 });
462 let pdu = buildHexStr(0, CB_MESSAGE_SIZE_GSM * 2);
463 sendCellBroadcastMessage(pdu);
464 }
466 function cleanUp() {
467 if (pendingEmulatorCmdCount > 0) {
468 window.setTimeout(cleanUp, 100);
469 return;
470 }
472 SpecialPowers.removePermission("mobileconnection", document);
473 SpecialPowers.removePermission("cellbroadcast", true, document);
475 finish();
476 }
478 waitFor(testGsmMessageAttributes, function() {
479 return navigator.mozMobileConnections[0].voice.connected;
480 });