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

Commit 6f5572de authored by Tim Yu's avatar Tim Yu
Browse files

Autofill Better Request ID

Modifies Request ID to have the following characteristics:

1. Small enough to have a bunch of collisions between devices - this is
   done by truncate it to 16 bits (% 0xFFFF)
2. Unique within the same device - so it still works as expected

Fixes: 342013721
Test: tdb
Change-Id: I86f627b842fc614d992b11be2e1b13a81af9b65a
parent 88c1d4a0
Loading
Loading
Loading
Loading
+94 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.server.autofill;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

// Helper class containing various methods to deal with FillRequest Ids.
// For authentication flows, there needs to be a way to know whether to retrieve the Fill
// Response from the primary provider or the secondary provider from the requestId. A simple
// way to achieve this is by assigning odd number request ids to secondary provider and
// even numbers to primary provider.
public class RequestId {

  private AtomicInteger sIdCounter;

  // Mainly used for tests
  RequestId(int start) {
    sIdCounter = new AtomicInteger(start);
  }

  public RequestId() {
    this((int) (Math.floor(Math.random() * 0xFFFF)));
  }

  public static int getLastRequestIdIndex(List<Integer> requestIds) {
    int lastId = -1;
    int indexOfBiggest = -1;
    // Biggest number is usually the latest request, since IDs only increase
    // The only exception is when the request ID wraps around back to 0
      for (int i = requestIds.size() - 1; i >= 0; i--) {
        if (requestIds.get(i) > lastId) {
        lastId = requestIds.get(i);
        indexOfBiggest = i;
      }
    }

    // 0xFFFE + 2 == 0x1 (for secondary)
    // 0xFFFD + 2 == 0x0 (for primary)
    // Wrap has occurred
    if (lastId >= 0xFFFD) {
      // Calculate the biggest size possible
      // If list only has one kind of request ids - we need to multiple by 2
      // (since they skip odd ints)
      // Also subtract one from size because at least one integer exists pre-wrap
      int calcSize = (requestIds.size()) * 2;
      //Biggest possible id after wrapping
      int biggestPossible = (lastId + calcSize) % 0xFFFF;
      lastId = -1;
      indexOfBiggest = -1;
      for (int i = 0; i < requestIds.size(); i++) {
        int currentId = requestIds.get(i);
        if (currentId <= biggestPossible && currentId > lastId) {
          lastId = currentId;
          indexOfBiggest = i;
        }
      }
    }

    return indexOfBiggest;
  }

  public int nextId(boolean isSecondary) {
        // For authentication flows, there needs to be a way to know whether to retrieve the Fill
        // Response from the primary provider or the secondary provider from the requestId. A simple
        // way to achieve this is by assigning odd number request ids to secondary provider and
        // even numbers to primary provider.
        int requestId;

        do {
            requestId = sIdCounter.incrementAndGet() % 0xFFFF;
            sIdCounter.set(requestId);
        } while (isSecondaryProvider(requestId) != isSecondary);
        return requestId;
  }

  public static boolean isSecondaryProvider(int requestId) {
      return requestId % 2 == 1;
  }
}
+17 −46
Original line number Diff line number Diff line
@@ -263,7 +263,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState

    static final int AUGMENTED_AUTOFILL_REQUEST_ID = 1;

    private static AtomicInteger sIdCounter = new AtomicInteger(2);
    private static RequestId mRequestId = new RequestId();

    private static AtomicInteger sIdCounterForPcc = new AtomicInteger(2);

@@ -1333,7 +1333,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        }

        viewState.setState(newState);
        int requestId = getRequestId(isSecondary);
        int requestId = mRequestId.nextId(isSecondary);

        // Create a metrics log for the request
        final int ordinal = mRequestLogs.size() + 1;
