Index: trunk/python/pyregfi/__init__.py
===================================================================
--- trunk/python/pyregfi/__init__.py	(revision 220)
+++ trunk/python/pyregfi/__init__.py	(revision 221)
@@ -5,4 +5,92 @@
 #
 
+## @mainpage API Documentation
+#
+# The pyregfi module provides a Python interface to the @ref regfi Windows 
+# registry library.  
+#
+# The library operates on registry hives, each of which is contained within a
+# single file.  To get started, one must first open the registry hive file with
+# the open() or file() Python built-in functions (or equivalent) and then pass
+# the resulting file object to pyregfi. For example:
+# @code
+# >>> import pyregfi
+# >>> fh = open('/mnt/win/c/WINDOWS/system32/config/system', 'rb')
+# >>> myHive = pyregfi.Hive(fh)
+# @endcode
+#
+# Using this Hive object, one can begin investigating what top-level keys
+# exist by starting with the root Key attribute:
+# @code
+# >>> for key in myHive.root.subkeys:
+# ...   print(key.name)
+# ControlSet001
+# ControlSet003
+# LastKnownGoodRecovery
+# MountedDevices
+# Select
+# Setup
+# WPA
+# @endcode
+#
+# From there, accessing subkeys and values by name is a simple matter of:
+# @code
+# >>> myKey = myHive.root.subkeys['Select']
+# >>> myValue = myKey.values['Current']
+# @endcode
+#
+# The data associated with a Value can be obtained through the fetch_data()
+# method:
+# @code
+# >>> print(myValue.fetch_data())
+# 1
+# @endcode
+# 
+# While useful for simple exercises, using the subkeys object for deeply nested
+# paths is not efficient and doesn't make for particularly attractive code.  
+# Instead, a special-purpose HiveIterator class is provided for simplicity of
+# use and fast access to specific known paths:
+# @code
+# >>> myIter = pyregfi.HiveIterator(myHive)
+# >>> myIter.descend(['ControlSet001','Control','NetworkProvider','HwOrder'])
+# >>> myKey = myIter.current_key()
+# >>> print(myKey.values['ProviderOrder'].fetch_data())
+# RDPNP,LanmanWorkstation,WebClient
+# @endcode
+# 
+# The first two lines above can be simplified in some "syntactic sugar" provided
+# by the Hive.subtree() method.  Also, as one might expect, the HiveIterator 
+# also acts as an iterator, producing keys in a depth-first order.
+# For instance, to traverse all keys under the ControlSet003\\Services key, 
+# printing their names as we go, we could do:
+# @code
+# >>> for key in Hive.subtree(['ControlSet003','Services']):
+# >>>   print(key.name)
+# Services
+# Abiosdsk
+# abp480n5
+# Parameters
+# PnpInterface
+# ACPI
+# [...]
+# @endcode
+#
+# Note that "Services" was printed first, since the subtree is traversed as a 
+# "preordering depth-first" search starting with the HiveIterator's current_key().  
+# As one might expect, traversals of subtrees stops when all elements in a 
+# specific subtree (and none outside of it) have been traversed.
+#
+# For more information, peruse the various attributes and methods available on 
+# the Hive, HiveIterator, Key, Value, and Security classes.
+#
+# @note @ref regfi is a read-only library by design and there 
+# are no plans to implement write support.
+# 
+# @note At present, pyregfi has been tested with Python versions 2.6 and 3.1
+#
+# @note Developers strive to make pyregfi thread-safe.
+# 
+# @note Key and Value names are case-sensitive in regfi and pyregfi
+#
 import sys
 import time
@@ -13,12 +101,35 @@
 import ctypes.util
 
