/*
 * Copyright (C) 2019 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 android.net.ipsec.ike.exceptions;

import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;

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

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;

/**
 * IkeProtocolException is an abstract class that represents the common information for all IKE
 * protocol errors.
 *
 * <p>Error types are as defined by RFC 7296.
 *
 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.10.1">RFC 7296, Internet Key Exchange
 *     Protocol Version 2 (IKEv2)</a>
 * @hide
 */
@SystemApi
public abstract class IkeProtocolException extends IkeException {
    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
        ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD,
        ERROR_TYPE_INVALID_IKE_SPI,
        ERROR_TYPE_INVALID_MAJOR_VERSION,
        ERROR_TYPE_INVALID_SYNTAX,
        ERROR_TYPE_INVALID_MESSAGE_ID,
        ERROR_TYPE_NO_PROPOSAL_CHOSEN,
        ERROR_TYPE_INVALID_KE_PAYLOAD,
        ERROR_TYPE_AUTHENTICATION_FAILED,
        ERROR_TYPE_SINGLE_PAIR_REQUIRED,
        ERROR_TYPE_NO_ADDITIONAL_SAS,
        ERROR_TYPE_INTERNAL_ADDRESS_FAILURE,
        ERROR_TYPE_FAILED_CP_REQUIRED,
        ERROR_TYPE_TS_UNACCEPTABLE,
        ERROR_TYPE_INVALID_SELECTORS,
        ERROR_TYPE_TEMPORARY_FAILURE,
        ERROR_TYPE_CHILD_SA_NOT_FOUND,
    })
    public @interface ErrorType {}

    /** Unsupported critical payload */
    public static final int ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD = 1;
    /** Unrecognized destination IKE SPI */
    public static final int ERROR_TYPE_INVALID_IKE_SPI = 4;
    /** Invalid major version */
    public static final int ERROR_TYPE_INVALID_MAJOR_VERSION = 5;
    /** Invalid syntax */
    public static final int ERROR_TYPE_INVALID_SYNTAX = 7;
    /** Invalid message ID */
    public static final int ERROR_TYPE_INVALID_MESSAGE_ID = 9;
    /** No SA Proposal Chosen is acceptable */
    public static final int ERROR_TYPE_NO_PROPOSAL_CHOSEN = 14;
    /** Invalid Key Exchaneg Payload */
    public static final int ERROR_TYPE_INVALID_KE_PAYLOAD = 17;
    /** IKE authentication failed */
    public static final int ERROR_TYPE_AUTHENTICATION_FAILED = 24;
    /** Only Traffic Selectors specifying a single pair of addresses are acceptable */
    public static final int ERROR_TYPE_SINGLE_PAIR_REQUIRED = 34;
    /** No additional SAa are acceptable */
    public static final int ERROR_TYPE_NO_ADDITIONAL_SAS = 35;
    /** No internal addresses can be assigned */
    public static final int ERROR_TYPE_INTERNAL_ADDRESS_FAILURE = 36;
    /** Configuration Payload required but not found in IKE setup */
    public static final int ERROR_TYPE_FAILED_CP_REQUIRED = 37;
    /** No Traffic Selectors are acceptable */
    public static final int ERROR_TYPE_TS_UNACCEPTABLE = 38;
    /**
     * An IPsec Packet was found to have mismatched Traffic Selectors of the IPsec SA on which it
     * was delivered
     */
    public static final int ERROR_TYPE_INVALID_SELECTORS = 39;
    /** Temporary failure */
    public static final int ERROR_TYPE_TEMPORARY_FAILURE = 43;
    /** Child SA in the received packet does not exist */
    public static final int ERROR_TYPE_CHILD_SA_NOT_FOUND = 44;

    /** @hide */
    public static final byte[] ERROR_DATA_NOT_INCLUDED = new byte[0];

    private static final int INTEGER_BYTE_SIZE = 4;

    @ErrorType private final int mErrorType;
    private final byte[] mErrorData;

    /** @hide */
    protected IkeProtocolException(@ErrorType int code) {
        super();
        mErrorType = code;
        mErrorData = ERROR_DATA_NOT_INCLUDED;
    }

    /** @hide */
    protected IkeProtocolException(@ErrorType int code, String message) {
        super(message);
        mErrorType = code;
        mErrorData = ERROR_DATA_NOT_INCLUDED;
    }

    /** @hide */
    protected IkeProtocolException(@ErrorType int code, Throwable cause) {
        super(cause);
        mErrorType = code;
        mErrorData = ERROR_DATA_NOT_INCLUDED;
    }

    /** @hide */
    protected IkeProtocolException(@ErrorType int code, String message, Throwable cause) {
        super(message, cause);
        mErrorType = code;
        mErrorData = ERROR_DATA_NOT_INCLUDED;
    }

    /**
     * Construct an instance from a notify Payload.
     *
     * @hide
     */
    protected IkeProtocolException(@ErrorType int code, byte[] notifyData) {
        super();

        if (!isValidDataLength(notifyData.length)) {
            throw new IllegalArgumentException(
                    "Invalid error data for error type: "
                            + code
                            + " Received error data size: "
                            + notifyData.length);
        }

        mErrorType = code;
        mErrorData = notifyData;
    }

    /** @hide */
    protected abstract boolean isValidDataLength(int dataLen);

    /** @hide */
    protected static byte[] integerToByteArray(int integer, int arraySize) {
        if (arraySize > INTEGER_BYTE_SIZE) {
            throw new IllegalArgumentException(
                    "Cannot convert integer to a byte array of length: " + arraySize);
        }

        ByteBuffer dataBuffer = ByteBuffer.allocate(INTEGER_BYTE_SIZE);
        dataBuffer.putInt(integer);
        dataBuffer.rewind();

        byte[] zeroPad = new byte[INTEGER_BYTE_SIZE - arraySize];
        byte[] byteData = new byte[arraySize];
        dataBuffer.get(zeroPad).get(byteData);

        return byteData;
    }

    /** @hide */
    protected static int byteArrayToInteger(byte[] byteArray) {
        if (byteArray == null || byteArray.length > INTEGER_BYTE_SIZE) {
            throw new IllegalArgumentException("Cannot convert the byte array to integer");
        }

        ByteBuffer dataBuffer = ByteBuffer.allocate(INTEGER_BYTE_SIZE);
        byte[] zeroPad = new byte[INTEGER_BYTE_SIZE - byteArray.length];
        dataBuffer.put(zeroPad).put(byteArray);
        dataBuffer.rewind();

        return dataBuffer.getInt();
    }

    /**
     * Returns the IKE standard protocol error type of this {@link IkeProtocolException} instance.
     *
     * @return the IKE standard protocol error type.
     */
    @ErrorType
    public int getErrorType() {
        return mErrorType;
    }

    /**
     * Returns the included error data of this {@link IkeProtocolException} instance.
     *
     * <p>Note that only few error types will go with an error data. This data has different meaning
     * with different error types. Callers should first check if an error data is included before
     * they call this method.
     *
     * @return the included error data in byte array, or {@code null} if no error data is available.
     */
    @Nullable
    public byte[] getErrorData() {
        return mErrorData;
    }

    /**
     * Build an IKE Notification Payload for this {@link IkeProtocolException} instance.
     *
     * @return the notification payload.
     * @hide
     */
    public IkeNotifyPayload buildNotifyPayload() {
        return new IkeNotifyPayload(mErrorType, mErrorData);
    }
}
