diff --git a/applications/external/esubghz_chat/LICENSE b/applications/external/esubghz_chat/LICENSE
new file mode 100644
index 000000000..f288702d2
--- /dev/null
+++ b/applications/external/esubghz_chat/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/applications/external/esubghz_chat/application.fam b/applications/external/esubghz_chat/application.fam
new file mode 100644
index 000000000..70b8c6c70
--- /dev/null
+++ b/applications/external/esubghz_chat/application.fam
@@ -0,0 +1,15 @@
+App(
+ appid="esubghz_chat",
+ name="Enhanced Sub-Ghz Chat",
+ apptype=FlipperAppType.EXTERNAL,
+ entry_point="esubghz_chat",
+ requires=[
+ "gui",
+ "subghz",
+ ],
+ stack_size=8 * 1024,
+ fap_category="Sub-GHz",
+ fap_icon="assets/chat_10px.png",
+ fap_icon_assets="assets",
+ fap_icon_assets_symbol="esubghz_chat",
+)
diff --git a/applications/external/esubghz_chat/assets/Loading_24.png b/applications/external/esubghz_chat/assets/Loading_24.png
new file mode 100644
index 000000000..93a59fe68
Binary files /dev/null and b/applications/external/esubghz_chat/assets/Loading_24.png differ
diff --git a/applications/external/esubghz_chat/assets/chat_10px.png b/applications/external/esubghz_chat/assets/chat_10px.png
new file mode 100644
index 000000000..9ee8343c1
Binary files /dev/null and b/applications/external/esubghz_chat/assets/chat_10px.png differ
diff --git a/applications/external/esubghz_chat/crypto/aes.c b/applications/external/esubghz_chat/crypto/aes.c
new file mode 100644
index 000000000..2be72d571
--- /dev/null
+++ b/applications/external/esubghz_chat/crypto/aes.c
@@ -0,0 +1,483 @@
+/******************************************************************************
+*
+* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
+*
+* This is a simple and straightforward implementation of the AES Rijndael
+* 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus
+* of this work was correctness & accuracy. It is written in 'C' without any
+* particular focus upon optimization or speed. It should be endian (memory
+* byte order) neutral since the few places that care are handled explicitly.
+*
+* This implementation of Rijndael was created by Steven M. Gibson of GRC.com.
+*
+* It is intended for general purpose use, but was written in support of GRC's
+* reference implementation of the SQRL (Secure Quick Reliable Login) client.
+*
+* See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
+*
+* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
+* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
+*
+*******************************************************************************/
+
+#include "aes.h"
+
+static int aes_tables_inited = 0; // run-once flag for performing key
+ // expasion table generation (see below)
+/*
+ * The following static local tables must be filled-in before the first use of
+ * the GCM or AES ciphers. They are used for the AES key expansion/scheduling
+ * and once built are read-only and thread safe. The "gcm_initialize" function
+ * must be called once during system initialization to populate these arrays
+ * for subsequent use by the AES key scheduler. If they have not been built
+ * before attempted use, an error will be returned to the caller.
+ *
+ * NOTE: GCM Encryption/Decryption does NOT REQUIRE AES decryption. Since
+ * GCM uses AES in counter-mode, where the AES cipher output is XORed with
+ * the GCM input, we ONLY NEED AES encryption. Thus, to save space AES
+ * decryption is typically disabled by setting AES_DECRYPTION to 0 in aes.h.
+ */
+ // We always need our forward tables
+static uchar FSb[256]; // Forward substitution box (FSb)
+static uint32_t FT0[256]; // Forward key schedule assembly tables
+static uint32_t FT1[256];
+static uint32_t FT2[256];
+static uint32_t FT3[256];
+
+#if AES_DECRYPTION // We ONLY need reverse for decryption
+static uchar RSb[256]; // Reverse substitution box (RSb)
+static uint32_t RT0[256]; // Reverse key schedule assembly tables
+static uint32_t RT1[256];
+static uint32_t RT2[256];
+static uint32_t RT3[256];
+#endif /* AES_DECRYPTION */
+
+static uint32_t RCON[10]; // AES round constants
+
+/*
+ * Platform Endianness Neutralizing Load and Store Macro definitions
+ * AES wants platform-neutral Little Endian (LE) byte ordering
+ */
+#define GET_UINT32_LE(n,b,i) { \
+ (n) = ( (uint32_t) (b)[(i) ] ) \
+ | ( (uint32_t) (b)[(i) + 1] << 8 ) \
+ | ( (uint32_t) (b)[(i) + 2] << 16 ) \
+ | ( (uint32_t) (b)[(i) + 3] << 24 ); }
+
+#define PUT_UINT32_LE(n,b,i) { \
+ (b)[(i) ] = (uchar) ( (n) ); \
+ (b)[(i) + 1] = (uchar) ( (n) >> 8 ); \
+ (b)[(i) + 2] = (uchar) ( (n) >> 16 ); \
+ (b)[(i) + 3] = (uchar) ( (n) >> 24 ); }
+
+/*
+ * AES forward and reverse encryption round processing macros
+ */
+#define AES_FROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3) \
+{ \
+ X0 = *RK++ ^ FT0[ ( Y0 ) & 0xFF ] ^ \
+ FT1[ ( Y1 >> 8 ) & 0xFF ] ^ \
+ FT2[ ( Y2 >> 16 ) & 0xFF ] ^ \
+ FT3[ ( Y3 >> 24 ) & 0xFF ]; \
+ \
+ X1 = *RK++ ^ FT0[ ( Y1 ) & 0xFF ] ^ \
+ FT1[ ( Y2 >> 8 ) & 0xFF ] ^ \
+ FT2[ ( Y3 >> 16 ) & 0xFF ] ^ \
+ FT3[ ( Y0 >> 24 ) & 0xFF ]; \
+ \
+ X2 = *RK++ ^ FT0[ ( Y2 ) & 0xFF ] ^ \
+ FT1[ ( Y3 >> 8 ) & 0xFF ] ^ \
+ FT2[ ( Y0 >> 16 ) & 0xFF ] ^ \
+ FT3[ ( Y1 >> 24 ) & 0xFF ]; \
+ \
+ X3 = *RK++ ^ FT0[ ( Y3 ) & 0xFF ] ^ \
+ FT1[ ( Y0 >> 8 ) & 0xFF ] ^ \
+ FT2[ ( Y1 >> 16 ) & 0xFF ] ^ \
+ FT3[ ( Y2 >> 24 ) & 0xFF ]; \
+}
+
+#define AES_RROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3) \
+{ \
+ X0 = *RK++ ^ RT0[ ( Y0 ) & 0xFF ] ^ \
+ RT1[ ( Y3 >> 8 ) & 0xFF ] ^ \
+ RT2[ ( Y2 >> 16 ) & 0xFF ] ^ \
+ RT3[ ( Y1 >> 24 ) & 0xFF ]; \
+ \
+ X1 = *RK++ ^ RT0[ ( Y1 ) & 0xFF ] ^ \
+ RT1[ ( Y0 >> 8 ) & 0xFF ] ^ \
+ RT2[ ( Y3 >> 16 ) & 0xFF ] ^ \
+ RT3[ ( Y2 >> 24 ) & 0xFF ]; \
+ \
+ X2 = *RK++ ^ RT0[ ( Y2 ) & 0xFF ] ^ \
+ RT1[ ( Y1 >> 8 ) & 0xFF ] ^ \
+ RT2[ ( Y0 >> 16 ) & 0xFF ] ^ \
+ RT3[ ( Y3 >> 24 ) & 0xFF ]; \
+ \
+ X3 = *RK++ ^ RT0[ ( Y3 ) & 0xFF ] ^ \
+ RT1[ ( Y2 >> 8 ) & 0xFF ] ^ \
+ RT2[ ( Y1 >> 16 ) & 0xFF ] ^ \
+ RT3[ ( Y0 >> 24 ) & 0xFF ]; \
+}
+
+/*
+ * These macros improve the readability of the key
+ * generation initialization code by collapsing
+ * repetitive common operations into logical pieces.
+ */
+#define ROTL8(x) ( ( x << 8 ) & 0xFFFFFFFF ) | ( x >> 24 )
+#define XTIME(x) ( ( x << 1 ) ^ ( ( x & 0x80 ) ? 0x1B : 0x00 ) )
+#define MUL(x,y) ( ( x && y ) ? pow[(log[x]+log[y]) % 255] : 0 )
+#define MIX(x,y) { y = ( (y << 1) | (y >> 7) ) & 0xFF; x ^= y; }
+#define CPY128 { *RK++ = *SK++; *RK++ = *SK++; \
+ *RK++ = *SK++; *RK++ = *SK++; }
+
+/******************************************************************************
+ *
+ * AES_INIT_KEYGEN_TABLES
+ *
+ * Fills the AES key expansion tables allocated above with their static
+ * data. This is not "per key" data, but static system-wide read-only
+ * table data. THIS FUNCTION IS NOT THREAD SAFE. It must be called once
+ * at system initialization to setup the tables for all subsequent use.
+ *
+ ******************************************************************************/
+void aes_init_keygen_tables( void )
+{
+ int i, x, y, z; // general purpose iteration and computation locals
+ int pow[256];
+ int log[256];
+
+ if (aes_tables_inited) return;
+
+ // fill the 'pow' and 'log' tables over GF(2^8)
+ for( i = 0, x = 1; i < 256; i++ ) {
+ pow[i] = x;
+ log[x] = i;
+ x = ( x ^ XTIME( x ) ) & 0xFF;
+ }
+ // compute the round constants
+ for( i = 0, x = 1; i < 10; i++ ) {
+ RCON[i] = (uint32_t) x;
+ x = XTIME( x ) & 0xFF;
+ }
+ // fill the forward and reverse substitution boxes
+ FSb[0x00] = 0x63;
+#if AES_DECRYPTION // whether AES decryption is supported
+ RSb[0x63] = 0x00;
+#endif /* AES_DECRYPTION */
+
+ for( i = 1; i < 256; i++ ) {
+ x = y = pow[255 - log[i]];
+ MIX(x,y);
+ MIX(x,y);
+ MIX(x,y);
+ MIX(x,y);
+ FSb[i] = (uchar) ( x ^= 0x63 );
+#if AES_DECRYPTION // whether AES decryption is supported
+ RSb[x] = (uchar) i;
+#endif /* AES_DECRYPTION */
+
+ }
+ // generate the forward and reverse key expansion tables
+ for( i = 0; i < 256; i++ ) {
+ x = FSb[i];
+ y = XTIME( x ) & 0xFF;
+ z = ( y ^ x ) & 0xFF;
+
+ FT0[i] = ( (uint32_t) y ) ^ ( (uint32_t) x << 8 ) ^
+ ( (uint32_t) x << 16 ) ^ ( (uint32_t) z << 24 );
+
+ FT1[i] = ROTL8( FT0[i] );
+ FT2[i] = ROTL8( FT1[i] );
+ FT3[i] = ROTL8( FT2[i] );
+
+#if AES_DECRYPTION // whether AES decryption is supported
+ x = RSb[i];
+
+ RT0[i] = ( (uint32_t) MUL( 0x0E, x ) ) ^
+ ( (uint32_t) MUL( 0x09, x ) << 8 ) ^
+ ( (uint32_t) MUL( 0x0D, x ) << 16 ) ^
+ ( (uint32_t) MUL( 0x0B, x ) << 24 );
+
+ RT1[i] = ROTL8( RT0[i] );
+ RT2[i] = ROTL8( RT1[i] );
+ RT3[i] = ROTL8( RT2[i] );
+#endif /* AES_DECRYPTION */
+ }
+ aes_tables_inited = 1; // flag that the tables have been generated
+} // to permit subsequent use of the AES cipher
+
+/******************************************************************************
+ *
+ * AES_SET_ENCRYPTION_KEY
+ *
+ * This is called by 'aes_setkey' when we're establishing a key for
+ * subsequent encryption. We give it a pointer to the encryption
+ * context, a pointer to the key, and the key's length in bytes.
+ * Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits).
+ *
+ ******************************************************************************/
+int aes_set_encryption_key( aes_context *ctx,
+ const uchar *key,
+ uint keysize )
+{
+ uint i; // general purpose iteration local
+ uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer
+
+ for( i = 0; i < (keysize >> 2); i++ ) {
+ GET_UINT32_LE( RK[i], key, i << 2 );
+ }
+
+ switch( ctx->rounds )
+ {
+ case 10:
+ for( i = 0; i < 10; i++, RK += 4 ) {
+ RK[4] = RK[0] ^ RCON[i] ^
+ ( (uint32_t) FSb[ ( RK[3] >> 8 ) & 0xFF ] ) ^
+ ( (uint32_t) FSb[ ( RK[3] >> 16 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) FSb[ ( RK[3] >> 24 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) FSb[ ( RK[3] ) & 0xFF ] << 24 );
+
+ RK[5] = RK[1] ^ RK[4];
+ RK[6] = RK[2] ^ RK[5];
+ RK[7] = RK[3] ^ RK[6];
+ }
+ break;
+
+ case 12:
+ for( i = 0; i < 8; i++, RK += 6 ) {
+ RK[6] = RK[0] ^ RCON[i] ^
+ ( (uint32_t) FSb[ ( RK[5] >> 8 ) & 0xFF ] ) ^
+ ( (uint32_t) FSb[ ( RK[5] >> 16 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) FSb[ ( RK[5] >> 24 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) FSb[ ( RK[5] ) & 0xFF ] << 24 );
+
+ RK[7] = RK[1] ^ RK[6];
+ RK[8] = RK[2] ^ RK[7];
+ RK[9] = RK[3] ^ RK[8];
+ RK[10] = RK[4] ^ RK[9];
+ RK[11] = RK[5] ^ RK[10];
+ }
+ break;
+
+ case 14:
+ for( i = 0; i < 7; i++, RK += 8 ) {
+ RK[8] = RK[0] ^ RCON[i] ^
+ ( (uint32_t) FSb[ ( RK[7] >> 8 ) & 0xFF ] ) ^
+ ( (uint32_t) FSb[ ( RK[7] >> 16 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) FSb[ ( RK[7] >> 24 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) FSb[ ( RK[7] ) & 0xFF ] << 24 );
+
+ RK[9] = RK[1] ^ RK[8];
+ RK[10] = RK[2] ^ RK[9];
+ RK[11] = RK[3] ^ RK[10];
+
+ RK[12] = RK[4] ^
+ ( (uint32_t) FSb[ ( RK[11] ) & 0xFF ] ) ^
+ ( (uint32_t) FSb[ ( RK[11] >> 8 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) FSb[ ( RK[11] >> 16 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) FSb[ ( RK[11] >> 24 ) & 0xFF ] << 24 );
+
+ RK[13] = RK[5] ^ RK[12];
+ RK[14] = RK[6] ^ RK[13];
+ RK[15] = RK[7] ^ RK[14];
+ }
+ break;
+
+ default:
+ return -1;
+ }
+ return( 0 );
+}
+
+#if AES_DECRYPTION // whether AES decryption is supported
+
+/******************************************************************************
+ *
+ * AES_SET_DECRYPTION_KEY
+ *
+ * This is called by 'aes_setkey' when we're establishing a
+ * key for subsequent decryption. We give it a pointer to
+ * the encryption context, a pointer to the key, and the key's
+ * length in bits. Valid lengths are: 128, 192, or 256 bits.
+ *
+ ******************************************************************************/
+int aes_set_decryption_key( aes_context *ctx,
+ const uchar *key,
+ uint keysize )
+{
+ int i, j;
+ aes_context cty; // a calling aes context for set_encryption_key
+ uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer
+ uint32_t *SK;
+ int ret;
+
+ cty.rounds = ctx->rounds; // initialize our local aes context
+ cty.rk = cty.buf; // round count and key buf pointer
+
+ if (( ret = aes_set_encryption_key( &cty, key, keysize )) != 0 )
+ return( ret );
+
+ SK = cty.rk + cty.rounds * 4;
+
+ CPY128 // copy a 128-bit block from *SK to *RK
+
+ for( i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8 ) {
+ for( j = 0; j < 4; j++, SK++ ) {
+ *RK++ = RT0[ FSb[ ( *SK ) & 0xFF ] ] ^
+ RT1[ FSb[ ( *SK >> 8 ) & 0xFF ] ] ^
+ RT2[ FSb[ ( *SK >> 16 ) & 0xFF ] ] ^
+ RT3[ FSb[ ( *SK >> 24 ) & 0xFF ] ];
+ }
+ }
+ CPY128 // copy a 128-bit block from *SK to *RK
+ memset( &cty, 0, sizeof( aes_context ) ); // clear local aes context
+ return( 0 );
+}
+
+#endif /* AES_DECRYPTION */
+
+/******************************************************************************
+ *
+ * AES_SETKEY
+ *
+ * Invoked to establish the key schedule for subsequent encryption/decryption
+ *
+ ******************************************************************************/
+int aes_setkey( aes_context *ctx, // AES context provided by our caller
+ int mode, // ENCRYPT or DECRYPT flag
+ const uchar *key, // pointer to the key
+ uint keysize ) // key length in bytes
+{
+ // since table initialization is not thread safe, we could either add
+ // system-specific mutexes and init the AES key generation tables on
+ // demand, or ask the developer to simply call "gcm_initialize" once during
+ // application startup before threading begins. That's what we choose.
+ if( !aes_tables_inited ) return ( -1 ); // fail the call when not inited.
+
+ ctx->mode = mode; // capture the key type we're creating
+ ctx->rk = ctx->buf; // initialize our round key pointer
+
+ switch( keysize ) // set the rounds count based upon the keysize
+ {
+ case 16: ctx->rounds = 10; break; // 16-byte, 128-bit key
+ case 24: ctx->rounds = 12; break; // 24-byte, 192-bit key
+ case 32: ctx->rounds = 14; break; // 32-byte, 256-bit key
+ default: return(-1);
+ }
+
+#if AES_DECRYPTION
+ if( mode == DECRYPT ) // expand our key for encryption or decryption
+ return( aes_set_decryption_key( ctx, key, keysize ) );
+ else /* ENCRYPT */
+#endif /* AES_DECRYPTION */
+ return( aes_set_encryption_key( ctx, key, keysize ) );
+}
+
+/******************************************************************************
+ *
+ * AES_CIPHER
+ *
+ * Perform AES encryption and decryption.
+ * The AES context will have been setup with the encryption mode
+ * and all keying information appropriate for the task.
+ *
+ ******************************************************************************/
+int aes_cipher( aes_context *ctx,
+ const uchar input[16],
+ uchar output[16] )
+{
+ int i;
+ uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; // general purpose locals
+
+ RK = ctx->rk;
+
+ GET_UINT32_LE( X0, input, 0 ); X0 ^= *RK++; // load our 128-bit
+ GET_UINT32_LE( X1, input, 4 ); X1 ^= *RK++; // input buffer in a storage
+ GET_UINT32_LE( X2, input, 8 ); X2 ^= *RK++; // memory endian-neutral way
+ GET_UINT32_LE( X3, input, 12 ); X3 ^= *RK++;
+
+#if AES_DECRYPTION // whether AES decryption is supported
+
+ if( ctx->mode == DECRYPT )
+ {
+ for( i = (ctx->rounds >> 1) - 1; i > 0; i-- )
+ {
+ AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );
+ AES_RROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );
+ }
+
+ AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );
+
+ X0 = *RK++ ^ \
+ ( (uint32_t) RSb[ ( Y0 ) & 0xFF ] ) ^
+ ( (uint32_t) RSb[ ( Y3 >> 8 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) RSb[ ( Y2 >> 16 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) RSb[ ( Y1 >> 24 ) & 0xFF ] << 24 );
+
+ X1 = *RK++ ^ \
+ ( (uint32_t) RSb[ ( Y1 ) & 0xFF ] ) ^
+ ( (uint32_t) RSb[ ( Y0 >> 8 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) RSb[ ( Y3 >> 16 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) RSb[ ( Y2 >> 24 ) & 0xFF ] << 24 );
+
+ X2 = *RK++ ^ \
+ ( (uint32_t) RSb[ ( Y2 ) & 0xFF ] ) ^
+ ( (uint32_t) RSb[ ( Y1 >> 8 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) RSb[ ( Y0 >> 16 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) RSb[ ( Y3 >> 24 ) & 0xFF ] << 24 );
+
+ X3 = *RK++ ^ \
+ ( (uint32_t) RSb[ ( Y3 ) & 0xFF ] ) ^
+ ( (uint32_t) RSb[ ( Y2 >> 8 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) RSb[ ( Y1 >> 16 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) RSb[ ( Y0 >> 24 ) & 0xFF ] << 24 );
+ }
+ else /* ENCRYPT */
+ {
+#endif /* AES_DECRYPTION */
+
+ for( i = (ctx->rounds >> 1) - 1; i > 0; i-- )
+ {
+ AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );
+ AES_FROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );
+ }
+
+ AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );
+
+ X0 = *RK++ ^ \
+ ( (uint32_t) FSb[ ( Y0 ) & 0xFF ] ) ^
+ ( (uint32_t) FSb[ ( Y1 >> 8 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) FSb[ ( Y2 >> 16 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) FSb[ ( Y3 >> 24 ) & 0xFF ] << 24 );
+
+ X1 = *RK++ ^ \
+ ( (uint32_t) FSb[ ( Y1 ) & 0xFF ] ) ^
+ ( (uint32_t) FSb[ ( Y2 >> 8 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) FSb[ ( Y3 >> 16 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) FSb[ ( Y0 >> 24 ) & 0xFF ] << 24 );
+
+ X2 = *RK++ ^ \
+ ( (uint32_t) FSb[ ( Y2 ) & 0xFF ] ) ^
+ ( (uint32_t) FSb[ ( Y3 >> 8 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) FSb[ ( Y0 >> 16 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) FSb[ ( Y1 >> 24 ) & 0xFF ] << 24 );
+
+ X3 = *RK++ ^ \
+ ( (uint32_t) FSb[ ( Y3 ) & 0xFF ] ) ^
+ ( (uint32_t) FSb[ ( Y0 >> 8 ) & 0xFF ] << 8 ) ^
+ ( (uint32_t) FSb[ ( Y1 >> 16 ) & 0xFF ] << 16 ) ^
+ ( (uint32_t) FSb[ ( Y2 >> 24 ) & 0xFF ] << 24 );
+
+#if AES_DECRYPTION // whether AES decryption is supported
+ }
+#endif /* AES_DECRYPTION */
+
+ PUT_UINT32_LE( X0, output, 0 );
+ PUT_UINT32_LE( X1, output, 4 );
+ PUT_UINT32_LE( X2, output, 8 );
+ PUT_UINT32_LE( X3, output, 12 );
+
+ return( 0 );
+}
+/* end of aes.c */
diff --git a/applications/external/esubghz_chat/crypto/aes.h b/applications/external/esubghz_chat/crypto/aes.h
new file mode 100644
index 000000000..d4d28db52
--- /dev/null
+++ b/applications/external/esubghz_chat/crypto/aes.h
@@ -0,0 +1,81 @@
+/******************************************************************************
+*
+* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
+*
+* This is a simple and straightforward implementation of the AES Rijndael
+* 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus
+* of this work was correctness & accuracy. It is written in 'C' without any
+* particular focus upon optimization or speed. It should be endian (memory
+* byte order) neutral since the few places that care are handled explicitly.
+*
+* This implementation of Rijndael was created by Steven M. Gibson of GRC.com.
+*
+* It is intended for general purpose use, but was written in support of GRC's
+* reference implementation of the SQRL (Secure Quick Reliable Login) client.
+*
+* See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
+*
+* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
+* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
+*
+*******************************************************************************/
+
+#ifndef AES_HEADER
+#define AES_HEADER
+
+/******************************************************************************/
+#define AES_DECRYPTION 0 // whether AES decryption is supported
+/******************************************************************************/
+
+#include
+
+#define ENCRYPT 1 // specify whether we're encrypting
+#define DECRYPT 0 // or decrypting
+
+#if defined(_MSC_VER)
+ #include
+ typedef UINT32 uint32_t;
+#else
+ #include
+#endif
+
+typedef unsigned char uchar; // add some convienent shorter types
+typedef unsigned int uint;
+
+
+/******************************************************************************
+ * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use
+ ******************************************************************************/
+void aes_init_keygen_tables( void );
+
+
+/******************************************************************************
+ * AES_CONTEXT : cipher context / holds inter-call data
+ ******************************************************************************/
+typedef struct {
+ int mode; // 1 for Encryption, 0 for Decryption
+ int rounds; // keysize-based rounds count
+ uint32_t *rk; // pointer to current round key
+ uint32_t buf[68]; // key expansion buffer
+} aes_context;
+
+
+/******************************************************************************
+ * AES_SETKEY : called to expand the key for encryption or decryption
+ ******************************************************************************/
+int aes_setkey( aes_context *ctx, // pointer to context
+ int mode, // 1 or 0 for Encrypt/Decrypt
+ const uchar *key, // AES input key
+ uint keysize ); // size in bytes (must be 16, 24, 32 for
+ // 128, 192 or 256-bit keys respectively)
+ // returns 0 for success
+
+/******************************************************************************
+ * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data
+ ******************************************************************************/
+int aes_cipher( aes_context *ctx, // pointer to context
+ const uchar input[16], // 128-bit block to en/decipher
+ uchar output[16] ); // 128-bit output result block
+ // returns 0 for success
+
+#endif /* AES_HEADER */
diff --git a/applications/external/esubghz_chat/crypto/gcm.c b/applications/external/esubghz_chat/crypto/gcm.c
new file mode 100644
index 000000000..6b1cba211
--- /dev/null
+++ b/applications/external/esubghz_chat/crypto/gcm.c
@@ -0,0 +1,511 @@
+/******************************************************************************
+*
+* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
+*
+* This is a simple and straightforward implementation of AES-GCM authenticated
+* encryption. The focus of this work was correctness & accuracy. It is written
+* in straight 'C' without any particular focus upon optimization or speed. It
+* should be endian (memory byte order) neutral since the few places that care
+* are handled explicitly.
+*
+* This implementation of AES-GCM was created by Steven M. Gibson of GRC.com.
+*
+* It is intended for general purpose use, but was written in support of GRC's
+* reference implementation of the SQRL (Secure Quick Reliable Login) client.
+*
+* See: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
+* http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/
+* gcm/gcm-revised-spec.pdf
+*
+* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
+* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
+*
+*******************************************************************************/
+
+#include "gcm.h"
+#include "aes.h"
+
+/******************************************************************************
+ * ==== IMPLEMENTATION WARNING ====
+ *
+ * This code was developed for use within SQRL's fixed environmnent. Thus, it
+ * is somewhat less "general purpose" than it would be if it were designed as
+ * a general purpose AES-GCM library. Specifically, it bothers with almost NO
+ * error checking on parameter limits, buffer bounds, etc. It assumes that it
+ * is being invoked by its author or by someone who understands the values it
+ * expects to receive. Its behavior will be undefined otherwise.
+ *
+ * All functions that might fail are defined to return 'ints' to indicate a
+ * problem. Most do not do so now. But this allows for error propagation out
+ * of internal functions if robust error checking should ever be desired.
+ *
+ ******************************************************************************/
+
+/* Calculating the "GHASH"
+ *
+ * There are many ways of calculating the so-called GHASH in software, each with
+ * a traditional size vs performance tradeoff. The GHASH (Galois field hash) is
+ * an intriguing construction which takes two 128-bit strings (also the cipher's
+ * block size and the fundamental operation size for the system) and hashes them
+ * into a third 128-bit result.
+ *
+ * Many implementation solutions have been worked out that use large precomputed
+ * table lookups in place of more time consuming bit fiddling, and this approach
+ * can be scaled easily upward or downward as needed to change the time/space
+ * tradeoff. It's been studied extensively and there's a solid body of theory and
+ * practice. For example, without using any lookup tables an implementation
+ * might obtain 119 cycles per byte throughput, whereas using a simple, though
+ * large, key-specific 64 kbyte 8-bit lookup table the performance jumps to 13
+ * cycles per byte.
+ *
+ * And Intel's processors have, since 2010, included an instruction which does
+ * the entire 128x128->128 bit job in just several 64x64->128 bit pieces.
+ *
+ * Since SQRL is interactive, and only processing a few 128-bit blocks, I've
+ * settled upon a relatively slower but appealing small-table compromise which
+ * folds a bunch of not only time consuming but also bit twiddling into a simple
+ * 16-entry table which is attributed to Victor Shoup's 1996 work while at
+ * Bellcore: "On Fast and Provably Secure MessageAuthentication Based on
+ * Universal Hashing." See: http://www.shoup.net/papers/macs.pdf
+ * See, also section 4.1 of the "gcm-revised-spec" cited above.
+ */
+
+/*
+ * This 16-entry table of pre-computed constants is used by the
+ * GHASH multiplier to improve over a strictly table-free but
+ * significantly slower 128x128 bit multiple within GF(2^128).
+ */
+static const uint64_t last4[16] = {
+ 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
+ 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0 };
+
+/*
+ * Platform Endianness Neutralizing Load and Store Macro definitions
+ * GCM wants platform-neutral Big Endian (BE) byte ordering
+ */
+#define GET_UINT32_BE(n,b,i) { \
+ (n) = ( (uint32_t) (b)[(i) ] << 24 ) \
+ | ( (uint32_t) (b)[(i) + 1] << 16 ) \
+ | ( (uint32_t) (b)[(i) + 2] << 8 ) \
+ | ( (uint32_t) (b)[(i) + 3] ); }
+
+#define PUT_UINT32_BE(n,b,i) { \
+ (b)[(i) ] = (uchar) ( (n) >> 24 ); \
+ (b)[(i) + 1] = (uchar) ( (n) >> 16 ); \
+ (b)[(i) + 2] = (uchar) ( (n) >> 8 ); \
+ (b)[(i) + 3] = (uchar) ( (n) ); }
+
+
+/******************************************************************************
+ *
+ * GCM_INITIALIZE
+ *
+ * Must be called once to initialize the GCM library.
+ *
+ * At present, this only calls the AES keygen table generator, which expands
+ * the AES keying tables for use. This is NOT A THREAD-SAFE function, so it
+ * MUST be called during system initialization before a multi-threading
+ * environment is running.
+ *
+ ******************************************************************************/
+int gcm_initialize( void )
+{
+ aes_init_keygen_tables();
+ return( 0 );
+}
+
+
+/******************************************************************************
+ *
+ * GCM_MULT
+ *
+ * Performs a GHASH operation on the 128-bit input vector 'x', setting
+ * the 128-bit output vector to 'x' times H using our precomputed tables.
+ * 'x' and 'output' are seen as elements of GCM's GF(2^128) Galois field.
+ *
+ ******************************************************************************/
+static void gcm_mult( gcm_context *ctx, // pointer to established context
+ const uchar x[16], // pointer to 128-bit input vector
+ uchar output[16] ) // pointer to 128-bit output vector
+{
+ int i;
+ uchar lo, hi, rem;
+ uint64_t zh, zl;
+
+ lo = (uchar)( x[15] & 0x0f );
+ hi = (uchar)( x[15] >> 4 );
+ zh = ctx->HH[lo];
+ zl = ctx->HL[lo];
+
+ for( i = 15; i >= 0; i-- ) {
+ lo = (uchar) ( x[i] & 0x0f );
+ hi = (uchar) ( x[i] >> 4 );
+
+ if( i != 15 ) {
+ rem = (uchar) ( zl & 0x0f );
+ zl = ( zh << 60 ) | ( zl >> 4 );
+ zh = ( zh >> 4 );
+ zh ^= (uint64_t) last4[rem] << 48;
+ zh ^= ctx->HH[lo];
+ zl ^= ctx->HL[lo];
+ }
+ rem = (uchar) ( zl & 0x0f );
+ zl = ( zh << 60 ) | ( zl >> 4 );
+ zh = ( zh >> 4 );
+ zh ^= (uint64_t) last4[rem] << 48;
+ zh ^= ctx->HH[hi];
+ zl ^= ctx->HL[hi];
+ }
+ PUT_UINT32_BE( zh >> 32, output, 0 );
+ PUT_UINT32_BE( zh, output, 4 );
+ PUT_UINT32_BE( zl >> 32, output, 8 );
+ PUT_UINT32_BE( zl, output, 12 );
+}
+
+
+/******************************************************************************
+ *
+ * GCM_SETKEY
+ *
+ * This is called to set the AES-GCM key. It initializes the AES key
+ * and populates the gcm context's pre-calculated HTables.
+ *
+ ******************************************************************************/
+int gcm_setkey( gcm_context *ctx, // pointer to caller-provided gcm context
+ const uchar *key, // pointer to the AES encryption key
+ const uint keysize) // size in bytes (must be 16, 24, 32 for
+ // 128, 192 or 256-bit keys respectively)
+{
+ int ret, i, j;
+ uint64_t hi, lo;
+ uint64_t vl, vh;
+ unsigned char h[16];
+
+ memset( ctx, 0, sizeof(gcm_context) ); // zero caller-provided GCM context
+ memset( h, 0, 16 ); // initialize the block to encrypt
+
+ // encrypt the null 128-bit block to generate a key-based value
+ // which is then used to initialize our GHASH lookup tables
+ if(( ret = aes_setkey( &ctx->aes_ctx, ENCRYPT, key, keysize )) != 0 )
+ return( ret );
+ if(( ret = aes_cipher( &ctx->aes_ctx, h, h )) != 0 )
+ return( ret );
+
+ GET_UINT32_BE( hi, h, 0 ); // pack h as two 64-bit ints, big-endian
+ GET_UINT32_BE( lo, h, 4 );
+ vh = (uint64_t) hi << 32 | lo;
+
+ GET_UINT32_BE( hi, h, 8 );
+ GET_UINT32_BE( lo, h, 12 );
+ vl = (uint64_t) hi << 32 | lo;
+
+ ctx->HL[8] = vl; // 8 = 1000 corresponds to 1 in GF(2^128)
+ ctx->HH[8] = vh;
+ ctx->HH[0] = 0; // 0 corresponds to 0 in GF(2^128)
+ ctx->HL[0] = 0;
+
+ for( i = 4; i > 0; i >>= 1 ) {
+ uint32_t T = (uint32_t) ( vl & 1 ) * 0xe1000000U;
+ vl = ( vh << 63 ) | ( vl >> 1 );
+ vh = ( vh >> 1 ) ^ ( (uint64_t) T << 32);
+ ctx->HL[i] = vl;
+ ctx->HH[i] = vh;
+ }
+ for (i = 2; i < 16; i <<= 1 ) {
+ uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i;
+ vh = *HiH;
+ vl = *HiL;
+ for( j = 1; j < i; j++ ) {
+ HiH[j] = vh ^ ctx->HH[j];
+ HiL[j] = vl ^ ctx->HL[j];
+ }
+ }
+ return( 0 );
+}
+
+
+/******************************************************************************
+ *
+ * GCM processing occurs four phases: SETKEY, START, UPDATE and FINISH.
+ *
+ * SETKEY:
+ *
+ * START: Sets the Encryption/Decryption mode.
+ * Accepts the initialization vector and additional data.
+ *
+ * UPDATE: Encrypts or decrypts the plaintext or ciphertext.
+ *
+ * FINISH: Performs a final GHASH to generate the authentication tag.
+ *
+ ******************************************************************************
+ *
+ * GCM_START
+ *
+ * Given a user-provided GCM context, this initializes it, sets the encryption
+ * mode, and preprocesses the initialization vector and additional AEAD data.
+ *
+ ******************************************************************************/
+int gcm_start( gcm_context *ctx, // pointer to user-provided GCM context
+ int mode, // GCM_ENCRYPT or GCM_DECRYPT
+ const uchar *iv, // pointer to initialization vector
+ size_t iv_len, // IV length in bytes (should == 12)
+ const uchar *add, // ptr to additional AEAD data (NULL if none)
+ size_t add_len ) // length of additional AEAD data (bytes)
+{
+ int ret; // our error return if the AES encrypt fails
+ uchar work_buf[16]; // XOR source built from provided IV if len != 16
+ const uchar *p; // general purpose array pointer
+ size_t use_len; // byte count to process, up to 16 bytes
+ size_t i; // local loop iterator
+
+ // since the context might be reused under the same key
+ // we zero the working buffers for this next new process
+ memset( ctx->y, 0x00, sizeof(ctx->y ) );
+ memset( ctx->buf, 0x00, sizeof(ctx->buf) );
+ ctx->len = 0;
+ ctx->add_len = 0;
+
+ ctx->mode = mode; // set the GCM encryption/decryption mode
+ ctx->aes_ctx.mode = ENCRYPT; // GCM *always* runs AES in ENCRYPTION mode
+
+ if( iv_len == 12 ) { // GCM natively uses a 12-byte, 96-bit IV
+ memcpy( ctx->y, iv, iv_len ); // copy the IV to the top of the 'y' buff
+ ctx->y[15] = 1; // start "counting" from 1 (not 0)
+ }
+ else // if we don't have a 12-byte IV, we GHASH whatever we've been given
+ {
+ memset( work_buf, 0x00, 16 ); // clear the working buffer
+ PUT_UINT32_BE( iv_len * 8, work_buf, 12 ); // place the IV into buffer
+
+ p = iv;
+ while( iv_len > 0 ) {
+ use_len = ( iv_len < 16 ) ? iv_len : 16;
+ for( i = 0; i < use_len; i++ ) ctx->y[i] ^= p[i];
+ gcm_mult( ctx, ctx->y, ctx->y );
+ iv_len -= use_len;
+ p += use_len;
+ }
+ for( i = 0; i < 16; i++ ) ctx->y[i] ^= work_buf[i];
+ gcm_mult( ctx, ctx->y, ctx->y );
+ }
+ if( ( ret = aes_cipher( &ctx->aes_ctx, ctx->y, ctx->base_ectr ) ) != 0 )
+ return( ret );
+
+ ctx->add_len = add_len;
+ p = add;
+ while( add_len > 0 ) {
+ use_len = ( add_len < 16 ) ? add_len : 16;
+ for( i = 0; i < use_len; i++ ) ctx->buf[i] ^= p[i];
+ gcm_mult( ctx, ctx->buf, ctx->buf );
+ add_len -= use_len;
+ p += use_len;
+ }
+ return( 0 );
+}
+
+/******************************************************************************
+ *
+ * GCM_UPDATE
+ *
+ * This is called once or more to process bulk plaintext or ciphertext data.
+ * We give this some number of bytes of input and it returns the same number
+ * of output bytes. If called multiple times (which is fine) all but the final
+ * invocation MUST be called with length mod 16 == 0. (Only the final call can
+ * have a partial block length of < 128 bits.)
+ *
+ ******************************************************************************/
+int gcm_update( gcm_context *ctx, // pointer to user-provided GCM context
+ size_t length, // length, in bytes, of data to process
+ const uchar *input, // pointer to source data
+ uchar *output ) // pointer to destination data
+{
+ int ret; // our error return if the AES encrypt fails
+ uchar ectr[16]; // counter-mode cipher output for XORing
+ size_t use_len; // byte count to process, up to 16 bytes
+ size_t i; // local loop iterator
+
+ ctx->len += length; // bump the GCM context's running length count
+
+ while( length > 0 ) {
+ // clamp the length to process at 16 bytes
+ use_len = ( length < 16 ) ? length : 16;
+
+ // increment the context's 128-bit IV||Counter 'y' vector
+ for( i = 16; i > 12; i-- ) if( ++ctx->y[i - 1] != 0 ) break;
+
+ // encrypt the context's 'y' vector under the established key
+ if( ( ret = aes_cipher( &ctx->aes_ctx, ctx->y, ectr ) ) != 0 )
+ return( ret );
+
+ // encrypt or decrypt the input to the output
+ if( ctx->mode == ENCRYPT )
+ {
+ for( i = 0; i < use_len; i++ ) {
+ // XOR the cipher's ouptut vector (ectr) with our input
+ output[i] = (uchar) ( ectr[i] ^ input[i] );
+ // now we mix in our data into the authentication hash.
+ // if we're ENcrypting we XOR in the post-XOR (output)
+ // results, but if we're DEcrypting we XOR in the input
+ // data
+ ctx->buf[i] ^= output[i];
+ }
+ }
+ else
+ {
+ for( i = 0; i < use_len; i++ ) {
+ // but if we're DEcrypting we XOR in the input data first,
+ // i.e. before saving to ouput data, otherwise if the input
+ // and output buffer are the same (inplace decryption) we
+ // would not get the correct auth tag
+
+ ctx->buf[i] ^= input[i];
+
+ // XOR the cipher's ouptut vector (ectr) with our input
+ output[i] = (uchar) ( ectr[i] ^ input[i] );
+ }
+ }
+ gcm_mult( ctx, ctx->buf, ctx->buf ); // perform a GHASH operation
+
+ length -= use_len; // drop the remaining byte count to process
+ input += use_len; // bump our input pointer forward
+ output += use_len; // bump our output pointer forward
+ }
+ return( 0 );
+}
+
+/******************************************************************************
+ *
+ * GCM_FINISH
+ *
+ * This is called once after all calls to GCM_UPDATE to finalize the GCM.
+ * It performs the final GHASH to produce the resulting authentication TAG.
+ *
+ ******************************************************************************/
+int gcm_finish( gcm_context *ctx, // pointer to user-provided GCM context
+ uchar *tag, // pointer to buffer which receives the tag
+ size_t tag_len ) // length, in bytes, of the tag-receiving buf
+{
+ uchar work_buf[16];
+ uint64_t orig_len = ctx->len * 8;
+ uint64_t orig_add_len = ctx->add_len * 8;
+ size_t i;
+
+ if( tag_len != 0 ) memcpy( tag, ctx->base_ectr, tag_len );
+
+ if( orig_len || orig_add_len ) {
+ memset( work_buf, 0x00, 16 );
+
+ PUT_UINT32_BE( ( orig_add_len >> 32 ), work_buf, 0 );
+ PUT_UINT32_BE( ( orig_add_len ), work_buf, 4 );
+ PUT_UINT32_BE( ( orig_len >> 32 ), work_buf, 8 );
+ PUT_UINT32_BE( ( orig_len ), work_buf, 12 );
+
+ for( i = 0; i < 16; i++ ) ctx->buf[i] ^= work_buf[i];
+ gcm_mult( ctx, ctx->buf, ctx->buf );
+ for( i = 0; i < tag_len; i++ ) tag[i] ^= ctx->buf[i];
+ }
+ return( 0 );
+}
+
+
+/******************************************************************************
+ *
+ * GCM_CRYPT_AND_TAG
+ *
+ * This either encrypts or decrypts the user-provided data and, either
+ * way, generates an authentication tag of the requested length. It must be
+ * called with a GCM context whose key has already been set with GCM_SETKEY.
+ *
+ * The user would typically call this explicitly to ENCRYPT a buffer of data
+ * and optional associated data, and produce its an authentication tag.
+ *
+ * To reverse the process the user would typically call the companion
+ * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided
+ * authentication tag. The GCM_AUTH_DECRYPT function calls this function
+ * to perform its decryption and tag generation, which it then compares.
+ *
+ ******************************************************************************/
+int gcm_crypt_and_tag(
+ gcm_context *ctx, // gcm context with key already setup
+ int mode, // cipher direction: GCM_ENCRYPT or GCM_DECRYPT
+ const uchar *iv, // pointer to the 12-byte initialization vector
+ size_t iv_len, // byte length if the IV. should always be 12
+ const uchar *add, // pointer to the non-ciphered additional data
+ size_t add_len, // byte length of the additional AEAD data
+ const uchar *input, // pointer to the cipher data source
+ uchar *output, // pointer to the cipher data destination
+ size_t length, // byte length of the cipher data
+ uchar *tag, // pointer to the tag to be generated
+ size_t tag_len ) // byte length of the tag to be generated
+{ /*
+ assuming that the caller has already invoked gcm_setkey to
+ prepare the gcm context with the keying material, we simply
+ invoke each of the three GCM sub-functions in turn...
+ */
+ gcm_start ( ctx, mode, iv, iv_len, add, add_len );
+ gcm_update ( ctx, length, input, output );
+ gcm_finish ( ctx, tag, tag_len );
+ return( 0 );
+}
+
+
+/******************************************************************************
+ *
+ * GCM_AUTH_DECRYPT
+ *
+ * This DECRYPTS a user-provided data buffer with optional associated data.
+ * It then verifies a user-supplied authentication tag against the tag just
+ * re-created during decryption to verify that the data has not been altered.
+ *
+ * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption
+ * and authentication tag generation.
+ *
+ ******************************************************************************/
+int gcm_auth_decrypt(
+ gcm_context *ctx, // gcm context with key already setup
+ const uchar *iv, // pointer to the 12-byte initialization vector
+ size_t iv_len, // byte length if the IV. should always be 12
+ const uchar *add, // pointer to the non-ciphered additional data
+ size_t add_len, // byte length of the additional AEAD data
+ const uchar *input, // pointer to the cipher data source
+ uchar *output, // pointer to the cipher data destination
+ size_t length, // byte length of the cipher data
+ const uchar *tag, // pointer to the tag to be authenticated
+ size_t tag_len ) // byte length of the tag <= 16
+{
+ uchar check_tag[16]; // the tag generated and returned by decryption
+ int diff; // an ORed flag to detect authentication errors
+ size_t i; // our local iterator
+ /*
+ we use GCM_DECRYPT_AND_TAG (above) to perform our decryption
+ (which is an identical XORing to reverse the previous one)
+ and also to re-generate the matching authentication tag
+ */
+ gcm_crypt_and_tag( ctx, DECRYPT, iv, iv_len, add, add_len,
+ input, output, length, check_tag, tag_len );
+
+ // now we verify the authentication tag in 'constant time'
+ for( diff = 0, i = 0; i < tag_len; i++ )
+ diff |= tag[i] ^ check_tag[i];
+
+ if( diff != 0 ) { // see whether any bits differed?
+ memset( output, 0, length ); // if so... wipe the output data
+ return( GCM_AUTH_FAILURE ); // return GCM_AUTH_FAILURE
+ }
+ return( 0 );
+}
+
+/******************************************************************************
+ *
+ * GCM_ZERO_CTX
+ *
+ * The GCM context contains both the GCM context and the AES context.
+ * This includes keying and key-related material which is security-
+ * sensitive, so it MUST be zeroed after use. This function does that.
+ *
+ ******************************************************************************/
+void gcm_zero_ctx( gcm_context *ctx )
+{
+ // zero the context originally provided to us
+ memset( ctx, 0, sizeof( gcm_context ) );
+}
diff --git a/applications/external/esubghz_chat/crypto/gcm.h b/applications/external/esubghz_chat/crypto/gcm.h
new file mode 100644
index 000000000..063154195
--- /dev/null
+++ b/applications/external/esubghz_chat/crypto/gcm.h
@@ -0,0 +1,187 @@
+/******************************************************************************
+*
+* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
+*
+* This is a simple and straightforward implementation of AES-GCM authenticated
+* encryption. The focus of this work was correctness & accuracy. It is written
+* in straight 'C' without any particular focus upon optimization or speed. It
+* should be endian (memory byte order) neutral since the few places that care
+* are handled explicitly.
+*
+* This implementation of AES-GCM was created by Steven M. Gibson of GRC.com.
+*
+* It is intended for general purpose use, but was written in support of GRC's
+* reference implementation of the SQRL (Secure Quick Reliable Login) client.
+*
+* See: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
+* http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ \
+* gcm/gcm-revised-spec.pdf
+*
+* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
+* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
+*
+*******************************************************************************/
+#ifndef GCM_HEADER
+#define GCM_HEADER
+
+#define GCM_AUTH_FAILURE 0x55555555 // authentication failure
+
+#include "aes.h" // gcm_context includes aes_context
+
+#if defined(_MSC_VER)
+ #include
+ typedef unsigned int size_t;// use the right type for length declarations
+ typedef UINT32 uint32_t;
+ typedef UINT64 uint64_t;
+#else
+ #include
+#endif
+
+
+/******************************************************************************
+ * GCM_CONTEXT : GCM context / holds keytables, instance data, and AES ctx
+ ******************************************************************************/
+typedef struct {
+ int mode; // cipher direction: encrypt/decrypt
+ uint64_t len; // cipher data length processed so far
+ uint64_t add_len; // total add data length
+ uint64_t HL[16]; // precalculated lo-half HTable
+ uint64_t HH[16]; // precalculated hi-half HTable
+ uchar base_ectr[16]; // first counter-mode cipher output for tag
+ uchar y[16]; // the current cipher-input IV|Counter value
+ uchar buf[16]; // buf working value
+ aes_context aes_ctx; // cipher context used
+} gcm_context;
+
+
+/******************************************************************************
+ * GCM_CONTEXT : MUST be called once before ANY use of this library
+ ******************************************************************************/
+int gcm_initialize( void );
+
+
+/******************************************************************************
+ * GCM_SETKEY : sets the GCM (and AES) keying material for use
+ ******************************************************************************/
+int gcm_setkey( gcm_context *ctx, // caller-provided context ptr
+ const uchar *key, // pointer to cipher key
+ const uint keysize // size in bytes (must be 16, 24, 32 for
+ // 128, 192 or 256-bit keys respectively)
+); // returns 0 for success
+
+
+/******************************************************************************
+ *
+ * GCM_CRYPT_AND_TAG
+ *
+ * This either encrypts or decrypts the user-provided data and, either
+ * way, generates an authentication tag of the requested length. It must be
+ * called with a GCM context whose key has already been set with GCM_SETKEY.
+ *
+ * The user would typically call this explicitly to ENCRYPT a buffer of data
+ * and optional associated data, and produce its an authentication tag.
+ *
+ * To reverse the process the user would typically call the companion
+ * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided
+ * authentication tag. The GCM_AUTH_DECRYPT function calls this function
+ * to perform its decryption and tag generation, which it then compares.
+ *
+ ******************************************************************************/
+int gcm_crypt_and_tag(
+ gcm_context *ctx, // gcm context with key already setup
+ int mode, // cipher direction: ENCRYPT (1) or DECRYPT (0)
+ const uchar *iv, // pointer to the 12-byte initialization vector
+ size_t iv_len, // byte length if the IV. should always be 12
+ const uchar *add, // pointer to the non-ciphered additional data
+ size_t add_len, // byte length of the additional AEAD data
+ const uchar *input, // pointer to the cipher data source
+ uchar *output, // pointer to the cipher data destination
+ size_t length, // byte length of the cipher data
+ uchar *tag, // pointer to the tag to be generated
+ size_t tag_len ); // byte length of the tag to be generated
+
+
+/******************************************************************************
+ *
+ * GCM_AUTH_DECRYPT
+ *
+ * This DECRYPTS a user-provided data buffer with optional associated data.
+ * It then verifies a user-supplied authentication tag against the tag just
+ * re-created during decryption to verify that the data has not been altered.
+ *
+ * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption
+ * and authentication tag generation.
+ *
+ ******************************************************************************/
+int gcm_auth_decrypt(
+ gcm_context *ctx, // gcm context with key already setup
+ const uchar *iv, // pointer to the 12-byte initialization vector
+ size_t iv_len, // byte length if the IV. should always be 12
+ const uchar *add, // pointer to the non-ciphered additional data
+ size_t add_len, // byte length of the additional AEAD data
+ const uchar *input, // pointer to the cipher data source
+ uchar *output, // pointer to the cipher data destination
+ size_t length, // byte length of the cipher data
+ const uchar *tag, // pointer to the tag to be authenticated
+ size_t tag_len ); // byte length of the tag <= 16
+
+
+/******************************************************************************
+ *
+ * GCM_START
+ *
+ * Given a user-provided GCM context, this initializes it, sets the encryption
+ * mode, and preprocesses the initialization vector and additional AEAD data.
+ *
+ ******************************************************************************/
+int gcm_start( gcm_context *ctx, // pointer to user-provided GCM context
+ int mode, // ENCRYPT (1) or DECRYPT (0)
+ const uchar *iv, // pointer to initialization vector
+ size_t iv_len, // IV length in bytes (should == 12)
+ const uchar *add, // pointer to additional AEAD data (NULL if none)
+ size_t add_len ); // length of additional AEAD data (bytes)
+
+
+/******************************************************************************
+ *
+ * GCM_UPDATE
+ *
+ * This is called once or more to process bulk plaintext or ciphertext data.
+ * We give this some number of bytes of input and it returns the same number
+ * of output bytes. If called multiple times (which is fine) all but the final
+ * invocation MUST be called with length mod 16 == 0. (Only the final call can
+ * have a partial block length of < 128 bits.)
+ *
+ ******************************************************************************/
+int gcm_update( gcm_context *ctx, // pointer to user-provided GCM context
+ size_t length, // length, in bytes, of data to process
+ const uchar *input, // pointer to source data
+ uchar *output ); // pointer to destination data
+
+
+/******************************************************************************
+ *
+ * GCM_FINISH
+ *
+ * This is called once after all calls to GCM_UPDATE to finalize the GCM.
+ * It performs the final GHASH to produce the resulting authentication TAG.
+ *
+ ******************************************************************************/
+int gcm_finish( gcm_context *ctx, // pointer to user-provided GCM context
+ uchar *tag, // ptr to tag buffer - NULL if tag_len = 0
+ size_t tag_len ); // length, in bytes, of the tag-receiving buf
+
+
+/******************************************************************************
+ *
+ * GCM_ZERO_CTX
+ *
+ * The GCM context contains both the GCM context and the AES context.
+ * This includes keying and key-related material which is security-
+ * sensitive, so it MUST be zeroed after use. This function does that.
+ *
+ ******************************************************************************/
+void gcm_zero_ctx( gcm_context *ctx );
+
+
+#endif /* GCM_HEADER */
diff --git a/applications/external/esubghz_chat/crypto_wrapper.c b/applications/external/esubghz_chat/crypto_wrapper.c
new file mode 100644
index 000000000..397bc13d1
--- /dev/null
+++ b/applications/external/esubghz_chat/crypto_wrapper.c
@@ -0,0 +1,191 @@
+#include
+#include
+#include
+
+#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+#include "crypto/gcm.h"
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+
+#include "crypto_wrapper.h"
+
+DICT_DEF2(ESubGhzChatReplayDict, uint64_t, uint32_t)
+
+struct ESugGhzChatCryptoCtx {
+ uint8_t key[KEY_BITS / 8];
+#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+ gcm_context gcm_ctx;
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+ ESubGhzChatReplayDict_t replay_dict;
+ uint64_t run_id;
+ uint32_t counter;
+};
+
+struct ESubGhzChatCryptoMsg {
+ uint64_t run_id;
+ uint32_t counter;
+ uint8_t iv[IV_BYTES];
+ uint8_t tag[TAG_BYTES];
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+void crypto_init(void)
+{
+#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+ /* init the GCM and AES tables */
+ gcm_initialize();
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+}
+
+void crypto_explicit_bzero(void *s, size_t len)
+{
+ memset(s, 0, len);
+ asm volatile("" ::: "memory");
+}
+
+ESubGhzChatCryptoCtx *crypto_ctx_alloc(void)
+{
+ ESubGhzChatCryptoCtx *ret = malloc(sizeof(ESubGhzChatCryptoCtx));
+
+ if (ret != NULL) {
+ memset(ret, 0, sizeof(ESubGhzChatCryptoCtx));
+ ESubGhzChatReplayDict_init(ret->replay_dict);
+ ret->run_id = 0;
+ ret->counter = 1;
+ }
+
+ return ret;
+}
+
+void crypto_ctx_free(ESubGhzChatCryptoCtx *ctx)
+{
+ crypto_ctx_clear(ctx);
+ ESubGhzChatReplayDict_clear(ctx->replay_dict);
+ free(ctx);
+}
+
+void crypto_ctx_clear(ESubGhzChatCryptoCtx *ctx)
+{
+ crypto_explicit_bzero(ctx->key, sizeof(ctx->key));
+#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+ crypto_explicit_bzero(&(ctx->gcm_ctx), sizeof(ctx->gcm_ctx));
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+ ESubGhzChatReplayDict_reset(ctx->replay_dict);
+ ctx->run_id = 0;
+ ctx->counter = 1;
+}
+
+static uint64_t crypto_calc_run_id(FuriString *flipper_name, uint32_t tick)
+{
+ const char *fn = furi_string_get_cstr(flipper_name);
+ size_t fn_len = strlen(fn);
+
+ uint8_t h_in[fn_len + sizeof(uint32_t)];
+ memcpy(h_in, fn, fn_len);
+ memcpy(h_in + fn_len, &tick, sizeof(uint32_t));
+
+ uint8_t h_out[256];
+ sha256(h_in, fn_len + sizeof(uint32_t), h_out);
+
+ uint64_t run_id;
+ memcpy(&run_id, h_out, sizeof(uint64_t));
+
+ return run_id;
+}
+
+bool crypto_ctx_set_key(ESubGhzChatCryptoCtx *ctx, const uint8_t *key,
+ FuriString *flipper_name, uint32_t tick)
+{
+ ctx->run_id = crypto_calc_run_id(flipper_name, tick);
+ ctx->counter = 1;
+
+ memcpy(ctx->key, key, KEY_BITS / 8);
+#ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+ return true;
+#else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+ return (gcm_setkey(&(ctx->gcm_ctx), key, KEY_BITS / 8) == 0);
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+}
+
+void crypto_ctx_get_key(ESubGhzChatCryptoCtx *ctx, uint8_t *key)
+{
+ memcpy(key, ctx->key, KEY_BITS / 8);
+}
+
+bool crypto_ctx_decrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,
+ uint8_t *out)
+{
+ if (in_len < MSG_OVERHEAD + 1) {
+ return false;
+ }
+
+ struct ESubGhzChatCryptoMsg *msg = (struct ESubGhzChatCryptoMsg *) in;
+
+ // check if message is stale, if yes, discard
+ uint32_t *counter = ESubGhzChatReplayDict_get(ctx->replay_dict,
+ msg->run_id);
+ if (counter != NULL) {
+ if (*counter >= __ntohl(msg->counter)) {
+ return false;
+ }
+ }
+
+ // decrypt and auth message
+#ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+ bool ret = (furi_hal_crypto_gcm_decrypt_and_verify(ctx->key,
+ msg->iv,
+ (uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES,
+ msg->data, out,
+ in_len - MSG_OVERHEAD,
+ msg->tag) == FuriHalCryptoGCMStateOk);
+#else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+ bool ret = (gcm_auth_decrypt(&(ctx->gcm_ctx),
+ msg->iv, IV_BYTES,
+ (uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES,
+ msg->data, out,
+ in_len - MSG_OVERHEAD,
+ msg->tag, TAG_BYTES) == 0);
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+
+ // if auth was successful update replay dict
+ if (ret) {
+ ESubGhzChatReplayDict_set_at(ctx->replay_dict, msg->run_id,
+ __ntohl(msg->counter));
+ }
+
+ return ret;
+}
+
+bool crypto_ctx_encrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,
+ uint8_t *out)
+{
+ struct ESubGhzChatCryptoMsg *msg = (struct ESubGhzChatCryptoMsg *) out;
+
+ // fill message header
+ msg->run_id = ctx->run_id;
+ msg->counter = __htonl(ctx->counter);
+ furi_hal_random_fill_buf(msg->iv, IV_BYTES);
+
+ // encrypt message and store tag in header
+#ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+ bool ret = (furi_hal_crypto_gcm_encrypt_and_tag(ctx->key,
+ msg->iv,
+ (uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES,
+ in, msg->data,
+ in_len,
+ msg->tag) == FuriHalCryptoGCMStateOk);
+#else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+ bool ret = (gcm_crypt_and_tag(&(ctx->gcm_ctx), ENCRYPT,
+ msg->iv, IV_BYTES,
+ (uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES,
+ in, msg->data,
+ in_len,
+ msg->tag, TAG_BYTES) == 0);
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+
+ // increase internal counter
+ if (ret) {
+ ctx->counter++;
+ }
+
+ return ret;
+}
diff --git a/applications/external/esubghz_chat/crypto_wrapper.h b/applications/external/esubghz_chat/crypto_wrapper.h
new file mode 100644
index 000000000..cfa992e75
--- /dev/null
+++ b/applications/external/esubghz_chat/crypto_wrapper.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define RUN_ID_BYTES (sizeof(uint64_t))
+#define COUNTER_BYTES (sizeof(uint32_t))
+#define KEY_BITS 256
+#define IV_BYTES 12
+#define TAG_BYTES 16
+
+#define MSG_OVERHEAD (RUN_ID_BYTES + COUNTER_BYTES + IV_BYTES + TAG_BYTES)
+
+typedef struct ESugGhzChatCryptoCtx ESubGhzChatCryptoCtx;
+
+void crypto_init(void);
+
+/* Function to clear sensitive memory. */
+void crypto_explicit_bzero(void *s, size_t len);
+
+ESubGhzChatCryptoCtx *crypto_ctx_alloc(void);
+void crypto_ctx_free(ESubGhzChatCryptoCtx *ctx);
+
+void crypto_ctx_clear(ESubGhzChatCryptoCtx *ctx);
+
+bool crypto_ctx_set_key(ESubGhzChatCryptoCtx *ctx, const uint8_t *key,
+ FuriString *flipper_name, uint32_t tick);
+void crypto_ctx_get_key(ESubGhzChatCryptoCtx *ctx, uint8_t *key);
+
+bool crypto_ctx_decrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,
+ uint8_t *out);
+bool crypto_ctx_encrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,
+ uint8_t *out);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/applications/external/esubghz_chat/esubghz_chat.c b/applications/external/esubghz_chat/esubghz_chat.c
new file mode 100644
index 000000000..0c0bd5c1f
--- /dev/null
+++ b/applications/external/esubghz_chat/esubghz_chat.c
@@ -0,0 +1,737 @@
+#include
+#include
+#include
+#include
+
+#include "esubghz_chat_i.h"
+
+#define CHAT_LEAVE_DELAY 10
+#define TICK_INTERVAL 50
+#define MESSAGE_COMPLETION_TIMEOUT 500
+#define TIMEOUT_BETWEEN_MESSAGES 500
+
+#define KBD_UNLOCK_CNT 3
+#define KBD_UNLOCK_TIMEOUT 1000
+
+/* Callback for RX events from the Sub-GHz worker. Records the current ticks as
+ * the time of the last reception. */
+static void have_read_cb(void* context)
+{
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ state->last_time_rx_data = furi_get_tick();
+}
+
+/* Sets the header for the chat input field depending on whether or not a
+ * message preview exists. */
+void set_chat_input_header(ESubGhzChatState *state)
+{
+ if (strlen(state->msg_preview) == 0) {
+ text_input_set_header_text(state->text_input, "Message");
+ } else {
+ text_input_set_header_text(state->text_input,
+ state->msg_preview);
+ }
+}
+
+/* Appends the latest message to the chat box and prepares the message preview.
+ */
+void append_msg(ESubGhzChatState *state, const char *msg)
+{
+ /* append message to text box */
+ furi_string_cat_printf(state->chat_box_store, "\n%s", msg);
+
+ /* prepare message preview */
+ strncpy(state->msg_preview, msg, MSG_PREVIEW_SIZE);
+ state->msg_preview[MSG_PREVIEW_SIZE] = 0;
+ set_chat_input_header(state);
+
+ /* reset text box contents and focus */
+ text_box_set_text(state->chat_box,
+ furi_string_get_cstr(state->chat_box_store));
+ text_box_set_focus(state->chat_box, TextBoxFocusEnd);
+}
+
+/* Decrypts a message for post_rx(). */
+static bool post_rx_decrypt(ESubGhzChatState *state, size_t rx_size)
+{
+ bool ret = crypto_ctx_decrypt(state->crypto_ctx,
+ state->rx_buffer, rx_size,
+ (uint8_t*) state->rx_str_buffer);
+
+ if (ret) {
+ state->rx_str_buffer[rx_size - (MSG_OVERHEAD)] = 0;
+ } else {
+ state->rx_str_buffer[0] = 0;
+ }
+
+ return ret;
+}
+
+/* Post RX handler, decrypts received messages and calls append_msg(). */
+static void post_rx(ESubGhzChatState *state, size_t rx_size)
+{
+ furi_assert(state);
+
+ if (rx_size == 0) {
+ return;
+ }
+
+ furi_check(rx_size <= RX_TX_BUFFER_SIZE);
+
+ /* decrypt if necessary */
+ if (!state->encrypted) {
+ memcpy(state->rx_str_buffer, state->rx_buffer, rx_size);
+ state->rx_str_buffer[rx_size] = 0;
+
+ /* remove trailing newline if it is there, for compat with CLI
+ * Sub-GHz chat */
+ if (state->rx_str_buffer[rx_size - 1] == '\n') {
+ state->rx_str_buffer[rx_size - 1] = 0;
+ }
+ } else {
+ /* if decryption fails output an error message */
+ if (!post_rx_decrypt(state, rx_size)) {
+ strcpy(state->rx_str_buffer, "ERR: Decryption failed!");
+ }
+ }
+
+ /* append message to text box and prepare message preview */
+ append_msg(state, state->rx_str_buffer);
+
+ /* send notification (make the flipper vibrate) */
+ notification_message(state->notification, &sequence_single_vibro);
+}
+
+/* Reads the message from msg_input, encrypts it if necessary and then
+ * transmits it. */
+void tx_msg_input(ESubGhzChatState *state)
+{
+ /* encrypt message if necessary */
+ size_t msg_len = strlen(furi_string_get_cstr(state->msg_input));
+ size_t tx_size = msg_len;
+ if (state->encrypted) {
+ tx_size += MSG_OVERHEAD;
+ furi_check(tx_size <= sizeof(state->tx_buffer));
+
+ crypto_ctx_encrypt(state->crypto_ctx,
+ (uint8_t *)
+ furi_string_get_cstr(state->msg_input),
+ msg_len,
+ state->tx_buffer);
+ } else {
+ tx_size += 2;
+ furi_check(tx_size <= sizeof(state->tx_buffer));
+ memcpy(state->tx_buffer,
+ furi_string_get_cstr(state->msg_input),
+ msg_len);
+
+ /* append \r\n for compat with Sub-GHz CLI chat */
+ state->tx_buffer[msg_len] = '\r';
+ state->tx_buffer[msg_len + 1] = '\n';
+ }
+
+ /* transmit */
+ subghz_tx_rx_worker_write(state->subghz_worker, state->tx_buffer,
+ tx_size);
+}
+
+/* Displays whether or not encryption has been enabled in the text box. Also
+ * clears the text input buffer to remove the password and starts the Sub-GHz
+ * worker. After starting the worker a join message is transmitted. */
+void enter_chat(ESubGhzChatState *state)
+{
+ furi_string_cat_printf(state->chat_box_store, "\nEncrypted: %s",
+ (state->encrypted ? "yes" : "no"));
+
+ subghz_tx_rx_worker_start(state->subghz_worker, state->subghz_device,
+ state->frequency);
+
+ /* concatenate the name prefix and join message */
+ furi_string_set(state->msg_input, state->name_prefix);
+ furi_string_cat_str(state->msg_input, " joined chat.");
+
+ /* encrypt and transmit message */
+ tx_msg_input(state);
+
+ /* clear message input buffer */
+ furi_string_set_char(state->msg_input, 0, 0);
+}
+
+/* Sends a leave message */
+void exit_chat(ESubGhzChatState *state)
+{
+ /* concatenate the name prefix and leave message */
+ furi_string_set(state->msg_input, state->name_prefix);
+ furi_string_cat_str(state->msg_input, " left chat.");
+
+ /* encrypt and transmit message */
+ tx_msg_input(state);
+
+ /* clear message input buffer */
+ furi_string_set_char(state->msg_input, 0, 0);
+
+ /* wait for leave message to be delivered */
+ furi_delay_ms(CHAT_LEAVE_DELAY);
+}
+
+/* Whether or not to display the locked message. */
+static bool kbd_lock_msg_display(ESubGhzChatState *state)
+{
+ return (state->kbd_lock_msg_ticks != 0);
+}
+
+/* Whether or not to hide the locked message again. */
+static bool kbd_lock_msg_reset_timeout(ESubGhzChatState *state)
+{
+ if (state->kbd_lock_msg_ticks == 0) {
+ return false;
+ }
+
+ if (furi_get_tick() - state->kbd_lock_msg_ticks > KBD_UNLOCK_TIMEOUT) {
+ return true;
+ }
+
+ return false;
+}
+
+/* Resets the timeout for the locked message and turns off the backlight if
+ * specified. */
+static void kbd_lock_msg_reset(ESubGhzChatState *state, bool backlight_off)
+{
+ state->kbd_lock_msg_ticks = 0;
+ state->kbd_lock_count = 0;
+
+ if (backlight_off) {
+ notification_message(state->notification,
+ &sequence_display_backlight_off);
+ }
+}
+
+/* Locks the keyboard. */
+static void kbd_lock(ESubGhzChatState *state)
+{
+ state->kbd_locked = true;
+ kbd_lock_msg_reset(state, true);
+}
+
+/* Unlocks the keyboard. */
+static void kbd_unlock(ESubGhzChatState *state)
+{
+ state->kbd_locked = false;
+ kbd_lock_msg_reset(state, false);
+}
+
+/* Custom event callback for view dispatcher. Just calls scene manager. */
+static bool esubghz_chat_custom_event_callback(void* context, uint32_t event)
+{
+ FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_custom_event_callback");
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+ return scene_manager_handle_custom_event(state->scene_manager, event);
+}
+
+/* Navigation event callback for view dispatcher. Just calls scene manager. */
+static bool esubghz_chat_navigation_event_callback(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_navigation_event_callback");
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+ return scene_manager_handle_back_event(state->scene_manager);
+}
+
+/* Tick event callback for view dispatcher. Called every TICK_INTERVAL. Resets
+ * the locked message if necessary. Retrieves a received message from the
+ * Sub-GHz worker and calls post_rx(). Then calls the scene manager. */
+static void esubghz_chat_tick_event_callback(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_tick_event_callback");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ /* reset locked message if necessary */
+ if (kbd_lock_msg_reset_timeout(state)) {
+ kbd_lock_msg_reset(state, true);
+ }
+
+ /* if the maximum message size was reached or the
+ * MESSAGE_COMPLETION_TIMEOUT has expired, retrieve a message and call
+ * post_rx() */
+ size_t avail = 0;
+ while ((avail = subghz_tx_rx_worker_available(state->subghz_worker)) >
+ 0) {
+ volatile uint32_t since_last_rx = furi_get_tick() -
+ state->last_time_rx_data;
+ if (avail < RX_TX_BUFFER_SIZE && since_last_rx <
+ MESSAGE_COMPLETION_TIMEOUT) {
+ break;
+ }
+
+ size_t rx_size = subghz_tx_rx_worker_read(state->subghz_worker,
+ state->rx_buffer, RX_TX_BUFFER_SIZE);
+ post_rx(state, rx_size);
+ }
+
+ /* call scene manager */
+ scene_manager_handle_tick_event(state->scene_manager);
+}
+
+/* Hooks into the view port's draw callback to overlay the keyboard locked
+ * message. */
+static void esubghz_hooked_draw_callback(Canvas* canvas, void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_draw_callback");
+
+ furi_assert(canvas);
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ /* call original callback */
+ state->orig_draw_cb(canvas, state->view_dispatcher);
+
+ /* display if the keyboard is locked */
+ if (state->kbd_locked) {
+ canvas_set_font(canvas, FontPrimary);
+ elements_multiline_text_framed(canvas, 42, 30, "Locked");
+ }
+
+ /* display the unlock message if necessary */
+ if (kbd_lock_msg_display(state)) {
+ canvas_set_font(canvas, FontSecondary);
+ elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
+ elements_multiline_text(canvas, 65, 26, "To unlock\npress:");
+ canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8);
+ canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8);
+ canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8);
+ canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
+ }
+}
+
+/* Hooks into the view port's input callback to handle the user locking the
+ * keyboard. */
+static void esubghz_hooked_input_callback(InputEvent* event, void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_input_callback");
+
+ furi_assert(event);
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ /* if the keyboard is locked no key presses are forwarded */
+ if (state->kbd_locked) {
+ /* key has been pressed, display the unlock message and
+ * initiate the timer */
+ if (state->kbd_lock_count == 0) {
+ state->kbd_lock_msg_ticks = furi_get_tick();
+ }
+
+ /* back button has been pressed, increase the lock counter */
+ if (event->key == InputKeyBack && event->type ==
+ InputTypeShort) {
+ state->kbd_lock_count++;
+ }
+
+ /* unlock the keyboard */
+ if (state->kbd_lock_count >= KBD_UNLOCK_CNT) {
+ kbd_unlock(state);
+ }
+
+ /* do not handle the event */
+ return;
+ }
+
+ if (event->key == InputKeyOk) {
+ /* if we are in the chat view and no input is ongoing, allow
+ * locking */
+ if (state->view_dispatcher->current_view ==
+ text_box_get_view(state->chat_box) &&
+ !(state->kbd_ok_input_ongoing)) {
+ /* lock keyboard upon long press of Ok button */
+ if (event->type == InputTypeLong) {
+ kbd_lock(state);
+ }
+
+ /* do not handle any Ok key events to prevent blocking
+ * of other keys */
+ return;
+ }
+
+ /* handle ongoing inputs when changing to chat view */
+ if (event->type == InputTypePress) {
+ state->kbd_ok_input_ongoing = true;
+ } else if (event->type == InputTypeRelease) {
+ state->kbd_ok_input_ongoing = false;
+ }
+ }
+
+ if (event->key == InputKeyLeft) {
+ /* if we are in the chat view and no input is ongoing, allow
+ * switching to msg input */
+ if (state->view_dispatcher->current_view ==
+ text_box_get_view(state->chat_box) &&
+ !(state->kbd_left_input_ongoing)) {
+ /* go to msg input upon short press of Left button */
+ if (event->type == InputTypeShort) {
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_GotoMsgInput);
+ }
+
+ /* do not handle any Left key events to prevent
+ * blocking of other keys */
+ return;
+ }
+
+ /* handle ongoing inputs when changing to chat view */
+ if (event->type == InputTypePress) {
+ state->kbd_left_input_ongoing = true;
+ } else if (event->type == InputTypeRelease) {
+ state->kbd_left_input_ongoing = false;
+ }
+ }
+
+ if (event->key == InputKeyRight) {
+ /* if we are in the chat view and no input is ongoing, allow
+ * switching to key display */
+ if (state->view_dispatcher->current_view ==
+ text_box_get_view(state->chat_box) &&
+ !(state->kbd_right_input_ongoing)) {
+ /* go to key display upon short press of Right button
+ */
+ if (event->type == InputTypeShort) {
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_GotoKeyDisplay);
+ }
+
+ /* do not handle any Right key events to prevent
+ * blocking of other keys */
+ return;
+ }
+
+ /* handle ongoing inputs when changing to chat view */
+ if (event->type == InputTypePress) {
+ state->kbd_right_input_ongoing = true;
+ } else if (event->type == InputTypeRelease) {
+ state->kbd_right_input_ongoing = false;
+ }
+ }
+
+ /* call original callback */
+ state->orig_input_cb(event, state->view_dispatcher);
+}
+
+static bool helper_strings_alloc(ESubGhzChatState *state)
+{
+ furi_assert(state);
+
+ state->name_prefix = furi_string_alloc();
+ if (state->name_prefix == NULL) {
+ return false;
+ }
+
+ state->msg_input = furi_string_alloc();
+ if (state->msg_input == NULL) {
+ furi_string_free(state->name_prefix);
+ return false;
+ }
+
+ return true;
+}
+
+static void helper_strings_free(ESubGhzChatState *state)
+{
+ furi_assert(state);
+
+ furi_string_free(state->name_prefix);
+ furi_string_free(state->msg_input);
+}
+
+static bool chat_box_alloc(ESubGhzChatState *state)
+{
+ furi_assert(state);
+
+ state->chat_box = text_box_alloc();
+ if (state->chat_box == NULL) {
+ return false;
+ }
+
+ state->chat_box_store = furi_string_alloc();
+ if (state->chat_box_store == NULL) {
+ text_box_free(state->chat_box);
+ return false;
+ }
+
+ furi_string_reserve(state->chat_box_store, CHAT_BOX_STORE_SIZE);
+ furi_string_set_char(state->chat_box_store, 0, 0);
+ text_box_set_text(state->chat_box,
+ furi_string_get_cstr(state->chat_box_store));
+ text_box_set_focus(state->chat_box, TextBoxFocusEnd);
+
+ return true;
+}
+
+static void chat_box_free(ESubGhzChatState *state)
+{
+ furi_assert(state);
+
+ text_box_free(state->chat_box);
+ furi_string_free(state->chat_box_store);
+}
+
+int32_t esubghz_chat(void)
+{
+ /* init the crypto system */
+ crypto_init();
+
+ int32_t err = -1;
+
+ FURI_LOG_I(APPLICATION_NAME, "Starting...");
+
+ /* allocate necessary structs and buffers */
+
+ ESubGhzChatState *state = malloc(sizeof(ESubGhzChatState));
+ if (state == NULL) {
+ goto err_alloc;
+ }
+ memset(state, 0, sizeof(*state));
+
+ state->scene_manager = scene_manager_alloc(
+ &esubghz_chat_scene_event_handlers, state);
+ if (state->scene_manager == NULL) {
+ goto err_alloc_sm;
+ }
+
+ state->view_dispatcher = view_dispatcher_alloc();
+ if (state->view_dispatcher == NULL) {
+ goto err_alloc_vd;
+ }
+
+ if (!helper_strings_alloc(state)) {
+ goto err_alloc_hs;
+ }
+
+ state->menu = menu_alloc();
+ if (state->menu == NULL) {
+ goto err_alloc_menu;
+ }
+
+ state->text_input = text_input_alloc();
+ if (state->text_input == NULL) {
+ goto err_alloc_ti;
+ }
+
+ state->hex_key_input = byte_input_alloc();
+ if (state->hex_key_input == NULL) {
+ goto err_alloc_hki;
+ }
+
+ if (!chat_box_alloc(state)) {
+ goto err_alloc_cb;
+ }
+
+ state->key_display = dialog_ex_alloc();
+ if (state->key_display == NULL) {
+ goto err_alloc_kd;
+ }
+
+ state->nfc_popup = popup_alloc();
+ if (state->nfc_popup == NULL) {
+ goto err_alloc_np;
+ }
+
+ state->subghz_worker = subghz_tx_rx_worker_alloc();
+ if (state->subghz_worker == NULL) {
+ goto err_alloc_worker;
+ }
+
+ state->nfc_worker = nfc_worker_alloc();
+ if (state->nfc_worker == NULL) {
+ goto err_alloc_nworker;
+ }
+
+ state->nfc_dev_data = malloc(sizeof(NfcDeviceData));
+ if (state->nfc_dev_data == NULL) {
+ goto err_alloc_ndevdata;
+ }
+ memset(state->nfc_dev_data, 0, sizeof(NfcDeviceData));
+
+ state->crypto_ctx = crypto_ctx_alloc();
+ if (state->crypto_ctx == NULL) {
+ goto err_alloc_crypto;
+ }
+
+ /* set the have_read callback of the Sub-GHz worker */
+ subghz_tx_rx_worker_set_callback_have_read(state->subghz_worker,
+ have_read_cb, state);
+
+ /* enter suppress charge mode */
+ furi_hal_power_suppress_charge_enter();
+
+ /* init internal device */
+ subghz_devices_init();
+ state->subghz_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
+
+ /* set chat name prefix */
+ furi_string_printf(state->name_prefix, "%s",
+ furi_hal_version_get_name_ptr());
+
+ /* get notification record, we use this to make the flipper vibrate */
+ /* no error handling here, don't know how */
+ state->notification = furi_record_open(RECORD_NOTIFICATION);
+
+ /* hook into the view port's draw and input callbacks */
+ state->orig_draw_cb = state->view_dispatcher->view_port->draw_callback;
+ state->orig_input_cb = state->view_dispatcher->view_port->input_callback;
+ view_port_draw_callback_set(state->view_dispatcher->view_port,
+ esubghz_hooked_draw_callback, state);
+ view_port_input_callback_set(state->view_dispatcher->view_port,
+ esubghz_hooked_input_callback, state);
+
+ view_dispatcher_enable_queue(state->view_dispatcher);
+
+ /* set callbacks for view dispatcher */
+ view_dispatcher_set_event_callback_context(state->view_dispatcher, state);
+ view_dispatcher_set_custom_event_callback(
+ state->view_dispatcher,
+ esubghz_chat_custom_event_callback);
+ view_dispatcher_set_navigation_event_callback(
+ state->view_dispatcher,
+ esubghz_chat_navigation_event_callback);
+ view_dispatcher_set_tick_event_callback(
+ state->view_dispatcher,
+ esubghz_chat_tick_event_callback,
+ TICK_INTERVAL);
+
+ /* add our two views to the view dispatcher */
+ view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Menu,
+ menu_get_view(state->menu));
+ view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Input,
+ text_input_get_view(state->text_input));
+ view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_HexKeyInput,
+ byte_input_get_view(state->hex_key_input));
+ view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox,
+ text_box_get_view(state->chat_box));
+ view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_KeyDisplay,
+ dialog_ex_get_view(state->key_display));
+ view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_NfcPopup,
+ popup_get_view(state->nfc_popup));
+
+ /* get the GUI record and attach the view dispatcher to the GUI */
+ /* no error handling here, don't know how */
+ Gui *gui = furi_record_open(RECORD_GUI);
+ view_dispatcher_attach_to_gui(state->view_dispatcher, gui,
+ ViewDispatcherTypeFullscreen);
+
+ /* switch to the frequency input scene */
+ scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_FreqInput);
+
+ /* run the view dispatcher, this call only returns when we close the
+ * application */
+ view_dispatcher_run(state->view_dispatcher);
+
+ /* if it is running, stop the Sub-GHz worker */
+ if (subghz_tx_rx_worker_is_running(state->subghz_worker)) {
+ exit_chat(state);
+ subghz_tx_rx_worker_stop(state->subghz_worker);
+ }
+
+ /* if it is running, stop the NFC worker */
+ nfc_worker_stop(state->nfc_worker);
+
+ err = 0;
+
+ /* close GUI record */
+ furi_record_close(RECORD_GUI);
+
+ /* remove our two views from the view dispatcher */
+ view_dispatcher_remove_view(state->view_dispatcher,
+ ESubGhzChatView_Menu);
+ view_dispatcher_remove_view(state->view_dispatcher,
+ ESubGhzChatView_Input);
+ view_dispatcher_remove_view(state->view_dispatcher,
+ ESubGhzChatView_HexKeyInput);
+ view_dispatcher_remove_view(state->view_dispatcher,
+ ESubGhzChatView_ChatBox);
+ view_dispatcher_remove_view(state->view_dispatcher,
+ ESubGhzChatView_KeyDisplay);
+ view_dispatcher_remove_view(state->view_dispatcher,
+ ESubGhzChatView_NfcPopup);
+
+ /* close notification record */
+ furi_record_close(RECORD_NOTIFICATION);
+
+ /* clear the key and potential password */
+ crypto_explicit_bzero(state->text_input_store,
+ sizeof(state->text_input_store));
+ crypto_explicit_bzero(state->hex_key_input_store,
+ sizeof(state->hex_key_input_store));
+ crypto_explicit_bzero(state->key_hex_str, sizeof(state->key_hex_str));
+ crypto_ctx_clear(state->crypto_ctx);
+
+ /* clear nfc data */
+ if (state->nfc_dev_data->parsed_data != NULL) {
+ furi_string_free(state->nfc_dev_data->parsed_data);
+ }
+ crypto_explicit_bzero(state->nfc_dev_data, sizeof(NfcDeviceData));
+
+ /* deinit devices */
+ subghz_devices_deinit();
+
+ /* exit suppress charge mode */
+ furi_hal_power_suppress_charge_exit();
+
+ /* free everything we allocated */
+
+ crypto_ctx_free(state->crypto_ctx);
+
+err_alloc_crypto:
+ free(state->nfc_dev_data);
+
+err_alloc_ndevdata:
+ nfc_worker_free(state->nfc_worker);
+
+err_alloc_nworker:
+ subghz_tx_rx_worker_free(state->subghz_worker);
+
+err_alloc_worker:
+ popup_free(state->nfc_popup);
+
+err_alloc_np:
+ dialog_ex_free(state->key_display);
+
+err_alloc_kd:
+ chat_box_free(state);
+
+err_alloc_cb:
+ byte_input_free(state->hex_key_input);
+
+err_alloc_hki:
+ text_input_free(state->text_input);
+
+err_alloc_ti:
+ menu_free(state->menu);
+
+err_alloc_menu:
+ helper_strings_free(state);
+
+err_alloc_hs:
+ view_dispatcher_free(state->view_dispatcher);
+
+err_alloc_vd:
+ scene_manager_free(state->scene_manager);
+
+err_alloc_sm:
+ free(state);
+
+err_alloc:
+ if (err != 0) {
+ FURI_LOG_E(APPLICATION_NAME, "Failed to launch (alloc error)!");
+ } else {
+ FURI_LOG_I(APPLICATION_NAME, "Clean exit.");
+ }
+
+ return err;
+}
diff --git a/applications/external/esubghz_chat/esubghz_chat_i.h b/applications/external/esubghz_chat/esubghz_chat_i.h
new file mode 100644
index 000000000..b29e6292d
--- /dev/null
+++ b/applications/external/esubghz_chat/esubghz_chat_i.h
@@ -0,0 +1,124 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "crypto_wrapper.h"
+#include "scenes/esubghz_chat_scene.h"
+
+#include
+#include "esubghz_chat_icons.h"
+
+#define APPLICATION_NAME "ESubGhzChat"
+
+#define DEFAULT_FREQ 433920000
+
+#define KEY_READ_POPUP_MS 3000
+
+#define RX_TX_BUFFER_SIZE 1024
+
+#define CHAT_BOX_STORE_SIZE 4096
+#define TEXT_INPUT_STORE_SIZE 256
+#define MSG_PREVIEW_SIZE 32
+
+#define KEY_HEX_STR_SIZE ((KEY_BITS / 8) * 3)
+
+typedef struct {
+ SceneManager* scene_manager;
+ ViewDispatcher* view_dispatcher;
+ NotificationApp* notification;
+
+ // UI elements
+ Menu* menu;
+ TextBox* chat_box;
+ FuriString* chat_box_store;
+ TextInput* text_input;
+ char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
+ ByteInput* hex_key_input;
+ uint8_t hex_key_input_store[KEY_BITS / 8];
+ DialogEx* key_display;
+ char key_hex_str[KEY_HEX_STR_SIZE + 1];
+ Popup* nfc_popup;
+
+ // for Sub-GHz
+ uint32_t frequency;
+ SubGhzTxRxWorker* subghz_worker;
+ const SubGhzDevice* subghz_device;
+
+ // for NFC
+ NfcWorker* nfc_worker;
+ NfcDeviceData* nfc_dev_data;
+
+ // message assembly before TX
+ FuriString* name_prefix;
+ FuriString* msg_input;
+
+ // message preview
+ char msg_preview[MSG_PREVIEW_SIZE + 1];
+
+ // encryption
+ bool encrypted;
+ ESubGhzChatCryptoCtx* crypto_ctx;
+
+ // RX and TX buffers
+ uint8_t rx_buffer[RX_TX_BUFFER_SIZE];
+ uint8_t tx_buffer[RX_TX_BUFFER_SIZE];
+ char rx_str_buffer[RX_TX_BUFFER_SIZE + 1];
+ volatile uint32_t last_time_rx_data;
+
+ // for locking
+ ViewPortDrawCallback orig_draw_cb;
+ ViewPortInputCallback orig_input_cb;
+ bool kbd_locked;
+ uint32_t kbd_lock_msg_ticks;
+ uint8_t kbd_lock_count;
+
+ // for ongoing inputs
+ bool kbd_ok_input_ongoing;
+ bool kbd_left_input_ongoing;
+ bool kbd_right_input_ongoing;
+} ESubGhzChatState;
+
+typedef enum {
+ ESubGhzChatEvent_FreqEntered,
+ ESubGhzChatEvent_KeyMenuNoEncryption,
+ ESubGhzChatEvent_KeyMenuPassword,
+ ESubGhzChatEvent_KeyMenuHexKey,
+ ESubGhzChatEvent_KeyMenuGenKey,
+ ESubGhzChatEvent_KeyMenuReadKeyFromNfc,
+ ESubGhzChatEvent_KeyReadPopupFailed,
+ ESubGhzChatEvent_KeyReadPopupSucceeded,
+ ESubGhzChatEvent_PassEntered,
+ ESubGhzChatEvent_HexKeyEntered,
+ ESubGhzChatEvent_MsgEntered,
+ ESubGhzChatEvent_GotoMsgInput,
+ ESubGhzChatEvent_GotoKeyDisplay,
+ ESubGhzChatEvent_KeyDisplayBack,
+ ESubGhzChatEvent_KeyDisplayShare,
+} ESubGhzChatEvent;
+
+typedef enum {
+ ESubGhzChatView_Menu,
+ ESubGhzChatView_Input,
+ ESubGhzChatView_HexKeyInput,
+ ESubGhzChatView_ChatBox,
+ ESubGhzChatView_KeyDisplay,
+ ESubGhzChatView_NfcPopup,
+} ESubGhzChatView;
+
+void set_chat_input_header(ESubGhzChatState* state);
+void append_msg(ESubGhzChatState* state, const char* msg);
+void tx_msg_input(ESubGhzChatState* state);
+void enter_chat(ESubGhzChatState* state);
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_chat_box.c b/applications/external/esubghz_chat/scenes/esubghz_chat_chat_box.c
new file mode 100644
index 000000000..49c30ae87
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_chat_box.c
@@ -0,0 +1,65 @@
+#include "../esubghz_chat_i.h"
+
+/* Prepares the text box scene. */
+void scene_on_enter_chat_box(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ text_box_reset(state->chat_box);
+ text_box_set_text(state->chat_box,
+ furi_string_get_cstr(state->chat_box_store));
+ text_box_set_focus(state->chat_box, TextBoxFocusEnd);
+
+ view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
+}
+
+/* Handles scene manager events for the text box scene. */
+bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_box");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ bool consumed = false;
+
+ switch(event.type) {
+ case SceneManagerEventTypeCustom:
+ switch(event.event) {
+ /* switch to message input scene */
+ case ESubGhzChatEvent_GotoMsgInput:
+ if (!scene_manager_previous_scene(
+ state->scene_manager)) {
+ view_dispatcher_stop(state->view_dispatcher);
+ }
+ consumed = true;
+ break;
+ case ESubGhzChatEvent_GotoKeyDisplay:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_KeyDisplay);
+ consumed = true;
+ break;
+ }
+ break;
+
+ default:
+ consumed = false;
+ break;
+ }
+
+ return consumed;
+}
+
+/* Cleans up the text box scene. */
+void scene_on_exit_chat_box(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ text_box_reset(state->chat_box);
+}
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_chat_input.c b/applications/external/esubghz_chat/scenes/esubghz_chat_chat_input.c
new file mode 100644
index 000000000..3f18dd8d7
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_chat_input.c
@@ -0,0 +1,118 @@
+#include "../esubghz_chat_i.h"
+
+/* If no message was entred this simply emits a MsgEntered event to the scene
+ * manager to switch to the text box. If a message was entered it is appended
+ * to the name string. The result is encrypted, if encryption is enabled, and
+ * then copied into the TX buffer. The contents of the TX buffer are then
+ * transmitted. The sent message is appended to the text box and a MsgEntered
+ * event is sent to the scene manager to switch to the text box view. */
+static bool chat_input_validator(const char *text, FuriString *error,
+ void *context)
+{
+ UNUSED(error);
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ /* no message, just switch to the text box view */
+ if (strlen(text) == 0) {
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_MsgEntered);
+ return true;
+ }
+
+ /* concatenate the name prefix and the actual message */
+ furi_string_set(state->msg_input, state->name_prefix);
+ furi_string_cat_str(state->msg_input, ": ");
+ furi_string_cat_str(state->msg_input, text);
+
+ /* append the message to the chat box and prepare message preview */
+ append_msg(state, furi_string_get_cstr(state->msg_input));
+
+ /* encrypt and transmit message */
+ tx_msg_input(state);
+
+ /* clear message input buffer */
+ furi_string_set_char(state->msg_input, 0, 0);
+
+ /* switch to text box view */
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_MsgEntered);
+
+ return true;
+}
+
+/* Prepares the message input scene. */
+void scene_on_enter_chat_input(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ state->text_input_store[0] = 0;
+ text_input_reset(state->text_input);
+ /* use validator for scene change to get around minimum length
+ * requirement */
+ text_input_set_result_callback(
+ state->text_input,
+ NULL,
+ NULL,
+ state->text_input_store,
+ sizeof(state->text_input_store),
+ true);
+ text_input_set_validator(
+ state->text_input,
+ chat_input_validator,
+ state);
+ set_chat_input_header(state);
+
+ view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
+}
+
+/* Handles scene manager events for the message input scene. */
+bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ bool consumed = false;
+
+ switch(event.type) {
+ case SceneManagerEventTypeCustom:
+ switch(event.event) {
+ /* switch to text box scene */
+ case ESubGhzChatEvent_MsgEntered:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_ChatBox);
+ consumed = true;
+ break;
+ }
+ break;
+
+ case SceneManagerEventTypeBack:
+ /* stop the application if the user presses back here */
+ view_dispatcher_stop(state->view_dispatcher);
+ consumed = true;
+ break;
+
+ default:
+ consumed = false;
+ break;
+ }
+
+ return consumed;
+}
+
+/* Cleans up the password input scene. */
+void scene_on_exit_chat_input(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ text_input_reset(state->text_input);
+}
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_freq_input.c b/applications/external/esubghz_chat/scenes/esubghz_chat_freq_input.c
new file mode 100644
index 000000000..85f954fb0
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_freq_input.c
@@ -0,0 +1,127 @@
+#include "../esubghz_chat_i.h"
+
+/* Sends FreqEntered event to scene manager and displays the frequency in the
+ * text box. */
+static void freq_input_cb(void *context)
+{
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ furi_string_cat_printf(state->chat_box_store, "Frequency: %lu",
+ state->frequency);
+
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_FreqEntered);
+}
+
+/* Validates the entered frequency. */
+static bool freq_input_validator(const char *text, FuriString *error,
+ void *context)
+{
+ furi_assert(text);
+ furi_assert(error);
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ int ret = sscanf(text, "%lu", &(state->frequency));
+ if (ret != 1) {
+ furi_string_printf(error, "Please enter\nfrequency\nin Hz!");
+ return false;
+ }
+
+ if (!subghz_devices_is_frequency_valid(state->subghz_device,
+ state->frequency)) {
+ furi_string_printf(error, "Frequency\n%lu\n is invalid!",
+ state->frequency);
+ return false;
+ }
+
+#ifdef FW_ORIGIN_Official
+ if (!furi_hal_region_is_frequency_allowed(state->frequency)) {
+#else /* FW_ORIGIN_Official */
+ if (!furi_hal_subghz_is_tx_allowed(state->frequency)) {
+#endif /* FW_ORIGIN_Official */
+ furi_string_printf(error, "TX forbidden\non frequency\n%lu!",
+ state->frequency);
+ return false;
+ }
+
+ return true;
+}
+
+/* Prepares the frequency input scene. */
+void scene_on_enter_freq_input(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ snprintf(state->text_input_store, TEXT_INPUT_STORE_SIZE, "%lu",
+ (uint32_t) DEFAULT_FREQ);
+ text_input_reset(state->text_input);
+ text_input_set_result_callback(
+ state->text_input,
+ freq_input_cb,
+ state,
+ state->text_input_store,
+ sizeof(state->text_input_store),
+ true);
+ text_input_set_validator(
+ state->text_input,
+ freq_input_validator,
+ state);
+ text_input_set_header_text(
+ state->text_input,
+ "Frequency");
+
+ view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
+}
+
+/* Handles scene manager events for the frequency input scene. */
+bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ bool consumed = false;
+
+ switch(event.type) {
+ case SceneManagerEventTypeCustom:
+ switch(event.event) {
+ /* switch to password input scene */
+ case ESubGhzChatEvent_FreqEntered:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_KeyMenu);
+ consumed = true;
+ break;
+ }
+ break;
+
+ case SceneManagerEventTypeBack:
+ /* stop the application if the user presses back here */
+ view_dispatcher_stop(state->view_dispatcher);
+ consumed = true;
+ break;
+
+ default:
+ consumed = false;
+ break;
+ }
+
+ return consumed;
+}
+
+/* Cleans up the frequency input scene. */
+void scene_on_exit_freq_input(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ text_input_reset(state->text_input);
+}
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_hex_key_input.c b/applications/external/esubghz_chat/scenes/esubghz_chat_hex_key_input.c
new file mode 100644
index 000000000..560f5478c
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_hex_key_input.c
@@ -0,0 +1,91 @@
+#include "../esubghz_chat_i.h"
+
+/* Sets the entered bytes as the key, enters the chat and sends a HexKeyEntered
+ * event to the scene manager. */
+static void hex_key_input_cb(void* context)
+{
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ /* initiate the crypto context */
+ bool ret = crypto_ctx_set_key(state->crypto_ctx,
+ state->hex_key_input_store, state->name_prefix,
+ furi_get_tick());
+
+ /* cleanup */
+ crypto_explicit_bzero(state->hex_key_input_store,
+ sizeof(state->hex_key_input_store));
+
+ if (!ret) {
+ crypto_ctx_clear(state->crypto_ctx);
+ return;
+ }
+
+ state->encrypted = true;
+
+ enter_chat(state);
+
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_HexKeyEntered);
+}
+
+/* Prepares the hex key input scene. */
+void scene_on_enter_hex_key_input(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_hex_key_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ byte_input_set_result_callback(state->hex_key_input,
+ hex_key_input_cb,
+ NULL,
+ state,
+ state->hex_key_input_store,
+ sizeof(state->hex_key_input_store));
+
+ view_dispatcher_switch_to_view(state->view_dispatcher,
+ ESubGhzChatView_HexKeyInput);
+}
+
+/* Handles scene manager events for the hex key input scene. */
+bool scene_on_event_hex_key_input(void* context, SceneManagerEvent event)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_event_hex_key_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ bool consumed = false;
+
+ switch(event.type) {
+ case SceneManagerEventTypeCustom:
+ switch(event.event) {
+ /* switch to message input scene */
+ case ESubGhzChatEvent_HexKeyEntered:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_ChatInput);
+ consumed = true;
+ break;
+ }
+ break;
+
+ default:
+ consumed = false;
+ break;
+ }
+
+ return consumed;
+}
+
+/* Cleans up the hex key input scene. */
+void scene_on_exit_hex_key_input(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_hex_key_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ crypto_explicit_bzero(state->hex_key_input_store,
+ sizeof(state->hex_key_input_store));
+}
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_key_display.c b/applications/external/esubghz_chat/scenes/esubghz_chat_key_display.c
new file mode 100644
index 000000000..fbc9e37e2
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_key_display.c
@@ -0,0 +1,128 @@
+#include "../esubghz_chat_i.h"
+
+void key_display_result_cb(DialogExResult result, void* context)
+{
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ switch(result) {
+ case DialogExResultLeft:
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_KeyDisplayBack);
+ break;
+
+ case DialogExResultCenter:
+ if (state->encrypted) {
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_KeyDisplayShare);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Prepares the key display scene. */
+void scene_on_enter_key_display(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_key_display");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ if (state->encrypted) {
+ uint8_t key[KEY_BITS / 8];
+ crypto_ctx_get_key(state->crypto_ctx, key);
+ snprintf(state->key_hex_str, KEY_HEX_STR_SIZE,
+ "%02hX%02hX%02hX%02hX"
+ "%02hX%02hX%02hX%02hX\n"
+ "%02hX%02hX%02hX%02hX"
+ "%02hX%02hX%02hX%02hX\n"
+ "%02hX%02hX%02hX%02hX"
+ "%02hX%02hX%02hX%02hX\n"
+ "%02hX%02hX%02hX%02hX"
+ "%02hX%02hX%02hX%02hX",
+ key[0], key[1], key[2], key[3],
+ key[4], key[5], key[6], key[7],
+ key[8], key[9], key[10], key[11],
+ key[12], key[13], key[14], key[15],
+ key[16], key[17], key[18], key[19],
+ key[20], key[21], key[22], key[23],
+ key[24], key[25], key[26], key[27],
+ key[28], key[29], key[30], key[31]);
+ crypto_explicit_bzero(key, sizeof(key));
+ } else {
+ strcpy(state->key_hex_str, "No Key");
+ }
+
+ dialog_ex_reset(state->key_display);
+
+ dialog_ex_set_text(state->key_display, state->key_hex_str, 64, 2,
+ AlignCenter, AlignTop);
+
+ dialog_ex_set_icon(state->key_display, 0, 0, NULL);
+
+ dialog_ex_set_left_button_text(state->key_display, "Back");
+
+ if (state->encrypted) {
+ dialog_ex_set_center_button_text(state->key_display, "Share");
+ }
+
+ dialog_ex_set_result_callback(state->key_display,
+ key_display_result_cb);
+ dialog_ex_set_context(state->key_display, state);
+
+ view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_KeyDisplay);
+}
+
+/* Handles scene manager events for the key display scene. */
+bool scene_on_event_key_display(void* context, SceneManagerEvent event)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_event_key_display");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ bool consumed = false;
+
+ switch(event.type) {
+ case SceneManagerEventTypeCustom:
+ switch(event.event) {
+ /* switch to message input scene */
+ case ESubGhzChatEvent_KeyDisplayBack:
+ if (!scene_manager_previous_scene(
+ state->scene_manager)) {
+ view_dispatcher_stop(state->view_dispatcher);
+ }
+ consumed = true;
+ break;
+
+ /* open key sharing popup */
+ case ESubGhzChatEvent_KeyDisplayShare:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_KeySharePopup);
+ consumed = true;
+ break;
+ }
+ break;
+
+ default:
+ consumed = false;
+ break;
+ }
+
+ return consumed;
+}
+
+/* Cleans up the key display scene. */
+void scene_on_exit_key_display(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_key_display");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ dialog_ex_reset(state->key_display);
+ crypto_explicit_bzero(state->key_hex_str, sizeof(state->key_hex_str));
+}
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_key_menu.c b/applications/external/esubghz_chat/scenes/esubghz_chat_key_menu.c
new file mode 100644
index 000000000..3665003e1
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_key_menu.c
@@ -0,0 +1,194 @@
+#include "../esubghz_chat_i.h"
+
+typedef enum {
+ ESubGhzChatKeyMenuItems_NoEncryption,
+ ESubGhzChatKeyMenuItems_Password,
+ ESubGhzChatKeyMenuItems_HexKey,
+ ESubGhzChatKeyMenuItems_GenKey,
+ ESubGhzChatKeyMenuItems_ReadKeyFromNfc,
+} ESubGhzChatKeyMenuItems;
+
+static void key_menu_cb(void* context, uint32_t index)
+{
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ uint8_t key[KEY_BITS / 8];
+
+ switch(index) {
+ case ESubGhzChatKeyMenuItems_NoEncryption:
+ state->encrypted = false;
+ enter_chat(state);
+
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_KeyMenuNoEncryption);
+ break;
+
+ case ESubGhzChatKeyMenuItems_Password:
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_KeyMenuPassword);
+ break;
+
+ case ESubGhzChatKeyMenuItems_HexKey:
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_KeyMenuHexKey);
+ break;
+
+ case ESubGhzChatKeyMenuItems_GenKey:
+ /* generate a random key */
+ furi_hal_random_fill_buf(key, KEY_BITS / 8);
+
+ /* initiate the crypto context */
+ bool ret = crypto_ctx_set_key(state->crypto_ctx, key,
+ state->name_prefix, furi_get_tick());
+
+ /* cleanup */
+ crypto_explicit_bzero(key, sizeof(key));
+
+ if (!ret) {
+ crypto_ctx_clear(state->crypto_ctx);
+ return;
+ }
+
+ /* set encrypted flag and enter the chat */
+ state->encrypted = true;
+ enter_chat(state);
+
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_KeyMenuGenKey);
+ break;
+
+ case ESubGhzChatKeyMenuItems_ReadKeyFromNfc:
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_KeyMenuReadKeyFromNfc);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Prepares the key menu scene. */
+void scene_on_enter_key_menu(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_key_menu");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ menu_reset(state->menu);
+
+ menu_add_item(
+ state->menu,
+ "No encryption",
+ NULL,
+ ESubGhzChatKeyMenuItems_NoEncryption,
+ key_menu_cb,
+ state
+ );
+ menu_add_item(
+ state->menu,
+ "Password",
+ NULL,
+ ESubGhzChatKeyMenuItems_Password,
+ key_menu_cb,
+ state
+ );
+ menu_add_item(
+ state->menu,
+ "Hex Key",
+ NULL,
+ ESubGhzChatKeyMenuItems_HexKey,
+ key_menu_cb,
+ state
+ );
+ menu_add_item(
+ state->menu,
+ "Generate Key",
+ NULL,
+ ESubGhzChatKeyMenuItems_GenKey,
+ key_menu_cb,
+ state
+ );
+ menu_add_item(
+ state->menu,
+ "Read Key from NFC",
+ NULL,
+ ESubGhzChatKeyMenuItems_ReadKeyFromNfc,
+ key_menu_cb,
+ state
+ );
+
+ view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Menu);
+}
+
+/* Handles scene manager events for the key menu scene. */
+bool scene_on_event_key_menu(void* context, SceneManagerEvent event)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_event_key_menu");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ bool consumed = false;
+
+ switch(event.type) {
+ case SceneManagerEventTypeCustom:
+ switch(event.event) {
+ /* switch to message input scene */
+ case ESubGhzChatEvent_KeyMenuNoEncryption:
+ case ESubGhzChatEvent_KeyMenuGenKey:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_ChatInput);
+ consumed = true;
+ break;
+
+ /* switch to password input scene */
+ case ESubGhzChatEvent_KeyMenuPassword:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_PassInput);
+ consumed = true;
+ break;
+
+ /* switch to hex key input scene */
+ case ESubGhzChatEvent_KeyMenuHexKey:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_HexKeyInput);
+ consumed = true;
+ break;
+
+ /* switch to hex key read scene */
+ case ESubGhzChatEvent_KeyMenuReadKeyFromNfc:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_KeyReadPopup);
+ consumed = true;
+ break;
+ }
+
+ break;
+
+ case SceneManagerEventTypeBack:
+ /* stop the application if the user presses back here */
+ view_dispatcher_stop(state->view_dispatcher);
+ consumed = true;
+ break;
+
+ default:
+ consumed = false;
+ break;
+ }
+
+ return consumed;
+}
+
+/* Cleans up the key menu scene. */
+void scene_on_exit_key_menu(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_key_menu");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ menu_reset(state->menu);
+}
+
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_key_read_popup.c b/applications/external/esubghz_chat/scenes/esubghz_chat_key_read_popup.c
new file mode 100644
index 000000000..d06b14632
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_key_read_popup.c
@@ -0,0 +1,251 @@
+#include "../esubghz_chat_i.h"
+
+typedef enum {
+ KeyReadPopupState_Idle,
+ KeyReadPopupState_Detecting,
+ KeyReadPopupState_Reading,
+ KeyReadPopupState_Fail,
+ KeyReadPopupState_Success,
+} KeyReadPopupState;
+
+static bool read_worker_cb(NfcWorkerEvent event, void* context)
+{
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ view_dispatcher_send_custom_event(state->view_dispatcher, event);
+
+ return true;
+}
+
+static void key_read_popup_timeout_cb(void* context)
+{
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ uint32_t cur_state = scene_manager_get_scene_state(
+ state->scene_manager, ESubGhzChatScene_KeyReadPopup);
+
+ /* done displaying our failure */
+ if (cur_state == KeyReadPopupState_Fail) {
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_KeyReadPopupFailed);
+ /* done displaying our success, enter chat */
+ } else if (cur_state == KeyReadPopupState_Success) {
+ enter_chat(state);
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_KeyReadPopupSucceeded);
+ }
+}
+
+static bool key_read_popup_handle_key_read(ESubGhzChatState *state)
+{
+ NfcDeviceData *dev_data = state->nfc_dev_data;
+
+ if (dev_data->mf_ul_data.data_read < KEY_BITS / 8) {
+ return false;
+ }
+
+ /* initiate the crypto context */
+ bool ret = crypto_ctx_set_key(state->crypto_ctx,
+ dev_data->mf_ul_data.data, state->name_prefix,
+ furi_get_tick());
+
+ /* cleanup */
+ crypto_explicit_bzero(dev_data->mf_ul_data.data, KEY_BITS / 8);
+
+ if (!ret) {
+ crypto_ctx_clear(state->crypto_ctx);
+ return false;
+ }
+
+ /* set encrypted flag */
+ state->encrypted = true;
+
+ return true;
+}
+
+static void key_read_popup_set_state(ESubGhzChatState *state, KeyReadPopupState
+ new_state)
+{
+ uint32_t cur_state = scene_manager_get_scene_state(
+ state->scene_manager, ESubGhzChatScene_KeyReadPopup);
+ if (cur_state == new_state) {
+ return;
+ }
+
+ if (new_state == KeyReadPopupState_Detecting) {
+ popup_reset(state->nfc_popup);
+ popup_disable_timeout(state->nfc_popup);
+ popup_set_text(state->nfc_popup, "Tap Flipper\n to sender", 97,
+ 24, AlignCenter, AlignTop);
+ popup_set_icon(state->nfc_popup, 0, 8, &I_NFC_manual_60x50);
+ notification_message(state->notification,
+ &sequence_blink_start_cyan);
+ } else if (new_state == KeyReadPopupState_Reading) {
+ popup_reset(state->nfc_popup);
+ popup_disable_timeout(state->nfc_popup);
+ popup_set_header(state->nfc_popup, "Reading key\nDon't "
+ "move...", 85, 24, AlignCenter, AlignTop);
+ popup_set_icon(state->nfc_popup, 12, 23, &I_Loading_24);
+ notification_message(state->notification,
+ &sequence_blink_start_yellow);
+ } else if (new_state == KeyReadPopupState_Fail) {
+ nfc_worker_stop(state->nfc_worker);
+
+ popup_reset(state->nfc_popup);
+ popup_set_header(state->nfc_popup, "Failure!", 64, 2,
+ AlignCenter, AlignTop);
+ popup_set_text(state->nfc_popup, "Failed\nto read\nkey.", 78,
+ 16, AlignLeft, AlignTop);
+ popup_set_icon(state->nfc_popup, 21, 13, &I_Cry_dolph_55x52);
+
+ popup_set_timeout(state->nfc_popup, KEY_READ_POPUP_MS);
+ popup_set_context(state->nfc_popup, state);
+ popup_set_callback(state->nfc_popup,
+ key_read_popup_timeout_cb);
+ popup_enable_timeout(state->nfc_popup);
+
+ notification_message(state->notification,
+ &sequence_blink_stop);
+ } else if (new_state == KeyReadPopupState_Success) {
+ nfc_worker_stop(state->nfc_worker);
+
+ popup_reset(state->nfc_popup);
+ popup_set_header(state->nfc_popup, "Key\nread!", 13, 22,
+ AlignLeft, AlignBottom);
+ popup_set_icon(state->nfc_popup, 32, 5, &I_DolphinNice_96x59);
+
+ popup_set_timeout(state->nfc_popup, KEY_READ_POPUP_MS);
+ popup_set_context(state->nfc_popup, state);
+ popup_set_callback(state->nfc_popup,
+ key_read_popup_timeout_cb);
+ popup_enable_timeout(state->nfc_popup);
+
+ notification_message(state->notification, &sequence_success);
+ notification_message(state->notification,
+ &sequence_blink_stop);
+ }
+
+ scene_manager_set_scene_state(state->scene_manager,
+ ESubGhzChatScene_KeyReadPopup, new_state);
+
+ view_dispatcher_switch_to_view(state->view_dispatcher,
+ ESubGhzChatView_NfcPopup);
+}
+
+/* Prepares the key share read scene. */
+void scene_on_enter_key_read_popup(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_key_read_popup");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ key_read_popup_set_state(state, KeyReadPopupState_Detecting);
+
+ state->nfc_dev_data->parsed_data = furi_string_alloc();
+ if (state->nfc_dev_data->parsed_data == NULL) {
+ /* can't do anything here, crash */
+ furi_check(0);
+ }
+
+ nfc_worker_start(state->nfc_worker, NfcWorkerStateRead,
+ state->nfc_dev_data, read_worker_cb, state);
+}
+
+/* Handles scene manager events for the key read popup scene. */
+bool scene_on_event_key_read_popup(void* context, SceneManagerEvent event)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_event_key_read_popup");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ bool consumed = false;
+
+ switch(event.type) {
+ case SceneManagerEventTypeCustom:
+ switch(event.event) {
+ /* card detected */
+ case NfcWorkerEventCardDetected:
+ key_read_popup_set_state(state,
+ KeyReadPopupState_Reading);
+ consumed = true;
+ break;
+
+ /* no card detected */
+ case NfcWorkerEventNoCardDetected:
+ key_read_popup_set_state(state,
+ KeyReadPopupState_Detecting);
+ consumed = true;
+ break;
+
+ /* key probably read */
+ case NfcWorkerEventReadMfUltralight:
+ if (key_read_popup_handle_key_read(state)) {
+ key_read_popup_set_state(state,
+ KeyReadPopupState_Success);
+ } else {
+ key_read_popup_set_state(state,
+ KeyReadPopupState_Fail);
+ }
+ consumed = true;
+ break;
+
+ /* close the popup and go back */
+ case ESubGhzChatEvent_KeyReadPopupFailed:
+ if (!scene_manager_previous_scene(
+ state->scene_manager)) {
+ view_dispatcher_stop(state->view_dispatcher);
+ }
+ consumed = true;
+ break;
+
+ /* success, go to chat input */
+ case ESubGhzChatEvent_KeyReadPopupSucceeded:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_ChatInput);
+ consumed = true;
+ break;
+
+ /* something else happend, treat as failure */
+ default:
+ key_read_popup_set_state(state,
+ KeyReadPopupState_Fail);
+ consumed = true;
+ break;
+ }
+
+ break;
+
+ default:
+ consumed = false;
+ break;
+ }
+
+ return consumed;
+}
+
+/* Cleans up the key read popup scene. */
+void scene_on_exit_key_read_popup(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_key_read_popup");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ popup_reset(state->nfc_popup);
+ scene_manager_set_scene_state(state->scene_manager,
+ ESubGhzChatScene_KeyReadPopup, KeyReadPopupState_Idle);
+
+ notification_message(state->notification, &sequence_blink_stop);
+
+ nfc_worker_stop(state->nfc_worker);
+
+ crypto_explicit_bzero(state->nfc_dev_data->mf_ul_data.data, KEY_BITS / 8);
+ if (state->nfc_dev_data->parsed_data != NULL) {
+ furi_string_free(state->nfc_dev_data->parsed_data);
+ }
+ memset(state->nfc_dev_data, 0, sizeof(NfcDeviceData));
+}
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_key_share_popup.c b/applications/external/esubghz_chat/scenes/esubghz_chat_key_share_popup.c
new file mode 100644
index 000000000..d5c40dced
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_key_share_popup.c
@@ -0,0 +1,88 @@
+#include "../esubghz_chat_i.h"
+
+static void prepare_nfc_dev_data(ESubGhzChatState *state)
+{
+ NfcDeviceData *dev_data = state->nfc_dev_data;
+
+ dev_data->protocol = NfcDeviceProtocolMifareUl;
+ furi_hal_random_fill_buf(dev_data->nfc_data.uid, 7);
+ dev_data->nfc_data.uid_len = 7;
+ dev_data->nfc_data.atqa[0] = 0x44;
+ dev_data->nfc_data.atqa[1] = 0x00;
+ dev_data->nfc_data.sak = 0x00;
+
+ dev_data->mf_ul_data.type = MfUltralightTypeNTAG215;
+ dev_data->mf_ul_data.version.header = 0x00;
+ dev_data->mf_ul_data.version.vendor_id = 0x04;
+ dev_data->mf_ul_data.version.prod_type = 0x04;
+ dev_data->mf_ul_data.version.prod_subtype = 0x02;
+ dev_data->mf_ul_data.version.prod_ver_major = 0x01;
+ dev_data->mf_ul_data.version.prod_ver_minor = 0x00;
+ dev_data->mf_ul_data.version.storage_size = 0x11;
+ dev_data->mf_ul_data.version.protocol_type = 0x03;
+
+ /* Add 16 to the size for config pages */
+ dev_data->mf_ul_data.data_size = (KEY_BITS / 8) + 16;
+ crypto_ctx_get_key(state->crypto_ctx, dev_data->mf_ul_data.data);
+}
+
+/* Prepares the key share popup scene. */
+void scene_on_enter_key_share_popup(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_key_share_popup");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ popup_reset(state->nfc_popup);
+
+ popup_disable_timeout(state->nfc_popup);
+
+ popup_set_header(state->nfc_popup, "Sharing...", 67, 13, AlignLeft,
+ AlignTop);
+ popup_set_icon(state->nfc_popup, 0, 3, &I_NFC_dolphin_emulation_47x61);
+ popup_set_text(state->nfc_popup, "Sharing\nKey via\nNFC", 90, 28,
+ AlignCenter, AlignTop);
+
+ prepare_nfc_dev_data(state);
+ nfc_worker_start(state->nfc_worker, NfcWorkerStateMfUltralightEmulate,
+ state->nfc_dev_data, NULL, NULL);
+
+ notification_message(state->notification,
+ &sequence_blink_start_magenta);
+
+ view_dispatcher_switch_to_view(state->view_dispatcher,
+ ESubGhzChatView_NfcPopup);
+}
+
+/* Handles scene manager events for the key share popup scene. */
+bool scene_on_event_key_share_popup(void* context, SceneManagerEvent event)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_event_key_share_popup");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ UNUSED(state);
+ UNUSED(event);
+
+ return false;
+}
+
+/* Cleans up the key share popup scene. */
+void scene_on_exit_key_share_popup(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_key_share_popup");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ popup_reset(state->nfc_popup);
+
+ notification_message(state->notification, &sequence_blink_stop);
+
+ nfc_worker_stop(state->nfc_worker);
+
+ crypto_explicit_bzero(state->nfc_dev_data->mf_ul_data.data, KEY_BITS / 8);
+ memset(state->nfc_dev_data, 0, sizeof(NfcDeviceData));
+}
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_pass_input.c b/applications/external/esubghz_chat/scenes/esubghz_chat_pass_input.c
new file mode 100644
index 000000000..6dcf15c74
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_pass_input.c
@@ -0,0 +1,127 @@
+#include "../esubghz_chat_i.h"
+
+/* Sends PassEntered event to scene manager and enters the chat. */
+static void pass_input_cb(void *context)
+{
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ crypto_explicit_bzero(state->text_input_store,
+ sizeof(state->text_input_store));
+
+ enter_chat(state);
+
+ view_dispatcher_send_custom_event(state->view_dispatcher,
+ ESubGhzChatEvent_PassEntered);
+}
+
+/* If a password was entered this derives a key from the password using a
+ * single pass of SHA256 and initiates the AES-GCM context for encryption. If
+ * the initiation fails, the password is rejected. */
+static bool pass_input_validator(const char *text, FuriString *error,
+ void *context)
+{
+ furi_assert(text);
+ furi_assert(error);
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ if (strlen(text) == 0) {
+ furi_string_printf(error, "Enter a\npassword!");
+ return false;
+ }
+
+ unsigned char key[KEY_BITS / 8];
+
+ /* derive a key from the password */
+ sha256((unsigned char *) text, strlen(text), key);
+
+ /* initiate the crypto context */
+ bool ret = crypto_ctx_set_key(state->crypto_ctx, key,
+ state->name_prefix, furi_get_tick());
+
+ /* cleanup */
+ crypto_explicit_bzero(key, sizeof(key));
+
+ if (!ret) {
+ crypto_ctx_clear(state->crypto_ctx);
+ furi_string_printf(error, "Failed to\nset key!");
+ return false;
+ }
+
+ state->encrypted = true;
+
+ return true;
+}
+
+/* Prepares the password input scene. */
+void scene_on_enter_pass_input(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ state->text_input_store[0] = 0;
+ text_input_reset(state->text_input);
+ text_input_set_result_callback(
+ state->text_input,
+ pass_input_cb,
+ state,
+ state->text_input_store,
+ sizeof(state->text_input_store),
+ true);
+ text_input_set_validator(
+ state->text_input,
+ pass_input_validator,
+ state);
+ text_input_set_header_text(
+ state->text_input,
+ "Password");
+
+ view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
+}
+
+/* Handles scene manager events for the password input scene. */
+bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ bool consumed = false;
+
+ switch(event.type) {
+ case SceneManagerEventTypeCustom:
+ switch(event.event) {
+ /* switch to message input scene */
+ case ESubGhzChatEvent_PassEntered:
+ scene_manager_next_scene(state->scene_manager,
+ ESubGhzChatScene_ChatInput);
+ consumed = true;
+ break;
+ }
+ break;
+
+ default:
+ consumed = false;
+ break;
+ }
+
+ return consumed;
+}
+
+/* Cleans up the password input scene. */
+void scene_on_exit_pass_input(void* context)
+{
+ FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
+
+ furi_assert(context);
+ ESubGhzChatState* state = context;
+
+ text_input_reset(state->text_input);
+ crypto_explicit_bzero(state->text_input_store,
+ sizeof(state->text_input_store));
+}
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_scene.c b/applications/external/esubghz_chat/scenes/esubghz_chat_scene.c
new file mode 100644
index 000000000..5efb8ea10
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_scene.c
@@ -0,0 +1,30 @@
+#include "esubghz_chat_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) scene_on_enter_##name,
+void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
+#include "esubghz_chat_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) scene_on_event_##name,
+bool (*const esubghz_chat_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "esubghz_chat_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) scene_on_exit_##name,
+void (*const esubghz_chat_scene_on_exit_handlers[])(void* context) = {
+#include "esubghz_chat_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
+ .on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
+ .on_event_handlers = esubghz_chat_scene_on_event_handlers,
+ .on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
+ .scene_num = ESubGhzChatScene_MAX,
+};
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_scene.h b/applications/external/esubghz_chat/scenes/esubghz_chat_scene.h
new file mode 100644
index 000000000..45663e6dd
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_scene.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) ESubGhzChatScene_##id,
+typedef enum {
+#include "esubghz_chat_scene_config.h"
+ ESubGhzChatScene_MAX
+} ESubGhzChatScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers esubghz_chat_scene_event_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void scene_on_enter_##name(void*);
+#include "esubghz_chat_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+ bool scene_on_event_##name(void* context, SceneManagerEvent event);
+#include "esubghz_chat_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void scene_on_exit_##name(void* context);
+#include "esubghz_chat_scene_config.h"
+#undef ADD_SCENE
diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_scene_config.h b/applications/external/esubghz_chat/scenes/esubghz_chat_scene_config.h
new file mode 100644
index 000000000..85981c898
--- /dev/null
+++ b/applications/external/esubghz_chat/scenes/esubghz_chat_scene_config.h
@@ -0,0 +1,9 @@
+ADD_SCENE(esubghz_chat, freq_input, FreqInput)
+ADD_SCENE(esubghz_chat, key_menu, KeyMenu)
+ADD_SCENE(esubghz_chat, pass_input, PassInput)
+ADD_SCENE(esubghz_chat, hex_key_input, HexKeyInput)
+ADD_SCENE(esubghz_chat, key_read_popup, KeyReadPopup)
+ADD_SCENE(esubghz_chat, chat_input, ChatInput)
+ADD_SCENE(esubghz_chat, chat_box, ChatBox)
+ADD_SCENE(esubghz_chat, key_display, KeyDisplay)
+ADD_SCENE(esubghz_chat, key_share_popup, KeySharePopup)