VulpesOS/gaia/apps/system/test/unit/ndef_utils_test.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

574 lines
18 KiB
JavaScript
Executable file

'use strict';
/* globals MocksHelper, NDEFUtils, NDEF, NfcUtils,
MozNDEFRecord */
require('/shared/test/unit/mocks/mock_moz_ndefrecord.js');
require('/shared/js/nfc_utils.js');
requireApp('system/js/ndef_utils.js');
var mocksForNfcUtils = new MocksHelper([
'MozNDEFRecord'
]).init();
suite('NDEFUtils tests', function() {
var nfcUtils;
mocksForNfcUtils.attachTestHelpers();
setup(function() {
nfcUtils = new NfcUtils();
});
suite('Helper functions tests', function() {
test('formatMAC()', function() {
assert.isNull(NDEFUtils.formatMAC(null));
assert.isNull(NDEFUtils.formatMAC([]));
assert.isNull(NDEFUtils.formatMAC([1,2,3,4,5]));
assert.isNull(NDEFUtils.formatMAC([1,2,3,4,5,6,7]));
assert.equal(NDEFUtils.formatMAC([1,2,3,4,5,6]),
'06:05:04:03:02:01');
assert.equal(NDEFUtils.formatMAC([0xAB, 0xCD, 0xEF, 0, 1, 2]),
'02:01:00:EF:CD:AB');
var mac = [1,2,3,4,5,6];
var mac2 = NDEFUtils.parseMAC(NDEFUtils.formatMAC(mac));
assert.deepEqual(mac2, mac);
});
test('parseMAC()', function() {
assert.isNull(NDEFUtils.parseMAC(null));
assert.isNull(NDEFUtils.parseMAC(''));
assert.isNull(NDEFUtils.parseMAC('lorem ipsum'));
assert.isNull(NDEFUtils.parseMAC('ab:cd:ef:gh:ij:kl'));
assert.isNull(NDEFUtils.parseMAC('ab:cd:ef:12:34'));
assert.isNull(NDEFUtils.parseMAC(':::::'));
assert.isNull(NDEFUtils.parseMAC('0:12:12:12:12:12'));
assert.isNotNull(NDEFUtils.parseMAC('01:23:45:67:89:AB'));
assert.isNotNull(NDEFUtils.parseMAC('01:23:45:67:89:ab'));
});
test('validateCPS()', function() {
assert.isFalse(NDEFUtils.validateCPS(-1));
assert.isFalse(NDEFUtils.validateCPS(4));
assert.isTrue(NDEFUtils.validateCPS(NDEF.CPS_INACTIVE));
assert.isTrue(NDEFUtils.validateCPS(NDEF.CPS_ACTIVE));
assert.isTrue(NDEFUtils.validateCPS(NDEF.CPS_ACTIVATING));
assert.isTrue(NDEFUtils.validateCPS(NDEF.CPS_UNKNOWN));
});
});
suite('parseHandoverNDEF() tests', function() {
var mac;
setup(function() {
mac = '01:23:45:67:89:AB';
});
function assertArraysEqual(test, expected, msg) {
for (var i = 0; i < test.length; i++ ) {
assert.equal(test[i], expected[i],
(msg || 'Arrays not equal') + ' at position: ' + i);
}
}
test('Transitive with encodeHandoverSelect()', function() {
var cps = 2;
var hin = NDEFUtils.encodeHandoverSelect(mac, cps);
var hout = NDEFUtils.parseHandoverNDEF(hin);
assert.equal(hout.majorVersion, 1);
assert.equal(hout.minorVersion, 2);
assert.equal(hout.type, 'Hs');
// Only one AC record (for Bluetooth).
assert.equal(hout.ac.length, 1);
assert.equal(hout.ac[0].cps, cps);
assert.deepEqual(hout.ac[0].cdr.payload, hin[1].payload);
});
test('Transitive with encodeHandoverRequest()', function() {
var cps = 1;
var hin = NDEFUtils.encodeHandoverRequest(mac, cps);
var hout = NDEFUtils.parseHandoverNDEF(hin);
assert.equal(hout.majorVersion, 1);
assert.equal(hout.minorVersion, 2);
assert.equal(hout.type, 'Hr');
// Only one AC record (for Bluetooth).
assert.equal(hout.ac.length, 1);
assert.equal(hout.ac[0].cps, cps);
assert.deepEqual(hout.ac[0].cdr.payload, hin[1].payload);
});
test('Decodes AC record correctly', function() {
var cps = 2;
var name = nfcUtils.fromUTF8('lorem ipsum');
var hin = NDEFUtils.encodeHandoverSelect(mac, cps, name);
var hout = NDEFUtils.parseHandoverNDEF(hin);
var ac = hout.ac[0].cdr.payload;
var m = ac.subarray(2, 8);
assert.equal(NDEFUtils.formatMAC(m), mac);
var n = ac.subarray(10, 10 + name.length);
assert.equal(nfcUtils.toUTF8(n), 'lorem ipsum');
});
test('Parses multiple Alternative Carrier (AC) records', function() {
var makeAC = function(id, cps, order) {
var f;
if (order === 'first') {
f = 0x91; // set MB bit
} else if (order === 'last') {
f = 0x51; // set ME bit
} else {
f = 0x11; // leave MB and ME unset
}
return [f, 0x02, 0x04, 0x61, 0x63, cps, 0x01, id, 0x00];
};
var makeBTRecord = function(id, mac) {
return new MozNDEFRecord({
tnf: NDEF.TNF_MIME_MEDIA,
type: NDEF.MIME_BLUETOOTH_OOB,
id: new Uint8Array([id]),
payload: new Uint8Array([8, 0].concat(mac))});
};
var macs = [
[1, 2, 3, 4, 5, 6],
[6, 5, 4, 3, 2, 1],
[0xFF, 0, 0, 0, 0, 0]
];
var hin = [new MozNDEFRecord({
tnf: NDEF.TNF_WELL_KNOWN,
type: NDEF.RTD_HANDOVER_SELECT,
payload: new Uint8Array([0x12].concat(
makeAC(0, 0, 'first'), makeAC(1, 1, 2),
makeAC(2, 2, 'last')))}),
makeBTRecord(0, macs[0]),
makeBTRecord(1, macs[1]),
makeBTRecord(2, macs[2])];
var hout = NDEFUtils.parseHandoverNDEF(hin);
assert.isNotNull(hout);
assert.equal(hout.ac.length, 3);
assert.deepEqual(hout.ac[0].cps, 0);
assert.deepEqual(hout.ac[1].cps, 1);
assert.deepEqual(hout.ac[2].cps, 2);
assertArraysEqual(hout.ac[0].cdr.payload, [8, 0].concat(macs[0]));
assertArraysEqual(hout.ac[1].cdr.payload, [8, 0].concat(macs[1]));
assertArraysEqual(hout.ac[2].cdr.payload, [8, 0].concat(macs[2]));
});
test('Parses Collision Resolution Record', function() {
var stubMathRandom = this.sinon.stub(Math, 'random');
stubMathRandom.onCall(0).returns(0.11223344);
stubMathRandom.onCall(1).returns(0.44332211);
var his = NDEFUtils.encodeHandoverRequest(mac, 1);
var hout = NDEFUtils.parseHandoverNDEF(his);
assert.equal(hout.cr, 7281);
});
test('Returns null when no input given', function() {
var hout = NDEFUtils.parseHandoverNDEF();
assert.isNull(hout);
});
test('Returns null when TNF of first record invalid', function() {
var hin = NDEFUtils.encodeHandoverSelect(mac, 1);
// Any value other than TNF_WELL_KNOWN is invalid.
hin[0].tnf = NDEF.TNF_MIME_MEDIA;
var hout = NDEFUtils.parseHandoverNDEF(hin);
assert.isNull(hout);
});
test('Returns null when no collision resolution record in Hr', function() {
// Hs message is almost identical to Hr, but it does not
// contain collision resolution record. So use that, only
// fix it's type.
var hin = NDEFUtils.encodeHandoverSelect(mac, 1);
hin[0].type = NDEF.RTD_HANDOVER_REQUEST;
var hout = NDEFUtils.parseHandoverNDEF(hin);
assert.isNull(hout);
});
test('Returns null when invalid collision resolution record', function() {
// Add third byte in collision resolution record.
var hin = NDEFUtils.encodeHandoverRequest(mac, 1);
var payload = Array.apply([], hin[0].payload);
payload[3] = 3;
payload.splice(6, 0, 0x01);
hin[0].payload = new Uint8Array(payload);
var hout = NDEFUtils.parseHandoverNDEF(hin);
assert.isNull(hout);
});
test('Do not attempt to parse records other that Hs/Hr', function() {
var hin = NDEFUtils.encodeHandoverRequest(mac, 1);
hin[0].type = nfcUtils.fromUTF8('Xx');
var hout = NDEFUtils.parseHandoverNDEF(hin);
assert.isNull(hout);
});
test('Return null if Hs/Hr record contains unexpected bytes.', function() {
var hin = NDEFUtils.encodeHandoverRequest(mac, 1);
var payload = Array.apply([], hin[0].payload);
payload.push(0x00);
hin[0].payload = new Uint8Array(payload);
var hout = NDEFUtils.parseHandoverNDEF(hin);
assert.isNull(hout);
});
test('Decodes even when records other than "cr"/"ac" are present',
function() {
var hin = NDEFUtils.encodeHandoverRequest(mac, 1);
var payload = Array.apply([], hin[0].payload);
// 0x63, 0x63 is "cc" record which isn't supported
// (probably doesn't even exist ;))
payload = payload.concat([0x51, 0x02, 0x02, 0x63, 0x63, 0x00, 0x01]);
// Previously last records isn't last anymore, so remove ME bit.
payload[8] = 0x11;
hin[0].payload = new Uint8Array(payload);
var hout = NDEFUtils.parseHandoverNDEF(hin);
assert.isNotNull(hout);
});
test('Return null when ID in AC record does not match ID of any records',
function() {
var hin = NDEFUtils.encodeHandoverRequest(mac, 1);
hin[1].id[0] += 1;
var hout = NDEFUtils.parseHandoverNDEF(hin);
assert.isNull(hout);
});
});
suite('searchForBluetoothAC() tests', function() {
var h;
setup(function() {
var mac = '01:23:45:67:89:AB';
var cps = 1;
var hin = NDEFUtils.encodeHandoverSelect(mac, cps);
h = NDEFUtils.parseHandoverNDEF(hin);
});
test('Returns when BT AC present', function() {
var btAC = NDEFUtils.searchForBluetoothAC(h);
assert.isNotNull(btAC);
});
test('No BT AC present - incorrect TNF', function() {
h.ac[0].cdr.tnf = NDEF.TNF_WELL_KNOWN;
var btAC = NDEFUtils.searchForBluetoothAC(h);
assert.isNull(btAC);
});
test('No BT AC present - incorrect type', function() {
h.ac[0].cdr.type = nfcUtils.fromUTF8('application/invalid');
var btAC = NDEFUtils.searchForBluetoothAC(h);
assert.isNull(btAC);
});
});
suite('parseBluetoothSSP() tests', function() {
var mac;
var macArr;
setup(function() {
mac = '01:23:45:67:89:AB';
macArr = NDEFUtils.parseMAC(mac);
});
test('Returns null if invoked with invalid record', function() {
assert.isNull(NDEFUtils.parseBluetoothSSP());
assert.isNull(NDEFUtils.parseBluetoothSSP({}));
});
test('Returns null if payload length invalid', function() {
var record = {
payload: new Uint8Array([0x08])
};
var dec = NDEFUtils.parseBluetoothSSP(record);
assert.isNull(dec);
});
test('Decodes MAC from valid record', function() {
var OOB = [0x08, 0x00];
var record = {
payload: new Uint8Array(OOB.concat(macArr))
};
var dec = NDEFUtils.parseBluetoothSSP(record);
assert.equal(dec.mac, mac);
});
test('Decodes Bluetooth local name from valid record', function() {
var name = 'Lorem ipsum';
var record = NDEFUtils.encodeHandoverSelect(mac, 1,
nfcUtils.fromUTF8(name));
var dec = NDEFUtils.parseBluetoothSSP(record[1]);
assert.equal(dec.localName, name);
});
test('Ignores unsupported EIR/OOB fields', function() {
// Contruct this by hand, since encodeHandoverSelect() does
// not support extra EIR parameters (other than MAC and BT
// local name).
// Some unsupported field: Simple Pairing Randomizer.
var pairingRandomizer = [0x0F, 0x0E, 0x0D, 0x0C,
0x0B, 0x0A, 0x09, 0x08,
0x07, 0x06, 0x05, 0x04,
0x03, 0x02, 0x01, 0x00];
var prEIRType = 0x0F;
var prEIRLength = 1 + pairingRandomizer.length;
// Another unsupported field: Class of Device.
var deviceClass = [0x20, 0x06, 0x08];
var dcEIRType = 0x0D;
var dcEIRLength = 1 + deviceClass.length;
// Bluetooth local name (supported field).
var name = 'Lorem ipsum';
var nameArr = Array.apply([], nfcUtils.fromUTF8(name));
var nEIRType = 0x09;
var nEIRLength = 1 + nameArr.length;
var totalLength = 2 + macArr.length + (prEIRLength + 1) +
(dcEIRLength + 1)+ (nEIRLength + 1);
var OOB = [totalLength, 0]
.concat(macArr, prEIRLength, prEIRType, pairingRandomizer,
nEIRLength, nEIRType, nameArr, dcEIRLength, dcEIRType,
deviceClass);
var record = {
payload: OOB
};
var dec = NDEFUtils.parseBluetoothSSP(record);
assert.equal(dec.mac, mac);
assert.equal(dec.localName, name);
});
test('Returns null on invalid record - MAC incomplete', function() {
var record = NDEFUtils.encodeHandoverSelect(mac, 1);
// Make MAC address invalid: delete one hex digit (2 bytes)
// from it.
var OOB = Array.apply([], record[1].payload);
OOB.splice(5, 1);
record = {
payload: OOB
};
assert.isNull(NDEFUtils.parseBluetoothSSP(record));
});
test('Returns null on invalid record - local name incomplete',
function() {
var record = NDEFUtils.encodeHandoverSelect(mac, 1,
nfcUtils.fromUTF8('Lorem ipsum'));
// Make local name invalid: delete one character from it.
// Set OOB length as if this character was missing.
// So now it should fail because EIR field length will
// be inconsistent.
var OOB = Array.apply([], record[1].payload);
OOB.splice(13, 1);
OOB[0] -= 1;
record = {
payload: OOB
};
assert.isNull(NDEFUtils.parseBluetoothSSP(record));
});
});
suite('encodeHandoverRequest() tests', function() {
var cps;
var btMac;
var stubMathRandom;
setup(function() {
cps = NDEF.CPS_ACTIVE;
btMac = '00:0D:44:E7:95:AB';
stubMathRandom = this.sinon.stub(Math, 'random');
stubMathRandom.onCall(0).returns(0.1234567);
stubMathRandom.onCall(1).returns(0.7654321);
});
teardown(function() {
stubMathRandom.restore();
});
test('Encodes CPS', function() {
var records;
var cps1 = NDEF.CPS_INACTIVE;
records = NDEFUtils.encodeHandoverRequest(btMac, cps1);
assert.equal(records[0].payload[13], cps1);
var cps2 = NDEF.CPS_ACTIVE;
records = NDEFUtils.encodeHandoverRequest(btMac, cps2);
assert.equal(records[0].payload[13], cps2);
});
test('Encodes MAC', function() {
var records = NDEFUtils.encodeHandoverRequest(btMac, cps);
var mac = '';
for (var m = 7; m >= 2; m -= 1) {
var n = records[1].payload[m];
mac += (n < 0x10 ? '0' : '' ) + n.toString(16);
if (m > 2) {
mac += ':';
}
}
assert.equal(mac.toUpperCase(), btMac);
});
test('Encodes random number for collision detection', function() {
var request = NDEFUtils.encodeHandoverRequest(btMac, cps);
assert.isTrue(stubMathRandom.calledTwice);
var rndMSB = request[0].payload[6];
var rndLSB = request[0].payload[7];
assert.equal(rndMSB, 31);
assert.equal(rndLSB, 195);
});
test('Returns null when MAC invalid', function() {
var invalidMAC = 'AB:CD';
var records = NDEFUtils.encodeHandoverRequest(invalidMAC, cps);
assert.isNull(records);
});
test('Returns null when CPS invalid', function() {
var invalidCPS = 5;
var records = NDEFUtils.encodeHandoverRequest(btMac, invalidCPS);
assert.isNull(records);
});
});
suite('encodeHandoverSelect() tests', function() {
var cps;
var btMac;
var btName;
var recordsDefault;
setup(function() {
cps = NDEF.CPS_ACTIVE;
btMac = '00:0D:44:E7:95:AB';
btName = nfcUtils.fromUTF8('UE MINI BOOM');
/*
* The following NDEF message contains a static handover request
* from a Motorola UE Mini Boom. The NDEF message encodes the
* MAC address(00:0D:44:E7:95:AB) and its name (UE MINI BOOM).
*/
recordsDefault = [{
tnf: NDEF.TNF_WELL_KNOWN,
type: new Uint8Array([72, 115]),
payload: new Uint8Array([18, 209, 2, 4, 97, 99, 1, 1, 48, 0])
}, {
tnf: NDEF.TNF_MIME_MEDIA,
type: new Uint8Array([97, 112, 112, 108, 105, 99, 97, 116, 105,
111, 110, 47, 118, 110, 100, 46, 98, 108, 117,
101, 116, 111, 111, 116, 104, 46, 101, 112,
46, 111, 111, 98]),
id: new Uint8Array([48]),
payload: new Uint8Array([22, 0, 171, 149, 231, 68, 13, 0, 13, 9, 85,
69, 32, 77, 73, 78, 73, 32, 66, 79,
79, 77])
}];
});
test('With MAC, CPS and device name', function() {
var records = NDEFUtils.encodeHandoverSelect(btMac, cps, btName);
for (var i = 0; i < records.length; i++) {
assert.deepEqual(records[i].tnf, recordsDefault[i].tnf);
if (records[i].type) {
assert.deepEqual(records[i].type, recordsDefault[i].type);
}
if (records[i].id) {
assert.deepEqual(records[i].id, recordsDefault[i].id);
}
if (records[i].payload) {
assert.deepEqual(records[i].payload, recordsDefault[i].payload);
}
}
});
test('With MAC and CPS only', function() {
recordsDefault[1].payload =
new Uint8Array([8, 0, 171, 149, 231, 68, 13, 0]);
var records = NDEFUtils.encodeHandoverSelect(btMac, cps);
for (var i = 0; i < records.length; i++) {
assert.deepEqual(records[i].tnf, recordsDefault[i].tnf);
if (records[i].type) {
assert.deepEqual(records[i].type, recordsDefault[i].type);
}
if (records[i].id) {
assert.deepEqual(records[i].id, recordsDefault[i].id);
}
if (records[i].payload) {
assert.deepEqual(records[i].payload, recordsDefault[i].payload);
}
}
});
test('Encodes CPS', function() {
var records;
var cps1 = NDEF.CPS_INACTIVE;
records = NDEFUtils.encodeHandoverSelect(btMac, cps1, btName);
assert.equal(records[0].payload[6], cps1);
var cps2 = NDEF.CPS_ACTIVATING;
records = NDEFUtils.encodeHandoverSelect(btMac, cps2, btName);
assert.equal(records[0].payload[6], cps2);
});
test('Returns null when MAC invalid', function() {
var invalidMAC = 'AB:CD';
var records = NDEFUtils.encodeHandoverSelect(invalidMAC, cps);
assert.isNull(records);
});
test('Returns null when CPS invalid', function() {
var invalidCPS = 5;
var records = NDEFUtils.encodeHandoverSelect(btMac, invalidCPS);
assert.isNull(records);
});
});
});