-
-## Retrieves messages produced by regfi during parsing and interpretation
-#
-def GetLogMessages():
-    msgs = regfi.regfi_log_get_str()
-    if msgs == None:
-        return ''
-    return msgs.decode('utf-8')
+## An enumeration of registry Value data types
+#
+# @note This is a static class, there is no need to instantiate it. 
+#       Just access its attributes directly as DATA_TYPES.SZ, etc
+class DATA_TYPES(object):
+    ## None / Unknown
+    NONE                       =  0
+    ## String
+    SZ                         =  1
+    ## String with %...% expansions
+    EXPAND_SZ                  =  2
+    ## Binary buffer
+    BINARY                     =  3
+    ## 32 bit integer (little endian)
+    DWORD                      =  4 # DWORD, little endian
+    ## 32 bit integer (little endian)
+    DWORD_LE                   =  4
+    ## 32 bit integer (big endian)
+    DWORD_BE                   =  5 # DWORD, big endian
+    ## Symbolic link
+    LINK                       =  6
+    ## List of strings
+    MULTI_SZ                   =  7
+    ## Unknown structure
+    RESOURCE_LIST              =  8
+    ## Unknown structure
+    FULL_RESOURCE_DESCRIPTOR   =  9
+    ## Unknown structure
+    RESOURCE_REQUIREMENTS_LIST = 10
+    ## 64 bit integer
+    QWORD                      = 11 # 64-bit little endian
 
 
@@ -58,6 +169,19 @@
 
 
-## Abstract class which Handles memory management and proxies attribute
-#  access to base structures  
+## Retrieves messages produced by regfi during parsing and interpretation
+#
+# The regfi C library may generate log messages stored in a special thread-safe
+# global data structure.  These messages should be retrieved periodically or 
+# after each major operation by callers to determine if any errors or warnings
+# should be reported to the user.  Failure to retrieve these could result in 
+# excessive memory consumption.
+def GetLogMessages():
+    msgs = regfi.regfi_log_get_str()
+    if msgs == None:
+        return ''
+    return msgs.decode('utf-8')
+
+
+## Abstract class for most objects returned by the library
 class _StructureWrapper(object):
     _hive = None
@@ -76,10 +200,25 @@
         self._base = base
 
+    # Memory management for most regfi structures is taken care of here
     def __del__(self):
         regfi.regfi_free_record(self._base)
 
+    # Any attribute requests not explicitly defined in subclasses gets passed
+    # to the equivalent REGFI_* structure defined in structures.py
     def __getattr__(self, name):
         return getattr(self._base.contents, name)
-
+    
+    ## Test for equality
+    #
+    # Records returned by pyregfi may be compared with one another.  For example:
+    # @code
+    #  >>> key2 = key1.subkeys['child']
+    #  >>> key1 == key2
+    #  False
+    #  >>> key1 != key2
+    #  True
+    #  >>> key1 == key2.get_parent()
+    #  True
+    # @endcode
     def __eq__(self, other):
         return (type(self) == type(other)) and (self.offset == other.offset)
@@ -88,19 +227,19 @@
         return (not self.__eq__(other))
 
-class Key(_StructureWrapper):
+
+class Key():
     pass
 
-class Value(_StructureWrapper):
+
+class Value():
     pass
 
-## Registry value data
-class Data(_StructureWrapper):
-    pass
-
-## Registry security record/permissions
+
+## Registry security record and descriptor
+# XXX: Access to security descriptors not yet implemented
 class Security(_StructureWrapper):
     pass
 
-
+## Abstract class for ValueList and SubkeyList
 class _GenericList(object):
     _hive = None
@@ -109,5 +248,5 @@
     _current = None
 
-    # implementation-specific functions for _SubkeyList and _ValueList
+    # implementation-specific functions for SubkeyList and ValueList
     _fetch_num = None
     _find_element = None
@@ -128,9 +267,15 @@
         self._key = weakref.proxy(key)
         self._length = self._fetch_num(key._base)
-
-    
+    
+    
+    ## Length of list
     def __len__(self):
         return self._length
 
+
+    ## Retrieves a list element by name
+    #
+    # @return the first element whose name matches, or None if the element
+    #         could not be found
     def __getitem__(self, name):
         index = ctypes.c_uint32()
