/*
 * 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.
 */

#include "Log.h"

#include "Broadcaster.h"

#include "IncidentService.h"

#include <android/os/DropBoxManager.h>
#include <binder/IServiceManager.h>
#include <thread>

namespace android {
namespace os {
namespace incidentd {

using android::os::IIncidentCompanion;
using binder::Status;

// ============================================================
Broadcaster::ConsentListener::ConsentListener(const sp<Broadcaster>& broadcaster,
        const ReportId& reportId)
    :mBroadcaster(broadcaster),
     mId(reportId) {
}

Broadcaster::ConsentListener::~ConsentListener() {
}

Status Broadcaster::ConsentListener::onReportApproved() {
    mBroadcaster->report_approved(mId);
    return Status::ok();
}

Status Broadcaster::ConsentListener::onReportDenied() {
    mBroadcaster->report_denied(mId);
    return Status::ok();
}

// ============================================================
Broadcaster::ReportId::ReportId()
    :id(),
     pkg(),
     cls() {
}

Broadcaster::ReportId::ReportId(const ReportId& that)
    :id(that.id),
     pkg(that.pkg),
     cls(that.cls) {
}

Broadcaster::ReportId::ReportId(const string& i, const string& p, const string& c)
    :id(i),
     pkg(p),
     cls(c) {
}

Broadcaster::ReportId::~ReportId() {
}

bool Broadcaster::ReportId::operator<(const ReportId& that) const {
    if (id < that.id) {
        return true;
    }
    if (id > that.id) {
        return false;
    }
    if (pkg < that.pkg) {
        return true;
    }
    if (pkg > that.pkg) {
        return false;
    }
    if (cls < that.cls) {
        return true;
    }
    return false;
}

// ============================================================
Broadcaster::ReportStatus::ReportStatus()
    :approval_sent(false),
     ready_sent(false),
     listener(nullptr) {
}

Broadcaster::ReportStatus::ReportStatus(const ReportStatus& that)
    :approval_sent(that.approval_sent),
     ready_sent(that.ready_sent),
     listener(that.listener) {
}

Broadcaster::ReportStatus::~ReportStatus() {
}

// ============================================================
Broadcaster::Broadcaster(const sp<WorkDirectory>& workDirectory)
        :mReportHandler(),
         mWorkDirectory(workDirectory) {
}

void Broadcaster::setHandler(const sp<ReportHandler>& handler) {
    mReportHandler = handler;
}

void Broadcaster::reset() {
    unique_lock<mutex> lock(mLock);
    mLastSent = 0;
    mHistory.clear();
    // Could cancel the listeners, but this happens when
    // the system process crashes, so don't bother.
}

void Broadcaster::clearBroadcasts(const string& pkg, const string& cls, const string& id) {
    unique_lock<mutex> lock(mLock);

    map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls));
    if (found != mHistory.end()) {
        if (found->second.listener != nullptr) {
            sp<IIncidentCompanion> ics = get_incident_companion();
            if (ics != nullptr) {
                ics->cancelAuthorization(found->second.listener);
            }
        }
        mHistory.erase(found);
    }
}

void Broadcaster::clearPackageBroadcasts(const string& pkg) {
    unique_lock<mutex> lock(mLock);

    map<ReportId,ReportStatus>::iterator it = mHistory.begin();
    while (it != mHistory.end()) {
        if (it->first.pkg == pkg) {
            if (it->second.listener != nullptr) {
                sp<IIncidentCompanion> ics = get_incident_companion();
                if (ics != nullptr) {
                    ics->cancelAuthorization(it->second.listener);
                }
            }
            it = mHistory.erase(it);
        } else {
            it++;
        }
    }
}

Broadcaster::broadcast_status_t Broadcaster::sendBroadcasts() {
    int err;
    int64_t lastSent = get_last_sent();

    vector<sp<ReportFile>> files;
    mWorkDirectory->getReports(&files, 0); //lastSent);

    // Don't send multiple broadcasts to the same receiver.
    set<ReportId> reportReadyBroadcasts;

    for (const sp<ReportFile>& file: files) {
        err = file->loadEnvelope();
        if (err != NO_ERROR) {
            ALOGW("Error (%s) loading envelope from %s", strerror(-err),
                    file->getEnvelopeFileName().c_str());
            continue;
        }

        const ReportFileProto& envelope = file->getEnvelope();

        if (!envelope.completed()) {
            ALOGI("Incident report not completed skipping it: %s",
                    file->getEnvelopeFileName().c_str());
            continue;
        }

        // When one of the broadcast functions in this loop fails, it's almost
        // certainly because the system process is crashing or has crashed.  Rather
        // than continuing to pound on the system process and potentially make things
        // worse, we bail right away, return BROADCASTS_BACKOFF, and we will try
        // again later.  In the meantime, if the system process did crash, it might
        // clear out mHistory, which means we'll be back here again to send the
        // backlog.
        size_t reportCount = envelope.report_size();
        bool hasApprovalPending = false;
        for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) {

            const ReportFileProto_Report& report = envelope.report(reportIndex);
            status_t err;
            if (report.privacy_policy() == PRIVACY_POLICY_AUTOMATIC || report.share_approved()) {
                // It's privacy policy is AUTO, or it's been approved,
                // so send the actual broadcast.
                if (!was_ready_sent(file->getId(), report.pkg(), report.cls())) {
                    if (report.pkg() == DROPBOX_SENTINEL.getPackageName()
                            && report.cls() == DROPBOX_SENTINEL.getClassName()) {
                        IncidentReportArgs args;
                        get_args_from_report(&args, report);
                        err = send_to_dropbox(file, args);
                        if (err != NO_ERROR) {
                            return BROADCASTS_BACKOFF;
                        }
                    } else {
                        reportReadyBroadcasts.insert(ReportId(file->getId(), report.pkg(),
                                    report.cls()));
                    }
                }
            } else {
                // It's not approved yet, so send the approval.
                if (!was_approval_sent(file->getId(), report.pkg(), report.cls())) {
                    err = send_approval_broadcasts(file->getId(), report.pkg(), report.cls());
                    if (err != NO_ERROR) {
                        return BROADCASTS_BACKOFF;
                    }
                    hasApprovalPending = true;
                }
            }
        }

        lastSent = file->getTimestampNs();
        if (!hasApprovalPending) {
            set_last_sent(lastSent);
        }
    }

