/*
 * Copyright (C) 2016 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.
 */
#define DEBUG false
#include "Log.h"

#include "IncidentService.h"

#include "FdBuffer.h"
#include "PrivacyFilter.h"
#include "Reporter.h"
#include "incidentd_util.h"
#include "section_list.h"

#include <android/os/IncidentReportArgs.h>
#include <binder/IPCThreadState.h>
#include <binder/IResultReceiver.h>
#include <binder/IServiceManager.h>
#include <binder/IShellCallback.h>
#include <log/log.h>
#include <private/android_filesystem_config.h>
#include <utils/Looper.h>
#include <thread>

#include <unistd.h>

enum {
    WHAT_TAKE_REPORT = 1,
    WHAT_SEND_BROADCASTS = 2
};

#define DEFAULT_DELAY_NS (1000000000LL)

#define DEFAULT_BYTES_SIZE_LIMIT (96 * 1024 * 1024)        // 96MB
#define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000)  // 1 Day

// Skip these sections for dumpstate only. Dumpstate allows 10s max for each service to dump.
// Skip logs (1100 - 1108) and traces (1200 - 1202) because they are already in the bug report.
// Skip 3018 because it takes too long.
#define SKIPPED_SECTIONS { 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, /* Logs */ \
                           1200, 1201, 1202, /* Native, hal, java traces */ \
                           3018  /* "meminfo -a --proto" */ }

namespace android {
namespace os {
namespace incidentd {

String16 const APPROVE_INCIDENT_REPORTS("android.permission.APPROVE_INCIDENT_REPORTS");
String16 const DUMP_PERMISSION("android.permission.DUMP");
String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS");

static Status checkIncidentPermissions(const IncidentReportArgs& args) {
    uid_t callingUid = IPCThreadState::self()->getCallingUid();
    pid_t callingPid = IPCThreadState::self()->getCallingPid();
    if (callingUid == AID_ROOT || callingUid == AID_SHELL) {
        // Root and shell are ok.
        return Status::ok();
    }

    if (checkCallingPermission(APPROVE_INCIDENT_REPORTS)) {
        // Permission controller (this is a singleton permission that is always granted
        // exactly for PermissionController) is allowed to access incident reports
        // so it can show the user info about what they are approving.
        return Status::ok();
    }

    // checking calling permission.
    if (!checkCallingPermission(DUMP_PERMISSION)) {
        ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP",
              callingPid, callingUid);
        return Status::fromExceptionCode(
                Status::EX_SECURITY,
                "Calling process does not have permission: android.permission.DUMP");
    }
    if (!checkCallingPermission(USAGE_STATS_PERMISSION)) {
        ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS",
              callingPid, callingUid);
        return Status::fromExceptionCode(
                Status::EX_SECURITY,
                "Calling process does not have permission: android.permission.USAGE_STATS");
    }

    // checking calling request uid permission.
    switch (args.getPrivacyPolicy()) {
        case PRIVACY_POLICY_LOCAL:
            if (callingUid != AID_SHELL && callingUid != AID_ROOT) {
                ALOGW("Calling pid %d and uid %d does not have permission to get local data.",
                      callingPid, callingUid);
                return Status::fromExceptionCode(
                        Status::EX_SECURITY,
                        "Calling process does not have permission to get local data.");
            }
            break;
        case PRIVACY_POLICY_EXPLICIT:
            if (callingUid != AID_SHELL && callingUid != AID_ROOT && callingUid != AID_STATSD &&
                    callingUid != AID_SYSTEM) {
                ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.",
                      callingPid, callingUid);
                return Status::fromExceptionCode(
                        Status::EX_SECURITY,
                        "Calling process does not have permission to get explicit data.");
            }
            break;
    }
    return Status::ok();
}

static string build_uri(const string& pkg, const string& cls, const string& id) {
    return "content://android.os.IncidentManager/pending?pkg="
        + pkg + "&receiver=" + cls + "&r=" + id;
}

