chromium/components/webauthn/android/java/src/org/chromium/components/webauthn/Barrier.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.webauthn;

import org.chromium.base.Callback;
import org.chromium.blink.mojom.AuthenticatorStatus;

/**
 * Barrier class is responsible for waiting the completion of Fido2 and/or Android Credential
 * Manager APIs. It supports either single mode (just one of the calls) or parallel mode (both
 * calls).
 *
 * In parallel mode, Fido2 API call results are privileged. For example, if Fido2 API call fails
 * with error code 1 and Android Credential Manager fails with error code 2, the resulting error
 * code is 1.
 */
public class Barrier {
    private enum Status {
        NONE,
        WAITING,
        SUCCESS,
        FAILURE,
    }

    public enum Mode {
        // Barrier will complete once the Fido2Api calls are complete.
        ONLY_FIDO_2_API,
        // Barrier will complete once the CredMan calls are complete.
        ONLY_CRED_MAN,
        // Barrier will complete when both calls are complete.
        BOTH,
    }

    private Callback<Integer> mErrorCallback;
    private Runnable mFido2ApiRunnable;
    private Runnable mCredManRunnable;
    private Status mFido2ApiStatus;
    private Status mCredManStatus;
    private int mFido2ApiError;
    private boolean mFido2ApiCancelled;
    private boolean mCredManCancelled;

    public Barrier(Callback<Integer> errorCallback) {
        mErrorCallback = errorCallback;
        mFido2ApiStatus = Status.NONE;
        mCredManStatus = Status.NONE;
    }

    public void resetAndSetWaitStatus(Mode mode) {
        reset();
        switch (mode) {
            case ONLY_FIDO_2_API:
                mFido2ApiStatus = Status.WAITING;
                break;
            case ONLY_CRED_MAN:
                mCredManStatus = Status.WAITING;
                break;
            case BOTH:
                mFido2ApiStatus = Status.WAITING;
                mCredManStatus = Status.WAITING;
                break;
            default:
                assert false : "Unhandled Barrier mode: " + mode;
        }
    }

    public void onCredManSuccessful(Runnable onBarrierComplete) {
        if (mFido2ApiStatus == Status.FAILURE) {
            onBarrierComplete.run();
        } else if (mFido2ApiStatus == Status.SUCCESS) {
            onBarrierComplete.run();
            mFido2ApiRunnable.run();
        } else if (mFido2ApiStatus == Status.WAITING) {
            mCredManRunnable = onBarrierComplete;
            mCredManStatus = Status.SUCCESS;
        } else {
            onBarrierComplete.run();
        }
    }

    public void onCredManFailed(int error) {
        if (mFido2ApiStatus == Status.FAILURE) {
            mErrorCallback.onResult(mFido2ApiError);
        } else if (mFido2ApiStatus == Status.SUCCESS) {
            mFido2ApiRunnable.run();
        } else if (mFido2ApiStatus == Status.WAITING) {
            mCredManStatus = Status.FAILURE;
        } else {
            mErrorCallback.onResult(error);
        }
    }

    public void onFido2ApiSuccessful(Runnable onBarrierComplete) {
        if (mCredManStatus == Status.FAILURE) {
            onBarrierComplete.run();
        } else if (mCredManStatus == Status.SUCCESS) {
            mCredManRunnable.run();
            onBarrierComplete.run();
        } else if (mCredManStatus == Status.WAITING) {
            mFido2ApiRunnable = onBarrierComplete;
            mFido2ApiStatus = Status.SUCCESS;
        } else {
            onBarrierComplete.run();
        }
    }

    public void onFido2ApiFailed(int error) {
        if (mCredManStatus == Status.FAILURE) {
            mErrorCallback.onResult(error);
        } else if (mCredManStatus == Status.SUCCESS) {
            mCredManRunnable.run();
        } else if (mCredManStatus == Status.WAITING) {
            mFido2ApiError = error;
            mFido2ApiStatus = Status.FAILURE;
        } else {
            mErrorCallback.onResult(error);
        }
    }

    public void onCredManCancelled() {
        if (mFido2ApiStatus == Status.NONE || mFido2ApiCancelled) {
            mErrorCallback.onResult(AuthenticatorStatus.ABORT_ERROR);
            mFido2ApiCancelled = false;
            return;
        }
        mCredManCancelled = true;
    }

    public void onFido2ApiCancelled() {
        if (mCredManStatus == Status.NONE || mCredManCancelled) {
            mErrorCallback.onResult(AuthenticatorStatus.ABORT_ERROR);
            mCredManCancelled = false;
            return;
        }
        mFido2ApiCancelled = true;
    }

    private void reset() {
        mFido2ApiRunnable = null;
        mCredManRunnable = null;
        mFido2ApiStatus = Status.NONE;
        mCredManStatus = Status.NONE;
        mCredManCancelled = false;
        mFido2ApiCancelled = false;
    }
}