    for (const ReportId& report: reportReadyBroadcasts) {
        err = send_report_ready_broadcasts(report.id, report.pkg, report.cls);
        if (err != NO_ERROR) {
            return BROADCASTS_BACKOFF;
        }
    }

    return mWorkDirectory->hasMore(lastSent) ? BROADCASTS_REPEAT : BROADCASTS_FINISHED;
}

void Broadcaster::set_last_sent(int64_t timestamp) {
    unique_lock<mutex> lock(mLock);
    mLastSent = timestamp;
}

int64_t Broadcaster::get_last_sent() {
    unique_lock<mutex> lock(mLock);
    return mLastSent;
}

/*
void Broadcaster::printReportStatuses() const {
    ALOGD("mHistory {");
    for (map<ReportId,ReportStatus>::const_iterator it = mHistory.begin();
            it != mHistory.end(); it++) {
        ALOGD("   [%s %s] --> [%d %d]", it->first.id.c_str(), it->first.pkg.c_str(),
                it->second.approval_sent, it->second.ready_sent);
    }
    ALOGD("}");
}
*/

bool Broadcaster::was_approval_sent(const string& id, const string& pkg, const string& cls) {
    unique_lock<mutex> lock(mLock);
    map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls));
    if (found != mHistory.end()) {
        return found->second.approval_sent;
    }
    return false;
}

void Broadcaster::set_approval_sent(const string& id, const string& pkg, const string& cls,
        const sp<ConsentListener>& listener) {
    unique_lock<mutex> lock(mLock);
    ReportStatus& reportStatus = mHistory[ReportId(id, pkg, cls)];
    reportStatus.approval_sent = true;
    reportStatus.listener = listener;
}

bool Broadcaster::was_ready_sent(const string& id, const string& pkg, const string& cls) {
    unique_lock<mutex> lock(mLock);
    map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls));
    if (found != mHistory.end()) {
        return found->second.ready_sent;
    }
    return false;
}

void Broadcaster::set_ready_sent(const string& id, const string& pkg, const string& cls) {
    unique_lock<mutex> lock(mLock);
    mHistory[ReportId(id, pkg, cls)].ready_sent = true;
}