// ================================================================================
ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
            const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
            const sp<Throttler>& throttler)
        :mLock(),
         mWorkDirectory(workDirectory),
         mBroadcaster(broadcaster),
         mHandlerLooper(handlerLooper),
         mBacklogDelay(DEFAULT_DELAY_NS),
         mThrottler(throttler),
         mBatch(new ReportBatch()) {
}

ReportHandler::~ReportHandler() {
}

void ReportHandler::handleMessage(const Message& message) {
    switch (message.what) {
        case WHAT_TAKE_REPORT:
            take_report();
            break;
        case WHAT_SEND_BROADCASTS:
            send_broadcasts();
            break;
    }
}

void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) {
    mBatch->addPersistedReport(args);
    mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
    mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
}

void ReportHandler::scheduleStreamingReport(const IncidentReportArgs& args,
        const sp<IIncidentReportStatusListener>& listener, int streamFd) {
    mBatch->addStreamingReport(args, listener, streamFd);
    mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
    mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
}

void ReportHandler::scheduleSendBacklog() {
    unique_lock<mutex> lock(mLock);
    mBacklogDelay = DEFAULT_DELAY_NS;
    schedule_send_broadcasts_locked();
}

void ReportHandler::schedule_send_broadcasts_locked() {
    mHandlerLooper->removeMessages(this, WHAT_SEND_BROADCASTS);
    mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, Message(WHAT_SEND_BROADCASTS));
}

void ReportHandler::take_report() {
    // Cycle the batch and throttle.
    sp<ReportBatch> batch;
    {
        unique_lock<mutex> lock(mLock);
        batch = mThrottler->filterBatch(mBatch);
    }

    if (batch->empty()) {
        // Nothing to do.
        return;
    }

    sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);

    // Take the report, which might take a while. More requests might queue
    // up while we're doing this, and we'll handle them in their next batch.
    // TODO: We should further rate-limit the reports to no more than N per time-period.
    // TODO: Move this inside reporter.
    size_t reportByteSize = 0;
    reporter->runReport(&reportByteSize);

    // Tell the throttler how big it was, for the next throttling.
    // TODO: This still isn't ideal. The throttler really should just track the
    // persisted reqeusts, but changing Reporter::runReport() to track that individually
    // will be a big change.
    if (batch->hasPersistedReports()) {
        mThrottler->addReportSize(reportByteSize);
    }

    // Kick off the next steps, one of which is to send any new or otherwise remaining
    // approvals, and one of which is to send any new or remaining broadcasts.
    {
        unique_lock<mutex> lock(mLock);
        schedule_send_broadcasts_locked();
    }
}

void ReportHandler::send_broadcasts() {
    Broadcaster::broadcast_status_t result = mBroadcaster->sendBroadcasts();
    if (result == Broadcaster::BROADCASTS_FINISHED) {
        // We're done.
        unique_lock<mutex> lock(mLock);
        mBacklogDelay = DEFAULT_DELAY_NS;
    } else if (result == Broadcaster::BROADCASTS_REPEAT) {
        // It worked, but there are more.
        unique_lock<mutex> lock(mLock);
        mBacklogDelay = DEFAULT_DELAY_NS;
        schedule_send_broadcasts_locked();
    } else if (result == Broadcaster::BROADCASTS_BACKOFF) {
        // There was a failure. Exponential backoff.
        unique_lock<mutex> lock(mLock);
        mBacklogDelay *= 2;
        ALOGI("Error sending to dropbox. Trying again in %lld minutes",
              (mBacklogDelay / (1000000000LL * 60)));
        schedule_send_broadcasts_locked();
    }
}

// ================================================================================
IncidentService::IncidentService(const sp<Looper>& handlerLooper) {
    mThrottler = new Throttler(DEFAULT_BYTES_SIZE_LIMIT, DEFAULT_REFACTORY_PERIOD_MS);
    mWorkDirectory = new WorkDirectory();
    mBroadcaster = new Broadcaster(mWorkDirectory);
    mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
            mThrottler);
    mBroadcaster->setHandler(mHandler);
}

