From 41ac213a575ff331a5f0c13e5de4cd89466742b2 Mon Sep 17 00:00:00 2001 From: Antoine Viallon Date: Sun, 21 Sep 2025 00:50:32 +0200 Subject: [PATCH 1/2] Add type annotations, remove dead code --- main.py | 9 ++- recuperabit/fs/constants.py | 4 +- recuperabit/fs/core_types.py | 107 +++++++++++++++++++---------------- recuperabit/fs/ntfs.py | 81 +++++++++++++------------- recuperabit/logic.py | 56 +++++++++--------- recuperabit/utils.py | 74 ++++++++++++------------ 6 files changed, 172 insertions(+), 159 deletions(-) diff --git a/main.py b/main.py index f523ae5..f1134e8 100755 --- a/main.py +++ b/main.py @@ -30,6 +30,7 @@ import sys try: import readline + readline # ignore unused import warning except ImportError: pass @@ -37,6 +38,10 @@ # scanners from recuperabit.fs.ntfs import NTFSScanner +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from recuperabit.fs.core_types import Partition + __author__ = "Andrea Lazzarotto" __copyright__ = "(c) 2014-2021, Andrea Lazzarotto" __license__ = "GPLv3" @@ -97,7 +102,7 @@ def check_valid_part(num, parts, shorthands, rebuild=True): return None -def interpret(cmd, arguments, parts, shorthands, outdir): +def interpret(cmd, arguments, parts: dict[int, Partition], shorthands, outdir): """Perform command required by user.""" if cmd == 'help': print('Available commands:') @@ -362,7 +367,7 @@ def main(): pickle.dump(interesting, savefile) # Ask for partitions - parts = {} + parts: dict[int, Partition] = {} for scanner in scanners: parts.update(scanner.get_partitions()) diff --git a/recuperabit/fs/constants.py b/recuperabit/fs/constants.py index 9370a77..69c928d 100644 --- a/recuperabit/fs/constants.py +++ b/recuperabit/fs/constants.py @@ -19,5 +19,5 @@ # along with RecuperaBit. If not, see . -sector_size = 512 -max_sectors = 256 # Maximum block size for recovery +sector_size: int = 512 +max_sectors: int = 256 # Maximum block size for recovery diff --git a/recuperabit/fs/core_types.py b/recuperabit/fs/core_types.py index 87dda78..eb94d2a 100644 --- a/recuperabit/fs/core_types.py +++ b/recuperabit/fs/core_types.py @@ -25,6 +25,8 @@ import logging import os.path +from typing import Optional, Dict, Set, List, Tuple, Union, Any, Iterator +from datetime import datetime from .constants import sector_size @@ -32,49 +34,49 @@ class File(object): - """Filesystem-independent representation of a file.""" - def __init__(self, index, name, size, is_directory=False, - is_deleted=False, is_ghost=False): - self.index = index - self.name = name - self.size = size - self.is_directory = is_directory - self.is_deleted = is_deleted - self.is_ghost = is_ghost - self.parent = None - self.mac = { + """Filesystem-independent representation of a file. Aka Node.""" + def __init__(self, index: Union[int, str], name: str, size: Optional[int], is_directory: bool = False, + is_deleted: bool = False, is_ghost: bool = False) -> None: + self.index: Union[int, str] = index + self.name: str = name + self.size: Optional[int] = size + self.is_directory: bool = is_directory + self.is_deleted: bool = is_deleted + self.is_ghost: bool = is_ghost + self.parent: Optional[Union[int, str]] = None + self.mac: Dict[str, Optional[datetime]] = { 'modification': None, 'access': None, 'creation': None } - self.children = set() - self.children_names = set() # Avoid name clashes breaking restore - self.offset = None # Offset from beginning of disk + self.children: Set['File'] = set() + self.children_names: Set[str] = set() # Avoid name clashes breaking restore + self.offset: Optional[int] = None # Offset from beginning of disk - def set_parent(self, parent): + def set_parent(self, parent: Optional[Union[int, str]]) -> None: """Set a pointer to the parent directory.""" self.parent = parent - def set_mac(self, modification, access, creation): + def set_mac(self, modification: Optional[datetime], access: Optional[datetime], creation: Optional[datetime]) -> None: """Set the modification, access and creation times.""" self.mac['modification'] = modification self.mac['access'] = access self.mac['creation'] = creation - def get_mac(self): + def get_mac(self) -> List[Optional[datetime]]: """Get the modification, access and creation times.""" keys = ('modification', 'access', 'creation') return [self.mac[k] for k in keys] - def set_offset(self, offset): + def set_offset(self, offset: Optional[int]) -> None: """Set the offset of the file record with respect to the disk image.""" self.offset = offset - def get_offset(self): + def get_offset(self) -> Optional[int]: """Get the offset of the file record with respect to the disk image.""" return self.offset - def add_child(self, node): + def add_child(self, node: 'File') -> None: """Add a new child to this directory.""" original_name = node.name i = 0 @@ -90,7 +92,7 @@ def add_child(self, node): self.children.add(node) self.children_names.add(node.name) - def full_path(self, part): + def full_path(self, part: 'Partition') -> str: """Return the full path of this file.""" if self.parent is not None: parent = part[self.parent] @@ -98,7 +100,7 @@ def full_path(self, part): else: return self.name - def get_content(self, partition): + def get_content(self, partition: 'Partition') -> Optional[Union[bytes, Iterator[bytes]]]: # pylint: disable=W0613 """Extract the content of the file. @@ -109,14 +111,14 @@ def get_content(self, partition): raise NotImplementedError # pylint: disable=R0201 - def ignore(self): + def ignore(self) -> bool: """The following method is used by the restore procedure to check files that should not be recovered. For example, in NTFS file $BadClus:$Bad shall not be recovered because it creates an output with the same size as the partition (usually many GBs).""" return False - def __repr__(self): + def __repr__(self) -> str: return ( u'File(#%s, ^^%s^^, %s, offset = %s sectors)' % (self.index, self.parent, self.name, self.offset) @@ -128,42 +130,42 @@ class Partition(object): Parameter root_id represents the identifier assigned to the root directory of a partition. This can be file system dependent.""" - def __init__(self, fs_type, root_id, scanner): - self.fs_type = fs_type - self.root_id = root_id - self.size = None - self.offset = None - self.root = None - self.lost = File(-1, 'LostFiles', 0, is_directory=True, is_ghost=True) - self.files = {} - self.recoverable = False - self.scanner = scanner - - def add_file(self, node): + def __init__(self, fs_type: str, root_id: Union[int, str], scanner: 'DiskScanner') -> None: + self.fs_type: str = fs_type + self.root_id: Union[int, str] = root_id + self.size: Optional[int] = None + self.offset: Optional[int] = None + self.root: Optional[File] = None + self.lost: File = File(-1, 'LostFiles', 0, is_directory=True, is_ghost=True) + self.files: Dict[Union[int, str], File] = {} + self.recoverable: bool = False + self.scanner: 'DiskScanner' = scanner + + def add_file(self, node: File) -> None: """Insert a new file in the partition.""" index = node.index self.files[index] = node - def set_root(self, node): + def set_root(self, node: File) -> None: """Set the root directory.""" if not node.is_directory: raise TypeError('Not a directory') self.root = node self.root.set_parent(None) - def set_size(self, size): + def set_size(self, size: int) -> None: """Set the (estimated) size of the partition.""" self.size = size - def set_offset(self, offset): + def set_offset(self, offset: int) -> None: """Set the offset from the beginning of the disk.""" self.offset = offset - def set_recoverable(self, recoverable): + def set_recoverable(self, recoverable: bool) -> None: """State if the partition contents are also recoverable.""" self.recoverable = recoverable - def rebuild(self): + def rebuild(self) -> None: """Rebuild the partition structure. This method processes the contents of files and it rebuilds the @@ -201,11 +203,11 @@ def rebuild(self): return # pylint: disable=R0201 - def additional_repr(self): + def additional_repr(self) -> List[Tuple[str, Any]]: """Return additional values to show in the string representation.""" return [] - def __repr__(self): + def __repr__(self) -> str: size = ( readable_bytes(self.size * sector_size) if self.size is not None else '??? b' @@ -227,14 +229,14 @@ def __repr__(self): ', '.join(a+': '+str(b) for a, b in data) ) - def __getitem__(self, index): + def __getitem__(self, index: Union[int, str]) -> File: if index in self.files: return self.files[index] if index == self.lost.index: return self.lost raise KeyError - def get(self, index, default=None): + def get(self, index: Union[int, str], default: Optional[File] = None) -> Optional[File]: """Get a file or the special LostFiles directory.""" try: return self.__getitem__(index) @@ -244,17 +246,22 @@ def get(self, index, default=None): class DiskScanner(object): """Abstract stub for the implementation of disk scanners.""" - def __init__(self, pointer): - self.image = pointer + def __init__(self, pointer: Any) -> None: + self.image: Any = pointer - def get_image(self): + def get_image(self) -> Any: """Return the image reference.""" return self.image - def feed(self, index, sector): + @staticmethod + def get_image(scanner: 'DiskScanner') -> Any: + """Static method to get image from scanner instance.""" + return scanner.image + + def feed(self, index: int, sector: bytes) -> Optional[str]: """Feed a new sector.""" raise NotImplementedError - def get_partitions(self): + def get_partitions(self) -> Dict[int, Partition]: """Get a list of the found partitions.""" raise NotImplementedError diff --git a/recuperabit/fs/ntfs.py b/recuperabit/fs/ntfs.py index f0e820c..20086fe 100644 --- a/recuperabit/fs/ntfs.py +++ b/recuperabit/fs/ntfs.py @@ -24,6 +24,7 @@ import logging from collections import Counter +from typing import Any, Dict, List, Optional, Tuple, Union, Iterator, Set from .constants import max_sectors, sector_size from .core_types import DiskScanner, File, Partition @@ -36,7 +37,7 @@ from ..utils import merge, sectors, unpack # Some attributes may appear multiple times -multiple_attributes = set([ +multiple_attributes: Set[str] = set([ '$FILE_NAME', '$DATA', '$INDEX_ROOT', @@ -45,11 +46,11 @@ ]) # Size of records in sectors -FILE_size = 2 -INDX_size = 8 +FILE_size: int = 2 +INDX_size: int = 8 -def best_name(entries): +def best_name(entries: List[Tuple[int, str]]) -> Optional[str]: """Return the best file name available. This function accepts a list of tuples formed by a namespace and a string. @@ -66,7 +67,7 @@ def best_name(entries): return name if len(name) else None -def parse_mft_attr(attr): +def parse_mft_attr(attr: bytes) -> Tuple[Dict[str, Any], Optional[str]]: """Parse the contents of a MFT attribute.""" header = unpack(attr, attr_header_fmt) attr_type = header['type'] @@ -94,7 +95,7 @@ def parse_mft_attr(attr): return header, name -def _apply_fixup_values(header, entry): +def _apply_fixup_values(header: Dict[str, Any], entry: bytearray) -> None: """Apply the fixup values to FILE and INDX records.""" offset = header['off_fixup'] for i in range(1, header['n_entries']): @@ -102,7 +103,7 @@ def _apply_fixup_values(header, entry): entry[pos-2:pos] = entry[offset + 2*i:offset + 2*(i+1)] -def _attributes_reader(entry, offset): +def _attributes_reader(entry: bytes, offset: int) -> Dict[str, Any]: """Read every attribute.""" attributes = {} while offset < len(entry) - 16: @@ -133,7 +134,7 @@ def _attributes_reader(entry, offset): return attributes -def parse_file_record(entry): +def parse_file_record(entry: bytes) -> Dict[str, Any]: """Parse the contents of a FILE record (MFT entry).""" header = unpack(entry, entry_fmt) if (header['size_alloc'] is None or @@ -154,7 +155,7 @@ def parse_file_record(entry): return header -def parse_indx_record(entry): +def parse_indx_record(entry: bytes) -> Dict[str, Any]: """Parse the contents of a INDX record (directory index).""" header = unpack(entry, indx_fmt) @@ -200,7 +201,7 @@ def parse_indx_record(entry): return header -def _integrate_attribute_list(parsed, part, image): +def _integrate_attribute_list(parsed: Dict[str, Any], part: 'NTFSPartition', image: Any) -> None: """Integrate missing attributes in the parsed MTF entry.""" base_record = parsed['record_n'] attrs = parsed['attributes'] @@ -264,7 +265,7 @@ def _integrate_attribute_list(parsed, part, image): class NTFSFile(File): """NTFS File.""" - def __init__(self, parsed, offset, is_ghost=False, ads=''): + def __init__(self, parsed: Dict[str, Any], offset: Optional[int], is_ghost: bool = False, ads: str = '') -> None: index = parsed['record_n'] ads_suffix = ':' + ads if ads != '' else ads if ads != '': @@ -322,7 +323,7 @@ def __init__(self, parsed, offset, is_ghost=False, ads=''): self.ads = ads @staticmethod - def _padded_bytes(image, offset, size): + def _padded_bytes(image: Any, offset: int, size: int) -> bytes: dump = sectors(image, offset, size, 1) if len(dump) < size: logging.warning( @@ -331,7 +332,7 @@ def _padded_bytes(image, offset, size): dump += bytearray(b'\x00' * (size - len(dump))) return dump - def content_iterator(self, partition, image, datas): + def content_iterator(self, partition: 'NTFSPartition', image: Any, datas: List[Dict[str, Any]]) -> Iterator[bytes]: """Return an iterator for the contents of this file.""" vcn = 0 spc = partition.sec_per_clus @@ -378,7 +379,7 @@ def content_iterator(self, partition, image, datas): yield bytes(partial) vcn = attr['end_VCN'] + 1 - def get_content(self, partition): + def get_content(self, partition: 'NTFSPartition') -> Optional[Union[bytes, Iterator[bytes]]]: """Extract the content of the file. This method works by extracting the $DATA attribute.""" @@ -439,7 +440,7 @@ def get_content(self, partition): ) return self.content_iterator(partition, image, non_resident) - def ignore(self): + def ignore(self) -> bool: """Determine which files should be ignored.""" return ( (self.index == '8:$Bad') or @@ -449,13 +450,13 @@ def ignore(self): class NTFSPartition(Partition): """Partition with additional fields for NTFS recovery.""" - def __init__(self, scanner, position=None): + def __init__(self, scanner: 'NTFSScanner', position: Optional[int] = None) -> None: Partition.__init__(self, 'NTFS', 5, scanner) - self.sec_per_clus = None - self.mft_pos = position - self.mftmirr_pos = None + self.sec_per_clus: Optional[int] = None + self.mft_pos: Optional[int] = position + self.mftmirr_pos: Optional[int] = None - def additional_repr(self): + def additional_repr(self) -> List[Tuple[str, Any]]: """Return additional values to show in the string representation.""" return [ ('Sec/Clus', self.sec_per_clus), @@ -466,17 +467,17 @@ def additional_repr(self): class NTFSScanner(DiskScanner): """NTFS Disk Scanner.""" - def __init__(self, pointer): + def __init__(self, pointer: Any) -> None: DiskScanner.__init__(self, pointer) - self.found_file = set() - self.parsed_file_review = {} - self.found_indx = set() - self.parsed_indx = {} - self.indx_list = None - self.found_boot = [] - self.found_spc = [] - - def feed(self, index, sector): + self.found_file: Set[int] = set() + self.parsed_file_review: Dict[int, Dict[str, Any]] = {} + self.found_indx: Set[int] = set() + self.parsed_indx: Dict[int, Dict[str, Any]] = {} + self.indx_list: Optional[SparseList[int]] = None + self.found_boot: List[int] = [] + self.found_spc: List[int] = [] + + def feed(self, index: int, sector: bytes) -> Optional[str]: """Feed a new sector.""" # check boot sector if sector.endswith(b'\x55\xAA') and b'NTFS' in sector[:8]: @@ -494,7 +495,7 @@ def feed(self, index, sector): return 'NTFS index record' @staticmethod - def add_indx_entries(entries, part): + def add_indx_entries(entries: List[Dict[str, Any]], part: NTFSPartition) -> None: """Insert new ghost files which were not already found.""" for rec in entries: if (rec['record_n'] not in part.files and @@ -512,7 +513,7 @@ def add_indx_entries(entries, part): rec['flags'] = 0x1 part.add_file(NTFSFile(rec, None, is_ghost=True)) - def add_from_indx_root(self, parsed, part): + def add_from_indx_root(self, parsed: Dict[str, Any], part: NTFSPartition) -> None: """Add ghost entries to part from INDEX_ROOT attributes in parsed.""" for attribute in parsed['attributes']['$INDEX_ROOT']: if (attribute.get('content') is None or @@ -520,7 +521,7 @@ def add_from_indx_root(self, parsed, part): continue self.add_indx_entries(attribute['content']['records'], part) - def most_likely_sec_per_clus(self): + def most_likely_sec_per_clus(self) -> List[int]: """Determine the most likely value of sec_per_clus of each partition, to speed up the search.""" counter = Counter() @@ -528,7 +529,7 @@ def most_likely_sec_per_clus(self): counter.update(2**i for i in range(8)) return [i for i, _ in counter.most_common()] - def find_boundary(self, part, mft_address, multipliers): + def find_boundary(self, part: NTFSPartition, mft_address: int, multipliers: List[int]) -> Tuple[Optional[int], Optional[int]]: """Determine the starting sector of a partition with INDX records.""" nodes = ( self.parsed_file_review[node.offset] @@ -593,7 +594,7 @@ def find_boundary(self, part, mft_address, multipliers): else: return (None, None) - def add_from_indx_allocation(self, parsed, part): + def add_from_indx_allocation(self, parsed: Dict[str, Any], part: NTFSPartition) -> None: """Add ghost entries to part from INDEX_ALLOCATION attributes in parsed. This procedure requires that the beginning of the partition has already @@ -625,7 +626,7 @@ def add_from_indx_allocation(self, parsed, part): entries = parse_indx_record(dump)['entries'] self.add_indx_entries(entries, part) - def add_from_attribute_list(self, parsed, part, offset): + def add_from_attribute_list(self, parsed: Dict[str, Any], part: NTFSPartition, offset: int) -> None: """Add additional entries to part from attributes in ATTRIBUTE_LIST. Files with many attributes may have additional attributes not in the @@ -643,7 +644,7 @@ def add_from_attribute_list(self, parsed, part, offset): if ads_name and len(ads_name): part.add_file(NTFSFile(parsed, offset, ads=ads_name)) - def add_from_mft_mirror(self, part): + def add_from_mft_mirror(self, part: NTFSPartition) -> None: """Fix the first file records using the MFT mirror.""" img = DiskScanner.get_image(self) mirrpos = part.mftmirr_pos @@ -664,7 +665,7 @@ def add_from_mft_mirror(self, part): '%s from backup', node.index, node.name, part.offset ) - def finalize_reconstruction(self, part): + def finalize_reconstruction(self, part: NTFSPartition) -> None: """Finish information gathering from a file. This procedure requires that the beginning of the @@ -693,9 +694,9 @@ def finalize_reconstruction(self, part): parsed = self.parsed_file_review[node.offset] self.add_from_indx_allocation(parsed, part) - def get_partitions(self): + def get_partitions(self) -> Dict[int, NTFSPartition]: """Get a list of the found partitions.""" - partitioned_files = {} + partitioned_files: Dict[int, NTFSPartition] = {} img = DiskScanner.get_image(self) logging.info('Parsing MFT entries') diff --git a/recuperabit/logic.py b/recuperabit/logic.py index e97052b..c4f10c2 100644 --- a/recuperabit/logic.py +++ b/recuperabit/logic.py @@ -27,30 +27,34 @@ import sys import time import types +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, Iterator, Set, Tuple, TypeVar, Generic -from .utils import tiny_repr +T = TypeVar('T') +if TYPE_CHECKING: + from .fs.core_types import File, Partition -class SparseList(object): + +class SparseList(Generic[T]): """List which only stores values at some places.""" - def __init__(self, data=None, default=None): - self.keys = [] # This is always kept in order - self.elements = {} - self.default = default + def __init__(self, data: Optional[Dict[int, T]] = None, default: Optional[T] = None) -> None: + self.keys: List[int] = [] # This is always kept in order + self.elements: Dict[int, T] = {} + self.default: Optional[T] = default if data is not None: self.keys = sorted(data) self.elements.update(data) - def __len__(self): + def __len__(self) -> int: try: return self.keys[-1] + 1 except IndexError: return 0 - def __getitem__(self, index): + def __getitem__(self, index: int) -> Optional[T]: return self.elements.get(index, self.default) - def __setitem__(self, index, item): + def __setitem__(self, index: int, item: T) -> None: if item == self.default: if index in self.elements: del self.elements[index] @@ -60,18 +64,18 @@ def __setitem__(self, index, item): bisect.insort(self.keys, index) self.elements[index] = item - def __contains__(self, element): + def __contains__(self, element: T) -> bool: return element in self.elements.values() - def __iter__(self): + def __iter__(self) -> Iterator[int]: return self.keys.__iter__() - def __repr__(self): + def __repr__(self) -> str: elems = [] prevk = 0 if len(self.elements) > 0: k = self.keys[0] - elems.append(str(k) + ' -> ' + tiny_repr(self.elements[k])) + elems.append(str(k) + ' -> ' + repr(self.elements[k])) prevk = self.keys[0] for i in range(1, len(self.elements)): nextk = self.keys[i] @@ -79,31 +83,31 @@ def __repr__(self): while prevk < nextk - 1: elems.append('__') prevk += 1 - elems.append(tiny_repr(self.elements[nextk])) + elems.append(repr(self.elements[nextk])) else: elems.append('\n... ' + str(nextk) + ' -> ' + - tiny_repr(self.elements[nextk])) + repr(self.elements[nextk])) prevk = nextk return '[' + ', '.join(elems) + ']' - def iterkeys(self): + def iterkeys(self) -> Iterator[int]: """An iterator over the keys of actual elements.""" return self.__iter__() - def iterkeys_rev(self): + def iterkeys_rev(self) -> Iterator[int]: """An iterator over the keys of actual elements (reversed).""" i = len(self.keys) while i > 0: i -= 1 yield self.keys[i] - def itervalues(self): + def itervalues(self) -> Iterator[T]: """An iterator over the elements.""" for k in self.keys: yield self.elements[k] - def wipe_interval(self, bottom, top): + def wipe_interval(self, bottom: int, top: int) -> None: """Remove elements between bottom and top.""" new_keys = set() if bottom > top: @@ -121,12 +125,12 @@ def wipe_interval(self, bottom, top): self.keys = sorted(new_keys) -def preprocess_pattern(pattern): +def preprocess_pattern(pattern: SparseList[T]) -> Dict[T, List[int]]: """Preprocess a SparseList for approximate string matching. This function performs preprocessing for the Baeza-Yates--Perleberg fast and practical approximate string matching algorithm.""" - result = {} + result: Dict[T, List[int]] = {} length = pattern.__len__() for k in pattern: name = pattern[k] @@ -137,7 +141,7 @@ def preprocess_pattern(pattern): return result -def approximate_matching(records, pattern, stop, k=1): +def approximate_matching(records: SparseList[T], pattern: SparseList[T], stop: int, k: int = 1) -> Optional[List[Union[Set[int], int, float]]]: """Find the best match for a given pattern. The Baeza-Yates--Perleberg algorithm requires a preprocessed pattern. This @@ -152,8 +156,8 @@ def approximate_matching(records, pattern, stop, k=1): return None lookup = preprocess_pattern(pattern) - count = SparseList(default=0) - match_offsets = set() + count: SparseList[int] = SparseList(default=0) + match_offsets: Set[int] = set() i = 0 j = 0 # previous value of i @@ -192,7 +196,7 @@ def approximate_matching(records, pattern, stop, k=1): return None -def makedirs(path): +def makedirs(path: str) -> bool: """Make directories if they do not exist.""" try: os.makedirs(path) @@ -205,7 +209,7 @@ def makedirs(path): return True -def recursive_restore(node, part, outputdir, make_dirs=True): +def recursive_restore(node: 'File', part: 'Partition', outputdir: str, make_dirs: bool = True) -> None: """Restore a directory structure starting from a file node.""" parent_path = str( part[node.parent].full_path(part) if node.parent is not None diff --git a/recuperabit/utils.py b/recuperabit/utils.py index 3ee1424..0303390 100644 --- a/recuperabit/utils.py +++ b/recuperabit/utils.py @@ -19,25 +19,31 @@ # along with RecuperaBit. If not, see . +from datetime import datetime import logging import pprint import string import sys import time +from typing import TYPE_CHECKING, Any, Optional, List, Dict, Tuple, Union, Callable import unicodedata +import io from .fs.constants import sector_size -printer = pprint.PrettyPrinter(indent=4) +printer: pprint.PrettyPrinter = pprint.PrettyPrinter(indent=4) all_chars = (chr(i) for i in range(sys.maxunicode)) -unicode_printable = set( +unicode_printable: set[str] = set( c for c in all_chars if not unicodedata.category(c)[0].startswith('C') ) -ascii_printable = set(string.printable[:-5]) +ascii_printable: set[str] = set(string.printable[:-5]) +if TYPE_CHECKING: + from .fs.core_types import File, Partition -def sectors(image, offset, size, bsize=sector_size, fill=True): + +def sectors(image: io.BufferedReader, offset: int, size: int, bsize: int = sector_size, fill: bool = True) -> Optional[bytearray]: """Read from a file descriptor.""" read = True try: @@ -60,7 +66,7 @@ def sectors(image, offset, size, bsize=sector_size, fill=True): return None return bytearray(dump) -def unixtime(dtime): +def unixtime(dtime: Optional[datetime]) -> int: """Convert datetime to UNIX epoch.""" if dtime is None: return 0 @@ -72,9 +78,9 @@ def unixtime(dtime): # format: # [(label, (formatter, lower, higher)), ...] -def unpack(data, fmt): +def unpack(data: bytes, fmt: List[Tuple[str, Tuple[Union[str, Callable[[bytes], Any]], Union[int, Callable[[Dict[str, Any]], Optional[int]]], Union[int, Callable[[Dict[str, Any]], Optional[int]]]]]]) -> Dict[str, Any]: """Extract formatted information from a string of bytes.""" - result = {} + result: Dict[str, Any] = {} for label, description in fmt: formatter, lower, higher = description # If lower is a function, then apply it @@ -112,9 +118,9 @@ def unpack(data, fmt): return result -def feed_all(image, scanners, indexes): +def feed_all(image: io.BufferedReader, scanners: List[Any], indexes: List[int]) -> List[int]: # Scan the disk image and feed the scanners - interesting = [] + interesting: List[int] = [] for index in indexes: sector = sectors(image, index, 1, fill=False) if not sector: @@ -128,29 +134,19 @@ def feed_all(image, scanners, indexes): return interesting -def printable(text, default='.', alphabet=None): +def printable(text: str, default: str = '.', alphabet: Optional[set[str]] = None) -> str: """Replace unprintable characters in a text with a default one.""" if alphabet is None: alphabet = unicode_printable return ''.join((i if i in alphabet else default) for i in text) -def pretty(dictionary): - """Format dictionary with the pretty printer.""" - return printer.pformat(dictionary) -def show(dictionary): - """Print dictionary with the pretty printer.""" - printer.pprint(dictionary) -def tiny_repr(element): - """deprecated: Return a representation of unicode strings without the u.""" - rep = repr(element) - return rep[1:] if type(element) == unicode else rep -def readable_bytes(amount): +def readable_bytes(amount: Optional[int]) -> str: """Return a human readable string representing a size in bytes.""" if amount is None: return '??? B' @@ -164,7 +160,7 @@ def readable_bytes(amount): return '%.2f %sB' % (scaled, powers[biggest]) -def _file_tree_repr(node): +def _file_tree_repr(node: 'File') -> str: """Give a nice representation for the tree.""" desc = ( ' [GHOST]' if node.is_ghost else @@ -188,9 +184,9 @@ def _file_tree_repr(node): ) -def tree_folder(directory, padding=0): +def tree_folder(directory: 'File', padding: int = 0) -> str: """Return a tree-like textual representation of a directory.""" - lines = [] + lines: List[str] = [] pad = ' ' * padding lines.append( pad + _file_tree_repr(directory) @@ -207,7 +203,7 @@ def tree_folder(directory, padding=0): return '\n'.join(lines) -def _bodyfile_repr(node, path): +def _bodyfile_repr(node: 'File', path: str) -> str: """Return a body file line for node.""" end = '/' if node.is_directory or len(node.children) else '' return '|'.join(str(el) for el in [ @@ -223,13 +219,13 @@ def _bodyfile_repr(node, path): ]) -def bodyfile_folder(directory, path=''): +def bodyfile_folder(directory: 'File', path: str = '') -> List[str]: """Create a body file compatible with TSK 3.x. Format: '#MD5|name|inode|mode_as_string|UID|GID|size|atime|mtime|ctime|crtime' See also: http://wiki.sleuthkit.org/index.php?title=Body_file""" - lines = [_bodyfile_repr(directory, path)] + lines: List[str] = [_bodyfile_repr(directory, path)] path += directory.name + '/' for entry in directory.children: if len(entry.children) or entry.is_directory: @@ -239,7 +235,7 @@ def bodyfile_folder(directory, path=''): return lines -def _ltx_clean(label): +def _ltx_clean(label: Any) -> str: """Small filter to prepare strings to be included in LaTeX code.""" clean = str(label).replace('$', r'\$').replace('_', r'\_') if clean[0] == '-': @@ -247,7 +243,7 @@ def _ltx_clean(label): return clean -def _tikz_repr(node): +def _tikz_repr(node: 'File') -> str: """Represent the node for a Tikz diagram.""" return r'node %s{%s\enskip{}%s}' % ( '[ghost]' if node.is_ghost else '[deleted]' if node.is_deleted else '', @@ -255,11 +251,11 @@ def _tikz_repr(node): ) -def tikz_child(directory, padding=0): +def tikz_child(directory: 'File', padding: int = 0) -> Tuple[str, int]: """Write a child row for Tikz representation.""" pad = ' ' * padding - lines = [r'%schild {%s' % (pad, _tikz_repr(directory))] - count = len(directory.children) + lines: List[str] = [r'%schild {%s' % (pad, _tikz_repr(directory))] + count: int = len(directory.children) for entry in directory.children: content, number = tikz_child(entry, padding+4) lines.append(content) @@ -270,7 +266,7 @@ def tikz_child(directory, padding=0): return '\n'.join(lines).replace('\n}', '}'), count -def tikz_part(part): +def tikz_part(part: 'Partition') -> str: """Create LaTeX code to represent the directory structure as a nice Tikz diagram. @@ -296,7 +292,7 @@ def tikz_part(part): ) -def csv_part(part): +def csv_part(part: 'Partition') -> list[str]: """Provide a CSV representation for a partition.""" contents = [ ','.join(('Id', 'Parent', 'Name', 'Full Path', 'Modification Time', @@ -324,9 +320,9 @@ def csv_part(part): return contents -def _sub_locate(text, directory, part): +def _sub_locate(text: str, directory: 'File', part: 'Partition') -> List[Tuple['File', str]]: """Helper for locate.""" - lines = [] + lines: List[Tuple['File', str]] = [] for entry in sorted(directory.children, key=lambda node: node.name): path = entry.full_path(part) if text in path.lower(): @@ -336,16 +332,16 @@ def _sub_locate(text, directory, part): return lines -def locate(part, text): +def locate(part: 'Partition', text: str) -> List[Tuple['File', str]]: """Return paths of files matching the text.""" - lines = [] + lines: List[Tuple['File', str]] = [] text = text.lower() lines += _sub_locate(text, part.lost, part) lines += _sub_locate(text, part.root, part) return lines -def merge(part, piece): +def merge(part: 'Partition', piece: 'Partition') -> None: """Merge piece into part (both are partitions).""" for index in piece.files: if ( From 476a5e832c840f856e6af620be84ded926c7cfc2 Mon Sep 17 00:00:00 2001 From: Antoine Viallon Date: Sun, 28 Sep 2025 16:09:12 +0200 Subject: [PATCH 2/2] feat: init tests --- main.py | 2 +- recuperabit/fs/ntfs.py | 14 +- recuperabit/utils.py | 2 +- tests/__init__.py | 1 + .../deep/nested/directory/deep_file.txt | 1 + tests/data/reference_files/empty_file.empty | 0 tests/data/reference_files/file_with_ads.txt | 1 + tests/data/reference_files/large_file.dat | 1 + tests/data/reference_files/medium_binary.bin | Bin 0 -> 4096 bytes tests/data/reference_files/small_text.txt | 1 + .../subdirectory/subdir_file1.txt | 1 + .../subdirectory/subdir_file2.bin | Bin 0 -> 1024 bytes ...name_\321\204\320\260\320\271\320\273.txt" | 1 + tests/data/reference_ntfs.img.gz | Bin 0 -> 231997 bytes tests/data/reference_ntfs.json | 20 + tests/reference_image.py | 168 +++++++++ tests/run_tests.py | 125 +++++++ tests/test_integration.py | 347 ++++++++++++++++++ tests/test_ntfs_e2e.py | 251 +++++++++++++ tests/test_ntfs_unit.py | 297 +++++++++++++++ tools/build_reference_ntfs.py | 289 +++++++++++++++ 21 files changed, 1516 insertions(+), 6 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/data/reference_files/deep/nested/directory/deep_file.txt create mode 100644 tests/data/reference_files/empty_file.empty create mode 100644 tests/data/reference_files/file_with_ads.txt create mode 100644 tests/data/reference_files/large_file.dat create mode 100644 tests/data/reference_files/medium_binary.bin create mode 100644 tests/data/reference_files/small_text.txt create mode 100644 tests/data/reference_files/subdirectory/subdir_file1.txt create mode 100644 tests/data/reference_files/subdirectory/subdir_file2.bin create mode 100644 "tests/data/reference_files/unicode_name_\321\204\320\260\320\271\320\273.txt" create mode 100644 tests/data/reference_ntfs.img.gz create mode 100644 tests/data/reference_ntfs.json create mode 100644 tests/reference_image.py create mode 100644 tests/run_tests.py create mode 100644 tests/test_integration.py create mode 100644 tests/test_ntfs_e2e.py create mode 100644 tests/test_ntfs_unit.py create mode 100755 tools/build_reference_ntfs.py diff --git a/main.py b/main.py index f1134e8..07de873 100755 --- a/main.py +++ b/main.py @@ -102,7 +102,7 @@ def check_valid_part(num, parts, shorthands, rebuild=True): return None -def interpret(cmd, arguments, parts: dict[int, Partition], shorthands, outdir): +def interpret(cmd, arguments, parts: dict[int, 'Partition'], shorthands, outdir): """Perform command required by user.""" if cmd == 'help': print('Available commands:') diff --git a/recuperabit/fs/ntfs.py b/recuperabit/fs/ntfs.py index 20086fe..a168404 100644 --- a/recuperabit/fs/ntfs.py +++ b/recuperabit/fs/ntfs.py @@ -67,8 +67,10 @@ def best_name(entries: List[Tuple[int, str]]) -> Optional[str]: return name if len(name) else None -def parse_mft_attr(attr: bytes) -> Tuple[Dict[str, Any], Optional[str]]: +def parse_mft_attr(attr: Union[bytes, bytearray]) -> Tuple[Dict[str, Any], Optional[str]]: """Parse the contents of a MFT attribute.""" + assert isinstance(attr, (bytes, bytearray)), f"attr must be bytes or bytearray, got {type(attr)}" + header = unpack(attr, attr_header_fmt) attr_type = header['type'] @@ -103,7 +105,7 @@ def _apply_fixup_values(header: Dict[str, Any], entry: bytearray) -> None: entry[pos-2:pos] = entry[offset + 2*i:offset + 2*(i+1)] -def _attributes_reader(entry: bytes, offset: int) -> Dict[str, Any]: +def _attributes_reader(entry: Union[bytes, bytearray], offset: int) -> Dict[str, Any]: """Read every attribute.""" attributes = {} while offset < len(entry) - 16: @@ -134,8 +136,10 @@ def _attributes_reader(entry: bytes, offset: int) -> Dict[str, Any]: return attributes -def parse_file_record(entry: bytes) -> Dict[str, Any]: +def parse_file_record(entry: bytearray | bytes) -> Dict[str, Any]: """Parse the contents of a FILE record (MFT entry).""" + assert isinstance(entry, (bytearray, bytes)), f"entry must be bytearray or bytes, got {type(entry)}" + header = unpack(entry, entry_fmt) if (header['size_alloc'] is None or header['size_alloc'] > len(entry) or @@ -155,8 +159,10 @@ def parse_file_record(entry: bytes) -> Dict[str, Any]: return header -def parse_indx_record(entry: bytes) -> Dict[str, Any]: +def parse_indx_record(entry: bytearray | bytes) -> Dict[str, Any]: """Parse the contents of a INDX record (directory index).""" + assert isinstance(entry, (bytearray, bytes)), f"entry must be bytearray or bytes, got {type(entry)}" + header = unpack(entry, indx_fmt) _apply_fixup_values(header, entry) diff --git a/recuperabit/utils.py b/recuperabit/utils.py index 0303390..45baaa5 100644 --- a/recuperabit/utils.py +++ b/recuperabit/utils.py @@ -78,7 +78,7 @@ def unixtime(dtime: Optional[datetime]) -> int: # format: # [(label, (formatter, lower, higher)), ...] -def unpack(data: bytes, fmt: List[Tuple[str, Tuple[Union[str, Callable[[bytes], Any]], Union[int, Callable[[Dict[str, Any]], Optional[int]]], Union[int, Callable[[Dict[str, Any]], Optional[int]]]]]]) -> Dict[str, Any]: +def unpack(data: bytes | bytearray, fmt: List[Tuple[str, Tuple[Union[str, Callable[[bytes], Any]], Union[int, Callable[[Dict[str, Any]], Optional[int]]], Union[int, Callable[[Dict[str, Any]], Optional[int]]]]]]) -> Dict[str, Any]: """Extract formatted information from a string of bytes.""" result: Dict[str, Any] = {} for label, description in fmt: diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..76b86d6 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Test suite for RecuperaBit NTFS recovery tool.""" diff --git a/tests/data/reference_files/deep/nested/directory/deep_file.txt b/tests/data/reference_files/deep/nested/directory/deep_file.txt new file mode 100644 index 0000000..22ce1eb --- /dev/null +++ b/tests/data/reference_files/deep/nested/directory/deep_file.txt @@ -0,0 +1 @@ +File in deep nested directory diff --git a/tests/data/reference_files/empty_file.empty b/tests/data/reference_files/empty_file.empty new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/reference_files/file_with_ads.txt b/tests/data/reference_files/file_with_ads.txt new file mode 100644 index 0000000..3a0c07f --- /dev/null +++ b/tests/data/reference_files/file_with_ads.txt @@ -0,0 +1 @@ +File with ADS main content diff --git a/tests/data/reference_files/large_file.dat b/tests/data/reference_files/large_file.dat new file mode 100644 index 0000000..8f0c060 --- /dev/null +++ b/tests/data/reference_files/large_file.dat @@ -0,0 +1 @@ +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA \ No newline at end of file diff --git a/tests/data/reference_files/medium_binary.bin b/tests/data/reference_files/medium_binary.bin new file mode 100644 index 0000000000000000000000000000000000000000..a6a8d35950066abbfd41c0ad441232127fc679a2 GIT binary patch literal 4096 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 literal 0 HcmV?d00001 diff --git a/tests/data/reference_files/small_text.txt b/tests/data/reference_files/small_text.txt new file mode 100644 index 0000000..85d20ae --- /dev/null +++ b/tests/data/reference_files/small_text.txt @@ -0,0 +1 @@ +Hello, World! This is a small text file. diff --git a/tests/data/reference_files/subdirectory/subdir_file1.txt b/tests/data/reference_files/subdirectory/subdir_file1.txt new file mode 100644 index 0000000..9b7eac5 --- /dev/null +++ b/tests/data/reference_files/subdirectory/subdir_file1.txt @@ -0,0 +1 @@ +File in subdirectory diff --git a/tests/data/reference_files/subdirectory/subdir_file2.bin b/tests/data/reference_files/subdirectory/subdir_file2.bin new file mode 100644 index 0000000000000000000000000000000000000000..bd26b49cc531eca6028e79475afb49d00e71f8d3 GIT binary patch literal 1024 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�dfKVyD||BU__$pR;~Qic^62yly~+ya#oxVDuY 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 z zTsXeu0h2ovaSm&Q>|dFCOoE#-Yn9OU<}iWNyNIxP$2Gj@!Lj0gD|BRkK<#|8CBL&_ zH21kD!h7P~{YE6a-QsKcQkAZLVFVvBC>$!53QfXWmX%a1JF_GvbK@#ad^tq6XAupP zVB&S}VTTlXV_b%xc%Qf}g49>-jLBUzALM3QX)Fkqb3=2)d=k;m<-zaCHq??IU|l|M z6Q+;~pUvLjjxLP^I7X`6CXc?^q}Jw|@=NtgVY6J=XG%e=aPr%Ww>usvTV`{IpZ?xx z|Ek0vMn~eKil6jqvGw==2P;tEqk#Yr00KY&2mk>f00e*l5C8%|;Qv)X+NHerr@uaA zjcj=LJ$L{CAOHk_01yBIKmZ5;0U!VbfB+Bx0)Kab&9S%l|5Xc^ln(#&cV8@69}oZn zKmZ5;0U!VbfB+Bx0zd!=00AKI{>Z|oJAc&z{*(r)z6Bsa00;m9AOHk_01yBIKmZ5; z0U!VbfWY5g;DfWq$bnxr{_FP+gG1}}KMb98yXWt|QhMo9$Ei!M&Obaho!^E05~hf> zKBey76l6W>+Bow53B}^{v(t)C9_-&zZmq0)Wd7}Gn)t5x9$UCw<*&j5c=|stAhK%m zN4#I*_4W98#~E4d_rE?+s?pS|696|01;XC~q@ z>HONB@R{yMrE8HV2UuGWtCTraWG4dsG+r&_YFx_w=7qe6{B?)1rJ7}7blR3O`NzS0 z$|pUKPWm5PckkHYG7+5V_hY8ogW7=|8|tq4bG&^l_upLN&*_~In!3bReF%+Td8=FD zJK=02(`R8c(Sxr<tCYiW{f1#J@)Mz9D2t$P zu$PWm0aZa!am^JCz3Kyh1jk>KNb2R;s9{pm0>MCk4~H22ZB5L;_ZysZqme$0-5GCq zrlx`RzBCj#_lRPKzGje{*(VmDKDUyBj5PGd6J?Af1~211#RB@IZ1TvlNU6G5b(`}_83*e9D~FV|KlCB;CQj zntymbVB}TO2AWQuX=U;2`J_!b<#2z8WOJO({+dHqhN-gUVwr`cIen6Q!bR87(FqD# zCx3)X^*JA;hq#{Qq9)VIRO^d{k0!l!mhF;W{rv5l>vw6f55)0JQzb8vHWAT9NM?pq zT`sLGGF9L%fvO$VoB413EHPP8BaF|0}DqK}H<<>+@uD#jvfct&uNmKU7 zh@hv1P@*qq6JFXA)Pvo;F|Up0T6vr=nr-JKPrxkY&zBBWQ}60HhN0`jk*sz1EZS*9 zuh3VG$xO|6Yd+7aeSGTmg5mhZot0yM-1J^cQ*%~)leM`1n*Y_^=xHH6Xh?fyF4+o| z^>kb2`dY_ETvKH2^6id-yjKUT99xgfJ|Pz(yQZ;C2J;HLqCVWVZ4mmX<+J#VE5Ff$V|(xWqXLC`+AM?pnS9!(X^y)J8;E=xhf_6|lNK9LEsre=kV zYfu~}$D*=g`Tge=%BK@Mtz3mw($7hc_t8m}u{K|Yz11^2f7$kb{%<8%01yBIKmZ5; z0U!VbfB+Bx0zlwDDj-k+xAgq&tFvH%KmZ5;0U!VbfB+Bx0zd!=00AHX1pd1KxTWX6 zYk;4D01yBIKmZ5;0U!VbfB+Bx0zd!=`~w6&$jE&E@6m(bB-oS}|A6&^B?18;00e*l z5C8%|00;m9AOHk_01)`63-l<93y*I*9r7=p%HbzJXmuaoZT{PD@>exqbvq6INj%J}vlV&(Tgi?|zgXXLZaPp-ZPC@F7jXTBpLruEY3v7`Ud`xwB7|8oR>HL$ER zxv`*<`1y0s@KI47y;i65b+w3wZLery(S;ZLd@n{)ZwYyefq~CmZgpkBR^z?e bool: + """Check if the reference image exists.""" + print(self.image_path, self.metadata_path) + return self.image_path.exists() and self.metadata_path.exists() + + def is_compressed(self) -> bool: + """Check if the reference image is compressed (e.g., .img.gz).""" + return self.image_path.suffix == '.gz' + + def _compute_file_hash(self, filepath: Path, compressed: bool = False) -> str: + """Compute SHA256 hash of a file.""" + sha256_hash = hashlib.sha256() + opener = gzip.open if compressed else open + with opener(filepath, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + sha256_hash.update(chunk) + return sha256_hash.hexdigest() + + def _compute_directory_hash(self, directory: Path) -> Dict[str, str]: + """Compute hashes for all files in a directory recursively.""" + file_hashes = {} + + for filepath in directory.rglob('*'): + if filepath.is_file(): + relative_path = filepath.relative_to(directory) + file_hashes[str(relative_path)] = self._compute_file_hash(filepath) + + return file_hashes + + def validate(self) -> Tuple[bool, Optional[str]]: + """Validate that the reference image is up-to-date and uncorrupted. + + Returns: + (is_valid, error_message) + """ + if not self.exists(): + return False, f"Reference image not found: {self.image_path}" + + try: + # Load metadata + with open(self.metadata_path, 'r') as f: + metadata = json.load(f) + + # Validate image hash + current_image_hash = self._compute_file_hash(self.image_path, self.is_compressed()) + expected_image_hash = metadata.get('image_hash') + + if current_image_hash != expected_image_hash: + return False, f"Image hash mismatch: expected {expected_image_hash}, got {current_image_hash}" + + # Validate source files hash (to detect if reference files changed) + reference_files_dir = Path("tests/data/reference_files") + if reference_files_dir.exists(): + current_files_hash = self._compute_directory_hash(reference_files_dir) + expected_files_hash = metadata.get('reference_files_hashes', {}) + + if current_files_hash != expected_files_hash: + return False, "Reference files have changed, image needs to be rebuilt" + + return True, None + + except Exception as e: + return False, f"Validation error: {e}" + + def get_expected_files(self) -> Dict[str, str]: + """Get the expected file hashes from the reference image metadata. + + Returns: + Dictionary mapping relative file paths to their SHA256 hashes + """ + if not self.metadata_path.exists(): + return {} + + try: + with open(self.metadata_path, 'r') as f: + metadata = json.load(f) + return metadata.get('reference_files_hashes', {}) + except Exception as e: + self.logger.error(f"Failed to load metadata: {e}") + return {} + + def get_reference_files_dir(self) -> Path: + """Get the directory containing the original reference files.""" + return self.metadata_path.parent / "reference_files" + + def copy_to_temp(self, temp_path: Path) -> None: + """Copy the reference image to a temporary location for testing. + + Args: + temp_path: Path where to copy the image + """ + if not self.exists(): + raise FileNotFoundError(f"Reference image not found: {self.image_path}") + + if self.is_compressed(): + with gzip.open(self.image_path, 'rb') as f_in, open(temp_path, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + else: + shutil.copy2(self.image_path, temp_path) + self.logger.debug(f"Copied reference image to {temp_path}") + + def get_info(self) -> Dict: + """Get information about the reference image. + + Returns: + Dictionary with image metadata + """ + if not self.metadata_path.exists(): + return {} + + try: + with open(self.metadata_path, 'r') as f: + return json.load(f) + except Exception as e: + self.logger.error(f"Failed to load metadata: {e}") + return {} + + +def ensure_reference_image() -> ReferenceNTFSImage: + """Ensure reference NTFS image exists and is valid. + + Returns: + ReferenceNTFSImage instance + + Raises: + FileNotFoundError: If image doesn't exist + ValueError: If image is corrupted or outdated + """ + ref_image = ReferenceNTFSImage() + + if not ref_image.exists(): + raise FileNotFoundError( + "Reference NTFS image not found. Please run: " + "sudo python tools/build_reference_ntfs.py" + ) + + is_valid, error = ref_image.validate() + if not is_valid: + raise ValueError( + f"Reference NTFS image validation failed: {error}. " + "Please rebuild with: sudo python tools/build_reference_ntfs.py" + ) + + return ref_image diff --git a/tests/run_tests.py b/tests/run_tests.py new file mode 100644 index 0000000..84a523b --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,125 @@ +"""Test runner and configuration for RecuperaBit test suite.""" + +import unittest +import sys +import os +import logging +from pathlib import Path + +# Add the project root to the Python path +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +# Import test modules +from tests.test_ntfs_unit import * +from tests.test_ntfs_e2e import * +from tests.test_integration import * + + +def create_test_suite(): + """Create and return the complete test suite.""" + loader = unittest.TestLoader() + suite = unittest.TestSuite() + + # Add unit tests + suite.addTests(loader.loadTestsFromModule(sys.modules['tests.test_ntfs_unit'])) + + # Add integration tests + suite.addTests(loader.loadTestsFromModule(sys.modules['tests.test_integration'])) + + # Add E2E tests (these may be skipped if tools are not available) + suite.addTests(loader.loadTestsFromModule(sys.modules['tests.test_ntfs_e2e'])) + + return suite + + +def run_unit_tests_only(): + """Run only unit tests (fast, no external dependencies).""" + loader = unittest.TestLoader() + suite = unittest.TestSuite() + + suite.addTests(loader.loadTestsFromModule(sys.modules['tests.test_ntfs_unit'])) + suite.addTests(loader.loadTestsFromModule(sys.modules['tests.test_integration'])) + + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + return result.wasSuccessful() + + +def run_e2e_tests_only(): + """Run only end-to-end tests (slower, requires system tools).""" + loader = unittest.TestLoader() + suite = unittest.TestSuite() + + suite.addTests(loader.loadTestsFromModule(sys.modules['tests.test_ntfs_e2e'])) + + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + return result.wasSuccessful() + + +def run_all_tests(): + """Run the complete test suite.""" + suite = create_test_suite() + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + return result.wasSuccessful() + + +def main(): + """Main test runner with command line options.""" + import argparse + + parser = argparse.ArgumentParser(description='RecuperaBit Test Runner') + parser.add_argument('--unit', action='store_true', + help='Run only unit tests (fast)') + parser.add_argument('--e2e', action='store_true', + help='Run only end-to-end tests (requires system tools)') + parser.add_argument('--integration', action='store_true', + help='Run only integration tests') + parser.add_argument('--verbose', '-v', action='store_true', + help='Verbose logging output') + parser.add_argument('--debug', action='store_true', + help='Debug level logging') + + args = parser.parse_args() + + # Set up logging + log_level = logging.WARNING + if args.verbose: + log_level = logging.INFO + if args.debug: + log_level = logging.DEBUG + + logging.basicConfig( + level=log_level, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + + # Run selected tests + success = True + + if args.unit: + print("Running unit tests only...") + success = run_unit_tests_only() + elif args.e2e: + print("Running end-to-end tests only...") + success = run_e2e_tests_only() + elif args.integration: + print("Running integration tests only...") + loader = unittest.TestLoader() + suite = unittest.TestSuite() + suite.addTests(loader.loadTestsFromModule(sys.modules['tests.test_integration'])) + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + success = result.wasSuccessful() + else: + print("Running complete test suite...") + success = run_all_tests() + + # Exit with appropriate code + sys.exit(0 if success else 1) + + +if __name__ == '__main__': + main() diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 0000000..3e3e806 --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,347 @@ +"""Integration tests for RecuperaBit logic and utilities.""" + +import unittest +import tempfile +import os +from unittest.mock import Mock, patch +from io import BytesIO + +from recuperabit.logic import SparseList, approximate_matching +from recuperabit.utils import merge, sectors, unpack + + +class TestSparseListIntegration(unittest.TestCase): + """Integration tests for SparseList with NTFS components.""" + + def test_sparse_list_with_mft_references(self): + """Test SparseList with MFT-like reference patterns.""" + # Simulate MFT record references + mft_refs = { + 0: 0, # Root directory points to itself + 16: 0, # System file points to root + 32: 16, # File in system directory + 48: 0, # Another root-level file + 64: 48, # File in subdirectory + 80: 48, # Another file in same subdirectory + } + + sparse_list = SparseList(mft_refs) + + # Test basic operations + self.assertEqual(sparse_list[0], 0) + self.assertEqual(sparse_list[16], 0) + self.assertEqual(sparse_list[64], 48) + self.assertIsNone(sparse_list[24]) # Gap + + # Test length + self.assertEqual(len(sparse_list), 81) + + # Test iteration gives keys, not all indices + keys = list(sparse_list) + expected_keys = [0, 16, 32, 48, 64, 80] + self.assertEqual(keys, expected_keys) + + def test_sparse_list_large_gaps(self): + """Test SparseList with large gaps (common in fragmented filesystems).""" + fragmented_refs = { + 100: 0, + 5000: 100, + 10000: 5000, + 50000: 10000, + } + + sparse_list = SparseList(fragmented_refs) + + # Should handle large indices efficiently + self.assertEqual(sparse_list[100], 0) + self.assertEqual(sparse_list[5000], 100) + self.assertEqual(sparse_list[50000], 10000) + + # Large gaps should return None + self.assertIsNone(sparse_list[1000]) + self.assertIsNone(sparse_list[25000]) + + +class TestApproximateMatching(unittest.TestCase): + """Test approximate matching functionality.""" + + def test_approximate_matching_perfect_match(self): + """Test approximate matching with perfect match.""" + # Create text (haystack) and pattern (needle) + text_data = {i: i // 4 for i in range(0, 100, 4)} # Every 4th position + pattern_data = {i: i // 4 for i in range(0, 20, 4)} # First 5 elements + + text_list = SparseList(text_data) + pattern_list = SparseList(pattern_data) + + # Should find match at position 0 + result = approximate_matching(text_list, pattern_list, 0, k=3) + + self.assertIsNotNone(result) + positions, count, percentage = result + self.assertIn(0, positions) + self.assertGreater(percentage, 0.8) # High match percentage + + def test_approximate_matching_shifted_pattern(self): + """Test approximate matching with shifted pattern.""" + # Create text and pattern with some overlap + text_data = {i: i % 5 for i in range(0, 100, 4)} # Pattern repeating every 5 + pattern_data = {i: i % 5 for i in range(0, 20, 4)} # Same pattern but shorter + + text_list = SparseList(text_data) + pattern_list = SparseList(pattern_data) + + # Should find matches at multiple positions + result = approximate_matching(text_list, pattern_list, 50, k=1) + + if result is not None: + positions, count, percentage = result + # positions is a set, not a list, and contains actual match positions + self.assertIsInstance(positions, set) + self.assertGreater(len(positions), 0) + else: + # If no exact match found, that's also acceptable for this pattern + self.assertIsNone(result) + + def test_approximate_matching_no_match(self): + """Test approximate matching with no good match.""" + # Create text and completely different pattern + text_data = {i: 1 for i in range(0, 100, 4)} # All 1s + pattern_data = {i: 2 for i in range(0, 20, 4)} # All 2s + + text_list = SparseList(text_data) + pattern_list = SparseList(pattern_data) + + # Should not find good match + result = approximate_matching(text_list, pattern_list, 0, k=3) + + if result is not None: + positions, count, percentage = result + self.assertLess(percentage, 0.1) # Very low match percentage + + +class TestUtilityFunctions(unittest.TestCase): + """Test utility functions.""" + + def test_merge_function(self): + """Test the merge function.""" + from recuperabit.fs.core_types import Partition, File + + # Create mock scanner + class MockScanner: + pass + scanner = MockScanner() + + # Create partition objects with files + part1 = Partition('TEST', 0, scanner) + part2 = Partition('TEST', 0, scanner) + + # Add files to partitions + file1 = File(1, 'file1.txt', 100) + file2 = File(2, 'file2.txt', 200) + file3 = File(3, 'file3.txt', 300) + file4 = File(4, 'file4.txt', 400) + + part1.add_file(file1) + part1.add_file(file2) + part2.add_file(file3) + part2.add_file(file4) + + # Test merge + merge(part1, part2) + + # part1 should now contain files from both + self.assertIn(1, part1.files) + self.assertIn(2, part1.files) + self.assertIn(3, part1.files) + self.assertIn(4, part1.files) + self.assertEqual(len(part1.files), 4) + + def test_merge_with_conflicts(self): + """Test merge function with conflicting keys.""" + from recuperabit.fs.core_types import Partition, File + + # Create mock scanner + class MockScanner: + pass + scanner = MockScanner() + + # Create partition objects + part1 = Partition('TEST', 0, scanner) + part2 = Partition('TEST', 0, scanner) + + # Add conflicting files (same index) + file1_ghost = File(1, 'file1_ghost.txt', 100, is_ghost=True) + file1_real = File(1, 'file1_real.txt', 100, is_ghost=False) + file2 = File(2, 'file2.txt', 200) + file3 = File(3, 'file3.txt', 300) + + part1.add_file(file1_ghost) + part1.add_file(file2) + part2.add_file(file1_real) + part2.add_file(file3) + + merge(part1, part2) + + # part1 should replace ghost with real file + self.assertIn(1, part1.files) + self.assertIn(2, part1.files) + self.assertIn(3, part1.files) + # The ghost file should be replaced by the real file + self.assertFalse(part1.files[1].is_ghost) + + def test_sectors_function(self): + """Test the sectors function.""" + # Create test data + test_data = b'A' * 512 + b'B' * 512 + b'C' * 512 # 3 sectors + test_file = BytesIO(test_data) + + # Test reading single sector + result = sectors(test_file, 0, 1) + self.assertEqual(result, b'A' * 512) + + # Test reading multiple sectors + result = sectors(test_file, 1, 2) + self.assertEqual(result, b'B' * 512 + b'C' * 512) + + # Test reading with byte granularity + result = sectors(test_file, 256, 512, 1) # 512 bytes starting at byte 256 + expected = b'A' * 256 + b'B' * 256 + self.assertEqual(result, expected) + + def test_sectors_out_of_bounds(self): + """Test sectors function with out-of-bounds access.""" + test_data = b'A' * 512 # Only 1 sector + test_file = BytesIO(test_data) + + # Try to read beyond file + result = sectors(test_file, 1, 1) + self.assertEqual(result, b'') # Should return empty bytes + + def test_unpack_function(self): + """Test the unpack function with simple format.""" + # Create test data + test_data = b'\x01\x02\x03\x04\x05\x06\x07\x08' + + # Create format specification: [(label, (formatter, lower, higher)), ...] + test_format = [ + ('first_byte', ('i', 0, 0)), # Single byte at position 0 + ('two_bytes', ('2i', 1, 2)), # Two bytes from position 1-2 + ('last_four', ('4i', 4, 7)) # Four bytes from position 4-7 + ] + + result = unpack(test_data, test_format) + + # Check that we get expected structure + self.assertIn('first_byte', result) + self.assertIn('two_bytes', result) + self.assertIn('last_four', result) + + def test_unpack_insufficient_data(self): + """Test unpack function with insufficient data.""" + # Create short test data + test_data = b'\x01\x02' + + # Format that requires more data than available + test_format = [ + ('valid_data', ('i', 0, 1)), # Valid range + ('out_of_bounds', ('i', 5, 8)) # Tries to read beyond data + ] + + # Should handle gracefully, setting None for missing data + result = unpack(test_data, test_format) + + # Should have valid data for first field + self.assertIn('valid_data', result) + # Should handle out of bounds gracefully + self.assertIn('out_of_bounds', result) + + def test_unpack_insufficient_data(self): + """Test unpack function with insufficient data.""" + # Create short test data + test_data = b'\x01\x02' + + # Format that requires more data than available + test_format = [ + ('valid_data', ('i', 0, 1)), # Valid range + ('out_of_bounds', ('i', 5, 8)) # Tries to read beyond data + ] + + # Should handle gracefully, setting None for missing data + result = unpack(test_data, test_format) + + # Should have valid data for first field + self.assertIn('valid_data', result) + # Should handle out of bounds gracefully + self.assertIn('out_of_bounds', result) + + +class TestNTFSIntegration(unittest.TestCase): + """Integration tests combining multiple NTFS components.""" + + def test_mft_indx_relationship(self): + """Test the relationship between MFT and INDX records.""" + # Simulate finding related MFT and INDX records + mft_positions = {100, 200, 300, 400} # MFT record positions + indx_positions = {1000, 2000, 3000} # INDX record positions + + # Simulate INDX records pointing to MFT records + indx_references = { + 1000: {'parent': 100, 'children': {200, 300}}, + 2000: {'parent': 200, 'children': {400}}, + 3000: {'parent': 300, 'children': set()}, + } + + # Create SparseList for INDX relationships + indx_list = SparseList({pos: info['parent'] for pos, info in indx_references.items()}) + + # Verify relationships + self.assertEqual(indx_list[1000], 100) # INDX at 1000 points to MFT 100 + self.assertEqual(indx_list[2000], 200) # INDX at 2000 points to MFT 200 + + # Test that we can find directory structure + root_mft = 100 + subdirs = [pos for pos, info in indx_references.items() if info['parent'] == root_mft] + self.assertEqual(len(subdirs), 1) + self.assertEqual(subdirs[0], 1000) + + # Test children relationships + children_of_200 = indx_references[2000]['children'] + self.assertEqual(children_of_200, {400}) + + def test_partition_boundary_detection(self): + """Test partition boundary detection logic.""" + # Simulate MFT pattern for boundary detection + base_pattern = {10: 100, 20: 100, 30: 200, 40: 200} # Cluster -> MFT record + + # Test different sectors per cluster values + for sec_per_clus in [1, 2, 4, 8]: + # Convert cluster pattern to sector pattern + sector_pattern = { + cluster * sec_per_clus: mft_record + for cluster, mft_record in base_pattern.items() + } + + pattern_list = SparseList(sector_pattern) + + # Simulate text list (found INDX records) + text_data = {} + for sector in range(0, 400): + if sector in sector_pattern: + text_data[sector + 1000] = sector_pattern[sector] # Offset by 1000 + + text_list = SparseList(text_data) + + # Test approximate matching for boundary detection + mft_address = 1000 # Assumed MFT start + result = approximate_matching(text_list, pattern_list, mft_address + min(sector_pattern.keys()), k=2) + + if result is not None: + positions, count, percentage = result + # Should find at least one potential boundary + self.assertGreater(len(positions), 0) + self.assertGreater(percentage, 0.1) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_ntfs_e2e.py b/tests/test_ntfs_e2e.py new file mode 100644 index 0000000..ad0bdc6 --- /dev/null +++ b/tests/test_ntfs_e2e.py @@ -0,0 +1,251 @@ +"""End-to-end tests for RecuperaBit NTFS recovery. + +This module uses pre-built reference NTFS images to test complete recovery workflows. +""" + +import unittest +import tempfile +import os +import shutil +import hashlib +from pathlib import Path +from typing import Dict +import logging + +from recuperabit.fs.ntfs import NTFSPartition, NTFSScanner +from tests.reference_image import ensure_reference_image +import main # Import main module to access interpret function + + +class TestNTFSE2E(unittest.TestCase): + """End-to-end tests for NTFS recovery.""" + + @classmethod + def setUpClass(cls): + """Set up class-level fixtures.""" + # Ensure reference image exists and is valid + try: + cls.ref_image = ensure_reference_image() + logging.info(f"Using reference NTFS image: {cls.ref_image.image_path}") + except (FileNotFoundError, ValueError) as e: + raise unittest.SkipTest(f"Reference image not available: {e}") + + # Get expected file hashes from reference image + cls.expected_files = cls.ref_image.get_expected_files() + + if not cls.expected_files: + raise unittest.SkipTest("No expected files in reference image metadata") + + logging.info(f"Reference image contains {len(cls.expected_files)} test files") + + # Set up temp directory for working files + cls.test_dir = tempfile.mkdtemp(prefix='recuperabit_e2e_') + cls.recovery_dir = os.path.join(cls.test_dir, 'recovered') + os.makedirs(cls.recovery_dir, exist_ok=True) + + logging.basicConfig(level=logging.DEBUG) + + @classmethod + def tearDownClass(cls): + """Clean up class-level fixtures.""" + # Clean up test directory + if hasattr(cls, 'test_dir') and os.path.exists(cls.test_dir): + shutil.rmtree(cls.test_dir) + + def setUp(self): + """Set up test fixtures.""" + # Create a temporary copy of the reference image for this test + self.image_path = os.path.join(self.test_dir, f'test_ntfs_{id(self)}.img') + self.ref_image.copy_to_temp(Path(self.image_path)) + + def tearDown(self): + """Clean up test fixtures.""" + # Clean up the temporary image copy + if hasattr(self, 'image_path') and os.path.exists(self.image_path): + os.remove(self.image_path) + + def _scan_image_with_scanner(self, scanner_class: type[NTFSScanner]) -> Dict[int, NTFSPartition]: + """Scan the image with the given scanner class.""" + # Keep file handle open and return it along with partitions + img_file = open(self.image_path, 'rb') + scanner = scanner_class(img_file) + + # Feed sectors to scanner + sector_size = 512 + sector_index = 0 + + while True: + img_file.seek(sector_index * sector_size) + sector = img_file.read(sector_size) + + if len(sector) < sector_size: + break + + result = scanner.feed(sector_index, sector) + if result: + logging.debug(f"Found {result} at sector {sector_index}") + + sector_index += 1 + + # Get partitions + partitions = scanner.get_partitions() + # Store the file handle so it doesn't get closed + self._img_file = img_file + return partitions + + def _close_image_file(self): + """Close the image file handle.""" + if hasattr(self, '_img_file') and self._img_file: + self._img_file.close() + self._img_file = None + + def _recover_files_from_partition(self, partition: NTFSPartition, partition_id: int) -> Dict[str, bytes]: + """Recover files from a partition using high-level interpret function with proper hierarchy.""" + # Create temporary recovery directory + recovery_dir = os.path.join(self.test_dir, f'recovered_partition_{partition_id}') + os.makedirs(recovery_dir, exist_ok=True) + + # Create shorthands structure like main.py + parts = {0: partition} # Simple mapping for our single partition + shorthands = [(0, 0)] # (index, partition_key) pairs + + try: + # Use the high-level interpret function to restore the root directory + # This will properly handle filesystem hierarchy, directories, etc. + main.interpret('restore', ['0', '5'], parts, shorthands, recovery_dir) # '5' is typically the root directory + + # Collect all recovered files and their content + recovered_files = {} + + # Walk through the recovered directory structure + partition_dir = os.path.join(recovery_dir, 'Partition0', 'Root') + if os.path.exists(partition_dir): + for root, dirs, files in os.walk(partition_dir): + for file in files: + file_path = os.path.join(root, file) + # Get relative path from partition directory + relative_path = os.path.relpath(file_path, partition_dir) + + try: + with open(file_path, 'rb') as f: + content = f.read() + recovered_files[relative_path] = content + logging.info(f"Recovered file: {relative_path} ({len(content)} bytes)") + except Exception as e: + logging.error(f"Error reading recovered file {relative_path}: {e}") + + return recovered_files + + except Exception as e: + logging.error(f"Error during recovery: {e}") + return {} + finally: + # Clean up recovery directory + if os.path.exists(recovery_dir): + shutil.rmtree(recovery_dir, ignore_errors=True) + + def _compare_files(self, original_hashes: Dict[str, str], + recovered_files: Dict[str, bytes]) -> Dict[str, bool]: + """Compare original and recovered files, handling path normalization.""" + results = {} + + # Normalize recovered file paths by removing Root/ prefix + + print(f"DEBUG: Expected files: {list(original_hashes.keys())}") + print(f"DEBUG: Normalized recovered files: {list(recovered_files.keys())}") + + expected_recovered_files = [filename for filename in list(recovered_files.keys()) if filename in original_hashes.keys()] + print(f"DEBUG: Matching recovered files: {expected_recovered_files} ({len(expected_recovered_files)} / {len(original_hashes)})") + + # Check how many files were recovered successfully + for filename, original_hash in original_hashes.items(): + if filename in recovered_files: + recovered_file = recovered_files[filename] + recovered_hash = hashlib.sha256(recovered_file).hexdigest() + results[filename] = (original_hash == recovered_hash) + if results[filename]: + logging.info(f"✓ {filename}: Recovery successful ({len(recovered_file)} bytes)") + else: + logging.error(f"✗ {filename}: Hash mismatch! Expected: {original_hash}, Got: {recovered_hash}") + # Print first 64 bytes of recovered content vs the original content for debugging + logging.error(f" Recovered content (first 64 bytes): {recovered_file[:64]}") + with open(self.ref_image.get_reference_files_dir() / filename, 'rb') as original_file: + original_content = original_file.read(64) + logging.error(f" Original content (first 64 bytes): {original_content[:64]}") + else: + results[filename] = False + logging.error(f"✗ {filename}: File not recovered") + + return results + + def test_basic_ntfs_recovery(self): + """Test basic NTFS file recovery using reference image.""" + print(f"DEBUG: Using reference NTFS image at {self.image_path}") + + try: + # Test recovery with standard scanner + partitions = self._scan_image_with_scanner(NTFSScanner) + self.assertGreater(len(partitions), 0, "No NTFS partitions found") + + # Recover files from the LARGEST partition (most likely to contain user data) + if not partitions: + self.fail("No NTFS partitions found") + + # Find the largest partition by number of files (user data indicator) + largest_partition_id = None + largest_partition = None + max_files = 0 + + print(f"DEBUG: Found {len(partitions)} partitions:") + for partition_id, partition in partitions.items(): + file_count = len(partition.files) if hasattr(partition, 'files') else 0 + print(f" Partition {partition_id}: {file_count} files, offset {partition.offset}") + + if file_count > max_files: + max_files = file_count + largest_partition_id = partition_id + largest_partition = partition + + if largest_partition is None: + self.fail("No partition with files found") + + print(f"DEBUG: Processing largest partition {largest_partition_id} with {max_files} files at offset {largest_partition.offset}") + + # Recover files from the largest partition only + all_recovered_files = self._recover_files_from_partition(largest_partition, largest_partition_id) + + for filename, content in all_recovered_files.items(): + print(f"DEBUG: Recovered file '{filename}' with content size {len(content)} bytes") + + # Compare results using expected files from reference image + comparison = self._compare_files(self.expected_files, all_recovered_files) + + # Check that at least some files were recovered correctly + successful_recoveries = sum(1 for success in comparison.values() if success) + total_files = len(self.expected_files) + + self.assertGreater(successful_recoveries, 0, "No files recovered successfully") + + # We expect most files to be recovered (allowing for some edge cases) + recovery_rate = successful_recoveries / total_files + self.assertAlmostEqual(recovery_rate, 1.0, + f"Low recovery rate: {recovery_rate:.2%} ({successful_recoveries}/{total_files})") + + # Log success for visibility + print(f"SUCCESS: Hierarchical recovery rate {recovery_rate:.2%} ({successful_recoveries}/{total_files})") + print(f"✅ All {total_files} files found with correct filesystem hierarchy!") + print(f"✅ High-level recovery APIs working correctly!") + print(f"✅ Largest partition selection working!") + finally: + # Always close the image file handle + self._close_image_file() + + +if __name__ == '__main__': + # Set up logging + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' + ) + + unittest.main(verbosity=2) diff --git a/tests/test_ntfs_unit.py b/tests/test_ntfs_unit.py new file mode 100644 index 0000000..a35d24e --- /dev/null +++ b/tests/test_ntfs_unit.py @@ -0,0 +1,297 @@ +"""Unit tests for NTFS parsing functions and core types.""" + +import unittest +from unittest.mock import Mock + +# Import the modules under test +from recuperabit.fs.ntfs import ( + NTFSFile, NTFSPartition, + NTFSScanner, best_name, _apply_fixup_values +) +from recuperabit.logic import SparseList + + +class TestNTFSParsing(unittest.TestCase): + """Test NTFS parsing functions.""" + + def setUp(self): + """Set up test fixtures.""" + # Create a mock MFT entry for testing + self.mock_mft_entry = bytearray(1024) # 1KB MFT entry + # FILE signature + self.mock_mft_entry[0:4] = b'FILE' + # Fixup offset at position 4-6 (little endian) + self.mock_mft_entry[4:6] = (48).to_bytes(2, 'little') + # Number of fixup entries at position 6-8 + self.mock_mft_entry[6:8] = (2).to_bytes(2, 'little') + # First attribute offset at position 20-22 + self.mock_mft_entry[20:22] = (56).to_bytes(2, 'little') + # MFT record size allocated at position 28-32 + self.mock_mft_entry[28:32] = (1024).to_bytes(4, 'little') + # Record number at position 44-48 + self.mock_mft_entry[44:48] = (42).to_bytes(4, 'little') + + # Mock INDX entry + self.mock_indx_entry = bytearray(4096) # 4KB INDX entry + # INDX signature + self.mock_indx_entry[0:4] = b'INDX' + # Fixup offset at position 4-6 + self.mock_indx_entry[4:6] = (40).to_bytes(2, 'little') + # Number of fixup entries at position 6-8 + self.mock_indx_entry[6:8] = (8).to_bytes(2, 'little') + + def test_apply_fixup_values(self): + """Test the fixup values application.""" + # Create a test entry with 3 sectors (1536 bytes) to test both fixups + entry = bytearray(1536) + header = { + 'off_fixup': 48, + 'n_entries': 3 # 1 original + 2 fixup entries + } + + # Set up fixup array at offset 48 + entry[48:50] = b'\xAA\xBB' # Original value (not used in replacement) + entry[50:52] = b'\xCC\xDD' # First replacement (for sector 1) + entry[52:54] = b'\xEE\xFF' # Second replacement (for sector 2) + + # Set sectors to have the original values that need fixing + # sector_size = 512, so positions are 512*i - 2 + entry[510:512] = b'\x00\x00' # End of first sector (512*1 - 2) + entry[1022:1024] = b'\x00\x00' # End of second sector (512*2 - 2) + + _apply_fixup_values(header, entry) + + # Check that fixup was applied correctly + self.assertEqual(entry[510:512], b'\xCC\xDD') + self.assertEqual(entry[1022:1024], b'\xEE\xFF') + + def test_best_name(self): + """Test the best_name function.""" + # Test with NTFS namespace (preferred) + entries = [(1, 'short.txt'), (3, 'long_filename.txt')] + self.assertEqual(best_name(entries), 'long_filename.txt') + + # Test without NTFS namespace + entries = [(1, 'short.txt'), (2, 'dos_name.txt')] + self.assertEqual(best_name(entries), 'short.txt') + + # Test with empty list + self.assertIsNone(best_name([])) + + # Test with empty name + entries = [(3, '')] + self.assertIsNone(best_name(entries)) + + +class TestNTFSFile(unittest.TestCase): + """Test NTFSFile class.""" + + def setUp(self): + """Set up test fixtures.""" + self.mock_parsed = { + 'record_n': 42, + 'flags': 0x01, # Not deleted + 'attributes': { + '$FILE_NAME': [{ + 'content': { + 'namespace': 3, + 'name': 'test_file.txt', + 'name_length': 13, + 'parent_entry': 5 + } + }], + '$DATA': [{ + 'name': '', + 'real_size': 1024, + 'non_resident': False, + 'content_size': 1024 + }], + '$STANDARD_INFORMATION': { + 'content': { + 'modification_time': 132000000000000000, + 'access_time': 132000000000000000, + 'creation_time': 132000000000000000 + } + } + } + } + + def test_ntfs_file_creation(self): + """Test NTFSFile creation with valid data.""" + file_obj = NTFSFile(self.mock_parsed, 12345) + + self.assertEqual(file_obj.index, 42) + self.assertEqual(file_obj.name, 'test_file.txt') + self.assertEqual(file_obj.size, 1024) + self.assertFalse(file_obj.is_directory) + self.assertFalse(file_obj.is_deleted) + self.assertFalse(file_obj.is_ghost) + self.assertEqual(file_obj.parent, 5) + self.assertEqual(file_obj.ads, '') + + def test_ntfs_file_with_ads(self): + """Test NTFSFile creation with alternate data stream.""" + file_obj = NTFSFile(self.mock_parsed, 12345, ads='stream1') + + self.assertEqual(file_obj.index, '42:stream1') + self.assertEqual(file_obj.name, 'test_file.txt:stream1') + self.assertEqual(file_obj.ads, 'stream1') + + def test_ntfs_file_directory(self): + """Test NTFSFile creation for directory.""" + self.mock_parsed['flags'] = 0x03 # Directory flag + file_obj = NTFSFile(self.mock_parsed, 12345) + + self.assertTrue(file_obj.is_directory) + + def test_ntfs_file_deleted(self): + """Test NTFSFile creation for deleted file.""" + self.mock_parsed['flags'] = 0x00 # Deleted flag + file_obj = NTFSFile(self.mock_parsed, 12345) + + self.assertTrue(file_obj.is_deleted) + + def test_ntfs_file_ghost(self): + """Test NTFSFile creation for ghost file.""" + file_obj = NTFSFile(self.mock_parsed, 12345, is_ghost=True) + + self.assertTrue(file_obj.is_ghost) + + def test_ntfs_file_ignore(self): + """Test NTFSFile ignore logic.""" + # Test $Bad file + self.mock_parsed['record_n'] = 8 + file_obj = NTFSFile(self.mock_parsed, 12345, ads='$Bad') + file_obj.index = '8:$Bad' + self.assertTrue(file_obj.ignore()) + + # Test $UsnJrnl file + self.mock_parsed['record_n'] = 100 + file_obj = NTFSFile(self.mock_parsed, 12345, ads='$J') + file_obj.parent = 11 + self.assertTrue(file_obj.ignore()) + + +class TestNTFSPartition(unittest.TestCase): + """Test NTFSPartition class.""" + + def setUp(self): + """Set up test fixtures.""" + self.scanner = Mock(spec=NTFSScanner) + + def test_ntfs_partition_creation(self): + """Test NTFSPartition creation.""" + partition = NTFSPartition(self.scanner, 12345) + + self.assertEqual(partition.fs_type, 'NTFS') + self.assertEqual(partition.root_id, 5) + self.assertEqual(partition.mft_pos, 12345) + self.assertIsNone(partition.sec_per_clus) + self.assertIsNone(partition.mftmirr_pos) + + def test_ntfs_partition_additional_repr(self): + """Test NTFSPartition additional representation.""" + partition = NTFSPartition(self.scanner, 12345) + partition.sec_per_clus = 8 + partition.mftmirr_pos = 67890 + + additional = partition.additional_repr() + expected = [ + ('Sec/Clus', 8), + ('MFT offset', 12345), + ('MFT mirror offset', 67890) + ] + self.assertEqual(additional, expected) + + +class TestNTFSScanner(unittest.TestCase): + """Test NTFSScanner class.""" + + def setUp(self): + """Set up test fixtures.""" + self.scanner = NTFSScanner(Mock()) + + def test_feed_boot_sector(self): + """Test feeding a boot sector.""" + boot_sector = b'NTFS' + b'\x00' * 506 + b'\x55\xAA' + result = self.scanner.feed(0, boot_sector) + + self.assertEqual(result, 'NTFS boot sector') + self.assertIn(0, self.scanner.found_boot) + + def test_feed_file_record(self): + """Test feeding a FILE record.""" + file_record = b'FILE' + b'\x00' * 508 + result = self.scanner.feed(100, file_record) + + self.assertEqual(result, 'NTFS file record') + self.assertIn(100, self.scanner.found_file) + + def test_feed_baad_record(self): + """Test feeding a BAAD record.""" + baad_record = b'BAAD' + b'\x00' * 508 + result = self.scanner.feed(200, baad_record) + + self.assertEqual(result, 'NTFS file record') + self.assertIn(200, self.scanner.found_file) + + def test_feed_indx_record(self): + """Test feeding an INDX record.""" + indx_record = b'INDX' + b'\x00' * 508 + result = self.scanner.feed(300, indx_record) + + self.assertEqual(result, 'NTFS index record') + self.assertIn(300, self.scanner.found_indx) + + def test_feed_unknown_sector(self): + """Test feeding an unknown sector.""" + unknown_sector = b'UNKN' + b'\x00' * 508 + result = self.scanner.feed(400, unknown_sector) + + self.assertIsNone(result) + self.assertNotIn(400, self.scanner.found_boot) + self.assertNotIn(400, self.scanner.found_file) + self.assertNotIn(400, self.scanner.found_indx) + + def test_most_likely_sec_per_clus(self): + """Test most_likely_sec_per_clus function.""" + self.scanner.found_spc = [8, 8, 8, 4, 4, 16] + result = self.scanner.most_likely_sec_per_clus() + + # Should return 8 first (most common), then others + self.assertEqual(result[0], 8) + self.assertIn(4, result) + self.assertIn(16, result) + +class TestSparseList(unittest.TestCase): + """Test SparseList functionality.""" + + def test_sparse_list_creation(self): + """Test SparseList creation and basic operations.""" + data = {10: 'ten', 20: 'twenty', 30: 'thirty'} + sparse_list = SparseList(data) + + self.assertEqual(len(sparse_list), 31) # 0 to 30 + self.assertEqual(sparse_list[10], 'ten') + self.assertEqual(sparse_list[20], 'twenty') + self.assertEqual(sparse_list[30], 'thirty') + self.assertIsNone(sparse_list[15]) # Gap + + def test_sparse_list_iteration(self): + """Test SparseList iteration.""" + data = {1: 'one', 3: 'three', 5: 'five'} + sparse_list = SparseList(data) + + # SparseList should iterate over keys, not all values + keys = list(sparse_list) + expected_keys = [1, 3, 5] + self.assertEqual(keys, expected_keys) + + # Test itervalues method for getting values + values = list(sparse_list.itervalues()) + expected_values = ['one', 'three', 'five'] + self.assertEqual(values, expected_values) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/build_reference_ntfs.py b/tools/build_reference_ntfs.py new file mode 100755 index 0000000..0537893 --- /dev/null +++ b/tools/build_reference_ntfs.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +"""Build reference NTFS filesystem image for E2E tests. + +This script creates a reference NTFS filesystem image by: +1. Creating a loop-mounted NTFS filesystem +2. Copying reference test files to it +3. Unmounting and saving the image +4. Computing checksums for both the image and source files +5. Storing metadata for validation + +Usage: + python tools/build_reference_ntfs.py [--size SIZE_MB] [--output OUTPUT_PATH] +""" + +import argparse +import hashlib +import json +import logging +import os +import shutil +import subprocess +import tempfile +from pathlib import Path +from typing import Dict, List + +import gzip + +class NTFSImageBuilder: + """Builder for reference NTFS filesystem images.""" + + def __init__(self, size_mb: int = 100, output_path: str = None, compress: bool = True): + self.size_mb = size_mb + self.output_path = output_path or "tests/data/reference_ntfs.img" + self.metadata_path = self.output_path.replace('.img', '.json') + self.reference_files_dir = Path("tests/data/reference_files") + self.compress = compress + + # Set up logging + logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') + self.logger = logging.getLogger(__name__) + + def _check_requirements(self) -> None: + """Check if required tools are available.""" + required_tools = ['mkfs.ntfs', 'losetup', 'mount', 'umount', 'sync'] + missing_tools = [] + + for tool in required_tools: + if shutil.which(tool) is None: + missing_tools.append(tool) + + if missing_tools: + raise RuntimeError(f"Missing required tools: {', '.join(missing_tools)}") + + # Check if running as root (needed for loop devices) + if os.geteuid() != 0: + raise RuntimeError("This script must be run as root to create loop devices") + + def _compute_file_hash(self, filepath: Path) -> str: + """Compute SHA256 hash of a file.""" + sha256_hash = hashlib.sha256() + with open(filepath, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + sha256_hash.update(chunk) + return sha256_hash.hexdigest() + + def _compute_directory_hash(self, directory: Path) -> Dict[str, str]: + """Compute hashes for all files in a directory recursively.""" + file_hashes = {} + + for filepath in directory.rglob('*'): + if filepath.is_file(): + relative_path = filepath.relative_to(directory) + file_hashes[str(relative_path)] = self._compute_file_hash(filepath) + self.logger.info(f"Hashed {relative_path}: {file_hashes[str(relative_path)][:16]}...") + + return file_hashes + + def _create_empty_image(self, image_path: Path) -> None: + """Create an empty disk image file.""" + self.logger.info(f"Creating {self.size_mb}MB empty image at {image_path}") + + with open(image_path, 'wb') as f: + f.seek(self.size_mb * 1024 * 1024 - 1) + f.write(b'\0') + + def _format_ntfs(self, image_path: Path) -> None: + """Format the image as NTFS.""" + self.logger.info("Formatting image as NTFS...") + + cmd = ['mkfs.ntfs', '-F', '-f', str(image_path)] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + raise RuntimeError(f"Failed to format NTFS: {result.stderr}") + + def _setup_loop_device(self, image_path: Path) -> str: + """Set up loop device for the image.""" + self.logger.info("Setting up loop device...") + + cmd = ['losetup', '--find', '--show', str(image_path)] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + raise RuntimeError(f"Failed to set up loop device: {result.stderr}") + + loop_device = result.stdout.strip() + self.logger.info(f"Created loop device: {loop_device}") + return loop_device + + def _cleanup_loop_device(self, loop_device: str) -> None: + """Clean up loop device.""" + self.logger.info(f"Cleaning up loop device: {loop_device}") + + cmd = ['losetup', '-d', loop_device] + subprocess.run(cmd, capture_output=True, text=True) + + def _mount_filesystem(self, loop_device: str, mount_point: Path) -> None: + """Mount the NTFS filesystem.""" + self.logger.info(f"Mounting {loop_device} at {mount_point}") + + cmd = ['mount', '-t', 'ntfs-3g', '-o', 'sync', loop_device, str(mount_point)] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + raise RuntimeError(f"Failed to mount filesystem: {result.stderr}") + + def _unmount_filesystem(self, mount_point: Path) -> None: + """Unmount the filesystem.""" + self.logger.info(f"Unmounting {mount_point}") + + cmd = ['umount', str(mount_point)] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + self.logger.warning(f"Failed to unmount cleanly: {result.stderr}") + + def _copy_files(self, mount_point: Path) -> None: + """Copy reference files to the mounted filesystem.""" + self.logger.info("Copying reference files to mounted filesystem...") + + if not self.reference_files_dir.exists(): + raise RuntimeError(f"Reference files directory not found: {self.reference_files_dir}") + + # Copy all files and directories + for item in self.reference_files_dir.iterdir(): + dest = mount_point / item.name + + if item.is_file(): + shutil.copy2(item, dest) + self.logger.info(f"Copied file: {item.name}") + elif item.is_dir(): + shutil.copytree(item, dest) + self.logger.info(f"Copied directory: {item.name}") + + # Create alternate data stream (if supported) + try: + ads_file = mount_point / "file_with_ads.txt" + if ads_file.exists(): + # Try to create ADS using attr command if available + if shutil.which('attr'): + cmd = ['attr', '-s', 'stream1', '-V', 'ADS content', str(ads_file)] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + self.logger.info("Created alternate data stream") + else: + self.logger.warning("Failed to create ADS, not supported") + else: + self.logger.warning("attr tool not available, skipping ADS creation") + except Exception as e: + self.logger.warning(f"Could not create alternate data stream: {e}") + + def _save_metadata(self, image_path: Path, file_hashes: Dict[str, str]) -> None: + """Save metadata about the image and source files.""" + self.logger.info("Computing image hash and saving metadata...") + + # Compute image hash + image_hash = self._compute_file_hash(image_path) + + # Get image size + image_size = image_path.stat().st_size + + metadata = { + "version": "1.0", + "created_by": "build_reference_ntfs.py", + "size_mb": self.size_mb, + "image_size_bytes": image_size, + "image_hash": image_hash, + "reference_files_hashes": file_hashes, + "file_count": len(file_hashes), + "notes": "Reference NTFS filesystem for RecuperaBit E2E tests" + } + + with open(self.metadata_path, 'w') as f: + json.dump(metadata, f, indent=2, sort_keys=True) + + self.logger.info(f"Saved metadata to {self.metadata_path}") + self.logger.info(f"Image hash: {image_hash}") + + def build(self) -> None: + """Build the reference NTFS image.""" + self.logger.info("Starting NTFS reference image build...") + + # Check requirements + self._check_requirements() + + # Prepare paths + image_path = Path(self.output_path) + image_path.parent.mkdir(parents=True, exist_ok=True) + + # Compute hashes of source files + self.logger.info("Computing hashes of reference files...") + file_hashes = self._compute_directory_hash(self.reference_files_dir) + + loop_device = None + temp_mount = None + + try: + # Create and format image + self._create_empty_image(image_path) + self._format_ntfs(image_path) + + # Set up loop device + loop_device = self._setup_loop_device(image_path) + + # Create temporary mount point + temp_mount = Path(tempfile.mkdtemp(prefix="ntfs_build_")) + + # Mount, copy files, unmount + self._mount_filesystem(loop_device, temp_mount) + self._copy_files(temp_mount) + + # Sync to ensure all data is written + subprocess.run(['sync', str(temp_mount)], check=True) + + self._unmount_filesystem(temp_mount) + + # Save metadata + self._save_metadata(image_path, file_hashes) + + self.logger.info(f"Successfully created reference NTFS image: {image_path}") + self.logger.info(f"Image size: {image_path.stat().st_size / (1024*1024):.1f} MB") + + # Compress image + if self.compress: + self.logger.info("Compressing image with gzip...") + with open(image_path, 'rb') as f_in, gzip.open(f"{image_path}.gz", 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + image_path.unlink() # Remove uncompressed image + + finally: + # Clean up + if loop_device: + self._cleanup_loop_device(loop_device) + + if temp_mount and temp_mount.exists(): + shutil.rmtree(temp_mount) + + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser(description="Build reference NTFS image for E2E tests") + parser.add_argument('--size', type=int, default=100, + help='Image size in MB (default: 100)') + parser.add_argument('--output', type=str, + default='tests/data/reference_ntfs.img', + help='Output image path (default: tests/data/reference_ntfs.img)') + + args = parser.parse_args() + + builder = NTFSImageBuilder(size_mb=args.size, output_path=args.output) + + try: + builder.build() + print(f"\n✓ Success! Reference NTFS image created at: {args.output}") + print(f"✓ Metadata saved at: {args.output.replace('.img', '.json')}") + print("\nNext steps:") + print("1. Add the .img file to Git LFS: git lfs track '*.img'") + print("2. Commit both the image and metadata files") + print("3. The E2E tests will now use this reference image") + + except Exception as e: + print(f"\n✗ Error: {e}") + return 1 + + return 0 + + +if __name__ == '__main__': + exit(main())