@@ -172,5 +317,20 @@
 
 
-class _SubkeyList(_GenericList):
+## The list of subkeys associated with a Key
+#
+# This attribute is both iterable:
+# @code
+#   for k in myKey.subkeys: 
+#     ...
+# @endcode
+# and accessible as a dictionary:
+# @code
+#   mySubkey = myKey.subkeys["keyName"]
+# @endcode
+#
+# @note SubkeyLists should never be accessed directly and only exist
+#       in association with a parent Key object.  Do not retain references to 
+#       SubkeyLists.  Instead, access them via their parent Key at all times.
+class SubkeyList(_GenericList):
     _fetch_num = regfi.regfi_fetch_num_subkeys
     _find_element = regfi.regfi_find_subkey
@@ -178,5 +338,20 @@
 
 
-class _ValueList(_GenericList):
+## The list of values associated with a Key
+#
+# This attribute is both iterable:
+# @code
+#   for v in myKey.values: 
+#     ...
+# @endcode
+# and accessible as a dictionary:
+# @code
+#   myValue = myKey.values["valueName"]
+# @endcode
+#
+# @note ValueLists should never be accessed directly and only exist
+#       in association with a parent Key object.  Do not retain references to 
+#       ValueLists.  Instead, access them via their parent Key at all times.
+class ValueList(_GenericList):
     _fetch_num = regfi.regfi_fetch_num_values
     _find_element = regfi.regfi_find_value
@@ -185,12 +360,40 @@
 
 ## Registry key 
+# These represent registry keys (@ref REGFI_NK records) and provide
+# access to their subkeys, values, and other metadata.
+#
+# @note Value instances may provide access to more than the attributes
+#       documented here.  However, undocumented attributes may change over time
+#       and are not officially supported.  If you need access to an attribute 
+#       not shown here, see pyregfi.structures.
 class Key(_StructureWrapper):
+    ## A @ref ValueList object representing the list of Values 
+    #  stored on this Key
     values = None
+
+    ## A @ref SubkeyList object representing the list of subkeys 
+    #  stored on this Key
     subkeys = None
+
+    ## The raw Key name as an uninterpreted bytearray
+    name_raw = (b"...")
+    
+    ## The name of the Key as a (unicode) string
+    name = "..."
+    
+    ## The absolute file offset of the Key record's cell in the Hive file
+    offset = 0xCAFEBABE
+
+    ## This Key's last modified time represented as the number of seconds 
+    #  since the UNIX epoch in UTC; similar to what time.time() returns
+    modified = 1300000000.123456
+
+    ## The NK record's flags field
+    flags = 0x10110001
 
     def __init__(self, hive, base):
         super(Key, self).__init__(hive, base)
-        self.values = _ValueList(self)
-        self.subkeys = _SubkeyList(self)
+        self.values = ValueList(self)
+        self.subkeys = SubkeyList(self)
 
     def __getattr__(self, name):
@@ -216,8 +419,19 @@
         return ret_val
 
+
+    ## Retrieves the Security properties for this key
     def fetch_security(self):
         return Security(self._hive,
                         regfi.regfi_fetch_sk(self._hive.file, self._base))
 
+
+    ## Retrieves the class name for this key
+    #
+    # Class names are typically stored as UTF-16LE strings, so these are decoded
+    # into proper python (unicode) strings.  However, if this fails, a bytearray
+    # is instead returned containing the raw buffer stored for the class name.
+    #
+    # @return The class name as a string or bytearray.  None if a class name
+    #         doesn't exist or an unrecoverable error occurred during retrieval.
     def fetch_classname(self):
         ret_val = None
@@ -234,4 +448,9 @@
         return ret_val
 
+
+    ## Retrieves this key's parent key
+    #
+    # @return The parent's Key instance or None if current key is root 
+    #         (or an error occured) 
     def get_parent(self):
         if self.is_root():
