Source code for objutils.image

#!/usr/bin/env python
"""

"""

__version__ = "0.1.0"

__copyright__ = """
    objutils - Object file library for Python.

   (C) 2010-2025 by Christoph Schueler <cpu12.gems@googlemail.com>

   All Rights Reserved

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""

import enum
import sys
from bisect import bisect_right
from operator import attrgetter, eq
from typing import Optional, Union

from objutils.exceptions import InvalidAddressError
from objutils.section import Section, join_sections


#
# TODO: Use crypto hashes (comparison, optimized storage, ...)
#


[docs] class AddressSpace(enum.IntEnum): """Adress-space constants.""" AS_16 = 0 AS_24 = 1 AS_32 = 2 AS_64 = 3
[docs] class Image: """Manage images. An image is a collection of :class:`Section`s and meta-data. Parameters ---------- sections: iteratable (typically `list`or `tuple` The sections the image should initialized with. meta: object Arbitrary meta-data. """ def __init__(self, sections=None, join=True, meta=None): if meta is None: meta = {} if not sections: sections = [] elif isinstance(sections, Section) or hasattr(sections, "__iter__"): sections = list(sections) else: raise TypeError(f"Argument section is of wrong type {sections!r}") self._sections = sorted(sections, key=attrgetter("start_address")) self._join = join if join: self.join_sections() _validate_sections(self._sections) self.address = 0 # if meta and not isinstance(meta, MetaRecord): # raise TypeError("meta-data must be of instance 'MetaRecord'") self.meta = meta def __repr__(self): result = [] for segment in self.sections: result.append(repr(segment)) return "\n".join(result) __str__ = __repr__ def __len__(self): return sum(len(s) for s in self.sections) def __iter__(self): return iter(self.sections) def __getitem__(self, idx): return self.sections[idx] def __eq__(self, other): if len(self.sections) == len(other.sections): return all(eq(left, right) for left, right in zip(self.sections, other.sections, strict=True)) else: return False def __ne__(self, other): return not (self == other) def __contains__(self, addr): return any(addr in sec for sec in self.sections)
[docs] def hexdump(self, fp=sys.stdout): """ """ for idx, section in enumerate(self.sections): print(f"\nSection #{idx:04d}", file=fp) print("-" * 13, file=fp) section.hexdump(fp)
[docs] def _call_address_function(self, func_name, addr, *args, **kws): for section in self.sections: if addr in section: func = getattr(section, func_name) return func(addr, *args, **kws) raise InvalidAddressError(f"Address 0x{addr:08x} not in range.")
[docs] def read(self, addr, length, **kws): """Read bytes from image. Parameters ---------- addr: int Startaddress. length: int Number of bytes to read. Returns ------- bytes Raises ------ :class:`InvalidAddressError` if `addr` is out of range Note ---- if `addr` + `len` is out of range, result is silently truncated, i.e. without raising an exception. """ return self._call_address_function("read", addr, length, **kws)
[docs] def write(self, addr, data, **kws): """Write bytes to image. Parameters ---------- addr: int Startaddress. data: bytes Raises ------ :class:`InvalidAddressError` if `addr` is out of range """ self._call_address_function("write", addr, data, **kws)
[docs] def read_numeric(self, addr, dtype, **kws): """ """ return self._call_address_function("read_numeric", addr, dtype, **kws)
[docs] def write_numeric(self, addr, value, dtype, **kws): """ """ self._call_address_function("write_numeric", addr, value, dtype, **kws)
[docs] def read_numeric_array(self, addr, length, dtype, **kws): """ """ return self._call_address_function("read_numeric_array", addr, length, dtype, **kws)
[docs] def write_numeric_array(self, addr, data, dtype, **kws): # TODO: bounds-checking. """ """ self._call_address_function("write_numeric_array", addr, data, dtype)
[docs] def write_ndarray(self, addr, array, order=None, **kws): """ """ self._call_address_function("write_ndarray", addr, array, order=None, **kws)
[docs] def read_ndarray(self, addr, length, dtype, shape=None, order=None, **kws): """ """ return self._call_address_function("read_ndarray", addr, length, dtype, shape, order, **kws)
[docs] def read_string(self, addr, encoding="latin1", length=-1, **kws): """ """ return self._call_address_function("read_string", addr, encoding, length, **kws)
[docs] def write_string(self, addr, value, encoding="latin1", **kws): """ """ self._call_address_function("write_string", addr, value, encoding, **kws)
[docs] def _address_contained(self, address, length): """Check if address space exists. Parameters ---------- address: int length: int Returns ------- bool """ return address in self or (address + length - 1) in self
[docs] def insert_section( self, data: Union[bytes, bytearray, memoryview, str], start_address: Optional[int] = None, join: bool = True ): """Insert/add a new section to image. Parameters ---------- data: convertible to bytearray() -- s. :func:`_data_converter`. Bytes making up the section. start_address: int join: bool Join/merge adjacent section. Raises ------ :class:`InvalidAddressError` Notes ----- Overlapping sections are not supported. To update/replace a section use :meth:`update_section`. """ start_address = ( start_address if start_address is not None else self.address ) # If Address omitted, create continuous address space. if self._address_contained(start_address, len(data)): raise InvalidAddressError("Overlapping address-space") if isinstance(data, str): data = [ord(x) for x in data] # array.array('B',data) self._sections.append(Section(start_address, data)) if join: self.join_sections() self.address = start_address + len(data)
@property def sections(self): return self._sections
[docs] def get_section(self, address): """Get :class:`Section` containing `address`. Parameters ---------- address: int Returns ------- :class:`Section` Raises ------ :class:`InvalidAddressError` """ if address not in self: raise InvalidAddressError("Address not in range") result = bisect_right(self._sections, address, key=attrgetter("start_address")) return self._sections[result - 1]
[docs] def update_section(self, data, address=None): """ """ if not self._address_contained(address, len(data)): raise InvalidAddressError("Address-space not in range")
[docs] def delete_section(self, address=None): """ """
[docs] def join_sections(self): """ """ self._sections = join_sections(self._sections)
[docs] def split(self, at=None, equal_parts=None, remap=None): print("SPLIT-IMAGE", at, equal_parts, remap)
[docs] def _validate_sections(sections): """Test for required protocol""" ATTRIBUTES = ("start_address", "length", "data") if "__iter__" not in dir(sections): raise TypeError("Sections must be iteratable.") for section in sections: if not all(hasattr(section, attr) for attr in ATTRIBUTES): raise TypeError("Section '{0}' doesn't fulfills required protocol (missing attributes).")