# Rekall Memory Forensics
# Copyright (c) 2010, 2011, 2012 Michael Ligh <michael.ligh@mnin.org>
# Copyright 2013 Google Inc. All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
from rekall import utils
from rekall.plugins.windows import common

# pylint: disable=protected-access

#References:
# http://www.dcl.hpi.uni-potsdam.de/research/WRK/2007/08/a-performance-issue-in-windows-timer-management/


class Timers(common.WindowsCommandPlugin):
    """Print kernel timers and associated module DPCs.

    Ref:
    http://computer.forensikblog.de/en/2011/10/timers-and-times.html
    """

    __name = "timers"

    table_header = [
        dict(name="Tbl", align="r", width=3),
        dict(name="_KTIMER", style="address"),
        dict(name="due", width=22),
        dict(name="due_time", width=24),
        dict(name="period", width=11, align="r"),
        dict(name="sig", align="r", width=4),
        dict(name="routine", style="address"),
        dict(name="symbol")
    ]


    def _timers(self):
        if self.profile.get_constant("KiTimerTableListHead"):
            # On XP x64, Windows 2003 SP1-SP2, and Vista SP0-SP2,
            # KiTimerTableListHead is an array of 512 _KTIMER_TABLE_ENTRY
            # structs.
            if self.profile.has_type("_KTIMER_TABLE_ENTRY"):
                lists = self.profile.get_constant_object(
                    "KiTimerTableListHead",
                    target="Array",
                    target_args=dict(
                        target='_KTIMER_TABLE_ENTRY',
                        count=512)
                    )

                for i, l in enumerate(lists):
                    for t in l.Entry.list_of_type("_KTIMER", "TimerListEntry"):
                        yield i, t

            else:
                # On XP SP0-SP3 x86 and Windows 2003 SP0, KiTimerTableListHead
                # is an array of 256 _LIST_ENTRY for _KTIMERs.
                lists = self.profile.get_constant_object(
                    "KiTimerTableListHead",
                    target="Array",
                    target_args=dict(
                        target='_LIST_ENTRY',
                        count=256)
                    )

                for i, l in enumerate(lists):
                    for t in l.list_of_type_fast("_KTIMER", "TimerListEntry"):
                        yield i, t
        else:
            # On Windows 7, there is no more KiTimerTableListHead. The list is
            # at _KPCR.PrcbData.TimerTable.TimerEntries (credits to Matt Suiche
            # for this one. See http://pastebin.com/FiRsGW3f).
            kpcr = self.session.plugins.kpcr().kpcr()
            for i, table in enumerate(kpcr.Prcb.TimerTable.TimerEntries):
                self.session.report_progress("Table %r", table)
                for t in table.Entry.list_of_type_fast(
                        "_KTIMER", "TimerListEntry"):
                    yield i, t

    def timers(self):
        """A generator of timer objects."""
        # Sort the timers by address to make them easier to inspect.
        for i, timer in self._timers():
            # This is the code from reactos which checks for this:
            # #define ASSERT_TIMER(E) \
            #    NT_ASSERT(((E)->Header.Type == TimerNotificationObject) || \
            #              ((E)->Header.Type == TimerSynchronizationObject))
            if timer.Header.Type not in ["TimerNotificationObject",
                                         "TimerNotificationObject"]:
                continue

            self.session.report_progress("Looking at %#x", timer)

            # Ignore timers without DPCs
            if (not timer.Dpc.is_valid() or
                    not timer.Dpc.DeferredRoutine.is_valid()):
                continue

            yield i, timer

    def collect(self):
        # Print kuser_shared things.
        kuser_shared = self.profile.get_constant_object(
            "KI_USER_SHARED_DATA", "_KUSER_SHARED_DATA")

        interrupt_time = ((kuser_shared.InterruptTime.High1Time << 32) +
                          kuser_shared.InterruptTime.LowPart)
        now = kuser_shared.SystemTime.as_windows_timestamp() - interrupt_time
        seen = set()

        for i, timer in self.timers():
            if timer in seen:
                continue

            seen.add(timer)

            if timer.Header.SignalState.v():
                signaled = "Yes"
            else:
                signaled = "-"

            yield dict(Tbl=i,
                       _KTIMER=timer,
                       # Due time in InterruptTime (100ns).
                       due="0x%0.20x" % timer.DueTime.QuadPart,
                       due_time=self.profile.WinFileTime(
                           value=now+timer.DueTime.QuadPart,
                           is_utc=True),
                       period=timer.Period,
                       sig=signaled,
                       routine=timer.Dpc.DeferredRoutine,
                       symbol=utils.FormattedAddress(
                           self.session.address_resolver,
                           timer.Dpc.DeferredRoutine)
            )