@@ -250,6 +469,47 @@
 # These represent registry values (@ref REGFI_VK records) and provide
 # access to their associated data.
-# 
+#
+# @note Value instances may provide access to more than the attributes
+#       documented here.  However, undocumented attributes may change over time
+#       and are not officially supported.  If you need access to an attribute
+#       not shown here, see pyregfi.structures.
 class Value(_StructureWrapper):
+    ## The raw Value name as an uninterpreted bytearray
+    name_raw = (b"...")
+    
+    ## The name of the Value as a (unicode) string
+    name = "..."
+    
+    ## The absolute file offset of the Value record's cell in the Hive file
+    offset = 0xCAFEBABE
+
+    ## The length of data advertised in the VK record
+    data_size = 0xCAFEBABE
+
+    ## An integer which represents the data type for this Value's data
+    # Typically this value is one of 12 types defined in @ref DATA_TYPES,
+    # but in some cases (the SAM hive) it may be used for other purposes
+    type = DATA_TYPES.NONE
+
+    ## The VK record's flags field
+    flags = 0x10110001
+
+    ## Retrieves the Value's data according to advertised type
+    #
+    # Data is loaded from its cell(s) and then interpreted based on the data
+    # type recorded in the Value.  It is not uncommon for data to be stored with
+    # the wrong type or even with invalid types.  If you have difficulty 
+    # obtaining desired data here, use @ref fetch_raw_data().
+    #
+    # @return The interpreted representation of the data as one of several
+    #         possible Python types, as listed below.  None if any failure 
+    #         occurred during extraction or conversion.
+    #
+    # @retval string for SZ, EXPAND_SZ, and LINK
+    # @retval int for DWORD, DWORD_BE, and QWORD
+    # @retval list(string) for MULTI_SZ
+    # @retval bytearray for NONE, BINARY, RESOURCE_LIST, 
+    #         FULL_RESOURCE_DESCRIPTOR, and RESOURCE_REQUIREMENTS_LIST
+    #
     def fetch_data(self):
         ret_val = None
@@ -261,19 +521,19 @@
         if data_struct.interpreted_size == 0:
             ret_val = None
-        elif data_struct.type in (REG_SZ, REG_EXPAND_SZ, REG_LINK):
+        elif data_struct.type in (DATA_TYPES.SZ, DATA_TYPES.EXPAND_SZ, DATA_TYPES.LINK):
             # Unicode strings
             ret_val = data_struct.interpreted.string.decode('utf-8', 'replace')
-        elif data_struct.type in (REG_DWORD, REG_DWORD_BE):
+        elif data_struct.type in (DATA_TYPES.DWORD, DATA_TYPES.DWORD_BE):
             # 32 bit integers
             ret_val = data_struct.interpreted.dword
-        elif data_struct.type == REG_QWORD:
+        elif data_struct.type == DATA_TYPES.QWORD:
             # 64 bit integers
             ret_val = data_struct.interpreted.qword
-        elif data_struct.type == REG_MULTI_SZ:
+        elif data_struct.type == DATA_TYPES.MULTI_SZ:
             ret_val = _charss2strlist(data_struct.interpreted.multiple_string)
-        elif data_struct.type in (REG_NONE, REG_RESOURCE_LIST,
-                                  REG_FULL_RESOURCE_DESCRIPTOR,
-                                  REG_RESOURCE_REQUIREMENTS_LIST,
-                                  REG_BINARY):
+        elif data_struct.type in (DATA_TYPES.NONE, DATA_TYPES.RESOURCE_LIST,
+                                  DATA_TYPES.FULL_RESOURCE_DESCRIPTOR,
+                                  DATA_TYPES.RESOURCE_REQUIREMENTS_LIST,
+                                  DATA_TYPES.BINARY):
             ret_val = _buffer2bytearray(data_struct.interpreted.none,
                                         data_struct.interpreted_size)