IncidentService::~IncidentService() {}

Status IncidentService::reportIncident(const IncidentReportArgs& args) {
    IncidentReportArgs argsCopy(args);

    // Validate that the privacy policy is one of the real ones.
    // If it isn't, clamp it to the next more restrictive real one.
    argsCopy.setPrivacyPolicy(cleanup_privacy_policy(args.getPrivacyPolicy()));

    // TODO: Check that the broadcast recevier has the proper permissions
    // TODO: Maybe we should consider relaxing the permissions if it's going to
    // dropbox, but definitely not if it's going to the broadcaster.
    Status status = checkIncidentPermissions(args);
    if (!status.isOk()) {
        return status;
    }

    // If they asked for the LOCAL privacy policy, give them EXPLICT.  LOCAL has to
    // be streamed. (This only applies to shell/root, because everyone else would have
    // been rejected by checkIncidentPermissions()).
    if (argsCopy.getPrivacyPolicy() < PRIVACY_POLICY_EXPLICIT) {
        ALOGI("Demoting privacy policy to EXPLICT for persisted report.");
        argsCopy.setPrivacyPolicy(PRIVACY_POLICY_EXPLICIT);
    }

    // If they didn't specify a component, use dropbox.
    if (argsCopy.receiverPkg().length() == 0 && argsCopy.receiverCls().length() == 0) {
        argsCopy.setReceiverPkg(DROPBOX_SENTINEL.getPackageName());
        argsCopy.setReceiverCls(DROPBOX_SENTINEL.getClassName());
    }

    mHandler->schedulePersistedReport(argsCopy);

    return Status::ok();
}

Status IncidentService::reportIncidentToStream(const IncidentReportArgs& args,
                                               const sp<IIncidentReportStatusListener>& listener,
                                               const unique_fd& stream) {
    IncidentReportArgs argsCopy(args);

    // Streaming reports can not also be broadcast.
    argsCopy.setReceiverPkg("");
    argsCopy.setReceiverCls("");

    // Validate that the privacy policy is one of the real ones.
    // If it isn't, clamp it to the next more restrictive real one.
    argsCopy.setPrivacyPolicy(cleanup_privacy_policy(args.getPrivacyPolicy()));

    Status status = checkIncidentPermissions(argsCopy);
    if (!status.isOk()) {
        return status;
    }

    // The ReportRequest takes ownership of the fd, so we need to dup it.
    int fd = dup(stream.get());
    if (fd < 0) {
        return Status::fromStatusT(-errno);
    }

    mHandler->scheduleStreamingReport(argsCopy, listener, fd);

    return Status::ok();
}

Status IncidentService::systemRunning() {
    if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
        return Status::fromExceptionCode(Status::EX_SECURITY,
                                         "Only system uid can call systemRunning");
    }

    // When system_server is up and running, schedule the dropbox task to run.
    mBroadcaster->reset();
    mHandler->scheduleSendBacklog();

    return Status::ok();
}

Status IncidentService::getIncidentReportList(const String16& pkg16, const String16& cls16,
            vector<String16>* result) {
    status_t err;
    const string pkg(String8(pkg16).string());
    const string cls(String8(cls16).string());

    // List the reports
    vector<sp<ReportFile>> all;
    err = mWorkDirectory->getReports(&all, 0);
    if (err != NO_ERROR) {
        return Status::fromStatusT(err);
    }

    // Find the ones that match pkg and cls.
    for (sp<ReportFile>& file: all) {
        err = file->loadEnvelope();
        if (err != NO_ERROR) {
            continue;
        }
        const ReportFileProto& envelope = file->getEnvelope();
        size_t reportCount = envelope.report_size();
        for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) {
            const ReportFileProto_Report& report = envelope.report(reportIndex);
            if (pkg == report.pkg() && cls == report.cls()) {
                result->push_back(String16(build_uri(pkg, cls, file->getId()).c_str()));
                break;
            }
        }
    }

    return Status::ok();
}

