# 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
import re
from typing import List

from volatility3.framework import renderers, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.layers import scanners
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.plugins.linux import pslist

vollog = logging.getLogger(__name__)


class VmaRegExScan(plugins.PluginInterface):
    """Scans all virtual memory areas for tasks using RegEx."""

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

    MAXSIZE_DEFAULT = 128

    @classmethod
    def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
        # Since we're calling the plugin, make sure we have the plugin's requirements
        return [
            requirements.ModuleRequirement(
                name="kernel",
                description="Linux kernel",
                architectures=["Intel32", "Intel64"],
            ),
            requirements.VersionRequirement(
                name="pslist", component=pslist.PsList, version=(4, 0, 0)
            ),
            requirements.ListRequirement(
                name="pid",
                description="Filter on specific process IDs",
                element_type=int,
                optional=True,
            ),
            requirements.StringRequirement(
                name="pattern", description="RegEx pattern", optional=False
            ),
            requirements.VersionRequirement(
                name="regex_scanner",
                component=scanners.RegExScanner,
                version=(1, 0, 0),
            ),
            requirements.IntRequirement(
                name="maxsize",
                description="Maximum size in bytes for displayed context",
                default=cls.MAXSIZE_DEFAULT,
                optional=True,
            ),
        ]

    def _generator(self, regex_pattern, tasks):
        regex_pattern = bytes(regex_pattern, "UTF-8")
        vollog.debug(f"RegEx Pattern: {regex_pattern}")

        for task in tasks:

            if not task.mm:
                continue
            name = utility.array_to_string(task.comm)

            # attempt to create a process layer for each task and skip those
            # that cannot (e.g. kernel threads)
            proc_layer_name = task.add_process_layer()
            if not proc_layer_name:
                continue

            # get the proc_layer object from the context
            proc_layer = self.context.layers[proc_layer_name]

            # get process sections for scanning
            sections = [
                (start, size) for (start, size) in task.get_process_memory_sections()
            ]

            for offset in proc_layer.scan(
                context=self.context,
                scanner=scanners.RegExScanner(regex_pattern),
                sections=sections,
                progress_callback=self._progress_callback,
            ):
                result_data = proc_layer.read(offset, self.MAXSIZE_DEFAULT, pad=True)

                # reapply the regex in order to extract just the match
                regex_result = re.match(regex_pattern, result_data)

                if regex_result:
                    # the match is within the results_data (e.g. it fits within MAXSIZE_DEFAULT)
                    # extract just the match itself
                    regex_match = regex_result.group(0)
                    text_result = str(regex_match, encoding="UTF-8", errors="replace")
                    bytes_result = regex_match
                else:
                    # the match is not with the results_data (e.g. it doesn't fit within MAXSIZE_DEFAULT)
                    text_result = str(result_data, encoding="UTF-8", errors="replace")
                    bytes_result = result_data

                user_pid = task.tgid
                yield 0, (
                    user_pid,
                    name,
                    format_hints.Hex(offset),
                    text_result,
                    bytes_result,
                )

    def run(self):
        filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))

        return renderers.TreeGrid(
            [
                ("PID", int),
                ("Process", str),
                ("Offset", format_hints.Hex),
                ("Text", str),
                ("Hex", bytes),
            ],
            self._generator(
                self.config.get("pattern"),
                pslist.PsList.list_tasks(
                    self.context, self.config["kernel"], filter_func=filter_func
                ),
            ),
        )
