content: package org.altbeacon.beacon;
  
  import org.altbeacon.beacon.logging.LogManager;
  import org.altbeacon.beacon.logging.Loggers;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  import org.robolectric.RobolectricTestRunner;
  import org.robolectric.annotation.Config;
  
  import java.util.Arrays;
  
  import static org.junit.Assert.assertArrayEquals;
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertNotNull;
  import static org.junit.Assert.assertNull;
  
  @Config(sdk = 28)
  
  @RunWith(RobolectricTestRunner.class)
  
  /*
  HOW TO SEE DEBUG LINES FROM YOUR UNIT TESTS:
  1. set a line like this at the start of your test:
             org.robolectric.shadows.ShadowLog.stream = System.err;
  2. run the tests from the command line
  3. Look at the test report file in your web browser, e.g.
     file:///Users/dyoung/workspace/AndroidProximityLibrary/build/reports/tests/index.html
  4. Expand the System.err section
   */
  public class BeaconParserTest {
  
      public static byte[] hexStringToByteArray(String s) {
          int len = s.length();
          byte[] data = new byte[len / 2];
          for (int i = 0; i < len; i += 2) {
              data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                      + Character.digit(s.charAt(i+1), 16));
          }
          return data;
      }
      public static String byteArrayToHexString(byte[] bytes) {
          StringBuilder sb = new StringBuilder();
          for (int i = 0; i < bytes.length; i++) {
              sb.append(String.format("%02x", bytes[i]));
          }
          return sb.toString();
      }
  
      @Test
      public void testSetBeaconLayout() {
          byte[] bytes = hexStringToByteArray("02011a1bffbeac2f234454cf6d4a0fadf2f4911ba9ffa600010002c509000000");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25");
  
          assertEquals("parser should get beacon type code start offset", new Integer(2), parser.mMatchingBeaconTypeCodeStartOffset);
          assertEquals("parser should get beacon type code end offset",  new Integer(3), parser.mMatchingBeaconTypeCodeEndOffset);
          assertEquals("parser should get beacon type code", new Long(0xbeac), parser.getMatchingBeaconTypeCode());
          assertEquals("parser should get identifier start offset", new Integer(4), parser.mIdentifierStartOffsets.get(0));
          assertEquals("parser should get identifier end offset", new Integer(19), parser.mIdentifierEndOffsets.get(0));
          assertEquals("parser should get identifier start offset", new Integer(20), parser.mIdentifierStartOffsets.get(1));
          assertEquals("parser should get identifier end offset", new Integer(21), parser.mIdentifierEndOffsets.get(1));
          assertEquals("parser should get identifier start offset", new Integer(22), parser.mIdentifierStartOffsets.get(2));
          assertEquals("parser should get identifier end offset", new Integer(23), parser.mIdentifierEndOffsets.get(2));
          assertEquals("parser should get power start offset", new Integer(24), parser.mPowerStartOffset);
          assertEquals("parser should get power end offset", new Integer(24), parser.mPowerEndOffset);
          assertEquals("parser should get data start offset", new Integer(25), parser.mDataStartOffsets.get(0));
          assertEquals("parser should get data end offset", new Integer(25), parser.mDataEndOffsets.get(0));
  
      }
  
      @Test
      public void testLongToByteArray() {
          BeaconParser parser = new BeaconParser();
          byte[] bytes = parser.longToByteArray(10, 1);
          assertEquals("first byte should be 10", 10, bytes[0]);
      }
  
      @Test
      public void testRecognizeBeacon() {
          LogManager.setLogger(Loggers.verboseLogger());
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("02011a1aff180112342f234454cf6d4a0fadf2f4911ba9ffa600010002c5");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:2-3=1234,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25");
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertEquals("mRssi should be as passed in", -55, beacon.getRssi());
          assertEquals("uuid should be parsed", "2f234454-cf6d-4a0f-adf2-f4911ba9ffa6", beacon.getIdentifier(0).toString());
          assertEquals("id2 should be parsed", "1", beacon.getIdentifier(1).toString());
          assertEquals("id3 should be parsed", "2", beacon.getIdentifier(2).toString());
          assertEquals("txPower should be parsed", -59, beacon.getTxPower());
          assertEquals("manufacturer should be parsed", 0x118 ,beacon.getManufacturer());
      }
  
      @Test
      public void testAllowsAccessToParserIdentifier() {
          LogManager.setLogger(Loggers.verboseLogger());
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("02011a1aff180112342f234454cf6d4a0fadf2f4911ba9ffa600010002c5");
          BeaconParser parser = new BeaconParser("my_beacon_type");
          parser.setBeaconLayout("m:2-3=1234,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25");
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertEquals("parser identifier should be accessible", "my_beacon_type", beacon.getParserIdentifier());
      }
  
      @Test
      public void testParsesBeaconMissingDataField() {
          LogManager.setLogger(Loggers.verboseLogger());
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("02011a1aff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c5000000");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25");
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertEquals("mRssi should be as passed in", -55, beacon.getRssi());
          assertEquals("uuid should be parsed", "2f234454-cf6d-4a0f-adf2-f4911ba9ffa6", beacon.getIdentifier(0).toString());
          assertEquals("id2 should be parsed", "1", beacon.getIdentifier(1).toString());
          assertEquals("id3 should be parsed", "2", beacon.getIdentifier(2).toString());
          assertEquals("txPower should be parsed", -59, beacon.getTxPower());
          assertEquals("manufacturer should be parsed", 0x118 ,beacon.getManufacturer());
          assertEquals("missing data field zero should be zero", new Long(0l), beacon.getDataFields().get(0));
  
      }
  
  
      @Test
      public void testRecognizeBeaconWithFormatSpecifyingManufacturer() {
          LogManager.setLogger(Loggers.verboseLogger());
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("02011a1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c509000000");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:0-3=1801beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25");
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertEquals("mRssi should be as passed in", -55, beacon.getRssi());
          assertEquals("uuid should be parsed", "2f234454-cf6d-4a0f-adf2-f4911ba9ffa6", beacon.getIdentifier(0).toString());
          assertEquals("id2 should be parsed", "1", beacon.getIdentifier(1).toString());
          assertEquals("id3 should be parsed", "2", beacon.getIdentifier(2).toString());
          assertEquals("txPower should be parsed", -59, beacon.getTxPower());
          assertEquals("manufacturer should be parsed", 0x118 ,beacon.getManufacturer());
      }
  
      @Test
      public void testReEncodesBeacon() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("02011a1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c509");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25");
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          byte[] regeneratedBytes = parser.getBeaconAdvertisementData(beacon);
          byte[] expectedMatch = Arrays.copyOfRange(bytes, 7, bytes.length);
          assertArrayEquals("beacon advertisement bytes should be the same after re-encoding", expectedMatch, regeneratedBytes);
      }
  
      @Test
      public void testReEncodesBeaconForEddystoneTelemetry() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("0201060303aafe1516aafe2001021203130414243405152535");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout(BeaconParser.EDDYSTONE_TLM_LAYOUT);
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          byte[] regeneratedBytes = parser.getBeaconAdvertisementData(beacon);
          byte[] expectedMatch = Arrays.copyOfRange(bytes, 11, bytes.length);
          assertEquals("beacon advertisement bytes should be the same after re-encoding", byteArrayToHexString(expectedMatch), byteArrayToHexString(regeneratedBytes));
      }
  
      @Test
      public void testLittleEndianIdentifierParsing() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("02011a1bff1801beac0102030405060708090a0b0c0d0e0f1011121314c50900000000");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:2-3=beac,i:4-9,i:10-15l,i:16-23,p:24-24,d:25-25");
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertEquals("mRssi should be as passed in", -55, beacon.getRssi());
          assertEquals("id1 should be big endian", "0x010203040506", beacon.getIdentifier(0).toString());
          assertEquals("id2 should be little endian", "0x0c0b0a090807", beacon.getIdentifier(1).toString());
          assertEquals("id3 should be big endian", "0x0d0e0f1011121314", beacon.getIdentifier(2).toString());
          assertEquals("txPower should be parsed", -59, beacon.getTxPower());
          assertEquals("manufacturer should be parsed", 0x118, beacon.getManufacturer());
      }
  
      @Test
      public void testReEncodesLittleEndianBeacon() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("02011a1bff1801beac0102030405060708090a0b0c0d0e0f1011121314c509");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:2-3=beac,i:4-9,i:10-15l,i:16-23,p:24-24,d:25-25");
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          byte[] regeneratedBytes = parser.getBeaconAdvertisementData(beacon);
          byte[] expectedMatch = Arrays.copyOfRange(bytes, 7, bytes.length);
          System.err.println(byteArrayToHexString(expectedMatch));
          System.err.println(byteArrayToHexString(regeneratedBytes));
          assertEquals("beacon advertisement bytes should be the same after re-encoding", byteArrayToHexString(expectedMatch), byteArrayToHexString(regeneratedBytes));
      }
  
  
      @Test
      public void testRecognizeBeaconCapturedManufacturer() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("0201061bffaabbbeace2c56db5dffb48d2b060d0f5a71096e000010004c50000000000000000000000000000000000000000000000000000000000000000");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25");
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertEquals("manufacturer should be parsed", "bbaa", String.format("%04x", beacon.getManufacturer()));
      }
  
  
      @Test
      public void testParseGattIdentifierThatRunsOverPduLength() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("0201060303aafe0d16aafe10e702676f6f676c65000c09526164426561636f6e204700000000000000000000000000000000000000000000000000000000");
          BeaconParser parser = new BeaconParser();
          parser.setAllowPduOverflow(false);
          parser.setBeaconLayout("s:0-1=feaa,m:2-2=10,p:3-3:-41,i:4-20");
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertNull("beacon should not be parsed", beacon);
      }
  
      @Test
      public void testLongUrlBeaconIdentifier() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
          byte[] bytes = hexStringToByteArray("0201060303aafe0d16aafe10e70102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f00000000000000000000000000000000000000");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("s:0-1=feaa,m:2-2=10,p:3-3:-41,i:4-20v");
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertEquals("URL Identifier should be truncated at 8 bytes", 8, beacon.getId1().toByteArray().length);
      }
  
      @Test
      public void testParseManufacturerIdentifierThatRunsOverPduLength() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
  
          // Note that the length field below is 0x16 instead of 0x1b, indicating that the packet ends
          // one byte before the second identifier field starts
          byte[] bytes = hexStringToByteArray("02011a16ff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c509000000");
          BeaconParser parser = new BeaconParser();
          parser.setAllowPduOverflow(false);
          parser.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25");
  
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertNull("beacon should not be parsed", beacon);
      }
  
      @Test
      public void testParseProblematicBeaconFromIssue229() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
  
          // Note that the length field below is 0x16 instead of 0x1b, indicating that the packet ends
          // one byte before the second identifier field starts
  
          byte[] bytes = hexStringToByteArray("0201061bffe000beac7777772e626c756b692e636f6d000100010001abaa000000");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25");
  
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertNotNull("beacon should be parsed", beacon);
      }
  
  
      @Test
      public void testCanParseLocationBeacon() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
          //BeaconManager.setDebug(true);
  
          double latitude = 38.93;
          double longitude = -77.23;
          Beacon beacon = new Beacon.Builder()
                  .setManufacturer(0x0118) // Radius Networks
                  .setId1("1") // device sequence number
                  .setId2(String.format("0x%08X", (long)((latitude+90)*10000.0)))
                  .setId3(String.format("0x%08X", (long)((longitude+180)*10000.0)))
                  .setTxPower(-59) // The measured transmitter power at one meter in dBm
                  .build();
          // TODO: make this pass if data fields are little endian or > 4 bytes (or even > 2 bytes)
          BeaconParser p = new BeaconParser().
                  setBeaconLayout("m:2-3=10ca,i:4-9,i:10-13,i:14-17,p:18-18");
          byte[] bytes = p.getBeaconAdvertisementData(beacon);
          byte[] headerBytes = hexStringToByteArray("02011a1bff1801");
          byte[] advBytes = new byte[bytes.length+headerBytes.length];
          System.arraycopy(headerBytes, 0, advBytes, 0, headerBytes.length);
          System.arraycopy(bytes, 0, advBytes, headerBytes.length, bytes.length);
  
          Beacon parsedBeacon = p.fromScanData(advBytes, -59, null, 123456L);
          assertNotNull(String.format("Parsed beacon from %s should not be null", byteArrayToHexString(advBytes)), parsedBeacon);
          double parsedLatitude = Long.parseLong(parsedBeacon.getId2().toString().substring(2), 16) / 10000.0 - 90.0;
          double parsedLongitude = Long.parseLong(parsedBeacon.getId3().toString().substring(2), 16) / 10000.0 - 180.0;
  
          long encodedLatitude = (long)((latitude+90)*10000.0);
          assertEquals("encoded latitude hex should match", String.format("0x%08x", encodedLatitude), parsedBeacon.getId2().toString());
          assertEquals("device sequence num should be same", "0x000000000001", parsedBeacon.getId1().toString());
          assertEquals("latitude should be about right", latitude, parsedLatitude, 0.0001);
          assertEquals("longitude should be about right", longitude, parsedLongitude, 0.0001);
  
      }
      @Test
      public void testCanGetAdvertisementDataForUrlBeacon() {
          org.robolectric.shadows.ShadowLog.stream = System.err;
          BeaconManager.setDebug(true);
          Beacon beacon = new Beacon.Builder()
                  .setManufacturer(0x0118)
                  .setId1("02646576656c6f7065722e636f6d") // http://developer.com
                  .setTxPower(-59) // The measured transmitter power at one meter in dBm
                  .build();
          BeaconParser p = new BeaconParser().
                  setBeaconLayout("s:0-1=feaa,m:2-2=10,p:3-3:-41,i:4-20v");
          byte[] bytes = p.getBeaconAdvertisementData(beacon);
          assertEquals("First byte of url should be in position 3", 0x02, bytes[2]);
      }
      @Test
      public void doesNotCashWithOverflowingByteCodeComparisonOnPdu() {
          // Test for https://github.com/AltBeacon/android-beacon-library/issues/323
          org.robolectric.shadows.ShadowLog.stream = System.err;
  
          // Note that the length field below is 0x16 instead of 0x1b, indicating that the packet ends
          // one byte before the second identifier field starts
  
          byte[] bytes = hexStringToByteArray("02010604ffe000be");
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25");
  
          Beacon beacon = parser.fromScanData(bytes, -55, null, 123456L);
          assertNull("beacon not be parsed without an exception being thrown", beacon);
      }
  
      @Test
      public void testCanParseLongDataTypeOfDifferentSize(){
          // Create a beacon parser
          BeaconParser parser = new BeaconParser();
          parser.setBeaconLayout("m:2-3=0118,i:4-7,p:8-8,d:9-16,d:18-21,d:22-25");
  
          // Generate sample beacon for test purpose.
          java.util.List<Long> sampleData = new java.util.ArrayList<Long>();
          Long now = System.currentTimeMillis();
          sampleData.add(now);
          sampleData.add(1234L);
          sampleData.add(9876L);
          Beacon beacon = new Beacon.Builder()
                  .setManufacturer(0x0118)
                  .setId1("02646576656c6f7065722e636f6d")
                  .setTxPower(-59)
                  .setDataFields(sampleData)
                  .build();
  
          assertEquals("beacon contains a valid data on index 0", now, beacon.getDataFields().get(0));
  
          // Make byte array
          byte[] headerBytes = hexStringToByteArray("1bff1801");
          byte[] bodyBytes = parser.getBeaconAdvertisementData(beacon);
          byte[] bytes = new byte[headerBytes.length + bodyBytes.length];
          System.arraycopy(headerBytes, 0, bytes, 0, headerBytes.length);
          System.arraycopy(bodyBytes, 0, bytes, headerBytes.length, bodyBytes.length);
  
          // Try parsing the byte array
          Beacon parsedBeacon = parser.fromScanData(bytes, -59, null, 123456L);
  
          assertEquals("parsed beacon should contain a valid data on index 0", now, parsedBeacon.getDataFields().get(0));
          assertEquals("parsed beacon should contain a valid data on index 1", Long.valueOf(1234L), parsedBeacon.getDataFields().get(1));
          assertEquals("parsed beacon should contain a valid data on index 2", Long.valueOf(9876L), parsedBeacon.getDataFields().get(2));
  
      }
  }