Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 114f3b44 authored by Rishab Ghanti's avatar Rishab Ghanti Committed by Gerrit Code Review
Browse files

Merge "[BluetoothMetrics] Create new bloom filter for medical device identification" into main

parents 20ffeff2 8c9ba182
Loading
Loading
Loading
Loading
+76 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.bluetooth.btservice;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/** Class to generate a medical device bloomfilter */
public class MedicalDeviceBloomfilterGenerator {

    public static final String BLOOM_FILTER_DEFAULT =
            "01070000003C01002106584044800850055"
                    + "002308488410020D9A00001138410510000"
                    + "000042004200000000000C2000000040064"
                    + "0120080020110412A500090520210040C40"
                    + "4002601040005004400148414006198A041"
                    + "00890000600400000800210041810600800"
                    + "0142208000721A030000028102448201110"
                    + "0002007120020101448C211490A2B000084"
                    + "C010004004C00C080808200026210608110"
                    + "200011200000015000000212C4400040802"
                    + "00111114840000001002080040186000404"
                    + "81C064400052381109017039900000200C9"
                    + "C0002E6480000101C40000601064001A018"
                    + "40440280A810001000040455A0404617034"
                    + "50000140040D020020C6204100804041600"
                    + "80840002000800804280028000440000122"
                    + "00808409905022000590000110448080400"
                    + "561004210020430092602000040C0090C00"
                    + "C18480020000519C1482100111011120390"
                    + "02C0000228208104800C050440000004040"
                    + "00871400882400140080000005308124900"
                    + "104000040002410508CA349000200000202"
                    + "90200920181890100800110220A20874820"
                    + "0428080054A0005101C0820060090080040"
                    + "6820C480F40081014010201800000018101"
                    + "208914100321008006520002030010800C4"
                    + "1022C000048206002010041220000008021"
                    + "002080314040000100030002008";

