def _get_latest_firmware_version(): """ Return the latest released version of the Octowire firmware. :return: String """ firmware_release_url = 'https://api.bitbucket.org/2.0/repositories/octowire/octowire-firmware-releases/' \ 'refs/tags?sort=-name&pagelen=1' resp = requests.get(firmware_release_url) if resp.status_code == 200: latest_release = resp.json()["values"] if latest_release: return latest_release[0]["name"] else: Logger().handle('No release found for the Octowire firmware', Logger.ERROR) return None elif resp.status_code == 429: Logger().handle( "API rate limit reached, please try updating again later.", Logger.ERROR) return None else: Logger().handle('Unable to get the latest firmware version', Logger.ERROR) return None
def version(owf_instance, *args): """ Displays the Framework, Library and Firmware versions :param owf_instance: Octowire framework instance (self). :param args: Varargs command options. :return: Nothing """ versions = [] # Identify the current Octowire Framework and Library versions for pkg in ["octowire-framework", "octowire-lib"]: pkg_version = _get_package_version(pkg) if pkg_version: versions.append({"Name": pkg, "Version": pkg_version}) # Identify the current Octowire firmware version Logger().handle( "Attempting to detect the Octowire to identify the firmware version...", Logger.HEADER) s_octowire = detect_and_connect() if s_octowire is not None: Logger().handle("Identifying the current firmware version...") # Instantiate the Octowire octowire_instance = Octowire(serial_instance=s_octowire) # Get the current firmware version fw_version = octowire_instance.get_octowire_version().split()[1] versions.append({"Name": "Octowire firmware", "Version": fw_version}) else: versions.append({ "Name": "Octowire firmware", "Version": "Octowire not detected" }) owf_instance.logger.print_tabulate(versions, headers={ "Name": "Name", "Version": "Version" })
def __init__(self, serial_instance=None): """ Init function. :param serial_instance: Octowire USB CDC Serial (pyserial) instance. """ self.serial_instance = serial_instance self.logger = Logger()
def __init__(self): self.bitbucket_base_url = 'https://api.bitbucket.org/2.0' self.bitbucket_download_url = "https://bitbucket.org/octowire/{}/get/{}.tar.gz" self.logger = Logger() self.not_updated = [] self.to_update = [] self.to_install = []
def __init__(self, owf_config): self.logger = Logger() self.config = owf_config self.owf_serial = None self.meta = { 'name': '', 'version': '', 'description': '', 'author': '' } self.options = {} self.advanced_options = {} self.dependencies = []
def main(): parser = argparse.ArgumentParser() parser.add_argument('-m', '--moduleonly', help='Only update/install modules, not the framework', action="store_true") args = parser.parse_args() try: is_admin = os.getuid() == 0 except AttributeError: is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 if not is_admin and not is_venv(): Logger().handle( "Please run 'owfupdate' as root or use a virtualenv. Exiting...", Logger.ERROR) exit(1) print( '-----------------------------------------------------------------------' ) print( '----------------Fetching and installing available modules--------------' ) print( '-----------------------------------------------------------------------' ) if args.moduleonly: OWFUpdate().update(update_framework=False) else: OWFUpdate().update(update_framework=True)
def _print_bootloader_instruction(): Logger().handle( "The Octowire must now be placed in 'bootloader' mode. Follow the steps below to enter bootloader " "mode:\n" " 1. Press and maintain the 'User' button on the Octowire\n" " 2. Press the 'Reset' button while keeping 'User' pressed until the blue LED lights up\n" " 3. Press enter on your keyboard to continue", Logger.USER_INTERACT)
def _download_release(firmware_version): """ Download a firmware release. :param firmware_version: The firmware version number. :return: Filename or None. """ Logger().handle(f"Downloading 'octowire-firmware' v{firmware_version}...", Logger.INFO) resp = requests.get( f"https://bitbucket.org/octowire/octowire-firmware-releases/get/{firmware_version}.tar.gz", stream=True) if resp.status_code == 200: file = tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False) file.write(resp.content) file.close() return file.name else: Logger().handle( "failed to download firmware - HTTP response code: {}".format( resp.status_code), Logger.ERROR) return None
def detect_octowire(verbose=True): """ Iterate over serial devices to find the Octowire, returning the serial port. :return: Octowire port or None. """ ports_list = list_ports.comports(include_links=True) for port in ports_list: if port.vid == 0xc0de and port.pid == 0x0c70: if verbose: Logger().handle("Octowire found: {}".format(port.device), Logger.RESULT) return port.device if port.vid == 0xc0de and port.pid == 0xb007: if verbose: Logger().handle( "Octowire found in bootloader mode, please press the reset button and retry...", Logger.ERROR) return None if verbose: Logger().handle("Octowire not found...", Logger.ERROR) return None
def _get_package_version(package_name): """ Return the version of a given Python package. :param package_name: Package name. Return: Version string or None """ try: return pkg_resources.get_distribution(package_name).version except pkg_resources.DistributionNotFound: Logger().handle(f"Unable to find package '{package_name}'", Logger.ERROR) return None
def _check_dependencies(self): """ Verify the module dependencies. :return: Bool """ update_msg = "Please run 'owfupdate' in your terminal to install the latest module version" for dependency in self.dependencies: req = pkg_resources.Requirement.parse(dependency) try: pkg_resources.get_provider(req) except pkg_resources.RequirementParseError as err: self.logger.handle(err, self.logger.ERROR) return False except pkg_resources.DistributionNotFound: self.logger.handle( "The '{}' distribution was not found and is required " "by the module".format(dependency), self.logger.ERROR) self.logger.handle(update_msg, self.logger.USER_INTERACT) return False except pkg_resources.VersionConflict: dist = pkg_resources.get_distribution(req.name) Logger().handle( "Version conflict: '{}' is required. Version '{}' is currently " "installed".format(dependency, dist.version), Logger().ERROR) self.logger.handle(update_msg, self.logger.USER_INTERACT) return False # Recursively check sub_module dependencies if req.name == "octowire-lib": sub_module = import_module("octowire") else: sub_module = import_module(req.name) for d_amodule_class in get_amodule_class(sub_module, req.name, AModule): amodule_class = d_amodule_class["class"](load_config()) if not amodule_class._check_dependencies(): return False return True
def _is_octowire_in_bootloader(): """ Iterate over serial devices to find the Octowire, returning the serial port. :return: Octowire port if found in normal mode, False otherwise. """ ports_list = list_ports.comports(include_links=True) for port in ports_list: if port.vid == 0xc0de and port.pid == 0xb007: Logger().handle("Octowire detected in bootloader mode.", Logger.SUCCESS) return port.device elif port.vid == 0xc0de and port.pid == 0x0c70: Logger().handle("Please enter bootloader mode on the Octowire.", Logger.ERROR) Logger().handle( "Follow the steps below:\n" " 1. Press and maintain the 'User' button on the Octowire\n" " 2. Press the 'Reset' button while keeping 'User' pressed until the blue LED lights up\n" " 3. Press enter on your keyboard", Logger.USER_INTERACT) return None Logger().handle( "Octowire was not detected. Please ensure the Octowire is properly plugged into your computer and " "try again.", Logger.ERROR) return None
def main(): parser = argparse.ArgumentParser() parser.add_argument( '-s', '--file', help='A file containing framework commands (one per line)') args = parser.parse_args() t_width, _ = shutil.get_terminal_size() welcome(terminal_width=t_width) if t_width < 95: Logger().handle( "Please consider using a terminal width >= 95 for a better experience.", Logger.WARNING) instance = Framework() instance.run(args.file)
def erase_flash(octowire_ser, fw): """ Erase flash (for given firmware size) :param octowire_ser: Octowire serial instance :param fw: Buffer containing the firmware image :return: Bool """ print(f"{Colors.OKBLUE}Erasing flash before writing to it...{Colors.ENDC}") octowire_ser.write(bytes("x%i\n" % len(fw), "ascii")) res = octowire_ser.readline().strip().decode() if res.startswith("ERROR"): Logger().handle( "An Error occurred during the erase process. Exiting...", Logger.ERROR) return False return True
def _update_process(firmware_version): _print_bootloader_instruction() while True: try: input("Press enter to continue or 'Ctrl-C' to abort:") port = _is_octowire_in_bootloader() if port: # Attempt to open Serial port octowire_ser = None try: octowire_ser = serial.Serial(port, 115200, timeout=1) except serial.SerialException as e: print( f"{Colors.FAIL}Error opening serial port {port}: {e}{Colors.ENDC}" ) break _manage_install(octowire_ser, firmware_version) break else: continue except KeyboardInterrupt: Logger().handle("\nAborted.", Logger.ERROR) break
def octowire_serial(serial_name): """ Connect to the Octowire with the correct baudrate value and set the current datetime. :param serial_name: Octowire serial port. :return: Serial instance or None if error. """ try: s_octowire = serial.Serial(serial_name, 7372800, timeout=1) t_octowire = Time(s_octowire) now = datetime.now() octo_str_date = t_octowire.get_time() if (datetime.strptime(octo_str_date.decode(), "%Y/%m/%d %H:%M:%S") - now).seconds > 60: t_octowire.set_time(year=now.year, month=now.month, day=now.day, hour=now.hour, minute=now.minute, second=now.second) return s_octowire except serial.SerialException as err: Logger().handle("Connection error: {}".format(err), Logger.ERROR) return None
def write_flash(octowire_ser, fw): """ Write a chunk of Flash :param octowire_ser: Octowire serial instance :param fw: Buffer containing the firmware image :return: Bool """ print(f"{Colors.OKBLUE}Writing to flash...{Colors.ENDC}") w_data = fw addr = 0 while len(w_data): chunk = w_data[:_PAGE_SIZE] w_data = w_data[_PAGE_SIZE:] octowire_ser.write( bytes("w%s,%s\n" % (hex(addr)[2:], chunk.hex()), "ascii")) res = octowire_ser.readline().strip().decode() if res.startswith("ERROR"): Logger().handle( "An Error occurred during the write process. Exiting...", Logger.ERROR) return False addr += len(chunk) return True
def fwupdate(owf_instance, *args): """ Download and install the latest Octowire firmware. This command automatically detects the Octowire. :param owf_instance: Octowire framework instance (self). :param args: Varargs command options. :return: Nothing """ Logger().handle("Attempting to detect Octowire...", Logger.HEADER) s_octowire = detect_and_connect() if s_octowire is not None: Logger().handle("Identifying currently installed firmware version...") # Instantiate the Octowire octowire_instance = Octowire(serial_instance=s_octowire) # Get the current firmware version current_version = pkg_resources.parse_version( octowire_instance.get_octowire_version().split()[1]) if current_version: latest_version = _get_latest_firmware_version() if latest_version: latest_version = pkg_resources.parse_version(latest_version) if latest_version > current_version: Logger().handle( f"A new firmware version is available ({current_version} -> {latest_version})", Logger.RESULT) res = prompt("Do you wish to proceed with update? [Y/n]:") if res.upper() == "Y" or res == "": _update_process(latest_version) else: Logger().handle( f"The latest firmware version is already installed.", Logger.WARNING) res = prompt("Do you wish to re-install it? [y/N]:") if res.upper() == "Y": _update_process(latest_version) else: Logger().handle("Unable to get the latest firmware version.") else: Logger().handle( "Unable to get the current Octowire version, please press the Reset button and retry...", Logger.ERROR)
class OWFUpdate: def __init__(self): self.bitbucket_base_url = 'https://api.bitbucket.org/2.0' self.bitbucket_download_url = "https://bitbucket.org/octowire/{}/get/{}.tar.gz" self.logger = Logger() self.not_updated = [] self.to_update = [] self.to_install = [] def _get_installed_modules(self): """ Return a dict of currently installed module(s). :return: A dict of currently installed module(s) {'module_name': 'version', ...}. """ module_name = "owfmodules" installed_modules = {} try: package = import_module(module_name) except ImportError: self.logger.handle('No modules currently installed', Logger.ERROR) return installed_modules for loader, module, is_pkg in pkgutil.walk_packages( package.__path__, prefix=package.__name__ + '.'): try: imported_module = import_module(module) for x in dir(imported_module): obj = getattr(imported_module, x) if inspect.isclass(obj) and issubclass( obj, AModule) and obj is not AModule: installed_modules[ module] = pkg_resources.get_distribution( module).version except ImportError: self.logger.handle( 'Error while dynamically importing package "{}"... Unable to update it' .format(module), Logger.ERROR) self.not_updated.append(module) return installed_modules def _get_available_modules(self): """ Return a dict of modules that have a release on the octowire-framework project. :return: A dict of available modules {'module_name': 'version', ...}. """ self.logger.handle( "Obtaining the list of package releases... This may take a while...", self.logger.USER_INTERACT) modules = {} resp = requests.get('{}{}'.format( self.bitbucket_base_url, '/repositories/octowire/?q=project.key="MOD"')) while True: if resp.status_code == 200: for pkg in resp.json()["values"]: pkg_tag_url = pkg["links"]["tags"]["href"] resp_tags = requests.get(pkg_tag_url + "?sort=-name&pagelen=1") if resp_tags.status_code == 200: latest_release = resp_tags.json()["values"] if latest_release: modules[pkg["name"]] = latest_release[0]["name"] if "next" in resp.json(): resp = requests.get('{}'.format(resp.json()["next"])) else: break elif resp.status_code == 429: self.logger.handle( "API rate limit reached, please try updating again later.", self.logger.ERROR) break else: self.logger.handle( "failed to load module list - HTTP response code: {}". format(resp.status_code), self.logger.ERROR) break if not modules: self.logger.handle('No releases/modules found', Logger.ERROR) return modules def _get_latest_framework_version(self): """ Return the latest release version of the Octowire framework. :return: String """ module_release_url = self.bitbucket_base_url + '/repositories/octowire/octowire-framework/' \ 'refs/tags?sort=-name&pagelen=1' resp = requests.get(module_release_url) if resp.status_code == 200: latest_release = resp.json()["values"] if latest_release: return latest_release[0]["name"] else: self.logger.handle( 'No release found for the Octowire framework', Logger.ERROR) return None else: self.logger.handle('Unable to get the latest framework release', Logger.ERROR) return None def _get_latest_library_version(self): """ Return the latest release version of the Octowire-lib package. :return: String """ pypi_api_url = 'https://pypi.org/pypi/octowire-lib/json' resp = requests.get(pypi_api_url) if resp.status_code == 200: releases = collections.OrderedDict( sorted(resp.json()["releases"].items())) latest_release_version = list(releases.keys())[-1] if latest_release_version: return latest_release_version else: self.logger.handle( 'No release found for the octowire-lib package', Logger.ERROR) return None else: self.logger.handle('Unable to get the latest octowire-lib release', Logger.ERROR) return None @staticmethod def _get_filename_from_cd(cd): """ Get filename from content-disposition. :param cd: Content-Disposition HTTP header. :return: filename from Content-Disposition or None. """ if not cd: return None fname = re.findall('filename=(.+)', cd) if len(fname) == 0: return None return fname[0] def _download_release(self, package_name, package_version): """ Download the latest release of a package (module or framework). :param package_version: The package version. :param package_name: The name of the package to downloade (module or framework). :return: Filename or None. """ self.logger.handle("Downloading {} v{}...".format( package_name, package_version)) resp = requests.get(self.bitbucket_download_url.format( package_name, package_version), stream=True) if resp.status_code == 200: file = tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False) file.write(resp.content) file.close() return file.name else: self.logger.handle( "failed to download the latest release for the module '{}' - " "HTTP response code: {}".format(package_name, resp.status_code), self.logger.ERROR) return None @staticmethod def _extract_tarball(filename): """ Extract the specified tarball. :param filename: The tarball file path. :return: Directory path of the extracted archive. """ tar = tarfile.open(filename) path = re.sub(r'\.tar.gz$', '', filename) setup_dir = '{}/{}'.format(path, tar.getmembers()[0].name) tar.extractall(path) tar.close() return setup_dir def _manage_install(self, package_name, package_version): """ Manage the installation of the specified package (module or framework). :param package_name: The name of the package to install (module or framework). :param package_version: The package version to install (module or framework). :return: Bool: True if successfully installed, False otherwise. """ filename = self._download_release(package_name, package_version) python_path = sys.executable if filename: setup_dir = self._extract_tarball(filename) package_dir = setup_dir.split("/")[-1] setup_dir = '/'.join(setup_dir.split("/")[:-1]) try: if package_name != "octowire-framework": pipes = subprocess.Popen([ python_path, '-m', 'pip', 'install', '--upgrade', f"./{package_dir}" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=setup_dir) stdout, stderr = pipes.communicate() if pipes.returncode != 0: self.logger.handle( "Error while installing {} package: {}".format( package_name, stderr.strip()), Logger.ERROR) return False else: self.logger.handle( "'{}' successfully installed".format(package_name), Logger.SUCCESS) return True else: # This method is necessary to update the framework. Indeed, this allows releasing the owfupdate # executable in order to replace it. current_dir = pathlib.Path().absolute() log_file = current_dir / "framework_install.log" if os.path.isfile(setup_dir + "/update_framework.py"): if platform.system() == "Windows": subprocess.Popen( [ python_path, 'update_framework.py', '-p', str(os.getpid()), '-f', str(log_file) ], cwd=setup_dir, creationflags=subprocess.DETACHED_PROCESS) else: subprocess.Popen([ python_path, 'update_framework.py', '-p', str(os.getpid()), '-f', str(log_file) ], cwd=setup_dir) self.logger.handle( "The framework update was launched in background... check the following " "file to see if it was successfully updated: {}". format(str(log_file)), self.logger.WARNING) return True else: self.logger.handle( "The 'update_framework.py' file is missing from the installer package... " "Unable to update the octowire-framework package...", self.logger.ERROR) return False except subprocess.CalledProcessError as err: self.logger.handle( "The setup command failed for the '{}' module".format( package_name), Logger.ERROR) print(err.stderr) return False else: self.logger.handle( "Failed to download the latest release for '{}'".format( package_name), Logger.ERROR) return False def _update_framework(self): """ This function updates the Octowire framework. :return: Nothing """ latest_release_version = self._get_latest_framework_version() if latest_release_version: latest_release_version = pkg_resources.parse_version( latest_release_version) try: current_version = pkg_resources.parse_version( pkg_resources.get_distribution( 'octowire-framework').version) except pkg_resources.DistributionNotFound: current_version = pkg_resources.parse_version('') if latest_release_version > current_version: self.logger.handle( 'A new framework release is available, running update...', Logger.INFO) if not self._manage_install( 'octowire-framework', latest_release_version.base_version): self.not_updated.append('octowire-framework') else: self.logger.handle('Octowire framework is already up-to-date', Logger.SUCCESS) else: self.not_updated.append('octowire-framework') def _update_library(self): """ This function updates the octowire-lib package. :return: Nothing """ python_path = sys.executable latest_release_version = self._get_latest_library_version() if latest_release_version: latest_release_version = pkg_resources.parse_version( latest_release_version) try: current_version = pkg_resources.parse_version( pkg_resources.get_distribution('octowire-lib').version) except pkg_resources.DistributionNotFound: current_version = pkg_resources.parse_version('') if latest_release_version > current_version: self.logger.handle( 'A new octowire-lib release is available, running update...', Logger.INFO) pipes = subprocess.Popen([ python_path, '-m', 'pip', 'install', '--upgrade', 'octowire-lib' ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = pipes.communicate() if pipes.returncode != 0: self.logger.handle( "Error while installing the octowire-lib package: {}". format(stderr.strip()), Logger.ERROR) self.not_updated.append('octowire-lib') else: self.logger.handle("'octowire-lib' successfully installed", Logger.SUCCESS) else: self.logger.handle('Octowire library is already up-to-date', Logger.SUCCESS) else: self.not_updated.append('octowire-lib') def update(self, update_framework=None): """ This script checks all released Octowire modules and compares them with currently installed modules. If an update is available, this script installs it. Moreover, if a module is available and not installed, it will be installed. :param update_framework: If True, check whether a framework update is available. :return: Nothing """ try: available_modules = self._get_available_modules() installed_modules = self._get_installed_modules() for name, version in available_modules.items(): installed_module_version = installed_modules.get(name, None) if installed_module_version is not None: if pkg_resources.parse_version( installed_module_version ) < pkg_resources.parse_version(version): self.to_update.append({ "name": name, "version": version }) else: self.to_install.append({"name": name, "version": version}) if not self.to_update and not self.to_install: self.logger.handle("Everything is up-to-date", Logger.SUCCESS) for module in self.to_update: self.logger.handle( "Updating module '{}' to version {}".format( module["name"], module["version"]), Logger.INFO) if not self._manage_install(module["name"], module["version"]): self.not_updated.append(module["name"]) for module in self.to_install: self.logger.handle( "Installing module '{}' version {}".format( module["name"], module["version"]), Logger.INFO) if not self._manage_install(module["name"], module["version"]): self.not_updated.append(module["name"]) if len(self.not_updated) > 0: self.logger.handle( "Unable to update/install the following package(s):", Logger.ERROR) for module in self.not_updated: print(" - {}".format(module)) # Update the octowire-lib package self._update_library() if update_framework: self._update_framework() except requests.exceptions.ConnectionError: self.logger.handle( "Failed to reach servers. Please check your internet connection.", Logger.ERROR)
def __init__(self): self.logger = Logger() self.not_removed = []
class Octowire: """ Octowire superclass. Each protocol should inherit from it. It implements basic functionality. """ def __init__(self, serial_instance=None): """ Init function. :param serial_instance: Octowire USB CDC Serial (pyserial) instance. """ self.serial_instance = serial_instance self.logger = Logger() def is_connected(self): """ Check octowire serial port connection status. :return: Bool """ if self._is_serial_instance(): if self.serial_instance.is_open: return True else: self.logger.handle("Serial port is a valid serial instance but no connection was detected.", self.logger.ERROR) return False else: self.logger.handle("{}{}".format(type(self.serial_instance), " is not a Serial instance."), self.logger.ERROR) return False def _is_serial_instance(self): """ Check whether serial_port is a valid Serial instance. :return: """ return isinstance(self.serial_instance, serial.Serial) def ensure_binary_mode(self): """ Ensure that the Octowire is in binary mode. Otherwise enter it. :return: bool """ if self.is_connected(): current_mode = self.mode if current_mode == 't': binmode_opcode = b"binmode\n" self.serial_instance.write(binmode_opcode) time.sleep(1) # read local echo self.serial_instance.read(self.serial_instance.in_waiting) if self.mode == 'b': return True else: self.logger.handle("Unable to switch the Octowire to binary mode.", self.logger.ERROR) return False elif current_mode == 'b': return True else: return False def ensure_text_mode(self): """ Ensure that the Octowire is in text mode. Otherwise enter it. :return: bool """ if self.is_connected(): current_mode = self.mode if current_mode == 'b': mode_opcode = b"\x00\x00\x01" self.serial_instance.write(mode_opcode) time.sleep(1) # read local echo self.serial_instance.read(self.serial_instance.in_waiting) if self.mode == 't': return True else: self.logger.handle("Unable to switch the Octowire to text mode.", self.logger.ERROR) return False elif current_mode == 't': return True else: return False def octowire_status_is_valid(self): """ Determine whether Octowire is in a valid mode (b or t). :return: Bool """ if self._is_serial_instance(): octowire_mode = self.mode if octowire_mode in ["t", "b"]: return True else: self.logger.handle("Invalid string mode received", self.logger.ERROR) return False else: self.logger.handle("The serial_instance parameter is not a valid Serial instance.", self.logger.ERROR) return False def get_octowire_version(self): """ Return the Octowire version. :return: string (Octowire version). """ if self.octowire_status_is_valid(): version_opcode = b"\x00\x00\x02" mode = self.mode # If the Octowire is in text mode, enter in binary mode if mode == "t": self.ensure_binary_mode() # Get Octowire version self.serial_instance.write(version_opcode) # First, read the command status status = self.serial_instance.read(1) if status == b"\x00": # Then, read 4 bytes to get the response size sent by the Octowire size = self.serial_instance.read(4) if int.from_bytes(size, byteorder='little') > 0: # Finally, read the Octowire version string version = self.serial_instance.read(struct.unpack("<L", size)[0]) return version.decode() else: self.logger.handle("Invalid version size returned by the Octowire.", self.logger.ERROR) return None else: self.logger.handle("Error while trying to get Octowire version.", self.logger.ERROR) return None else: return None def _read_response_code(self, operation_name=None, disable_timeout=False): """ This function handles reading the response code. :param operation_name: String Octowire operation name. :param disable_timeout: If true, disable the Serial instance timeout. :return: Nothing """ timeout = self.serial_instance.timeout if disable_timeout: self.serial_instance.timeout = None status = self.serial_instance.read(1) self.serial_instance.timeout = timeout if status != b"\x00": raise Exception("Operation '{}' returned an error.".format(operation_name)) def _get_size_read_data(self, operation_name=None): """ This function receives the 4 bytes data size to receive from the Octowire and returns it. :param operation_name: String Octowire operation name. :return: Size of the next data chunk to read. :rtype: int """ resp = self.serial_instance.read(4) if not resp: raise Exception("Unable to get the size of the data to" " receive from the Octowire (Operation: {}).".format(operation_name)) return struct.unpack("<L", resp)[0] def _read_chunk(self, chunk_size, operation_name=None): """ This function reads a chunk of data. :param chunk_size: The data length to read (bytes). :param operation_name: Octowire operation name. :return: the data chunk that was read. :rtype: bytes """ chunk = self.serial_instance.read(chunk_size) if not chunk: raise Exception("No data received from the Octowire (Operation: {}).".format(operation_name)) return chunk def _read_data(self, expected_size=None, operation_name=None): """ This function handles data reception. :param operation_name: Octowire operation name. :param expected_size: The expected size to read. :return: Data read from the Octowire. :rtype: bytes """ data = bytearray() if expected_size is not None: while expected_size > 0: chunk_size = self._get_size_read_data(operation_name) data.extend(self._read_chunk(chunk_size, operation_name=operation_name)) expected_size = expected_size - chunk_size # When the size of data to receive is not known, for get Octowire version for example, read only the first chunk else: chunk_size = self._get_size_read_data(operation_name) data.extend(self._read_chunk(chunk_size, operation_name=operation_name)) return data @property def mode(self): """ This function sends the 0x0a character to detect the current mode (binary or text). :return: 'b' or 't'. :rtype: string """ self.serial_instance.write(b"\x0a") time.sleep(0.1) if self.serial_instance.in_waiting: self.serial_instance.read(self.serial_instance.in_waiting) return "t" else: # If in bin mode, we have to terminate the command (args size + version opcode + empty arguments) self.serial_instance.write(b"\x00\x02" + 10 * b"\x00") self._read_response_code(operation_name="Get current Octowire's mode") self._read_data(operation_name="Detect current Octowire's mode") return "b" @mode.setter def mode(self, value): """ Allow to switch between the binary and text mode. :param value: 'b' for binary, 't' for text. """ if value in ["t", "b"]: if value == "t": if not self.ensure_text_mode(): raise Exception("Unable to switch the Octowire to text mode.") else: if not self.ensure_binary_mode(): raise Exception("Unable to switch the Octowire to binary mode.") else: raise ValueError("Invalid mode selector. 'b' or 't' is waiting.")
def miniterm(owf_instance=None, *args): """ Run a serial console session (using miniterm from serial package). :param args: Varargs command options. :param owf_instance: Octowire framework instance (self). :return: Nothing. """ if len(args) < 1: config = None else: config = args[0] if isinstance(args[0], configparser.ConfigParser) else None logger = Logger() filters = [] if owf_instance is None and config is None: logger.handle("You need to set owf_instance or config", logger.ERROR) return False if owf_instance is not None: config = owf_instance.config octowire_cfg = config['OCTOWIRE'] miniterm_cfg = config['MINITERM'] filters.append(miniterm_cfg['filters']) try: serial_instance = serial.serial_for_url( octowire_cfg['port'], octowire_cfg['baudrate'], parity=miniterm_cfg['parity'], rtscts=0, xonxoff=config.getboolean('MINITERM', 'xonxoff'), do_not_open=True) if not hasattr(serial_instance, 'cancel_read'): # enable timeout for alive flag polling if cancel_read is not available serial_instance.timeout = 1 if isinstance(serial_instance, serial.Serial): serial_instance.exclusive = True serial_instance.open() except serial.SerialException as e: logger.handle("could not open port {!r}: {}".format(octowire_cfg['port'], e), logger.ERROR) return False mt = Miniterm( serial_instance, echo=config.getboolean('MINITERM', 'echo'), eol=miniterm_cfg['eol'].lower(), filters=filters) mt.exit_character = chr(int(miniterm_cfg['exit_char'])) mt.menu_character = chr(int(miniterm_cfg['menu_char'])) mt.raw = miniterm_cfg['raw'] mt.set_rx_encoding(miniterm_cfg['serial_port_encoding']) mt.set_tx_encoding(miniterm_cfg['serial_port_encoding']) if not config.getboolean('MINITERM', 'quiet'): Logger().handle('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format( p=mt.serial)) toolbar = '--- Quit: <{}> | Menu: <{}> | Help: <{}> followed by <{}> ---\n'.format( key_description(int(miniterm_cfg['exit_char'])), key_description(int(miniterm_cfg['menu_char'])), key_description(int(miniterm_cfg['menu_char'])), key_description(int(miniterm_cfg['menu_char_help']))) print(toolbar) mt.start() try: mt.join(True) except KeyboardInterrupt: pass if not config.getboolean('MINITERM', 'quiet'): Logger().handle('\n--- exit ---\n') mt.join() mt.close()
class AModule(ABC): def __init__(self, owf_config): self.logger = Logger() self.config = owf_config self.owf_serial = None self.meta = { 'name': '', 'version': '', 'description': '', 'author': '' } self.options = {} self.advanced_options = {} self.dependencies = [] def __name__(self): """ Simply return the module name :return: Module name :rtype: string """ return self.meta["name"] def _check_dependencies(self): """ Verify the module dependencies. :return: Bool """ update_msg = "Please run 'owfupdate' in your terminal to install the latest module version" for dependency in self.dependencies: req = pkg_resources.Requirement.parse(dependency) try: pkg_resources.get_provider(req) except pkg_resources.RequirementParseError as err: self.logger.handle(err, self.logger.ERROR) return False except pkg_resources.DistributionNotFound: self.logger.handle( "The '{}' distribution was not found and is required " "by the module".format(dependency), self.logger.ERROR) self.logger.handle(update_msg, self.logger.USER_INTERACT) return False except pkg_resources.VersionConflict: dist = pkg_resources.get_distribution(req.name) Logger().handle( "Version conflict: '{}' is required. Version '{}' is currently " "installed".format(dependency, dist.version), Logger().ERROR) self.logger.handle(update_msg, self.logger.USER_INTERACT) return False # Recursively check sub_module dependencies if req.name == "octowire-lib": sub_module = import_module("octowire") else: sub_module = import_module(req.name) for d_amodule_class in get_amodule_class(sub_module, req.name, AModule): amodule_class = d_amodule_class["class"](load_config()) if not amodule_class._check_dependencies(): return False return True def connect(self): """ Connect to the Octowire using configured options. If owf_serial is a valid serial instance, skip the connexion part (manage by a parent module). :return: Nothing """ if not isinstance(self.owf_serial, serial.Serial) or not self.owf_serial.is_open: if self.config["OCTOWIRE"].getint("detect"): self.owf_serial = self._manage_connection() else: port = self.config["OCTOWIRE"]["port"] baudrate = self.config["OCTOWIRE"].getint("baudrate") timeout = self.config["OCTOWIRE"].getint("read_timeout") self.owf_serial = self._manage_connection( auto_connect=False, octowire_port=port, octowire_baudrate=baudrate, octowire_timeout=timeout) def _manage_connection(self, auto_connect=True, octowire_port=None, octowire_baudrate=None, octowire_timeout=1): """ Manage connection to the Octowire hardware and return a serial instance. :param auto_connect: Automatically detect and connect to the octowire if set to True. :param octowire_port: The Octowire port (required if auto_connect is False). :param octowire_baudrate: The Octowire baudrate. Default=7372800 (required if auto_connect is False). :param octowire_timeout: The Octowire timeout. Default=1 (required if auto_connect is False). :return: Serial instance or None. """ try: if auto_connect: return detect_and_connect() else: if not octowire_port: self.logger.handle( "If auto_detect is set to False, please specify the Octowire port.", self.logger.ERROR) return None return serial.Serial(port=octowire_port, baudrate=octowire_baudrate, timeout=octowire_timeout) except serial.SerialException as err: self.logger.handle( "Error during serial connection: {}".format(err)) return None def show_options(self, options): """ Print available options for the module to the console. :return: Nothing """ formatted_options = [] if len(options) > 0: self.print_meta() for option_name, option in options.items(): if option["Default"] != "" and option["Value"] == "": formatted_options.append({ 'Name': option_name, 'Value': option["Default"], 'Required': option["Required"], 'Description': option["Description"] }) else: formatted_options.append({ 'Name': option_name, 'Value': option["Value"], 'Required': option["Required"], 'Description': option["Description"] }) self.logger.print_tabulate(formatted_options, headers={ "Name": "Name", "Value": "Value", "Required": "Required", "Description": "Description" }) def print_meta(self): """ Print meta information of the module (author, module name, description). :return: Nothing """ self.logger.handle('Author: {}'.format(self.meta['author']), Logger.HEADER) self.logger.handle( 'Module name: {}, version {}'.format(self.meta['name'], self.meta['version']), Logger.HEADER) self.logger.handle('Description: {}'.format(self.meta['description']), Logger.HEADER) @abstractmethod def run(self): """ Main function prototype. """ pass
def __init__(self): self.logger = Logger()
class Validator: """ This class checks module options, verifying whether format is valid. """ def __init__(self): self.logger = Logger() def _args_validator(self, option_name, option): """ Check argument type validity & convert to the specified format. :param option: Module option. :return: bool """ try: # Do not validated empty value if option is not required if option["Value"] == "" and not option["Required"]: return True if option["Type"] == "int": if not isinstance(option["Value"], int): option["Value"] = int(option["Value"], 10) if option["Type"] == "float": if not isinstance(option["Value"], float): option["Value"] = float(option["Value"]) # Hex string to int elif option["Type"] == "hex": if not isinstance(option["Value"], int): option["Value"] = int(option["Value"], 16) elif option["Type"] == "bool": if not isinstance(option["Value"], bool): if str(option["Value"]).upper() == "FALSE": option["Value"] = False elif str(option["Value"]).upper() == "TRUE": option["Value"] = True else: raise ValueError( "option {}: True or False are expected.".format( option["Value"])) # Hex string to bytes elif option["Type"] == "hextobytes": if not isinstance(option["Value"], bytes): option["Value"] = bytes.fromhex(option["Value"]) # File to read elif option["Type"] == "file_r": if not os.access(option["Value"], os.R_OK): raise Exception( "{}: file does not exist or permission denied.".format( option["Value"])) # File to write elif option["Type"] == "file_w": dirname = os.path.dirname(option["Value"]) dirname = './' if dirname == '' else dirname if not os.access(dirname, os.W_OK): raise Exception("{}: permission denied.".format( option["Value"])) if option["Value"] == "None": option["Value"] = None except ValueError: self.logger.handle("Value error: {} is not a valid {}".format( option_name, option["Type"])) return False return True def check_args(self, options_dict): """ Check whether all arguments are defined by user, setting them to their default values otherwise (if available). :param options_dict: Module options dictionary. :return: bool """ if len(options_dict) > 0: for option_name, option in options_dict.items(): if option["Value"] == "": if option["Default"] == "" and option["Required"]: self.logger.handle( "OptionValidationError: The following options failed to validate: {}." .format(option_name), Logger.ERROR) return False else: option["Value"] = option["Default"] if not self._args_validator(option_name, option): return False return True
class OWFRemove: def __init__(self): self.logger = Logger() self.not_removed = [] def _get_installed_modules(self): """ Return a dict of currently installed module(s). :return: A dict of currently installed module(s) {'module_name': 'version', ...}. """ module_name = "owfmodules" installed_modules = {} try: package = import_module(module_name) except ImportError: return installed_modules for loader, module, is_pkg in pkgutil.walk_packages(package.__path__, prefix=package.__name__ + '.'): try: imported_module = import_module(module) for x in dir(imported_module): obj = getattr(imported_module, x) if inspect.isclass(obj) and issubclass(obj, AModule) and obj is not AModule: installed_modules[module] = pkg_resources.get_distribution(module).version except ImportError: self.logger.handle('Error while dynamically importing package "{}"... Unable to removed it' .format(module), Logger.ERROR) self.not_removed.append(module) return installed_modules @staticmethod def _create_uninstall_script(): """ Create the uninstall script that will be executed in a subprocess. :return: Bool """ file = tempfile.NamedTemporaryFile(mode="w+", suffix=".py", delete=False) file.write(script) file.close() return file.name def _manage_uninstall(self, package_name): """ Removing the specified package (module or framework). :param package_name: The name of the package to install (module or framework). :return: Bool: True if successfully removed, False otherwise. """ python_path = sys.executable current_dir = pathlib.Path().absolute() if package_name != "octowire-framework": pipes = subprocess.Popen([python_path, '-m', 'pip', 'uninstall', '-y', package_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = pipes.communicate() if pipes.returncode != 0: self.logger.handle("Error while removing the '{}' package: {}".format(package_name, stderr.strip()), Logger.ERROR) return False else: self.logger.handle("'{}' successfully removed".format(package_name), Logger.SUCCESS) return True else: # This method is necessary to remove the framework. Indeed, this allows releasing the owfremove # executable in order to removed it. script_name = self._create_uninstall_script() log_file = current_dir / "framework_remove.log" if platform.system() == "Windows": subprocess.Popen([python_path, script_name, '-p', str(os.getpid()), '-f', str(log_file)], creationflags=subprocess.DETACHED_PROCESS) else: subprocess.Popen([python_path, script_name, '-p', str(os.getpid()), '-f', str(log_file)]) self.logger.handle("The remove of the framework was launched in background... check the following " "file to see if it was successfully removed: {}".format(str(log_file)), self.logger.WARNING) return True def remove(self, remove_framework=None): """ This script checks all installed Octowire modules and remove it. :param remove_framework: If True, remove the octowire Framework. :return: Nothing """ installed_modules = self._get_installed_modules() if not installed_modules: self.logger.handle("No module seems installed", Logger.WARNING) for module_name, _ in installed_modules.items(): self.logger.handle(f"Removing module '{module_name}'..", Logger.INFO) if not self._manage_uninstall(module_name): self.not_removed.append(module_name) if len(self.not_removed) > 0: self.logger.handle("Unable to remove the following package(s):", Logger.ERROR) for module in self.not_removed: print(" - {}".format(module)) self.logger.handle("Please try to uninstall it manually with the following command: " "'pip3 uninstall owfmodules.<category>.<module_name>'", Logger.ERROR) if remove_framework: self._manage_uninstall("octowire-framework") self.logger.handle("User configuration files need to be manually removed; these are present in '~/.owf' " "directory for any user which has run the framework at least once.", self.logger.USER_INTERACT)