Update more API documentation.

This commit is contained in:
Mike Auty
2019-09-04 00:34:55 +01:00
parent a362dc109a
commit c88044db5b
8 changed files with 237 additions and 30 deletions
+11 -2
View File
@@ -88,7 +88,7 @@ class Context(interfaces.context.ContextInterface):
object_type: The name (or template) of the symbol type on which to construct the object. If this is a name, it should contain an explicit table name.
layer_name: The name of the layer on which to construct the object
offset: The offset within the layer at which the data used to create the object lives
native_layer_name: The name of the layer the object references (for pointers) if different to layer_name
Returns:
A fully constructed object
@@ -116,7 +116,16 @@ class Context(interfaces.context.ContextInterface):
offset: int,
native_layer_name: Optional[str] = None,
size: Optional[int] = None) -> interfaces.context.ModuleInterface:
"""Creates a module object"""
"""
Constructs a new os-independent module
Args:
module_name: The name of the module
layer_name: The layer within the context in which the module exists
offset: The offset at which the module exists in the layer
native_layer_name: The default native layer for objects constructed by the module
size: The size, in bytes, that the module occupys from offset location within the layer named layer_name
"""
if size:
return SizedModule(
self,
@@ -60,7 +60,13 @@ class HierarchicalDict(collections.abc.Mapping):
"""
def __init__(self, initial_dict: Dict = None, separator: str = CONFIG_SEPARATOR) -> None:
def __init__(self, initial_dict: Dict[str, 'SimpleTypeRequirement'] = None,
separator: str = CONFIG_SEPARATOR) -> None:
"""
Args:
initial_dict: A dictionary to populate the HierachicalDict with initially
separator: A custom hierarchy separator (defaults to CONFIG_SEPARATOR)
"""
if not (isinstance(separator, str) and len(separator) == 1):
raise TypeError("Separator must be a one character string: {}".format(separator))
self._separator = separator
@@ -107,7 +113,11 @@ class HierarchicalDict(collections.abc.Mapping):
return self.generator()
def generator(self) -> Generator[str, None, None]:
"""A generator for the data in this level and lower levels of this mapping"""
"""A generator for the data in this level and lower levels of this mapping
Returns:
Returns each item in the top level data, and then all subkeys in a depth first order
"""
for key in self._data:
yield key
for subdict_key in self._subdict:
@@ -201,7 +211,11 @@ class HierarchicalDict(collections.abc.Mapping):
Higher layers are not prefixed with the location of earlier layers, so branching a hierarchy containing `a.b.c.d`
on `a.b` would return a hierarchy containing `c.d`, not `a.b.c.d`.
@param key: The location within the hierarchy to return higher layers.
Args:
key: The location within the hierarchy to return higher layers.
Returns:
The HierarchicalDict underneath the specified key (not just the data at that key location in the tree)
"""
try:
if self.separator in key:
@@ -222,27 +236,32 @@ class HierarchicalDict(collections.abc.Mapping):
raise TypeError("Splice requires a string key and HierarchicalDict value")
self._setitem(key, value, False)
def merge(self, key: str, value: 'HierarchicalDict', overwrite: bool = False):
def merge(self, key: str, value: 'HierarchicalDict', overwrite: bool = False) -> None:
"""Acts similarly to splice, but maintains previous values
If overwrite is true, then entries in the new value are used over those that exist within key already
@param key: The location within the hierarchy at which to merge the `value`
@type key: str
@param value: HierarchicalDict to be merged under the key node
@type value: HierarchicalDict
Args:
key: The location within the hierarchy at which to merge the `value`
value: HierarchicalDict to be merged under the key node
overwrite: A boolean defining whether the value will be overwritten if it already exists
"""
if not isinstance(key, str) or not isinstance(value, HierarchicalDict):
raise TypeError("Splice requires a string key and HierarchicalDict value")
for item in dict(value):
if self.get(key + self._separator + item, None):
if self.get(key + self._separator + item, None) is not None:
if overwrite:
self[key + self._separator + item] = value[item]
else:
self[key + self._separator + item] = value[item]
def clone(self) -> 'HierarchicalDict':
"""Duplicates the configuration, allowing changes without affecting the original"""
"""Duplicates the configuration, allowing changes without affecting the original
Returns:
A duplicate HierarchicalDict of this object
"""
return copy.deepcopy(self)
def __str__(self) -> str:
@@ -267,6 +286,14 @@ class RequirementInterface(metaclass = ABCMeta):
description: str = None,
default: Optional[ConfigSimpleType] = None,
optional: bool = False) -> None:
"""
Args:
name: The name of the requirement
description: A short textual description of the requirement
default: The default value for the requirement is none is provided
optional: Whether the requirement must be satisfied or not
"""
super().__init__()
if CONFIG_SEPARATOR in name:
raise ValueError("Name cannot contain the config-hierarchy divider ({})".format(CONFIG_SEPARATOR))
@@ -281,7 +308,8 @@ class RequirementInterface(metaclass = ABCMeta):
@property
def name(self) -> str:
"""The name of the Requirement. Names cannot contain "." since this is used within the configuration hierarchy."""
"""The name of the Requirement. Names cannot contain CONFIG_SEPARATOR ('.' by default) since this
is used within the configuration hierarchy."""
return self._name
@property
@@ -306,7 +334,13 @@ class RequirementInterface(metaclass = ABCMeta):
def config_value(self, context: ContextInterface, config_path: str,
default: ConfigSimpleType = None) -> ConfigSimpleType:
"""Returns the value for this Requirement from its config path"""
"""Returns the value for this Requirement from its config path
Args:
context: the configuration store to find the value for this requirement
config_path: the configuration path of the instance of the requirement to be recovered
default: a default value to provide if the requirement's configuration value is not found
"""
return context.config.get(config_path, default)
# Child operations
@@ -316,15 +350,32 @@ class RequirementInterface(metaclass = ABCMeta):
return self._requirements.copy()
def add_requirement(self, requirement: 'RequirementInterface') -> None:
"""Adds a child to the list of requirements"""
"""Adds a child to the list of requirements
Args:
requirement: The requirement to add as a child-requirement
"""
self._requirements[requirement.name] = requirement
def remove_requirement(self, requirement: 'RequirementInterface') -> None:
"""Removes a child from the list of requirements"""
"""Removes a child from the list of requirements
Args:
requirement: The requirement to remove as a child-requirement
"""
del self._requirements[requirement.name]
def unsatisfied_children(self, context: ContextInterface, config_path: str) -> Dict[str, 'RequirementInterface']:
"""Method that will validate all child requirements"""
"""Method that will validate all child requirements
Args:
context: the context containing the configuration data for this requirement
config_path: the configuration path of this instance of the requirement
Returns:
A dictionary of full configuration paths for each unsatisfied child-requirement
"""
result = {}
for requirement in self.requirements.values():
if not requirement.optional:
@@ -338,6 +389,13 @@ class RequirementInterface(metaclass = ABCMeta):
"""Method to validate the value stored at config_path for the configuration object against a context
Returns a list containing its own name (or multiple unsatisfied requirement names) when invalid
Args:
context: The context object containing the configuration for this requirement
config_path: The configuration path for this requirement to test satisfaction
Returns:
A dictionary of configuration-paths to requirements that could not be satisfied
"""
@@ -377,6 +435,7 @@ class ClassRequirement(RequirementInterface):
@property
def cls(self) -> Type:
"""Contains the actual chosen class based on the configuration value's class name"""
return self._cls
def unsatisfied(self, context: ContextInterface, config_path: str) -> Dict[str, RequirementInterface]:
@@ -417,11 +476,20 @@ class ConstructableRequirementInterface(RequirementInterface):
@abstractmethod
def construct(self, context: ContextInterface, config_path: str) -> None:
"""Method for constructing within the context any required elements from subrequirements"""
"""Method for constructing within the context any required elements from subrequirements
Args:
context: The context object containing the configuration data for the constructable
config_path: The configuration path for the specific instance of this constructable
"""
def _validate_class(self, context: ContextInterface, config_path: str) -> None:
"""Method to check if the class Requirement is valid and if so populate the other requirements
(but no need to validate, since we're invalid already)
Args:
context: The context object containing the configuration data for the constructable
config_path: The configuration path for the specific instance of this constructable
"""
class_req = self.requirements['class']
subreq_config_path = path_join(config_path, self.name)
@@ -493,6 +561,7 @@ class ConfigurableInterface(metaclass = ABCMeta):
@config_path.setter
def config_path(self, value: str) -> None:
"""The configuration path on which this configurable lives"""
self._config_path = value
self._config_cache = None
+59 -6
View File
@@ -11,10 +11,10 @@ import copy
from abc import ABCMeta, abstractmethod
from typing import Optional, Union
from volatility.framework import interfaces, constants
from volatility.framework import interfaces
class ContextInterface(object, metaclass = ABCMeta):
class ContextInterface(metaclass = ABCMeta):
"""All context-like objects must adhere to the following interface.
This interface is present to avoid import dependency cycles.
@@ -68,7 +68,14 @@ class ContextInterface(object, metaclass = ABCMeta):
Looks up the layer_name in the context, finds the object template based on the symbol,
and constructs an object using the object template on the layer at the offset.
Returns a fully constructed object
Args:
object_type: Either a string name of the type, or a Template of the type to be constructed
layer_name: The name of the layer on which to construct the object
offset: The address within the layer at which to construct the object
native_layer_name: The layer this object references (should it be a pointer or similar)
Returns:
A fully constructed object
"""
def clone(self) -> 'ContextInterface':
@@ -84,7 +91,22 @@ class ContextInterface(object, metaclass = ABCMeta):
offset: int,
native_layer_name: Optional[str] = None,
size: Optional[int] = None) -> 'ModuleInterface':
"""Create a module object """
"""Create a module object
A module object is associated with a symbol table, and acts like a context, but offsets locations by a known value
and looks up symbols, by default within the associated symbol table. It can also be sized should that information
be available.
Args:
module_name: The name of the module
layer_name: The layer the module is associated with (which layer the module lives within)
offset: The initial/base offset of the module (used as the offset for relative symbols)
native_layer_name: The default native_layer_name to use when the module constructs objects
size: The size, in bytes, that the module occupys from offset location within the layer named layer_name
Returns:
A module object
"""
class ModuleInterface(metaclass = ABCMeta):
@@ -100,6 +122,17 @@ class ModuleInterface(metaclass = ABCMeta):
offset: int,
symbol_table_name: Optional[str] = None,
native_layer_name: Optional[str] = None) -> None:
"""
Constructs a new os-independent module
Args:
context: The context within which this module will exist
module_name: The name of the module
layer_name: The layer within the context in which the module exists
offset: The offset at which the module exists in the layer
symbol_table_name: The name of an associated symbol table
native_layer_name: The default native layer for objects constructed by the module
"""
self._context = context
self._module_name = module_name
self._layer_name = layer_name
@@ -112,6 +145,7 @@ class ModuleInterface(metaclass = ABCMeta):
@property
def name(self) -> str:
"""The name of the constructed module"""
return self._module_name
@property
@@ -136,7 +170,17 @@ class ModuleInterface(metaclass = ABCMeta):
native_layer_name: Optional[str] = None,
absolute: bool = False,
**kwargs) -> 'interfaces.objects.ObjectInterface':
"""Returns an object created using the symbol_table_name and layer_name of the Module"""
"""Returns an object created using the symbol_table_name and layer_name of the Module
Args:
object_type: The name of object type to construct (using the module's symbol_table)
offset: the offset (unless absolute is set) from the start of the module
native_layer_name: The native layer for objects that reference a different layer (if not the default provided during module construction)
absolute: A boolean specifying whether the offset is absolute within the layer, or relative to the start of the module
Returns:
The constructed object
"""
@abstractmethod
def object_from_symbol(self,
@@ -144,7 +188,16 @@ class ModuleInterface(metaclass = ABCMeta):
native_layer_name: Optional[str] = None,
absolute: bool = False,
**kwargs) -> 'interfaces.objects.ObjectInterface':
"""Returns an object created usnig the symbol_table_name and layer_name of the Module"""
"""Returns an object created using the symbol_table_name and layer_name of the Module
Args:
symbol_name: The name of a symbol (that must be present in the module's symbol table). The symbol's associated type will be used to construct an object at the symbol's offset.
native_layer_name: The native layer for objects that reference a different layer (if not the default provided during module construction)
absolute: A boolean specifying whether the offset is absolute within the layer, or relative to the start of the module
Returns:
The constructed object
"""
def get_type(self, name: str) -> 'interfaces.objects.Template':
"""Returns a type from the module"""
+14 -3
View File
@@ -134,7 +134,15 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
@abstractmethod
def is_valid(self, offset: int, length: int = 1) -> bool:
"""Returns a boolean based on whether the entire chunk of data (from offset to length) is valid or not"""
"""Returns a boolean based on whether the entire chunk of data (from offset to length) is valid or not
Args:
offset: The address to start determining whether bytes are readable/valid
length: The number of bytes from offset of which to test the validity
Returns:
Whether the bytes are valid and accessible
"""
@abstractmethod
def read(self, offset: int, length: int, pad: bool = False) -> bytes:
@@ -161,7 +169,7 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
"""
def destroy(self) -> None:
"""Allows DataLayers to close any open handles, etc.
"""Causes a DataLayer to close any open handles, etc.
Systems that make use of Data Layers should call destroy when they are done with them.
This will close all handles, and make the object unreadable
@@ -175,7 +183,10 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
@property
def dependencies(self) -> List[str]:
"""DataLayers must never define on other layers"""
"""A list of other layer names required by this layer.
Note:
DataLayers must never define other layers"""
return []
# ## General scanning methods
+22 -2
View File
@@ -62,6 +62,15 @@ class ObjectInformation(ReadOnlyMapping):
member_name: Optional[str] = None,
parent: Optional['ObjectInterface'] = None,
native_layer_name: Optional[str] = None):
"""Constructs a container for basic information about an object
Args:
layer_name: Layer from which the data for the object will be read
offset: Offset within the layer at which the data for the object will be read
member_name: If the object was accessed as a member of a parent object, this was the name used to access it
parent: If the object was accessed as a member of a parent object, this is the parent object
native_layer_name: If this object references other objects (such as a pointer), what layer those objects live in
"""
super().__init__({
'layer_name': layer_name,
'offset': offset,
@@ -76,6 +85,13 @@ class ObjectInterface(metaclass = ABCMeta):
def __init__(self, context: 'interfaces_context.ContextInterface', type_name: str, object_info: 'ObjectInformation',
**kwargs) -> None:
"""Constructs an Object adhereing to the ObjectInterface
Args:
context: The context associated with the object
type_name: The name of the type structure for the object
object_info: Basic information relevant to the object (layer, offset, member_name, parent, etc)
"""
# Since objects are likely to be instantiated often,
# we're reliant on type_checking to ensure correctness of context, offset and parent
# Everything else may be wrong, but that will get caught later on
@@ -147,7 +163,11 @@ class ObjectInterface(metaclass = ABCMeta):
return object_template(context = self._context, object_info = object_info)
def has_member(self, member_name: str) -> bool:
"""Returns whether the object would contain a member called member_name"""
"""Returns whether the object would contain a member called member_name
Args:
member_name: Name to test whether a member exists within the type structure
"""
return False
class VolTemplateProxy(metaclass = abc.ABCMeta):
@@ -213,7 +233,7 @@ class Template:
"""
def __init__(self, type_name: str, **arguments) -> None:
"""Stores the keyword arguments for later use"""
"""Stores the keyword arguments for later object creation"""
# Allow the updating of template arguments whilst still in template form
super().__init__()
self._arguments = arguments
+19 -1
View File
@@ -24,6 +24,12 @@ class FileInterface(metaclass = ABCMeta):
"""Class for storing Files in the plugin as a means to output a file or files when necessary"""
def __init__(self, filename: str, data: bytes = None) -> None:
"""
Args:
filename: The requested name of the filename for the data
data: The data to be stored in a file
"""
self.preferred_filename = filename
if data is None:
data = b''
@@ -38,7 +44,11 @@ class FileConsumerInterface(object):
"""
def consume_file(self, file: FileInterface) -> None:
"""Consumes a file as passed back to a UI by a plugin"""
"""Consumes a file as passed back to a UI by a plugin
Args:
file: A FileInterface object with the data to write to a file
"""
#
@@ -70,6 +80,13 @@ class PluginInterface(interfaces_configuration.ConfigurableInterface, metaclass
context: interfaces_context.ContextInterface,
config_path: str,
progress_callback: constants.ProgressCallback = None) -> None:
"""
Args:
context: The context that the plugin will operate within
config_path: The path to configuration data within the context configuration data
progress_callback: A callable that can provide feedback at progress points
"""
super().__init__(context, config_path)
self._progress_callback = progress_callback or (lambda f, s: None)
# Plugins self validate on construction, it makes it more difficult to work with them, but then
@@ -80,6 +97,7 @@ class PluginInterface(interfaces_configuration.ConfigurableInterface, metaclass
self._file_consumer = None # type: Optional[FileConsumerInterface]
def set_file_consumer(self, consumer: FileConsumerInterface) -> None:
"""Sets the file consumer to be used by this plugin"""
self._file_consumer = consumer
def produce_file(self, filedata: FileInterface) -> None:
@@ -192,4 +192,10 @@ class TreeGrid(object, metaclass = ABCMeta):
The order of that the nodes are visited is always depth first, however, the order children are traversed can
be set based on a sort_key function which should accept a node's values and return something that can be
sorted to receive the desired order (similar to the sort/sorted key).
Args:
node: The initial node to be visited
function: The visitor to apply to the nodes under the initial node
initial_accumulator: An accumulator that allows data to be transfered between one visitor call to the next
sort_key: Information about the sort order of columns in order to determine the ordering of results
"""
@@ -20,6 +20,14 @@ class SymbolInterface:
address: int,
type: Optional[objects.Template] = None,
constant_data: Optional[bytes] = None) -> None:
"""
Args:
name: Name of the symbol
address: Numeric address value of the symbol
type: Optional type structure information associated with the symbol
constant_data: Potential constant data the symbol points at
"""
self._name = name
if constants.BANG in self._name:
raise ValueError("Symbol names cannot contain the symbol differentiator ({})".format(constants.BANG))
@@ -55,6 +63,7 @@ class SymbolInterface:
@property
def constant_data(self) -> Optional[bytes]:
"""Returns any constant data associated with the symbol"""
return self._constant_data
@@ -72,6 +81,14 @@ class BaseSymbolTableInterface:
native_types: 'NativeTableInterface',
table_mapping: Optional[Dict[str, str]] = None,
class_types: Optional[Dict[str, Type[objects.ObjectInterface]]] = None) -> None:
"""
Args:
name: Name of the symbol table
native_types: The native symbol table used to resolve any base/native types
table_mapping: A dictionary mapping names of tables (which when present within the table will be changed to the mapped table)
class_types: A dictionary of types and classes that should be instantiated instead of Struct to construct them
"""
self.name = name
if table_mapping is None:
table_mapping = {}
@@ -140,6 +157,10 @@ class BaseSymbolTableInterface:
"""Overrides the object class for a specific Symbol type
Name *must* be present in self.types
Args:
name: The name of the type to override the class for
clazz: The actual class to override for the provided type name
"""
raise NotImplementedError("Abstract method set_type_class not implemented yet.")