status_t Broadcaster::send_approval_broadcasts(const string& id, const string& pkg,
        const string& cls) {
    sp<IIncidentCompanion> ics = get_incident_companion();
    if (ics == nullptr) {
        return NAME_NOT_FOUND;
    }

    sp<ConsentListener> listener = new ConsentListener(this, ReportId(id, pkg, cls));

    ALOGI("send_approval_broadcasts for %s %s/%s", id.c_str(), pkg.c_str(), cls.c_str());

    Status status = ics->authorizeReport(0, String16(pkg.c_str()),
            String16(cls.c_str()), String16(id.c_str()), 0, listener);

    if (!status.isOk()) {
        // authorizeReport is oneway, so any error is a transaction error.
        return status.transactionError();
    }

    set_approval_sent(id, pkg, cls, listener);

    return NO_ERROR;
}

void Broadcaster::report_approved(const ReportId& reportId) {
    status_t err;

    // Kick off broadcaster to do send the ready broadcasts.
    ALOGI("The user approved the report, so kicking off another broadcast pass. %s %s/%s",
            reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str());
    sp<ReportFile> file = mWorkDirectory->getReport(reportId.pkg, reportId.cls, reportId.id,
            nullptr);
    if (file != nullptr) {
        err = file->loadEnvelope();
        if (err != NO_ERROR) {
            return;
        }

        err = file->markApproved(reportId.pkg, reportId.cls);
        if (err != NO_ERROR) {
            ALOGI("Couldn't find report that was just approved: %s %s/%s",
                    reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str());
            return;
        }

        file->saveEnvelope();
        if (err != NO_ERROR) {
            return;
        }
    }
    mReportHandler->scheduleSendBacklog();
}

void Broadcaster::report_denied(const ReportId& reportId) {
    // The user didn't approve the report, so remove it from the WorkDirectory.
    ALOGI("The user denied the report, so deleting it. %s %s/%s",
            reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str());
    sp<ReportFile> file = mWorkDirectory->getReport(reportId.pkg, reportId.cls, reportId.id,
            nullptr);
    if (file != nullptr) {
        mWorkDirectory->commit(file, reportId.pkg, reportId.cls);
    }
}

status_t Broadcaster::send_report_ready_broadcasts(const string& id, const string& pkg,
        const string& cls) {
    sp<IIncidentCompanion> ics = get_incident_companion();
    if (ics == nullptr) {
        return NAME_NOT_FOUND;
    }

    ALOGI("send_report_ready_broadcasts for %s %s/%s", id.c_str(), pkg.c_str(), cls.c_str());

    Status status = ics->sendReportReadyBroadcast(String16(pkg.c_str()), String16(cls.c_str()));

    if (!status.isOk()) {
        // sendReportReadyBroadcast is oneway, so any error is a transaction error.
        return status.transactionError();
    }

    set_ready_sent(id, pkg, cls);

    return NO_ERROR;
}

status_t Broadcaster::send_to_dropbox(const sp<ReportFile>& file,
        const IncidentReportArgs& args) {
    status_t err;

    sp<DropBoxManager> dropbox = new DropBoxManager();
    if (dropbox == nullptr) {
        ALOGW("Can't reach dropbox now, so we won't be able to write the incident report to there");
        return NO_ERROR;
    }

    int fds[2];
    if (pipe(fds) != 0) {
        ALOGW("Error opening pipe to filter incident report: %s", file->getDataFileName().c_str());
        return NO_ERROR;
    }

    int readFd = 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();

    // Takes ownership of readFd.
    Status status = dropbox->addFile(String16("incident"), readFd, 0);
    if (!status.isOk()) {
        // TODO: This may or may not leak the readFd, depending on where it failed.
        // Not sure how to fix this given the dropbox API.
        ALOGW("Error sending incident report to dropbox.");
        return -errno;
    }

    // On successful write, tell the working directory that this file is done.
    mWorkDirectory->commit(file, DROPBOX_SENTINEL.getPackageName(),
            DROPBOX_SENTINEL.getClassName());

    // Don't need to call set_ready_sent, because we just removed it from the ReportFile,
    // so we'll never hear about it again.

    return NO_ERROR;
}

sp<IIncidentCompanion> Broadcaster::get_incident_companion() {
    sp<IBinder> binder = defaultServiceManager()->getService(String16("incidentcompanion"));
    if (binder == nullptr) {
        ALOGI("Can not find IIncidentCompanion service to send broadcast. Will try again later.");
        return nullptr;
    }

    sp<IIncidentCompanion> ics = interface_cast<IIncidentCompanion>(binder);
    if (ics == nullptr) {
        ALOGI("The incidentcompanion service is not an IIncidentCompanion. Will try again later.");
        return nullptr;
    }

    return ics;
}

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