Status IncidentService::getIncidentReport(const String16& pkg16, const String16& cls16,
            const String16& id16, IncidentManager::IncidentReport* result) {
    status_t err;

    const string pkg(String8(pkg16).string());
    const string cls(String8(cls16).string());
    const string id(String8(id16).string());

    IncidentReportArgs args;
    sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, &args);
    if (file != nullptr) {
        // Create pipe
        int fds[2];
        if (pipe(fds) != 0) {
            ALOGW("Error opening pipe to filter incident report: %s",
                  file->getDataFileName().c_str());
            return Status::ok();
        }
        result->setTimestampNs(file->getTimestampNs());
        result->setPrivacyPolicy(file->getEnvelope().privacy_policy());
        result->takeFileDescriptor(fds[0]);
        int writeFd = fds[1];
        // spawn a thread to write the data. Release the writeFd ownership to the thread.
        thread th([file, writeFd, args]() { file->startFilteringData(writeFd, args); });

        th.detach();
    }

    return Status::ok();
}

Status IncidentService::deleteIncidentReports(const String16& pkg16, const String16& cls16,
            const String16& id16) {
    const string pkg(String8(pkg16).string());
    const string cls(String8(cls16).string());
    const string id(String8(id16).string());

    sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, nullptr);
    if (file != nullptr) {
        mWorkDirectory->commit(file, pkg, cls);
    }
    mBroadcaster->clearBroadcasts(pkg, cls, id);

    return Status::ok();
}

Status IncidentService::deleteAllIncidentReports(const String16& pkg16) {
    const string pkg(String8(pkg16).string());

    mWorkDirectory->commitAll(pkg);
    mBroadcaster->clearPackageBroadcasts(pkg);

    return Status::ok();
}

/**
 * Implement our own because the default binder implementation isn't
 * properly handling SHELL_COMMAND_TRANSACTION.
 */
status_t IncidentService::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
                                     uint32_t flags) {
    status_t err;

    switch (code) {
        case SHELL_COMMAND_TRANSACTION: {
            int in = data.readFileDescriptor();
            int out = data.readFileDescriptor();
            int err = data.readFileDescriptor();
            int argc = data.readInt32();
            Vector<String8> args;
            for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
                args.add(String8(data.readString16()));
            }
            sp<IShellCallback> shellCallback = IShellCallback::asInterface(data.readStrongBinder());
            sp<IResultReceiver> resultReceiver =
                    IResultReceiver::asInterface(data.readStrongBinder());

            FILE* fin = fdopen(in, "r");
            FILE* fout = fdopen(out, "w");
            FILE* ferr = fdopen(err, "w");

            if (fin == NULL || fout == NULL || ferr == NULL) {
                resultReceiver->send(NO_MEMORY);
            } else {
                err = command(fin, fout, ferr, args);
                resultReceiver->send(err);
            }

            if (fin != NULL) {
                fflush(fin);
                fclose(fin);
            }
            if (fout != NULL) {
                fflush(fout);
                fclose(fout);
            }
            if (fout != NULL) {
                fflush(ferr);
                fclose(ferr);
            }

            return NO_ERROR;
        } break;
        default: { return BnIncidentManager::onTransact(code, data, reply, flags); }
    }
}

status_t IncidentService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
    const int argCount = args.size();

    if (argCount >= 1) {
        if (!args[0].compare(String8("privacy"))) {
            return cmd_privacy(in, out, err, args);
        }
        if (!args[0].compare(String8("throttler"))) {
            mThrottler->dump(out);
            return NO_ERROR;
        }
        if (!args[0].compare(String8("section"))) {
            int id = atoi(args[1]);
            int idx = 0;
            while (SECTION_LIST[idx] != NULL) {
                const Section* section = SECTION_LIST[idx];
                if (section->id == id) {
                    fprintf(out, "Section[%d] %s\n", id, section->name.string());
                    break;
                }
                idx++;
            }
            return NO_ERROR;
        }
    }
    return cmd_help(out);
}

