# 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.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"

    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 render(self, renderer):
        renderer.table_header([
            ("Table", "", "3"),
            ("Offset", "offset", "[addrpad]"),
            ("DueTime(H)", "due_high", "#020x"),
            ("DueTime", "due", "24"),
            ("Period(ms)", "period", ">10"),
            ("Signaled", "signaled", ">10"),
            ("Routine", "routine", "[addrpad]"),
            ("Module", "module", "20")])

        # Print kuser_shared things.
        kuser_shared = self.profile._KUSER_SHARED_DATA(
            self.profile.get_constant("KI_USER_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 = "-"

            renderer.table_row(
                i,
                timer,
                timer.DueTime.QuadPart,   # Due time in InterruptTime (100ns).
                self.profile.WinFileTime(value=now+timer.DueTime.QuadPart,
                                         is_utc=True),
                timer.Period,
                signaled,
                timer.Dpc.DeferredRoutine,
                self.session.address_resolver.format_address(
                    timer.Dpc.DeferredRoutine,
                    max_distance=0xFFFFFFFFFFFFF))
