/*
 * 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.internal.net.ipsec.ike.ike3gpp;

import static android.net.ipsec.ike.IkeManager.getIkeLog;
import static android.net.ipsec.ike.ike3gpp.Ike3gppBackoffTimer.isValidErrorNotifyCause;

import static com.android.internal.net.ipsec.ike.ike3gpp.Ike3gppExtensionExchange.NOTIFY_TYPE_BACKOFF_TIMER;
import static com.android.internal.net.ipsec.ike.ike3gpp.Ike3gppExtensionExchange.NOTIFY_TYPE_DEVICE_IDENTITY;
import static com.android.internal.net.ipsec.ike.ike3gpp.Ike3gppExtensionExchange.NOTIFY_TYPE_N1_MODE_INFORMATION;

import android.annotation.NonNull;
import android.net.ipsec.ike.exceptions.InvalidSyntaxException;
import android.net.ipsec.ike.ike3gpp.Ike3gppBackoffTimer;
import android.net.ipsec.ike.ike3gpp.Ike3gppData;
import android.net.ipsec.ike.ike3gpp.Ike3gppExtension;
import android.net.ipsec.ike.ike3gpp.Ike3gppN1ModeInformation;
import android.util.ArraySet;

import com.android.internal.net.ipsec.ike.message.IkeNotifyPayload;
import com.android.internal.net.ipsec.ike.message.IkePayload;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

/**
 * Ike3gppIkeAuth contains the implementation for IKE_AUTH 3GPP-specific functionality in IKEv2.
 *
 * <p>This class is package-private.
 */
class Ike3gppIkeAuth extends Ike3gppExchangeBase {
    private static final String TAG = Ike3gppIkeAuth.class.getSimpleName();
    private static final Set<Integer> SUPPORTED_RESPONSE_NOTIFY_TYPES = new ArraySet<>();
    private boolean mIsDeviceIdentityRequestedByNetwork;

    static {
        SUPPORTED_RESPONSE_NOTIFY_TYPES.add(NOTIFY_TYPE_N1_MODE_INFORMATION);
        SUPPORTED_RESPONSE_NOTIFY_TYPES.add(NOTIFY_TYPE_BACKOFF_TIMER);
        SUPPORTED_RESPONSE_NOTIFY_TYPES.add(NOTIFY_TYPE_DEVICE_IDENTITY);
    }

    /** Initializes an Ike3gppIkeAuth. */
    Ike3gppIkeAuth(@NonNull Ike3gppExtension ike3gppExtension, @NonNull Executor userCbExecutor) {
        super(ike3gppExtension, userCbExecutor);
    }

    /** Provides a list of 3GPP payloads to add in the outbound EAP AUTH REQ. */
    List<IkePayload> getRequestPayloadsInEap(boolean serverAuthenticated) {
        List<IkePayload> ike3gppPayloads = new ArrayList<>();

        if (mIsDeviceIdentityRequestedByNetwork && serverAuthenticated) {
            String deviceIdentity = mIke3gppExtension.getIke3gppParams().getMobileDeviceIdentity();
            if (deviceIdentity != null) {
                ike3gppPayloads.add(
                        Ike3gppDeviceIdentityUtils.generateDeviceIdentityPayload(deviceIdentity));
            }
        }
        return ike3gppPayloads;
    }

    List<IkePayload> getRequestPayloads() {
        List<IkePayload> ike3gppPayloads = new ArrayList<>();
        if (mIke3gppExtension.getIke3gppParams().hasPduSessionId()) {
            ike3gppPayloads.add(
                    Ike3gppN1ModeUtils.generateN1ModeCapabilityPayload(
                            mIke3gppExtension.getIke3gppParams().getPduSessionId()));
        }

        return ike3gppPayloads;
    }

    List<IkePayload> extract3gppResponsePayloads(List<IkePayload> payloads) {
        List<IkePayload> ike3gppPayloads = new ArrayList<>();

        for (IkePayload payload : payloads) {
            switch (payload.payloadType) {
                case IkePayload.PAYLOAD_TYPE_NOTIFY:
                    IkeNotifyPayload notifyPayload = (IkeNotifyPayload) payload;
                    if (SUPPORTED_RESPONSE_NOTIFY_TYPES.contains(notifyPayload.notifyType)
                            || isValidErrorNotifyCause(notifyPayload)) {
                        ike3gppPayloads.add(notifyPayload);
                    }
                    break;
                default:
                    // not a 3GPP-specific payload
                    break;
            }
        }

        return ike3gppPayloads;
    }

    void handleAuthResp(List<IkePayload> ike3gppPayloads) throws InvalidSyntaxException {
        List<Ike3gppData> ike3gppDataList = new ArrayList<>();
        List<IkeNotifyPayload> notifyPayloads =
                IkePayload.getPayloadListForTypeInProvidedList(
                        IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class, ike3gppPayloads);

        IkeNotifyPayload backoffTimerPayload = null;
        IkeNotifyPayload backoffTimerCause = null;
        for (IkeNotifyPayload notifyPayload : notifyPayloads) {
            switch (notifyPayload.notifyType) {
                case NOTIFY_TYPE_N1_MODE_INFORMATION:
                    // N1_MODE_CAPABILITY must be configured for the client to be notified
                    if (!mIke3gppExtension.getIke3gppParams().hasPduSessionId()) {
                        logw("Received N1_MODE_INFORMATION when N1 Mode is not enabled");
                        continue;
                    }

                    byte[] snssai =
                            Ike3gppN1ModeUtils.getSnssaiFromNotifyData(notifyPayload.notifyData);
                    ike3gppDataList.add(new Ike3gppN1ModeInformation(snssai));
                    break;
                case NOTIFY_TYPE_BACKOFF_TIMER:
                    backoffTimerPayload = notifyPayload;
                    break;
                case NOTIFY_TYPE_DEVICE_IDENTITY:
                    mIsDeviceIdentityRequestedByNetwork = true;
                    break;
                default:
                    if (isValidErrorNotifyCause(notifyPayload)) {
                        if (backoffTimerCause == null) {
                            backoffTimerCause = notifyPayload;
                        }
                    } else {
                        // non-3GPP payload. Can be ignored.
                        logd(
                                "Non-3GPP payload processed as 3GPP: "
                                        + notifyPayload.getTypeString());
                    }
                    break;
            }
        }

        if (backoffTimerPayload != null && backoffTimerCause != null) {
            byte backoffTimer =
                    Ike3gppBackoffTimerUtils.getBackoffTimerfromNotifyData(
                            backoffTimerPayload.notifyData);
            ike3gppDataList.add(
                    new Ike3gppBackoffTimer(backoffTimer, backoffTimerCause.notifyType));
        } else if (backoffTimerPayload != null) {
            logw("Received BACKOFF_TIMER payload without an Error-Notify");
        }

        maybeInvokeUserCallback(ike3gppDataList);
    }

    private void logd(String msg) {
        getIkeLog().d(TAG, msg);
    }

    private void logw(String msg) {
        getIkeLog().w(TAG, msg);
    }
}
