text: 'use strict';
  
  /* globals MocksHelper, MozNDEFRecord, NDEF,
             NfcUtils, NDEFUtils, NfcBuffer */
  
  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('NFC Utils', function() {
  
    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);
      }
    }
  
    mocksForNfcUtils.attachTestHelpers();
  
    var nfcUtils;
    var string1;
    var uint8array1;
    var urlU8a; // Uint8Array
  
    setup(function() {
      nfcUtils = new NfcUtils();
      string1 = 'StringTestString ABCDEFGHIJKLMNOPQRSTUVWXYZ ą嗨è©';
      uint8array1 = new Uint8Array([0x53, 0x74, 0x72, 0x69, 0x6e, 0x67,
                                    0x54, 0x65, 0x73, 0x74,
                                    0x53, 0x74, 0x72, 0x69, 0x6e, 0x67,
                                    0x20,
                                    0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
                                    0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c,
                                    0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52,
                                    0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
                                    0x59, 0x5a,
                                    0x20,
                                    0xC4, 0x85, 0xE5, 0x97, 0xA8, 0xC3,
                                    0xA8, 0xC2, 0xA9]);
  
      // SR = 1, TNF = 0x01 (NFC Forum Well Known Type),
      // One record only: ME=1, MB=1
      urlU8a = new Uint8Array([0xd1, // TNF and header
                               0x01, // Record type length
                               0x0c, // payload length
                               0x55, // 'U',  NDEF.RTD_URI type
                               0x03, // NDEF.URIS[0x03] = 'http://';
                               0x6d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61,
                               0x2e,
                               0x6f, 0x72, 0x67]); // mozilla.org
    });
  
    test('transitive', function() {
      var u8a = nfcUtils.fromUTF8(string1);
      var str = nfcUtils.toUTF8(uint8array1);
      var backStr = nfcUtils.toUTF8(u8a);
      var backU8a = nfcUtils.fromUTF8(str);
      var nullObj = nfcUtils.toUTF8(null);
      var nullStr = nfcUtils.fromUTF8(null);
  
      var u1 = nfcUtils.equalArrays(u8a, uint8array1);
      var s1 = nfcUtils.equalArrays(str, string1);
      var bs1 = nfcUtils.equalArrays(string1, backStr);
      var bs2 = nfcUtils.equalArrays(str, backStr);
      var bu1 = nfcUtils.equalArrays(u8a, backU8a);
      var bu2 = nfcUtils.equalArrays(uint8array1, backU8a);
  
      assert.equal(u1, true);
      assert.equal(s1, true);
      assert.equal(bs1, true);
      assert.equal(bs2, true);
      assert.equal(bu1, true);
      assert.equal(bu2, true);
      assert.equal(nullObj, null);
      assert.equal(nullStr, null);
    });
  
    suite('equalArrays', function() {
      test('Accepts null arguments', function() {
        assert.isFalse(nfcUtils.equalArrays());
        assert.isFalse(nfcUtils.equalArrays(null));
        assert.isFalse(nfcUtils.equalArrays(null, null));
        assert.isFalse(nfcUtils.equalArrays(undefined, null));
      });
  
      test('Accepts non-array objects', function() {
        assert.isFalse(nfcUtils.equalArrays({}, [1]));
      });
  
      test('Compares arrays', function() {
        assert.isFalse(nfcUtils.equalArrays([1, 2, 3], [1, 3, 2]));
        assert.isFalse(nfcUtils.equalArrays([1, 2, 3], [1]));
  
        assert.isTrue(nfcUtils.equalArrays([1, 2, 3], [1, 2, 3]));
        assert.isTrue(nfcUtils.equalArrays([1, 2, 3], new Uint8Array([1, 2, 3])));
      });
    });
  
    suite('String <> byte conversions', function() {
      var cprght = '©';
      var cprghtUTF16BE = new Uint8Array([0x00, 0xA9]);
      var cprghtUTF16BEBOM = new Uint8Array([0xFE, 0xFF, 0x00, 0xA9]);
      var cprghtUTF16LEBOM = new Uint8Array([0xFF, 0xFE, 0xA9, 0x00]);
  
      var plchr = 'ą';
      var plchrUTF16BE = new Uint8Array([0x01, 0x05]);
  
      var hai = '嗨';
      var haiUTF16BE = new Uint8Array([0x55, 0xE8]);
  
      var foxTxt = 'Big brown fox! 大棕狐狸?';
      var foxTxtUTF16BE = new Uint8Array(
        [0x00, 0x42, 0x00, 0x69, 0x00, 0x67, 0x00, 0x20, 0x00, 0x62, 0x00, 0x72,
         0x00, 0x6F, 0x00, 0x77, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x66, 0x00, 0x6F,
         0x00, 0x78, 0x00, 0x21, 0x00, 0x20, 0x59, 0x27, 0x68, 0xD5, 0x72, 0xD0,
         0x72, 0xF8, 0x00, 0x3F]
      );
  
      test('UTF16BytesToStr', function() {
        assert.equal(cprght, nfcUtils.UTF16BytesToStr(cprghtUTF16BE));
        assert.equal(cprght, nfcUtils.UTF16BytesToStr(cprghtUTF16BEBOM));
        assert.equal(cprght, nfcUtils.UTF16BytesToStr(cprghtUTF16LEBOM));
  
        assert.equal(plchr, nfcUtils.UTF16BytesToStr(plchrUTF16BE));
        assert.equal(hai, nfcUtils.UTF16BytesToStr(haiUTF16BE));
        assert.equal(foxTxt, nfcUtils.UTF16BytesToStr(foxTxtUTF16BE));
      });
  
      test('strToUTF16Bytes', function() {
        assert.deepEqual(cprghtUTF16BE, nfcUtils.strToUTF16Bytes(cprght));
        assert.deepEqual(plchrUTF16BE, nfcUtils.strToUTF16Bytes(plchr));
        assert.deepEqual(haiUTF16BE, nfcUtils.strToUTF16Bytes(hai));
        assert.deepEqual(foxTxtUTF16BE, nfcUtils.strToUTF16Bytes(foxTxt));
      });
    });
  
    suite('encodeNDEF', function() {
      var urlNDEF; // MozNDEFRecord
  
      setup(function() {
        var tnf     = NDEF.TNF_WELL_KNOWN;
        var type    = NDEF.RTD_URI;
        // Short Record, 0x3 or "http://"
        var payload = new Uint8Array(nfcUtils.fromUTF8(
                                     '\u0003mozilla.org'));
  
        urlNDEF = new MozNDEFRecord({tnf: tnf, type: type, payload: payload});
      });
  
      test('Subrecord', function() {
        var encodedNdefU8a = nfcUtils.encodeNDEF([urlNDEF]);
        // MozNDEFRecord is abstract, and does not contain some extra bits in the
        // header for NDEF payload subrecords:
        var cpUrlU8a = new Uint8Array(encodedNdefU8a);
        cpUrlU8a[0] = cpUrlU8a[0] & NDEF.TNF;
  
        var equals1 = nfcUtils.equalArrays(encodedNdefU8a, urlU8a);
        assert.equal(equals1, true);
      });
  
      test('Short record', function() {
        var ndefMsg = new Uint8Array([0xd1, 0x01, 0x11, 0x55, 0x04, 0x77, 0x69,
                                     0x6b, 0x69, 0x2e, 0x6d, 0x6f, 0x7a, 0x69,
                                      0x6c, 0x6c, 0x61, 0x2e, 0x6f, 0x72, 0x67]);
  
        // "wiki.mozilla.org" with abbreviation "https://" (0x04)
        var payload = new Uint8Array([0x04, 0x77, 0x69, 0x6b, 0x69, 0x2e, 0x6d,
                                      0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2e,
                                      0x6f, 0x72, 0x67]);
        var ndefRecord = new MozNDEFRecord({tnf: NDEF.TNF_WELL_KNOWN,
                                            type: NDEF.RTD_URI,
                                            payload: payload});
  
        var result = nfcUtils.encodeNDEF([ndefRecord]);
        assertArraysEqual(result, ndefMsg);
      });
  
      test('Long record', function() {
        // Excerpt from Wikipedia about Mozilla Foundation, UTF-16 encoded.
        var payloadArray =
          [0x85, 0x70, 0x6c, 0x5f, 0x70, 0x6c, 0xff, 0xfe, 0x46, 0x00, 0x75,
          0x00, 0x6e, 0x00, 0x64, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6a, 0x00,
          0x61, 0x00, 0x20, 0x00, 0x4d, 0x00, 0x6f, 0x00, 0x7a, 0x00, 0x69,
          0x00, 0x6c, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x20, 0x00, 0x28, 0x00,
          0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x2e, 0x00, 0x20, 0x00, 0x54,
          0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x4d, 0x00, 0x6f, 0x00,
          0x7a, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x20,
          0x00, 0x46, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00,
          0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x29,
          0x00, 0x20, 0x00, 0x13, 0x20, 0x20, 0x00, 0x6f, 0x00, 0x72, 0x00,
          0x67, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x7a, 0x00, 0x61,
          0x00, 0x63, 0x00, 0x6a, 0x00, 0x61, 0x00, 0x20, 0x00, 0x6e, 0x00,
          0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f,
          0x00, 0x66, 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x70, 0x00,
          0x6f, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x42, 0x01, 0x61, 0x00, 0x6e,
          0x00, 0x61, 0x00, 0x20, 0x00, 0x31, 0x00, 0x35, 0x00, 0x20, 0x00,
          0x6c, 0x00, 0x69, 0x00, 0x70, 0x00, 0x63, 0x00, 0x61, 0x00, 0x20,
          0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x33, 0x00, 0x2c, 0x00,
          0x20, 0x00, 0x6b, 0x00, 0x74, 0x00, 0xf3, 0x00, 0x72, 0x00, 0x65,
          0x00, 0x6a, 0x00, 0x20, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6c, 0x00,
          0x65, 0x00, 0x6d, 0x00, 0x20, 0x00, 0x6a, 0x00, 0x65, 0x00, 0x73,
          0x00, 0x74, 0x00, 0x20, 0x00, 0x7a, 0x00, 0x61, 0x00, 0x70, 0x00,
          0x65, 0x00, 0x77, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x65, 0x00, 0x6e,
          0x00, 0x69, 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x72, 0x00,
          0x67, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x7a, 0x00, 0x61,
          0x00, 0x63, 0x00, 0x79, 0x00, 0x6a, 0x00, 0x6e, 0x00, 0x65, 0x00,
          0x67, 0x00, 0x6f, 0x00, 0x2c, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72,
          0x00, 0x61, 0x00, 0x77, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x67, 0x00,
          0x6f, 0x00, 0x20, 0x00, 0x69, 0x00, 0x20, 0x00, 0x66, 0x00, 0x69,
          0x00, 0x6e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x6f, 0x00,
          0x77, 0x00, 0x65, 0x00, 0x67, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x77,
          0x00, 0x73, 0x00, 0x70, 0x00, 0x61, 0x00, 0x72, 0x00, 0x63, 0x00,
          0x69, 0x00, 0x61, 0x00, 0x20, 0x00, 0x64, 0x00, 0x6c, 0x00, 0x61,
          0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x6a, 0x00,
          0x65, 0x00, 0x6b, 0x00, 0x74, 0x00, 0x75, 0x00, 0x20, 0x00, 0x4d,
          0x00, 0x6f, 0x00, 0x7a, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x6c, 0x00,
          0x61, 0x00, 0x20, 0x00, 0x69, 0x00, 0x20, 0x00, 0x6a, 0x00, 0x65,
          0x00, 0x67, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x70, 0x00, 0x6f, 0x00,
          0x63, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x6e, 0x00, 0x79,
          0x00, 0x63, 0x00, 0x68, 0x00, 0x2e, 0x00];
  
        var ndefMsg = new Uint8Array([0xc1, 0x01, 0x00, 0x00, 0x01, 0xb4, 0x54]
          .concat(payloadArray));
        var payload = new Uint8Array(payloadArray);
  
        var ndefRecord = new MozNDEFRecord({tnf: NDEF.TNF_WELL_KNOWN,
                                            type: NDEF.RTD_TEXT,
                                            payload: payload});
  
        var result = nfcUtils.encodeNDEF([ndefRecord]);
        assertArraysEqual(result, ndefMsg);
      });
  
      test('No type, no paylod, no id', function() {
        var ndefMsg = new Uint8Array([0xd0, 0x00, 0x00]);
  
        var result = nfcUtils.encodeNDEF([new MozNDEFRecord()]);
        assertArraysEqual(result, ndefMsg);
      });
  
      test('Multiple records', function() {
        var ndefMsg = new Uint8Array([0x91, 0x01, 0x11, 0x55, 0x04, 0x77, 0x69,
                                      0x6b, 0x69, 0x2e, 0x6d, 0x6f, 0x7a, 0x69,
                                      0x6c, 0x6c, 0x61, 0x2e, 0x6f, 0x72, 0x67,
                                      0x51, 0x01, 0x0c, 0x55, 0x03, 0x6d, 0x6f,
                                      0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2e, 0x6f,
                                      0x72, 0x67]);
  
        var payload1 = new Uint8Array([0x04, 0x77, 0x69, 0x6b, 0x69, 0x2e, 0x6d,
                                       0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2e,
                                       0x6f, 0x72, 0x67]);
        var rec1 = new MozNDEFRecord({tnf: NDEF.TNF_WELL_KNOWN,
                                      type: NDEF.RTD_URI,
                                      payload: payload1});
  
        var payload2 = new Uint8Array([0x03, 0x6d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c,
                                       0x61, 0x2e, 0x6f, 0x72, 0x67]);
        var rec2 = new MozNDEFRecord({tnf: NDEF.TNF_WELL_KNOWN,
                                      type: NDEF.RTD_URI,
                                      payload: payload2});
  
        var result = nfcUtils.encodeNDEF([rec1, rec2]);
        assertArraysEqual(result, ndefMsg);
      });
    });
  
    suite('parseNDEF', function() {
      var msg1;
  
      setup(function() {
        // These record payloads aren't valid, but that's fine because
        // NDEF doesn't care about the payload.
        msg1 = [
          // TNF = 1; MB | SR bits; type=[0x55]; payload length = 3
          0x91, 0x01, 0x03, 0x55, 0x03, 0x6D, 0x6F,
          // TNF = 3; SR bit; type=[0xAA, 0xBB]; payload length = 5
          0x13, 0x02, 0x05, 0xAA, 0xBB, 0x02, 0xAD, 0x8F, 0x71, 0x96,
          // TNF = 4; ME | SR bits; type=[0x55]; payload length = 7
          0x54, 0x01, 0x07, 0x55, 0xFF, 0x12, 0x9A, 0xA7, 0x11, 0xDC, 0xA8];
      });
  
      test('Subrecord', function() {
        var buf = new NfcBuffer(urlU8a);
        var ndefrecords = nfcUtils.parseNDEF(buf);
        var equal;
  
        // There is only one record here:
        assert.equal(ndefrecords[0].tnf, NDEF.TNF_WELL_KNOWN);
        equal = nfcUtils.equalArrays(ndefrecords[0].type, NDEF.RTD_URI);
        assert.equal(equal, true, 'type');
  
        equal = nfcUtils.equalArrays(ndefrecords[0].id, new Uint8Array());
        assert.equal(equal, true, 'id');
  
        equal = nfcUtils.equalArrays(ndefrecords[0].payload,
                                   nfcUtils.fromUTF8('\u0003mozilla.org'));
        assert.equal(equal, true, 'payload');
      });
  
      test('Returns null when first record has no MB bit set',
        function() {
          msg1[0] ^= 128;
          var parsed = nfcUtils.parseNDEF(new NfcBuffer(new Uint8Array(msg1)));
          assert.isNull(parsed);
      });
  
      test('Returns null when last record has no ME bit set',
        function() {
          msg1[17] ^= 64;
          var parsed = nfcUtils.parseNDEF(new NfcBuffer(new Uint8Array(msg1)));
          assert.isNull(parsed);
      });
  
      test('Returns null when not only last record has ME bit set',
        function() {
  
          msg1[7] ^= 64;
          var parsed = nfcUtils.parseNDEF(new NfcBuffer(new Uint8Array(msg1)));
          assert.isNull(parsed);
      });
  
      test('Returns null when no record has ME bit set', function() {
        msg1[17] ^= 64;
        var parsed = nfcUtils.parseNDEF(new NfcBuffer(new Uint8Array(msg1)));
        assert.isNull(parsed);
      });
  
      test('Returns null if MB is set for record other than first',
        function() {
  
          msg1[7] ^= 128;
          var parsed = nfcUtils.parseNDEF(new NfcBuffer(new Uint8Array(msg1)));
          assert.isNull(parsed);
      });
  
      test('Returns null if CF is set (not supported)',
        function() {
  
          msg1[0] ^= 32;
          var parsed = nfcUtils.parseNDEF(new NfcBuffer(new Uint8Array(msg1)));
          assert.isNull(parsed);
      });
  
      test('Decodes multi record messages', function() {
        var parsed = nfcUtils.parseNDEF(new NfcBuffer(new Uint8Array(msg1)));
        assert.isNotNull(parsed);
        assert.equal(parsed.length, 3);
  
        assert.equal(parsed[0].tnf, NDEF.TNF_WELL_KNOWN);
        assert.equal(parsed[1].tnf, NDEF.TNF_ABSOLUTE_URI);
        assert.equal(parsed[2].tnf, NDEF.TNF_EXTERNAL_TYPE);
  
        assertArraysEqual(parsed[0].type, [0x55], 'type 0');
        assertArraysEqual(parsed[1].type, [0xAA, 0xBB], 'type 1');
        assertArraysEqual(parsed[2].type, [0x55], 'type 2');
  
        assertArraysEqual(parsed[0].payload,
          [0x03, 0x6D, 0x6F], 'payload 0');
        assertArraysEqual(parsed[1].payload,
          [0x02, 0xAD, 0x8F, 0x71, 0x96], 'payload 1');
        assertArraysEqual(parsed[2].payload,
          [0xFF, 0x12, 0x9A, 0xA7, 0x11, 0xDC, 0xA8], 'payload 2');
      });
    });
  
    suite('parseNDEFRecord', function() {
      var record;
  
      setup(function() {
        // URI record, "http://mozilla.com"
        record = [0xD1, 0x01, 0x0C, 0x55,
                  0x03, 0x6D, 0x6F, 0x7A,
                  0x69, 0x6C, 0x6C, 0x61,
                  0x2E, 0x6F, 0x72, 0x67];
      });
  
      test('Supports records with ID', function() {
        // URI record, "http://mozilla.com"
        // id = [0x01, 0x02, 0x03, 0x04]
        var record = [0xD9, 0x01, 0x0C, 0x04,
                      0x55, 0x01, 0x02, 0x03,
                      0x04, 0x03, 0x6D, 0x6F,
                      0x7A, 0x69, 0x6C, 0x6C,
                      0x61, 0x2E, 0x6F, 0x72,
                      0x67];
        var parsed = nfcUtils._parseNDEFRecord(new NfcBuffer(record));
        assert.isNotNull(parsed);
        assertArraysEqual(parsed.id, [0x01, 0x02, 0x03, 0x04]);
      });
  
      test('Supports records without ID', function() {
        var parsed = nfcUtils._parseNDEFRecord(new NfcBuffer(record));
        assert.isNotNull(parsed);
        assertArraysEqual(parsed.id, []);
      });
  
      test('Supports long records (no SR bit set)', function() {
        var record = [0xC1, 0x01, 0x00, 0x00, 0x01, 0x1C, 0x55];
        var payload = new Array(284);
        for (var i = 0; i < payload.length; i += 1) {
          payload[i] = 7;
        }
        record = record.concat(payload);
  
        var parsed = nfcUtils._parseNDEFRecord(new NfcBuffer(record));
        assert.isNotNull(parsed);
        assertArraysEqual(parsed.payload, payload);
      });
  
      test('Decodes TNF', function() {
        var parsed = nfcUtils._parseNDEFRecord(new NfcBuffer(record));
        assert.isNotNull(parsed);
        assert.equal(parsed.tnf, NDEF.TNF_WELL_KNOWN);
  
        record[0] ^= 1;
        record[0] |= 5;
        parsed = nfcUtils._parseNDEFRecord(new NfcBuffer(record));
        assert.isNotNull(parsed);
        assert.equal(parsed.tnf, NDEF.TNF_UNKNOWN);
      });
  
      test('Decodes type', function() {
        var parsed = nfcUtils._parseNDEFRecord(new NfcBuffer(record));
        assert.isNotNull(parsed);
        assertArraysEqual(parsed.type, [0x55]);
      });
  
      test('Decodes payload', function() {
        var parsed = nfcUtils._parseNDEFRecord(new NfcBuffer(record));
        assert.isNotNull(parsed);
        assertArraysEqual(parsed.payload, [0x03, 0x6D, 0x6F, 0x7A, 0x69,
                                          0x6C, 0x6C, 0x61, 0x2E, 0x6F,
                                          0x72, 0x67]);
      });
    });
  
    suite('NDEFUtils', function() {
       test('Encode and Parse Handover Request', function() {
        var mac = '01:02:03:04:05:06';
        var cps = 0x2;
        var hrNDEFs1 = NDEFUtils.encodeHandoverRequest(mac, cps);
        assert.equal(!!hrNDEFs1, true);
        var hrNDEFU8a1 = nfcUtils.encodeNDEF(hrNDEFs1);
        assert.equal(!!hrNDEFU8a1, true);
  
        var buf = new NfcBuffer(hrNDEFU8a1);
        var hrNDEFs2 = nfcUtils.parseNDEF(buf);
        assert.equal(!!hrNDEFs2, true);
  
        var hrNDEFU8a2 = nfcUtils.encodeNDEF(hrNDEFs2);
        assert.equal(!!hrNDEFU8a2, true);
  
        var equal1 = nfcUtils.equalArrays(hrNDEFU8a2, hrNDEFU8a1);
        assert.equal(equal1, true);
      });
  
      test('Encode and Parse Handover Select', function() {
        var mac = '01:02:03:04:05:06';
        var cps = 0x2;
        var hsNDEFs1 = NDEFUtils.encodeHandoverSelect(mac, cps);
        assert.equal(!!hsNDEFs1, true);
  
        var hsNDEFU8a1 = nfcUtils.encodeNDEF(hsNDEFs1);
        assert.equal(!!hsNDEFU8a1, true);
  
        var buf = new NfcBuffer(hsNDEFU8a1);
        var hsNDEFs2 = nfcUtils.parseNDEF(buf);
        assert.equal(!!hsNDEFs2, true);
  
        var hsNDEFU8a2 = nfcUtils.encodeNDEF(hsNDEFs2);
        assert.equal(!!hsNDEFU8a2, true);
  
        var equal1 = nfcUtils.equalArrays(hsNDEFU8a2, hsNDEFU8a1);
        assert.equal(equal1, true);
      });
    });
  });
  
  suite('NfcBuffer', function() {
    var arr;
    var buf;
  
    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);
      }
    }
  
    setup(function() {
      arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
      buf = new NfcBuffer(arr);
    });
  
    test('getOctet()', function() {
      assert.equal(0, buf.getOctet());
      assert.equal(1, buf.getOctet());
      assert.equal(2, buf.getOctet());
      assert.equal(3, buf.getOctet());
    });
  
    test('getOctetArray()', function() {
      assertArraysEqual([0, 1, 2], buf.getOctetArray(3));
      assertArraysEqual([3], buf.getOctetArray(1));
      assertArraysEqual([4, 5, 6, 7], buf.getOctetArray(4));
      assertArraysEqual([], buf.getOctetArray(0));
    });
  
    test('skip()', function() {
      buf.skip(2);
      assert.equal(2, buf.getOctet());
      buf.skip(3);
      assert.equal(6, buf.getOctet());
      buf.skip(0);
      assert.equal(7, buf.getOctet());
    });
  
    test('peek()', function() {
      assert.equal(buf.peek(), 0);
      assert.equal(buf.peek(), 0);
      assert.equal(buf.peek(), 0);
      buf.getOctet();
      assert.equal(buf.peek(), 1);
      assert.equal(buf.peek(), 1);
    });
  
    test('len is required', function() {
      assert.throws(function() {
        buf.getOctetArray();
      });
  
      assert.throws(function() {
        buf.skip();
      });
    });
  
    test('getOctetArray() throws when len is negative', function() {
      assert.throws(function() {
        buf.getOctetArray(-1);
      }, Error);
    });
  
    test('skip() throws when len is negative', function() {
      assert.throws(function() {
        buf.skip(-1);
      });
    });
  
    test('getOctetArray() throws on attempt to read too many octets',
      function() {
  
      assert.throws(function() {
        buf.getOctetArray(arr.length + 1);
      }, Error);
    });
  
    test('getOctet() throws on attempt to read after last octet', function() {
      buf.skip(arr.length);
  
      assert.throws(function() {
        buf.getOctet();
      }, Error);
    });
  
    test('skip() throws when after last octet', function() {
      buf.skip(arr.length);
  
      assert.throws(function() {
        buf.skip(1);
      }, Error);
    });
  });
  
  suite('NDEF.payload', function() {
    var nfcUtils;
    mocksForNfcUtils.attachTestHelpers();
  
    setup(function() {
      nfcUtils = new NfcUtils();
    });
  
    suite('decode()', function() {
      test('TNF empty', function() {
        var decoded = NDEF.payload.decode(NDEF.TNF_EMPTY, undefined, undefined);
        assert.deepEqual(decoded, {type: 'empty'});
      });
  
      test('TNF well known', function() {
        var stubDecodeWellKnown = this.sinon.stub(NDEF.payload,
                                                  'decodeWellKnown');
        NDEF.payload.decode(NDEF.TNF_WELL_KNOWN, NDEF.RTD_URI, 'payload');
        assert.isTrue(stubDecodeWellKnown.withArgs(NDEF.RTD_URI, 'payload')
                                         .calledOnce);
      });
  
      test('TNF mime media-type', function() {
        // text/plain
        var type = new Uint8Array([0x74, 0x65, 0x78, 0x74,
                                   0x2F, 0x70, 0x6C, 0x61,
                                   0x69, 0x6E]);
        // What up?!
        var payload = new Uint8Array([0x57, 0x68, 0x61, 0x74,
                                      0x20, 0x75, 0x70, 0x3F,
                                      0x21]);
  
        var stubDecodeMIME = this.sinon.stub(NDEF.payload, 'decodeMIME');
  
        NDEF.payload.decode(NDEF.TNF_MIME_MEDIA, type, payload);
        assert.isTrue(stubDecodeMIME.withArgs(type, payload).calledOnce);
      });
  
      test('TNF absolute uri', function() {
        // TNF_ABSOLUTE_URI has uri in the type
        var type = new Uint8Array([0x68, 0x74, 0x74, 0x70,
                                   0x3A, 0x2F, 0x2F, 0x6D,
                                   0x6F, 0x7A, 0x69, 0x6C,
                                   0x6C, 0x61, 0x2E, 0x6F,
                                   0x72, 0x67]);
  
        var decoded = NDEF.payload.decode(NDEF.TNF_ABSOLUTE_URI, type, null);
        assert.deepEqual(decoded, { type: 'http://mozilla.org' });
      });
  
      test('TNF external type', function() {
        var type = new Uint8Array([0x6D, 0x6F, 0x7A, 0x69,
                                   0x6C, 0x6C, 0x61, 0x2E,
                                   0x6F, 0x72, 0x67, 0x3A,
                                   0x62, 0x75, 0x67]);
  
        var decoded = NDEF.payload.decode(NDEF.TNF_EXTERNAL_TYPE, type, null);
        assert.deepEqual(decoded, { type: 'mozilla.org:bug' });
      });
  
      test('TNF unknown', function() {
        var decUnknown = NDEF.payload.decode(NDEF.TNF_UNKNOWN, null, 'data1');
        assert.deepEqual(decUnknown, {}, 'should be empty initialized object');
      });
  
      test('TNF unchanged', function() {
        var decoded = NDEF.payload.decode(NDEF.TNF_UNCHANGED, null, 'data');
        assert.equal(decoded, null);
      });
    }),
  
    suite('decodeWellKnown()', function() {
      test('RTD Text', function() {
        var stubDecodeText = this.sinon.stub(NDEF.payload, 'decodeText');
  
        NDEF.payload.decodeWellKnown(NDEF.RTD_TEXT, 'payload');
        assert.isTrue(stubDecodeText.withArgs('payload').calledOnce);
      });
  
      test('RTD URI', function() {
        var stubDecodeURI = this.sinon.stub(NDEF.payload, 'decodeURI');
  
        NDEF.payload.decodeWellKnown(NDEF.RTD_URI, 'payload');
        assert.isTrue(stubDecodeURI.withArgs('payload').calledOnce);
      });
  
      test('RTD Smart Poster', function() {
        var stubDecodeSmartPoster = this.sinon.stub(NDEF.payload,
                                                    'decodeSmartPoster');
  
        NDEF.payload.decodeWellKnown(NDEF.RTD_SMART_POSTER, 'payload');
        assert.isTrue(stubDecodeSmartPoster.withArgs('payload').calledOnce);
      });
  
      test('Wrong RTD', function() {
        var decoded = NDEF.payload.decodeWellKnown('fake', 'payload');
        assert.equal(decoded, null);
      });
    }),
  
    suite('decodeText()', function() {
      test('UTF-8 en', function() {
        var payload = new Uint8Array([0x02, 0x65, 0x6E, 0x48,
                                      0x65, 0x79, 0x21, 0x20,
                                      0x55, 0x54, 0x46, 0x2D,
                                      0x38, 0x20, 0x65, 0x6E]);
  
        var decoded = NDEF.payload.decodeText(payload);
        assert.deepEqual(decoded, {
          type: 'text',
          text: 'Hey! UTF-8 en',
          language: 'en',
          encoding: 'UTF-8'
        });
      });
  
      test('UTF-16 en', function() {
        var payload = new Uint8Array([0x82, 0x65, 0x6E, 0xFF,
                                  0xFE, 0x48, 0x00, 0x6F,
                                  0x00, 0x21, 0x00, 0x20,
                                  0x00, 0x55, 0x00, 0x54,
                                  0x00, 0x46, 0x00, 0x2D,
                                  0x00, 0x31, 0x00, 0x36,
                                  0x00, 0x20, 0x00, 0x65,
                                  0x00, 0x6E, 0x00]);
  
        var decoded = NDEF.payload.decodeText(payload);
        assert.deepEqual(decoded, {
          type: 'text',
          text: 'Ho! UTF-16 en',
          language: 'en',
          encoding: 'UTF-16'
        });
      });
    }),
  
    suite('decodeURI()', function() {
      test('https://', function() {
        var payload = new Uint8Array([0x04, 0x77, 0x69, 0x6B,
                                      0x69, 0x2E, 0x6D, 0x6F,
                                      0x7A, 0x69, 0x6C, 0x6C,
                                      0x61, 0x2E, 0x6F, 0x72,
                                      0x67]);
  
        var decoded = NDEF.payload.decodeURI(payload);
        assert.deepEqual(decoded, {
          type: 'uri',
          uri: 'https://wiki.mozilla.org'
        });
      });
  
      test('mailto:', function() {
        // \u0006 is short for mailto:
        var payload = nfcUtils.fromUTF8('\u0006jorge@borges.ar');
  
        var decoded = NDEF.payload.decodeURI(payload);
        assert.deepEqual(decoded, {
          type: 'uri',
          uri: 'mailto:jorge@borges.ar'
        });
      });
  
      test('tel:', function() {
        // \u0005 is short for tel:
        var payload = nfcUtils.fromUTF8('\u00050054267437');
  
        var decoded = NDEF.payload.decodeURI(payload);
        assert.deepEqual(decoded, {
          type: 'uri',
          uri: 'tel:0054267437'
        });
      });
  
      test('unabbreviated', function() {
        var payload = nfcUtils.fromUTF8('\u0000http://mozilla.com');
  
        var decoded = NDEF.payload.decodeURI(payload);
        assert.deepEqual(decoded, {
          type: 'uri',
          uri: 'http://mozilla.com'
        });
      });
    }),
  
    suite('decodeSmartPoster()', function() {
      /**
       * Constructs a NDEF message with one record. This record
       * is a smart poster, so it contains multiple subrecords
       * in the payload, encoded as bytes.
       *
       * Accepts 3*N arguments, where:
       *  3*(N + 0) argument - TNF of the record
       *  3*(N + 1) argument - type given as string
       *  3*(N + 2) argument - payload, given as Uint8Array
       */
      var makePoster = function() {
        var records = [];
        for (var arg = 0; arg < arguments.length; arg += 3) {
          records.push({
            tnf: arguments[arg],
            type: nfcUtils.fromUTF8(arguments[arg + 1]),
            payload: arguments[arg + 2],
            id: new Uint8Array()
          });
        }
  
        var payload = nfcUtils.encodeNDEF(records);
        return [{
          tnf: NDEF.TNF_WELL_KNOWN,
          type: NDEF.RTD_SMART_POSTER,
          id: new Uint8Array(),
          payload: new Uint8Array(payload)
        }];
      };
  
      var recordURI,
          recordTextEnglish,
          recordTextPolish,
          recordAction,
          recordIcon;
  
      setup(function() {
        // URI "http://www.youtube.com" with abbreviation
        // for "http://www."
        recordURI = [
          0x01,  // Payload: abbreviation
          0x79, 0x6F, 0x75, 0x74, // 'yout'
          0x75, 0x62, 0x65, 0x2E, // 'ube.'
          0x63, 0x6F, 0x6D        // 'com'
        ];
  
        // Text record with contents: "Best page ever!  q#@"
        // and language code: "en"
        recordTextEnglish = [
          0x02,  // Status byte: UTF-8, two byte lang code
          0x65, 0x6E, // ISO language code: 'en'
          0x42, 0x65, 0x73, 0x74, // 'Best'
          0x20, 0x70, 0x61, 0x67, // ' pag'
          0x65, 0x20, 0x65, 0x76, // 'e ev'
          0x65, 0x72, 0x21, 0x20, // 'er! '
          0x20, 0x71, 0x23, 0x40  // ' q#@'
        ];
  
        // Text record with contents: "ąćńó"
        // and language code: "pl"
        recordTextPolish = [
         0x02,  // Status byte: UTF-8, two byte lang code
         0x70, 0x6C, // ISO language code: 'pl'
         0xC4, 0x85, 0xC4, 0x87, // 'ąć'
         0xC5, 0x84, 0xC3, 0xB3  // 'ńó'
        ];
  
        // Action record with action = 0
        recordAction = [0x00];
  
        // Icon record with simple 4x4 PNG image.
        recordIcon = [
          0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
          0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
          0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04,
          0x08, 0x02, 0x00, 0x00, 0x00, 0x26, 0x93, 0x09,
          0x29, 0x00, 0x00, 0x00, 0x1b, 0x49, 0x44, 0x41,
          0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff, 0xff, 0x3f,
          0x03, 0x0c, 0x30, 0xc2, 0x39, 0x8c, 0x8c, 0x8c,
          0x4c, 0x10, 0x0a, 0x2a, 0x85, 0xac, 0x0c, 0x00,
          0x26, 0x0b, 0x09, 0x01, 0xc3, 0xd1, 0x9a, 0x7b,
          0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
          0xae, 0x42, 0x60, 0x82
        ];
      });
  
      test('Decodes simple poster', function() {
        var poster = makePoster(NDEF.TNF_WELL_KNOWN, 'U', recordURI);
        var decoded = NDEF.payload.decodeSmartPoster(poster[0].payload);
        assert.equal(decoded.type, 'smartposter');
        assert.equal(decoded.uri, 'http://www.youtube.com');
      });
  
      test('Decodes extra records', function() {
        var poster = makePoster(NDEF.TNF_WELL_KNOWN, 'U', recordURI,
                                NDEF.TNF_WELL_KNOWN, 'T', recordTextEnglish,
                                NDEF.TNF_WELL_KNOWN, 'T', recordTextPolish,
                                NDEF.TNF_WELL_KNOWN, 'act', recordAction,
                                NDEF.TNF_MIME_MEDIA, 'image/png', recordIcon);
  
        var decoded = NDEF.payload.decodeSmartPoster(poster[0].payload);
        assert.equal(decoded.uri, 'http://www.youtube.com');
        assert.equal(decoded.text.en, 'Best page ever!  q#@');
        assert.equal(decoded.text.pl, 'ąćńó');
        assert.equal(decoded.icons.length, 1);
        assert.equal(decoded.icons[0].type, 'image/png');
        assert.isTrue(nfcUtils.equalArrays(decoded.icons[0].bytes,
                                           recordIcon));
      });
  
      test('Does not handle poster with multiple URIs', function() {
        var poster = makePoster(NDEF.TNF_WELL_KNOWN, 'U', recordURI,
                                NDEF.TNF_WELL_KNOWN, 'U', recordURI);
  
        var decoded = NDEF.payload.decodeSmartPoster(poster[0].payload);
        assert.equal(decoded, null);
      });
  
      test('Does not handle poster with no URI', function() {
        var poster = makePoster(NDEF.TNF_WELL_KNOWN, 'T', recordTextEnglish,
                                NDEF.TNF_WELL_KNOWN, 'act', recordAction);
  
        var decoded = NDEF.payload.decodeSmartPoster(poster[0].payload);
        assert.equal(decoded, null);
      });
  
      test('Does not handle poster with multiple title ' +
        'records in the same language', function() {
  
        var poster = makePoster(NDEF.TNF_WELL_KNOWN, 'U', recordURI,
                                NDEF.TNF_WELL_KNOWN, 'T', recordTextEnglish,
                                NDEF.TNF_WELL_KNOWN, 'T', recordTextEnglish);
  
        var decoded = NDEF.payload.decodeSmartPoster(poster[0].payload);
        assert.equal(decoded, null);
      });
    });
  
    suite('decodeMIME()', function() {
      suite('vcards', function() {
        var vcardTxt = 'BEGIN:VCARD\nVERSION:2.1\nN:J;\nEND:VCARD';
        var payload;
        var decoded;
  
        setup(function() {
          payload = nfcUtils.fromUTF8(vcardTxt);
          decoded = {
            type: 'text/vcard',
            blob: new Blob([vcardTxt], {type: 'text/vcard'})
          };
        });
  
        test('text/vcard', function() {
          var vcard = NDEF.payload.decodeMIME(nfcUtils.fromUTF8('text/vcard'),
                                              payload);
          assert.deepEqual(decoded, vcard);
        });
  
        test('text/x-vCard', function() {
          var vcard = NDEF.payload.decodeMIME(nfcUtils.fromUTF8('text/x-vCard'),
                                              payload);
          assert.deepEqual(decoded, vcard);
        });
  
        test('text/x-vcard', function() {
          var vcard = NDEF.payload.decodeMIME(nfcUtils.fromUTF8('text/x-vcard'),
                                              payload);
          assert.deepEqual(decoded, vcard);
        });
      });
  
      test('other', function() {
        var decoded = NDEF.payload.decodeMIME(nfcUtils.fromUTF8('text/plain'),
                                               'payload');
        assert.deepEqual(decoded, { type: 'text/plain' });
      });
    });
  });
  