/*
 * 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 android.bluetooth;

import static android.bluetooth.BluetoothSocket.SocketType;

import static java.util.Objects.requireNonNull;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresNoPermission;
import android.annotation.SystemApi;

import com.android.bluetooth.flags.Flags;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

/**
 * Defines parameters for creating Bluetooth server and client socket channels.
 *
 * <p>Used with {@link BluetoothAdapter#listenUsingSocketSettings} to create a server socket and
 * {@link BluetoothDevice#createUsingSocketSettings} to create a client socket.
 *
 * @see BluetoothAdapter#listenUsingSocketSettings
 * @see BluetoothDevice#createUsingSocketSettings
 */
@FlaggedApi(Flags.FLAG_SOCKET_SETTINGS_API)
public final class BluetoothSocketSettings {

    private static final int L2CAP_PSM_UNSPECIFIED = -1;

    /**
     * Annotation to define the data path used for Bluetooth socket communication. This determines
     * how data flows between the application and the Bluetooth controller.
     *
     * @hide
     */
    @IntDef(
            prefix = {"DATA_PATH_"},
            value = {DATA_PATH_NO_OFFLOAD, DATA_PATH_HARDWARE_OFFLOAD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SocketDataPath {}

    /**
     * Non-offload data path where the application's socket data is processed by the main Bluetooth
     * stack.
     *
     * @hide
     */
    @SystemApi public static final int DATA_PATH_NO_OFFLOAD = 0;

    /**
     * Hardware offload data path where the application's socket data is processed by a offloaded
     * application running on the low-power processor.
     *
     * <p>Using this data path requires the {@code BLUETOOTH_PRIVILEGED} permission, which will be
     * checked when a socket connection or channel is created.
     *
     * @hide
     */
    @SystemApi public static final int DATA_PATH_HARDWARE_OFFLOAD = 1;

    /**
     * Maximum size (in bytes) of a data packet that can be received from the endpoint when using
     * {@link #DATA_PATH_HARDWARE_OFFLOAD}.
     */
    @SystemApi private static final int HARDWARE_OFFLOAD_PACKET_MAX_SIZE = 65535;

    /**
     * Maximum length (in bytes) of a socket name when using {@link #DATA_PATH_HARDWARE_OFFLOAD}.
     */
    @SystemApi private static final int HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH = 127;

    /**
     * Constant representing an invalid hub ID. This value indicates that a hub ID has not been
     * assigned or is not valid.
     *
     * @hide
     */
    private static final long INVALID_HUB_ID = 0;

    /**
     * Constant representing an invalid hub endpoint ID. This value indicates that an endpoint ID
     * has not been assigned or is not valid.
     *
     * @hide
     */
    private static final long INVALID_ENDPOINT_ID = 0;

    /** Type of the Bluetooth socket */
    @SocketType private final int mSocketType;

    /** Encryption requirement for the Bluetooth socket. */
    private final boolean mEncryptionRequired;

    /** Authentication requirement for the Bluetooth socket. */
    private final boolean mAuthenticationRequired;

    /** L2CAP Protocol/Service Multiplexer (PSM) for the Bluetooth Socket. */
    private final int mL2capPsm;

    /** RFCOMM service name associated with the Bluetooth socket. */
    private final String mRfcommServiceName;

    /** RFCOMM service UUID associated with the Bluetooth socket. */
    private final UUID mRfcommUuid;

    /**
     * Specifies the data path used for this socket, influencing how data is transmitted and
     * processed. Select the appropriate data path based on performance and power consumption
     * requirements:
     *
     * <ul>
     *   <li>{@link #DATA_PATH_NO_OFFLOAD}: Suitable for applications that require the full
     *       processing capabilities of the main Bluetooth stack.
     *   <li>{@link #DATA_PATH_HARDWARE_OFFLOAD}: Optimized for lower power consumption by utilizing
     *       an offloaded application running on a dedicated low-power processor.
     * </ul>
     */
    private final @SocketDataPath int mDataPath;

    /**
     * A user-friendly name for this socket, primarily for debugging and logging. This name should
     * be descriptive and can help identify the socket during development and troubleshooting.
     *
     * <p>When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the offloaded
     * application running on the low-power processor. This allows the offloaded application to
     * identify and manage the socket.
     */
    private final String mSocketName;

    /**
     * When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this identifies the hub hosting the endpoint.
     *
     * <p>Hub represents a logical/physical representation of multiple endpoints. A pair of {@code
     * mHubId} and {@code mEndpointId} uniquely identifies the endpoint globally.
     */
    private final long mHubId;

    /**
     * When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this identifies the specific endpoint within
     * the hub that is associated with this socket.
     */
    private final long mEndpointId;

    /**
     * The maximum size (in bytes) of a single data packet that can be received from the endpoint
     * when using {@link #DATA_PATH_HARDWARE_OFFLOAD}.
     */
    private final int mMaximumPacketSize;

    /**
     * Returns the type of the Bluetooth socket.
     *
     * <p>Defaults to {@code BluetoothSocket#TYPE_RFCOMM}.
     */
    @RequiresNoPermission
    @SocketType
    public int getSocketType() {
        return mSocketType;
    }

    /** Returns the L2CAP PSM value used for a BluetoothSocket#TYPE_LE socket. */
    @RequiresNoPermission
    public @IntRange(from = 128, to = 255) int getL2capPsm() {
        return mL2capPsm;
    }

    /**
     * Returns the RFCOMM service name used for a BluetoothSocket#TYPE_RFCOMM socket.
     *
     * <p>Defaults to {@code null}.
     */
    @Nullable
    @RequiresNoPermission
    public String getRfcommServiceName() {
        return mRfcommServiceName;
    }

    /**
     * Returns the RFCOMM service UUID used for a BluetoothSocket#TYPE_RFCOMM socket.
     *
     * <p>Defaults to {@code null}.
     */
    @Nullable
    @RequiresNoPermission
    public UUID getRfcommUuid() {
        return mRfcommUuid;
    }

    /**
     * Checks if encryption is enabled for the Bluetooth socket.
     *
     * <p>Defaults to {@code false}.
     */
    @RequiresNoPermission
    public boolean isEncryptionRequired() {
        return mEncryptionRequired;
    }

    /**
     * Checks if authentication is enabled for the Bluetooth socket.
     *
     * <p>Defaults to {@code false}.
     */
    @RequiresNoPermission
    public boolean isAuthenticationRequired() {
        return mAuthenticationRequired;
    }

    /**
     * Returns the data path used for this socket. The data path determines how data is routed and
     * processed for the socket connection.
     *
     * <p>Defaults to {@link #DATA_PATH_NO_OFFLOAD}.
     *
     * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
     * available through the System API.
     *
     * @hide
     */
    @SystemApi
    @RequiresNoPermission
    public @SocketDataPath int getDataPath() {
        return mDataPath;
    }

    /**
     * Returns the user-friendly name assigned to this socket. This name is primarily used for
     * debugging and logging purposes.
     *
     * <p>When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the offloaded
     * application running on the low-power processor.
     *
     * <p>Defaults to {@code null} if no name was explicitly set.
     *
     * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
     * available through the System API.
     *
     * @hide
     */
    @SystemApi
    @NonNull
    @RequiresNoPermission
    public String getSocketName() {
        return mSocketName;
    }

    /**
     * Returns the ID of the hub associated with this socket when using {@link
     * #DATA_PATH_HARDWARE_OFFLOAD}.
     *
     * <p>If the data path is not set to {@link #DATA_PATH_HARDWARE_OFFLOAD}, this method returns
     * {@link #INVALID_HUB_ID}.
     *
     * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
     * available through the System API.
     *
     * @hide
     */
    @SystemApi
    @RequiresNoPermission
    public long getHubId() {
        if (mDataPath != DATA_PATH_HARDWARE_OFFLOAD) {
            return INVALID_HUB_ID;
        }
        return mHubId;
    }

    /**
     * Returns the ID of the endpoint within the hub associated with this socket when using {@link
     * #DATA_PATH_HARDWARE_OFFLOAD}. An endpoint represents a specific point of communication within
     * the hub.
     *
     * <p>If the data path is not set to {@link #DATA_PATH_HARDWARE_OFFLOAD}, this method returns
     * {@link #INVALID_ENDPOINT_ID}.
     *
     * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
     * available through the System API.
     *
     * @hide
     */
    @SystemApi
    @RequiresNoPermission
    public long getEndpointId() {
        if (mDataPath != DATA_PATH_HARDWARE_OFFLOAD) {
            return INVALID_ENDPOINT_ID;
        }
        return mEndpointId;
    }

    /**
     * Returns the requested maximum size (in bytes) of a data packet that can be received from the
     * endpoint associated with this socket when using {@link #DATA_PATH_HARDWARE_OFFLOAD}.
     *
     * <p>Defaults to {@link #HARDWARE_OFFLOAD_PACKET_MAX_SIZE}.
     *
     * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
     * available through the System API.
     *
     * @hide
     */
    @SystemApi
    @RequiresNoPermission
    public int getRequestedMaximumPacketSize() {
        return mMaximumPacketSize;
    }

    /**
     * Returns a {@link String} that describes each BluetoothSocketSettings parameter current value.
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("BluetoothSocketSettings{");
        builder.append("mSocketType=")
                .append(mSocketType)
                .append(", mEncryptionRequired=")
                .append(mEncryptionRequired)
                .append(", mAuthenticationRequired=")
                .append(mAuthenticationRequired);
        if (mSocketType == BluetoothSocket.TYPE_RFCOMM) {
            builder.append(", mRfcommServiceName=")
                    .append(mRfcommServiceName)
                    .append(", mRfcommUuid=")
                    .append(mRfcommUuid);
        } else {
            builder.append(", mL2capPsm=").append(mL2capPsm);
        }
        if (mDataPath == DATA_PATH_HARDWARE_OFFLOAD) {
            builder.append(", mDataPath=")
                    .append(mDataPath)
                    .append(", mSocketName=")
                    .append(mSocketName)
                    .append(", mHubId=")
                    .append(mHubId)
                    .append(", mEndpointId=")
                    .append(mEndpointId)
                    .append(", mMaximumPacketSize=")
                    .append(mMaximumPacketSize);
        }
        builder.append("}");
        return builder.toString();
    }

    private BluetoothSocketSettings(
            int socketType,
            int l2capPsm,
            boolean encryptionRequired,
            boolean authenticationRequired,
            String rfcommServiceName,
            UUID rfcommUuid,
            int dataPath,
            String socketName,
            long hubId,
            long endpointId,
            int maximumPacketSize) {
        mSocketType = socketType;
        mL2capPsm = l2capPsm;
        mEncryptionRequired = encryptionRequired;
        mAuthenticationRequired = authenticationRequired;
        mRfcommUuid = rfcommUuid;
        mRfcommServiceName = rfcommServiceName;
        mDataPath = dataPath;
        mSocketName = socketName;
        mHubId = hubId;
        mEndpointId = endpointId;
        mMaximumPacketSize = maximumPacketSize;
    }

    /** Builder for {@link BluetoothSocketSettings}. */
    @FlaggedApi(Flags.FLAG_SOCKET_SETTINGS_API)
    public static final class Builder {
        private int mSocketType = BluetoothSocket.TYPE_RFCOMM;
        private int mL2capPsm = L2CAP_PSM_UNSPECIFIED;
        private boolean mEncryptionRequired = false;
        private boolean mAuthenticationRequired = false;
        private String mRfcommServiceName = null;
        private UUID mRfcommUuid = null;
        private int mDataPath = DATA_PATH_NO_OFFLOAD;
        private String mSocketName = BluetoothSocket.DEFAULT_SOCKET_NAME;
        private long mHubId = INVALID_HUB_ID;
        private long mEndpointId = INVALID_ENDPOINT_ID;
        private int mMaximumPacketSize = HARDWARE_OFFLOAD_PACKET_MAX_SIZE;

        public Builder() {}

        /**
         * Sets the socket type.
         *
         * <p>Must be one of:
         *
         * <ul>
         *   <li>{@link BluetoothSocket#TYPE_RFCOMM}
         *   <li>{@link BluetoothSocket#TYPE_LE}
         * </ul>
         *
         * <p>Defaults to {@code BluetoothSocket#TYPE_RFCOMM}.
         *
         * @param socketType The type of socket.
         * @return This builder.
         * @throws IllegalArgumentException If {@code socketType} is invalid.
         */
        @NonNull
        @RequiresNoPermission
        public Builder setSocketType(@SocketType int socketType) {
            if (socketType != BluetoothSocket.TYPE_RFCOMM
                    && socketType != BluetoothSocket.TYPE_LE) {
                throw new IllegalArgumentException("invalid socketType - " + socketType);
            }
            mSocketType = socketType;
            return this;
        }

        /**
         * Sets the L2CAP PSM (Protocol/Service Multiplexer) for the Bluetooth socket.
         *
         * <p>This is only used for {@link BluetoothSocket#TYPE_LE} sockets.
         *
         * <p>Valid PSM values for {@link BluetoothSocket#TYPE_LE} sockets is ranging from 128
         * (0x80) to 255 (0xFF).
         *
         * <p>Application using this API is responsible for obtaining protocol/service multiplexer
         * (PSM) value from remote device.
         *
         * @param l2capPsm The L2CAP PSM value.
         * @return This builder.
         * @throws IllegalArgumentException If l2cap PSM is not in given range.
         */
        @NonNull
        @RequiresNoPermission
        public Builder setL2capPsm(@IntRange(from = 128, to = 255) int l2capPsm) {
            if (l2capPsm < 128 || l2capPsm > 255) {
                throw new IllegalArgumentException("invalid L2cap PSM - " + l2capPsm);
            }
            mL2capPsm = l2capPsm;
            return this;
        }

        /**
         * Sets the encryption requirement for the Bluetooth socket.
         *
         * <p>Defaults to {@code false}.
         *
         * @param encryptionRequired {@code true} if encryption is required for this socket, {@code
         *     false} otherwise.
         * @return This builder.
         */
        @NonNull
        @RequiresNoPermission
        public Builder setEncryptionRequired(boolean encryptionRequired) {
            mEncryptionRequired = encryptionRequired;
            return this;
        }

        /**
         * Sets the authentication requirement for the Bluetooth socket.
         *
         * <p>Defaults to {@code false}.
         *
         * @param authenticationRequired {@code true} if authentication is required for this socket,
         *     {@code false} otherwise.
         * @return This builder.
         */
        @NonNull
        @RequiresNoPermission
        public Builder setAuthenticationRequired(boolean authenticationRequired) {
            mAuthenticationRequired = authenticationRequired;
            return this;
        }

        /**
         * Sets the RFCOMM service name associated with the Bluetooth socket.
         *
         * <p>This name is used to identify the service when a remote device searches for it using
         * SDP.
         *
         * <p>This is only used for {@link BluetoothSocket#TYPE_RFCOMM} sockets.
         *
         * <p>Defaults to {@code null}.
         *
         * @param rfcommServiceName The RFCOMM service name.
         * @return This builder.
         */
        @NonNull
        @RequiresNoPermission
        public Builder setRfcommServiceName(@NonNull String rfcommServiceName) {
            mRfcommServiceName = rfcommServiceName;
            return this;
        }

        /**
         * Sets the RFCOMM service UUID associated with the Bluetooth socket.
         *
         * <p>This UUID is used to uniquely identify the service when a remote device searches for
         * it using SDP.
         *
         * <p>This is only used for {@link BluetoothSocket#TYPE_RFCOMM} sockets.
         *
         * <p>Defaults to {@code null}.
         *
         * @param rfcommUuid The RFCOMM service UUID.
         * @return This builder.
         */
        @NonNull
        @RequiresNoPermission
        public Builder setRfcommUuid(@NonNull UUID rfcommUuid) {
            mRfcommUuid = rfcommUuid;
            return this;
        }

        /**
         * Sets the data path for this socket. The data path determines how data is routed and
         * processed for the socket connection.
         *
         * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
         * available through the System API.
         *
         * @param dataPath The desired data path for the socket.
         * @return This Builder object to allow for method chaining.
         * @throws IllegalArgumentException If {@code dataPath} is an invalid value.
         * @hide
         */
        @SystemApi
        @NonNull
        @RequiresNoPermission
        public Builder setDataPath(@SocketDataPath int dataPath) {
            if (dataPath < DATA_PATH_NO_OFFLOAD || dataPath > DATA_PATH_HARDWARE_OFFLOAD) {
                throw new IllegalArgumentException("Invalid dataPath - " + dataPath);
            }
            mDataPath = dataPath;
            return this;
        }

        /**
         * Sets a user-friendly name for this socket. This name is primarily used for debugging and
         * logging purposes.
         *
         * <p>When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the
         * offloaded application running on low-power processor.
         *
         * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
         * available through the System API.
         *
         * @param socketName The desired name for the socket. This should be a descriptive name that
         *     helps identify the socket during development and troubleshooting. The socket name
         *     cannot exceed {@link #HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH} bytes in length when
         *     encoded in UTF-8.
         * @return This Builder object to allow for method chaining.
         * @throws IllegalArgumentException if the provided `socketName` exceeds {@link
         *     #HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH} bytes when encoded in UTF-8.
         * @hide
         */
        @SystemApi
        @NonNull
        @RequiresNoPermission
        public Builder setSocketName(@NonNull String socketName) {
            byte[] socketNameBytes = requireNonNull(socketName).getBytes(StandardCharsets.UTF_8);
            if (socketNameBytes.length > HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH) {
                throw new IllegalArgumentException(
                        "Socket name cannot exceed "
                                + HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH
                                + " bytes in length when encoded in UTF-8.");
            }
            mSocketName = requireNonNull(socketName);
            return this;
        }

        /**
         * Sets the ID of the hub to be associated with this socket when using {@link
         * #DATA_PATH_HARDWARE_OFFLOAD}.
         *
         * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
         * available through the System API.
         *
         * @param hubId The ID of the hub.
         * @return This Builder object to allow for method chaining.
         * @hide
         */
        @SystemApi
        @NonNull
        @RequiresNoPermission
        public Builder setHubId(long hubId) {
            mHubId = hubId;
            return this;
        }

        /**
         * Sets the ID of the endpoint within the hub to be associated with this socket when using
         * {@link #DATA_PATH_HARDWARE_OFFLOAD}. An endpoint represents a specific point of
         * communication within the hub.
         *
         * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
         * available through the System API.
         *
         * @param endpointId The ID of the endpoint within the hub.
         * @return This Builder object to allow for method chaining.
         * @hide
         */
        @SystemApi
        @NonNull
        @RequiresNoPermission
        public Builder setEndpointId(long endpointId) {
            mEndpointId = endpointId;
            return this;
        }

        /**
         * Sets the requested maximum size (in bytes) of a data packet that can be received from the
         * endpoint associated with this socket when using {@link #DATA_PATH_HARDWARE_OFFLOAD}.
         *
         * <p>The main Bluetooth stack may adjust this value based on the actual capabilities
         * negotiated with the peer device during connection establishment. To get the final
         * negotiated value, use {@link BluetoothSocket#getMaxReceivePacketSize()} after the socket
         * is connected.
         *
         * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only
         * available through the System API.
         *
         * @param maximumPacketSize The maximum packet size in bytes.
         * @return This Builder object to allow for method chaining.
         * @hide
         */
        @SystemApi
        @NonNull
        @RequiresNoPermission
        public Builder setRequestedMaximumPacketSize(int maximumPacketSize) {
            mMaximumPacketSize = maximumPacketSize;
            return this;
        }

        /**
         * Builds a {@link BluetoothSocketSettings} object.
         *
         * @return A new {@link BluetoothSocketSettings} object with the configured parameters.
         * @throws IllegalArgumentException on invalid parameters
         */
        @NonNull
        @RequiresNoPermission
        public BluetoothSocketSettings build() {
            if (mSocketType == BluetoothSocket.TYPE_RFCOMM) {
                if (mRfcommUuid == null) {
                    throw new IllegalArgumentException("RFCOMM socket with missing uuid");
                }
                if (mL2capPsm != L2CAP_PSM_UNSPECIFIED) {
                    throw new IllegalArgumentException(
                            "Invalid Socket config: "
                                    + " Socket type: "
                                    + mSocketType
                                    + " L2cap PSM: "
                                    + mL2capPsm);
                }
            }
            if (mSocketType == BluetoothSocket.TYPE_LE) {
                if (mRfcommUuid != null) {
                    throw new IllegalArgumentException(
                            "Invalid Socket config: "
                                    + "Socket type: "
                                    + mSocketType
                                    + " Rfcomm Service Name: "
                                    + mRfcommServiceName
                                    + " Rfcomm Uuid: "
                                    + mRfcommUuid);
                }
            }
            if (mDataPath == DATA_PATH_HARDWARE_OFFLOAD) {
                if (mHubId == INVALID_HUB_ID || mEndpointId == INVALID_ENDPOINT_ID) {
                    throw new IllegalArgumentException(
                            "Hub ID and endpoint ID must be set for hardware data path");
                }
                if (mMaximumPacketSize < 0) {
                    throw new IllegalArgumentException("invalid packet size " + mMaximumPacketSize);
                }
            } else {
                if (mHubId != INVALID_HUB_ID || mEndpointId != INVALID_ENDPOINT_ID) {
                    throw new IllegalArgumentException(
                            "Hub ID and endpoint ID may not be set for software data path");
                }
            }
            return new BluetoothSocketSettings(
                    mSocketType,
                    mL2capPsm,
                    mEncryptionRequired,
                    mAuthenticationRequired,
                    mRfcommServiceName,
                    mRfcommUuid,
                    mDataPath,
                    mSocketName,
                    mHubId,
                    mEndpointId,
                    mMaximumPacketSize);
        }
    }
}
