# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import Dict, Iterator, List

import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import deprecation, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.constants import architectures
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols.linux import extensions
from volatility3.framework.symbols.linux.utilities import tainting

vollog = logging.getLogger(__name__)


class Modxview(interfaces.plugins.PluginInterface):
    """Centralize lsmod, check_modules and hidden_modules results to efficiently \
spot modules presence and taints."""

    _version = (1, 0, 0)
    _required_framework_version = (2, 17, 0)

    @classmethod
    def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
        return [
            requirements.ModuleRequirement(
                name="kernel",
                description="Linux kernel",
                architectures=architectures.LINUX_ARCHS,
            ),
            requirements.VersionRequirement(
                name="linux_utilities_modules",
                component=linux_utilities_modules.Modules,
                version=(3, 0, 0),
            ),
            requirements.VersionRequirement(
                name="linux_utilities_module_gatherer_lsmod",
                component=linux_utilities_modules.ModuleGathererLsmod,
                version=(1, 0, 0),
            ),
            requirements.VersionRequirement(
                name="linux_utilities_module_gatherer_sysfs",
                component=linux_utilities_modules.ModuleGathererSysFs,
                version=(1, 0, 0),
            ),
            requirements.VersionRequirement(
                name="linux_utilities_module_gatherer_scanner",
                component=linux_utilities_modules.ModuleGathererScanner,
                version=(1, 0, 0),
            ),
            requirements.VersionRequirement(
                name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0)
            ),
            requirements.BooleanRequirement(
                name="plain_taints",
                description="Display the plain taints string for each module.",
                optional=True,
                default=False,
            ),
        ]

    @classmethod
    @deprecation.deprecated_method(
        replacement=linux_utilities_modules.Modules.flatten_run_modules_results,
        replacement_version=(3, 0, 0),
        removal_date="2026-03-25",
    )
    def flatten_run_modules_results(
        cls, run_results: Dict[str, List[extensions.module]], deduplicate: bool = True
    ) -> Iterator[extensions.module]:
        """Flatten a dictionary mapping plugin names and modules list, to a single merged list.
        This is useful to get a generic lookup list of all the detected modules.

        Args:
            run_results: dictionary of plugin names mapping a list of detected modules
            deduplicate: remove duplicate modules, based on their offsets

        Returns:
            Iterator of modules objects
        """
        return linux_utilities_modules.Modules.flatten_run_modules_results(
            run_results, deduplicate
        )

    @classmethod
    @deprecation.deprecated_method(
        replacement=linux_utilities_modules.Modules.run_modules_scanners,
        replacement_version=(3, 0, 0),
        removal_date="2026-03-25",
    )
    def run_modules_scanners(
        cls,
        context: interfaces.context.ContextInterface,
        kernel_name: str,
        run_hidden_modules: bool = True,
    ) -> Dict[str, List[extensions.module]]:
        """Run module scanning plugins and aggregate the results. It is designed
        to not operate any inter-plugin results triage."""
        return linux_utilities_modules.Modules.run_modules_scanners(
            context, kernel_name, run_hidden_modules
        )

    def _generator(self):
        kernel = self.context.modules[self.config["kernel"]]

        wanted_gatherers = [
            linux_utilities_modules.ModuleGathererLsmod,
            linux_utilities_modules.ModuleGathererSysFs,
            linux_utilities_modules.ModuleGathererScanner,
        ]

        run_results = linux_utilities_modules.Modules.run_modules_scanners(
            context=self.context,
            kernel_module_name=self.config["kernel"],
            caller_wanted_gatherers=wanted_gatherers,
            flatten=False,
        )

        aggregated_modules = {}
        # We want to be explicit on the plugins results we are interested in
        for gatherer in wanted_gatherers:
            # Iterate over each recovered module
            for mod_info in run_results[gatherer.name]:
                # Use offsets as unique keys, whether a module
                # appears in many plugin runs or not
                if aggregated_modules.get(mod_info.offset, None) is not None:
                    # Append the plugin to the list of originating plugins
                    aggregated_modules[mod_info.offset].append(gatherer.name)
                else:
                    aggregated_modules[mod_info.offset] = [gatherer.name]

        for module_offset, gatherers in aggregated_modules.items():
            module = kernel.object("module", offset=module_offset, absolute=True)

            # Tainting parsing capabilities applied to the module
            if self.config.get("plain_taints"):
                taints = tainting.Tainting.get_taints_as_plain_string(
                    self.context,
                    self.config["kernel"],
                    module.taints,
                    True,
                )
            else:
                taints = ",".join(
                    tainting.Tainting.get_taints_parsed(
                        self.context,
                        self.config["kernel"],
                        module.taints,
                        True,
                    )
                )

            yield (
                0,
                (
                    module.get_name() or renderers.NotAvailableValue(),
                    format_hints.Hex(module_offset),
                    linux_utilities_modules.ModuleGathererLsmod.name in gatherers,
                    linux_utilities_modules.ModuleGathererSysFs.name in gatherers,
                    linux_utilities_modules.ModuleGathererScanner.name in gatherers,
                    taints or renderers.NotAvailableValue(),
                ),
            )

    def run(self):
        columns = [
            ("Name", str),
            ("Address", format_hints.Hex),
            ("In procfs", bool),
            ("In sysfs", bool),
            ("In scan", bool),
            ("Taints", str),
        ]

        return renderers.TreeGrid(
            columns,
            self._generator(),
        )
