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

Commit 0e17b7d8 authored by Jakub Tyszkowski's avatar Jakub Tyszkowski
Browse files

Tmap: Allow basic GATT ops before authorized by LeAudioService

Before authorized by LeAudioService, the device should be able to read
GATT attributes without errors, but will get just some basic info and
filtered-out data. This is to prevent some of the devices to giving up
on TMAP control when they receive the first unathorized error response
for GATT operation on TBS.

This is totaly safe since the remote devices is a trusted one but
LeAudioService was not yet enabled or it did not validate the device yet.

Bug: 282938041
Test: atest BluetoothInstrumentationTests
Change-Id: Ia7bbc9770516e4f5700d3977996a7cfd40971c53
parent aa63e449
Loading
Loading
Loading
Loading
+181 −2
Original line number Diff line number Diff line
@@ -478,6 +478,7 @@ public class TbsGatt {

        private void notifyCharacteristicChanged(BluetoothDevice device,
                BluetoothGattCharacteristic characteristic) {
            if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) return;
            if (mBluetoothGattServer != null) {
                mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false);
            }
@@ -566,9 +567,18 @@ public class TbsGatt {
        }

        public boolean notifyWithValue(BluetoothDevice device, byte[] value) {
            if (isNotifiable()) {
                mNotifier.notifyWithValue(device, this, value);
                return true;
            }
            return false;
        }

        public void notify(BluetoothDevice device) {
            if (isNotifiable()) {
                mNotifier.notify(device, this);
            }
        }

        public boolean clearValue(boolean notify) {
            boolean success = super.setValue(new byte[0]);
@@ -1010,11 +1020,136 @@ public class TbsGatt {
        }
    }

    private void onUnauthorizedCharRead(BluetoothDevice device, GattOpContext op) {
        UUID charUuid = op.mCharacteristic.getUuid();
        boolean allowToReadRealValue = false;
        byte[] buffer = null;

        /* Allow only some informations to be disclosed at this stage. */
        if (charUuid.equals(UUID_BEARER_PROVIDER_NAME)) {
            ByteBuffer bb = ByteBuffer.allocate(0).order(ByteOrder.LITTLE_ENDIAN);
            bb.put("".getBytes());
            buffer = bb.array();

        } else if (charUuid.equals(UUID_BEARER_UCI)) {
            buffer = "E.164".getBytes();

        } else if (charUuid.equals(UUID_BEARER_TECHNOLOGY)) {
            allowToReadRealValue = true;

        } else if (charUuid.equals(UUID_BEARER_URI_SCHEMES_SUPPORTED_LIST)) {
            ByteBuffer bb = ByteBuffer.allocate(0).order(ByteOrder.LITTLE_ENDIAN);
            bb.put("".getBytes());
            buffer = bb.array();

        } else if (charUuid.equals(UUID_BEARER_LIST_CURRENT_CALLS)) {
            ByteBuffer bb = ByteBuffer.allocate(0).order(ByteOrder.LITTLE_ENDIAN);
            bb.put("".getBytes());
            buffer = bb.array();

        } else if (charUuid.equals(UUID_CONTENT_CONTROL_ID)) {
            allowToReadRealValue = true;

        } else if (charUuid.equals(UUID_STATUS_FLAGS)) {
            allowToReadRealValue = true;

        } else if (charUuid.equals(UUID_CALL_STATE)) {
            ByteBuffer bb = ByteBuffer.allocate(0).order(ByteOrder.LITTLE_ENDIAN);
            bb.put("".getBytes());
            buffer = bb.array();

        } else if (charUuid.equals(UUID_CALL_CONTROL_POINT)) {
            // No read is available on this characteristic

        } else if (charUuid.equals(UUID_CALL_CONTROL_POINT_OPTIONAL_OPCODES)) {
            ByteBuffer bb = ByteBuffer.allocate(1).order(ByteOrder.LITTLE_ENDIAN);
            bb.put((byte) 0x00);
            buffer = bb.array();

        } else if (charUuid.equals(UUID_TERMINATION_REASON)) {
            // No read is available on this characteristic

        } else if (charUuid.equals(UUID_INCOMING_CALL)) {
            ByteBuffer bb = ByteBuffer.allocate(0).order(ByteOrder.LITTLE_ENDIAN);
            bb.put("".getBytes());
            buffer = bb.array();

        } else if (charUuid.equals(UUID_CALL_FRIENDLY_NAME)) {
            ByteBuffer bb = ByteBuffer.allocate(1).order(ByteOrder.LITTLE_ENDIAN);
            bb.put((byte) 0x00);
            buffer = bb.array();
        }

        if (allowToReadRealValue) {
            if (op.mCharacteristic.getValue() != null) {
                buffer =
                        Arrays.copyOfRange(
                                op.mCharacteristic.getValue(),
                                op.mOffset,
                                op.mCharacteristic.getValue().length);
            }
        }

        if (buffer != null) {
            mBluetoothGattServer.sendResponse(
                    device, op.mRequestId, BluetoothGatt.GATT_SUCCESS, op.mOffset, buffer);
        } else {
            mBluetoothGattServer.sendResponse(
                    device,
                    op.mRequestId,
                    BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH,
                    op.mOffset,
                    buffer);
        }
    }

    private void onUnauthorizedGattOperation(BluetoothDevice device, GattOpContext op) {
        if (DBG) {
            Log.d(TAG, "onUnauthorizedGattOperation device: " + device);
        }

        int status = BluetoothGatt.GATT_SUCCESS;

        switch (op.mOperation) {
            /* Allow not yet authorized devices to subscribe for notifications */
            case READ_DESCRIPTOR:
                byte[] value = getCccBytes(device, op.mDescriptor.getCharacteristic().getUuid());
                if (value.length < op.mOffset) {
                    status = BluetoothGatt.GATT_INVALID_OFFSET;
                } else {
                    value = Arrays.copyOfRange(value, op.mOffset, value.length);
                    status = BluetoothGatt.GATT_SUCCESS;
                }

                mBluetoothGattServer.sendResponse(device, op.mRequestId, status, op.mOffset, value);
                return;
            case WRITE_DESCRIPTOR:
                if (op.mPreparedWrite) {
                    status = BluetoothGatt.GATT_FAILURE;
                } else if (op.mOffset > 0) {
                    status = BluetoothGatt.GATT_INVALID_OFFSET;
                } else if (op.mValue.length != 2) {
                    status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
                } else {
                    status = BluetoothGatt.GATT_SUCCESS;
                    setCcc(device, op.mDescriptor.getCharacteristic().getUuid(), op.mValue);
                }

                if (op.mResponseNeeded) {
                    mBluetoothGattServer.sendResponse(
                            device, op.mRequestId, status, op.mOffset, op.mValue);
                }
                return;
            case READ_CHARACTERISTIC:
                onUnauthorizedCharRead(device, op);
                return;
            case WRITE_CHARACTERISTIC:
                // store as pending operation
                break;
            default:
                break;
        }

        synchronized (mPendingGattOperationsLock) {
            List<GattOpContext> operations = mPendingGattOperations.get(device);
            if (operations == null) {
@@ -1160,6 +1295,38 @@ public class TbsGatt {
        }
    }

    private GattCharacteristic getLocalCharacteristicWrapper(UUID uuid) {
        if (uuid.equals(UUID_BEARER_PROVIDER_NAME)) {
            return mBearerProviderNameCharacteristic;
        } else if (uuid.equals(UUID_BEARER_UCI)) {
            return mBearerUciCharacteristic;
        } else if (uuid.equals(UUID_BEARER_TECHNOLOGY)) {
            return mBearerTechnologyCharacteristic;
        } else if (uuid.equals(UUID_BEARER_URI_SCHEMES_SUPPORTED_LIST)) {
            return mBearerUriSchemesSupportedListCharacteristic;
        } else if (uuid.equals(UUID_BEARER_LIST_CURRENT_CALLS)) {
            return mBearerListCurrentCallsCharacteristic;
        } else if (uuid.equals(UUID_CONTENT_CONTROL_ID)) {
            return mContentControlIdCharacteristic;
        } else if (uuid.equals(UUID_STATUS_FLAGS)) {
            return mStatusFlagsCharacteristic;
        } else if (uuid.equals(UUID_CALL_STATE)) {
            return mCallStateCharacteristic;
        } else if (uuid.equals(UUID_CALL_CONTROL_POINT)) {
            return mCallControlPointCharacteristic;
        } else if (uuid.equals(UUID_CALL_CONTROL_POINT_OPTIONAL_OPCODES)) {
            return mCallControlPointOptionalOpcodesCharacteristic;
        } else if (uuid.equals(UUID_TERMINATION_REASON)) {
            return mTerminationReasonCharacteristic;
        } else if (uuid.equals(UUID_INCOMING_CALL)) {
            return mIncomingCallCharacteristic;
        } else if (uuid.equals(UUID_CALL_FRIENDLY_NAME)) {
            return mCallFriendlyNameCharacteristic;
        }

        return null;
    }

    /**
     * Callback for TBS GATT instance about authorization change for device.
     *
@@ -1167,6 +1334,18 @@ public class TbsGatt {
     */
    public void onDeviceAuthorizationSet(BluetoothDevice device) {
        processPendingGattOperations(device);

        BluetoothGattService gattService = mBluetoothGattServer.getService(UUID_GTBS);
        if (gattService != null) {
            List<BluetoothGattCharacteristic> characteristics = gattService.getCharacteristics();
            for (BluetoothGattCharacteristic characteristic : characteristics) {
                GattCharacteristic wrapper =
                        getLocalCharacteristicWrapper(characteristic.getUuid());
                if (wrapper != null) {
                    wrapper.notify(device);
                }
            }
        }
    }

    private void clearUnauthorizedGattOperationss(BluetoothDevice device) {
+3 −16
Original line number Diff line number Diff line
@@ -23,15 +23,9 @@ import static org.mockito.AdditionalMatchers.*;
import android.bluetooth.*;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.IBluetoothManager;
import android.bluetooth.IBluetoothLeCallControl;
import android.bluetooth.IBluetoothLeCallControlCallback;
import android.content.Context;
import android.os.Looper;
import android.util.Pair;
import android.util.Log;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -44,26 +38,19 @@ import com.google.common.primitives.Bytes;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;

@@ -850,7 +837,7 @@ public class TbsGattTest {
        mTbsGatt.mGattServerCallback.onCharacteristicReadRequest(
                mFirstDevice, 1, 0, characteristic);

        verify(mMockTbsService).onDeviceUnauthorized(eq(mFirstDevice));
        verify(mMockTbsService, times(0)).onDeviceUnauthorized(eq(mFirstDevice));
    }

    @Test
@@ -928,7 +915,7 @@ public class TbsGattTest {

        mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor);

        verify(mMockTbsService).onDeviceUnauthorized(eq(mFirstDevice));
        verify(mMockTbsService, times(0)).onDeviceUnauthorized(eq(mFirstDevice));
    }

    @Test
@@ -972,6 +959,6 @@ public class TbsGattTest {
        mTbsGatt.mGattServerCallback.onDescriptorWriteRequest(
                mFirstDevice, 1, descriptor, false, true, 0, value);

        verify(mMockTbsService).onDeviceUnauthorized(eq(mFirstDevice));
        verify(mMockTbsService, times(0)).onDeviceUnauthorized(eq(mFirstDevice));
    }
}