    /** Provide byte[] version of a given string */
    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;
    }

    /** Generate bloom filter file given filePath */
    public static void generateDefaultBloomfilter(String filePath) throws IOException {
        File outputFile = new File(filePath);
        outputFile.createNewFile(); // if file already exists will do nothing
        FileOutputStream fos = new FileOutputStream(filePath);
        fos.write(hexStringToByteArray(BLOOM_FILTER_DEFAULT));
        fos.close();
    }
}
+99 −12
Original line number Diff line number Diff line
@@ -96,7 +96,10 @@ public class MetricsLogger {
    private static final String TAG = "BluetoothMetricsLogger";
    private static final String BLOOMFILTER_PATH = "/data/misc/bluetooth";
    private static final String BLOOMFILTER_FILE = "/devices_for_metrics_v3";
    private static final String MEDICAL_DEVICE_BLOOMFILTER_FILE = "/medical_devices_for_metrics_v1";
    public static final String BLOOMFILTER_FULL_PATH = BLOOMFILTER_PATH + BLOOMFILTER_FILE;
    public static final String MEDICAL_DEVICE_BLOOMFILTER_FULL_PATH =
            BLOOMFILTER_PATH + MEDICAL_DEVICE_BLOOMFILTER_FILE;

    // 6 hours timeout for counter metrics
    private static final long BLUETOOTH_COUNTER_METRICS_ACTION_DURATION_MILLIS = 6L * 3600L * 1000L;
@@ -114,6 +117,10 @@ public class MetricsLogger {
    private BloomFilter<byte[]> mBloomFilter = null;
    protected boolean mBloomFilterInitialized = false;

    private BloomFilter<byte[]> mMedicalDeviceBloomFilter = null;

    protected boolean mMedicalDeviceBloomFilterInitialized = false;

    private AlarmManager.OnAlarmListener mOnAlarmListener =
            new AlarmManager.OnAlarmListener() {
                @Override
@@ -185,10 +192,48 @@ public class MetricsLogger {
        return true;
    }

    /** Initialize medical device bloom filter */
    public boolean initMedicalDeviceBloomFilter(String path) {
        try {
            File medicalDeviceFile = new File(path);
            if (!medicalDeviceFile.exists()) {
                Log.w(TAG, "MetricsLogger is creating a new medical device Bloomfilter file");
                MedicalDeviceBloomfilterGenerator.generateDefaultBloomfilter(path);
            }

            FileInputStream inputStream = new FileInputStream(new File(path));
            mMedicalDeviceBloomFilter =
                    BloomFilter.readFrom(inputStream, Funnels.byteArrayFunnel());
            mMedicalDeviceBloomFilterInitialized = true;
        } catch (IOException e1) {
            Log.w(TAG, "MetricsLogger can't read the medical device BloomFilter file.");
            byte[] bloomfilterData =
                    MedicalDeviceBloomfilterGenerator.hexStringToByteArray(
                            MedicalDeviceBloomfilterGenerator.BLOOM_FILTER_DEFAULT);
            try {
                mMedicalDeviceBloomFilter =
                        BloomFilter.readFrom(
                                new ByteArrayInputStream(bloomfilterData),
                                Funnels.byteArrayFunnel());
                mMedicalDeviceBloomFilterInitialized = true;
                Log.i(TAG, "The medical device bloomfilter is used");
                return true;
            } catch (IOException e2) {
                Log.w(TAG, "The medical device bloomfilter can't be used.");
            }
            return false;
        }
        return true;
    }

    protected void setBloomfilter(BloomFilter bloomfilter) {
        mBloomFilter = bloomfilter;
    }

    protected void setMedicalDeviceBloomfilter(BloomFilter bloomfilter) {
        mMedicalDeviceBloomFilter = bloomfilter;
    }

    void init(AdapterService adapterService, RemoteDevices remoteDevices) {
        if (mInitialized) {
            return;
@@ -203,6 +248,12 @@ public class MetricsLogger {
            // We still want to use this class even if the bloomfilter isn't initialized
            // so still return true here.
        }
        if (!initMedicalDeviceBloomFilter(MEDICAL_DEVICE_BLOOMFILTER_FULL_PATH)) {
            Log.w(TAG, "MetricsLogger can't initialize the medical device bloomfilter");
            // The class is for multiple metrics tasks.
            // We still want to use this class even if the bloomfilter isn't initialized
            // so still return true here.
        }
        IntentFilter filter = new IntentFilter();
        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
@@ -289,7 +340,8 @@ public class MetricsLogger {
        BluetoothDevice device = connIntent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        int state = connIntent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
        int metricId = mAdapterService.getMetricId(device);
        byte[] remoteDeviceInfoBytes = getRemoteDeviceInfoProto(device);
        boolean includeMedicalDevices = false;
        byte[] remoteDeviceInfoBytes = getRemoteDeviceInfoProto(device, includeMedicalDevices);
        if (state == BluetoothProfile.STATE_CONNECTING) {
            String deviceName = mRemoteDevices.getName(device);
            BluetoothStatsLog.write(
@@ -417,6 +469,7 @@ public class MetricsLogger {
        mAdapterService = null;
        mInitialized = false;
        mBloomFilterInitialized = false;
        mMedicalDeviceBloomFilterInitialized = false;
    }

    protected void cancelPendingDrain() {
@@ -446,15 +499,31 @@ public class MetricsLogger {

    /**
     * Retrieves a byte array containing serialized remote device information for the specified
     * BluetoothDevice. This data can be used for remote device identification and logging.
     * BluetoothDevice. This data can be used for remote device identification and logging. Does not
     * include medical remote devices.
     *
     * @param device The BluetoothDevice for which to retrieve device information.
     * @return A byte array containing the serialized remote device information.
     */
    public byte[] getRemoteDeviceInfoProto(BluetoothDevice device) {
        if (!mInitialized) {
            return null;
        return mInitialized ? buildRemoteDeviceInfoProto(device, false) : null;
    }

    /**
     * Retrieves a byte array containing serialized remote device information for the specified
     * BluetoothDevice. This data can be used for remote device identification and logging.
     *
     * @param device The BluetoothDevice for which to retrieve device information.
     * @param includeMedicalDevices Should be true only if logging as de-identified metric,
     *     otherwise false.
     * @return A byte array containing the serialized remote device information.
     */
    public byte[] getRemoteDeviceInfoProto(BluetoothDevice device, boolean includeMedicalDevices) {
        return mInitialized ? buildRemoteDeviceInfoProto(device, includeMedicalDevices) : null;
    }

    private byte[] buildRemoteDeviceInfoProto(
            BluetoothDevice device, boolean includeMedicalDevices) {
        ProtoOutputStream proto = new ProtoOutputStream();

        // write Allowlisted Device Name Hash
@@ -463,7 +532,8 @@ public class MetricsLogger {
                ProtoOutputStream.FIELD_TYPE_STRING,
                ProtoOutputStream.FIELD_COUNT_SINGLE,
                BluetoothRemoteDeviceInformation.ALLOWLISTED_DEVICE_NAME_HASH_FIELD_NUMBER,
                getAllowlistedDeviceNameHash(mAdapterService.getRemoteName(device)));
                getAllowlistedDeviceNameHash(
                        mAdapterService.getRemoteName(device), includeMedicalDevices));

        // write COD
        writeFieldIfNotNull(
@@ -567,7 +637,7 @@ public class MetricsLogger {
        }
    }

    private String getMatchedString(List<String> wordBreakdownList) {
    private String getMatchedString(List<String> wordBreakdownList, boolean includeMedicalDevices) {
        if (!mBloomFilterInitialized || wordBreakdownList.isEmpty()) {
            return "";
        }
@@ -579,6 +649,21 @@ public class MetricsLogger {
                matchedString = word;
            }
        }

        return (matchedString.equals("") && includeMedicalDevices)
                ? getMatchedStringForMedicalDevice(wordBreakdownList)
                : matchedString;
    }

    private String getMatchedStringForMedicalDevice(List<String> wordBreakdownList) {
        String matchedString = "";
        for (String word : wordBreakdownList) {
            byte[] sha256 = getSha256(word);
            if (mMedicalDeviceBloomFilter.mightContain(sha256)
                    && word.length() > matchedString.length()) {
                matchedString = word;
            }
        }
        return matchedString;
    }

@@ -664,16 +749,18 @@ public class MetricsLogger {
                advDurationMs);
    }

    protected String getAllowlistedDeviceNameHash(String deviceName) {
    protected String getAllowlistedDeviceNameHash(
            String deviceName, boolean includeMedicalDevices) {
        List<String> wordBreakdownList = getWordBreakdownList(deviceName);
        String matchedString = getMatchedString(wordBreakdownList);
        String matchedString = getMatchedString(wordBreakdownList, includeMedicalDevices);
        return getSha256String(matchedString);
    }

    protected String logAllowlistedDeviceNameHash(
            int metricId, String deviceName, boolean logRestrictedNames) {
        List<String> wordBreakdownList = getWordBreakdownList(deviceName);
        String matchedString = getMatchedString(wordBreakdownList);
        boolean includeMedicalDevices = false;
        String matchedString = getMatchedString(wordBreakdownList, includeMedicalDevices);
        if (logRestrictedNames) {
            // Log the restricted bluetooth device name
            if (SdkLevel.isAtLeastU()) {
@@ -705,7 +792,7 @@ public class MetricsLogger {
                state,
                uid,
                mAdapterService.getMetricId(device),
                getRemoteDeviceInfoProto(device));
                getRemoteDeviceInfoProto(device, false));
    }

    protected static String getSha256String(String name) {
@@ -836,6 +923,6 @@ public class MetricsLogger {
                latencyPaSyncMs,
                latencyBisSyncMs,
                syncStatus,
                getRemoteDeviceInfoProto(device));
                getRemoteDeviceInfoProto(device, false));
    }
}
+22 −0
Original line number Diff line number Diff line
@@ -269,6 +269,28 @@ public class MetricsLoggerTest {
        }
    }

    @Test
    public void testGetAllowlistedDeviceNameHashForMedicalDevice() {
        String deviceName = "Sam's rphonak hearing aid";
        String expectMedicalDeviceSha256 = MetricsLogger.getSha256String("rphonakhearingaid");

        String actualMedicalDeviceSha256 =
                mTestableMetricsLogger.getAllowlistedDeviceNameHash(deviceName, true);

        Assert.assertEquals(expectMedicalDeviceSha256, actualMedicalDeviceSha256);
    }

    @Test
    public void testGetAllowlistedDeviceNameHashForMedicalDeviceIdentifiedLogging() {
        String deviceName = "Sam's rphonak hearing aid";
        String expectMedicalDeviceSha256 = "";

        String actualMedicalDeviceSha256 =
                mTestableMetricsLogger.getAllowlistedDeviceNameHash(deviceName, false);

        Assert.assertEquals(expectMedicalDeviceSha256, actualMedicalDeviceSha256);
    }

    @Test
    public void uploadEmptyDeviceName() {
        initTestingBloomfilter();