@@ -1415,25 +1415,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        requestAssistStructureLocked(requestId, flags);
    }

    private static int getRequestId(boolean isSecondary) {
        // For authentication flows, there needs to be a way to know whether to retrieve the Fill
        // Response from the primary provider or the secondary provider from the requestId. A simple
        // way to achieve this is by assigning odd number request ids to secondary provider and
        // even numbers to primary provider.
        int requestId;
        // TODO(b/158623971): Update this to prevent possible overflow
        if (isSecondary) {
            do {
                requestId = sIdCounter.getAndIncrement();
            } while (!isSecondaryProviderRequestId(requestId));
        } else {
            do {
                requestId = sIdCounter.getAndIncrement();
            } while (requestId == INVALID_REQUEST_ID || isSecondaryProviderRequestId(requestId));
        }
        return requestId;
    }

    private boolean isRequestSupportFillDialog(int flags) {
        return (flags & FLAG_SUPPORTS_FILL_DIALOG) != 0;
    }
@@ -1441,7 +1422,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
    @GuardedBy("mLock")
    private void requestAssistStructureForPccLocked(int flags) {
        if (!mClassificationState.shouldTriggerRequest()) return;
        mFillRequestIdSnapshot = sIdCounter.get();
        mFillRequestIdSnapshot = sIdCounterForPcc.get();
        mClassificationState.updatePendingRequest();
        // Get request id
        int requestId;
@@ -2884,7 +2865,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
            removeFromService();
            return;
        }
        final FillResponse authenticatedResponse = isSecondaryProviderRequestId(requestId)
        final FillResponse authenticatedResponse = mRequestId.isSecondaryProvider(requestId)
                ? mSecondaryResponses.get(requestId)
                : mResponses.get(requestId);
        if (authenticatedResponse == null || data == null) {
@@ -2982,7 +2963,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                mPresentationStatsEventLogger.maybeSetAuthenticationResult(
                        AUTHENTICATION_RESULT_SUCCESS);
                if (newClientState != null) {
                    if (sDebug) Slog.d(TAG,  "Updating client state from auth dataset");
                    if (sDebug)
                        Slog.d(TAG, "Updating client state from auth dataset");
                    mClientState = newClientState;
                }
                Dataset datasetFromResult = getEffectiveDatasetForAuthentication((Dataset) result);
@@ -3011,10 +2993,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        }
    }

    private static boolean isSecondaryProviderRequestId(int requestId) {
        return requestId % 2 == 1;
    }

    private Dataset getDatasetFromCredentialResponse(GetCredentialResponse result) {
        if (result == null) {
            return null;
@@ -6923,22 +6901,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState

    @GuardedBy("mLock")
    private int getLastResponseIndexLocked() {
        // The response ids are monotonically increasing so
        // we just find the largest id which is the last. We
        // do not rely on the internal ordering in sparse
        // array to avoid - wow this stopped working!?
        int lastResponseIdx = -1;
        int lastResponseId = -1;
        if (mResponses != null) {
            List<Integer> requestIdList = new ArrayList<>();
            final int responseCount = mResponses.size();
            for (int i = 0; i < responseCount; i++) {
                if (mResponses.keyAt(i) > lastResponseId) {
                    lastResponseIdx = i;
                    lastResponseId = mResponses.keyAt(i);
                }
                requestIdList.add(mResponses.keyAt(i));
            }
            return mRequestId.getLastRequestIdIndex(requestIdList);
        }
        return lastResponseIdx;
        return -1;
    }

    private LogMaker newLogMaker(int category) {
+185 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.server.autofill;

import static com.google.common.truth.Truth.assertThat;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.util.ArrayList;
import java.util.List;

@RunWith(JUnit4.class)
public class RequestIdTest {

    List<Integer> datasetPrimaryNoWrap = new ArrayList<>();
    List<Integer> datasetPrimaryWrap = new ArrayList<>();
    List<Integer> datasetSecondaryNoWrap = new ArrayList<>();
    List<Integer> datasetSecondaryWrap = new ArrayList<>();
    List<Integer> datasetMixedNoWrap = new ArrayList<>();
    List<Integer> datasetMixedWrap = new ArrayList<>();

    @Before
    public void setup() throws Exception {
      int datasetSize = 300;

        { // Generate primary only ids that do not wrap
            RequestId requestId = new RequestId(0);
            for (int i = 0; i < datasetSize; i++) {
                datasetPrimaryNoWrap.add(requestId.nextId(false));
            }
        }

        { // Generate primary only ids that wrap
            RequestId requestId = new RequestId(0xff00);
            for (int i = 0; i < datasetSize; i++) {
                datasetPrimaryWrap.add(requestId.nextId(false));
            }
        }

        { // Generate SECONDARY only ids that do not wrap
            RequestId requestId = new RequestId(0);
            for (int i = 0; i < datasetSize; i++) {
                datasetSecondaryNoWrap.add(requestId.nextId(true));
            }
        }

        { // Generate SECONDARY only ids that wrap
            RequestId requestId = new RequestId(0xff00);
            for (int i = 0; i < datasetSize; i++) {
                datasetSecondaryWrap.add(requestId.nextId(true));
            }
        }

        { // Generate MIXED only ids that do not wrap
            RequestId requestId = new RequestId(0);
            for (int i = 0; i < datasetSize; i++) {
                datasetMixedNoWrap.add(requestId.nextId(i % 2 != 0));
            }
        }

        { // Generate MIXED only ids that wrap
            RequestId requestId = new RequestId(0xff00);
            for (int i = 0; i < datasetSize; i++) {
                datasetMixedWrap.add(requestId.nextId(i % 2 != 0));
            }
        }
    }

    @Test
    public void testRequestIdLists() {
        for (int id : datasetPrimaryNoWrap) {
            assertThat(RequestId.isSecondaryProvider(id)).isFalse();
            assertThat(id >= 0).isTrue();
            assertThat(id < 0xffff).isTrue();
        }

        for (int id : datasetPrimaryWrap) {
            assertThat(RequestId.isSecondaryProvider(id)).isFalse();
            assertThat(id >= 0).isTrue();
            assertThat(id < 0xffff).isTrue();
        }

        for (int id : datasetSecondaryNoWrap) {
            assertThat(RequestId.isSecondaryProvider(id)).isTrue();
            assertThat(id >= 0).isTrue();
            assertThat(id < 0xffff).isTrue();
        }

        for (int id : datasetSecondaryWrap) {
            assertThat(RequestId.isSecondaryProvider(id)).isTrue();
            assertThat(id >= 0).isTrue();
            assertThat(id < 0xffff).isTrue();
        }
    }

    @Test
    public void testRequestIdGeneration() {
        RequestId requestId = new RequestId(0);

        // Large Primary
        for (int i = 0; i < 100000; i++) {
            int y = requestId.nextId(false);
            assertThat(RequestId.isSecondaryProvider(y)).isFalse();
            assertThat(y >= 0).isTrue();
            assertThat(y < 0xffff).isTrue();
        }

        // Large Secondary
        requestId = new RequestId(0);
        for (int i = 0; i < 100000; i++) {
            int y = requestId.nextId(true);
            assertThat(RequestId.isSecondaryProvider(y)).isTrue();
            assertThat(y >= 0).isTrue();
            assertThat(y < 0xffff).isTrue();
        }

        // Large Mixed
        requestId = new RequestId(0);
        for (int i = 0; i < 50000; i++) {
            int y = requestId.nextId(i % 2 != 0);
            assertThat(RequestId.isSecondaryProvider(y)).isEqualTo(i % 2 == 0);
            assertThat(y >= 0).isTrue();
            assertThat(y < 0xffff).isTrue();
        }
    }

    @Test
    public void testGetLastRequestId() {
        // In this test, request ids are generated FIFO, so the last entry is also the last
        // request

        { // Primary no wrap
          int lastIdIndex = datasetPrimaryNoWrap.size() - 1;
          int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryNoWrap);
          assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
        }

        { // Primary wrap
            int lastIdIndex = datasetPrimaryWrap.size() - 1;
            int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryWrap);
            assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
        }

        { // Secondary no wrap
            int lastIdIndex = datasetSecondaryNoWrap.size() - 1;
            int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetSecondaryNoWrap);
            assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
        }

        { // Secondary wrap
            int lastIdIndex = datasetSecondaryWrap.size() - 1;
            int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetSecondaryWrap);
            assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
        }

        { // Mixed no wrap
            int lastIdIndex = datasetMixedNoWrap.size() - 1;
            int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetMixedNoWrap);
            assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
        }

        { // Mixed wrap
            int lastIdIndex = datasetMixedWrap.size() - 1;
            int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetMixedWrap);
            assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
        }

    }
}