/* capture_file.cpp
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "capture_file.h"

/*
 * @file Capture file class
 *
 * Wraps the capture_file struct, cfile global, and callbacks.
 */

#include "globals.h"
capture_file cfile;

#include "file.h"

#include "epan/epan_dissect.h"

#include "ui/capture.h"

#include <QFileInfo>
#include <QTimer>
#include <QDebug>

CaptureEvent::CaptureEvent(Context ctx, EventType evt) :
    _ctx(ctx),
    _evt(evt),
    _session(Q_NULLPTR)
{
}

CaptureEvent::CaptureEvent(Context ctx, EventType evt, QString file) :
    _ctx(ctx),
    _evt(evt),
    _filePath(file),
    _session(Q_NULLPTR)
{
}

CaptureEvent::CaptureEvent(Context ctx, EventType evt, capture_session * session) :
    _ctx(ctx),
    _evt(evt),
    _session(session)
{
}

CaptureEvent::CaptureEvent(const CaptureEvent &ce) :
    _ctx(ce._ctx),
    _evt(ce._evt),
    _filePath(ce._filePath),
    _session(ce._session)
{
}

CaptureEvent::Context CaptureEvent::captureContext() const
{ return _ctx; }

CaptureEvent::EventType CaptureEvent::eventType() const
{ return _evt; }

QString CaptureEvent::filePath() const
{ return _filePath; }

capture_session * CaptureEvent::capSession() const
{ return _session; }

// To do:
// - Add getters and (if needed) setters:
//   - Full filename
//   - Capture state (stopped, prepared, running).
// - Call common_create_progress_dlg. This would let us manage the stop
//   flag here as well as emit progress signals.

QString CaptureFile::no_capture_file_ = QObject::tr("[no capture file]");

CaptureFile::CaptureFile(QObject *parent, capture_file *cap_file) :
    QObject(parent),
    cap_file_(cap_file),
    file_state_(QString())
{
#ifdef HAVE_LIBPCAP
    capture_callback_add(captureCallback, (void *) this);
#endif
    cf_callback_add(captureFileCallback, (void *) this);
}

CaptureFile::~CaptureFile()
{
    cf_callback_remove(captureFileCallback, this);
}

bool CaptureFile::isValid() const
{
    if (cap_file_ && cap_file_->state != FILE_CLOSED && cap_file_->state != FILE_READ_PENDING) { // XXX FILE_READ_IN_PROGRESS as well?
        return true;
    }
    return false;
}

const QString CaptureFile::filePath()
{
    QString path;

    if (isValid()) {
        //
        // Sadly, some UN*Xes don't necessarily use UTF-8
        // for their file names, so we have to map the
        // file path to UTF-8.  If that fails, we're somewhat
        // stuck.
        //
        char *utf8_filename = g_filename_to_utf8(cap_file_->filename,
                                                 -1,
                                                 NULL,
                                                 NULL,
                                                 NULL);
        if (utf8_filename) {
            path = QString::fromUtf8(utf8_filename);
            g_free(utf8_filename);
        } else {
            // So what the heck else can we do here?
            path = QString();
        }
    } else {
        path = QString();
    }
    return path;
}

const QString CaptureFile::fileName()
{
    QString path, name;

    path = filePath();
    if (!path.isEmpty()) {
        QFileInfo cfi(path);
        name = cfi.fileName();
    } else {
        name = QString();
    }

    return name;
}

const QString CaptureFile::fileBaseName()
{
    QString baseName;

    if (isValid()) {
        char *basename = cf_get_basename(cap_file_);
        baseName = basename;
        g_free(basename);
    } else {
        baseName = QString();
    }
    return baseName;
}

const QString CaptureFile::fileDisplayName()
{
    QString displayName;

    if (isValid()) {
        char *display_name = cf_get_display_name(cap_file_);
        displayName = display_name;
        g_free(display_name);
    } else {
        displayName = QString();
    }
    return displayName;
}

const QString CaptureFile::fileTitle()
{
    QString title;

    if (isValid()) {
        title = fileDisplayName() + file_state_;
    } else {
        title = no_capture_file_;
    }
    return title;
}

struct _packet_info *CaptureFile::packetInfo()
{
    if (capFile() && capFile()->edt) {
        return &(capFile()->edt->pi);
    }
    return NULL;
}

int CaptureFile::timestampPrecision()
{
    if (capFile() && capFile()->provider.wth) {
        return wtap_file_tsprec(capFile()->provider.wth);
    }
    return WTAP_TSPREC_UNKNOWN;
}

void CaptureFile::retapPackets()
{
    if (cap_file_) {
        cf_retap_packets(cap_file_);
    }
}

void CaptureFile::delayedRetapPackets()
{
    QTimer::singleShot(0, this, SLOT(retapPackets()));
}

void CaptureFile::reload()
{
    if (cap_file_ && cap_file_->state == FILE_READ_DONE) {
        cf_reload(cap_file_);
    }
}

void CaptureFile::stopLoading()
{
    setCaptureStopFlag(true);
}

QString CaptureFile::displayFilter() const
{
    if (isValid())
        return QString(cap_file_->dfilter);
    return QString();
}

capture_file *CaptureFile::globalCapFile()
{
    return &cfile;
}

void *CaptureFile::window()
{
    if (cap_file_) return cap_file_->window;
    return NULL;
}

void CaptureFile::setCaptureStopFlag(bool stop_flag)
{
    if (cap_file_) cap_file_->stop_flag = stop_flag;
}

void CaptureFile::captureFileCallback(int event, void *data, void *user_data)
{
    CaptureFile *capture_file = static_cast<CaptureFile *>(user_data);
    if (!capture_file) return;

    capture_file->captureFileEvent(event, data);
}