status_t IncidentService::cmd_help(FILE* out) {
    fprintf(out, "usage: adb shell cmd incident privacy print <section_id>\n");
    fprintf(out, "usage: adb shell cmd incident privacy parse <section_id> < proto.txt\n");
    fprintf(out, "    Prints/parses for the section id.\n\n");
    fprintf(out, "usage: adb shell cmd incident section <section_id>\n");
    fprintf(out, "    Prints section id and its name.\n\n");
    fprintf(out, "usage: adb shell cmd incident throttler\n");
    fprintf(out, "    Prints the current throttler state\n");
    return NO_ERROR;
}

static void printPrivacy(const Privacy* p, FILE* out, String8 indent) {
    if (p == NULL) return;
    fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.string(), p->field_id, p->type, p->policy);
    if (p->children == NULL) return;
    for (int i = 0; p->children[i] != NULL; i++) {  // NULL-terminated.
        printPrivacy(p->children[i], out, indent + "  ");
    }
}

status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
    (void)in;

    const int argCount = args.size();
    if (argCount >= 3) {
        String8 opt = args[1];
        int sectionId = atoi(args[2].string());

        const Privacy* p = get_privacy_of_section(sectionId);
        if (p == NULL) {
            fprintf(err, "Can't find section id %d\n", sectionId);
            return NO_ERROR;
        }
        fprintf(err, "Get privacy for %d\n", sectionId);
        if (opt == "print") {
            printPrivacy(p, out, String8(""));
        } else if (opt == "parse") {
            /*
            FdBuffer buf;
            status_t error = buf.read(fileno(in), 60000);
            if (error != NO_ERROR) {
                fprintf(err, "Error reading from stdin\n");
                return error;
            }
            fprintf(err, "Read %zu bytes\n", buf.size());
            PrivacyFilter pBuf(p, buf.data());

            PrivacySpec spec = PrivacySpec::new_spec(argCount > 3 ? atoi(args[3]) : -1);
            error = pBuf.strip(spec);
            if (error != NO_ERROR) {
                fprintf(err, "Error strip pii fields with spec %d\n", spec.policy);
                return error;
            }
            return pBuf.flush(fileno(out));
            */
            return -1;
        }
    } else {
        return cmd_help(out);
    }
    return NO_ERROR;
}

status_t IncidentService::dump(int fd, const Vector<String16>& args) {
    if (std::find(args.begin(), args.end(), String16("--proto")) == args.end()) {
        ALOGD("Skip dumping incident. Only proto format is supported.");
        dprintf(fd, "Incident dump only supports proto version.\n");
        return NO_ERROR;
    }

    ALOGD("Dump incident proto");
    IncidentReportArgs incidentArgs;
    incidentArgs.setPrivacyPolicy(PRIVACY_POLICY_EXPLICIT);
    int skipped[] = SKIPPED_SECTIONS;
    for (const Section** section = SECTION_LIST; *section; section++) {
        const int id = (*section)->id;
        if (std::find(std::begin(skipped), std::end(skipped), id) == std::end(skipped)
                && !section_requires_specific_mention(id)) {
            incidentArgs.addSection(id);
        }
    }

    if (!checkIncidentPermissions(incidentArgs).isOk()) {
        return PERMISSION_DENIED;
    }

    // The ReportRequest takes ownership of the fd, so we need to dup it.
    int fd1 = dup(fd);
    if (fd1 < 0) {
        return -errno;
    }

    // TODO: Remove this.  Someone even dumpstate, wanting to get an incident report
    // should use the API.  That will take making dumpstated call the API, which is a
    // good thing.  It also means it won't be subject to the timeout.
    mHandler->scheduleStreamingReport(incidentArgs, NULL, fd1);

    return NO_ERROR;
}

}  // namespace incidentd
}  // namespace os
}  // namespace android