package com.uber.rxcentralble.core; import android.support.annotation.Nullable; import com.uber.rxcentralble.ParsedAdvertisement; import com.uber.rxcentralble.Utils; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; public class CoreParsedAdvertisement implements ParsedAdvertisement { /** * Advertising data types. */ private static final int AD_INCOMPLETE_16BIT_SVC_LIST = 0x02; private static final int AD_COMPLETE_16BIT_SVC_LIST = 0x03; private static final int AD_INCOMPLETE_32BIT_SVC_LIST = 0x04; private static final int AD_COMPLETE_32BIT_SVC_LIST = 0x05; private static final int AD_INCOMPLETE_128BIT_SVC_LIST = 0x06; private static final int AD_COMPLETE_128BIT_SVC_LIST = 0x07; private static final int AD_SHORTENED_LOCAL_NAME = 0x08; private static final int AD_COMPLETE_LOCAL_NAME = 0x09; private static final int AD_MANUFACTURER_DATA = 0xFF; private final List<UUID> servicesList = new ArrayList<>(); private final Map<Integer, byte[]> eirDataMap = new HashMap<>(); private final Map<Integer, byte[]> mfgDataMap = new HashMap<>(); private final byte[] rawAdData; @Nullable String name; public CoreParsedAdvertisement(byte[] rawAdData) { this.rawAdData = rawAdData; ByteBuffer byteBuffer = ByteBuffer.wrap(rawAdData).order(ByteOrder.LITTLE_ENDIAN); try { while (byteBuffer.hasRemaining()) { int length = byteBuffer.get() & 0xFF; if (length <= byteBuffer.remaining()) { int dataType = byteBuffer.get() & 0xFF; parseAdData(dataType, length - 1, byteBuffer); } } } catch (Exception e) { // Ignore exceptions; further data will not be parsed. } } @Override @Nullable public String getName() { return name; } @Override public boolean hasService(UUID svc) { return servicesList.contains(svc); } @Override @Nullable public byte[] getManufacturerData(int manufacturerId) { return mfgDataMap.get(manufacturerId); } @Override @Nullable public byte[] getEIRData(int eirDataType) { return eirDataMap.get(eirDataType); } @Override public byte[] getRawAdvertisement() { return rawAdData; } private void parseAdData(int dataType, int length, ByteBuffer byteBuffer) { if (length <= 0) { return; } int position = byteBuffer.position(); byte[] eirData = new byte[length]; byteBuffer.get(eirData); eirDataMap.put(dataType, eirData); byteBuffer.position(position); int bytesRead = 0; switch (dataType) { case AD_INCOMPLETE_16BIT_SVC_LIST: case AD_COMPLETE_16BIT_SVC_LIST: while (bytesRead < length) { int uuid = byteBuffer.getShort(); servicesList.add(Utils.uuidFromInteger(uuid)); bytesRead += 2; } break; case AD_INCOMPLETE_32BIT_SVC_LIST: case AD_COMPLETE_32BIT_SVC_LIST: while (bytesRead < length) { int uuid = byteBuffer.getInt(); servicesList.add(Utils.uuidFromInteger(uuid)); bytesRead += 4; } break; case AD_INCOMPLETE_128BIT_SVC_LIST: case AD_COMPLETE_128BIT_SVC_LIST: while (bytesRead < length) { long lsb = byteBuffer.getLong(); long msb = byteBuffer.getLong(); servicesList.add(new UUID(msb, lsb)); bytesRead += 16; } break; case AD_SHORTENED_LOCAL_NAME: case AD_COMPLETE_LOCAL_NAME: try { byte[] nameBytes = new byte[length]; byteBuffer.get(nameBytes); name = new String(nameBytes, "UTF-8"); } catch (UnsupportedEncodingException e) { // Ignore the exception; data will remain empty. } break; case AD_MANUFACTURER_DATA: int mfgId = byteBuffer.getShort() & 0xFFFF; byte[] data = new byte[length - 2]; byteBuffer.get(data); mfgDataMap.put(mfgId, data); break; default: byteBuffer.position(byteBuffer.position() + length); break; } } public static class Factory implements ParsedAdvertisement.Factory { @Override public ParsedAdvertisement produce(byte[] rawAdData) { return new CoreParsedAdvertisement(rawAdData); } } }