@@ -281,8 +541,12 @@
         regfi.regfi_free_record(data_p)
         return ret_val
-        
+    
+
+    ## Retrieves raw representation of Value's data
+    #
+    # @return A bytearray containing the data
+    #
     def fetch_raw_data(self):
         ret_val = None
-
         # XXX: should we load the data without interpretation instead?
         data_p = regfi.regfi_fetch_data(self._hive.file, self._base)
@@ -294,6 +558,6 @@
                                     data_struct.size)
         regfi.regfi_free_record(data_p)
-
         return ret_val
+
 
     def __getattr__(self, name):
@@ -314,11 +578,10 @@
 # Avoids chicken/egg class definitions.
 # Also makes for convenient code reuse in these lists' parent classes.
-_SubkeyList._constructor = Key
-_ValueList._constructor = Value
+SubkeyList._constructor = Key
+ValueList._constructor = Value
 
 
 
 ## Represents a single registry hive (file)
-#
 class Hive():
     file = None
@@ -326,8 +589,38 @@
     _root = None
 
+    ## The root Key of this Hive
+    root = None
+
+    ## This Hives's last modified time represented as the number of seconds 
+    #  since the UNIX epoch in UTC; similar to what time.time() returns
+    modified = 1300000000.123456
+
+    ## First sequence number
+    sequence1 = 12345678
+
+    ## Second sequence number
+    sequence2 = 12345678
+
+    ## Major version
+    major_version = 1
+
+    ## Minor version
+    minor_version = 5
+
+    # XXX: Possibly add a second or factory function which opens a 
+    #      hive file for you
+
+    ## Constructor
+    #
+    # @param fh A Python file object.  The constructor first looks for a valid
+    #           fileno attribute on this object and uses it if possible.  
+    #           Otherwise, the seek and read methods are used for file
+    #           access.
+    #
+    # @note Supplied file must be seekable
     def __init__(self, fh):
-        # The fileno method may not exist, or it may throw an exception
-        # when called if the file isn't backed with a descriptor.
         try:
+            # The fileno method may not exist, or it may throw an exception
+            # when called if the file isn't backed with a descriptor.
             if hasattr(fh, 'fileno'):
                 self.file = regfi.regfi_alloc(fh.fileno(), REGFI_ENCODING_UTF8)
@@ -342,4 +635,5 @@
         self.file = regfi.regfi_alloc_cb(self.raw_file, REGFI_ENCODING_UTF8)
 
+
     def __getattr__(self, name):
         if name == "root":
@@ -348,5 +642,9 @@
             return self._root
 
+        elif name == "modified":
+            return regfi.regfi_nt2unix_time(byref(self._base.contents.mtime))
+
         return getattr(self.file.contents, name)
+
     
     def __del__(self):
@@ -355,4 +653,5 @@
             self.raw_file = None
 
+
     def __iter__(self):
         return HiveIterator(self)
@@ -360,7 +659,12 @@
 
     ## Creates a @ref HiveIterator initialized at the specified path in
-    #  the hive. 
-    #
-    # Raises an Exception if the path could not be found/traversed.
+    #  the hive.
+    #
+    # @param path A list of Key names which represent an absolute path within
+    #             the Hive
+    #
+    # @return A @ref HiveIterator which is positioned at the specified path.
+    # 
+    # @exception Exception If the path could not be found/traversed
     def subtree(self, path):
         hi = HiveIterator(self)
@@ -416,4 +720,5 @@
 
             if not up_ret:
+                self._iteration_root = None
                 raise StopIteration('')
             
@@ -429,4 +734,17 @@
     next = __next__
 
+    # XXX: Should add sanity checks on some of these traversal functions
+    #      to throw exceptions if a traversal/retrieval *should* have worked
+    #      but failed for some reason.
+
+    ## Descends the iterator to a subkey
+    #
+    # Descends the iterator one level to the current subkey, or a subkey
+    # specified by name.
+    #
+    # @param subkey_name If specified, locates specified subkey by name
+    #                    (via find_subkey()) and descends to it.
+    #
+    # @return True if successful, False otherwise
     def down(self, subkey_name=None):
         if subkey_name == None:
@@ -438,7 +756,21 @@
                     and regfi.regfi_iterator_down(self._iter))
 
+
+    ## Causes the iterator to ascend to the current Key's parent
+    #
+    # @return True if successful, False otherwise
+    #
+    # @note The state of current subkeys and values at this level in the tree
+    #       is lost as a side effect.  That is, if you go up() and then back
+    #       down() again, current_subkey() and current_value() will return 
+    #       default selections.
     def up(self):
         return regfi.regfi_iterator_up(self._iter)
 
+
+    ## Selects first subkey of current key
+    #
+    # @return A Key instance for the first subkey.  
+    #         None on error or if the current key has no subkeys.
     def first_subkey(self):
         if regfi.regfi_iterator_first_subkey(self._iter):
@@ -446,4 +778,9 @@
         return None
 
+
+    ## Selects first value of current Key
+    #
+    # @return A Value instance for the first value.  
+    #         None on error or if the current key has no values.
     def first_value(self):
         if regfi.regfi_iterator_first_value(self._iter):
@@ -451,4 +788,9 @@
         return None
 
+
+    ## Selects the next subkey in the current Key's list
+    #
+    # @return A Key instance for the next subkey.
+    #         None if there are no remaining subkeys or an error occurred.
     def next_subkey(self):
         if regfi.regfi_iterator_next_subkey(self._iter):
@@ -456,4 +798,9 @@
         return None
 
+
+    ## Selects the next value in the current Key's list
+    #  
+    # @return A Value instance for the next value.
+    #         None if there are no remaining values or an error occurred.
     def next_value(self):
         if regfi.regfi_iterator_next_value(self._iter):
@@ -461,4 +808,9 @@
         return None
 
+
+    ## Selects the first subkey which has the specified name
+    #
+    # @return A Key instance for the selected key. 
+    #         None if it could not be located or an error occurred.
     def find_subkey(self, name):
         if name != None:
@@ -468,4 +820,9 @@
         return None
 
+
+    ## Selects the first value which has the specified name
+    #
+    # @return A Value instance for the selected value.
+    #         None if it could not be located or an error occurred.
     def find_value(self, name):
         if name != None:
@@ -475,13 +832,30 @@
         return None
 
+    ## Retrieves the currently selected subkey
+    #
+    # @return A Key instance of the current subkey
     def current_subkey(self):
         return Key(self._hive, regfi.regfi_iterator_cur_subkey(self._iter))
 
+    ## Retrieves the currently selected value
+    #
+    # @return A Value instance of the current value
     def current_value(self):
         return Value(self._hive, regfi.regfi_iterator_cur_value(self._iter))
 
+    ## Retrieves the current key
+    #
+    # @return A Key instance of the current position of the iterator
     def current_key(self):
         return Key(self._hive, regfi.regfi_iterator_cur_key(self._iter))
 
+
+    ## Traverse downward multiple levels
+    #
+    # This is more efficient than calling down() multiple times
+    #
+    # @param path A list of Key names which represent the path to descend
+    #
+    # @exception Exception If path could not be located
     def descend(self, path):
         cpath = _strlist2charss(path)
@@ -490,2 +864,8 @@
         if not regfi.regfi_iterator_walk_path(self._iter, cpath):
             raise Exception('Could not locate path.\n'+GetLogMessages())
+
+
+# Freeing symbols defined for the sake of documentation
+del Value.name,Value.name_raw,Value.offset,Value.data_size,Value.type,Value.flags
+del Key.name,Key.name_raw,Key.offset,Key.modified,Key.flags
+del Hive.root,Hive.modified,Hive.sequence1,Hive.sequence2,Hive.major_version,Hive.minor_version
