├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── hydrabus_framework ├── __init__.py ├── core │ ├── Dispatcher.py │ ├── __init__.py │ ├── command │ │ ├── __init__.py │ │ ├── back.py │ │ ├── help.py │ │ ├── miniterm.py │ │ ├── quit.py │ │ ├── reset_hb.py │ │ ├── run.py │ │ ├── save_config.py │ │ ├── set_config.py │ │ ├── set_globals.py │ │ ├── set_options.py │ │ ├── show.py │ │ ├── unset_globals.py │ │ ├── unset_options.py │ │ └── use.py │ ├── config.py │ ├── engine.py │ └── utils │ │ ├── HBFUpdate.py │ │ ├── Validator.py │ │ └── __init__.py ├── hbfconsole ├── hbfupdate ├── modules │ ├── AModule.py │ └── __init__.py └── utils │ ├── Colors.py │ ├── __init__.py │ ├── hb_generic_cmd.py │ ├── logger.py │ └── pyHydrabus │ ├── __init__.py │ ├── auxpin.py │ ├── common.py │ ├── hydrabus.py │ ├── i2c.py │ ├── onewire.py │ ├── protocol.py │ ├── rawwire.py │ ├── smartcard.py │ ├── spi.py │ ├── swd.py │ ├── uart.py │ └── utils.py ├── requirements.txt ├── setup.py └── tests └── travis.txt /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" # current default Python on Travis CI 4 | - "3.7" 5 | - "3.7-dev" # 3.7 development branch 6 | # command to install framework and dependencies 7 | install: 8 | - python setup.py install 9 | # command to run tests 10 | script: 11 | - bash -c "hbfupdate" | cat 12 | - bash -c "hbfconsole -s tests/travis.txt | cat" 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hydrabus-framework/framework/75dacb5eb287a7306f9fae42dd03209c626ab3ff/CHANGELOG.md -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | ## Before starting 4 | 5 | Thanks for your interest in the Hydrabus-framework project! 6 | There are multiple ways to help beyond just writing code: 7 | 8 | * Submit bug and feature requests with detailed information about your issue or idea. 9 | * Help fellow users with open issues. 10 | * Submit an update, a brand new module or a module/core improvement. 11 | 12 | # Contributing to Hydrabus-framework 13 | 14 | Here's a short list of do's and don'ts to make sure your valuable contributions actually make it into Hydrabus-framework's master branch. 15 | If you do not care to follow these rules, your contribution will be closed. 16 | 17 | ## Code contributions 18 | 19 | * Follow the [PEP 8 style guide for python code](https://www.python.org/dev/peps/pep-0008/) 20 | * Do one commit for each modification/addition/improvement 21 | * Use explicit commit message 22 | * License your code as GPLv3 clause 23 | * Always start a new module using the [hbfmodules.skeleton](https://github.com/hydrabus-framework/hbfmodules.skeleton) base repository 24 | 25 | ## Create a new module 26 | 27 | Create an issue on [hbfmodules.skeleton](https://github.com/hydrabus-framework/hbfmodules.skeleton) with these details: 28 | 29 | * Module name (hbfmodules.CATEGORY.Name) 30 | * A short description 31 | * Module category (uart, jtag, spi, ...) 32 | 33 | If I accept the module, I will create a repository initiated with the [hbfmodules.skeleton](https://github.com/hydrabus-framework/hbfmodules.skeleton) repository. 34 | 35 | ### Useful information 36 | 37 | **Use as much as possible the `pyHydrabus` library for protocol interaction.** 38 | 39 | #### Initial steps 40 | 41 | 1. Fill the README.md file 42 | 2. Rename the `category` directory (uart, spi, i2c, ...) 43 | 3. Rename the `module_name.py` file 44 | 4. Modifying the setup.py file 45 | 46 | #### Start coding (.py) 47 | 48 | 1. Update meta variable (into \_\_init\_\_ method) 49 | 50 | ``` 51 | self.meta.update({ 52 | 'name': 'Module short description', 53 | 'version': '0.0.1', 54 | 'description': 'Module long description', 55 | 'author': 'Our name and/or email' 56 | }) 57 | ``` 58 | 59 | 2. Define necessary options (A list of dict) -> 60 | ``` 61 | self.options = [ 62 | {"Name": "Option1", "Value": "", "Required": True, "Type": "string/int/bool", 63 | "Description": "Option1 description", "Default": "default value if available"}, 64 | {"Name": "Option2", "Value": "", "Required": False, "Type": "string/int/bool", 65 | "Description": "Option2 description", "Default": self.config["HYDRABUS"]["read_timeout"]}, 66 | ] 67 | ``` 68 | 69 | 3. Fulfill the `run` method 70 | 71 | 72 | ## Pull Requests 73 | 74 | * Write "WIP" on your PR if submitting working yet unfinished code. 75 | * Include only one module per pull request. 76 | * Always start your work on the master branch to ensure using the latest and stable codebase. 77 | * Use, as possible, function under utils directory. 78 | * Include verification steps on your PR message (target, environment, ...). 79 | * Include a short summary of what your code/module does on your PR message. 80 | 81 | 82 | ## Bug fixes 83 | 84 | * Link any corresponding issues in the format of ```See #1234``` in your PR description 85 | * Explain in a few words how do you fix it in your PR message 86 | 87 | ## Bug reports 88 | 89 | * Write a detailed description of your bug and use a descriptive title. 90 | * Give many details as possible of your environment. 91 | * Include reproduction steps, stack traces and anything that might help us fix your bug. 92 | * Search for your bug in opened issues before opening a new issue. 93 | 94 | 95 | Thank you for taking a few moments to read this contributing guideline! -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:alpine 2 | 3 | RUN apk add -U git curl wget 4 | 5 | WORKDIR /opt 6 | 7 | RUN curl -s https://api.github.com/repos/hydrabus-framework/framework/releases/latest | grep "tarball_url" | cut -d '"' -f 4 | wget -qi - -O framework.tar.gz 8 | 9 | RUN tar xvzf framework.tar.gz && rm framework.tar.gz && mv hydrabus* framework 10 | 11 | WORKDIR framework 12 | 13 | RUN python setup.py install 14 | 15 | RUN hbfupdate 16 | 17 | ENTRYPOINT ["hbfconsole"] 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/hydrabus-framework/framework.svg?branch=master)](https://travis-ci.org/hydrabus-framework/framework) [![Python 3.6|3.7|3.7-dev](https://img.shields.io/badge/python-3.6|3.7-blue.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv3-important.svg)](https://github.com/hydrabus-framework/framework/master/LICENSE.md) 2 | 3 | # hydrabus-framework [v0.0.1] 4 | 5 | This project is not maintained anymore. I am now maintaining an equivalent framework, but for an upcoming alternative hardware tool: [Octowire](https://bitbucket.org/octowire/octowire-framework/src/master/). 6 | The Octowire hardware tool is not yet available for retail, more details coming soon. 7 | The main reasons for this decision are a better communication protocol between the hardware and the host, more interfaces available and a more powerful tool. 8 | 9 | ## Description 10 | 11 | This project is a framework around the [hydrabus project](https://hydrabus.com/). 12 | It provides multiple modules allowing you to work efficiently and save time on any hardware project. 13 | 14 | [![asciicast](https://asciinema.org/a/z9iBJsZMsDmSB94TiYWRrctKi.svg)](https://asciinema.org/a/z9iBJsZMsDmSB94TiYWRrctKi) 15 | 16 | ## Installation 17 | 18 | Clone this repository or get the latest release, then: 19 | 20 | ``` 21 | python3 setup.py install 22 | ``` 23 | 24 | ## Usage 25 | 26 | This framework work like metasploit. Simply run hbfconsole, load any available modules and enjoy! 27 | 28 | ## Use Docker image 29 | 30 | ### Build the image 31 | 32 | ``` 33 | docker build -t hbf . 34 | ``` 35 | 36 | ### Run the instance 37 | 38 | ``` 39 | docker run --rm -it -v /local/folder/:/remote/folder --device=/dev/ttyACM0:/dev/hydrabus hbf 40 | ``` 41 | 42 | ## Configuration explanation 43 | 44 | ```bash 45 | 46 | [HYDRABUS] 47 | port = /dev/ttyACM0 # Hydrabus device 48 | baudrate = 115200 # baudrate value to communicate with hydrabus device 49 | read_timeout = 1 # The read timeout value 50 | 51 | [MINITERM] 52 | parity = N # set parity (hydrabus communication, not device). one of {N, E, O, S, M} 53 | xonxoff = False # enable software flow control 54 | echo = False # enable local echo 55 | filters = default # Text transformation, see Miniterm man 56 | raw = False # Do no apply any encodings/transformations if True 57 | quiet = False # suppress non-error messages 58 | exit_char = 29 # Unicode of special character that is used to exit the application, default ctrl+] (29) 59 | menu_char = 20 # Unicode code of special character that is used to control miniterm (menu), default ctrl+t (20)** 60 | serial_port_encoding = UTF-8 # set the encoding for the serial port (Latin1, UTF-8, ...) 61 | eol = CR # end of line mode (CR, LF, CRLF) 62 | 63 | [THEME] # You can use HTML color code. For all possible theme value, see promp_toolkit manual https://python-prompt-toolkit.readthedocs.io/en/master/pages/advanced_topics/styling.html#style-strings 64 | base = #3399ff # Base prompt color [hbf] 65 | pound = #3399ff # Pound prompt color > 66 | module = #ff0000 bold # Selected module name color (baudrate) 67 | category = #ffffff # Selected module category color uart() 68 | 69 | ``` 70 | 71 | ## Contributing 72 | 73 | Follow the guideline on the [CONTRIBUTING.md](CONTRIBUTING.md) files 74 | 75 | ## Thanks 76 | 77 | I would like to thanks [@Nitr4x](https://github.com/Nitr4x) for his code review and ideas helping me to improve this framework. 78 | 79 | ## FAQ 80 | 81 | ### How to list available modules ? 82 | 83 | ``` [hbf] > show modules ``` 84 | 85 | ### What's a global options ? 86 | 87 | A global option is an option who will be used for every module loaded. 88 | Setting the options with the `setg` command will set the specified options globally for every module loaded. 89 | Unset a specific global option with `unsetg` command. 90 | It is also possible to print the previously defined global using `show global` command. 91 | 92 | ### Can you give me a typical example of use? 93 | 94 | You identify an SPI flash chip on a hardware device. You want to dump his memory. 95 | Simply run `hbfconsole` from a shell and follow these instructions: 96 | 97 | 1. List available module: 98 | 99 | ```[hbf] > show modules``` 100 | 101 | 2. Select the correct module: 102 | 103 | ```[hbf] > use spi/dump_eeprom``` 104 | 105 | 3. Show available options 106 | 107 | ```[hbf] spi(dump_eeprom) > show options``` 108 | 109 | 4. Set necessary options 110 | 111 | ```[hbf] spi(dump_eeprom) > set dump_file dump.bin``` 112 | 113 | 5. Run the module 114 | 115 | ```[hbf] spi(dump_eeprom) > run``` 116 | 117 | ### How to properly remove the framework along with installed modules? 118 | 119 | #### Manually method 120 | 121 | run hbfconsole and execute the `show modules` command to list installed modules. 122 | The module name returned by the framework is something like that (protocol/module_name). 123 | 124 | For each module, run `pip3 uninstall hbfmodules..`. 125 | 126 | Then, run `pip3 uninstall hydrabus_framework`. 127 | 128 | Finally, delete the `.hbf` directory in your home folder. 129 | 130 | ### hbfremove 131 | 132 | **Coming soon** 133 | -------------------------------------------------------------------------------- /hydrabus_framework/__init__.py: -------------------------------------------------------------------------------- 1 | __import__("pkg_resources").declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /hydrabus_framework/core/Dispatcher.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from inspect import signature 4 | 5 | from hydrabus_framework.core.command.back import back 6 | from hydrabus_framework.core.command.help import hbf_help 7 | from hydrabus_framework.core.command.miniterm import miniterm 8 | from hydrabus_framework.core.command.quit import hbf_exit 9 | from hydrabus_framework.core.command.reset_hb import reset_hb 10 | from hydrabus_framework.core.command.run import run_module 11 | from hydrabus_framework.core.command.save_config import save_config 12 | from hydrabus_framework.core.command.set_config import set_config 13 | from hydrabus_framework.core.command.set_globals import set_globals 14 | from hydrabus_framework.core.command.set_options import set_options 15 | from hydrabus_framework.core.command.show import show 16 | from hydrabus_framework.core.command.unset_globals import unset_globals 17 | from hydrabus_framework.core.command.unset_options import unset_options 18 | from hydrabus_framework.core.command.use import use 19 | 20 | 21 | __author__ = "Jordan Ovrè " 22 | 23 | 24 | class Dispatcher: 25 | def __init__(self): 26 | self.commands = [ 27 | {"name": "?", "descr": "Alias for help menu", "run": hbf_help, "arguments": {}}, 28 | {"name": "help", "descr": "Help menu", "run": hbf_help, "arguments": {}}, 29 | {"name": "show", "descr": "modules|options|config|global: Displays modules list, " 30 | "module options, global configuration or global options", "run": show, 31 | "arguments": {"options", "modules", "config", "global"}}, 32 | {"name": "use", "descr": "Load a module by name", "run": use, "arguments": {}}, 33 | {"name": "run", "descr": "Run the selected module", "run": run_module, "arguments": {}}, 34 | {"name": "back", "descr": "Move back from the current context", "run": back, "arguments": {}}, 35 | {"name": "set", "descr": "Set a context-specific variable to a value", "run": set_options, 36 | "arguments": {}}, 37 | {"name": "unset", "descr": "Unset a context-specific variable", "run": unset_options, "arguments": {}}, 38 | {"name": "setg", "descr": "Set a global variable to a value", "run": set_globals, "arguments": {}}, 39 | {"name": "unsetg", "descr": "Unset a global variable", "run": unset_globals, "arguments": {}}, 40 | {"name": "setc", "descr": "Set a config key to a value", "run": set_config, "arguments": {}}, 41 | {"name": "save", "descr": "Save the current config into hbf.cfg file", "run": save_config, "arguments": {}}, 42 | {"name": "miniterm", "descr": "Open a miniterm serial console", "run": miniterm, "arguments": {}}, 43 | {"name": "reset", "descr": "Reset hydrabus in main mode", "run": reset_hb, "arguments": {}}, 44 | {"name": "exit", "descr": "Exit the console", "run": hbf_exit, "arguments": {}} 45 | ] 46 | 47 | def handle(self, hbf_instance, command): 48 | """ 49 | User console command handler. 50 | :param hbf_instance: Hydrabus framework instance (self) 51 | :param command: User input 52 | """ 53 | args = command.split(" ") 54 | for cmd in self.commands: 55 | if command.split(" ")[0] == cmd["name"]: 56 | if len(signature(cmd["run"]).parameters) > 1: 57 | cmd["run"](hbf_instance, *args) 58 | else: 59 | cmd["run"](hbf_instance) 60 | break 61 | else: 62 | os.system(command) 63 | -------------------------------------------------------------------------------- /hydrabus_framework/core/__init__.py: -------------------------------------------------------------------------------- 1 | __import__("pkg_resources").declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/__init__.py: -------------------------------------------------------------------------------- 1 | __import__("pkg_resources").declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/back.py: -------------------------------------------------------------------------------- 1 | __author__ = "Jordan Ovrè " 2 | 3 | 4 | def back(hbf_instance): 5 | """ 6 | Move back from the current context. 7 | :param hbf_instance: Hydrabus framework instance (self). 8 | :return: Nothing. 9 | """ 10 | if len(hbf_instance.modules_history) > 0: 11 | previous_module = hbf_instance.modules_history.pop() 12 | hbf_instance.current_module_name = previous_module["path"] 13 | hbf_instance.current_module = previous_module["class"] 14 | else: 15 | if hbf_instance.current_module is not None: 16 | hbf_instance.current_module = None 17 | hbf_instance.current_module_name = None 18 | hbf_instance.update_completer_options_list() 19 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/help.py: -------------------------------------------------------------------------------- 1 | __author__ = "Jordan Ovrè " 2 | 3 | 4 | def hbf_help(hbf_instance): 5 | """ 6 | Print framework help on the console. 7 | :param hbf_instance: Hydrabus framework instance (self). 8 | :return: Nothing. 9 | """ 10 | print("\nCore Commands") 11 | print("=============") 12 | formatted_commands = [] 13 | for cmd in hbf_instance.dispatcher.commands: 14 | formatted_commands.append( 15 | { 16 | "Command": cmd["name"], 17 | "Description": cmd["descr"] 18 | } 19 | ) 20 | hbf_instance.logger.print_tabulate(formatted_commands, headers={"name": "Name", "descr": "Description"}) 21 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/miniterm.py: -------------------------------------------------------------------------------- 1 | import serial 2 | import sys 3 | 4 | from hydrabus_framework.utils.logger import Logger 5 | 6 | from serial.tools.miniterm import Miniterm 7 | 8 | 9 | __author__ = "Jordan Ovrè " 10 | 11 | 12 | def key_description(character): 13 | """ 14 | generate a readable description for a key. 15 | :param character: an ascii character. 16 | :return: readable description for key. 17 | """ 18 | ascii_code = ord(character) 19 | if ascii_code < 32: 20 | return 'Ctrl+{:c}'.format(ord('@') + ascii_code) 21 | else: 22 | return repr(character) 23 | 24 | 25 | def miniterm(hbf_instance=None, config=None): 26 | """ 27 | Run a serial console session (using miniterm from serial package). 28 | :param config: configparser instance. 29 | :param hbf_instance: Hydrabus framework instance (self). 30 | :return: Nothing. 31 | """ 32 | logger = Logger() 33 | filters = [] 34 | if hbf_instance is None and config is None: 35 | logger.handle("You need to set hbf_instance or config", logger.ERROR) 36 | return False 37 | if hbf_instance is not None: 38 | config = hbf_instance.config 39 | hydrabus_cfg = config['HYDRABUS'] 40 | miniterm_cfg = config['MINITERM'] 41 | filters.append(miniterm_cfg['filters']) 42 | 43 | while True: 44 | if hydrabus_cfg['port'] is None or hydrabus_cfg['port'] == '-': 45 | print('port is not given') 46 | try: 47 | serial_instance = serial.serial_for_url( 48 | hydrabus_cfg['port'], 49 | hydrabus_cfg['baudrate'], 50 | parity=miniterm_cfg['parity'], 51 | rtscts=None, 52 | xonxoff=config.getboolean('MINITERM', 'xonxoff'), 53 | do_not_open=True) 54 | 55 | if not hasattr(serial_instance, 'cancel_read'): 56 | # enable timeout for alive flag polling if cancel_read is not available 57 | serial_instance.timeout = 1 58 | 59 | if isinstance(serial_instance, serial.Serial): 60 | serial_instance.exclusive = True 61 | 62 | serial_instance.open() 63 | except serial.SerialException as e: 64 | logger.handle("could not open port {!r}: {}".format(hydrabus_cfg['port'], e), logger.ERROR) 65 | return False 66 | else: 67 | break 68 | 69 | miniterm = Miniterm( 70 | serial_instance, 71 | echo=config.getboolean('MINITERM', 'echo'), 72 | eol=miniterm_cfg['eol'].lower(), 73 | filters=filters) 74 | miniterm.exit_character = chr(int(miniterm_cfg['exit_char'])) 75 | miniterm.menu_character = chr(int(miniterm_cfg['menu_char'])) 76 | miniterm.raw = miniterm_cfg['raw'] 77 | miniterm.set_rx_encoding(miniterm_cfg['serial_port_encoding']) 78 | miniterm.set_tx_encoding(miniterm_cfg['serial_port_encoding']) 79 | 80 | if not config.getboolean('MINITERM', 'quiet'): 81 | sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format( 82 | p=miniterm.serial)) 83 | sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format( 84 | key_description(miniterm.exit_character), 85 | key_description(miniterm.menu_character), 86 | key_description(miniterm.menu_character), 87 | key_description('\x08'))) 88 | 89 | miniterm.start() 90 | try: 91 | miniterm.join(True) 92 | except KeyboardInterrupt: 93 | pass 94 | if not config.getboolean('MINITERM', 'quiet'): 95 | sys.stderr.write('\n--- exit ---\n') 96 | miniterm.join() 97 | miniterm.close() 98 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/quit.py: -------------------------------------------------------------------------------- 1 | __author__ = "Jordan Ovrè " 2 | 3 | 4 | def hbf_exit(hbf_instance): 5 | """ 6 | Quit Hydrabus framework. 7 | :return: Nothing. 8 | """ 9 | # TODO: close everything before exit 10 | exit(0) 11 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/reset_hb.py: -------------------------------------------------------------------------------- 1 | import serial 2 | 3 | from hydrabus_framework.utils.logger import Logger 4 | from hydrabus_framework.utils.hb_generic_cmd import hb_reset, hb_close, hb_connect 5 | 6 | 7 | __author__ = "Jordan Ovrè " 8 | 9 | 10 | def reset_hb(hbf_instance): 11 | """ 12 | Return hydrabus into console mode. 13 | :param hbf_instance: Hydrabus framework instance (self). 14 | :return: Nothing. 15 | """ 16 | logger = Logger() 17 | hydrabus_cfg = hbf_instance.config['HYDRABUS'] 18 | if hydrabus_cfg['port'] is None or hydrabus_cfg['port'] == '-': 19 | logger.handle('port is not set on the configuration (setc command)', Logger.ERROR) 20 | else: 21 | try: 22 | serial_instance = hb_connect(device=hydrabus_cfg['port'], baudrate=115200, timeout=1) 23 | except serial.SerialException as e: 24 | logger.handle("could not open port {!r}: {}".format(hydrabus_cfg['port'], e), logger.ERROR) 25 | return 26 | except UserWarning as err: 27 | logger.handle("{}".format(err), Logger.ERROR) 28 | return 29 | if isinstance(serial_instance, serial.Serial): 30 | hb_reset(serial_instance) 31 | hb_close(serial_instance) 32 | logger.handle("Reset sequence successfully sent to Hydrabus...", Logger.SUCCESS) 33 | else: 34 | logger.handle("Unable to reset Hydrabus due to connection error...", Logger.ERROR) 35 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/run.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | from hydrabus_framework.core.utils.Validator import Validator 4 | from hydrabus_framework.modules.AModule import AModule 5 | from hydrabus_framework.utils.logger import Logger 6 | 7 | 8 | __author__ = "Jordan Ovrè " 9 | 10 | 11 | def run_module(hbf_instance): 12 | """ 13 | Check all arguments and run the selected module. 14 | :param hbf_instance: Hydrabus framework instance (self). 15 | :return: Nothing. 16 | """ 17 | if isinstance(hbf_instance.current_module, AModule): 18 | ret = Validator().check_args(hbf_instance.current_module.options) 19 | if ret: 20 | try: 21 | hbf_instance.current_module.run() 22 | except KeyboardInterrupt: 23 | pass 24 | except: 25 | hbf_instance.logger.handle("Error running module", Logger.ERROR) 26 | traceback.print_exc() 27 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/save_config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | __author__ = "Jordan Ovrè " 5 | 6 | 7 | def save_config(hbf_instance): 8 | """ 9 | Save the current config into hbf.cfg file. 10 | :param hbf_instance: Hydrabus framework instance (self). 11 | :return: Nothing. 12 | """ 13 | hbf_config_path = Path.home() / '.hbf' / 'hbf.cfg' 14 | with hbf_config_path.open('w') as cfg_file: 15 | hbf_instance.config.write(cfg_file) 16 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/set_config.py: -------------------------------------------------------------------------------- 1 | from hydrabus_framework.utils.logger import Logger 2 | 3 | 4 | __author__ = "Jordan Ovrè " 5 | 6 | 7 | def set_config(hbf_instance, *args): 8 | """ 9 | Sets a config variable to a value. 10 | :param args: varargs command options. 11 | :param hbf_instance: Hydrabus framework instance (self). 12 | :return: Nothing. 13 | """ 14 | if len(args) < 4: 15 | hbf_instance.logger.handle("Bad usage", Logger.ERROR) 16 | hbf_instance.logger.handle("Usage: setc CATEGORY key value", Logger.INFO) 17 | else: 18 | if not hbf_instance.config.has_section(args[1]): 19 | hbf_instance.logger.handle("Config section '{}' does not exist".format(args[1]), Logger.ERROR) 20 | else: 21 | if not hbf_instance.config.has_option(args[1], args[2]): 22 | hbf_instance.logger.handle("Value '{}' does not exist in section '{}'" 23 | .format(args[2], args[1]), Logger.ERROR) 24 | else: 25 | hbf_instance.config[args[1]][args[2]] = args[3] 26 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/set_globals.py: -------------------------------------------------------------------------------- 1 | from hydrabus_framework.modules.AModule import AModule 2 | from hydrabus_framework.utils.logger import Logger 3 | 4 | 5 | __author__ = "Jordan Ovrè " 6 | 7 | 8 | def set_globals(hbf_instance, *args): 9 | """ 10 | Set a global variable to a value. 11 | :param args: varargs command options. 12 | :param hbf_instance: Hydrabus framework instance (self). 13 | :return: Nothing. 14 | """ 15 | if len(args) < 3: 16 | hbf_instance.logger.handle("Bad usage", Logger.ERROR) 17 | hbf_instance.logger.handle("Usage: setg option_name value", Logger.INFO) 18 | else: 19 | hbf_instance.global_options.update({args[1].upper(): args[2]}) 20 | msg = "{} ==> {}".format(args[1].upper(), args[2]) 21 | hbf_instance.logger.handle(msg) 22 | if isinstance(hbf_instance.current_module, AModule): 23 | for option in hbf_instance.current_module.options: 24 | if option["Name"].upper() == args[1].upper(): 25 | option["Value"] = args[2] 26 | hbf_instance.update_completer_global_options_list() 27 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/set_options.py: -------------------------------------------------------------------------------- 1 | from hydrabus_framework.modules.AModule import AModule 2 | from hydrabus_framework.utils.logger import Logger 3 | 4 | 5 | __author__ = "Jordan Ovrè " 6 | 7 | 8 | def set_options(hbf_instance, *args): 9 | """ 10 | Sets a context-specific variable to a value. 11 | :param args: varargs command options. 12 | :param hbf_instance: Hydrabus framework instance (self). 13 | :return: Nothing. 14 | """ 15 | if len(args) < 3: 16 | hbf_instance.logger.handle("Bad usage", Logger.ERROR) 17 | hbf_instance.logger.handle("Usage: set option_name value", Logger.INFO) 18 | else: 19 | if isinstance(hbf_instance.current_module, AModule): 20 | for option in hbf_instance.current_module.options: 21 | if option["Name"].upper() == args[1].upper(): 22 | option["Value"] = args[2] 23 | msg = "{} ==> {}".format(option["Name"], args[2]) 24 | hbf_instance.logger.handle(msg) 25 | break 26 | else: 27 | hbf_instance.logger.handle("option does not exist", Logger.ERROR) 28 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/show.py: -------------------------------------------------------------------------------- 1 | from hydrabus_framework.modules.AModule import AModule 2 | from hydrabus_framework.utils.logger import Logger 3 | 4 | 5 | __author__ = "Jordan Ovrè " 6 | 7 | 8 | def _print_usage(hbf_instance): 9 | """ 10 | Print show command usage 11 | :param hbf_instance: Hydrabus framework instance (self) 12 | :return: Nothing 13 | """ 14 | hbf_instance.logger.handle("Bad usage", Logger.ERROR) 15 | hbf_instance.logger.handle("Usage: show modules|options|config|global", Logger.INFO) 16 | 17 | 18 | def _check_args(hbf_instance, *args): 19 | """ 20 | Check the length of show commands arguments and its validity. 21 | :param hbf_instance: Hydrabus framework instance (self). 22 | :param args: vargs (show command argument). 23 | :return: Bool. 24 | """ 25 | if len(args) < 2: 26 | _print_usage(hbf_instance) 27 | return False 28 | if args[1] not in ["modules", "options", "config", "global"]: 29 | _print_usage(hbf_instance) 30 | return False 31 | return True 32 | 33 | 34 | def show(hbf_instance, *args): 35 | """ 36 | Displays modules list, module options or global config. Depending on arguments. 37 | :param args: varargs command options. 38 | :param hbf_instance: Hydrabus framework instance (self). 39 | :return: Nothing. 40 | """ 41 | # TODO: print by protocol separately 42 | if _check_args(hbf_instance, *args): 43 | if args[1] == "modules": 44 | print("\n================") 45 | print("| Modules list |") 46 | print("================\n") 47 | formatted_modules = [] 48 | for module in hbf_instance.modules: 49 | formatted_modules.append({"Path": module["path"], 50 | "Description": module["class"](hbf_instance.config).meta["description"]}) 51 | hbf_instance.logger.print_tabulate(formatted_modules, 52 | headers={"Path": "Path", "Description": "Description"}) 53 | elif args[1] == "config": 54 | print("\n================") 55 | print("| Config |") 56 | print("================\n") 57 | for section in hbf_instance.config: 58 | print("\n[{}]".format(section)) 59 | for key in hbf_instance.config[section]: 60 | print("{} = {}".format(key, hbf_instance.config[section][key])) 61 | elif args[1] == "global": 62 | print("\n==================") 63 | print("| Global options |") 64 | print("==================\n") 65 | for key, value in hbf_instance.global_options.items(): 66 | print(f"{key} ==> {value}") 67 | else: 68 | if isinstance(hbf_instance.current_module, AModule): 69 | hbf_instance.current_module.show_options() 70 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/unset_globals.py: -------------------------------------------------------------------------------- 1 | from hydrabus_framework.utils.logger import Logger 2 | 3 | 4 | __author__ = "Jordan Ovrè " 5 | 6 | 7 | def unset_globals(hbf_instance, *args): 8 | """ 9 | Unset a global variable. 10 | :param args: varargs command options. 11 | :param hbf_instance: Hydrabus framework instance (self). 12 | :return: Nothing. 13 | """ 14 | if len(args) < 2: 15 | hbf_instance.logger.handle("Bad usage", Logger.ERROR) 16 | hbf_instance.logger.handle("Usage: unsetg option_name", Logger.INFO) 17 | else: 18 | try: 19 | hbf_instance.global_options.pop(args[1].upper()) 20 | msg = "'{}' successfully unset".format(args[1].upper()) 21 | hbf_instance.logger.handle(msg) 22 | hbf_instance.update_completer_global_options_list() 23 | except KeyError: 24 | hbf_instance.logger.handle("'{}' is not declared as global variable".format(args[1]), Logger.ERROR) 25 | 26 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/unset_options.py: -------------------------------------------------------------------------------- 1 | from hydrabus_framework.modules.AModule import AModule 2 | from hydrabus_framework.utils.logger import Logger 3 | 4 | 5 | __author__ = "Jordan Ovrè " 6 | 7 | 8 | def unset_options(hbf_instance, *args): 9 | """ 10 | Unset a context-specific variable to a value. 11 | :param args: varargs command options. 12 | :param hbf_instance: Hydrabus framework instance (self). 13 | :return: Nothing. 14 | """ 15 | if len(args) < 2: 16 | hbf_instance.logger.handle("Bad usage", Logger.ERROR) 17 | hbf_instance.logger.handle("Usage: unset option_name", Logger.INFO) 18 | else: 19 | if isinstance(hbf_instance.current_module, AModule): 20 | for option in hbf_instance.current_module.options: 21 | if option["Name"].upper() == args[1].upper(): 22 | option["Value"] = "" 23 | msg = "{} ==> ".format(option["Name"]) 24 | hbf_instance.logger.handle(msg) 25 | break 26 | else: 27 | hbf_instance.logger.handle("option does not exist", Logger.ERROR) 28 | -------------------------------------------------------------------------------- /hydrabus_framework/core/command/use.py: -------------------------------------------------------------------------------- 1 | from hydrabus_framework.utils.logger import Logger 2 | 3 | 4 | __author__ = "Jordan Ovrè " 5 | 6 | 7 | def _print_usage(hbf_instance): 8 | """ 9 | Print use command usage. 10 | :param hbf_instance: Hydrabus framework instance (self). 11 | :return: Nothing. 12 | """ 13 | hbf_instance.logger.handle("Bad usage", Logger.ERROR) 14 | hbf_instance.logger.handle("Usage: use ", Logger.INFO) 15 | 16 | 17 | def _check_args(hbf_instance, *args): 18 | """ 19 | Check the length of use commands arguments and its validity. 20 | :param hbf_instance: Hydrabus framework instance (self). 21 | :param args: vargs (show command argument). 22 | :return: Bool. 23 | """ 24 | if len(args) < 2: 25 | _print_usage(hbf_instance) 26 | return False 27 | if args[1] == "": 28 | _print_usage(hbf_instance) 29 | return False 30 | return True 31 | 32 | 33 | def use(hbf_instance, *args): 34 | """ 35 | Method used to select a specific module. 36 | :param args: varargs command options. 37 | :param hbf_instance: Hydrabus framework instance (self). 38 | :return: Nothing. 39 | """ 40 | if _check_args(hbf_instance, *args): 41 | for module in hbf_instance.modules: 42 | if module["path"] == args[1]: 43 | if hbf_instance.current_module is not None and hbf_instance.current_module_name != module["path"]: 44 | hbf_instance.modules_history.append({"path": hbf_instance.current_module_name, 45 | "class": hbf_instance.current_module}) 46 | hbf_instance.current_module = module["class"](hbf_instance.config) 47 | hbf_instance.current_module_name = module["path"] 48 | for global_option_name, global_option_value in hbf_instance.global_options.items(): 49 | for module_option in hbf_instance.current_module.options: 50 | if global_option_name.upper() == module_option["Name"].upper(): 51 | module_option["Value"] = global_option_value 52 | hbf_instance.update_completer_options_list() 53 | break 54 | else: 55 | hbf_instance.logger.handle("module not found", Logger.ERROR) 56 | -------------------------------------------------------------------------------- /hydrabus_framework/core/config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | 3 | from pathlib import Path 4 | 5 | 6 | __author__ = "Jordan Ovrè " 7 | 8 | 9 | def create_default_config(config, hbf_dir, hbf_config_path): 10 | """ 11 | Create the default config file if it does not exist. 12 | :param config: configparser instance 13 | :param hbf_dir: pathlib object of the home directory of the current user 14 | :param hbf_config_path: pathlib object of the configuration path located in the home's current user 15 | :return: Nothing 16 | """ 17 | if not hbf_dir.is_dir(): 18 | hbf_dir.mkdir() 19 | config['HYDRABUS'] = { 20 | 'port': '/dev/ttyACM0', 21 | 'baudrate': '115200', 22 | 'read_timeout': 1 23 | } 24 | config['MINITERM'] = { 25 | 'parity': 'N', 26 | 'xonxoff': False, 27 | 'echo': False, 28 | 'filters': 'default', 29 | 'raw': False, 30 | 'quiet': False, 31 | 'exit_char': 0x1d, 32 | 'menu_char': 0x14, 33 | 'serial_port_encoding': 'UTF-8', 34 | 'eol': 'CR' 35 | } 36 | config['THEME'] = { 37 | # Prompt. 38 | 'user_input': '', 39 | 'base': '#3399ff', 40 | 'pound': '#3399ff', 41 | 'module': '#ff0000 bold', 42 | 'category': '#ffffff', 43 | } 44 | with hbf_config_path.open('w') as cfg_file: 45 | config.write(cfg_file) 46 | 47 | 48 | def load_config(): 49 | """ 50 | Load the existing configuration file and return it. 51 | :return: configparser file content 52 | """ 53 | config = configparser.ConfigParser(allow_no_value=True) 54 | hbf_dir = Path.home() / '.hbf' 55 | hbf_config_path = hbf_dir / 'hbf.cfg' 56 | if not hbf_dir.is_dir() or not hbf_config_path.is_file(): 57 | create_default_config(config, hbf_dir, hbf_config_path) 58 | config.read(str(hbf_config_path)) 59 | return config 60 | -------------------------------------------------------------------------------- /hydrabus_framework/core/engine.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import pkgutil 3 | import sys 4 | 5 | from importlib import import_module 6 | from pathlib import Path 7 | from prompt_toolkit.completion.nested import NestedCompleter 8 | from prompt_toolkit.shortcuts import PromptSession 9 | from prompt_toolkit.styles import Style 10 | 11 | from hydrabus_framework.core.config import load_config 12 | from hydrabus_framework.core.Dispatcher import Dispatcher 13 | from hydrabus_framework.modules.AModule import AModule 14 | from hydrabus_framework.utils.logger import Logger 15 | 16 | 17 | __author__ = "Jordan Ovrè " 18 | 19 | 20 | class HydraFramework: 21 | """ 22 | Framework core engine 23 | """ 24 | def __init__(self): 25 | self.logger = Logger() 26 | self.app_path = sys.path[0] 27 | self.current_module = None 28 | self.current_module_name = None 29 | self.dispatcher = Dispatcher() 30 | self.modules = self._list_modules() 31 | self.modules_history = [] 32 | self.config = load_config() 33 | self.completer_nested_dict = self._get_dict_completion() 34 | self.console_completer = NestedCompleter.from_nested_dict(self.completer_nested_dict) 35 | self.global_options = {} 36 | self.prompt_style = Style.from_dict({ 37 | # User input (default text), no value = system default. 38 | '': self.config['THEME']['user_input'], 39 | 40 | # Prompt. 41 | 'base': self.config['THEME']['base'], 42 | 'pound': self.config['THEME']['pound'], 43 | 'module': self.config['THEME']['module'], 44 | 'category': self.config['THEME']['category'], 45 | }) 46 | self.prompt = [ 47 | ('class:base', '[hbf] '), 48 | ('class:module', ''), 49 | ('class:category', ''), 50 | ('class:pound', '> '), 51 | ] 52 | 53 | def update_prompt(self): 54 | """ 55 | Update the user prompt. 56 | """ 57 | if self.current_module_name is not None: 58 | category = self.current_module_name.split("/")[0] 59 | module = self.current_module_name.split("/")[1] 60 | self.prompt = [ 61 | ('class:base', '[hbf] '), 62 | ('class:category', '{}'.format(category)), 63 | ('class:module', '({})'.format(module)), 64 | ('class:pound', '> '), 65 | ] 66 | else: 67 | self.prompt = [ 68 | ('class:base', '[hbf] '), 69 | ('class:category', ''), 70 | ('class:module', ''), 71 | ('class:pound', '> '), 72 | ] 73 | 74 | def _get_dict_completion(self): 75 | """ 76 | Get all command with associated arguments and return a dict for NestedCompleter. 77 | nested_dict = { 78 | 'set': { 79 | 'hydrabus': None, 80 | ... 81 | }, 82 | 'setc': { 83 | { 84 | 'section1': { 85 | {key1: None}, 86 | {key2: None} 87 | } 88 | } 89 | 'exit': None 90 | 'use': { 91 | {'module1': None}, 92 | ... 93 | } 94 | } 95 | :return: nested dictionary 96 | """ 97 | nested_completion_dict = {} 98 | modules_dict = {} 99 | config_dict = {} 100 | # Get all based command 101 | for command in self.dispatcher.commands: 102 | if len(command["arguments"]) == 0: 103 | nested_completion_dict.update({command["name"]: None}) 104 | else: 105 | nested_completion_dict.update({command["name"]: command["arguments"]}) 106 | # Append all loaded module to use command 107 | for module in self.modules: 108 | modules_dict.update({module["path"]: None}) 109 | nested_completion_dict["use"] = modules_dict 110 | # Append all config section and key to setc command 111 | for section in self.config: 112 | if section != "DEFAULT": 113 | config_key_dict = {} 114 | config_dict.update({section: None}) 115 | if len(self.config[section]) > 0: 116 | for key in self.config[section]: 117 | config_key_dict.update({key: None}) 118 | config_dict[section] = config_key_dict 119 | nested_completion_dict["setc"] = config_dict 120 | return nested_completion_dict 121 | 122 | def update_completer_options_list(self): 123 | """ 124 | Update prompt completer options list when loading a new module. 125 | :return: Nothing 126 | """ 127 | options = {} 128 | if isinstance(self.current_module, AModule): 129 | for option in self.current_module.options: 130 | options.update({option["Name"]: None}) 131 | self.completer_nested_dict["set"] = options 132 | self.completer_nested_dict["unset"] = options 133 | self.console_completer = NestedCompleter.from_nested_dict(self.completer_nested_dict) 134 | 135 | def update_completer_global_options_list(self): 136 | """ 137 | Update prompt completer global options list when loading a new module. 138 | :return: Nothing 139 | """ 140 | options = {} 141 | for keys, _ in self.global_options.items(): 142 | options.update({keys: None}) 143 | self.completer_nested_dict["setg"] = options 144 | self.completer_nested_dict["unsetg"] = options 145 | self.console_completer = NestedCompleter.from_nested_dict(self.completer_nested_dict) 146 | 147 | def _list_modules(self): 148 | """ 149 | Generate modules path and attributes list. 150 | :return: List of available modules 151 | """ 152 | modules = [] 153 | module_name = "hbfmodules" 154 | 155 | try: 156 | package = import_module(module_name) 157 | except ImportError: 158 | self.logger.handle("Unable to find any modules, please run 'hbfupdate' " 159 | "script to install available modules...", Logger.ERROR) 160 | else: 161 | for loader, module, is_pkg in pkgutil.walk_packages(package.__path__, prefix=package.__name__ + '.'): 162 | try: 163 | imported_module = import_module(module) 164 | for x in dir(imported_module): 165 | obj = getattr(imported_module, x) 166 | if inspect.isclass(obj) and issubclass(obj, AModule) and obj is not AModule: 167 | module_path = module.replace('hbfmodules.', '').replace('.', '/') 168 | modules.append({"path": module_path, "class": obj}) 169 | except ImportError: 170 | self.logger.handle('Error dynamically import package "{}"...'.format(module), Logger.ERROR) 171 | self.logger.handle("{} modules loaded, run 'hbfupdate' command to install the latest modules" 172 | .format(len(modules)), Logger.USER_INTERACT) 173 | return modules 174 | 175 | def run(self, file_script=None): 176 | """ 177 | Main loop, waiting for user input. 178 | :return: 179 | """ 180 | session = PromptSession() 181 | # This parts is used to automate test by passing file script 182 | if file_script is not None: 183 | file_script = Path(file_script) 184 | if file_script.is_file(): 185 | with file_script.open() as f: 186 | commands = f.readlines() 187 | for command in commands: 188 | command = session.prompt(self.prompt, style=self.prompt_style, default=command.strip(), 189 | accept_default=True) 190 | self.dispatcher.handle(self, command) 191 | self.update_prompt() 192 | else: 193 | self.logger.handle("File does not exist or it is not a file", self.logger.ERROR) 194 | # Normal mode waiting for user_input 195 | while True: 196 | try: 197 | command = session.prompt(self.prompt, style=self.prompt_style, completer=self.console_completer, 198 | complete_while_typing=False) 199 | self.dispatcher.handle(self, command) 200 | self.update_prompt() 201 | except KeyboardInterrupt: 202 | self.logger.handle("Please use 'exit' command or ctrl+D key to properly quit the framework", 203 | Logger.INFO) 204 | except EOFError: 205 | exit(0) 206 | -------------------------------------------------------------------------------- /hydrabus_framework/core/utils/HBFUpdate.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import inspect 3 | import os 4 | import pkg_resources 5 | import pkgutil 6 | import re 7 | import requests 8 | import subprocess 9 | import tarfile 10 | import traceback 11 | 12 | from importlib import import_module 13 | 14 | from hydrabus_framework.utils.logger import Logger 15 | from hydrabus_framework.modules.AModule import AModule 16 | 17 | 18 | __author__ = "Jordan Ovrè " 19 | 20 | 21 | class HBFUpdate: 22 | def __init__(self): 23 | self.github_base_url = 'https://api.github.com' 24 | self.logger = Logger() 25 | self.not_updated = [] 26 | self.to_update = [] 27 | self.to_install = [] 28 | 29 | def _get_installed_modules(self): 30 | """ 31 | Return a dict of currently installed module(s). 32 | :return: A dict of currently installed module(s) {'module_name': 'version', ...}. 33 | """ 34 | module_name = "hbfmodules" 35 | installed_modules = {} 36 | try: 37 | package = import_module(module_name) 38 | except ImportError: 39 | self.logger.handle('No modules currently installed', Logger.ERROR) 40 | return installed_modules 41 | for loader, module, is_pkg in pkgutil.walk_packages(package.__path__, prefix=package.__name__ + '.'): 42 | try: 43 | imported_module = import_module(module) 44 | for x in dir(imported_module): 45 | obj = getattr(imported_module, x) 46 | if inspect.isclass(obj) and issubclass(obj, AModule) and obj is not AModule: 47 | installed_modules[module] = pkg_resources.get_distribution(module).version 48 | except ImportError: 49 | self.logger.handle('Error dynamically import package "{}"... Unable to update it' 50 | .format(module), Logger.ERROR) 51 | self.not_updated.append(module) 52 | return installed_modules 53 | 54 | def _get_available_modules(self): 55 | """ 56 | Return a dict of module that have a release on the hydrabus-framework organization. 57 | :return: A dict of available module {'module_name': 'version', ...}. 58 | """ 59 | invalids = ['hbfmodules.skeleton', 'framework'] 60 | modules = {} 61 | resp = requests.get('{}{}'.format(self.github_base_url, '/orgs/hydrabus-framework/repos')) 62 | if resp.status_code == 200: 63 | for pkg in resp.json(): 64 | if pkg["name"] not in invalids: 65 | module_release_url = self.github_base_url +\ 66 | '/repos/hydrabus-framework/{}/releases/latest'.format(pkg["name"]) 67 | resp = requests.get(module_release_url) 68 | if resp.status_code == 200: 69 | modules[pkg["name"]] = resp.json()['tag_name'] 70 | return modules 71 | 72 | def _get_latest_framework_version(self): 73 | """ 74 | Return the latest release version of the Hydrabus framework. 75 | :return: string. 76 | """ 77 | module_release_url = self.github_base_url + '/repos/hydrabus-framework/framework/releases/latest' 78 | resp = requests.get(module_release_url) 79 | if resp.status_code == 200: 80 | return resp.json()['tag_name'] 81 | else: 82 | self.logger.handle('Unable to get the latest framework released version', Logger.ERROR) 83 | return None 84 | 85 | def _get_latest_release_url(self, module_name): 86 | """ 87 | Get the latest release URL of a module or framework. 88 | :param module_name: module name. 89 | :return: url or None if not found. 90 | """ 91 | module_release_url = self.github_base_url + '/repos/hydrabus-framework/{}/releases/latest'.format(module_name) 92 | resp = requests.get(module_release_url) 93 | if resp.status_code == 200: 94 | return resp.json()["tarball_url"] 95 | else: 96 | self.logger.handle(f"Unable to retrieve latest release URL for module '{module_name}'", Logger.ERROR) 97 | return None 98 | 99 | @staticmethod 100 | def _get_filename_from_cd(cd): 101 | """ 102 | Get filename from content-disposition. 103 | :param cd: Content-Disposition HTTP header. 104 | :return: filename from Content-Disposition or None 105 | """ 106 | if not cd: 107 | return None 108 | fname = re.findall('filename=(.+)', cd) 109 | if len(fname) == 0: 110 | return None 111 | return fname[0] 112 | 113 | def _download_release(self, module_tarball_url, package_name): 114 | """ 115 | Download the latest release of a module or framework. 116 | :param module_tarball_url: release URL. 117 | :param package_name: The package name downloaded (module or framework). 118 | :return: filename or None. 119 | """ 120 | self.logger.handle("Downloading {}...".format(package_name)) 121 | resp = requests.get(module_tarball_url, stream=True) 122 | if resp.status_code == 200: 123 | filename = '/tmp/hbfmodules/{}'.format(self._get_filename_from_cd(resp.headers.get('content-disposition'))) 124 | if not os.path.exists(os.path.dirname(filename)): 125 | try: 126 | os.makedirs(os.path.dirname(filename)) 127 | except OSError as exc: 128 | if exc.errno != errno.EEXIST: 129 | raise 130 | open(filename, 'wb').write(resp.content) 131 | return filename 132 | return None 133 | 134 | @staticmethod 135 | def _extract_tarball(filename): 136 | """ 137 | Extract the specified tarball. 138 | :param filename: the tarball file path 139 | :return: Directory path of the extracted archive 140 | """ 141 | tar = tarfile.open(filename) 142 | path = re.sub(r'\.tar.gz$', '', filename) 143 | setup_dir = '{}/{}'.format(path, tar.getmembers()[0].name) 144 | tar.extractall(path) 145 | tar.close() 146 | return setup_dir 147 | 148 | def _manage_install(self, package_name): 149 | """ 150 | Manage the installation of the specified package (module or framework). 151 | :param package_name: The package name to install (module or framework). 152 | :return: Bool: True if successfully update, False otherwise. 153 | """ 154 | release_tarball_url = self._get_latest_release_url(package_name) 155 | if release_tarball_url: 156 | filename = self._download_release(release_tarball_url, package_name) 157 | if filename: 158 | setup_dir = self._extract_tarball(filename) 159 | try: 160 | return_code = subprocess.call(['python', 'setup.py', 'install'], 161 | cwd=setup_dir, stdout=subprocess.DEVNULL) 162 | if return_code != 0: 163 | return False 164 | else: 165 | self.logger.handle("'{}' successfully installed".format(package_name), Logger.SUCCESS) 166 | return True 167 | except: 168 | self.logger.handle("The setup command are failed for the '{}' module".format(package_name), Logger.ERROR) 169 | traceback.print_exc() 170 | return False 171 | else: 172 | self.logger.handle("Failed to download the latest release for the module '{}'" 173 | .format(package_name), Logger.ERROR) 174 | return False 175 | else: 176 | return False 177 | 178 | def update(self, update_framework=None): 179 | """ 180 | This script check all released Hydrabus Framework modules and compare it with currently installed modules. 181 | If an update is available, this script install it. Moreover, if a module is available and not installed, it will 182 | be installed. 183 | :param update_framework: if True check if a framework update is available. 184 | :return: Nothing 185 | """ 186 | if update_framework: 187 | latest_release_version = self._get_latest_framework_version() 188 | if latest_release_version: 189 | if latest_release_version > pkg_resources.get_distribution('hydrabus_framework').version: 190 | self.logger.handle('A new framework release is available, running update...', Logger.INFO) 191 | if not self._manage_install('framework'): 192 | self.not_updated.append('framework') 193 | else: 194 | self.logger.handle('Hydrabus framework is up-to-date', Logger.SUCCESS) 195 | else: 196 | self.not_updated.append('framework') 197 | available_modules = self._get_available_modules() 198 | installed_modules = self._get_installed_modules() 199 | for module, version in available_modules.items(): 200 | installed_module_version = installed_modules.get(module, None) 201 | if installed_module_version is not None: 202 | if installed_module_version < version: 203 | self.to_update.append(module) 204 | else: 205 | self.to_install.append(module) 206 | for module_name in self.to_update: 207 | self.logger.handle("Update module '{}'".format(module_name), Logger.INFO) 208 | if not self._manage_install(module_name): 209 | self.not_updated.append(module_name) 210 | for module_name in self.to_install: 211 | self.logger.handle("Install module '{}'".format(module_name), Logger.INFO) 212 | if not self._manage_install(module_name): 213 | self.not_updated.append(module_name) 214 | if len(self.not_updated) > 0: 215 | self.logger.handle("Unable to update/install the following package:", Logger.ERROR) 216 | for module in self.not_updated: 217 | print(" - {}".format(module)) 218 | -------------------------------------------------------------------------------- /hydrabus_framework/core/utils/Validator.py: -------------------------------------------------------------------------------- 1 | from hydrabus_framework.utils.logger import Logger 2 | 3 | 4 | class Validator: 5 | """ 6 | Class allowing to check module options (if set and if format is ok). 7 | """ 8 | def __init__(self): 9 | self.logger = Logger() 10 | 11 | def _args_validator(self, options_dict): 12 | """ 13 | Check arguments type validity & Convert to the specified format. 14 | :param options_dict: module options dictionary 15 | :return: Bool 16 | """ 17 | for option in options_dict: 18 | try: 19 | if option["Type"] == "int": 20 | if not isinstance(option["Value"], int): 21 | option["Value"] = int(option["Value"], 10) 22 | if option["Type"] == "bool": 23 | if not isinstance(option["Value"], bool): 24 | if str(option["Value"]).upper() == "FALSE": 25 | option["Value"] = False 26 | elif str(option["Value"]).upper() == "TRUE": 27 | option["Value"] = True 28 | else: 29 | raise ValueError 30 | if option["Value"] == "None": 31 | option["Value"] = None 32 | except ValueError: 33 | self.logger.handle("Value error: {} is not a member of {}".format(option["Name"], option["Type"])) 34 | return False 35 | return True 36 | 37 | def check_args(self, options_dict): 38 | """ 39 | Check if all arguments are defined by user, or set default value if available. 40 | :param options_dict: module options dictionary 41 | :return: Bool 42 | """ 43 | if len(options_dict) > 0: 44 | for option in options_dict: 45 | if option["Required"] and option["Value"] == "": 46 | if option["Default"] == "": 47 | self.logger.handle("OptionValidateError: The following options failed to validate: {}." 48 | .format(option["Name"]), Logger.ERROR) 49 | return False 50 | else: 51 | option["Value"] = option["Default"] 52 | if not self._args_validator(options_dict): 53 | return False 54 | return True 55 | -------------------------------------------------------------------------------- /hydrabus_framework/core/utils/__init__.py: -------------------------------------------------------------------------------- 1 | __import__("pkg_resources").declare_namespace(__name__) -------------------------------------------------------------------------------- /hydrabus_framework/hbfconsole: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | 5 | from hydrabus_framework.core.engine import HydraFramework 6 | 7 | 8 | __author__ = "Jordan Ovrè " 9 | 10 | 11 | def welcome(): 12 | """ 13 | Print framework header. 14 | :return: 15 | """ 16 | print("""\033[92m 17 | _ ___ _______ _____ ____ _ _ _____ 18 | | | | \ \ / / __ \| __ \ /\ | _ \| | | |/ ____| 19 | | |__| |\ \_/ /| | | | |__) | / \ | |_) | | | | (___ 20 | | __ | \ / | | | | _ / / /\ \ | _ <| | | |\___ \ 21 | | | | | | | | |__| | | \ \ / ____ \| |_) | |__| |____) | 22 | |_|__|_|__|_| |_____/|_|__\_\/_/____\_\____/ \____/|_____/____ _ __ 23 | | ____| __ \ /\ | \/ | ____\ \ / / __ \| __ \| |/ / 24 | | |__ | |__) | / \ | \ / | |__ \ \ /\ / / | | | |__) | ' / 25 | | __| | _ / / /\ \ | |\/| | __| \ \/ \/ /| | | | _ /| < 26 | | | | | \ \ / ____ \| | | | |____ \ /\ / | |__| | | \ \| . \ 27 | |_| |_| \_\/_/ \_\_| |_|______| \/ \/ \____/|_| \_\_|\_\\ 28 | 29 | \033[0m""") 30 | 31 | 32 | if __name__ == "__main__": 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument('-s', '--file', help='A file containing framework commands\' (one per line)') 35 | args = parser.parse_args() 36 | welcome() 37 | instance = HydraFramework() 38 | instance.run(args.file) 39 | 40 | -------------------------------------------------------------------------------- /hydrabus_framework/hbfupdate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | from hydrabus_framework.core.utils.HBFUpdate import HBFUpdate 7 | from hydrabus_framework.utils.logger import Logger 8 | 9 | 10 | __author__ = "Jordan Ovrè " 11 | 12 | 13 | if __name__ == '__main__': 14 | if os.geteuid() != 0 and not hasattr(sys, 'real_prefix'): 15 | Logger().handle("Please run 'hbfupdate' as root or use a virtualenv. Exiting...", Logger.ERROR) 16 | exit(1) 17 | print('----------------------------------------------------------------------') 18 | print('----------------Retrieve and install available modules----------------') 19 | print('----------------------------------------------------------------------') 20 | HBFUpdate().update(update_framework=True) 21 | -------------------------------------------------------------------------------- /hydrabus_framework/modules/AModule.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from hydrabus_framework.utils.logger import Logger 4 | 5 | 6 | __author__ = "Jordan Ovrè " 7 | 8 | 9 | class AModule(ABC): 10 | def __init__(self, hbf_config): 11 | self.name = None 12 | self.meta = { 13 | 'name': '', 14 | 'version': '', 15 | 'description': '', 16 | 'author': '' 17 | } 18 | self.logger = Logger() 19 | self.options = [] 20 | self.config = hbf_config 21 | 22 | def __name__(self): 23 | """ 24 | Simply return the module name 25 | :return: module name 26 | """ 27 | return self.name 28 | 29 | def get_option_value(self, option_name): 30 | """ 31 | Return the value of a specific option. 32 | :param option_name: The needed option name. 33 | :return: Value. 34 | """ 35 | for option in self.options: 36 | if option["Name"] == option_name: 37 | return option["Value"] 38 | else: 39 | raise UserWarning("Value {} not found in module options".format(option_name)) 40 | 41 | def show_options(self): 42 | """ 43 | Print available options for the module to user console. 44 | :return: Nothing. 45 | """ 46 | formatted_options = [] 47 | if len(self.options) > 0: 48 | self.print_meta() 49 | for option in self.options: 50 | if option["Default"] != "" and option["Value"] == "": 51 | formatted_options.append( 52 | { 53 | 'Name': option["Name"], 54 | 'Value': option["Default"], 55 | 'Required': option["Required"], 56 | 'Description': option["Description"] 57 | } 58 | ) 59 | else: 60 | formatted_options.append( 61 | { 62 | 'Name': option["Name"], 63 | 'Value': option["Value"], 64 | 'Required': option["Required"], 65 | 'Description': option["Description"] 66 | } 67 | ) 68 | self.logger.print_tabulate(formatted_options, headers={"Name": "Name", "Description": "Description", 69 | "Value": "Value", "Required": "Required"}) 70 | 71 | def print_meta(self): 72 | """ 73 | Print meta of the module (author, module name, description). 74 | :return: Nothing. 75 | """ 76 | self.logger.handle('Author: {}'.format(self.meta['author']), Logger.HEADER) 77 | self.logger.handle('Module name: {}, version {}'.format(self.meta['name'], self.meta['version']), Logger.HEADER) 78 | self.logger.handle('Description: {}'.format(self.meta['description']), Logger.HEADER) 79 | 80 | @abstractmethod 81 | def run(self): 82 | """ 83 | Main function. 84 | :return: 85 | """ 86 | pass 87 | 88 | -------------------------------------------------------------------------------- /hydrabus_framework/modules/__init__.py: -------------------------------------------------------------------------------- 1 | __import__("pkg_resources").declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/Colors.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | __author__ = "Jordan Ovrè " 5 | 6 | 7 | class Colors(Enum): 8 | HEADER = '\033[95m' 9 | OKBLUE = '\033[94m' 10 | OKGREEN = '\033[92m' 11 | WARNING = '\033[93m' 12 | FAIL = '\033[91m' 13 | ENDC = '\033[0m' 14 | BOLD = '\033[1m' 15 | UNDERLINE = '\033[4m' 16 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/__init__.py: -------------------------------------------------------------------------------- 1 | __import__("pkg_resources").declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/hb_generic_cmd.py: -------------------------------------------------------------------------------- 1 | import time 2 | import serial 3 | from hydrabus_framework.utils.logger import Logger 4 | 5 | 6 | __author__ = "Jordan Ovrè " 7 | 8 | 9 | def hb_reset(serial_instance): 10 | """ 11 | Reset hydrabus to return in console mode. 12 | :param serial_instance: hydrabus serial instance. 13 | """ 14 | serial_instance.write(b'\x00') 15 | serial_instance.write(b'\x0F') 16 | time.sleep(0.2) 17 | # clean serial buffer 18 | serial_instance.read(serial_instance.in_waiting) 19 | 20 | 21 | def hb_close(serial_instance): 22 | """ 23 | Close hydrabus serial instance. 24 | :param serial_instance: hydrabus serial instance. 25 | """ 26 | serial_instance.close() 27 | 28 | 29 | def hb_switch_bbio(serial_instance): 30 | """ 31 | Init the hydrabus to switch into BBIO mode. 32 | :param serial_instance: hydrabus serial instance. 33 | :return: Bool. 34 | """ 35 | for i in range(20): 36 | serial_instance.write(b'\x00') 37 | if "BBIO1".encode('utf-8') not in serial_instance.read(5): 38 | return False 39 | return True 40 | 41 | 42 | def hb_wait_ubtn(serial_instance): 43 | """ 44 | Loop until user press hydrabus UBTN. 45 | :param serial_instance: hydrabus serial instance. 46 | :return: Nothing. 47 | """ 48 | # timeout=1 minute 49 | timeout = time.time() + 60 * 1 50 | try: 51 | while True: 52 | if serial_instance.hydrabus.read(1) == 'B'.encode('utf-8'): 53 | if serial_instance.hydrabus.read(3) == 'BIO'.encode('utf-8'): 54 | # carriage return needed to reset interface 55 | serial_instance.hydrabus.write(b'\x0D\x0A') 56 | time.sleep(0.2) 57 | serial_instance.read(serial_instance.hydrabus.in_waiting) 58 | break 59 | if time.time() > timeout: 60 | Logger().handle("Wait UBTN timeout reached", Logger.ERROR) 61 | break 62 | except KeyboardInterrupt: 63 | pass 64 | 65 | 66 | def hb_connect(device, baudrate, timeout): 67 | """ 68 | Connect to the hydrabus device. 69 | :param device: String, hydrabus device path. 70 | :param baudrate: integer, baudrate speed to communicate with hydrabus. 71 | :param timeout: integer, read timeout value (sec). 72 | :return: serial instance. 73 | """ 74 | try: 75 | serial_instance = serial.Serial(device, baudrate, timeout=timeout) 76 | return serial_instance 77 | except serial.serialutil.SerialException as err: 78 | Logger().handle(err, Logger.ERROR) 79 | return False 80 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/logger.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | 3 | from tabulate import tabulate 4 | from sys import stdout 5 | 6 | from hydrabus_framework.utils.Colors import Colors 7 | 8 | 9 | __author__ = "Jordan Ovrè " 10 | 11 | 12 | class Progress: 13 | """ 14 | Progress logger is used to dynamically print records. 15 | """ 16 | def __init__(self, header=''): 17 | self.header = header 18 | self.full_msg = '' 19 | 20 | def status(self, msg): 21 | """ 22 | Dynamically print character on the same line by calling it multiple time. 23 | :param msg: The text to print. 24 | :return: Nothing. 25 | """ 26 | # if msg is a white character, convert it to its hex representation 27 | if msg.isspace() and msg != " ": 28 | self.full_msg += '0x{}'.format(codecs.encode(bytes(msg, 'utf-8'), 'hex').decode()) 29 | else: 30 | self.full_msg += msg 31 | print('{}: {}'.format(self.header, self.full_msg), end='\r', flush=True) 32 | 33 | def stop(self): 34 | """ 35 | Stop the dynamic printer process instance. 36 | :return: Nothing. 37 | """ 38 | if self.full_msg == '': 39 | print(end='', flush=False) 40 | else: 41 | print(flush=False) 42 | 43 | 44 | class Logger: 45 | """ 46 | The aim of this class is to manage core framework and module printing. 47 | """ 48 | DEFAULT = 0 49 | ERROR = 1 50 | SUCCESS = 2 51 | INFO = 3 52 | RESULT = 4 53 | USER_INTERACT = 5 54 | HEADER = 6 55 | WARNING = 7 56 | 57 | def __init__(self): 58 | self.categories = [ 59 | self._print_default, 60 | self._print_error, 61 | self._print_success, 62 | self._print_info, 63 | self._print_result, 64 | self._print_user_interact, 65 | self._print_header, 66 | self._print_warning 67 | ] 68 | 69 | def handle(self, text, level=DEFAULT): 70 | """ 71 | This function print in different color a given string. 72 | :param text: type string, the string to print on the console. 73 | :param level: the message category. 74 | :return: Nothing. 75 | """ 76 | self.categories[level](text) if (level < len(self.categories)) else self.categories[self.DEFAULT](text) 77 | 78 | @staticmethod 79 | def _print_default(text): 80 | """ 81 | Print without style. 82 | :param text: String, message to be printed. 83 | :return: Nothing. 84 | """ 85 | print(text) 86 | 87 | @staticmethod 88 | def _print_error(text): 89 | """ 90 | Beautify error message. 91 | :param text: String, message to be printed. 92 | :return: Nothing. 93 | """ 94 | print("{}[✘]{} {}".format(Colors.FAIL.value, Colors.ENDC.value, text)) 95 | 96 | @staticmethod 97 | def _print_success(text): 98 | """ 99 | Beautify success message. 100 | :param text: String, message to be printed. 101 | :return: Nothing. 102 | """ 103 | print("{}[✔]{} {}".format(Colors.OKGREEN.value, Colors.ENDC.value, text)) 104 | 105 | @staticmethod 106 | def _print_info(text): 107 | """ 108 | Beautify info message. 109 | :param text: String, message to be printed. 110 | :return: Nothing. 111 | """ 112 | print("[*] {}".format(text)) 113 | 114 | @staticmethod 115 | def _print_result(text): 116 | """ 117 | Beautify result message. 118 | :param text: String, message to be printed. 119 | :return: Nothing. 120 | """ 121 | print("{}[✔] {}{}".format(Colors.OKGREEN.value, text, Colors.ENDC.value)) 122 | 123 | @staticmethod 124 | def _print_user_interact(text): 125 | """ 126 | Beautify needed user's interaction message. 127 | :param text: String, message to be printed. 128 | :return: Nothing. 129 | """ 130 | print("{}[*] {}{}".format(Colors.OKBLUE.value, text, Colors.ENDC.value)) 131 | 132 | @staticmethod 133 | def _print_header(text): 134 | """ 135 | Beautify header message. 136 | :param text: String, message to be printed. 137 | :return: Nothing. 138 | """ 139 | print("{}{}{}".format(Colors.BOLD.value, text, Colors.ENDC.value)) 140 | 141 | @staticmethod 142 | def _print_warning(text): 143 | """ 144 | Beautify warning message. 145 | :param text: String, message to be printed. 146 | :return: Nothing. 147 | """ 148 | print("{}[!] {}{}".format(Colors.WARNING.value, text, Colors.ENDC.value)) 149 | 150 | @staticmethod 151 | def print_tabulate(data, headers): 152 | """ 153 | Print data in a beautiful tab. 154 | :param data: Array. 155 | :param headers: The headers of array passed. 156 | :return: Nothing. 157 | """ 158 | print("\n{}\n".format(tabulate(data, headers=headers))) 159 | 160 | @staticmethod 161 | def progress(header): 162 | """ 163 | Creates a new progress logger. 164 | :return: Progress class instance. 165 | """ 166 | return Progress(header) 167 | 168 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from .spi import * 16 | from .i2c import * 17 | from .onewire import * 18 | from .rawwire import * 19 | from .smartcard import * 20 | from .uart import * 21 | from .utils import * 22 | from .swd import * 23 | 24 | import logging 25 | 26 | logger = logging.getLogger(__name__) 27 | logger.addHandler(logging.NullHandler()) 28 | 29 | INPUT = 1 30 | OUTPUT = 0 31 | 32 | name = "pyHydrabus" 33 | __version__ = 0.1 34 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/auxpin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from .common import set_bit 4 | 5 | 6 | class AUXPin: 7 | """ 8 | Auxilary pin base class 9 | 10 | This class is meant to be used in any mode and is instanciated in the 11 | Protocol class 12 | 13 | :example: 14 | 15 | >>> import pyHydrabus 16 | >>> i=pyHydrabus.RawWire('/dev/hydrabus') 17 | >>> # Set AUX pin 0 (PC4) to output 18 | >>> i.AUX[0].direction = pyHydrabus.OUTPUT 19 | >>> # Set AUX pin to logical 1 20 | >>> i.AUX[0].value = 1 21 | >>> # Read Auxiliary pin 2 (PC5) value 22 | >>> i.AUX[1].value 23 | 24 | """ 25 | 26 | OUTPUT = 0 27 | INPUT = 1 28 | 29 | def __init__(self, number, hydrabus): 30 | """ 31 | AUXPin constructor 32 | 33 | :param number: Auxilary pin number (0-3) 34 | :param hydrabus: Initialized pyHydrabus.Hydrabus instance 35 | """ 36 | self.number = number 37 | self._hydrabus = hydrabus 38 | self._logger = logging.getLogger(__name__) 39 | 40 | def _get_config(self): 41 | """ 42 | Gets Auxiliary pin config from Hydrabus 43 | 44 | :return: Auxilary pin config (4 bits pullup for AUX[0-3], 4 bits value for AUX[0-3]) 45 | :rtype: byte 46 | """ 47 | CMD = 0b11100000 48 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 49 | return self._hydrabus.read(1) 50 | 51 | def _get_values(self): 52 | """ 53 | Gets Auxiliary pin values from Hydrabus 54 | 55 | :return: Auxilary pin values AUX[0-3] 56 | :rtype: byte 57 | """ 58 | CMD = 0b11000000 59 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 60 | return self._hydrabus.read(1) 61 | 62 | @property 63 | def value(self): 64 | """ 65 | Auxiliary pin getter 66 | """ 67 | return (ord(self._get_values()) >> self.number) & 0b1 68 | 69 | @value.setter 70 | def value(self, value): 71 | """ 72 | Auxiliary pin setter 73 | 74 | :param value: auxiliary pin value (0 or 1) 75 | """ 76 | CMD = 0b11010000 77 | CMD = CMD | ord(set_bit(self._get_values(), value, self.number)) 78 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 79 | if self._hydrabus.read(1) == b"\x01": 80 | return 81 | else: 82 | self._logger.error("Error setting auxiliary pins value.") 83 | 84 | def toggle(self): 85 | """ 86 | Toggle pin state 87 | """ 88 | self.value = not self.value 89 | 90 | @property 91 | def direction(self): 92 | """ 93 | Auxiliary pin direction getter 94 | 95 | :return: The pin direction (0=output, 1=input) 96 | :rtype: int 97 | """ 98 | return (ord(self._get_config()) >> self.number) & 0b1 99 | 100 | @direction.setter 101 | def direction(self, value): 102 | """ 103 | Auxiliary pin direction setter 104 | 105 | :param value: The pin direction (0=output, 1=input) 106 | """ 107 | CMD = 0b11110000 108 | PARAM = self._get_config() 109 | PARAM = set_bit(PARAM, value, self.number) 110 | 111 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 112 | self._hydrabus.write(PARAM) 113 | if self._hydrabus.read(1) == b"\x01": 114 | return 115 | else: 116 | self._logger.error("Error setting auxiliary pins direction.") 117 | 118 | @property 119 | def pullup(self): 120 | """ 121 | Auxiliary pin pullup getter 122 | 123 | :return: Auxiliary pin pullup status (1=enabled, 0=disabled") 124 | :rtype: int 125 | """ 126 | return (ord(self._get_config()) >> (4 + self.number)) & 0b1 127 | 128 | @pullup.setter 129 | def pullup(self, value): 130 | """ 131 | Auxiliary pin pullup setter 132 | 133 | :param value: Auxiliary pin pullup (1=enabled, 0=disabled") 134 | """ 135 | CMD = 0b11110000 136 | PARAM = self._get_config() 137 | PARAM = set_bit(PARAM, value, 4 + self.number) 138 | 139 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 140 | self._hydrabus.write(PARAM) 141 | if self._hydrabus.read(1) == b"\x01": 142 | return 143 | else: 144 | self._logger.error("Error setting auxiliary pins value.") 145 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | def split(seq, length): 17 | """ 18 | Split a list in chunks of specific length 19 | """ 20 | return [seq[i : i + length] for i in range(0, len(seq), length)] 21 | 22 | 23 | def set_bit(byte, bit, position): 24 | v = ord(byte) 25 | if bit == 1: 26 | v = v | (1 << position) 27 | elif bit == 0: 28 | v = v & (~(1 << position)) & 0xFF 29 | else: 30 | raise ValueError("Bit must be 0 or 1") 31 | return v.to_bytes(1, byteorder="big") 32 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/hydrabus.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import time 16 | import logging 17 | 18 | import serial 19 | 20 | 21 | class Hydrabus: 22 | """ 23 | Base class for all modes. 24 | 25 | Manages all serial communication 26 | """ 27 | 28 | def __init__(self, port=""): 29 | """ 30 | Class init 31 | 32 | :param port: Serial port to use 33 | """ 34 | self.port = port 35 | self._logger = logging.getLogger(__name__) 36 | 37 | try: 38 | self._serialport = serial.Serial(self.port, timeout=None) 39 | self.enter_bbio() 40 | except serial.SerialException as e: 41 | self._logger.error(f"Cannot connect : {e.strerror}") 42 | raise type(e)(f"Cannot open {self.port}") 43 | 44 | def write(self, data=b""): 45 | """ 46 | Base write primitive 47 | 48 | :param data: Bytes to write 49 | :type data: bytes 50 | """ 51 | if not self.connected: 52 | raise serial.SerialException("Not connected.") 53 | try: 54 | self._logger.debug(f"==>[{str(len(data)).zfill(4)}] {data.hex()}") 55 | self._serialport.write(data) 56 | self._serialport.flush() 57 | except serial.SerialException as e: 58 | self._logger.error(f"Cannot send : {e.strerror}") 59 | raise type(e)(f"Cannot send : {e.strerror}") 60 | 61 | def read(self, length=1): 62 | """ 63 | Base read primitive 64 | 65 | :param length: Number of bytes to read 66 | :type length: int 67 | :return: Read data 68 | :rtype: bytes 69 | """ 70 | if not self.connected: 71 | raise serial.SerialException("Not connected.") 72 | try: 73 | data = self._serialport.read(length) 74 | self._logger.debug(f"<==[{str(length).zfill(4)}] {data.hex()}") 75 | self._lastread = data 76 | return data 77 | except serial.SerialException as e: 78 | self._logger.error(f"Cannot read : {e.strerror}") 79 | raise type(e)(f"Cannot read : {e.strerror}") 80 | 81 | def flush_input(self): 82 | """ 83 | Flush input buffer (data from Hydrabus) 84 | """ 85 | self._serialport.reset_input_buffer() 86 | 87 | @property 88 | def in_waiting(self): 89 | """ 90 | Return the number of bytes in the receive buffer. 91 | :return: Number of bytes 92 | :rtype: int 93 | """ 94 | return self._serialport.in_waiting 95 | 96 | def exit_bbio(self): 97 | """ 98 | Reset Hydrabus to CLI mode 99 | :return: Bool 100 | """ 101 | if not self.connected: 102 | raise serial.SerialException("Not connected.") 103 | if self.reset() == True: 104 | self.write(b"\x00") 105 | self.write(b"\x0F\n") 106 | return True 107 | else: 108 | return False 109 | 110 | def enter_bbio(self): 111 | """ 112 | Enter BBIO mode. 113 | This should be done prior all further operations 114 | :return: Bool 115 | """ 116 | if not self.connected: 117 | raise serial.SerialException("Not connected.") 118 | self.timeout = 0.01 119 | for _ in range(20): 120 | self.write(b"\x00") 121 | if b"BBIO1" in self.read(5): 122 | self.flush_input() 123 | self.timeout = None 124 | return True 125 | self._logger.error("Cannot enter BBIO mode.") 126 | self.timeout = None 127 | return False 128 | 129 | def reset(self): 130 | """ 131 | Force reset to BBIO main mode 132 | :return: Bool 133 | """ 134 | timeout = time.time() + 10 135 | if not self.connected: 136 | raise serial.SerialException("Not connected.") 137 | self.timeout = 0.1 138 | while self.read(5) != b"BBIO1": 139 | self.flush_input() 140 | self.write(b"\x00") 141 | if time.time() > timeout: 142 | self._logger.error(f"Unable to reset hydrabus") 143 | return False 144 | self.timeout = None 145 | return True 146 | 147 | def identify(self): 148 | """ 149 | Identify the current mode 150 | 151 | :return: Current mode 152 | :rtype: str 153 | """ 154 | if not self.connected: 155 | raise serial.SerialException("Not connected.") 156 | self.write(b"\x01") 157 | return self.read(4).decode("ascii") 158 | 159 | def close(self): 160 | """ 161 | Close the serial port 162 | """ 163 | self._serialport.close() 164 | 165 | @property 166 | def timeout(self): 167 | """ 168 | Serial port read timeout 169 | :return: timeout 170 | :rtype: int 171 | """ 172 | return self._serialport.timeout 173 | 174 | @timeout.setter 175 | def timeout(self, value): 176 | """ 177 | Set serial port read timeout 178 | :param value: timeout 179 | :type value: int 180 | """ 181 | self._serialport.timeout = value 182 | 183 | @property 184 | def connected(self): 185 | """ 186 | Check if serial port is opened 187 | :return: Bool 188 | """ 189 | return self._serialport.is_open 190 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/i2c.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | 18 | 19 | class I2C(Protocol): 20 | """ 21 | I2C protocol handler 22 | 23 | :example: 24 | 25 | >>> #Read data from an EEPROM 26 | >>> import pyHydrabus 27 | >>> i=pyHydrabus.I2C('/dev/hydrabus') 28 | >>> i.set_speed(pyHydrabus.I2C.I2C_SPEED_100K) 29 | >>> i.pullup=1 30 | >>> i.start();i.bulk_write(b'\xa0\x00');print(i.write_read(b'\xa1',64)) 31 | 32 | """ 33 | 34 | __I2C_DEFAULT_CONFIG = 0b000 35 | 36 | I2C_SPEED_50K = 0b00 37 | I2C_SPEED_100K = 0b01 38 | I2C_SPEED_400K = 0b10 39 | I2C_SPEED_1M = 0b11 40 | 41 | def __init__(self, port=""): 42 | self._config = self.__I2C_DEFAULT_CONFIG 43 | super().__init__(name=b"I2C1", fname="I2C", mode_byte=b"\x02", port=port) 44 | self._configure_port() 45 | 46 | def start(self): 47 | """ 48 | Send a I2C start condition 49 | """ 50 | self._hydrabus.write(b"\x02") 51 | if self._hydrabus.read(1) == b"\x00": 52 | self._logger.error("Cannot execute command.") 53 | return False 54 | return True 55 | 56 | def stop(self): 57 | """ 58 | Send a I2C stop condition 59 | """ 60 | self._hydrabus.write(b"\x03") 61 | if self._hydrabus.read(1) == b"\x00": 62 | self._logger.error("Cannot execute command.") 63 | return False 64 | return True 65 | 66 | def read_byte(self): 67 | """ 68 | Read a byte from the I2C bus 69 | """ 70 | self._hydrabus.write(b"\x04") 71 | return self._hydrabus.read(1) 72 | 73 | def send_ack(self): 74 | """ 75 | Send an ACK 76 | Used with the read_byte() function 77 | """ 78 | self._hydrabus.write(b"\x06") 79 | if self._hydrabus.read(1) == b"\x00": 80 | self._logger.error("Cannot execute command.") 81 | return False 82 | return True 83 | 84 | def send_nack(self): 85 | """ 86 | Send a NACK 87 | Used with the read_byte() function 88 | """ 89 | self._hydrabus.write(b"\x07") 90 | if self._hydrabus.read(1) == b"\x00": 91 | self._logger.error("Cannot execute command.") 92 | return False 93 | return True 94 | 95 | def write_read(self, data=b"", read_len=0): 96 | """ 97 | Write-then-read operation 98 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-I2C-mode-guide#i2c-write-then-read-0b00001000 99 | 100 | This method sends a start condition before writing and a stop condition after reading. 101 | 102 | :param data: Data to be sent 103 | :type data: bytes 104 | :param read_len: Number of bytes to read 105 | :type read_len: int 106 | :return: Read data 107 | :rtype: bytes 108 | """ 109 | CMD = 0b00001000 110 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 111 | 112 | self._hydrabus.write(len(data).to_bytes(2, byteorder="big")) 113 | 114 | self._hydrabus.write(read_len.to_bytes(2, byteorder="big")) 115 | 116 | self._hydrabus.timeout = 0 117 | if self._hydrabus.read(1) == b"\x00": 118 | self._logger.error("Cannot execute command. Too many bytes requested ?") 119 | return None 120 | self._hydrabus.timeout = None 121 | 122 | self._hydrabus.write(data) 123 | 124 | if self._hydrabus.read(1) != b"\x01": 125 | self._logger.warn("Data not ACKed. Aborting.") 126 | return None 127 | 128 | return self._hydrabus.read(read_len) 129 | 130 | def write(self, data=b""): 131 | """ 132 | Write on I2C bus 133 | 134 | :param data: data to be sent 135 | :type data: bytes 136 | """ 137 | self.write_read(data, read_len=0) 138 | 139 | def read(self, length=0): 140 | """ 141 | Read on I2C bus 142 | 143 | :param length: Number of bytes to read 144 | :type length: int 145 | :return: Read data 146 | :rtype: bytes 147 | """ 148 | result = [] 149 | for _ in range(length - 1): 150 | result.append(self.read_byte()) 151 | self.send_ack() 152 | result.append(self.read_byte()) 153 | self.send_nack() 154 | 155 | return b"".join(result) 156 | 157 | def bulk_write(self, data=b""): 158 | """ 159 | Bulk write on I2C bus 160 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-I2C-mode-guide#bulk-i2c-write-0b0001xxxx 161 | 162 | :param data: Data to be sent 163 | :type data: bytes 164 | 165 | :return: ACK status of the written bytes (b'\x00'=ACK, b'\x01'=NACK) 166 | :rtype: list 167 | """ 168 | CMD = 0b00010000 169 | if not len(data) > 0: 170 | raise ValueError("Send at least one byte") 171 | if not len(data) <= 16: 172 | raise ValueError("Too many bytes to write") 173 | CMD = CMD | (len(data) - 1) 174 | 175 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 176 | if self._hydrabus.read(1) != b"\x01": 177 | self._logger.warn("Unknown error.") 178 | return None 179 | 180 | self._hydrabus.write(data) 181 | 182 | return self._hydrabus.read(len(data)) 183 | 184 | def scan(self): 185 | """ 186 | Scan I2C bus and returns all available device addresses in a list 187 | 188 | :return: List of available device addresses 189 | :rtype: list 190 | """ 191 | result = [] 192 | for i in range(1, 0x78): 193 | addr = (i << 1).to_bytes(1, byteorder="big") 194 | self.start() 195 | if self.bulk_write(addr) == b"\x00": 196 | addr = int.from_bytes(addr, byteorder="big") >> 1 197 | result.append(addr) 198 | self.stop() 199 | return result 200 | 201 | def set_speed(self, speed): 202 | """ 203 | Set I2C bus speed 204 | 205 | :param speed: Select any of the defined speeds (I2C_SPEED_*) 206 | """ 207 | if not speed <= 0b11: 208 | raise ValueError("Incorrect speed") 209 | CMD = 0b01100000 210 | CMD = CMD | speed 211 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 212 | 213 | if self._hydrabus.read(1) == b"\x01": 214 | return True 215 | else: 216 | self._logger.error("Error setting speed.") 217 | return False 218 | 219 | def _configure_port(self): 220 | CMD = 0b01000000 221 | CMD = CMD | self._config 222 | 223 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 224 | if self._hydrabus.read(1) == b"\x01": 225 | return True 226 | else: 227 | self._logger.error("Error setting config.") 228 | return False 229 | 230 | @property 231 | def pullup(self): 232 | """ 233 | Pullup status (0=off, 1=on) 234 | """ 235 | if self._config & 0b100: 236 | return 1 237 | else: 238 | return 0 239 | 240 | @pullup.setter 241 | def pullup(self, value): 242 | if value == 0: 243 | self._config = self._config & ~(1 << 2) 244 | else: 245 | self._config = self._config | (1 << 2) 246 | self._configure_port() 247 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/onewire.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | from .common import split 18 | 19 | 20 | class OneWire(Protocol): 21 | """ 22 | One wire protocol handler 23 | 24 | :example: 25 | 26 | TODO 27 | 28 | """ 29 | 30 | __ONEWIRE_DEFAULT_CONFIG = 0b100 31 | 32 | def __init__(self, port=""): 33 | self._config = self.__ONEWIRE_DEFAULT_CONFIG 34 | super().__init__(name=b"1W01", fname="1-Wire", mode_byte=b"\x04", port=port) 35 | self._configure_port() 36 | 37 | def reset(self): 38 | """ 39 | Send a 1-Wire reset 40 | """ 41 | self._hydrabus.write(b"\x02") 42 | return True 43 | 44 | def read_byte(self): 45 | """ 46 | Read a byte from the 1-Wire bus 47 | """ 48 | self._hydrabus.write(b"\x04") 49 | return self._hydrabus.read(1) 50 | 51 | def write(self, data=b""): 52 | """ 53 | Write on 1-Wire bus 54 | 55 | :param data: data to be sent 56 | :type data: bytes 57 | """ 58 | for chunk in split(data, 16): 59 | self.bulk_write(chunk) 60 | 61 | def read(self, read_len=0): 62 | """ 63 | Read on 1-Wire bus 64 | 65 | :param read_len: Number of bytes to be read 66 | :return read_len: int 67 | :return: Read data 68 | :rtype: bytes 69 | """ 70 | return b"".join([self.read_byte() for x in range(read_len)]) 71 | 72 | def bulk_write(self, data=b""): 73 | """ 74 | Bulk write on 1-Wire bus 75 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-binary-1-Wire-mode-guide#bulk-1-wire-transfer-0b0001xxxx 76 | 77 | :param data: Data to be sent 78 | :type data: bytes 79 | """ 80 | CMD = 0b00010000 81 | if not len(data) > 0: 82 | raise ValueError("Send at least one byte") 83 | if not len(data) <= 16: 84 | raise ValueError("Too many bytes to write") 85 | CMD = CMD | (len(data) - 1) 86 | 87 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 88 | self._hydrabus.write(data) 89 | 90 | if self._hydrabus.read(1) != b"\x01": 91 | self._logger.warn("Unknown error.") 92 | 93 | def _configure_port(self): 94 | CMD = 0b01000000 95 | CMD = CMD | self._config 96 | 97 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 98 | if self._hydrabus.read(1) == b"\x01": 99 | return True 100 | else: 101 | self._logger.error("Error setting config.") 102 | return False 103 | 104 | @property 105 | def pullup(self): 106 | """ 107 | Pullup status (0=off, 1=on) 108 | """ 109 | if self._config & 0b100: 110 | return 1 111 | else: 112 | return 0 113 | 114 | @pullup.setter 115 | def pullup(self, value): 116 | if value == 0: 117 | self._config = self._config & ~(1 << 2) 118 | else: 119 | self._config = self._config | (1 << 2) 120 | self._configure_port() 121 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/protocol.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .hydrabus import Hydrabus 17 | from .auxpin import AUXPin 18 | 19 | 20 | class Protocol: 21 | """ 22 | Base class for all supported protocols 23 | 24 | :param name: Name of the protocol (returned by Hydrabus) eg. SPI1 25 | :type name: str 26 | :param fname: Full name of the protocol 27 | :type fname: str 28 | :param mode_byte: Byte used to enter the mode (eg. \x01 for SPI) 29 | :type mode_byte: bytes 30 | :param port: The port name 31 | :type port: str 32 | """ 33 | 34 | def __init__(self, name="", fname="", mode_byte=b"\x00", port=""): 35 | self.name = name 36 | self.fname = fname 37 | self._mode_byte = mode_byte 38 | self._hydrabus = Hydrabus(port) 39 | self._logger = logging.getLogger(__name__) 40 | 41 | self._enter() 42 | self._hydrabus.flush_input() 43 | 44 | self.AUX = [] 45 | for i in range(4): 46 | self.AUX.append(AUXPin(i, self._hydrabus)) 47 | 48 | def _enter(self): 49 | self._hydrabus.write(self._mode_byte) 50 | if self._hydrabus.read(4) == self.name: 51 | self._hydrabus.mode = self.name 52 | return True 53 | else: 54 | self._logger.error(f"Cannot enter mode.") 55 | return False 56 | 57 | def _exit(self): 58 | return self._hydrabus.reset() 59 | 60 | def identify(self): 61 | """ 62 | Identify the current mode 63 | 64 | :return: The current mode identifier (4 bytes) 65 | :rtype: str 66 | """ 67 | return self._hydrabus.identify() 68 | 69 | def close(self): 70 | """ 71 | Close the communication channel and resets Hydrabus 72 | """ 73 | self._hydrabus.exit_bbio() 74 | self._hydrabus.close() 75 | 76 | @property 77 | def hydrabus(self): 78 | """ 79 | Return _hydrabus instance to access Hydrabus class function and serial method 80 | from any protocol classes instance 81 | :return: _hydrabus class instance 82 | """ 83 | return self._hydrabus 84 | 85 | @property 86 | def timeout(self): 87 | return self._hydrabus.timeout 88 | 89 | @timeout.setter 90 | def timeout(self, value): 91 | self._hydrabus.timeout = value 92 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/rawwire.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | from .common import split 18 | 19 | 20 | class RawWire(Protocol): 21 | """ 22 | Raw wire protocol handler 23 | 24 | :example: 25 | 26 | >>> import pyHydrabus 27 | >>> r=pyHydrabus.RawWire('/dev/hydrabus') 28 | >>> # Set SDA to high 29 | >>> r.sda = 1 30 | >>> # Send two clock ticks 31 | >>> r.clocks(2) 32 | >>> # Read two bytes 33 | >>> data = r.read(2) 34 | 35 | """ 36 | 37 | __RAWWIRE_DEFAULT_CONFIG = 0b000 38 | 39 | def __init__(self, port=""): 40 | self._config = self.__RAWWIRE_DEFAULT_CONFIG 41 | self._clk = 0 42 | self._sda = 0 43 | super().__init__(name=b"RAW1", fname="Raw-Wire", mode_byte=b"\x05", port=port) 44 | self._configure_port() 45 | 46 | def read_bit(self): 47 | """ 48 | Sends a clock tick, and return the read bit value 49 | """ 50 | CMD = 0b00000111 51 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 52 | return self._hydrabus.read(1) 53 | 54 | def read_byte(self): 55 | """ 56 | Read a byte from the raw wire 57 | 58 | :return: The read byte 59 | :rtype: bytes 60 | """ 61 | CMD = 0b00000110 62 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 63 | return self._hydrabus.read(1) 64 | 65 | def clock(self): 66 | """ 67 | Send a clock tick 68 | """ 69 | CMD = 0b00001001 70 | 71 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 72 | if self._hydrabus.read(1) == b"\x01": 73 | return True 74 | else: 75 | self._logger.error("Error setting pin.") 76 | return False 77 | 78 | def bulk_ticks(self, num): 79 | """ 80 | Sends a bulk of clock ticks (1 to 16) 81 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-binary-raw-wire-mode-guide#bulk-clock-ticks-0b0010xxxx 82 | 83 | :param num: Number of clock ticks to send 84 | :type num: int 85 | """ 86 | if not num > 0: 87 | raise ValueError("Send at least one clock tick") 88 | if not num <= 16: 89 | raise ValueError("Too many ticks to send") 90 | 91 | CMD = 0b00100000 92 | CMD = CMD | (num - 1) 93 | 94 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 95 | 96 | if self._hydrabus.read(1) == b"\x01": 97 | return True 98 | else: 99 | self._logger.error("Error sending clocks.") 100 | return False 101 | 102 | def clocks(self, num): 103 | """ 104 | Sends a number of clock ticks 105 | 106 | :param num: Number of clock ticks to send 107 | :type num: int 108 | """ 109 | if not num > 0: 110 | raise ValueError("Must be a positive integer") 111 | 112 | while num > 16: 113 | self.bulk_ticks(16) 114 | num = num - 16 115 | self.bulk_ticks(num) 116 | 117 | def bulk_write(self, data=b""): 118 | """ 119 | Bulk write on Raw-Wire 120 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-binary-raw-wire-mode-guide#bulk-raw-wire-transfer-0b0001xxxx 121 | 122 | Parameters: 123 | :param data: Data to be sent 124 | :type data: bytes 125 | """ 126 | CMD = 0b00010000 127 | if not len(data) > 0: 128 | raise ValueError("Send at least one byte") 129 | if not len(data) <= 16: 130 | raise ValueError("Too many bytes to write") 131 | CMD = CMD | (len(data) - 1) 132 | 133 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 134 | self._hydrabus.write(data) 135 | 136 | if self._hydrabus.read(1) != b"\x01": 137 | self._logger.warn("Unknown error.") 138 | 139 | return self._hydrabus.read(len(data)) 140 | 141 | def set_speed(self, speed): 142 | """ 143 | Sets the clock max speed. 144 | 145 | :param speed: speed in Hz. Possible values : TODO 146 | """ 147 | speeds = {5000: 0b00, 50000: 0b01, 100_000: 0b10, 1_000_000: 0b11} 148 | if speed not in speeds.keys(): 149 | raise ValueError(f"Incorrect value. use {speeds.keys()}") 150 | CMD = 0b01100000 151 | CMD = CMD | speeds[speed] 152 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 153 | if self._hydrabus.read(1) == b"\x01": 154 | return True 155 | else: 156 | self._logger.error("Error setting speed.") 157 | return False 158 | 159 | def _configure_port(self): 160 | CMD = 0b10000000 161 | CMD = CMD | self._config 162 | 163 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 164 | if self._hydrabus.read(1) == b"\x01": 165 | return True 166 | else: 167 | self._logger.error("Error setting config.") 168 | return False 169 | 170 | def write(self, data=b""): 171 | """ 172 | Write on Raw-Wire bus 173 | 174 | :param data: data to be sent 175 | :type data: bytes 176 | 177 | :return: Read bytes 178 | :rtype: bytes 179 | """ 180 | result = b"" 181 | for chunk in split(data, 16): 182 | result += self.bulk_write(chunk) 183 | return result 184 | 185 | def read(self, length=0): 186 | """ 187 | Read on Raw-Wire bus 188 | 189 | :param length: Number of bytes to read 190 | :type length: int 191 | :return: Read data 192 | :rtype: bytes 193 | """ 194 | result = b"" 195 | for _ in range(length): 196 | result += self.read_byte() 197 | 198 | return result 199 | 200 | @property 201 | def clk(self): 202 | """ 203 | CLK pin status 204 | """ 205 | return self._clk 206 | 207 | @clk.setter 208 | def clk(self, value): 209 | value = value & 1 210 | CMD = 0b00001010 211 | CMD = CMD | value 212 | 213 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 214 | if self._hydrabus.read(1) == b"\x01": 215 | self._clk = value 216 | return True 217 | else: 218 | self._logger.error("Error setting pin.") 219 | return False 220 | 221 | @property 222 | def sda(self): 223 | """ 224 | SDA pin status 225 | """ 226 | CMD = 0b00001000 227 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 228 | return int.from_bytes(self._hydrabus.read(1), byteorder="big") 229 | 230 | @sda.setter 231 | def sda(self, value): 232 | value = value & 1 233 | CMD = 0b00001100 234 | CMD = CMD | value 235 | 236 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 237 | if self._hydrabus.read(1) == b"\x01": 238 | self._clk = value 239 | return True 240 | else: 241 | self._logger.error("Error setting pin.") 242 | return False 243 | 244 | @property 245 | def wires(self): 246 | """ 247 | Raw-Wire mode (2=2-Wire, 3=3-Wire) 248 | """ 249 | if self._config & 0b100 == 0: 250 | return 2 251 | else: 252 | return 3 253 | 254 | @wires.setter 255 | def wires(self, value): 256 | if value == 2: 257 | self._config = self._config & ~(1 << 2) 258 | self._configure_port() 259 | return True 260 | elif value == 3: 261 | self._config = self._config | (1 << 2) 262 | self._configure_port() 263 | return True 264 | else: 265 | self._logger.error("Incorrect value. Must be 2 or 3") 266 | 267 | @property 268 | def gpio_mode(self): 269 | """ 270 | Raw-Wire GPIO mode (0=Push-Pull, 1=Open Drain) 271 | """ 272 | return (self._config & 0b1000) >> 3 273 | 274 | @gpio_mode.setter 275 | def gpio_mode(self, value): 276 | if value == 0: 277 | self._config = self._config & ~(1 << 3) 278 | self._configure_port() 279 | return True 280 | elif value == 1: 281 | self._config = self._config | (1 << 3) 282 | self._configure_port() 283 | return True 284 | else: 285 | self._logger.error("Incorrect value. Must be 0 or 1") 286 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/smartcard.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | 17 | from .protocol import Protocol 18 | 19 | 20 | class Smartcard(Protocol): 21 | """ 22 | Smartcard protocol handler 23 | 24 | :example: 25 | 26 | >>> #Read ATR from a smartcard 27 | >>> import pyHydrabus 28 | >>> sm=pyHydrabus.Smartcard('/dev/hydrabus') 29 | >>> sm.prescaler=12 30 | >>> sm.baud=9600 31 | >>> sm.rst=1;sm.rst=0;sm.read(1) 32 | 33 | """ 34 | 35 | def __init__(self, port=""): 36 | self._config = 0b0000 37 | self._rst = 1 38 | self._baud = 9600 39 | self._prescaler = 12 40 | self._guardtime = 16 41 | super().__init__(name=b"CRD1", fname="Smartcard", mode_byte=b"\x0b", port=port) 42 | self._configure_port() 43 | 44 | def _configure_port(self): 45 | CMD = 0b10000000 46 | CMD = CMD | self._config 47 | 48 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 49 | if self._hydrabus.read(1) == b"\x01": 50 | self.rst = self._rst 51 | return True 52 | else: 53 | self._logger.error("Error setting config.") 54 | return False 55 | 56 | def write_read(self, data=b"", read_len=0): 57 | """ 58 | Write-then-read operation 59 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-binary-SMARTCARD-mode-guide#write-then-read-operation-0b00000100 60 | 61 | Parameters: 62 | data: Data to be sent 63 | read_len: number of bytes to read 64 | """ 65 | CMD = 0b00000100 66 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 67 | 68 | self._hydrabus.write(len(data).to_bytes(2, byteorder="big")) 69 | 70 | self._hydrabus.write(read_len.to_bytes(2, byteorder="big")) 71 | 72 | self._hydrabus.timeout = 0 73 | if self._hydrabus.read(1) == b"\x00": 74 | self._logger.error("Cannot execute command. Too many bytes requested ?") 75 | return None 76 | self._hydrabus.timeout = None 77 | 78 | self._hydrabus.write(data) 79 | 80 | if self._hydrabus.read(1) != b"\x01": 81 | self._logger.warn("Unknown error. Aborting") 82 | return None 83 | 84 | return self._hydrabus.read(read_len) 85 | 86 | def write(self, data=b""): 87 | """ 88 | Write on smartard 89 | 90 | Parameters: 91 | data: data to be sent 92 | """ 93 | self.write_read(data, read_len=0) 94 | 95 | def read(self, read_len=0): 96 | """ 97 | Read on smartard 98 | 99 | Parameters: 100 | read_len: number of bytes to be read 101 | """ 102 | return self.write_read(b"", read_len=read_len) 103 | 104 | @property 105 | def prescaler(self): 106 | """ 107 | Prescaler value 108 | """ 109 | return self._prescaler 110 | 111 | @prescaler.setter 112 | def prescaler(self, value): 113 | CMD = 0b00000110 114 | 115 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 116 | self._hydrabus.write(value.to_bytes(1, byteorder="big")) 117 | 118 | if self._hydrabus.read(1) == b"\x01": 119 | self._prescaler = value 120 | return True 121 | else: 122 | self._logger.error("Error setting prescaler.") 123 | return False 124 | 125 | @property 126 | def guardtime(self): 127 | """ 128 | Guard time value 129 | """ 130 | return self._prescaler 131 | 132 | @guardtime.setter 133 | def guardtime(self, value): 134 | CMD = 0b00000111 135 | 136 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 137 | self._hydrabus.write(value.to_bytes(1, byteorder="big")) 138 | 139 | if self._hydrabus.read(1) == b"\x01": 140 | self._guardtime = value 141 | return True 142 | else: 143 | self._logger.error("Error setting guard time.") 144 | return False 145 | 146 | @property 147 | def rst(self): 148 | """ 149 | RST pin status 150 | """ 151 | return self._rst 152 | 153 | @rst.setter 154 | def rst(self, value): 155 | value = value & 1 156 | CMD = 0b00000010 157 | CMD = CMD | value 158 | 159 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 160 | if self._hydrabus.read(1) == b"\x01": 161 | self._rst = value 162 | return True 163 | else: 164 | self._logger.error("Error setting pin.") 165 | return False 166 | 167 | @property 168 | def baud(self): 169 | """ 170 | Baud rate 171 | """ 172 | return self._baud 173 | 174 | @baud.setter 175 | def baud(self, value): 176 | CMD = 0b01100000 177 | 178 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 179 | self._hydrabus.write(value.to_bytes(4, byteorder="big")) 180 | 181 | if self._hydrabus.read(1) == b"\x01": 182 | self._baud = value 183 | return True 184 | else: 185 | self._logger.error("Error setting pin.") 186 | return False 187 | 188 | @property 189 | def pullup(self): 190 | if self._config & 0b100: 191 | return 1 192 | else: 193 | return 0 194 | 195 | @pullup.setter 196 | def pullup(self, value): 197 | if value == 0: 198 | self._config = self._config & ~(1 << 2) 199 | else: 200 | self._config = self._config | (1 << 2) 201 | self._configure_port() 202 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/spi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | 18 | 19 | class SPI(Protocol): 20 | """ 21 | SPI protocol handler 22 | 23 | :example: 24 | 25 | TODO 26 | 27 | """ 28 | 29 | __SPI_DEFAULT_CONFIG = 0b010 30 | 31 | SPI1_SPEED_320K = 0b000 32 | SPI1_SPEED_650K = 0b001 33 | SPI1_SPEED_1M = 0b010 34 | SPI1_SPEED_2M = 0b011 35 | SPI1_SPEED_5M = 0b100 36 | SPI1_SPEED_10M = 0b101 37 | SPI1_SPEED_21M = 0b110 38 | SPI1_SPEED_42M = 0b111 39 | 40 | SPI2_SPEED_160K = 0b000 41 | SPI2_SPEED_320K = 0b001 42 | SPI2_SPEED_650M = 0b010 43 | SPI2_SPEED_1M = 0b011 44 | SPI2_SPEED_2M = 0b100 45 | SPI2_SPEED_5M = 0b101 46 | SPI2_SPEED_10M = 0b110 47 | SPI2_SPEED_21M = 0b111 48 | 49 | def __init__(self, port=""): 50 | self._config = self.__SPI_DEFAULT_CONFIG 51 | self._cs_val = 1 52 | super().__init__(name=b"SPI1", fname="SPI", mode_byte=b"\x01", port=port) 53 | self._configure_port() 54 | 55 | @property 56 | def cs(self): 57 | """ 58 | Chip-Select (CS) status getter 59 | """ 60 | return self._cs_val 61 | 62 | @cs.setter 63 | def cs(self, mode=0): 64 | """ 65 | Chip-Select (CS) status setter 66 | 67 | :param mode: CS pin status (0=low, 1=high) 68 | """ 69 | CMD = 0b00000010 70 | CMD = CMD | mode 71 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 72 | if self._hydrabus.read(1) == b"\x01": 73 | self._cs_val = mode 74 | else: 75 | self._logger.error("Error setting CS.") 76 | 77 | def bulk_write(self, data=b""): 78 | """ 79 | Bulk write on SPI bus 80 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-SPI-mode-guide#bulk-spi-transfer-0b0001xxxx 81 | 82 | :param data: Data to be sent 83 | :type data: bytes 84 | 85 | :return: Bytes read during the transfer 86 | :rtype: bytes 87 | """ 88 | CMD = 0b00010000 89 | if not len(data) > 0: 90 | raise ValueError("Send at least one byte") 91 | if not len(data) <= 16: 92 | raise ValueError("Too many bytes to write") 93 | CMD = CMD | (len(data) - 1) 94 | 95 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 96 | if self._hydrabus.read(1) != b"\x01": 97 | self._logger.warn("Unknown error.") 98 | return None 99 | 100 | self._hydrabus.write(data) 101 | 102 | return self._hydrabus.read(len(data)) 103 | 104 | def write_read(self, data=b"", read_len=0, drive_cs=0): 105 | """ 106 | Write-then-read operation 107 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-SPI-mode-guide#write-then-read-operation-0b00000100---0b00000101 108 | 109 | :param data: Data to be sent 110 | :type data: bytes 111 | :param read_len: Number of bytes to read 112 | :type read_len: int 113 | :param drive_cs: Whether to enable chip select before writing/reading (0=yes, 1=no) 114 | :type drive_cs: int 115 | :return: Read data 116 | :rtype: bytes 117 | """ 118 | CMD = 0b00000100 119 | CMD = CMD | drive_cs 120 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 121 | 122 | self._hydrabus.write(len(data).to_bytes(2, byteorder="big")) 123 | 124 | self._hydrabus.write(read_len.to_bytes(2, byteorder="big")) 125 | 126 | self._hydrabus.timeout = 0 127 | ret = self._hydrabus.read(1) 128 | self._hydrabus.timeout = None 129 | if ret == b"\x00": 130 | self._logger.error("Cannot execute command. Too many bytes requested ?") 131 | return None 132 | elif ret == b"\x01" and len(data) == 0: 133 | return self._hydrabus.read(read_len) 134 | elif ret == b"": 135 | # No response, we can send data 136 | self._hydrabus.write(data) 137 | 138 | ret = self._hydrabus.read(1) 139 | if ret != b"\x01": 140 | self._logger.error("Transmit error") 141 | return None 142 | 143 | return self._hydrabus.read(read_len) 144 | else: 145 | self._logger.error(f"Unknown error") 146 | 147 | def write(self, data=b"", drive_cs=0): 148 | """ 149 | Write on SPI bus 150 | 151 | :param data: data to be sent 152 | :type data: bytes 153 | :param drive_cs: Whether to enable chip select before writing/reading (0=yes, 1=no) 154 | :type drive_cs: int 155 | """ 156 | self.write_read(data, read_len=0, drive_cs=drive_cs) 157 | 158 | def read(self, read_len=0, drive_cs=0): 159 | """ 160 | Read on SPI bus 161 | 162 | :param read_len: Number of bytes to be read 163 | :type read_len: int 164 | :param drive_cs: Whether to enable chip select before writing/reading (0=yes, 1=no) 165 | :type drive_cs: int 166 | :return: Read data 167 | :rtype: bytes 168 | """ 169 | result = b"" 170 | if drive_cs == 0: 171 | self.cs = 0 172 | while read_len > 0: 173 | if read_len >= 16: 174 | to_read = 16 175 | else: 176 | to_read = read_len 177 | result += self.bulk_write(b"\xff" * to_read) 178 | read_len -= to_read 179 | if drive_cs == 0: 180 | self.cs = 1 181 | return result 182 | 183 | def set_speed(self, speed): 184 | """ 185 | Set SPI bus speed 186 | 187 | :param speed: Select any of the defined speeds (SPI_SPEED_*) 188 | """ 189 | if not speed <= 0b111: 190 | raise ValueError("Incorrect speed") 191 | CMD = 0b01100000 192 | CMD = CMD | speed 193 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 194 | 195 | if self._hydrabus.read(1) == b"\x01": 196 | return True 197 | else: 198 | self._logger.error("Error setting speed.") 199 | return False 200 | 201 | def _configure_port(self): 202 | CMD = 0b10000000 203 | CMD = CMD | self._config 204 | 205 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 206 | if self._hydrabus.read(1) == b"\x01": 207 | return True 208 | else: 209 | self._logger.error("Error setting config.") 210 | return False 211 | 212 | @property 213 | def polarity(self): 214 | """ 215 | SPI polarity 216 | """ 217 | if self._config & 0b100: 218 | return 1 219 | else: 220 | return 0 221 | 222 | @polarity.setter 223 | def polarity(self, value): 224 | if value == 0: 225 | self._config = self._config & ~(1 << 2) 226 | else: 227 | self._config = self._config | (1 << 2) 228 | self._config & 0b111 229 | self._configure_port() 230 | 231 | @property 232 | def phase(self): 233 | """ 234 | SPI clock phase 235 | """ 236 | if self._config & 0b10: 237 | return 1 238 | else: 239 | return 0 240 | 241 | @phase.setter 242 | def phase(self, value): 243 | if value == 0: 244 | self._config = self._config & ~(1 << 1) 245 | else: 246 | self._config = self._config | (1 << 1) 247 | self._config & 0b111 248 | self._configure_port() 249 | 250 | @property 251 | def device(self): 252 | """ 253 | SPI device to use (0=spi1, 1=spi2) 254 | """ 255 | if self._config & 0b1: 256 | return 1 257 | else: 258 | return 0 259 | 260 | @device.setter 261 | def device(self, value): 262 | self._config = self.__SPI_DEFAULT_CONFIG 263 | if value == 0: 264 | self._config = self._config & ~(1 << 0) 265 | else: 266 | self._config = self._config | (1 << 0) 267 | self._configure_port() 268 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/swd.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .rawwire import RawWire 17 | 18 | 19 | class SWD: 20 | """ 21 | SWD protocol handler 22 | 23 | :example: 24 | 25 | >>> import pyHydrabus 26 | >>> swd = pyHydrabus.SWD('/dev/ttyACM0') 27 | >>> swd.bus_init() 28 | >>> swd.read_dp(0) 29 | >>> swd.write_dp(4, 0x50000000) 30 | >>> swd.scan_bus() 31 | 32 | """ 33 | 34 | def __init__(self, port=""): 35 | self._interface = RawWire(port) 36 | 37 | self._interface._config = 0xA 38 | self._interface._configure_port() 39 | 40 | def __del__(self): 41 | self.close() 42 | 43 | def _apply_dp_parity(self, value): 44 | tmp = value & 0b00011110 45 | if (bin(tmp).count("1") % 2) == 1: 46 | value = value | 1 << 5 47 | return value 48 | 49 | def close(self): 50 | self._interface.close() 51 | 52 | def _sync(self): 53 | self._interface.write(b"\x00") 54 | 55 | def bus_init(self): 56 | """ 57 | Initiate SWD bus. 58 | Sends the JTAG-TO-SWD token and sync clocks 59 | """ 60 | self._interface.write( 61 | b"\xff\xff\xff\xff\xff\xff\x7b\x9e\xff\xff\xff\xff\xff\xff\x0f" 62 | ) 63 | self._sync() 64 | 65 | def read_dp(self, addr, to_ap=0): 66 | """ 67 | Read a register from DP 68 | 69 | :param addr: DP register address 70 | :type addr: int 71 | 72 | :return: Value stored in register 73 | :rtype: int 74 | 75 | :example: 76 | >>> # read RDBUFF 77 | >>> swd.read_dp(0xc) 78 | """ 79 | CMD = 0x85 80 | CMD = CMD | to_ap << 1 81 | CMD = CMD | (addr & 0b1100) << 1 82 | CMD = self._apply_dp_parity(CMD) 83 | 84 | self._interface.write(CMD.to_bytes(1, byteorder="little")) 85 | status = 0 86 | for i in range(3): 87 | status += ord(self._interface.read_bit()) << i 88 | if status == 1: 89 | retval = int.from_bytes(self._interface.read(4), byteorder="little") 90 | self._sync() 91 | return retval 92 | elif status == 2: 93 | # When receiving WAIT, retry transaction 94 | self._sync() 95 | self.write_dp(0, 0x0000001F) 96 | return self.read_dp(addr, to_ap) 97 | else: 98 | self._sync() 99 | raise ValueError(f"Returned status is {hex(status)}") 100 | 101 | def write_dp(self, addr, value, to_ap=0): 102 | """ 103 | Write to DP register 104 | 105 | :param addr: DP register address 106 | :type addr: int 107 | :param value: Value to be written to register 108 | :type value: int 109 | 110 | :example: 111 | >>> write_dp(4, 0x50000000) 112 | """ 113 | CMD = 0x81 114 | CMD = CMD | to_ap << 1 115 | CMD = CMD | (addr & 0b1100) << 1 116 | CMD = self._apply_dp_parity(CMD) 117 | 118 | self._interface.write(CMD.to_bytes(1, byteorder="little")) 119 | status = 0 120 | for i in range(3): 121 | status += ord(self._interface.read_bit()) << i 122 | self._interface.clocks(2) 123 | if status == 2: 124 | # When receiving WAIT, retry transaction 125 | self._sync() 126 | self.write_dp(0, 0x0000001F) 127 | return self.write_dp(addr, value, to_ap) 128 | if status != 1: 129 | self._sync() 130 | raise ValueError(f"Returned status is {hex(status)}") 131 | self._interface.write(value.to_bytes(4, byteorder="little")) 132 | 133 | # Send the parity but along with the sync clocks 134 | if (bin(value).count("1") % 2) == 1: 135 | self._interface.write(b"\x01") 136 | else: 137 | self._interface.write(b"\x00") 138 | 139 | def read_ap(self, address, bank): 140 | """ 141 | Read AP register 142 | 143 | :param address: AP address on the bus 144 | :type address: int 145 | :param bank: AP register address 146 | :type bank: int 147 | 148 | :return: Value read from AP 149 | :rtype: int 150 | 151 | :example: 152 | >>> # Read AP IDR 153 | >>> read_ap(0, 0xfc) 154 | 155 | """ 156 | 157 | select_reg = 0 158 | # Place AP address in DP SELECT register 159 | select_reg = select_reg | address << 24 160 | # Place bank in register as well 161 | select_reg = select_reg | (bank & 0b11110000) 162 | # Write the SELECT DP register 163 | self.write_dp(8, select_reg) 164 | self.read_dp((bank & 0b1100), to_ap=1) 165 | # Read RDBUFF 166 | return self.read_dp(0xC) 167 | 168 | def write_ap(self, address, bank, value): 169 | """ 170 | Write to AP register 171 | 172 | :param address: AP address on the bus 173 | :type address: int 174 | :param bank: AP register address 175 | :type bank: int 176 | :param value: Value to be written to register 177 | :type value: int 178 | 179 | :example: 180 | >>> write_ap(0, 0x4, 0x20000000) 181 | """ 182 | 183 | select_reg = 0 184 | # Place AP address in DP SELECT register 185 | select_reg = select_reg | address << 24 186 | # Place bank in register as well 187 | select_reg = select_reg | (bank & 0b11110000) 188 | # Write the SELECT DP register 189 | self.write_dp(8, select_reg) 190 | # Send the actual value to the AP 191 | self.write_dp((bank & 0b1100), value, to_ap=1) 192 | 193 | def scan_bus(self): 194 | """ 195 | Scan the SWD bus for APs 196 | The SWD bus must have been enabled before using this command. 197 | """ 198 | 199 | for ap in range(256): 200 | idr = self.read_ap(ap, 0xFC) 201 | if idr != 0x0 and idr != 0xFFFFFFFF: 202 | print(f"0x{ap:02x}: 0x{idr:08x}") 203 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/uart.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | 17 | from .protocol import Protocol 18 | from .common import split 19 | 20 | 21 | class UART(Protocol): 22 | """ 23 | UART protocol handler 24 | 25 | :example: TODO 26 | 27 | >>> #Read data from an EEPROM 28 | >>> import pyHydrabus 29 | >>> u=pyHydrabus.UART('/dev/hydrabus') 30 | >>> u.baud=115200 31 | >>> u.echo=1 32 | 33 | """ 34 | 35 | def __init__(self, port=""): 36 | self._config = 0b0000 37 | self._echo = 0 38 | self._baud = 9600 39 | super().__init__(name=b"ART1", fname="UART", mode_byte=b"\x03", port=port) 40 | 41 | def bulk_write(self, data=b""): 42 | """ 43 | Bulk write on UART 44 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-I2C-mode-guide#bulk-i2c-write-0b0001xxxx FIXME: change link 45 | 46 | :param data: Data to be sent 47 | :type data: bytes 48 | 49 | :return: Returns the ACK status of the written bytes (b'\x00'=ACK, b'\x01'=NACK) 50 | :rtype: list 51 | """ 52 | CMD = 0b00010000 53 | if not len(data) > 0: 54 | raise ValueError("Send at least one byte") 55 | if not len(data) <= 16: 56 | raise ValueError("Too many bytes to write") 57 | CMD = CMD | (len(data) - 1) 58 | 59 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 60 | 61 | self._hydrabus.write(data) 62 | 63 | for _ in range(len(data)): 64 | if self._hydrabus.read(1) != b"\x01": 65 | self._logger.warn("Transfer error.") 66 | 67 | def write(self, data=b""): 68 | """ 69 | Write on UART bus 70 | 71 | :param data: data to be sent 72 | :type data: bytes 73 | """ 74 | for chunk in split(data, 16): 75 | self.bulk_write(chunk) 76 | 77 | @property 78 | def echo(self): 79 | """ 80 | Local echo (0=No, 1=Yes) 81 | """ 82 | return self._echo 83 | 84 | @echo.setter 85 | def echo(self, value): 86 | value = value & 1 87 | self._echo = value 88 | CMD = 0b00000010 89 | CMD = CMD | (not value) 90 | 91 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 92 | if self._hydrabus.read(1) == b"\x01": 93 | self._rst = value 94 | return True 95 | else: 96 | self._logger.error("Error setting echo.") 97 | return False 98 | 99 | def read(self, length=1): 100 | """ 101 | Read echoed data 102 | 103 | :param length: Number of bytes to read 104 | :type length: int 105 | 106 | :return: read bytes 107 | :rtype: bytes 108 | """ 109 | 110 | return self._hydrabus.read(length) 111 | 112 | @property 113 | def baud(self): 114 | """ 115 | Baud rate 116 | """ 117 | return self._baud 118 | 119 | @baud.setter 120 | def baud(self, value): 121 | # TODO: make a pull request if deleting self._baud = value before settings it 122 | CMD = 0b00000111 123 | 124 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 125 | self._hydrabus.write(value.to_bytes(4, byteorder="big")) 126 | 127 | if self._hydrabus.read(1) == b"\x01": 128 | self._baud = value 129 | return True 130 | else: 131 | self._logger.error("Error setting pin.") 132 | return False 133 | 134 | def bridge(self): 135 | """ 136 | Bind the Hydrabus USB and UART 137 | Note that in order to leave bridge mode, you need to press the UBTN 138 | """ 139 | CMD = 0b00001111 140 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 141 | -------------------------------------------------------------------------------- /hydrabus_framework/utils/pyHydrabus/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .hydrabus import Hydrabus 17 | 18 | 19 | class Utils: 20 | """ 21 | Utilities available in hydrafw 22 | 23 | :param port: The port name 24 | :type port: str 25 | """ 26 | 27 | def __init__(self, port=""): 28 | self._hydrabus = Hydrabus(port) 29 | self._logger = logging.getLogger(__name__) 30 | 31 | self._hydrabus.flush_input() 32 | 33 | @property 34 | def adc(self): 35 | """ 36 | Read ADC value 37 | 38 | :return: ADC value (10 bits) 39 | :rtype: int 40 | """ 41 | self._hydrabus.write(b"\x14") 42 | v = self._hydrabus.read(2) 43 | return int.from_bytes(v, byteorder="big") 44 | 45 | def continuous_adc(self): 46 | """ 47 | Continuously print ADC value 48 | """ 49 | try: 50 | self._hydrabus.write(b"\x15") 51 | while 1: 52 | v = self._hydrabus.read(2) 53 | except KeyboardInterrupt: 54 | self._hydrabus.write(b"\x00") 55 | self._hydrabus.reset() 56 | return True 57 | 58 | def frequency(self): 59 | """ 60 | Read frequency value 61 | 62 | :return: (frequency, duty cycle) 63 | :rtype: tuple 64 | """ 65 | self._hydrabus.write(b"\x16") 66 | freq = self._hydrabus.read(4) 67 | duty = self._hydrabus.read(4) 68 | return ( 69 | int.from_bytes(freq, byteorder="little"), 70 | int.from_bytes(duty, byteorder="little"), 71 | ) 72 | 73 | def close(self): 74 | """ 75 | Close the communication channel and resets Hydrabus 76 | """ 77 | self._hydrabus.exit_bbio() 78 | self._hydrabus.close() 79 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tabulate==0.8.3 2 | prompt_toolkit==3.0.2 3 | serial==0.0.97 4 | pyserial==3.4 5 | requests==2.22.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | 6 | __author__ = "Jordan Ovrè " 7 | 8 | description = 'Hydrabus Framework core' 9 | name = 'hydrabus_framework' 10 | 11 | 12 | setup( 13 | name=name, 14 | version='0.0.2', 15 | packages=find_packages(), 16 | license='GPLv3', 17 | description=description, 18 | author='Ghecko', 19 | url='https://github.com/hydrabus-framework/framework', 20 | install_requires=[ 21 | 'tabulate==0.8.3', 22 | 'prompt_toolkit==3.0.2', 23 | 'serial==0.0.97', 24 | 'pyserial==3.4', 25 | 'requests==2.22.0' 26 | ], 27 | classifiers=[ 28 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 29 | 'Programming Language :: Python :: 3', 30 | 'Development Status :: 3 - Alpha' 31 | ], 32 | keywords=['hydrabus', 'framework', 'hardware', 'security', 'core', 'engine'], 33 | scripts=['hydrabus_framework/hbfconsole', 'hydrabus_framework/hbfupdate'], 34 | ) 35 | -------------------------------------------------------------------------------- /tests/travis.txt: -------------------------------------------------------------------------------- 1 | ? 2 | show modules 3 | use uart/baudrates 4 | show options 5 | set timeout 1 6 | set unknown False 7 | setg dumpfile dump.bin 8 | run 9 | back 10 | show config 11 | back 12 | show global 13 | back 14 | use spi/dump_eeprom 15 | show options 16 | unsetg dumpfile 17 | run 18 | reset 19 | exit 20 | --------------------------------------------------------------------------------