/*
 * Copyright (C) 2010 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 dalvik.system;

import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;

import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;

import java.util.Objects;
import libcore.util.NonNull;

/**
 * Interface that enables {@code StrictMode} to install callbacks to implement
 * its policy detection and penalty behavior in {@code libcore} code.
 * <p>
 * The framework separately defines {@code StrictMode.ThreadPolicy} and
 * {@code StrictMode.VmPolicy}, so we mirror that separation here; the former is
 * designed for per-thread policies, and the latter for process-wide policies.
 * <p>
 * Note that this is all best-effort to catch most accidental mistakes and isn't
 * intended to be a perfect mechanism, nor provide any sort of security.
 *
 * @hide
 */
@SystemApi(client = MODULE_LIBRARIES)
@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
@libcore.api.IntraCoreApi
public final class BlockGuard {

    // TODO: refactor class name to something more generic, since its scope is
    // growing beyond just blocking/logging.

    /**
     * Per-thread interface used to implement {@code StrictMode.ThreadPolicy}.
     *
     * @hide
     */
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    @libcore.api.IntraCoreApi
    public interface Policy {
        /**
         * Called on disk writes.
         *
         * @hide
         */
        @SystemApi(client = MODULE_LIBRARIES)
        @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
        void onWriteToDisk();

        /**
         * Called on disk reads.
         *
         * @hide
         */
        @UnsupportedAppUsage
        @SystemApi(client = MODULE_LIBRARIES)
        @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
        void onReadFromDisk();

        /**
         * Called on network operations.
         *
         * @hide
         */
        @UnsupportedAppUsage
        @libcore.api.IntraCoreApi
        void onNetwork();

        /**
         * Called on unbuffered input/ouput operations.
         *
         * @hide
         */
        @SystemApi(client = MODULE_LIBRARIES)
        @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
        void onUnbufferedIO();

        /**
         * Called on explicit GC request, i.e. Runtime.gc().
         *
         * @hide
         */
        void onExplicitGc();

        /**
         * Returns the policy bitmask, for shipping over Binder calls
         * to remote threads/processes and reinstantiating the policy
         * there. The bits in the mask are from the {@code DISALLOW_*} and
         * {@code PENALTY_*} constants declared in {@code StrictMode} class.
         *
         * @return policy bitmask
         *
         * @hide
         */
        @SystemApi(client = MODULE_LIBRARIES)
        @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
        int getPolicyMask();
    }

    /**
     * Per-process interface used to implement {@code StrictMode.VmPolicy}.
     * @hide
     */
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public interface VmPolicy {
        /**
         * Called by core libraries code when the given path is accessed. This
         * allows an implementation to alert developers to unexpected path
         * access, such as trying to access encrypted files before the
         * encryption key has been installed.
         * <p>
         * This only needs to be called once when a path is first accessed by
         * the process; it doesn't need to be invoked for each subsequent
         * read/write. (In contrast, we always need to call the per-thread
         * policy for every read/write, since ownership of an open file can move
         * between threads.)
         * <p>
         * Note that this is all best-effort to catch most accidental mistakes
         * and isn't intended to be a perfect mechanism, nor provide any sort of
         * security.
         *
         * @param path The path in the local file system that is being accessed
         *            for reading or writing.
         *
         * @hide
         */
        @SystemApi(client = MODULE_LIBRARIES)
        @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
        void onPathAccess(@NonNull String path);
    }

    /**
     * @deprecated no longer actively used, but kept intact for hidden API lists.
     *
     * @hide
     */
    @Deprecated
    public static class BlockGuardPolicyException extends RuntimeException {
        // bitmask of DISALLOW_*, PENALTY_*, etc flags
        @UnsupportedAppUsage
        private final int mPolicyState;
        @UnsupportedAppUsage
        private final int mPolicyViolated;
        @UnsupportedAppUsage
        private final String mMessage;   // may be null

        public BlockGuardPolicyException(int policyState, int policyViolated) {
            this(policyState, policyViolated, null);
        }

        @UnsupportedAppUsage
        public BlockGuardPolicyException(int policyState, int policyViolated, String message) {
            mPolicyState = policyState;
            mPolicyViolated = policyViolated;
            mMessage = message;
            fillInStackTrace();
        }

        public int getPolicy() {
            return mPolicyState;
        }

        public int getPolicyViolation() {
            return mPolicyViolated;
        }

        public String getMessage() {
            // Note: do not change this format casually.  It's
            // somewhat unfortunately Parceled and passed around
            // Binder calls and parsed back into an Exception by
            // Android's StrictMode.  This was the least invasive
            // option and avoided a gross mix of Java Serialization
            // combined with Parcels.
            return "policy=" + mPolicyState + " violation=" + mPolicyViolated +
                    (mMessage == null ? "" : (" msg=" + mMessage));
        }
    }

    /**
     * The default, permissive per-thread policy.
     *
     * @hide
     */
    @UnsupportedAppUsage
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public static final Policy LAX_POLICY = new Policy() {
        @Override public String toString() { return "LAX_POLICY"; }
        @Override public void onWriteToDisk() {}
        @Override public void onReadFromDisk() {}
        @Override public void onNetwork() {}
        @Override public void onUnbufferedIO() {}
        @Override public void onExplicitGc() {}

        @Override
        public int getPolicyMask() {
            return 0;
        }
    };

    /**
     * The default, permissive per-process policy.
     *
     * @hide
     */
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public static final VmPolicy LAX_VM_POLICY = new VmPolicy() {
        @Override public String toString() { return "LAX_VM_POLICY"; }
        @Override public void onPathAccess(String path) {}
    };

    @UnsupportedAppUsage
    private static ThreadLocal<Policy> threadPolicy = new ThreadLocal<Policy>() {
        @Override protected Policy initialValue() {
            return LAX_POLICY;
        }
    };

    private static volatile VmPolicy vmPolicy = LAX_VM_POLICY;

    /**
     * Get the per-thread policy for the current thread.
     *
     * @return the current thread's policy. Will return the {@link #LAX_POLICY}
     *         instance if nothing else is set.
     *
     * @hide
     */
    @UnsupportedAppUsage
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    @libcore.api.IntraCoreApi
    public static @NonNull Policy getThreadPolicy() {
        return threadPolicy.get();
    }

    /**
     * Sets the per-thread policy for the current thread.
     * <p>
     * This should only be called by {@code StrictMode}, since there can only be
     * one policy active at any given time.
     *
     * @param policy policy to set. Use the public {@link #LAX_POLICY} if you
     *            want to unset the active policy.
     *
     * @hide
     */
    @UnsupportedAppUsage
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public static void setThreadPolicy(@NonNull Policy policy) {
        threadPolicy.set(Objects.requireNonNull(policy));
    }

    /**
     * Get the per-process policy for the current process.
     *
     * @return the current process's policy. Will return the
     *         {@link #LAX_VM_POLICY} instance if nothing else is set.
     *
     * @hide
     */
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public static @NonNull VmPolicy getVmPolicy() {
        return vmPolicy;
    }

    /**
     * Set the per-process policy for the current process.
     * <p>
     * This should only be called by {@code StrictMode}, since there can only be
     * one policy active at any given time.
     *
     * @param policy policy to set. Use the public {@link #LAX_VM_POLICY} if you
     *            want to unset the active policy.
     *
     * @hide
     */
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public static void setVmPolicy(@NonNull VmPolicy policy) {
        vmPolicy = Objects.requireNonNull(policy);
    }

    private BlockGuard() {}
}
