/*
 * Copyright 2017 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.net.watchlist;

import android.privacy.DifferentialPrivacyEncoder;
import android.privacy.internal.longitudinalreporting.LongitudinalReportingConfig;
import android.privacy.internal.longitudinalreporting.LongitudinalReportingEncoder;

import com.android.internal.annotations.VisibleForTesting;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Helper class to apply differential privacy to watchlist reports.
 */
class PrivacyUtils {

    private static final String TAG = "PrivacyUtils";

    /**
     * Parameters used for encoding watchlist reports.
     * These numbers are optimal parameters for protecting privacy with good utility.
     *
     * TODO: Add links to explain the math behind.
     */
    private static final String ENCODER_ID_PREFIX = "watchlist_encoder:";
    private static final double PROB_F = 0.469;
    private static final double PROB_P = 0.28;
    private static final double PROB_Q = 1.0;

    private PrivacyUtils() {
    }

    /**
     * Get insecure DP encoder.
     * Should not apply it directly on real data as seed is not randomized.
     */
    @VisibleForTesting
    static DifferentialPrivacyEncoder createInsecureDPEncoderForTest(String appDigest) {
        final LongitudinalReportingConfig config = createLongitudinalReportingConfig(appDigest);
        return LongitudinalReportingEncoder.createInsecureEncoderForTest(config);
    }

    /**
     * Get secure encoder to encode watchlist.
     *
     * Warning: If you use the same user secret and app digest, then you will get the same
     * PRR result.
     */
    @VisibleForTesting
    static DifferentialPrivacyEncoder createSecureDPEncoder(byte[] userSecret,
            String appDigest) {
        final LongitudinalReportingConfig config = createLongitudinalReportingConfig(appDigest);
        return LongitudinalReportingEncoder.createEncoder(config, userSecret);
    }

    /**
     * Get DP config for encoding watchlist reports.
     */
    private static LongitudinalReportingConfig createLongitudinalReportingConfig(String appDigest) {
        return new LongitudinalReportingConfig(ENCODER_ID_PREFIX + appDigest, PROB_F, PROB_P,
                PROB_Q);
    }

    /**
     * Create a map that stores appDigest, encoded_visitedWatchlist pairs.
     */
    @VisibleForTesting
    static Map<String, Boolean> createDpEncodedReportMap(boolean isSecure, byte[] userSecret,
            List<String> appDigestList, WatchlistReportDbHelper.AggregatedResult aggregatedResult) {
        final int appDigestListSize = appDigestList.size();
        final HashMap<String, Boolean> resultMap = new HashMap<>(appDigestListSize);
        for (int i = 0; i < appDigestListSize; i++) {
            final String appDigest = appDigestList.get(i);
            // Each app needs to have different PRR result, hence we use appDigest as encoder Id.
            final DifferentialPrivacyEncoder encoder = isSecure
                    ? createSecureDPEncoder(userSecret, appDigest)
                    : createInsecureDPEncoderForTest(appDigest);
            final boolean visitedWatchlist = aggregatedResult.appDigestList.contains(appDigest);
            // Get the least significant bit of first byte, and set result to True if it is 1
            boolean encodedVisitedWatchlist = ((int) encoder.encodeBoolean(visitedWatchlist)[0]
                    & 0x1) == 0x1;
            resultMap.put(appDigest, encodedVisitedWatchlist);
        }
        return resultMap;
    }
}
