/*
 * Copyright (C) 2020 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.connectivity;

import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;

import android.annotation.NonNull;
import android.net.IQosCallback;
import android.net.Network;
import android.net.QosCallbackException;
import android.net.QosFilter;
import android.net.QosSession;
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;

import com.android.modules.utils.build.SdkLevel;

import java.util.Objects;

/**
 * Wraps callback related information and sends messages between network agent and the application.
 * <p/>
 * This is a satellite class of {@link com.android.server.ConnectivityService} and not meant
 * to be used in other contexts.
 *
 * @hide
 */
class QosCallbackAgentConnection implements IBinder.DeathRecipient {
    private static final String TAG = QosCallbackAgentConnection.class.getSimpleName();
    private static final boolean DBG = false;

    private final int mAgentCallbackId;
    @NonNull private final QosCallbackTracker mQosCallbackTracker;
    @NonNull private final IQosCallback mCallback;
    @NonNull private final IBinder mBinder;
    @NonNull private final QosFilter mFilter;
    @NonNull private final NetworkAgentInfo mNetworkAgentInfo;

    private final int mUid;

    /**
     * Gets the uid
     * @return uid
     */
    int getUid() {
        return mUid;
    }

    /**
     * Gets the binder
     * @return binder
     */
    @NonNull
    IBinder getBinder() {
        return mBinder;
    }

    /**
     * Gets the callback id
     *
     * @return callback id
     */
    int getAgentCallbackId() {
        return mAgentCallbackId;
    }

    /**
     * Gets the network tied to the callback of this connection
     *
     * @return network
     */
    @NonNull
    Network getNetwork() {
        return mFilter.getNetwork();
    }

    QosCallbackAgentConnection(@NonNull final QosCallbackTracker qosCallbackTracker,
            final int agentCallbackId,
            @NonNull final IQosCallback callback,
            @NonNull final QosFilter filter,
            final int uid,
            @NonNull final NetworkAgentInfo networkAgentInfo) {
        Objects.requireNonNull(qosCallbackTracker, "qosCallbackTracker must be non-null");
        Objects.requireNonNull(callback, "callback must be non-null");
        Objects.requireNonNull(filter, "filter must be non-null");
        Objects.requireNonNull(networkAgentInfo, "networkAgentInfo must be non-null");

        mQosCallbackTracker = qosCallbackTracker;
        mAgentCallbackId = agentCallbackId;
        mCallback = callback;
        mFilter = filter;
        mUid = uid;
        mBinder = mCallback.asBinder();
        mNetworkAgentInfo = networkAgentInfo;
    }

    @Override
    public void binderDied() {
        logw("binderDied: binder died with callback id: " + mAgentCallbackId);
        mQosCallbackTracker.unregisterCallback(mCallback);
    }

    void unlinkToDeathRecipient() {
        mBinder.unlinkToDeath(this, 0);
    }

    // Returns false if the NetworkAgent was never notified.
    boolean sendCmdRegisterCallback() {
        final int exceptionType = mFilter.validate();
        if (exceptionType != EX_TYPE_FILTER_NONE) {
            try {
                if (DBG) log("sendCmdRegisterCallback: filter validation failed");
                mCallback.onError(exceptionType);
            } catch (final RemoteException e) {
                loge("sendCmdRegisterCallback:", e);
            }
            return false;
        }

        try {
            mBinder.linkToDeath(this, 0);
        } catch (final RemoteException e) {
            loge("failed linking to death recipient", e);
            return false;
        }
        mNetworkAgentInfo.onQosFilterCallbackRegistered(mAgentCallbackId, mFilter);
        return true;
    }

    void sendCmdUnregisterCallback() {
        if (DBG) log("sendCmdUnregisterCallback: unregistering");
        mNetworkAgentInfo.onQosCallbackUnregistered(mAgentCallbackId);
    }

    void sendEventEpsQosSessionAvailable(final QosSession session,
            final EpsBearerQosSessionAttributes attributes) {
        if (!validateOrSendErrorAndUnregister()) return;
        try {
            if (DBG) log("sendEventEpsQosSessionAvailable: sending...");
            mCallback.onQosEpsBearerSessionAvailable(session, attributes);
        } catch (final RemoteException e) {
            loge("sendEventEpsQosSessionAvailable: remote exception", e);
        }
    }

    void sendEventNrQosSessionAvailable(final QosSession session,
            final NrQosSessionAttributes attributes) {
        if (!validateOrSendErrorAndUnregister()) return;
        try {
            if (DBG) log("sendEventNrQosSessionAvailable: sending...");
            mCallback.onNrQosSessionAvailable(session, attributes);
        } catch (final RemoteException e) {
            loge("sendEventNrQosSessionAvailable: remote exception", e);
        }
    }

    void sendEventQosSessionLost(@NonNull final QosSession session) {
        if (!validateOrSendErrorAndUnregister()) return;
        try {
            if (DBG) log("sendEventQosSessionLost: sending...");
            mCallback.onQosSessionLost(session);
        } catch (final RemoteException e) {
            loge("sendEventQosSessionLost: remote exception", e);
        }
    }

    void sendEventQosCallbackError(@QosCallbackException.ExceptionType final int exceptionType) {
        try {
            if (DBG) log("sendEventQosCallbackError: sending...");
            mCallback.onError(exceptionType);
        } catch (final RemoteException e) {
            loge("sendEventQosCallbackError: remote exception", e);
        }
    }

    private boolean validateOrSendErrorAndUnregister() {
        final int exceptionType = mFilter.validate();
        if (exceptionType != EX_TYPE_FILTER_NONE) {
             log("validation fail before sending QosCallback.");
             // Error callback is returned from Android T to prevent any disruption of application
             // running on Android S.
             if (SdkLevel.isAtLeastT()) {
                sendEventQosCallbackError(exceptionType);
                mQosCallbackTracker.unregisterCallback(mCallback);
            }
            return false;
        }
        return true;
    }

    private static void log(@NonNull final String msg) {
        Log.d(TAG, msg);
    }

    private static void logw(@NonNull final String msg) {
        Log.w(TAG, msg);
    }

    private static void loge(@NonNull final String msg, final Throwable t) {
        Log.e(TAG, msg, t);
    }
}