#ifdef HAVE_LIBPCAP
void CaptureFile::captureCallback(int event, capture_session *cap_session, void *user_data)
{
    CaptureFile *capture_file = static_cast<CaptureFile *>(user_data);
    if (!capture_file) return;

    capture_file->captureSessionEvent(event, cap_session);
}
#endif

void CaptureFile::captureFileEvent(int event, void *data)
{
    switch(event) {
    case(cf_cb_file_opened):
        cap_file_ = (capture_file *) data;
        emit captureEvent(CaptureEvent(CaptureEvent::File, CaptureEvent::Opened));
        break;
    case(cf_cb_file_closing):
        file_state_ = tr(" [closing]");
        emit captureEvent(CaptureEvent(CaptureEvent::File, CaptureEvent::Closing));
        break;
    case(cf_cb_file_closed):
        file_state_ = tr(" [closed]");
        emit captureEvent(CaptureEvent(CaptureEvent::File, CaptureEvent::Closed));
        cap_file_ = NULL;
        file_state_ = QString();
        break;
    case(cf_cb_file_read_started):
        emit captureEvent(CaptureEvent(CaptureEvent::File, CaptureEvent::Started));
        break;
    case(cf_cb_file_read_finished):
        emit captureEvent(CaptureEvent(CaptureEvent::File, CaptureEvent::Finished));
        break;
    case(cf_cb_file_reload_started):
        emit captureEvent(CaptureEvent(CaptureEvent::Reload, CaptureEvent::Started));
        break;
    case(cf_cb_file_reload_finished):
        emit captureEvent(CaptureEvent(CaptureEvent::Reload, CaptureEvent::Finished));
        break;
    case(cf_cb_file_rescan_started):
        emit captureEvent(CaptureEvent(CaptureEvent::Rescan, CaptureEvent::Started));
        break;
    case(cf_cb_file_rescan_finished):
        emit captureEvent(CaptureEvent(CaptureEvent::Rescan, CaptureEvent::Finished));
        break;
    case(cf_cb_file_retap_started):
        emit captureEvent(CaptureEvent(CaptureEvent::Retap, CaptureEvent::Started));
        break;
    case(cf_cb_file_retap_finished):
        /* Flush any pending tapped packet before emitting captureFileRetapFinished() */
        emit captureEvent(CaptureEvent(CaptureEvent::Retap, CaptureEvent::Finished));
        emit captureEvent(CaptureEvent(CaptureEvent::Retap, CaptureEvent::Flushed));
        break;
    case(cf_cb_file_merge_started):
        emit captureEvent(CaptureEvent(CaptureEvent::Merge, CaptureEvent::Started));
        break;
    case(cf_cb_file_merge_finished):
        emit captureEvent(CaptureEvent(CaptureEvent::Merge, CaptureEvent::Finished));
        break;

    case(cf_cb_file_fast_save_finished):
        // gtk/main.c calls main_cf_cb_file_rescan_finished. Should we do
        // the equivalent?
        break;

    case(cf_cb_file_save_started):
    {
        emit captureEvent(CaptureEvent(CaptureEvent::Save, CaptureEvent::Started, QString((const char *)data)));
        break;
    }
    case(cf_cb_file_save_finished):
        emit captureEvent(CaptureEvent(CaptureEvent::Save, CaptureEvent::Finished));
        break;
    case(cf_cb_file_save_failed):
        emit captureEvent(CaptureEvent(CaptureEvent::Save, CaptureEvent::Failed));
        break;
    case(cf_cb_file_save_stopped):
        emit captureEvent(CaptureEvent(CaptureEvent::Save, CaptureEvent::Stopped));
        break;

    default:
        qWarning() << "CaptureFile::captureFileCallback: event " << event << " unknown";
        Q_ASSERT(false);
        break;
    }
}

#ifdef HAVE_LIBPCAP
void CaptureFile::captureSessionEvent(int event, capture_session *cap_session)
{
    switch(event) {
    case(capture_cb_capture_prepared):
        emit captureEvent(CaptureEvent(CaptureEvent::Capture, CaptureEvent::Prepared, cap_session));
        cap_file_ = cap_session->cf;
        break;
    case(capture_cb_capture_update_started):
        emit captureEvent(CaptureEvent(CaptureEvent::Update, CaptureEvent::Started, cap_session));
        break;
    case(capture_cb_capture_update_continue):
        emit captureEvent(CaptureEvent(CaptureEvent::Update, CaptureEvent::Continued, cap_session));
        break;
    case(capture_cb_capture_update_finished):
        emit captureEvent(CaptureEvent(CaptureEvent::Update, CaptureEvent::Finished, cap_session));
        break;
    case(capture_cb_capture_fixed_started):
        emit captureEvent(CaptureEvent(CaptureEvent::Fixed, CaptureEvent::Started, cap_session));
        break;
    case(capture_cb_capture_fixed_continue):
        emit captureEvent(CaptureEvent(CaptureEvent::Fixed, CaptureEvent::Continued, cap_session));
        break;
    case(capture_cb_capture_fixed_finished):
        emit captureEvent(CaptureEvent(CaptureEvent::Fixed, CaptureEvent::Finished, cap_session));
        break;
    case(capture_cb_capture_stopping):
        /* Beware: this state won't be called, if the capture child
             * closes the capturing on it's own! */
        emit captureEvent(CaptureEvent(CaptureEvent::Capture, CaptureEvent::Stopping, cap_session));
        break;
    case(capture_cb_capture_failed):
        emit captureEvent(CaptureEvent(CaptureEvent::Capture, CaptureEvent::Failed, cap_session));
        break;
    default:
        qWarning() << "main_capture_callback: event " << event << " unknown";
    }
}
#endif // HAVE_LIBPCAP
