diff --git a/.github/demo.webp b/.github/demo.webp
new file mode 100644
index 0000000..5662c7f
Binary files /dev/null and b/.github/demo.webp differ
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
new file mode 100644
index 0000000..313d533
--- /dev/null
+++ b/.github/workflows/linter.yml
@@ -0,0 +1,25 @@
+name: Lint code
+
+on: [ push, pull_request ]
+
+jobs:
+ python-lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: chartboost/ruff-action@v1
+ with:
+ src: "./webapp"
+ - uses: psf/black@stable
+ with:
+ src: "./webapp"
+
+ javascript-lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v1
+ with:
+ node-version: '*'
+ - run: npm install standard
+ - run: npx standard ./webapp/static/assets
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5dc95d2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+input_pcaps/*
+suricata/output/filestore
+suricata/output/tcpstore
+suricata/output/udpstore
+suricata/output/*.json
+suricata/output/*.log
+webapp/database/*
+.env
+
+# Python
+__pycache__
+.ruff_cache
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/COPYING
@@ -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/README.md b/README.md
new file mode 100644
index 0000000..0275db6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,100 @@
+# Shovel
+
+Shovel is a web application that offers a graphical user interface to explore
+[Suricata EVE outputs](https://docs.suricata.io/en/suricata-7.0.1/output/eve/eve-json-output.html).
+Its primary focus is to help [Capture-the-Flag players](https://ctftime.org/ctf-wtf)
+analyse network traffic dumps during stressful and time-limited attack-defense games such as
+[FAUSTCTF](https://faustctf.net/) or [ECSC](https://ecsc.eu/).
+Shovel is developed in the context of
+[ECSC Team France](https://ctftime.org/team/159269/) training.
+
+![Shovel during ENOWARS7](./.github/demo.webp)
+
+You might also want to have a look at these other awesome traffic analyser tools:
+
+ - https://github.com/secgroup/flower (first commit in 2018)
+ - https://github.com/eciavatta/caronte (first commit in 2020)
+ - https://github.com/OpenAttackDefenseTools/tulip (fork from flower in May 2022)
+
+Compared to these traffic analyser tools, Shovel relies on Suricata while making
+some opinionated choices for the frontend. This has a few nice implications:
+
+ - dissection of all application protocols already supported by Suricata (TCP and UDP),
+ - use a single SQLite database,
+ - on disk TCP/UDP/HTTP payload deduplication,
+ - filters based on libmagic, e.g. quickly filter flows containing PDF documents or PNG images,
+ - no heavy build tools needed, Shovel is easy to tweak.
+
+Moreover, Shovel is batteries-included with Grafana visualizations and some Suricata alert rules.
+
+## Setup
+
+### 0. Before the Capture-the-Flag event begins
+
+Copy `example.env` to `.env` and tweak the configuration parameters.
+Also add the flag format in `suricata/custom.rules` if needed.
+
+If you are playing a CTF using an IPv6 network, you might want to [enable IPv6 support in Docker deamon](https://docs.docker.com/config/daemon/ipv6/) before the CTF starts.
+
+### 1. Network capture setup
+
+You should place network captures in `input_pcaps/` folder.
+Capture files should be splitted into chunks to be progressively imported.
+If the CTF event does not already provide PCAP files, then you can adapt one
+of the following commands for a GNU/Linux system:
+```bash
+ssh root@10.20.9.6 tcpdump -i wg-faustctf -n -w - 'tcp port not 22' | tcpdump -n -r - -G 30 -w input_pcaps/trace-%Y-%m-%d_%H-%M-%S.pcap
+```
+For a Microsoft Windows system, you may adapt the following command (3389 is RDP):
+```powershell
+.\tshark.exe -b duration:60 -w \\share\captures\trace -f "tcp port not 3389"
+```
+
+### 2. Launch Suricata and webapp via Docker (option A)
+
+Start Suricata, the web application and Grafana using `docker compose up -d --build`.
+
+Please note that restarting Suricata will cause all network capture files to be loaded again from zero.
+
+### 2. Launch Suricata and webapp traditionally (option B)
+
+You may launch Suricata then the web application using the following:
+```bash
+# Start Suricata
+export $(grep -vE "^(#.*|\s*)$" .env)
+./suricata/entrypoint.sh
+```
+
+```bash
+# Start web app
+export $(grep -vE "^(#.*|\s*)$" .env)
+(cd webapp && uvicorn --host 0.0.0.0 main:app)
+```
+
+Please note that restarting Suricata will cause all network capture files to be loaded again from zero.
+
+## Frequently Asked Questions
+
+### Is Suricata `flow_id` really unique?
+
+`flow_id` is derived from timestamp (ms scale) and current flow parameters (such
+as source and destination ports and addresses). See source code:
+.
+
+### How do I reload rules without rebuilding the database?
+
+You can edit suricata rules in `suricata/custom.rules`, then reload the rules
+using:
+```bash
+kill -USR2 $(pidof suricata)
+```
+
+## Licensing
+
+Copyright (C) 2023 ANSSI
+
+Shovel 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, version 3.
+
+Shovel 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 Shovel. If not, see .
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..e6c927c
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,32 @@
+version: "3"
+
+services:
+ suricata:
+ build: ./suricata
+ image: shovel-suricata
+ volumes:
+ - "./input_pcaps:/input_pcaps:ro"
+ - "./suricata/custom.rules:/suricata/custom.rules:ro"
+ - "./suricata/output:/suricata/output:rw"
+ env_file:
+ - .env
+
+ webapp:
+ build: ./webapp
+ image: shovel-webapp
+ volumes:
+ - "./input_pcaps:/webapp/static/input_pcaps:ro"
+ - "./suricata/output:/suricata/output:ro"
+ - "./webapp/database:/webapp/database:rw"
+ ports:
+ - 8000:8000
+ env_file:
+ - .env
+
+ grafana:
+ build: ./grafana
+ image: shovel-grafana
+ volumes:
+ - "./webapp/database:/webapp/database:ro"
+ ports:
+ - 3000:3000
diff --git a/example.env b/example.env
new file mode 100644
index 0000000..1ba3807
--- /dev/null
+++ b/example.env
@@ -0,0 +1,62 @@
+# Examples from FAUSTCTF (2023-09-23)
+CTF_START_DATE=2023-09-23T15:00+02:00
+CTF_TICK_LENGTH=180
+CTF_HOME_NET=[fd66:666::0/32]
+CTF_SERVICES=chatapp,image_galoisry,jokes,rsamail,buerographie_app,tic_tac_toe,office_supplies,auction_service
+CTF_SERVICE_AUCTION_SERVICE=[fd66:666:798::2]:12345,[fd66:666:798::2]:12346
+CTF_SERVICE_BUEROGRAPHIE_APP=[fd66:666:798::2]:13731
+CTF_SERVICE_CHATAPP=[fd66:666:798::2]:3000
+CTF_SERVICE_IMAGE_GALOISRY=[fd66:666:798::2]:5005
+CTF_SERVICE_JOKES=[fd66:666:798::2]:5000
+CTF_SERVICE_OFFICE_SUPPLIES=[fd66:666:798::2]:1337
+CTF_SERVICE_RSAMAIL=[fd66:666:798::2]:5555
+CTF_SERVICE_TIC_TAC_TOE=[fd66:666:798::2]:3333
+
+# Examples from ENOWARS7 (2023-07-22)
+#CTF_START_DATE=2023-07-22T15:00+02:00
+#CTF_TICK_LENGTH=60
+#CTF_HOME_NET=[10.1.42.1/25]
+#CTF_SERVICES=asocialnetwork,bollwerk,granulizer,oldschool,phreaking,steinsgate,yvm
+#CTF_SERVICE_ASOCIALNETWORK=10.1.42.1:3000
+#CTF_SERVICE_BOLLWERK=10.1.42.1:6009
+#CTF_SERVICE_GRANULIZER=10.1.42.1:2345
+#CTF_SERVICE_OLDSCHOOL=10.1.42.1:9080
+#CTF_SERVICE_PHREAKING=10.1.42.1:3399,10.1.42.1:6060,10.1.42.1:9930,10.1.42.1:6061,10.1.42.1:9931,10.1.42.1:6062,10.1.42.1:9932,10.1.42.1:6063,10.1.42.1:9933,10.1.42.1:6064,10.1.42.1:9934,10.1.42.1:6065,10.1.42.1:9935,10.1.42.1:6066,10.1.42.1:9936,10.1.42.1:6067,10.1.42.1:9937,10.1.42.1:6068,10.1.42.1:9938,10.1.42.1:6069,10.1.42.1:9939
+#CTF_SERVICE_STEINSGATE=10.1.42.1:4433,10.1.42.1:4420
+#CTF_SERVICE_YVM=10.1.42.1:3165
+
+# Examples from ICC 2023 training (2023-07-09)
+#CTF_START_DATE=2023-07-09T11:00+02:00
+#CTF_TICK_LENGTH=120
+#CTF_HOME_NET=[10.20.9.0/24]
+#CTF_SERVICES=flag_prescription,navashield,win_dc1,win_srv1
+#CTF_SERVICE_FLAG_PRESCRIPTION=10.20.9.6:5001
+#CTF_SERVICE_NAVASHIELD=10.20.9.6:8001,10.20.9.6:5000
+#CTF_SERVICE_WIN_DC1=10.20.9.4:80,10.20.9.4:445,10.20.9.4:135
+#CTF_SERVICE_WIN_SRV1=10.20.9.5:80,10.20.9.5:31337,10.20.9.5:135
+
+# Examples from ECSC 2022 (2022-09-15)
+#CTF_START_DATE=2023-01-30T13:00+02:00
+#CTF_TICK_LENGTH=120
+#CTF_HOME_NET=[10.20.9.0/24]
+#CTF_SERVICES=dewaste,cantina,hps,aquaeductus,blinkygram,winds_of_the_past,techbay
+#CTF_SERVICE_DEWASTE=10.10.10.1:10010
+#CTF_SERVICE_CANTINA=10.10.10.1:10020,10.10.10.1:10021,10.10.10.1:10024
+#CTF_SERVICE_HPS=10.10.10.1:10030
+#CTF_SERVICE_AQUAEDUCTUS=10.10.10.1:10041
+#CTF_SERVICE_BLINKYGRAM=10.10.10.1:10050
+#CTF_SERVICE_WINDS_OF_THE_PAST=10.10.10.1:10060
+#CTF_SERVICE_TECHBAY=10.10.10.1:10070
+
+# Examples from FAUST CTF 2022 (2022-07-09)
+#CTF_START_DATE=2023-01-30T13:00+02:00
+#CTF_TICK_LENGTH=180
+#CTF_HOME_NET=[fd66:666::0/32]
+#CTF_SERVICES=compiler60,docsnotebook,digital_seconds_ago,fittyfit,fluxmail,notes_from_the_future,admincrashboard
+#CTF_SERVICE_COMPILER60=[fd66:666:534::2]:6061,[fd66:666:534::2]:6062
+#CTF_SERVICE_DOCSNOTEBOOK=[fd66:666:534::2]:9000
+#CTF_SERVICE_DIGITAL_SECONDS_AGO=[fd66:666:534::2]:13731
+#CTF_SERVICE_FITTYFIT=[fd66:666:534::2]:5001
+#CTF_SERVICE_FLUXMAIL=[fd66:666:534::2]:4242
+#CTF_SERVICE_NOTES_FROM_THE_FUTURE=[fd66:666:534::2]:1338
+#CTF_SERVICE_ADMINCRASHBOARD=[fd66:666:534::2]:5000,[fd66:666:534::2]:5002
diff --git a/grafana/Dockerfile b/grafana/Dockerfile
new file mode 100644
index 0000000..daa8733
--- /dev/null
+++ b/grafana/Dockerfile
@@ -0,0 +1,10 @@
+FROM grafana/grafana-oss:10.1.2
+ENV GF_ANALYTICS_CHECK_FOR_PLUGIN_UPDATES=false
+ENV GF_ANALYTICS_CHECK_FOR_UPDATES=false
+ENV GF_ANALYTICS_REPORTING_ENABLED=false
+ENV GF_AUTH_ANONYMOUS_ENABLED=true
+ENV GF_AUTH_ANONYMOUS_HIDE_VERSION=true
+ENV GF_INSTALL_PLUGINS=frser-sqlite-datasource
+ENV GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/var/lib/grafana/dashboards/home.json
+COPY ./provisioning /etc/grafana/provisioning
+COPY ./dashboards /var/lib/grafana/dashboards
diff --git a/grafana/dashboards/home.json b/grafana/dashboards/home.json
new file mode 100644
index 0000000..478d167
--- /dev/null
+++ b/grafana/dashboards/home.json
@@ -0,0 +1,401 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 2,
+ "id": 1,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "datasource": {
+ "type": "frser-sqlite-datasource",
+ "uid": "P2D2EEF3E092AF52B"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "purple",
+ "mode": "fixed"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 40,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 0,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 10,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "frser-sqlite-datasource",
+ "uid": "P2D2EEF3E092AF52B"
+ },
+ "queryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nGROUP BY tick",
+ "queryType": "table",
+ "rawQueryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nGROUP BY tick",
+ "refId": "A",
+ "timeColumns": []
+ }
+ ],
+ "title": "Flows per tick",
+ "type": "trend"
+ },
+ {
+ "datasource": {
+ "type": "frser-sqlite-datasource",
+ "uid": "P2D2EEF3E092AF52B"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "red",
+ "mode": "fixed"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 40,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 12,
+ "x": 0,
+ "y": 7
+ },
+ "id": 3,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "frser-sqlite-datasource",
+ "uid": "P2D2EEF3E092AF52B"
+ },
+ "queryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as flag_out_count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG OUT\")\nGROUP BY tick",
+ "queryType": "table",
+ "rawQueryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as flag_out_count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG OUT\")\nGROUP BY tick",
+ "refId": "A",
+ "timeColumns": []
+ }
+ ],
+ "title": "FLAG OUT per tick",
+ "transformations": [],
+ "type": "trend"
+ },
+ {
+ "datasource": {
+ "type": "frser-sqlite-datasource",
+ "uid": "P2D2EEF3E092AF52B"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "green",
+ "mode": "fixed"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 40,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 12,
+ "x": 12,
+ "y": 7
+ },
+ "id": 11,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "frser-sqlite-datasource",
+ "uid": "P2D2EEF3E092AF52B"
+ },
+ "queryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as flag_in_count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG IN\")\nGROUP BY tick",
+ "queryType": "table",
+ "rawQueryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as flag_in_count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG IN\")\nGROUP BY tick",
+ "refId": "A",
+ "timeColumns": []
+ }
+ ],
+ "title": "FLAG IN per tick",
+ "transformations": [],
+ "type": "trend"
+ },
+ {
+ "datasource": {
+ "type": "frser-sqlite-datasource",
+ "uid": "P2D2EEF3E092AF52B"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "continuous-GrYlRd"
+ },
+ "custom": {
+ "fillOpacity": 100,
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineWidth": 0
+ },
+ "displayName": "${__field.labels.dest_ipport}",
+ "mappings": [],
+ "noValue": "0",
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 13,
+ "w": 24,
+ "x": 0,
+ "y": 14
+ },
+ "id": 5,
+ "options": {
+ "colWidth": 1,
+ "legend": {
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "rowHeight": 0.8,
+ "showValue": "auto",
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "frser-sqlite-datasource",
+ "uid": "P2D2EEF3E092AF52B"
+ },
+ "queryText": "SELECT $__unixEpochGroupSeconds(ts_start / 1000, 180) as time, COUNT(*) as flag_out_count, dest_ipport FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG OUT\")\nGROUP BY time, dest_ipport",
+ "queryType": "time series",
+ "rawQueryText": "SELECT $__unixEpochGroupSeconds(ts_start / 1000, 180) as time, COUNT(*) as flag_out_count, dest_ipport FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG OUT\")\nGROUP BY time, dest_ipport",
+ "refId": "A",
+ "timeColumns": [
+ "time",
+ "ts"
+ ]
+ }
+ ],
+ "title": "FLAG OUT per service per tick",
+ "type": "status-history"
+ }
+ ],
+ "refresh": "10s",
+ "schemaVersion": 38,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": []
+ },
+ "time": {
+ "from": "1970-01-01T12:00:00.000Z",
+ "to": "2100-01-01T12:00:00.000Z"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "Home",
+ "uid": "WdNSDiRIz",
+ "version": 9,
+ "weekStart": ""
+}
\ No newline at end of file
diff --git a/grafana/provisioning/dashboards/default.yml b/grafana/provisioning/dashboards/default.yml
new file mode 100644
index 0000000..733035e
--- /dev/null
+++ b/grafana/provisioning/dashboards/default.yml
@@ -0,0 +1,8 @@
+apiVersion: 1
+
+providers:
+ - name: dashboards
+ type: file
+ allowUiUpdates: true
+ options:
+ path: /var/lib/grafana/dashboards
diff --git a/grafana/provisioning/datasources/sql.yml b/grafana/provisioning/datasources/sql.yml
new file mode 100644
index 0000000..9afcf9b
--- /dev/null
+++ b/grafana/provisioning/datasources/sql.yml
@@ -0,0 +1,8 @@
+apiVersion: 1
+
+datasources:
+ - name: SQLite
+ type: frser-sqlite-datasource
+ jsonData:
+ path: /webapp/database/database.db
+ isDefault: true
diff --git a/suricata/.dockerignore b/suricata/.dockerignore
new file mode 100644
index 0000000..53752db
--- /dev/null
+++ b/suricata/.dockerignore
@@ -0,0 +1 @@
+output
diff --git a/suricata/Dockerfile b/suricata/Dockerfile
new file mode 100644
index 0000000..5b99ba4
--- /dev/null
+++ b/suricata/Dockerfile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-3.0-only
+
+FROM alpine:20230901
+RUN apk add --no-cache suricata
+COPY . /suricata
+CMD ["/suricata/entrypoint.sh"]
diff --git a/suricata/custom.rules b/suricata/custom.rules
new file mode 100644
index 0000000..5f535bc
--- /dev/null
+++ b/suricata/custom.rules
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: GPL-3.0-only
+#
+# Suricata rules for Attack-Defense CTF games
+# Please remember to increment `sid` when adding new rules as its value must be unique.
+
+# Flags
+# As PCRE is slow, please use a content filter before.
+# Please test your regex at https://regex101.com/ using "PCRE2" mode.
+alert tcp $HOME_NET any -> any any (msg: "A ECSC flag was sent to client"; flow:to_client; content: "ECSC_"; pcre: "/ECSC_[A-Za-z0-9\/+]{32}/"; metadata: tag FLAG OUT, color danger; sid: 1;)
+alert tcp $HOME_NET any -> any any (msg: "A ECSC flag was sent to client (base64)"; flow:to_client; content: "RUNTQ1"; pcre: "/RUNTQ1[A-Za-z0-9\/+]{44}==/"; metadata: tag FLAG OUT B64, color danger; sid: 2;)
+alert tcp $HOME_NET any -> any any (msg: "A ENOWARS flag was sent to client"; flow:to_client; content: "ENO"; pcre: "/ENO[A-Za-z0-9+\/=]{48}/"; metadata: tag FLAG OUT, color danger; sid: 3;)
+alert tcp $HOME_NET any -> any any (msg: "A ENOWARS flag was sent to client (base64)"; flow:to_client; content: "RU5P"; pcre: "/RU5P[A-Za-z0-9\/+]{64}/"; metadata: tag FLAG OUT B64, color danger; sid: 4;)
+alert tcp $HOME_NET any -> any any (msg: "A FAUSTCTF flag was sent to client"; flow:to_client; content: "FAUST_"; metadata: tag FLAG OUT, color danger; sid: 5;)
+alert tcp $HOME_NET any -> any any (msg: "A FAUSTCTF flag was sent to client (base64)"; flow:to_client; content: "RkFVU1Rf"; metadata: tag FLAG OUT B64, color danger; sid: 6;)
+alert tcp $HOME_NET any -> any any (msg: "A FAUSTCTF flag was sent to client (base64, 1-byte shifted)"; flow:to_client; content: "ZBVVNUX"; metadata: tag FLAG OUT B64, color danger; sid: 7;)
+alert tcp $HOME_NET any -> any any (msg: "A FAUSTCTF flag was sent to client (base64, 2-byte shifted)"; flow:to_client; content: "GQVVTVF"; metadata: tag FLAG OUT B64, color danger; sid: 8;)
+alert tcp $HOME_NET any -> any any (msg: "A ICC flag was sent to client"; flow:to_client; content: "ICC_"; pcre: "/ICC_[A-Za-z0-9\/+]{32}/"; metadata: tag FLAG OUT, color danger; sid: 9;)
+alert tcp $HOME_NET any -> any any (msg: "A ICC flag was sent to client (base64)"; flow:to_client; content: "SUNDX"; pcre: "/SUNDX[A-Za-z0-9\/+]{43}/"; metadata: tag FLAG OUT, color danger; sid: 10;)
+alert tcp any any -> $HOME_NET any (msg: "A flag was placed in our services (probably by the CTF admins)"; flow:to_server; content: "ECSC_"; pcre: "/ECSC_[A-Za-z0-9\/+]{32}/"; metadata: tag FLAG IN, color success; sid: 51;)
+alert tcp any any -> $HOME_NET any (msg: "A flag was placed in our services (probably by the CTF admins)"; flow:to_server; content: "ENO"; pcre: "/ENO[A-Za-z0-9+\/=]{48}/"; metadata: tag FLAG IN, color success; sid: 52;)
+alert tcp any any -> $HOME_NET any (msg: "A flag was placed in our services (probably by the CTF admins)"; flow:to_server; content: "FAUST_"; pcre: "/FAUST_[A-Za-z0-9\/+]{32}/"; metadata: tag FLAG IN, color success; sid: 53;)
+alert tcp any any -> $HOME_NET any (msg: "A flag was placed in our services (probably by the CTF admins)"; flow:to_server; content: "ICC_"; pcre: "/ICC_[A-Za-z0-9\/+]{32}/"; metadata: tag FLAG IN, color success; sid: 54;)
+
+# Tag file formats using libmagic
+alert tcp any any -> any any (msg: "tag"; filemagic: "assembler source"; metadata: tag ASM, color primary; sid: 101;)
+alert tcp any any -> any any (msg: "tag"; filemagic: "GIF image"; metadata: tag GIF, color primary; sid: 102;)
+alert tcp any any -> any any (msg: "tag"; filemagic: "HTML document"; metadata: tag HTML, color primary; sid: 113;)
+alert tcp any any -> any any (msg: "tag"; filemagic: "JPEG image"; metadata: tag JPG, color primary; sid: 104;)
+alert tcp any any -> any any (msg: "tag"; filemagic: "JSON "; metadata: tag JSON, color primary; sid: 105;)
+alert tcp any any -> any any (msg: "tag"; filemagic: "PDF document"; metadata: tag PDF, color primary; sid: 106;)
+alert tcp any any -> any any (msg: "tag"; filemagic: "PNG image"; metadata: tag PNG, color primary; sid: 107;)
+alert tcp any any -> any any (msg: "tag"; filemagic: "SVG Scalable Vector Graphics image"; metadata: tag SVG, color primary; sid: 108;)
+alert tcp any any -> any any (msg: "tag"; filemagic: "VGM Video Game Music"; metadata: tag VGM, color primary; sid: 109;)
+alert tcp any any -> any any (msg: "tag"; filemagic: "Web Open Font"; metadata: tag WOFF, color primary; sid: 110;)
+alert tcp any any -> any any (msg: "tag"; filemagic: "Zip archive"; metadata: tag ZIP, color primary; sid: 111;)
+
+# Tag HTTP methods and status
+alert tcp any any -> any any (msg: "tag"; http.method; content: "GET"; metadata: tag GET, color info; sid: 201;)
+alert tcp any any -> any any (msg: "tag"; http.method; content: "POST"; metadata: tag POST, color info; sid: 202;)
+alert tcp any any -> any any (msg: "tag"; http.method; content: "PUT"; metadata: tag PUT, color info; sid: 203;)
+alert tcp any any -> any any (msg: "tag"; http.method; content: "HEAD"; metadata: tag HEAD, color info; sid: 204;)
+alert tcp any any -> any any (msg: "tag"; http.method; content: "DELETE"; metadata: tag DELETE, color info; sid: 205;)
+alert tcp any any -> any any (msg: "tag"; http.method; content: "TRACE"; metadata: tag TRACE, color info; sid: 206;)
+alert tcp any any -> any any (msg: "tag"; http.method; content: "OPTIONS"; metadata: tag OPTIONS, color info; sid: 207;)
+alert tcp any any -> any any (msg: "tag"; http.method; content: "CONNECT"; metadata: tag CONNECT, color info; sid: 208;)
+alert tcp any any -> any any (msg: "tag"; http.method; content: "PATCH"; metadata: tag PATCH, color info; sid: 209;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "200"; metadata: tag 200, color info; sid: 210;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "201"; metadata: tag 201, color info; sid: 211;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "202"; metadata: tag 202, color info; sid: 212;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "204"; metadata: tag 204, color info; sid: 213;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "301"; metadata: tag 301, color info; sid: 214;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "302"; metadata: tag 302, color info; sid: 215;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "304"; metadata: tag 304, color info; sid: 216;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "400"; metadata: tag 400, color info; sid: 217;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "401"; metadata: tag 401, color info; sid: 218;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "403"; metadata: tag 403, color info; sid: 219;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "404"; metadata: tag 404, color info; sid: 220;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "405"; metadata: tag 405, color info; sid: 221;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "408"; metadata: tag 408, color info; sid: 222;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "500"; metadata: tag 500, color warning; sid: 223;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "501"; metadata: tag 501, color warning; sid: 224;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "502"; metadata: tag 502, color warning; sid: 225;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "503"; metadata: tag 503, color warning; sid: 226;)
+alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "504"; metadata: tag 504, color warning; sid: 227;)
+
+# Side-channel indicators
+alert tcp any any -> $HOME_NET any (msg: "Found python-requests User-Agent"; flow:to_server; content: "python-requests/"; http_user_agent; metadata: tag UA PYREQ, color warning; sid: 301;)
+alert tcp any any -> $HOME_NET any (msg: "Found python-httpx User-Agent"; flow:to_server; content: "python-httpx/"; http_user_agent; metadata: tag UA HTTPX, color warning; sid: 302;)
+alert tcp any any -> $HOME_NET any (msg: "Found HeadlessChrome User-Agent"; flow:to_server; content: "HeadlessChrome/"; http_user_agent; metadata: tag UA HLCHROME, color warning; sid: 303;)
+alert tcp any any -> $HOME_NET any (msg: "Found Firefox User-Agent"; flow:to_server; content: "Firefox/"; http_user_agent; metadata: tag UA FIREFOX, color warning; sid: 304;)
diff --git a/suricata/entrypoint.sh b/suricata/entrypoint.sh
new file mode 100755
index 0000000..f682ff7
--- /dev/null
+++ b/suricata/entrypoint.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-3.0-only
+
+echo "Cleaning previous Suricata output"
+rm -f suricata/output/eve.json suricata/output/tcpstore.log suricata/output/udpstore.log
+
+echo "Starting Suricata with HOME_NET=$CTF_HOME_NET"
+suricata -c suricata/suricata.yaml -S suricata/custom.rules -r input_pcaps -l suricata/output --set vars.address-groups.HOME_NET="$CTF_HOME_NET" --runmode=single --no-random --pcap-file-continuous
diff --git a/suricata/lua/sha2.lua b/suricata/lua/sha2.lua
new file mode 100644
index 0000000..201f52e
--- /dev/null
+++ b/suricata/lua/sha2.lua
@@ -0,0 +1,5675 @@
+--------------------------------------------------------------------------------------------------------------------------
+-- sha2.lua
+--------------------------------------------------------------------------------------------------------------------------
+-- VERSION: 12 (2022-02-23)
+-- AUTHOR: Egor Skriptunoff
+-- LICENSE: MIT (the same license as Lua itself)
+-- URL: https://github.com/Egor-Skriptunoff/pure_lua_SHA
+--
+-- DESCRIPTION:
+-- This module contains functions to calculate SHA digest:
+-- MD5, SHA-1,
+-- SHA-224, SHA-256, SHA-512/224, SHA-512/256, SHA-384, SHA-512,
+-- SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256,
+-- HMAC,
+-- BLAKE2b, BLAKE2s, BLAKE2bp, BLAKE2sp, BLAKE2Xb, BLAKE2Xs,
+-- BLAKE3, BLAKE3_KDF
+-- Written in pure Lua.
+-- Compatible with:
+-- Lua 5.1, Lua 5.2, Lua 5.3, Lua 5.4, Fengari, LuaJIT 2.0/2.1 (any CPU endianness).
+-- Main feature of this module: it was heavily optimized for speed.
+-- For every Lua version the module contains particular implementation branch to get benefits from version-specific features.
+-- - branch for Lua 5.1 (emulating bitwise operators using look-up table)
+-- - branch for Lua 5.2 (using bit32/bit library), suitable for both Lua 5.2 with native "bit32" and Lua 5.1 with external library "bit"
+-- - branch for Lua 5.3/5.4 (using native 64-bit bitwise operators)
+-- - branch for Lua 5.3/5.4 (using native 32-bit bitwise operators) for Lua built with LUA_INT_TYPE=LUA_INT_INT
+-- - branch for LuaJIT without FFI library (useful in a sandboxed environment)
+-- - branch for LuaJIT x86 without FFI library (LuaJIT x86 has oddity because of lack of CPU registers)
+-- - branch for LuaJIT 2.0 with FFI library (bit.* functions work only with Lua numbers)
+-- - branch for LuaJIT 2.1 with FFI library (bit.* functions can work with "int64_t" arguments)
+--
+--
+-- USAGE:
+-- Input data should be provided as a binary string: either as a whole string or as a sequence of substrings (chunk-by-chunk loading, total length < 9*10^15 bytes).
+-- Result (SHA digest) is returned in hexadecimal representation as a string of lowercase hex digits.
+-- Simplest usage example:
+-- local sha = require("sha2")
+-- local your_hash = sha.sha256("your string")
+-- See file "sha2_test.lua" for more examples.
+--
+--
+-- CHANGELOG:
+-- version date description
+-- ------- ---------- -----------
+-- 12 2022-02-23 Now works in Luau (but NOT optimized for speed)
+-- 11 2022-01-09 BLAKE3 added
+-- 10 2022-01-02 BLAKE2 functions added
+-- 9 2020-05-10 Now works in OpenWrt's Lua (dialect of Lua 5.1 with "double" + "invisible int32")
+-- 8 2019-09-03 SHA-3 functions added
+-- 7 2019-03-17 Added functions to convert to/from base64
+-- 6 2018-11-12 HMAC added
+-- 5 2018-11-10 SHA-1 added
+-- 4 2018-11-03 MD5 added
+-- 3 2018-11-02 Bug fixed: incorrect hashing of long (2 GByte) data streams on Lua 5.3/5.4 built with "int32" integers
+-- 2 2018-10-07 Decreased module loading time in Lua 5.1 implementation branch (thanks to Peter Melnichenko for giving a hint)
+-- 1 2018-10-06 First release (only SHA-2 functions)
+-----------------------------------------------------------------------------
+
+
+local print_debug_messages = false -- set to true to view some messages about your system's abilities and implementation branch chosen for your system
+
+local unpack, table_concat, byte, char, string_rep, sub, gsub, gmatch, string_format, floor, ceil, math_min, math_max, tonumber, type, math_huge =
+ table.unpack or unpack, table.concat, string.byte, string.char, string.rep, string.sub, string.gsub, string.gmatch, string.format, math.floor, math.ceil, math.min, math.max, tonumber, type, math.huge
+
+
+--------------------------------------------------------------------------------
+-- EXAMINING YOUR SYSTEM
+--------------------------------------------------------------------------------
+
+local function get_precision(one)
+ -- "one" must be either float 1.0 or integer 1
+ -- returns bits_precision, is_integer
+ -- This function works correctly with all floating point datatypes (including non-IEEE-754)
+ local k, n, m, prev_n = 0, one, one
+ while true do
+ k, prev_n, n, m = k + 1, n, n + n + 1, m + m + k % 2
+ if k > 256 or n - (n - 1) ~= 1 or m - (m - 1) ~= 1 or n == m then
+ return k, false -- floating point datatype
+ elseif n == prev_n then
+ return k, true -- integer datatype
+ end
+ end
+end
+
+-- Make sure Lua has "double" numbers
+local x = 2/3
+local Lua_has_double = x * 5 > 3 and x * 4 < 3 and get_precision(1.0) >= 53
+assert(Lua_has_double, "at least 53-bit floating point numbers are required")
+
+-- Q:
+-- SHA2 was designed for FPU-less machines.
+-- So, why floating point numbers are needed for this module?
+-- A:
+-- 53-bit "double" numbers are useful to calculate "magic numbers" used in SHA.
+-- I prefer to write 50 LOC "magic numbers calculator" instead of storing more than 200 constants explicitly in this source file.
+
+local int_prec, Lua_has_integers = get_precision(1)
+local Lua_has_int64 = Lua_has_integers and int_prec == 64
+local Lua_has_int32 = Lua_has_integers and int_prec == 32
+assert(Lua_has_int64 or Lua_has_int32 or not Lua_has_integers, "Lua integers must be either 32-bit or 64-bit")
+
+-- Q:
+-- Does it mean that almost all non-standard configurations are not supported?
+-- A:
+-- Yes. Sorry, too many problems to support all possible Lua numbers configurations.
+-- Lua 5.1/5.2 with "int32" will not work.
+-- Lua 5.1/5.2 with "int64" will not work.
+-- Lua 5.1/5.2 with "int128" will not work.
+-- Lua 5.1/5.2 with "float" will not work.
+-- Lua 5.1/5.2 with "double" is OK. (default config for Lua 5.1, Lua 5.2, LuaJIT)
+-- Lua 5.3/5.4 with "int32" + "float" will not work.
+-- Lua 5.3/5.4 with "int64" + "float" will not work.
+-- Lua 5.3/5.4 with "int128" + "float" will not work.
+-- Lua 5.3/5.4 with "int32" + "double" is OK. (config used by Fengari)
+-- Lua 5.3/5.4 with "int64" + "double" is OK. (default config for Lua 5.3, Lua 5.4)
+-- Lua 5.3/5.4 with "int128" + "double" will not work.
+-- Using floating point numbers better than "double" instead of "double" is OK (non-IEEE-754 floating point implementation are allowed).
+-- Using "int128" instead of "int64" is not OK: "int128" would require different branch of implementation for optimized SHA512.
+
+-- Check for LuaJIT and 32-bit bitwise libraries
+local is_LuaJIT = ({false, [1] = true})[1] and _VERSION ~= "Luau" and (type(jit) ~= "table" or jit.version_num >= 20000) -- LuaJIT 1.x.x and Luau are treated as vanilla Lua 5.1/5.2
+local is_LuaJIT_21 -- LuaJIT 2.1+
+local LuaJIT_arch
+local ffi -- LuaJIT FFI library (as a table)
+local b -- 32-bit bitwise library (as a table)
+local library_name
+
+if is_LuaJIT then
+ -- Assuming "bit" library is always available on LuaJIT
+ b = require"bit"
+ library_name = "bit"
+ -- "ffi" is intentionally disabled on some systems for safety reason
+ local LuaJIT_has_FFI, result = pcall(require, "ffi")
+ if LuaJIT_has_FFI then
+ ffi = result
+ end
+ is_LuaJIT_21 = not not loadstring"b=0b0"
+ LuaJIT_arch = type(jit) == "table" and jit.arch or ffi and ffi.arch or nil
+else
+ -- For vanilla Lua, "bit"/"bit32" libraries are searched in global namespace only. No attempt is made to load a library if it's not loaded yet.
+ for _, libname in ipairs(_VERSION == "Lua 5.2" and {"bit32", "bit"} or {"bit", "bit32"}) do
+ if type(_G[libname]) == "table" and _G[libname].bxor then
+ b = _G[libname]
+ library_name = libname
+ break
+ end
+ end
+end
+
+--------------------------------------------------------------------------------
+-- You can disable here some of your system's abilities (for testing purposes)
+--------------------------------------------------------------------------------
+-- is_LuaJIT = nil
+-- is_LuaJIT_21 = nil
+-- ffi = nil
+-- Lua_has_int32 = nil
+-- Lua_has_int64 = nil
+-- b, library_name = nil
+--------------------------------------------------------------------------------
+
+if print_debug_messages then
+ -- Printing list of abilities of your system
+ print("Abilities:")
+ print(" Lua version: "..(is_LuaJIT and "LuaJIT "..(is_LuaJIT_21 and "2.1 " or "2.0 ")..(LuaJIT_arch or "")..(ffi and " with FFI" or " without FFI") or _VERSION))
+ print(" Integer bitwise operators: "..(Lua_has_int64 and "int64" or Lua_has_int32 and "int32" or "no"))
+ print(" 32-bit bitwise library: "..(library_name or "not found"))
+end
+
+-- Selecting the most suitable implementation for given set of abilities
+local method, branch
+if is_LuaJIT and ffi then
+ method = "Using 'ffi' library of LuaJIT"
+ branch = "FFI"
+elseif is_LuaJIT then
+ method = "Using special code for sandboxed LuaJIT (no FFI)"
+ branch = "LJ"
+elseif Lua_has_int64 then
+ method = "Using native int64 bitwise operators"
+ branch = "INT64"
+elseif Lua_has_int32 then
+ method = "Using native int32 bitwise operators"
+ branch = "INT32"
+elseif library_name then -- when bitwise library is available (Lua 5.2 with native library "bit32" or Lua 5.1 with external library "bit")
+ method = "Using '"..library_name.."' library"
+ branch = "LIB32"
+else
+ method = "Emulating bitwise operators using look-up table"
+ branch = "EMUL"
+end
+
+if print_debug_messages then
+ -- Printing the implementation selected to be used on your system
+ print("Implementation selected:")
+ print(" "..method)
+end
+
+
+--------------------------------------------------------------------------------
+-- BASIC 32-BIT BITWISE FUNCTIONS
+--------------------------------------------------------------------------------
+
+local AND, OR, XOR, SHL, SHR, ROL, ROR, NOT, NORM, HEX, XOR_BYTE
+-- Only low 32 bits of function arguments matter, high bits are ignored
+-- The result of all functions (except HEX) is an integer inside "correct range":
+-- for "bit" library: (-2^31)..(2^31-1)
+-- for "bit32" library: 0..(2^32-1)
+
+if branch == "FFI" or branch == "LJ" or branch == "LIB32" then
+
+ -- Your system has 32-bit bitwise library (either "bit" or "bit32")
+
+ AND = b.band -- 2 arguments
+ OR = b.bor -- 2 arguments
+ XOR = b.bxor -- 2..5 arguments
+ SHL = b.lshift -- second argument is integer 0..31
+ SHR = b.rshift -- second argument is integer 0..31
+ ROL = b.rol or b.lrotate -- second argument is integer 0..31
+ ROR = b.ror or b.rrotate -- second argument is integer 0..31
+ NOT = b.bnot -- only for LuaJIT
+ NORM = b.tobit -- only for LuaJIT
+ HEX = b.tohex -- returns string of 8 lowercase hexadecimal digits
+ assert(AND and OR and XOR and SHL and SHR and ROL and ROR and NOT, "Library '"..library_name.."' is incomplete")
+ XOR_BYTE = XOR -- XOR of two bytes (0..255)
+
+elseif branch == "EMUL" then
+
+ -- Emulating 32-bit bitwise operations using 53-bit floating point arithmetic
+
+ function SHL(x, n)
+ return (x * 2^n) % 2^32
+ end
+
+ function SHR(x, n)
+ x = x % 2^32 / 2^n
+ return x - x % 1
+ end
+
+ function ROL(x, n)
+ x = x % 2^32 * 2^n
+ local r = x % 2^32
+ return r + (x - r) / 2^32
+ end
+
+ function ROR(x, n)
+ x = x % 2^32 / 2^n
+ local r = x % 1
+ return r * 2^32 + (x - r)
+ end
+
+ local AND_of_two_bytes = {[0] = 0} -- look-up table (256*256 entries)
+ local idx = 0
+ for y = 0, 127 * 256, 256 do
+ for x = y, y + 127 do
+ x = AND_of_two_bytes[x] * 2
+ AND_of_two_bytes[idx] = x
+ AND_of_two_bytes[idx + 1] = x
+ AND_of_two_bytes[idx + 256] = x
+ AND_of_two_bytes[idx + 257] = x + 1
+ idx = idx + 2
+ end
+ idx = idx + 256
+ end
+
+ local function and_or_xor(x, y, operation)
+ -- operation: nil = AND, 1 = OR, 2 = XOR
+ local x0 = x % 2^32
+ local y0 = y % 2^32
+ local rx = x0 % 256
+ local ry = y0 % 256
+ local res = AND_of_two_bytes[rx + ry * 256]
+ x = x0 - rx
+ y = (y0 - ry) / 256
+ rx = x % 65536
+ ry = y % 256
+ res = res + AND_of_two_bytes[rx + ry] * 256
+ x = (x - rx) / 256
+ y = (y - ry) / 256
+ rx = x % 65536 + y % 256
+ res = res + AND_of_two_bytes[rx] * 65536
+ res = res + AND_of_two_bytes[(x + y - rx) / 256] * 16777216
+ if operation then
+ res = x0 + y0 - operation * res
+ end
+ return res
+ end
+
+ function AND(x, y)
+ return and_or_xor(x, y)
+ end
+
+ function OR(x, y)
+ return and_or_xor(x, y, 1)
+ end
+
+ function XOR(x, y, z, t, u) -- 2..5 arguments
+ if z then
+ if t then
+ if u then
+ t = and_or_xor(t, u, 2)
+ end
+ z = and_or_xor(z, t, 2)
+ end
+ y = and_or_xor(y, z, 2)
+ end
+ return and_or_xor(x, y, 2)
+ end
+
+ function XOR_BYTE(x, y)
+ return x + y - 2 * AND_of_two_bytes[x + y * 256]
+ end
+
+end
+
+HEX = HEX
+ or
+ pcall(string_format, "%x", 2^31) and
+ function (x) -- returns string of 8 lowercase hexadecimal digits
+ return string_format("%08x", x % 4294967296)
+ end
+ or
+ function (x) -- for OpenWrt's dialect of Lua
+ return string_format("%08x", (x + 2^31) % 2^32 - 2^31)
+ end
+
+local function XORA5(x, y)
+ return XOR(x, y or 0xA5A5A5A5) % 4294967296
+end
+
+local function create_array_of_lanes()
+ return {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+end
+
+
+--------------------------------------------------------------------------------
+-- CREATING OPTIMIZED INNER LOOP
+--------------------------------------------------------------------------------
+
+-- Inner loop functions
+local sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64
+
+-- Arrays of SHA-2 "magic numbers" (in "INT64" and "FFI" branches "*_lo" arrays contain 64-bit values)
+local sha2_K_lo, sha2_K_hi, sha2_H_lo, sha2_H_hi, sha3_RC_lo, sha3_RC_hi = {}, {}, {}, {}, {}, {}
+local sha2_H_ext256 = {[224] = {}, [256] = sha2_H_hi}
+local sha2_H_ext512_lo, sha2_H_ext512_hi = {[384] = {}, [512] = sha2_H_lo}, {[384] = {}, [512] = sha2_H_hi}
+local md5_K, md5_sha1_H = {}, {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0}
+local md5_next_shift = {0, 0, 0, 0, 0, 0, 0, 0, 28, 25, 26, 27, 0, 0, 10, 9, 11, 12, 0, 15, 16, 17, 18, 0, 20, 22, 23, 21}
+local HEX64, lanes_index_base -- defined only for branches that internally use 64-bit integers: "INT64" and "FFI"
+local common_W = {} -- temporary table shared between all calculations (to avoid creating new temporary table every time)
+local common_W_blake2b, common_W_blake2s, v_for_blake2s_feed_64 = common_W, common_W, {}
+local K_lo_modulo, hi_factor, hi_factor_keccak = 4294967296, 0, 0
+local sigma = {
+ { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 },
+ { 15, 11, 5, 9, 10, 16, 14, 7, 2, 13, 1, 3, 12, 8, 6, 4 },
+ { 12, 9, 13, 1, 6, 3, 16, 14, 11, 15, 4, 7, 8, 2, 10, 5 },
+ { 8, 10, 4, 2, 14, 13, 12, 15, 3, 7, 6, 11, 5, 1, 16, 9 },
+ { 10, 1, 6, 8, 3, 5, 11, 16, 15, 2, 12, 13, 7, 9, 4, 14 },
+ { 3, 13, 7, 11, 1, 12, 9, 4, 5, 14, 8, 6, 16, 15, 2, 10 },
+ { 13, 6, 2, 16, 15, 14, 5, 11, 1, 8, 7, 4, 10, 3, 9, 12 },
+ { 14, 12, 8, 15, 13, 2, 4, 10, 6, 1, 16, 5, 9, 7, 3, 11 },
+ { 7, 16, 15, 10, 12, 4, 1, 9, 13, 3, 14, 8, 2, 5, 11, 6 },
+ { 11, 3, 9, 5, 8, 7, 2, 6, 16, 12, 10, 15, 4, 13, 14, 1 },
+}; sigma[11], sigma[12] = sigma[1], sigma[2]
+local perm_blake3 = {
+ 1, 3, 4, 11, 13, 10, 12, 6,
+ 1, 3, 4, 11, 13, 10,
+ 2, 7, 5, 8, 14, 15, 16, 9,
+ 2, 7, 5, 8, 14, 15,
+}
+
+local function build_keccak_format(elem)
+ local keccak_format = {}
+ for _, size in ipairs{1, 9, 13, 17, 18, 21} do
+ keccak_format[size] = "<"..string_rep(elem, size)
+ end
+ return keccak_format
+end
+
+
+if branch == "FFI" then
+
+ local common_W_FFI_int32 = ffi.new("int32_t[?]", 80) -- 64 is enough for SHA256, but 80 is needed for SHA-1
+ common_W_blake2s = common_W_FFI_int32
+ v_for_blake2s_feed_64 = ffi.new("int32_t[?]", 16)
+ perm_blake3 = ffi.new("uint8_t[?]", #perm_blake3 + 1, 0, unpack(perm_blake3))
+ for j = 1, 10 do
+ sigma[j] = ffi.new("uint8_t[?]", #sigma[j] + 1, 0, unpack(sigma[j]))
+ end; sigma[11], sigma[12] = sigma[1], sigma[2]
+
+
+ -- SHA256 implementation for "LuaJIT with FFI" branch
+
+ function sha256_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K = common_W_FFI_int32, sha2_K_hi
+ for pos = offs, offs + size - 1, 64 do
+ for j = 0, 15 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos) -- slow, but doesn't depend on endianness
+ W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d)
+ end
+ for j = 16, 63 do
+ local a, b = W[j-15], W[j-2]
+ W[j] = NORM( XOR(ROR(a, 7), ROL(a, 14), SHR(a, 3)) + XOR(ROL(b, 15), ROL(b, 13), SHR(b, 10)) + W[j-7] + W[j-16] )
+ end
+ local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for j = 0, 63, 8 do -- Thanks to Peter Cawley for this workaround (unroll the loop to avoid "PHI shuffling too complex" due to PHIs overlap)
+ local z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j] + K[j+1] + h) )
+ h, g, f, e = g, f, e, NORM( d + z )
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+1] + K[j+2] + h) )
+ h, g, f, e = g, f, e, NORM( d + z )
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+2] + K[j+3] + h) )
+ h, g, f, e = g, f, e, NORM( d + z )
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+3] + K[j+4] + h) )
+ h, g, f, e = g, f, e, NORM( d + z )
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+4] + K[j+5] + h) )
+ h, g, f, e = g, f, e, NORM( d + z )
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+5] + K[j+6] + h) )
+ h, g, f, e = g, f, e, NORM( d + z )
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+6] + K[j+7] + h) )
+ h, g, f, e = g, f, e, NORM( d + z )
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+7] + K[j+8] + h) )
+ h, g, f, e = g, f, e, NORM( d + z )
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ end
+ H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4])
+ H[5], H[6], H[7], H[8] = NORM(e + H[5]), NORM(f + H[6]), NORM(g + H[7]), NORM(h + H[8])
+ end
+ end
+
+
+ local common_W_FFI_int64 = ffi.new("int64_t[?]", 80)
+ common_W_blake2b = common_W_FFI_int64
+ local int64 = ffi.typeof"int64_t"
+ local int32 = ffi.typeof"int32_t"
+ local uint32 = ffi.typeof"uint32_t"
+ hi_factor = int64(2^32)
+
+ if is_LuaJIT_21 then -- LuaJIT 2.1 supports bitwise 64-bit operations
+
+ local AND64, OR64, XOR64, NOT64, SHL64, SHR64, ROL64, ROR64 -- introducing synonyms for better code readability
+ = AND, OR, XOR, NOT, SHL, SHR, ROL, ROR
+ HEX64 = HEX
+
+
+ -- BLAKE2b implementation for "LuaJIT 2.1 + FFI" branch
+
+ do
+ local v = ffi.new("int64_t[?]", 16)
+ local W = common_W_blake2b
+
+ local function G(a, b, c, d, k1, k2)
+ local va, vb, vc, vd = v[a], v[b], v[c], v[d]
+ va = W[k1] + (va + vb)
+ vd = ROR64(XOR64(vd, va), 32)
+ vc = vc + vd
+ vb = ROR64(XOR64(vb, vc), 24)
+ va = W[k2] + (va + vb)
+ vd = ROR64(XOR64(vd, va), 16)
+ vc = vc + vd
+ vb = ROL64(XOR64(vb, vc), 1)
+ v[a], v[b], v[c], v[d] = va, vb, vc, vd
+ end
+
+ function blake2b_feed_128(H, _, str, offs, size, bytes_compressed, last_block_size, is_last_node)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for pos = offs, offs + size - 1, 128 do
+ if str then
+ for j = 1, 16 do
+ pos = pos + 8
+ local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos)
+ W[j] = XOR64(OR(SHL(h, 24), SHL(g, 16), SHL(f, 8), e) * int64(2^32), uint32(int32(OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a))))
+ end
+ end
+ v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8
+ v[0x8], v[0x9], v[0xA], v[0xB], v[0xD], v[0xE], v[0xF] = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8]
+ bytes_compressed = bytes_compressed + (last_block_size or 128)
+ v[0xC] = XOR64(sha2_H_lo[5], bytes_compressed) -- t0 = low_8_bytes(bytes_compressed)
+ -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes
+ if last_block_size then -- flag f0
+ v[0xE] = NOT64(v[0xE])
+ end
+ if is_last_node then -- flag f1
+ v[0xF] = NOT64(v[0xF])
+ end
+ for j = 1, 12 do
+ local row = sigma[j]
+ G(0, 4, 8, 12, row[ 1], row[ 2])
+ G(1, 5, 9, 13, row[ 3], row[ 4])
+ G(2, 6, 10, 14, row[ 5], row[ 6])
+ G(3, 7, 11, 15, row[ 7], row[ 8])
+ G(0, 5, 10, 15, row[ 9], row[10])
+ G(1, 6, 11, 12, row[11], row[12])
+ G(2, 7, 8, 13, row[13], row[14])
+ G(3, 4, 9, 14, row[15], row[16])
+ end
+ h1 = XOR64(h1, v[0x0], v[0x8])
+ h2 = XOR64(h2, v[0x1], v[0x9])
+ h3 = XOR64(h3, v[0x2], v[0xA])
+ h4 = XOR64(h4, v[0x3], v[0xB])
+ h5 = XOR64(h5, v[0x4], v[0xC])
+ h6 = XOR64(h6, v[0x5], v[0xD])
+ h7 = XOR64(h7, v[0x6], v[0xE])
+ h8 = XOR64(h8, v[0x7], v[0xF])
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ return bytes_compressed
+ end
+
+ end
+
+
+ -- SHA-3 implementation for "LuaJIT 2.1 + FFI" branch
+
+ local arr64_t = ffi.typeof"int64_t[?]"
+ -- lanes array is indexed from 0
+ lanes_index_base = 0
+ hi_factor_keccak = int64(2^32)
+
+ function create_array_of_lanes()
+ return arr64_t(30) -- 25 + 5 for temporary usage
+ end
+
+ function keccak_feed(lanes, _, str, offs, size, block_size_in_bytes)
+ -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8
+ local RC = sha3_RC_lo
+ local qwords_qty = SHR(block_size_in_bytes, 3)
+ for pos = offs, offs + size - 1, block_size_in_bytes do
+ for j = 0, qwords_qty - 1 do
+ pos = pos + 8
+ local h, g, f, e, d, c, b, a = byte(str, pos - 7, pos) -- slow, but doesn't depend on endianness
+ lanes[j] = XOR64(lanes[j], OR64(OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) * int64(2^32), uint32(int32(OR(SHL(e, 24), SHL(f, 16), SHL(g, 8), h)))))
+ end
+ for round_idx = 1, 24 do
+ for j = 0, 4 do
+ lanes[25 + j] = XOR64(lanes[j], lanes[j+5], lanes[j+10], lanes[j+15], lanes[j+20])
+ end
+ local D = XOR64(lanes[25], ROL64(lanes[27], 1))
+ lanes[1], lanes[6], lanes[11], lanes[16] = ROL64(XOR64(D, lanes[6]), 44), ROL64(XOR64(D, lanes[16]), 45), ROL64(XOR64(D, lanes[1]), 1), ROL64(XOR64(D, lanes[11]), 10)
+ lanes[21] = ROL64(XOR64(D, lanes[21]), 2)
+ D = XOR64(lanes[26], ROL64(lanes[28], 1))
+ lanes[2], lanes[7], lanes[12], lanes[22] = ROL64(XOR64(D, lanes[12]), 43), ROL64(XOR64(D, lanes[22]), 61), ROL64(XOR64(D, lanes[7]), 6), ROL64(XOR64(D, lanes[2]), 62)
+ lanes[17] = ROL64(XOR64(D, lanes[17]), 15)
+ D = XOR64(lanes[27], ROL64(lanes[29], 1))
+ lanes[3], lanes[8], lanes[18], lanes[23] = ROL64(XOR64(D, lanes[18]), 21), ROL64(XOR64(D, lanes[3]), 28), ROL64(XOR64(D, lanes[23]), 56), ROL64(XOR64(D, lanes[8]), 55)
+ lanes[13] = ROL64(XOR64(D, lanes[13]), 25)
+ D = XOR64(lanes[28], ROL64(lanes[25], 1))
+ lanes[4], lanes[14], lanes[19], lanes[24] = ROL64(XOR64(D, lanes[24]), 14), ROL64(XOR64(D, lanes[19]), 8), ROL64(XOR64(D, lanes[4]), 27), ROL64(XOR64(D, lanes[14]), 39)
+ lanes[9] = ROL64(XOR64(D, lanes[9]), 20)
+ D = XOR64(lanes[29], ROL64(lanes[26], 1))
+ lanes[5], lanes[10], lanes[15], lanes[20] = ROL64(XOR64(D, lanes[10]), 3), ROL64(XOR64(D, lanes[20]), 18), ROL64(XOR64(D, lanes[5]), 36), ROL64(XOR64(D, lanes[15]), 41)
+ lanes[0] = XOR64(D, lanes[0])
+ lanes[0], lanes[1], lanes[2], lanes[3], lanes[4] = XOR64(lanes[0], AND64(NOT64(lanes[1]), lanes[2]), RC[round_idx]), XOR64(lanes[1], AND64(NOT64(lanes[2]), lanes[3])), XOR64(lanes[2], AND64(NOT64(lanes[3]), lanes[4])), XOR64(lanes[3], AND64(NOT64(lanes[4]), lanes[0])), XOR64(lanes[4], AND64(NOT64(lanes[0]), lanes[1]))
+ lanes[5], lanes[6], lanes[7], lanes[8], lanes[9] = XOR64(lanes[8], AND64(NOT64(lanes[9]), lanes[5])), XOR64(lanes[9], AND64(NOT64(lanes[5]), lanes[6])), XOR64(lanes[5], AND64(NOT64(lanes[6]), lanes[7])), XOR64(lanes[6], AND64(NOT64(lanes[7]), lanes[8])), XOR64(lanes[7], AND64(NOT64(lanes[8]), lanes[9]))
+ lanes[10], lanes[11], lanes[12], lanes[13], lanes[14] = XOR64(lanes[11], AND64(NOT64(lanes[12]), lanes[13])), XOR64(lanes[12], AND64(NOT64(lanes[13]), lanes[14])), XOR64(lanes[13], AND64(NOT64(lanes[14]), lanes[10])), XOR64(lanes[14], AND64(NOT64(lanes[10]), lanes[11])), XOR64(lanes[10], AND64(NOT64(lanes[11]), lanes[12]))
+ lanes[15], lanes[16], lanes[17], lanes[18], lanes[19] = XOR64(lanes[19], AND64(NOT64(lanes[15]), lanes[16])), XOR64(lanes[15], AND64(NOT64(lanes[16]), lanes[17])), XOR64(lanes[16], AND64(NOT64(lanes[17]), lanes[18])), XOR64(lanes[17], AND64(NOT64(lanes[18]), lanes[19])), XOR64(lanes[18], AND64(NOT64(lanes[19]), lanes[15]))
+ lanes[20], lanes[21], lanes[22], lanes[23], lanes[24] = XOR64(lanes[22], AND64(NOT64(lanes[23]), lanes[24])), XOR64(lanes[23], AND64(NOT64(lanes[24]), lanes[20])), XOR64(lanes[24], AND64(NOT64(lanes[20]), lanes[21])), XOR64(lanes[20], AND64(NOT64(lanes[21]), lanes[22])), XOR64(lanes[21], AND64(NOT64(lanes[22]), lanes[23]))
+ end
+ end
+ end
+
+
+ local A5_long = 0xA5A5A5A5 * int64(2^32 + 1) -- It's impossible to use constant 0xA5A5A5A5A5A5A5A5LL because it will raise syntax error on other Lua versions
+
+ function XORA5(long, long2)
+ return XOR64(long, long2 or A5_long)
+ end
+
+
+ -- SHA512 implementation for "LuaJIT 2.1 + FFI" branch
+
+ function sha512_feed_128(H, _, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ local W, K = common_W_FFI_int64, sha2_K_lo
+ for pos = offs, offs + size - 1, 128 do
+ for j = 0, 15 do
+ pos = pos + 8
+ local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos) -- slow, but doesn't depend on endianness
+ W[j] = OR64(OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) * int64(2^32), uint32(int32(OR(SHL(e, 24), SHL(f, 16), SHL(g, 8), h))))
+ end
+ for j = 16, 79 do
+ local a, b = W[j-15], W[j-2]
+ W[j] = XOR64(ROR64(a, 1), ROR64(a, 8), SHR64(a, 7)) + XOR64(ROR64(b, 19), ROL64(b, 3), SHR64(b, 6)) + W[j-7] + W[j-16]
+ end
+ local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for j = 0, 79, 8 do
+ local z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+1] + W[j]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z
+ z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+2] + W[j+1]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z
+ z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+3] + W[j+2]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z
+ z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+4] + W[j+3]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z
+ z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+5] + W[j+4]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z
+ z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+6] + W[j+5]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z
+ z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+7] + W[j+6]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z
+ z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+8] + W[j+7]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z
+ end
+ H[1] = a + H[1]
+ H[2] = b + H[2]
+ H[3] = c + H[3]
+ H[4] = d + H[4]
+ H[5] = e + H[5]
+ H[6] = f + H[6]
+ H[7] = g + H[7]
+ H[8] = h + H[8]
+ end
+ end
+
+ else -- LuaJIT 2.0 doesn't support 64-bit bitwise operations
+
+ local U = ffi.new("union{int64_t i64; struct{int32_t "..(ffi.abi("le") and "lo, hi" or "hi, lo")..";} i32;}[3]")
+ -- this array of unions is used for fast splitting int64 into int32_high and int32_low
+
+ -- "xorrific" 64-bit functions :-)
+ -- int64 input is splitted into two int32 parts, some bitwise 32-bit operations are performed, finally the result is converted to int64
+ -- these functions are needed because bit.* functions in LuaJIT 2.0 don't work with int64_t
+
+ local function XORROR64_1(a)
+ -- return XOR64(ROR64(a, 1), ROR64(a, 8), SHR64(a, 7))
+ U[0].i64 = a
+ local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi
+ local t_lo = XOR(SHR(a_lo, 1), SHL(a_hi, 31), SHR(a_lo, 8), SHL(a_hi, 24), SHR(a_lo, 7), SHL(a_hi, 25))
+ local t_hi = XOR(SHR(a_hi, 1), SHL(a_lo, 31), SHR(a_hi, 8), SHL(a_lo, 24), SHR(a_hi, 7))
+ return t_hi * int64(2^32) + uint32(int32(t_lo))
+ end
+
+ local function XORROR64_2(b)
+ -- return XOR64(ROR64(b, 19), ROL64(b, 3), SHR64(b, 6))
+ U[0].i64 = b
+ local b_lo, b_hi = U[0].i32.lo, U[0].i32.hi
+ local u_lo = XOR(SHR(b_lo, 19), SHL(b_hi, 13), SHL(b_lo, 3), SHR(b_hi, 29), SHR(b_lo, 6), SHL(b_hi, 26))
+ local u_hi = XOR(SHR(b_hi, 19), SHL(b_lo, 13), SHL(b_hi, 3), SHR(b_lo, 29), SHR(b_hi, 6))
+ return u_hi * int64(2^32) + uint32(int32(u_lo))
+ end
+
+ local function XORROR64_3(e)
+ -- return XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23))
+ U[0].i64 = e
+ local e_lo, e_hi = U[0].i32.lo, U[0].i32.hi
+ local u_lo = XOR(SHR(e_lo, 14), SHL(e_hi, 18), SHR(e_lo, 18), SHL(e_hi, 14), SHL(e_lo, 23), SHR(e_hi, 9))
+ local u_hi = XOR(SHR(e_hi, 14), SHL(e_lo, 18), SHR(e_hi, 18), SHL(e_lo, 14), SHL(e_hi, 23), SHR(e_lo, 9))
+ return u_hi * int64(2^32) + uint32(int32(u_lo))
+ end
+
+ local function XORROR64_6(a)
+ -- return XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30))
+ U[0].i64 = a
+ local b_lo, b_hi = U[0].i32.lo, U[0].i32.hi
+ local u_lo = XOR(SHR(b_lo, 28), SHL(b_hi, 4), SHL(b_lo, 30), SHR(b_hi, 2), SHL(b_lo, 25), SHR(b_hi, 7))
+ local u_hi = XOR(SHR(b_hi, 28), SHL(b_lo, 4), SHL(b_hi, 30), SHR(b_lo, 2), SHL(b_hi, 25), SHR(b_lo, 7))
+ return u_hi * int64(2^32) + uint32(int32(u_lo))
+ end
+
+ local function XORROR64_4(e, f, g)
+ -- return XOR64(g, AND64(e, XOR64(f, g)))
+ U[0].i64 = f
+ U[1].i64 = g
+ U[2].i64 = e
+ local f_lo, f_hi = U[0].i32.lo, U[0].i32.hi
+ local g_lo, g_hi = U[1].i32.lo, U[1].i32.hi
+ local e_lo, e_hi = U[2].i32.lo, U[2].i32.hi
+ local result_lo = XOR(g_lo, AND(e_lo, XOR(f_lo, g_lo)))
+ local result_hi = XOR(g_hi, AND(e_hi, XOR(f_hi, g_hi)))
+ return result_hi * int64(2^32) + uint32(int32(result_lo))
+ end
+
+ local function XORROR64_5(a, b, c)
+ -- return XOR64(AND64(XOR64(a, b), c), AND64(a, b))
+ U[0].i64 = a
+ U[1].i64 = b
+ U[2].i64 = c
+ local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi
+ local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi
+ local c_lo, c_hi = U[2].i32.lo, U[2].i32.hi
+ local result_lo = XOR(AND(XOR(a_lo, b_lo), c_lo), AND(a_lo, b_lo))
+ local result_hi = XOR(AND(XOR(a_hi, b_hi), c_hi), AND(a_hi, b_hi))
+ return result_hi * int64(2^32) + uint32(int32(result_lo))
+ end
+
+ local function XORROR64_7(a, b, m)
+ -- return ROR64(XOR64(a, b), m), m = 1..31
+ U[0].i64 = a
+ U[1].i64 = b
+ local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi
+ local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi
+ local c_lo, c_hi = XOR(a_lo, b_lo), XOR(a_hi, b_hi)
+ local t_lo = XOR(SHR(c_lo, m), SHL(c_hi, -m))
+ local t_hi = XOR(SHR(c_hi, m), SHL(c_lo, -m))
+ return t_hi * int64(2^32) + uint32(int32(t_lo))
+ end
+
+ local function XORROR64_8(a, b)
+ -- return ROL64(XOR64(a, b), 1)
+ U[0].i64 = a
+ U[1].i64 = b
+ local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi
+ local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi
+ local c_lo, c_hi = XOR(a_lo, b_lo), XOR(a_hi, b_hi)
+ local t_lo = XOR(SHL(c_lo, 1), SHR(c_hi, 31))
+ local t_hi = XOR(SHL(c_hi, 1), SHR(c_lo, 31))
+ return t_hi * int64(2^32) + uint32(int32(t_lo))
+ end
+
+ local function XORROR64_9(a, b)
+ -- return ROR64(XOR64(a, b), 32)
+ U[0].i64 = a
+ U[1].i64 = b
+ local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi
+ local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi
+ local t_hi, t_lo = XOR(a_lo, b_lo), XOR(a_hi, b_hi)
+ return t_hi * int64(2^32) + uint32(int32(t_lo))
+ end
+
+ local function XOR64(a, b)
+ -- return XOR64(a, b)
+ U[0].i64 = a
+ U[1].i64 = b
+ local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi
+ local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi
+ local t_lo, t_hi = XOR(a_lo, b_lo), XOR(a_hi, b_hi)
+ return t_hi * int64(2^32) + uint32(int32(t_lo))
+ end
+
+ local function XORROR64_11(a, b, c)
+ -- return XOR64(a, b, c)
+ U[0].i64 = a
+ U[1].i64 = b
+ U[2].i64 = c
+ local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi
+ local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi
+ local c_lo, c_hi = U[2].i32.lo, U[2].i32.hi
+ local t_lo, t_hi = XOR(a_lo, b_lo, c_lo), XOR(a_hi, b_hi, c_hi)
+ return t_hi * int64(2^32) + uint32(int32(t_lo))
+ end
+
+ function XORA5(long, long2)
+ -- return XOR64(long, long2 or 0xA5A5A5A5A5A5A5A5)
+ U[0].i64 = long
+ local lo32, hi32 = U[0].i32.lo, U[0].i32.hi
+ local long2_lo, long2_hi = 0xA5A5A5A5, 0xA5A5A5A5
+ if long2 then
+ U[1].i64 = long2
+ long2_lo, long2_hi = U[1].i32.lo, U[1].i32.hi
+ end
+ lo32 = XOR(lo32, long2_lo)
+ hi32 = XOR(hi32, long2_hi)
+ return hi32 * int64(2^32) + uint32(int32(lo32))
+ end
+
+ function HEX64(long)
+ U[0].i64 = long
+ return HEX(U[0].i32.hi)..HEX(U[0].i32.lo)
+ end
+
+
+ -- SHA512 implementation for "LuaJIT 2.0 + FFI" branch
+
+ function sha512_feed_128(H, _, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ local W, K = common_W_FFI_int64, sha2_K_lo
+ for pos = offs, offs + size - 1, 128 do
+ for j = 0, 15 do
+ pos = pos + 8
+ local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos) -- slow, but doesn't depend on endianness
+ W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) * int64(2^32) + uint32(int32(OR(SHL(e, 24), SHL(f, 16), SHL(g, 8), h)))
+ end
+ for j = 16, 79 do
+ W[j] = XORROR64_1(W[j-15]) + XORROR64_2(W[j-2]) + W[j-7] + W[j-16]
+ end
+ local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for j = 0, 79, 8 do
+ local z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+1] + W[j]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z
+ z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+2] + W[j+1]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z
+ z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+3] + W[j+2]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z
+ z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+4] + W[j+3]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z
+ z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+5] + W[j+4]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z
+ z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+6] + W[j+5]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z
+ z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+7] + W[j+6]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z
+ z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+8] + W[j+7]
+ h, g, f, e = g, f, e, z + d
+ d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z
+ end
+ H[1] = a + H[1]
+ H[2] = b + H[2]
+ H[3] = c + H[3]
+ H[4] = d + H[4]
+ H[5] = e + H[5]
+ H[6] = f + H[6]
+ H[7] = g + H[7]
+ H[8] = h + H[8]
+ end
+ end
+
+
+ -- BLAKE2b implementation for "LuaJIT 2.0 + FFI" branch
+
+ do
+ local v = ffi.new("int64_t[?]", 16)
+ local W = common_W_blake2b
+
+ local function G(a, b, c, d, k1, k2)
+ local va, vb, vc, vd = v[a], v[b], v[c], v[d]
+ va = W[k1] + (va + vb)
+ vd = XORROR64_9(vd, va)
+ vc = vc + vd
+ vb = XORROR64_7(vb, vc, 24)
+ va = W[k2] + (va + vb)
+ vd = XORROR64_7(vd, va, 16)
+ vc = vc + vd
+ vb = XORROR64_8(vb, vc)
+ v[a], v[b], v[c], v[d] = va, vb, vc, vd
+ end
+
+ function blake2b_feed_128(H, _, str, offs, size, bytes_compressed, last_block_size, is_last_node)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for pos = offs, offs + size - 1, 128 do
+ if str then
+ for j = 1, 16 do
+ pos = pos + 8
+ local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos)
+ W[j] = XOR64(OR(SHL(h, 24), SHL(g, 16), SHL(f, 8), e) * int64(2^32), uint32(int32(OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a))))
+ end
+ end
+ v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8
+ v[0x8], v[0x9], v[0xA], v[0xB], v[0xD], v[0xE], v[0xF] = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8]
+ bytes_compressed = bytes_compressed + (last_block_size or 128)
+ v[0xC] = XOR64(sha2_H_lo[5], bytes_compressed) -- t0 = low_8_bytes(bytes_compressed)
+ -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes
+ if last_block_size then -- flag f0
+ v[0xE] = -1 - v[0xE]
+ end
+ if is_last_node then -- flag f1
+ v[0xF] = -1 - v[0xF]
+ end
+ for j = 1, 12 do
+ local row = sigma[j]
+ G(0, 4, 8, 12, row[ 1], row[ 2])
+ G(1, 5, 9, 13, row[ 3], row[ 4])
+ G(2, 6, 10, 14, row[ 5], row[ 6])
+ G(3, 7, 11, 15, row[ 7], row[ 8])
+ G(0, 5, 10, 15, row[ 9], row[10])
+ G(1, 6, 11, 12, row[11], row[12])
+ G(2, 7, 8, 13, row[13], row[14])
+ G(3, 4, 9, 14, row[15], row[16])
+ end
+ h1 = XORROR64_11(h1, v[0x0], v[0x8])
+ h2 = XORROR64_11(h2, v[0x1], v[0x9])
+ h3 = XORROR64_11(h3, v[0x2], v[0xA])
+ h4 = XORROR64_11(h4, v[0x3], v[0xB])
+ h5 = XORROR64_11(h5, v[0x4], v[0xC])
+ h6 = XORROR64_11(h6, v[0x5], v[0xD])
+ h7 = XORROR64_11(h7, v[0x6], v[0xE])
+ h8 = XORROR64_11(h8, v[0x7], v[0xF])
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ return bytes_compressed
+ end
+
+ end
+
+ end
+
+
+ -- MD5 implementation for "LuaJIT with FFI" branch
+
+ function md5_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K = common_W_FFI_int32, md5_K
+ for pos = offs, offs + size - 1, 64 do
+ for j = 0, 15 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos) -- slow, but doesn't depend on endianness
+ W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)
+ end
+ local a, b, c, d = H[1], H[2], H[3], H[4]
+ for j = 0, 15, 4 do
+ a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+1] + W[j ] + a), 7) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+2] + W[j+1] + a), 12) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+3] + W[j+2] + a), 17) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+4] + W[j+3] + a), 22) + b)
+ end
+ for j = 16, 31, 4 do
+ local g = 5*j
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+1] + W[AND(g + 1, 15)] + a), 5) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+2] + W[AND(g + 6, 15)] + a), 9) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+3] + W[AND(g - 5, 15)] + a), 14) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+4] + W[AND(g , 15)] + a), 20) + b)
+ end
+ for j = 32, 47, 4 do
+ local g = 3*j
+ a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+1] + W[AND(g + 5, 15)] + a), 4) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+2] + W[AND(g + 8, 15)] + a), 11) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+3] + W[AND(g - 5, 15)] + a), 16) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+4] + W[AND(g - 2, 15)] + a), 23) + b)
+ end
+ for j = 48, 63, 4 do
+ local g = 7*j
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+1] + W[AND(g , 15)] + a), 6) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+2] + W[AND(g + 7, 15)] + a), 10) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+3] + W[AND(g - 2, 15)] + a), 15) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+4] + W[AND(g + 5, 15)] + a), 21) + b)
+ end
+ H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4])
+ end
+ end
+
+
+ -- SHA-1 implementation for "LuaJIT with FFI" branch
+
+ function sha1_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W = common_W_FFI_int32
+ for pos = offs, offs + size - 1, 64 do
+ for j = 0, 15 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos) -- slow, but doesn't depend on endianness
+ W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d)
+ end
+ for j = 16, 79 do
+ W[j] = ROL(XOR(W[j-3], W[j-8], W[j-14], W[j-16]), 1)
+ end
+ local a, b, c, d, e = H[1], H[2], H[3], H[4], H[5]
+ for j = 0, 19, 5 do
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j] + 0x5A827999 + e)) -- constant = floor(2^30 * sqrt(2))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+1] + 0x5A827999 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+2] + 0x5A827999 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+3] + 0x5A827999 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+4] + 0x5A827999 + e))
+ end
+ for j = 20, 39, 5 do
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0x6ED9EBA1 + e)) -- 2^30 * sqrt(3)
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0x6ED9EBA1 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0x6ED9EBA1 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0x6ED9EBA1 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0x6ED9EBA1 + e))
+ end
+ for j = 40, 59, 5 do
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j] + 0x8F1BBCDC + e)) -- 2^30 * sqrt(5)
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+1] + 0x8F1BBCDC + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+2] + 0x8F1BBCDC + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+3] + 0x8F1BBCDC + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+4] + 0x8F1BBCDC + e))
+ end
+ for j = 60, 79, 5 do
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0xCA62C1D6 + e)) -- 2^30 * sqrt(10)
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0xCA62C1D6 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0xCA62C1D6 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0xCA62C1D6 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0xCA62C1D6 + e))
+ end
+ H[1], H[2], H[3], H[4], H[5] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]), NORM(e + H[5])
+ end
+ end
+
+end
+
+
+if branch == "FFI" and not is_LuaJIT_21 or branch == "LJ" then
+
+ if branch == "FFI" then
+ local arr32_t = ffi.typeof"int32_t[?]"
+
+ function create_array_of_lanes()
+ return arr32_t(31) -- 25 + 5 + 1 (due to 1-based indexing)
+ end
+
+ end
+
+
+ -- SHA-3 implementation for "LuaJIT 2.0 + FFI" and "LuaJIT without FFI" branches
+
+ function keccak_feed(lanes_lo, lanes_hi, str, offs, size, block_size_in_bytes)
+ -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8
+ local RC_lo, RC_hi = sha3_RC_lo, sha3_RC_hi
+ local qwords_qty = SHR(block_size_in_bytes, 3)
+ for pos = offs, offs + size - 1, block_size_in_bytes do
+ for j = 1, qwords_qty do
+ local a, b, c, d = byte(str, pos + 1, pos + 4)
+ lanes_lo[j] = XOR(lanes_lo[j], OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a))
+ pos = pos + 8
+ a, b, c, d = byte(str, pos - 3, pos)
+ lanes_hi[j] = XOR(lanes_hi[j], OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a))
+ end
+ for round_idx = 1, 24 do
+ for j = 1, 5 do
+ lanes_lo[25 + j] = XOR(lanes_lo[j], lanes_lo[j + 5], lanes_lo[j + 10], lanes_lo[j + 15], lanes_lo[j + 20])
+ end
+ for j = 1, 5 do
+ lanes_hi[25 + j] = XOR(lanes_hi[j], lanes_hi[j + 5], lanes_hi[j + 10], lanes_hi[j + 15], lanes_hi[j + 20])
+ end
+ local D_lo = XOR(lanes_lo[26], SHL(lanes_lo[28], 1), SHR(lanes_hi[28], 31))
+ local D_hi = XOR(lanes_hi[26], SHL(lanes_hi[28], 1), SHR(lanes_lo[28], 31))
+ lanes_lo[2], lanes_hi[2], lanes_lo[7], lanes_hi[7], lanes_lo[12], lanes_hi[12], lanes_lo[17], lanes_hi[17] = XOR(SHR(XOR(D_lo, lanes_lo[7]), 20), SHL(XOR(D_hi, lanes_hi[7]), 12)), XOR(SHR(XOR(D_hi, lanes_hi[7]), 20), SHL(XOR(D_lo, lanes_lo[7]), 12)), XOR(SHR(XOR(D_lo, lanes_lo[17]), 19), SHL(XOR(D_hi, lanes_hi[17]), 13)), XOR(SHR(XOR(D_hi, lanes_hi[17]), 19), SHL(XOR(D_lo, lanes_lo[17]), 13)), XOR(SHL(XOR(D_lo, lanes_lo[2]), 1), SHR(XOR(D_hi, lanes_hi[2]), 31)), XOR(SHL(XOR(D_hi, lanes_hi[2]), 1), SHR(XOR(D_lo, lanes_lo[2]), 31)), XOR(SHL(XOR(D_lo, lanes_lo[12]), 10), SHR(XOR(D_hi, lanes_hi[12]), 22)), XOR(SHL(XOR(D_hi, lanes_hi[12]), 10), SHR(XOR(D_lo, lanes_lo[12]), 22))
+ local L, H = XOR(D_lo, lanes_lo[22]), XOR(D_hi, lanes_hi[22])
+ lanes_lo[22], lanes_hi[22] = XOR(SHL(L, 2), SHR(H, 30)), XOR(SHL(H, 2), SHR(L, 30))
+ D_lo = XOR(lanes_lo[27], SHL(lanes_lo[29], 1), SHR(lanes_hi[29], 31))
+ D_hi = XOR(lanes_hi[27], SHL(lanes_hi[29], 1), SHR(lanes_lo[29], 31))
+ lanes_lo[3], lanes_hi[3], lanes_lo[8], lanes_hi[8], lanes_lo[13], lanes_hi[13], lanes_lo[23], lanes_hi[23] = XOR(SHR(XOR(D_lo, lanes_lo[13]), 21), SHL(XOR(D_hi, lanes_hi[13]), 11)), XOR(SHR(XOR(D_hi, lanes_hi[13]), 21), SHL(XOR(D_lo, lanes_lo[13]), 11)), XOR(SHR(XOR(D_lo, lanes_lo[23]), 3), SHL(XOR(D_hi, lanes_hi[23]), 29)), XOR(SHR(XOR(D_hi, lanes_hi[23]), 3), SHL(XOR(D_lo, lanes_lo[23]), 29)), XOR(SHL(XOR(D_lo, lanes_lo[8]), 6), SHR(XOR(D_hi, lanes_hi[8]), 26)), XOR(SHL(XOR(D_hi, lanes_hi[8]), 6), SHR(XOR(D_lo, lanes_lo[8]), 26)), XOR(SHR(XOR(D_lo, lanes_lo[3]), 2), SHL(XOR(D_hi, lanes_hi[3]), 30)), XOR(SHR(XOR(D_hi, lanes_hi[3]), 2), SHL(XOR(D_lo, lanes_lo[3]), 30))
+ L, H = XOR(D_lo, lanes_lo[18]), XOR(D_hi, lanes_hi[18])
+ lanes_lo[18], lanes_hi[18] = XOR(SHL(L, 15), SHR(H, 17)), XOR(SHL(H, 15), SHR(L, 17))
+ D_lo = XOR(lanes_lo[28], SHL(lanes_lo[30], 1), SHR(lanes_hi[30], 31))
+ D_hi = XOR(lanes_hi[28], SHL(lanes_hi[30], 1), SHR(lanes_lo[30], 31))
+ lanes_lo[4], lanes_hi[4], lanes_lo[9], lanes_hi[9], lanes_lo[19], lanes_hi[19], lanes_lo[24], lanes_hi[24] = XOR(SHL(XOR(D_lo, lanes_lo[19]), 21), SHR(XOR(D_hi, lanes_hi[19]), 11)), XOR(SHL(XOR(D_hi, lanes_hi[19]), 21), SHR(XOR(D_lo, lanes_lo[19]), 11)), XOR(SHL(XOR(D_lo, lanes_lo[4]), 28), SHR(XOR(D_hi, lanes_hi[4]), 4)), XOR(SHL(XOR(D_hi, lanes_hi[4]), 28), SHR(XOR(D_lo, lanes_lo[4]), 4)), XOR(SHR(XOR(D_lo, lanes_lo[24]), 8), SHL(XOR(D_hi, lanes_hi[24]), 24)), XOR(SHR(XOR(D_hi, lanes_hi[24]), 8), SHL(XOR(D_lo, lanes_lo[24]), 24)), XOR(SHR(XOR(D_lo, lanes_lo[9]), 9), SHL(XOR(D_hi, lanes_hi[9]), 23)), XOR(SHR(XOR(D_hi, lanes_hi[9]), 9), SHL(XOR(D_lo, lanes_lo[9]), 23))
+ L, H = XOR(D_lo, lanes_lo[14]), XOR(D_hi, lanes_hi[14])
+ lanes_lo[14], lanes_hi[14] = XOR(SHL(L, 25), SHR(H, 7)), XOR(SHL(H, 25), SHR(L, 7))
+ D_lo = XOR(lanes_lo[29], SHL(lanes_lo[26], 1), SHR(lanes_hi[26], 31))
+ D_hi = XOR(lanes_hi[29], SHL(lanes_hi[26], 1), SHR(lanes_lo[26], 31))
+ lanes_lo[5], lanes_hi[5], lanes_lo[15], lanes_hi[15], lanes_lo[20], lanes_hi[20], lanes_lo[25], lanes_hi[25] = XOR(SHL(XOR(D_lo, lanes_lo[25]), 14), SHR(XOR(D_hi, lanes_hi[25]), 18)), XOR(SHL(XOR(D_hi, lanes_hi[25]), 14), SHR(XOR(D_lo, lanes_lo[25]), 18)), XOR(SHL(XOR(D_lo, lanes_lo[20]), 8), SHR(XOR(D_hi, lanes_hi[20]), 24)), XOR(SHL(XOR(D_hi, lanes_hi[20]), 8), SHR(XOR(D_lo, lanes_lo[20]), 24)), XOR(SHL(XOR(D_lo, lanes_lo[5]), 27), SHR(XOR(D_hi, lanes_hi[5]), 5)), XOR(SHL(XOR(D_hi, lanes_hi[5]), 27), SHR(XOR(D_lo, lanes_lo[5]), 5)), XOR(SHR(XOR(D_lo, lanes_lo[15]), 25), SHL(XOR(D_hi, lanes_hi[15]), 7)), XOR(SHR(XOR(D_hi, lanes_hi[15]), 25), SHL(XOR(D_lo, lanes_lo[15]), 7))
+ L, H = XOR(D_lo, lanes_lo[10]), XOR(D_hi, lanes_hi[10])
+ lanes_lo[10], lanes_hi[10] = XOR(SHL(L, 20), SHR(H, 12)), XOR(SHL(H, 20), SHR(L, 12))
+ D_lo = XOR(lanes_lo[30], SHL(lanes_lo[27], 1), SHR(lanes_hi[27], 31))
+ D_hi = XOR(lanes_hi[30], SHL(lanes_hi[27], 1), SHR(lanes_lo[27], 31))
+ lanes_lo[6], lanes_hi[6], lanes_lo[11], lanes_hi[11], lanes_lo[16], lanes_hi[16], lanes_lo[21], lanes_hi[21] = XOR(SHL(XOR(D_lo, lanes_lo[11]), 3), SHR(XOR(D_hi, lanes_hi[11]), 29)), XOR(SHL(XOR(D_hi, lanes_hi[11]), 3), SHR(XOR(D_lo, lanes_lo[11]), 29)), XOR(SHL(XOR(D_lo, lanes_lo[21]), 18), SHR(XOR(D_hi, lanes_hi[21]), 14)), XOR(SHL(XOR(D_hi, lanes_hi[21]), 18), SHR(XOR(D_lo, lanes_lo[21]), 14)), XOR(SHR(XOR(D_lo, lanes_lo[6]), 28), SHL(XOR(D_hi, lanes_hi[6]), 4)), XOR(SHR(XOR(D_hi, lanes_hi[6]), 28), SHL(XOR(D_lo, lanes_lo[6]), 4)), XOR(SHR(XOR(D_lo, lanes_lo[16]), 23), SHL(XOR(D_hi, lanes_hi[16]), 9)), XOR(SHR(XOR(D_hi, lanes_hi[16]), 23), SHL(XOR(D_lo, lanes_lo[16]), 9))
+ lanes_lo[1], lanes_hi[1] = XOR(D_lo, lanes_lo[1]), XOR(D_hi, lanes_hi[1])
+ lanes_lo[1], lanes_lo[2], lanes_lo[3], lanes_lo[4], lanes_lo[5] = XOR(lanes_lo[1], AND(NOT(lanes_lo[2]), lanes_lo[3]), RC_lo[round_idx]), XOR(lanes_lo[2], AND(NOT(lanes_lo[3]), lanes_lo[4])), XOR(lanes_lo[3], AND(NOT(lanes_lo[4]), lanes_lo[5])), XOR(lanes_lo[4], AND(NOT(lanes_lo[5]), lanes_lo[1])), XOR(lanes_lo[5], AND(NOT(lanes_lo[1]), lanes_lo[2]))
+ lanes_lo[6], lanes_lo[7], lanes_lo[8], lanes_lo[9], lanes_lo[10] = XOR(lanes_lo[9], AND(NOT(lanes_lo[10]), lanes_lo[6])), XOR(lanes_lo[10], AND(NOT(lanes_lo[6]), lanes_lo[7])), XOR(lanes_lo[6], AND(NOT(lanes_lo[7]), lanes_lo[8])), XOR(lanes_lo[7], AND(NOT(lanes_lo[8]), lanes_lo[9])), XOR(lanes_lo[8], AND(NOT(lanes_lo[9]), lanes_lo[10]))
+ lanes_lo[11], lanes_lo[12], lanes_lo[13], lanes_lo[14], lanes_lo[15] = XOR(lanes_lo[12], AND(NOT(lanes_lo[13]), lanes_lo[14])), XOR(lanes_lo[13], AND(NOT(lanes_lo[14]), lanes_lo[15])), XOR(lanes_lo[14], AND(NOT(lanes_lo[15]), lanes_lo[11])), XOR(lanes_lo[15], AND(NOT(lanes_lo[11]), lanes_lo[12])), XOR(lanes_lo[11], AND(NOT(lanes_lo[12]), lanes_lo[13]))
+ lanes_lo[16], lanes_lo[17], lanes_lo[18], lanes_lo[19], lanes_lo[20] = XOR(lanes_lo[20], AND(NOT(lanes_lo[16]), lanes_lo[17])), XOR(lanes_lo[16], AND(NOT(lanes_lo[17]), lanes_lo[18])), XOR(lanes_lo[17], AND(NOT(lanes_lo[18]), lanes_lo[19])), XOR(lanes_lo[18], AND(NOT(lanes_lo[19]), lanes_lo[20])), XOR(lanes_lo[19], AND(NOT(lanes_lo[20]), lanes_lo[16]))
+ lanes_lo[21], lanes_lo[22], lanes_lo[23], lanes_lo[24], lanes_lo[25] = XOR(lanes_lo[23], AND(NOT(lanes_lo[24]), lanes_lo[25])), XOR(lanes_lo[24], AND(NOT(lanes_lo[25]), lanes_lo[21])), XOR(lanes_lo[25], AND(NOT(lanes_lo[21]), lanes_lo[22])), XOR(lanes_lo[21], AND(NOT(lanes_lo[22]), lanes_lo[23])), XOR(lanes_lo[22], AND(NOT(lanes_lo[23]), lanes_lo[24]))
+ lanes_hi[1], lanes_hi[2], lanes_hi[3], lanes_hi[4], lanes_hi[5] = XOR(lanes_hi[1], AND(NOT(lanes_hi[2]), lanes_hi[3]), RC_hi[round_idx]), XOR(lanes_hi[2], AND(NOT(lanes_hi[3]), lanes_hi[4])), XOR(lanes_hi[3], AND(NOT(lanes_hi[4]), lanes_hi[5])), XOR(lanes_hi[4], AND(NOT(lanes_hi[5]), lanes_hi[1])), XOR(lanes_hi[5], AND(NOT(lanes_hi[1]), lanes_hi[2]))
+ lanes_hi[6], lanes_hi[7], lanes_hi[8], lanes_hi[9], lanes_hi[10] = XOR(lanes_hi[9], AND(NOT(lanes_hi[10]), lanes_hi[6])), XOR(lanes_hi[10], AND(NOT(lanes_hi[6]), lanes_hi[7])), XOR(lanes_hi[6], AND(NOT(lanes_hi[7]), lanes_hi[8])), XOR(lanes_hi[7], AND(NOT(lanes_hi[8]), lanes_hi[9])), XOR(lanes_hi[8], AND(NOT(lanes_hi[9]), lanes_hi[10]))
+ lanes_hi[11], lanes_hi[12], lanes_hi[13], lanes_hi[14], lanes_hi[15] = XOR(lanes_hi[12], AND(NOT(lanes_hi[13]), lanes_hi[14])), XOR(lanes_hi[13], AND(NOT(lanes_hi[14]), lanes_hi[15])), XOR(lanes_hi[14], AND(NOT(lanes_hi[15]), lanes_hi[11])), XOR(lanes_hi[15], AND(NOT(lanes_hi[11]), lanes_hi[12])), XOR(lanes_hi[11], AND(NOT(lanes_hi[12]), lanes_hi[13]))
+ lanes_hi[16], lanes_hi[17], lanes_hi[18], lanes_hi[19], lanes_hi[20] = XOR(lanes_hi[20], AND(NOT(lanes_hi[16]), lanes_hi[17])), XOR(lanes_hi[16], AND(NOT(lanes_hi[17]), lanes_hi[18])), XOR(lanes_hi[17], AND(NOT(lanes_hi[18]), lanes_hi[19])), XOR(lanes_hi[18], AND(NOT(lanes_hi[19]), lanes_hi[20])), XOR(lanes_hi[19], AND(NOT(lanes_hi[20]), lanes_hi[16]))
+ lanes_hi[21], lanes_hi[22], lanes_hi[23], lanes_hi[24], lanes_hi[25] = XOR(lanes_hi[23], AND(NOT(lanes_hi[24]), lanes_hi[25])), XOR(lanes_hi[24], AND(NOT(lanes_hi[25]), lanes_hi[21])), XOR(lanes_hi[25], AND(NOT(lanes_hi[21]), lanes_hi[22])), XOR(lanes_hi[21], AND(NOT(lanes_hi[22]), lanes_hi[23])), XOR(lanes_hi[22], AND(NOT(lanes_hi[23]), lanes_hi[24]))
+ end
+ end
+ end
+
+end
+
+
+if branch == "LJ" then
+
+
+ -- SHA256 implementation for "LuaJIT without FFI" branch
+
+ function sha256_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K = common_W, sha2_K_hi
+ for pos = offs, offs + size - 1, 64 do
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d)
+ end
+ for j = 17, 64 do
+ local a, b = W[j-15], W[j-2]
+ W[j] = NORM( NORM( XOR(ROR(a, 7), ROL(a, 14), SHR(a, 3)) + XOR(ROL(b, 15), ROL(b, 13), SHR(b, 10)) ) + NORM( W[j-7] + W[j-16] ) )
+ end
+ local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for j = 1, 64, 8 do -- Thanks to Peter Cawley for this workaround (unroll the loop to avoid "PHI shuffling too complex" due to PHIs overlap)
+ local z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j] + W[j] + h) )
+ h, g, f, e = g, f, e, NORM(d + z)
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+1] + W[j+1] + h) )
+ h, g, f, e = g, f, e, NORM(d + z)
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+2] + W[j+2] + h) )
+ h, g, f, e = g, f, e, NORM(d + z)
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+3] + W[j+3] + h) )
+ h, g, f, e = g, f, e, NORM(d + z)
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+4] + W[j+4] + h) )
+ h, g, f, e = g, f, e, NORM(d + z)
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+5] + W[j+5] + h) )
+ h, g, f, e = g, f, e, NORM(d + z)
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+6] + W[j+6] + h) )
+ h, g, f, e = g, f, e, NORM(d + z)
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+7] + W[j+7] + h) )
+ h, g, f, e = g, f, e, NORM(d + z)
+ d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z )
+ end
+ H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4])
+ H[5], H[6], H[7], H[8] = NORM(e + H[5]), NORM(f + H[6]), NORM(g + H[7]), NORM(h + H[8])
+ end
+ end
+
+ local function ADD64_4(a_lo, a_hi, b_lo, b_hi, c_lo, c_hi, d_lo, d_hi)
+ local sum_lo = a_lo % 2^32 + b_lo % 2^32 + c_lo % 2^32 + d_lo % 2^32
+ local sum_hi = a_hi + b_hi + c_hi + d_hi
+ local result_lo = NORM( sum_lo )
+ local result_hi = NORM( sum_hi + floor(sum_lo / 2^32) )
+ return result_lo, result_hi
+ end
+
+ if LuaJIT_arch == "x86" then -- Special trick is required to avoid "PHI shuffling too complex" on x86 platform
+
+
+ -- SHA512 implementation for "LuaJIT x86 without FFI" branch
+
+ function sha512_feed_128(H_lo, H_hi, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k]
+ local W, K_lo, K_hi = common_W, sha2_K_lo, sha2_K_hi
+ for pos = offs, offs + size - 1, 128 do
+ for j = 1, 16*2 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d)
+ end
+ for jj = 17*2, 80*2, 2 do
+ local a_lo, a_hi = W[jj-30], W[jj-31]
+ local t_lo = XOR(OR(SHR(a_lo, 1), SHL(a_hi, 31)), OR(SHR(a_lo, 8), SHL(a_hi, 24)), OR(SHR(a_lo, 7), SHL(a_hi, 25)))
+ local t_hi = XOR(OR(SHR(a_hi, 1), SHL(a_lo, 31)), OR(SHR(a_hi, 8), SHL(a_lo, 24)), SHR(a_hi, 7))
+ local b_lo, b_hi = W[jj-4], W[jj-5]
+ local u_lo = XOR(OR(SHR(b_lo, 19), SHL(b_hi, 13)), OR(SHL(b_lo, 3), SHR(b_hi, 29)), OR(SHR(b_lo, 6), SHL(b_hi, 26)))
+ local u_hi = XOR(OR(SHR(b_hi, 19), SHL(b_lo, 13)), OR(SHL(b_hi, 3), SHR(b_lo, 29)), SHR(b_hi, 6))
+ W[jj], W[jj-1] = ADD64_4(t_lo, t_hi, u_lo, u_hi, W[jj-14], W[jj-15], W[jj-32], W[jj-33])
+ end
+ local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8]
+ local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8]
+ local zero = 0
+ for j = 1, 80 do
+ local t_lo = XOR(g_lo, AND(e_lo, XOR(f_lo, g_lo)))
+ local t_hi = XOR(g_hi, AND(e_hi, XOR(f_hi, g_hi)))
+ local u_lo = XOR(OR(SHR(e_lo, 14), SHL(e_hi, 18)), OR(SHR(e_lo, 18), SHL(e_hi, 14)), OR(SHL(e_lo, 23), SHR(e_hi, 9)))
+ local u_hi = XOR(OR(SHR(e_hi, 14), SHL(e_lo, 18)), OR(SHR(e_hi, 18), SHL(e_lo, 14)), OR(SHL(e_hi, 23), SHR(e_lo, 9)))
+ local sum_lo = u_lo % 2^32 + t_lo % 2^32 + h_lo % 2^32 + K_lo[j] + W[2*j] % 2^32
+ local z_lo, z_hi = NORM( sum_lo ), NORM( u_hi + t_hi + h_hi + K_hi[j] + W[2*j-1] + floor(sum_lo / 2^32) )
+ zero = zero + zero -- this thick is needed to avoid "PHI shuffling too complex" due to PHIs overlap
+ h_lo, h_hi, g_lo, g_hi, f_lo, f_hi = OR(zero, g_lo), OR(zero, g_hi), OR(zero, f_lo), OR(zero, f_hi), OR(zero, e_lo), OR(zero, e_hi)
+ local sum_lo = z_lo % 2^32 + d_lo % 2^32
+ e_lo, e_hi = NORM( sum_lo ), NORM( z_hi + d_hi + floor(sum_lo / 2^32) )
+ d_lo, d_hi, c_lo, c_hi, b_lo, b_hi = OR(zero, c_lo), OR(zero, c_hi), OR(zero, b_lo), OR(zero, b_hi), OR(zero, a_lo), OR(zero, a_hi)
+ u_lo = XOR(OR(SHR(b_lo, 28), SHL(b_hi, 4)), OR(SHL(b_lo, 30), SHR(b_hi, 2)), OR(SHL(b_lo, 25), SHR(b_hi, 7)))
+ u_hi = XOR(OR(SHR(b_hi, 28), SHL(b_lo, 4)), OR(SHL(b_hi, 30), SHR(b_lo, 2)), OR(SHL(b_hi, 25), SHR(b_lo, 7)))
+ t_lo = OR(AND(d_lo, c_lo), AND(b_lo, XOR(d_lo, c_lo)))
+ t_hi = OR(AND(d_hi, c_hi), AND(b_hi, XOR(d_hi, c_hi)))
+ local sum_lo = z_lo % 2^32 + t_lo % 2^32 + u_lo % 2^32
+ a_lo, a_hi = NORM( sum_lo ), NORM( z_hi + t_hi + u_hi + floor(sum_lo / 2^32) )
+ end
+ H_lo[1], H_hi[1] = ADD64_4(H_lo[1], H_hi[1], a_lo, a_hi, 0, 0, 0, 0)
+ H_lo[2], H_hi[2] = ADD64_4(H_lo[2], H_hi[2], b_lo, b_hi, 0, 0, 0, 0)
+ H_lo[3], H_hi[3] = ADD64_4(H_lo[3], H_hi[3], c_lo, c_hi, 0, 0, 0, 0)
+ H_lo[4], H_hi[4] = ADD64_4(H_lo[4], H_hi[4], d_lo, d_hi, 0, 0, 0, 0)
+ H_lo[5], H_hi[5] = ADD64_4(H_lo[5], H_hi[5], e_lo, e_hi, 0, 0, 0, 0)
+ H_lo[6], H_hi[6] = ADD64_4(H_lo[6], H_hi[6], f_lo, f_hi, 0, 0, 0, 0)
+ H_lo[7], H_hi[7] = ADD64_4(H_lo[7], H_hi[7], g_lo, g_hi, 0, 0, 0, 0)
+ H_lo[8], H_hi[8] = ADD64_4(H_lo[8], H_hi[8], h_lo, h_hi, 0, 0, 0, 0)
+ end
+ end
+
+ else -- all platforms except x86
+
+
+ -- SHA512 implementation for "LuaJIT non-x86 without FFI" branch
+
+ function sha512_feed_128(H_lo, H_hi, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k]
+ local W, K_lo, K_hi = common_W, sha2_K_lo, sha2_K_hi
+ for pos = offs, offs + size - 1, 128 do
+ for j = 1, 16*2 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d)
+ end
+ for jj = 17*2, 80*2, 2 do
+ local a_lo, a_hi = W[jj-30], W[jj-31]
+ local t_lo = XOR(OR(SHR(a_lo, 1), SHL(a_hi, 31)), OR(SHR(a_lo, 8), SHL(a_hi, 24)), OR(SHR(a_lo, 7), SHL(a_hi, 25)))
+ local t_hi = XOR(OR(SHR(a_hi, 1), SHL(a_lo, 31)), OR(SHR(a_hi, 8), SHL(a_lo, 24)), SHR(a_hi, 7))
+ local b_lo, b_hi = W[jj-4], W[jj-5]
+ local u_lo = XOR(OR(SHR(b_lo, 19), SHL(b_hi, 13)), OR(SHL(b_lo, 3), SHR(b_hi, 29)), OR(SHR(b_lo, 6), SHL(b_hi, 26)))
+ local u_hi = XOR(OR(SHR(b_hi, 19), SHL(b_lo, 13)), OR(SHL(b_hi, 3), SHR(b_lo, 29)), SHR(b_hi, 6))
+ W[jj], W[jj-1] = ADD64_4(t_lo, t_hi, u_lo, u_hi, W[jj-14], W[jj-15], W[jj-32], W[jj-33])
+ end
+ local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8]
+ local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8]
+ for j = 1, 80 do
+ local t_lo = XOR(g_lo, AND(e_lo, XOR(f_lo, g_lo)))
+ local t_hi = XOR(g_hi, AND(e_hi, XOR(f_hi, g_hi)))
+ local u_lo = XOR(OR(SHR(e_lo, 14), SHL(e_hi, 18)), OR(SHR(e_lo, 18), SHL(e_hi, 14)), OR(SHL(e_lo, 23), SHR(e_hi, 9)))
+ local u_hi = XOR(OR(SHR(e_hi, 14), SHL(e_lo, 18)), OR(SHR(e_hi, 18), SHL(e_lo, 14)), OR(SHL(e_hi, 23), SHR(e_lo, 9)))
+ local sum_lo = u_lo % 2^32 + t_lo % 2^32 + h_lo % 2^32 + K_lo[j] + W[2*j] % 2^32
+ local z_lo, z_hi = NORM( sum_lo ), NORM( u_hi + t_hi + h_hi + K_hi[j] + W[2*j-1] + floor(sum_lo / 2^32) )
+ h_lo, h_hi, g_lo, g_hi, f_lo, f_hi = g_lo, g_hi, f_lo, f_hi, e_lo, e_hi
+ local sum_lo = z_lo % 2^32 + d_lo % 2^32
+ e_lo, e_hi = NORM( sum_lo ), NORM( z_hi + d_hi + floor(sum_lo / 2^32) )
+ d_lo, d_hi, c_lo, c_hi, b_lo, b_hi = c_lo, c_hi, b_lo, b_hi, a_lo, a_hi
+ u_lo = XOR(OR(SHR(b_lo, 28), SHL(b_hi, 4)), OR(SHL(b_lo, 30), SHR(b_hi, 2)), OR(SHL(b_lo, 25), SHR(b_hi, 7)))
+ u_hi = XOR(OR(SHR(b_hi, 28), SHL(b_lo, 4)), OR(SHL(b_hi, 30), SHR(b_lo, 2)), OR(SHL(b_hi, 25), SHR(b_lo, 7)))
+ t_lo = OR(AND(d_lo, c_lo), AND(b_lo, XOR(d_lo, c_lo)))
+ t_hi = OR(AND(d_hi, c_hi), AND(b_hi, XOR(d_hi, c_hi)))
+ local sum_lo = z_lo % 2^32 + u_lo % 2^32 + t_lo % 2^32
+ a_lo, a_hi = NORM( sum_lo ), NORM( z_hi + u_hi + t_hi + floor(sum_lo / 2^32) )
+ end
+ H_lo[1], H_hi[1] = ADD64_4(H_lo[1], H_hi[1], a_lo, a_hi, 0, 0, 0, 0)
+ H_lo[2], H_hi[2] = ADD64_4(H_lo[2], H_hi[2], b_lo, b_hi, 0, 0, 0, 0)
+ H_lo[3], H_hi[3] = ADD64_4(H_lo[3], H_hi[3], c_lo, c_hi, 0, 0, 0, 0)
+ H_lo[4], H_hi[4] = ADD64_4(H_lo[4], H_hi[4], d_lo, d_hi, 0, 0, 0, 0)
+ H_lo[5], H_hi[5] = ADD64_4(H_lo[5], H_hi[5], e_lo, e_hi, 0, 0, 0, 0)
+ H_lo[6], H_hi[6] = ADD64_4(H_lo[6], H_hi[6], f_lo, f_hi, 0, 0, 0, 0)
+ H_lo[7], H_hi[7] = ADD64_4(H_lo[7], H_hi[7], g_lo, g_hi, 0, 0, 0, 0)
+ H_lo[8], H_hi[8] = ADD64_4(H_lo[8], H_hi[8], h_lo, h_hi, 0, 0, 0, 0)
+ end
+ end
+
+ end
+
+
+ -- MD5 implementation for "LuaJIT without FFI" branch
+
+ function md5_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K = common_W, md5_K
+ for pos = offs, offs + size - 1, 64 do
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)
+ end
+ local a, b, c, d = H[1], H[2], H[3], H[4]
+ for j = 1, 16, 4 do
+ a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j ] + W[j ] + a), 7) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+1] + W[j+1] + a), 12) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+2] + W[j+2] + a), 17) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+3] + W[j+3] + a), 22) + b)
+ end
+ for j = 17, 32, 4 do
+ local g = 5*j-4
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j ] + W[AND(g , 15) + 1] + a), 5) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+1] + W[AND(g + 5, 15) + 1] + a), 9) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+2] + W[AND(g + 10, 15) + 1] + a), 14) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+3] + W[AND(g - 1, 15) + 1] + a), 20) + b)
+ end
+ for j = 33, 48, 4 do
+ local g = 3*j+2
+ a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j ] + W[AND(g , 15) + 1] + a), 4) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+1] + W[AND(g + 3, 15) + 1] + a), 11) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+2] + W[AND(g + 6, 15) + 1] + a), 16) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+3] + W[AND(g - 7, 15) + 1] + a), 23) + b)
+ end
+ for j = 49, 64, 4 do
+ local g = j*7
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j ] + W[AND(g - 7, 15) + 1] + a), 6) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+1] + W[AND(g , 15) + 1] + a), 10) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+2] + W[AND(g + 7, 15) + 1] + a), 15) + b)
+ a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+3] + W[AND(g - 2, 15) + 1] + a), 21) + b)
+ end
+ H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4])
+ end
+ end
+
+
+ -- SHA-1 implementation for "LuaJIT without FFI" branch
+
+ function sha1_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W = common_W
+ for pos = offs, offs + size - 1, 64 do
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d)
+ end
+ for j = 17, 80 do
+ W[j] = ROL(XOR(W[j-3], W[j-8], W[j-14], W[j-16]), 1)
+ end
+ local a, b, c, d, e = H[1], H[2], H[3], H[4], H[5]
+ for j = 1, 20, 5 do
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j] + 0x5A827999 + e)) -- constant = floor(2^30 * sqrt(2))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+1] + 0x5A827999 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+2] + 0x5A827999 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+3] + 0x5A827999 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+4] + 0x5A827999 + e))
+ end
+ for j = 21, 40, 5 do
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0x6ED9EBA1 + e)) -- 2^30 * sqrt(3)
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0x6ED9EBA1 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0x6ED9EBA1 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0x6ED9EBA1 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0x6ED9EBA1 + e))
+ end
+ for j = 41, 60, 5 do
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j] + 0x8F1BBCDC + e)) -- 2^30 * sqrt(5)
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+1] + 0x8F1BBCDC + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+2] + 0x8F1BBCDC + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+3] + 0x8F1BBCDC + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+4] + 0x8F1BBCDC + e))
+ end
+ for j = 61, 80, 5 do
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0xCA62C1D6 + e)) -- 2^30 * sqrt(10)
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0xCA62C1D6 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0xCA62C1D6 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0xCA62C1D6 + e))
+ e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0xCA62C1D6 + e))
+ end
+ H[1], H[2], H[3], H[4], H[5] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]), NORM(e + H[5])
+ end
+ end
+
+
+ -- BLAKE2b implementation for "LuaJIT without FFI" branch
+
+ do
+ local v_lo, v_hi = {}, {}
+
+ local function G(a, b, c, d, k1, k2)
+ local W = common_W
+ local va_lo, vb_lo, vc_lo, vd_lo = v_lo[a], v_lo[b], v_lo[c], v_lo[d]
+ local va_hi, vb_hi, vc_hi, vd_hi = v_hi[a], v_hi[b], v_hi[c], v_hi[d]
+ local z = W[2*k1-1] + (va_lo % 2^32 + vb_lo % 2^32)
+ va_lo = NORM(z)
+ va_hi = NORM(W[2*k1] + (va_hi + vb_hi + floor(z / 2^32)))
+ vd_lo, vd_hi = XOR(vd_hi, va_hi), XOR(vd_lo, va_lo)
+ z = vc_lo % 2^32 + vd_lo % 2^32
+ vc_lo = NORM(z)
+ vc_hi = NORM(vc_hi + vd_hi + floor(z / 2^32))
+ vb_lo, vb_hi = XOR(vb_lo, vc_lo), XOR(vb_hi, vc_hi)
+ vb_lo, vb_hi = XOR(SHR(vb_lo, 24), SHL(vb_hi, 8)), XOR(SHR(vb_hi, 24), SHL(vb_lo, 8))
+ z = W[2*k2-1] + (va_lo % 2^32 + vb_lo % 2^32)
+ va_lo = NORM(z)
+ va_hi = NORM(W[2*k2] + (va_hi + vb_hi + floor(z / 2^32)))
+ vd_lo, vd_hi = XOR(vd_lo, va_lo), XOR(vd_hi, va_hi)
+ vd_lo, vd_hi = XOR(SHR(vd_lo, 16), SHL(vd_hi, 16)), XOR(SHR(vd_hi, 16), SHL(vd_lo, 16))
+ z = vc_lo % 2^32 + vd_lo % 2^32
+ vc_lo = NORM(z)
+ vc_hi = NORM(vc_hi + vd_hi + floor(z / 2^32))
+ vb_lo, vb_hi = XOR(vb_lo, vc_lo), XOR(vb_hi, vc_hi)
+ vb_lo, vb_hi = XOR(SHL(vb_lo, 1), SHR(vb_hi, 31)), XOR(SHL(vb_hi, 1), SHR(vb_lo, 31))
+ v_lo[a], v_lo[b], v_lo[c], v_lo[d] = va_lo, vb_lo, vc_lo, vd_lo
+ v_hi[a], v_hi[b], v_hi[c], v_hi[d] = va_hi, vb_hi, vc_hi, vd_hi
+ end
+
+ function blake2b_feed_128(H_lo, H_hi, str, offs, size, bytes_compressed, last_block_size, is_last_node)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ local W = common_W
+ local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8]
+ local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8]
+ for pos = offs, offs + size - 1, 128 do
+ if str then
+ for j = 1, 32 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = d * 2^24 + OR(SHL(c, 16), SHL(b, 8), a)
+ end
+ end
+ v_lo[0x0], v_lo[0x1], v_lo[0x2], v_lo[0x3], v_lo[0x4], v_lo[0x5], v_lo[0x6], v_lo[0x7] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo
+ v_lo[0x8], v_lo[0x9], v_lo[0xA], v_lo[0xB], v_lo[0xC], v_lo[0xD], v_lo[0xE], v_lo[0xF] = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[5], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8]
+ v_hi[0x0], v_hi[0x1], v_hi[0x2], v_hi[0x3], v_hi[0x4], v_hi[0x5], v_hi[0x6], v_hi[0x7] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi
+ v_hi[0x8], v_hi[0x9], v_hi[0xA], v_hi[0xB], v_hi[0xC], v_hi[0xD], v_hi[0xE], v_hi[0xF] = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4], sha2_H_hi[5], sha2_H_hi[6], sha2_H_hi[7], sha2_H_hi[8]
+ bytes_compressed = bytes_compressed + (last_block_size or 128)
+ local t0_lo = bytes_compressed % 2^32
+ local t0_hi = floor(bytes_compressed / 2^32)
+ v_lo[0xC] = XOR(v_lo[0xC], t0_lo) -- t0 = low_8_bytes(bytes_compressed)
+ v_hi[0xC] = XOR(v_hi[0xC], t0_hi)
+ -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes
+ if last_block_size then -- flag f0
+ v_lo[0xE] = NOT(v_lo[0xE])
+ v_hi[0xE] = NOT(v_hi[0xE])
+ end
+ if is_last_node then -- flag f1
+ v_lo[0xF] = NOT(v_lo[0xF])
+ v_hi[0xF] = NOT(v_hi[0xF])
+ end
+ for j = 1, 12 do
+ local row = sigma[j]
+ G(0, 4, 8, 12, row[ 1], row[ 2])
+ G(1, 5, 9, 13, row[ 3], row[ 4])
+ G(2, 6, 10, 14, row[ 5], row[ 6])
+ G(3, 7, 11, 15, row[ 7], row[ 8])
+ G(0, 5, 10, 15, row[ 9], row[10])
+ G(1, 6, 11, 12, row[11], row[12])
+ G(2, 7, 8, 13, row[13], row[14])
+ G(3, 4, 9, 14, row[15], row[16])
+ end
+ h1_lo = XOR(h1_lo, v_lo[0x0], v_lo[0x8])
+ h2_lo = XOR(h2_lo, v_lo[0x1], v_lo[0x9])
+ h3_lo = XOR(h3_lo, v_lo[0x2], v_lo[0xA])
+ h4_lo = XOR(h4_lo, v_lo[0x3], v_lo[0xB])
+ h5_lo = XOR(h5_lo, v_lo[0x4], v_lo[0xC])
+ h6_lo = XOR(h6_lo, v_lo[0x5], v_lo[0xD])
+ h7_lo = XOR(h7_lo, v_lo[0x6], v_lo[0xE])
+ h8_lo = XOR(h8_lo, v_lo[0x7], v_lo[0xF])
+ h1_hi = XOR(h1_hi, v_hi[0x0], v_hi[0x8])
+ h2_hi = XOR(h2_hi, v_hi[0x1], v_hi[0x9])
+ h3_hi = XOR(h3_hi, v_hi[0x2], v_hi[0xA])
+ h4_hi = XOR(h4_hi, v_hi[0x3], v_hi[0xB])
+ h5_hi = XOR(h5_hi, v_hi[0x4], v_hi[0xC])
+ h6_hi = XOR(h6_hi, v_hi[0x5], v_hi[0xD])
+ h7_hi = XOR(h7_hi, v_hi[0x6], v_hi[0xE])
+ h8_hi = XOR(h8_hi, v_hi[0x7], v_hi[0xF])
+ end
+ H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo % 2^32, h2_lo % 2^32, h3_lo % 2^32, h4_lo % 2^32, h5_lo % 2^32, h6_lo % 2^32, h7_lo % 2^32, h8_lo % 2^32
+ H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi % 2^32, h2_hi % 2^32, h3_hi % 2^32, h4_hi % 2^32, h5_hi % 2^32, h6_hi % 2^32, h7_hi % 2^32, h8_hi % 2^32
+ return bytes_compressed
+ end
+
+ end
+end
+
+
+if branch == "FFI" or branch == "LJ" then
+
+
+ -- BLAKE2s and BLAKE3 implementations for "LuaJIT with FFI" and "LuaJIT without FFI" branches
+
+ do
+ local W = common_W_blake2s
+ local v = v_for_blake2s_feed_64
+
+ local function G(a, b, c, d, k1, k2)
+ local va, vb, vc, vd = v[a], v[b], v[c], v[d]
+ va = NORM(W[k1] + (va + vb))
+ vd = ROR(XOR(vd, va), 16)
+ vc = NORM(vc + vd)
+ vb = ROR(XOR(vb, vc), 12)
+ va = NORM(W[k2] + (va + vb))
+ vd = ROR(XOR(vd, va), 8)
+ vc = NORM(vc + vd)
+ vb = ROR(XOR(vb, vc), 7)
+ v[a], v[b], v[c], v[d] = va, vb, vc, vd
+ end
+
+ function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local h1, h2, h3, h4, h5, h6, h7, h8 = NORM(H[1]), NORM(H[2]), NORM(H[3]), NORM(H[4]), NORM(H[5]), NORM(H[6]), NORM(H[7]), NORM(H[8])
+ for pos = offs, offs + size - 1, 64 do
+ if str then
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)
+ end
+ end
+ v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8
+ v[0x8], v[0x9], v[0xA], v[0xB], v[0xE], v[0xF] = NORM(sha2_H_hi[1]), NORM(sha2_H_hi[2]), NORM(sha2_H_hi[3]), NORM(sha2_H_hi[4]), NORM(sha2_H_hi[7]), NORM(sha2_H_hi[8])
+ bytes_compressed = bytes_compressed + (last_block_size or 64)
+ local t0 = bytes_compressed % 2^32
+ local t1 = floor(bytes_compressed / 2^32)
+ v[0xC] = XOR(sha2_H_hi[5], t0) -- t0 = low_4_bytes(bytes_compressed)
+ v[0xD] = XOR(sha2_H_hi[6], t1) -- t1 = high_4_bytes(bytes_compressed
+ if last_block_size then -- flag f0
+ v[0xE] = NOT(v[0xE])
+ end
+ if is_last_node then -- flag f1
+ v[0xF] = NOT(v[0xF])
+ end
+ for j = 1, 10 do
+ local row = sigma[j]
+ G(0, 4, 8, 12, row[ 1], row[ 2])
+ G(1, 5, 9, 13, row[ 3], row[ 4])
+ G(2, 6, 10, 14, row[ 5], row[ 6])
+ G(3, 7, 11, 15, row[ 7], row[ 8])
+ G(0, 5, 10, 15, row[ 9], row[10])
+ G(1, 6, 11, 12, row[11], row[12])
+ G(2, 7, 8, 13, row[13], row[14])
+ G(3, 4, 9, 14, row[15], row[16])
+ end
+ h1 = XOR(h1, v[0x0], v[0x8])
+ h2 = XOR(h2, v[0x1], v[0x9])
+ h3 = XOR(h3, v[0x2], v[0xA])
+ h4 = XOR(h4, v[0x3], v[0xB])
+ h5 = XOR(h5, v[0x4], v[0xC])
+ h6 = XOR(h6, v[0x5], v[0xD])
+ h7 = XOR(h7, v[0x6], v[0xE])
+ h8 = XOR(h8, v[0x7], v[0xF])
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ return bytes_compressed
+ end
+
+ function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ block_length = block_length or 64
+ local h1, h2, h3, h4, h5, h6, h7, h8 = NORM(H_in[1]), NORM(H_in[2]), NORM(H_in[3]), NORM(H_in[4]), NORM(H_in[5]), NORM(H_in[6]), NORM(H_in[7]), NORM(H_in[8])
+ H_out = H_out or H_in
+ for pos = offs, offs + size - 1, 64 do
+ if str then
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)
+ end
+ end
+ v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8
+ v[0x8], v[0x9], v[0xA], v[0xB] = NORM(sha2_H_hi[1]), NORM(sha2_H_hi[2]), NORM(sha2_H_hi[3]), NORM(sha2_H_hi[4])
+ v[0xC] = NORM(chunk_index % 2^32) -- t0 = low_4_bytes(chunk_index)
+ v[0xD] = floor(chunk_index / 2^32) -- t1 = high_4_bytes(chunk_index)
+ v[0xE], v[0xF] = block_length, flags
+ for j = 1, 7 do
+ G(0, 4, 8, 12, perm_blake3[j], perm_blake3[j + 14])
+ G(1, 5, 9, 13, perm_blake3[j + 1], perm_blake3[j + 2])
+ G(2, 6, 10, 14, perm_blake3[j + 16], perm_blake3[j + 7])
+ G(3, 7, 11, 15, perm_blake3[j + 15], perm_blake3[j + 17])
+ G(0, 5, 10, 15, perm_blake3[j + 21], perm_blake3[j + 5])
+ G(1, 6, 11, 12, perm_blake3[j + 3], perm_blake3[j + 6])
+ G(2, 7, 8, 13, perm_blake3[j + 4], perm_blake3[j + 18])
+ G(3, 4, 9, 14, perm_blake3[j + 19], perm_blake3[j + 20])
+ end
+ if wide_output then
+ H_out[ 9] = XOR(h1, v[0x8])
+ H_out[10] = XOR(h2, v[0x9])
+ H_out[11] = XOR(h3, v[0xA])
+ H_out[12] = XOR(h4, v[0xB])
+ H_out[13] = XOR(h5, v[0xC])
+ H_out[14] = XOR(h6, v[0xD])
+ H_out[15] = XOR(h7, v[0xE])
+ H_out[16] = XOR(h8, v[0xF])
+ end
+ h1 = XOR(v[0x0], v[0x8])
+ h2 = XOR(v[0x1], v[0x9])
+ h3 = XOR(v[0x2], v[0xA])
+ h4 = XOR(v[0x3], v[0xB])
+ h5 = XOR(v[0x4], v[0xC])
+ h6 = XOR(v[0x5], v[0xD])
+ h7 = XOR(v[0x6], v[0xE])
+ h8 = XOR(v[0x7], v[0xF])
+ end
+ H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ end
+
+ end
+
+end
+
+
+if branch == "INT64" then
+
+
+ -- implementation for Lua 5.3/5.4
+
+ hi_factor = 4294967296
+ hi_factor_keccak = 4294967296
+ lanes_index_base = 1
+
+ HEX64, XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 = load[=[-- branch "INT64"
+ local md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3 = ...
+ local string_format, string_unpack = string.format, string.unpack
+
+ local function HEX64(x)
+ return string_format("%016x", x)
+ end
+
+ local function XORA5(x, y)
+ return x ~ (y or 0xa5a5a5a5a5a5a5a5)
+ end
+
+ local function XOR_BYTE(x, y)
+ return x ~ y
+ end
+
+ local function sha256_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K = common_W, sha2_K_hi
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for pos = offs + 1, offs + size, 64 do
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", str, pos)
+ for j = 17, 64 do
+ local a = W[j-15]
+ a = a<<32 | a
+ local b = W[j-2]
+ b = b<<32 | b
+ W[j] = (a>>7 ~ a>>18 ~ a>>35) + (b>>17 ~ b>>19 ~ b>>42) + W[j-7] + W[j-16] & (1<<32)-1
+ end
+ local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8
+ for j = 1, 64 do
+ e = e<<32 | e & (1<<32)-1
+ local z = (e>>6 ~ e>>11 ~ e>>25) + (g ~ e & (f ~ g)) + h + K[j] + W[j]
+ h = g
+ g = f
+ f = e
+ e = z + d
+ d = c
+ c = b
+ b = a
+ a = a<<32 | a & (1<<32)-1
+ a = z + ((a ~ c) & d ~ a & c) + (a>>2 ~ a>>13 ~ a>>22)
+ end
+ h1 = a + h1
+ h2 = b + h2
+ h3 = c + h3
+ h4 = d + h4
+ h5 = e + h5
+ h6 = f + h6
+ h7 = g + h7
+ h8 = h + h8
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ end
+
+ local function sha512_feed_128(H, _, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ local W, K = common_W, sha2_K_lo
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for pos = offs + 1, offs + size, 128 do
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack(">i8i8i8i8i8i8i8i8i8i8i8i8i8i8i8i8", str, pos)
+ for j = 17, 80 do
+ local a = W[j-15]
+ local b = W[j-2]
+ W[j] = (a >> 1 ~ a >> 7 ~ a >> 8 ~ a << 56 ~ a << 63) + (b >> 6 ~ b >> 19 ~ b >> 61 ~ b << 3 ~ b << 45) + W[j-7] + W[j-16]
+ end
+ local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8
+ for j = 1, 80 do
+ local z = (e >> 14 ~ e >> 18 ~ e >> 41 ~ e << 23 ~ e << 46 ~ e << 50) + (g ~ e & (f ~ g)) + h + K[j] + W[j]
+ h = g
+ g = f
+ f = e
+ e = z + d
+ d = c
+ c = b
+ b = a
+ a = z + ((a ~ c) & d ~ a & c) + (a >> 28 ~ a >> 34 ~ a >> 39 ~ a << 25 ~ a << 30 ~ a << 36)
+ end
+ h1 = a + h1
+ h2 = b + h2
+ h3 = c + h3
+ h4 = d + h4
+ h5 = e + h5
+ h6 = f + h6
+ h7 = g + h7
+ h8 = h + h8
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ end
+
+ local function md5_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K, md5_next_shift = common_W, md5_K, md5_next_shift
+ local h1, h2, h3, h4 = H[1], H[2], H[3], H[4]
+ for pos = offs + 1, offs + size, 64 do
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack("> s) + b
+ s = md5_next_shift[s]
+ end
+ s = 32-5
+ for j = 17, 32 do
+ local F = (c ~ d & (b ~ c)) + a + K[j] + W[(5*j-4 & 15) + 1]
+ a = d
+ d = c
+ c = b
+ b = ((F<<32 | F & (1<<32)-1) >> s) + b
+ s = md5_next_shift[s]
+ end
+ s = 32-4
+ for j = 33, 48 do
+ local F = (b ~ c ~ d) + a + K[j] + W[(3*j+2 & 15) + 1]
+ a = d
+ d = c
+ c = b
+ b = ((F<<32 | F & (1<<32)-1) >> s) + b
+ s = md5_next_shift[s]
+ end
+ s = 32-6
+ for j = 49, 64 do
+ local F = (c ~ (b | ~d)) + a + K[j] + W[(j*7-7 & 15) + 1]
+ a = d
+ d = c
+ c = b
+ b = ((F<<32 | F & (1<<32)-1) >> s) + b
+ s = md5_next_shift[s]
+ end
+ h1 = a + h1
+ h2 = b + h2
+ h3 = c + h3
+ h4 = d + h4
+ end
+ H[1], H[2], H[3], H[4] = h1, h2, h3, h4
+ end
+
+ local function sha1_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W = common_W
+ local h1, h2, h3, h4, h5 = H[1], H[2], H[3], H[4], H[5]
+ for pos = offs + 1, offs + size, 64 do
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", str, pos)
+ for j = 17, 80 do
+ local a = W[j-3] ~ W[j-8] ~ W[j-14] ~ W[j-16]
+ W[j] = (a<<32 | a) << 1 >> 32
+ end
+ local a, b, c, d, e = h1, h2, h3, h4, h5
+ for j = 1, 20 do
+ local z = ((a<<32 | a & (1<<32)-1) >> 27) + (d ~ b & (c ~ d)) + 0x5A827999 + W[j] + e -- constant = floor(2^30 * sqrt(2))
+ e = d
+ d = c
+ c = (b<<32 | b & (1<<32)-1) >> 2
+ b = a
+ a = z
+ end
+ for j = 21, 40 do
+ local z = ((a<<32 | a & (1<<32)-1) >> 27) + (b ~ c ~ d) + 0x6ED9EBA1 + W[j] + e -- 2^30 * sqrt(3)
+ e = d
+ d = c
+ c = (b<<32 | b & (1<<32)-1) >> 2
+ b = a
+ a = z
+ end
+ for j = 41, 60 do
+ local z = ((a<<32 | a & (1<<32)-1) >> 27) + ((b ~ c) & d ~ b & c) + 0x8F1BBCDC + W[j] + e -- 2^30 * sqrt(5)
+ e = d
+ d = c
+ c = (b<<32 | b & (1<<32)-1) >> 2
+ b = a
+ a = z
+ end
+ for j = 61, 80 do
+ local z = ((a<<32 | a & (1<<32)-1) >> 27) + (b ~ c ~ d) + 0xCA62C1D6 + W[j] + e -- 2^30 * sqrt(10)
+ e = d
+ d = c
+ c = (b<<32 | b & (1<<32)-1) >> 2
+ b = a
+ a = z
+ end
+ h1 = a + h1
+ h2 = b + h2
+ h3 = c + h3
+ h4 = d + h4
+ h5 = e + h5
+ end
+ H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5
+ end
+
+ local keccak_format_i8 = build_keccak_format("i8")
+
+ local function keccak_feed(lanes, _, str, offs, size, block_size_in_bytes)
+ -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8
+ local RC = sha3_RC_lo
+ local qwords_qty = block_size_in_bytes / 8
+ local keccak_format = keccak_format_i8[qwords_qty]
+ for pos = offs + 1, offs + size, block_size_in_bytes do
+ local qwords_from_message = {string_unpack(keccak_format, str, pos)}
+ for j = 1, qwords_qty do
+ lanes[j] = lanes[j] ~ qwords_from_message[j]
+ end
+ local L01, L02, L03, L04, L05, L06, L07, L08, L09, L10, L11, L12, L13, L14, L15, L16, L17, L18, L19, L20, L21, L22, L23, L24, L25 =
+ lanes[1], lanes[2], lanes[3], lanes[4], lanes[5], lanes[6], lanes[7], lanes[8], lanes[9], lanes[10], lanes[11], lanes[12], lanes[13],
+ lanes[14], lanes[15], lanes[16], lanes[17], lanes[18], lanes[19], lanes[20], lanes[21], lanes[22], lanes[23], lanes[24], lanes[25]
+ for round_idx = 1, 24 do
+ local C1 = L01 ~ L06 ~ L11 ~ L16 ~ L21
+ local C2 = L02 ~ L07 ~ L12 ~ L17 ~ L22
+ local C3 = L03 ~ L08 ~ L13 ~ L18 ~ L23
+ local C4 = L04 ~ L09 ~ L14 ~ L19 ~ L24
+ local C5 = L05 ~ L10 ~ L15 ~ L20 ~ L25
+ local D = C1 ~ C3<<1 ~ C3>>63
+ local T0 = D ~ L02
+ local T1 = D ~ L07
+ local T2 = D ~ L12
+ local T3 = D ~ L17
+ local T4 = D ~ L22
+ L02 = T1<<44 ~ T1>>20
+ L07 = T3<<45 ~ T3>>19
+ L12 = T0<<1 ~ T0>>63
+ L17 = T2<<10 ~ T2>>54
+ L22 = T4<<2 ~ T4>>62
+ D = C2 ~ C4<<1 ~ C4>>63
+ T0 = D ~ L03
+ T1 = D ~ L08
+ T2 = D ~ L13
+ T3 = D ~ L18
+ T4 = D ~ L23
+ L03 = T2<<43 ~ T2>>21
+ L08 = T4<<61 ~ T4>>3
+ L13 = T1<<6 ~ T1>>58
+ L18 = T3<<15 ~ T3>>49
+ L23 = T0<<62 ~ T0>>2
+ D = C3 ~ C5<<1 ~ C5>>63
+ T0 = D ~ L04
+ T1 = D ~ L09
+ T2 = D ~ L14
+ T3 = D ~ L19
+ T4 = D ~ L24
+ L04 = T3<<21 ~ T3>>43
+ L09 = T0<<28 ~ T0>>36
+ L14 = T2<<25 ~ T2>>39
+ L19 = T4<<56 ~ T4>>8
+ L24 = T1<<55 ~ T1>>9
+ D = C4 ~ C1<<1 ~ C1>>63
+ T0 = D ~ L05
+ T1 = D ~ L10
+ T2 = D ~ L15
+ T3 = D ~ L20
+ T4 = D ~ L25
+ L05 = T4<<14 ~ T4>>50
+ L10 = T1<<20 ~ T1>>44
+ L15 = T3<<8 ~ T3>>56
+ L20 = T0<<27 ~ T0>>37
+ L25 = T2<<39 ~ T2>>25
+ D = C5 ~ C2<<1 ~ C2>>63
+ T1 = D ~ L06
+ T2 = D ~ L11
+ T3 = D ~ L16
+ T4 = D ~ L21
+ L06 = T2<<3 ~ T2>>61
+ L11 = T4<<18 ~ T4>>46
+ L16 = T1<<36 ~ T1>>28
+ L21 = T3<<41 ~ T3>>23
+ L01 = D ~ L01
+ L01, L02, L03, L04, L05 = L01 ~ ~L02 & L03, L02 ~ ~L03 & L04, L03 ~ ~L04 & L05, L04 ~ ~L05 & L01, L05 ~ ~L01 & L02
+ L06, L07, L08, L09, L10 = L09 ~ ~L10 & L06, L10 ~ ~L06 & L07, L06 ~ ~L07 & L08, L07 ~ ~L08 & L09, L08 ~ ~L09 & L10
+ L11, L12, L13, L14, L15 = L12 ~ ~L13 & L14, L13 ~ ~L14 & L15, L14 ~ ~L15 & L11, L15 ~ ~L11 & L12, L11 ~ ~L12 & L13
+ L16, L17, L18, L19, L20 = L20 ~ ~L16 & L17, L16 ~ ~L17 & L18, L17 ~ ~L18 & L19, L18 ~ ~L19 & L20, L19 ~ ~L20 & L16
+ L21, L22, L23, L24, L25 = L23 ~ ~L24 & L25, L24 ~ ~L25 & L21, L25 ~ ~L21 & L22, L21 ~ ~L22 & L23, L22 ~ ~L23 & L24
+ L01 = L01 ~ RC[round_idx]
+ end
+ lanes[1] = L01
+ lanes[2] = L02
+ lanes[3] = L03
+ lanes[4] = L04
+ lanes[5] = L05
+ lanes[6] = L06
+ lanes[7] = L07
+ lanes[8] = L08
+ lanes[9] = L09
+ lanes[10] = L10
+ lanes[11] = L11
+ lanes[12] = L12
+ lanes[13] = L13
+ lanes[14] = L14
+ lanes[15] = L15
+ lanes[16] = L16
+ lanes[17] = L17
+ lanes[18] = L18
+ lanes[19] = L19
+ lanes[20] = L20
+ lanes[21] = L21
+ lanes[22] = L22
+ lanes[23] = L23
+ lanes[24] = L24
+ lanes[25] = L25
+ end
+ end
+
+ local function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W = common_W
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for pos = offs + 1, offs + size, 64 do
+ if str then
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack("> 32 -- t1 = high_4_bytes(bytes_compressed)
+ if last_block_size then -- flag f0
+ vE = ~vE
+ end
+ if is_last_node then -- flag f1
+ vF = ~vF
+ end
+ for j = 1, 10 do
+ local row = sigma[j]
+ v0 = v0 + v4 + W[row[1]]
+ vC = vC ~ v0
+ vC = (vC & (1<<32)-1) >> 16 | vC << 16
+ v8 = v8 + vC
+ v4 = v4 ~ v8
+ v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20
+ v0 = v0 + v4 + W[row[2]]
+ vC = vC ~ v0
+ vC = (vC & (1<<32)-1) >> 8 | vC << 24
+ v8 = v8 + vC
+ v4 = v4 ~ v8
+ v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25
+ v1 = v1 + v5 + W[row[3]]
+ vD = vD ~ v1
+ vD = (vD & (1<<32)-1) >> 16 | vD << 16
+ v9 = v9 + vD
+ v5 = v5 ~ v9
+ v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20
+ v1 = v1 + v5 + W[row[4]]
+ vD = vD ~ v1
+ vD = (vD & (1<<32)-1) >> 8 | vD << 24
+ v9 = v9 + vD
+ v5 = v5 ~ v9
+ v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25
+ v2 = v2 + v6 + W[row[5]]
+ vE = vE ~ v2
+ vE = (vE & (1<<32)-1) >> 16 | vE << 16
+ vA = vA + vE
+ v6 = v6 ~ vA
+ v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20
+ v2 = v2 + v6 + W[row[6]]
+ vE = vE ~ v2
+ vE = (vE & (1<<32)-1) >> 8 | vE << 24
+ vA = vA + vE
+ v6 = v6 ~ vA
+ v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25
+ v3 = v3 + v7 + W[row[7]]
+ vF = vF ~ v3
+ vF = (vF & (1<<32)-1) >> 16 | vF << 16
+ vB = vB + vF
+ v7 = v7 ~ vB
+ v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20
+ v3 = v3 + v7 + W[row[8]]
+ vF = vF ~ v3
+ vF = (vF & (1<<32)-1) >> 8 | vF << 24
+ vB = vB + vF
+ v7 = v7 ~ vB
+ v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25
+ v0 = v0 + v5 + W[row[9]]
+ vF = vF ~ v0
+ vF = (vF & (1<<32)-1) >> 16 | vF << 16
+ vA = vA + vF
+ v5 = v5 ~ vA
+ v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20
+ v0 = v0 + v5 + W[row[10]]
+ vF = vF ~ v0
+ vF = (vF & (1<<32)-1) >> 8 | vF << 24
+ vA = vA + vF
+ v5 = v5 ~ vA
+ v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25
+ v1 = v1 + v6 + W[row[11]]
+ vC = vC ~ v1
+ vC = (vC & (1<<32)-1) >> 16 | vC << 16
+ vB = vB + vC
+ v6 = v6 ~ vB
+ v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20
+ v1 = v1 + v6 + W[row[12]]
+ vC = vC ~ v1
+ vC = (vC & (1<<32)-1) >> 8 | vC << 24
+ vB = vB + vC
+ v6 = v6 ~ vB
+ v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25
+ v2 = v2 + v7 + W[row[13]]
+ vD = vD ~ v2
+ vD = (vD & (1<<32)-1) >> 16 | vD << 16
+ v8 = v8 + vD
+ v7 = v7 ~ v8
+ v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20
+ v2 = v2 + v7 + W[row[14]]
+ vD = vD ~ v2
+ vD = (vD & (1<<32)-1) >> 8 | vD << 24
+ v8 = v8 + vD
+ v7 = v7 ~ v8
+ v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25
+ v3 = v3 + v4 + W[row[15]]
+ vE = vE ~ v3
+ vE = (vE & (1<<32)-1) >> 16 | vE << 16
+ v9 = v9 + vE
+ v4 = v4 ~ v9
+ v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20
+ v3 = v3 + v4 + W[row[16]]
+ vE = vE ~ v3
+ vE = (vE & (1<<32)-1) >> 8 | vE << 24
+ v9 = v9 + vE
+ v4 = v4 ~ v9
+ v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25
+ end
+ h1 = h1 ~ v0 ~ v8
+ h2 = h2 ~ v1 ~ v9
+ h3 = h3 ~ v2 ~ vA
+ h4 = h4 ~ v3 ~ vB
+ h5 = h5 ~ v4 ~ vC
+ h6 = h6 ~ v5 ~ vD
+ h7 = h7 ~ v6 ~ vE
+ h8 = h8 ~ v7 ~ vF
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ return bytes_compressed
+ end
+
+ local function blake2b_feed_128(H, _, str, offs, size, bytes_compressed, last_block_size, is_last_node)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ local W = common_W
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for pos = offs + 1, offs + size, 128 do
+ if str then
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack("> 32 | vC << 32
+ v8 = v8 + vC
+ v4 = v4 ~ v8
+ v4 = v4 >> 24 | v4 << 40
+ v0 = v0 + v4 + W[row[2]]
+ vC = vC ~ v0
+ vC = vC >> 16 | vC << 48
+ v8 = v8 + vC
+ v4 = v4 ~ v8
+ v4 = v4 >> 63 | v4 << 1
+ v1 = v1 + v5 + W[row[3]]
+ vD = vD ~ v1
+ vD = vD >> 32 | vD << 32
+ v9 = v9 + vD
+ v5 = v5 ~ v9
+ v5 = v5 >> 24 | v5 << 40
+ v1 = v1 + v5 + W[row[4]]
+ vD = vD ~ v1
+ vD = vD >> 16 | vD << 48
+ v9 = v9 + vD
+ v5 = v5 ~ v9
+ v5 = v5 >> 63 | v5 << 1
+ v2 = v2 + v6 + W[row[5]]
+ vE = vE ~ v2
+ vE = vE >> 32 | vE << 32
+ vA = vA + vE
+ v6 = v6 ~ vA
+ v6 = v6 >> 24 | v6 << 40
+ v2 = v2 + v6 + W[row[6]]
+ vE = vE ~ v2
+ vE = vE >> 16 | vE << 48
+ vA = vA + vE
+ v6 = v6 ~ vA
+ v6 = v6 >> 63 | v6 << 1
+ v3 = v3 + v7 + W[row[7]]
+ vF = vF ~ v3
+ vF = vF >> 32 | vF << 32
+ vB = vB + vF
+ v7 = v7 ~ vB
+ v7 = v7 >> 24 | v7 << 40
+ v3 = v3 + v7 + W[row[8]]
+ vF = vF ~ v3
+ vF = vF >> 16 | vF << 48
+ vB = vB + vF
+ v7 = v7 ~ vB
+ v7 = v7 >> 63 | v7 << 1
+ v0 = v0 + v5 + W[row[9]]
+ vF = vF ~ v0
+ vF = vF >> 32 | vF << 32
+ vA = vA + vF
+ v5 = v5 ~ vA
+ v5 = v5 >> 24 | v5 << 40
+ v0 = v0 + v5 + W[row[10]]
+ vF = vF ~ v0
+ vF = vF >> 16 | vF << 48
+ vA = vA + vF
+ v5 = v5 ~ vA
+ v5 = v5 >> 63 | v5 << 1
+ v1 = v1 + v6 + W[row[11]]
+ vC = vC ~ v1
+ vC = vC >> 32 | vC << 32
+ vB = vB + vC
+ v6 = v6 ~ vB
+ v6 = v6 >> 24 | v6 << 40
+ v1 = v1 + v6 + W[row[12]]
+ vC = vC ~ v1
+ vC = vC >> 16 | vC << 48
+ vB = vB + vC
+ v6 = v6 ~ vB
+ v6 = v6 >> 63 | v6 << 1
+ v2 = v2 + v7 + W[row[13]]
+ vD = vD ~ v2
+ vD = vD >> 32 | vD << 32
+ v8 = v8 + vD
+ v7 = v7 ~ v8
+ v7 = v7 >> 24 | v7 << 40
+ v2 = v2 + v7 + W[row[14]]
+ vD = vD ~ v2
+ vD = vD >> 16 | vD << 48
+ v8 = v8 + vD
+ v7 = v7 ~ v8
+ v7 = v7 >> 63 | v7 << 1
+ v3 = v3 + v4 + W[row[15]]
+ vE = vE ~ v3
+ vE = vE >> 32 | vE << 32
+ v9 = v9 + vE
+ v4 = v4 ~ v9
+ v4 = v4 >> 24 | v4 << 40
+ v3 = v3 + v4 + W[row[16]]
+ vE = vE ~ v3
+ vE = vE >> 16 | vE << 48
+ v9 = v9 + vE
+ v4 = v4 ~ v9
+ v4 = v4 >> 63 | v4 << 1
+ end
+ h1 = h1 ~ v0 ~ v8
+ h2 = h2 ~ v1 ~ v9
+ h3 = h3 ~ v2 ~ vA
+ h4 = h4 ~ v3 ~ vB
+ h5 = h5 ~ v4 ~ vC
+ h6 = h6 ~ v5 ~ vD
+ h7 = h7 ~ v6 ~ vE
+ h8 = h8 ~ v7 ~ vF
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ return bytes_compressed
+ end
+
+ local function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ block_length = block_length or 64
+ local W = common_W
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H_in[1], H_in[2], H_in[3], H_in[4], H_in[5], H_in[6], H_in[7], H_in[8]
+ H_out = H_out or H_in
+ for pos = offs + 1, offs + size, 64 do
+ if str then
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack("> 16 | vC << 16
+ v8 = v8 + vC
+ v4 = v4 ~ v8
+ v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20
+ v0 = v0 + v4 + W[perm_blake3[j + 14]]
+ vC = vC ~ v0
+ vC = (vC & (1<<32)-1) >> 8 | vC << 24
+ v8 = v8 + vC
+ v4 = v4 ~ v8
+ v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25
+ v1 = v1 + v5 + W[perm_blake3[j + 1]]
+ vD = vD ~ v1
+ vD = (vD & (1<<32)-1) >> 16 | vD << 16
+ v9 = v9 + vD
+ v5 = v5 ~ v9
+ v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20
+ v1 = v1 + v5 + W[perm_blake3[j + 2]]
+ vD = vD ~ v1
+ vD = (vD & (1<<32)-1) >> 8 | vD << 24
+ v9 = v9 + vD
+ v5 = v5 ~ v9
+ v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25
+ v2 = v2 + v6 + W[perm_blake3[j + 16]]
+ vE = vE ~ v2
+ vE = (vE & (1<<32)-1) >> 16 | vE << 16
+ vA = vA + vE
+ v6 = v6 ~ vA
+ v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20
+ v2 = v2 + v6 + W[perm_blake3[j + 7]]
+ vE = vE ~ v2
+ vE = (vE & (1<<32)-1) >> 8 | vE << 24
+ vA = vA + vE
+ v6 = v6 ~ vA
+ v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25
+ v3 = v3 + v7 + W[perm_blake3[j + 15]]
+ vF = vF ~ v3
+ vF = (vF & (1<<32)-1) >> 16 | vF << 16
+ vB = vB + vF
+ v7 = v7 ~ vB
+ v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20
+ v3 = v3 + v7 + W[perm_blake3[j + 17]]
+ vF = vF ~ v3
+ vF = (vF & (1<<32)-1) >> 8 | vF << 24
+ vB = vB + vF
+ v7 = v7 ~ vB
+ v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25
+ v0 = v0 + v5 + W[perm_blake3[j + 21]]
+ vF = vF ~ v0
+ vF = (vF & (1<<32)-1) >> 16 | vF << 16
+ vA = vA + vF
+ v5 = v5 ~ vA
+ v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20
+ v0 = v0 + v5 + W[perm_blake3[j + 5]]
+ vF = vF ~ v0
+ vF = (vF & (1<<32)-1) >> 8 | vF << 24
+ vA = vA + vF
+ v5 = v5 ~ vA
+ v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25
+ v1 = v1 + v6 + W[perm_blake3[j + 3]]
+ vC = vC ~ v1
+ vC = (vC & (1<<32)-1) >> 16 | vC << 16
+ vB = vB + vC
+ v6 = v6 ~ vB
+ v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20
+ v1 = v1 + v6 + W[perm_blake3[j + 6]]
+ vC = vC ~ v1
+ vC = (vC & (1<<32)-1) >> 8 | vC << 24
+ vB = vB + vC
+ v6 = v6 ~ vB
+ v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25
+ v2 = v2 + v7 + W[perm_blake3[j + 4]]
+ vD = vD ~ v2
+ vD = (vD & (1<<32)-1) >> 16 | vD << 16
+ v8 = v8 + vD
+ v7 = v7 ~ v8
+ v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20
+ v2 = v2 + v7 + W[perm_blake3[j + 18]]
+ vD = vD ~ v2
+ vD = (vD & (1<<32)-1) >> 8 | vD << 24
+ v8 = v8 + vD
+ v7 = v7 ~ v8
+ v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25
+ v3 = v3 + v4 + W[perm_blake3[j + 19]]
+ vE = vE ~ v3
+ vE = (vE & (1<<32)-1) >> 16 | vE << 16
+ v9 = v9 + vE
+ v4 = v4 ~ v9
+ v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20
+ v3 = v3 + v4 + W[perm_blake3[j + 20]]
+ vE = vE ~ v3
+ vE = (vE & (1<<32)-1) >> 8 | vE << 24
+ v9 = v9 + vE
+ v4 = v4 ~ v9
+ v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25
+ end
+ if wide_output then
+ H_out[ 9] = h1 ~ v8
+ H_out[10] = h2 ~ v9
+ H_out[11] = h3 ~ vA
+ H_out[12] = h4 ~ vB
+ H_out[13] = h5 ~ vC
+ H_out[14] = h6 ~ vD
+ H_out[15] = h7 ~ vE
+ H_out[16] = h8 ~ vF
+ end
+ h1 = v0 ~ v8
+ h2 = v1 ~ v9
+ h3 = v2 ~ vA
+ h4 = v3 ~ vB
+ h5 = v4 ~ vC
+ h6 = v5 ~ vD
+ h7 = v6 ~ vE
+ h8 = v7 ~ vF
+ end
+ H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ end
+
+ return HEX64, XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64
+ ]=](md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3)
+
+end
+
+
+if branch == "INT32" then
+
+
+ -- implementation for Lua 5.3/5.4 having non-standard numbers config "int32"+"double" (built with LUA_INT_TYPE=LUA_INT_INT)
+
+ K_lo_modulo = 2^32
+
+ function HEX(x) -- returns string of 8 lowercase hexadecimal digits
+ return string_format("%08x", x)
+ end
+
+ XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 = load[=[-- branch "INT32"
+ local md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sha3_RC_hi, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3 = ...
+ local string_unpack, floor = string.unpack, math.floor
+
+ local function XORA5(x, y)
+ return x ~ (y and (y + 2^31) % 2^32 - 2^31 or 0xA5A5A5A5)
+ end
+
+ local function XOR_BYTE(x, y)
+ return x ~ y
+ end
+
+ local function sha256_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K = common_W, sha2_K_hi
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for pos = offs + 1, offs + size, 64 do
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack(">i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4", str, pos)
+ for j = 17, 64 do
+ local a, b = W[j-15], W[j-2]
+ W[j] = (a>>7 ~ a<<25 ~ a<<14 ~ a>>18 ~ a>>3) + (b<<15 ~ b>>17 ~ b<<13 ~ b>>19 ~ b>>10) + W[j-7] + W[j-16]
+ end
+ local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8
+ for j = 1, 64 do
+ local z = (e>>6 ~ e<<26 ~ e>>11 ~ e<<21 ~ e>>25 ~ e<<7) + (g ~ e & (f ~ g)) + h + K[j] + W[j]
+ h = g
+ g = f
+ f = e
+ e = z + d
+ d = c
+ c = b
+ b = a
+ a = z + ((a ~ c) & d ~ a & c) + (a>>2 ~ a<<30 ~ a>>13 ~ a<<19 ~ a<<10 ~ a>>22)
+ end
+ h1 = a + h1
+ h2 = b + h2
+ h3 = c + h3
+ h4 = d + h4
+ h5 = e + h5
+ h6 = f + h6
+ h7 = g + h7
+ h8 = h + h8
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ end
+
+ local function sha512_feed_128(H_lo, H_hi, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k]
+ local floor, W, K_lo, K_hi = floor, common_W, sha2_K_lo, sha2_K_hi
+ local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8]
+ local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8]
+ for pos = offs + 1, offs + size, 128 do
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16],
+ W[17], W[18], W[19], W[20], W[21], W[22], W[23], W[24], W[25], W[26], W[27], W[28], W[29], W[30], W[31], W[32] =
+ string_unpack(">i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4", str, pos)
+ for jj = 17*2, 80*2, 2 do
+ local a_lo, a_hi, b_lo, b_hi = W[jj-30], W[jj-31], W[jj-4], W[jj-5]
+ local tmp =
+ (a_lo>>1 ~ a_hi<<31 ~ a_lo>>8 ~ a_hi<<24 ~ a_lo>>7 ~ a_hi<<25) % 2^32
+ + (b_lo>>19 ~ b_hi<<13 ~ b_lo<<3 ~ b_hi>>29 ~ b_lo>>6 ~ b_hi<<26) % 2^32
+ + W[jj-14] % 2^32 + W[jj-32] % 2^32
+ W[jj-1] =
+ (a_hi>>1 ~ a_lo<<31 ~ a_hi>>8 ~ a_lo<<24 ~ a_hi>>7)
+ + (b_hi>>19 ~ b_lo<<13 ~ b_hi<<3 ~ b_lo>>29 ~ b_hi>>6)
+ + W[jj-15] + W[jj-33] + floor(tmp / 2^32)
+ W[jj] = 0|((tmp + 2^31) % 2^32 - 2^31)
+ end
+ local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo
+ local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi
+ for j = 1, 80 do
+ local jj = 2*j
+ local z_lo = (e_lo>>14 ~ e_hi<<18 ~ e_lo>>18 ~ e_hi<<14 ~ e_lo<<23 ~ e_hi>>9) % 2^32 + (g_lo ~ e_lo & (f_lo ~ g_lo)) % 2^32 + h_lo % 2^32 + K_lo[j] + W[jj] % 2^32
+ local z_hi = (e_hi>>14 ~ e_lo<<18 ~ e_hi>>18 ~ e_lo<<14 ~ e_hi<<23 ~ e_lo>>9) + (g_hi ~ e_hi & (f_hi ~ g_hi)) + h_hi + K_hi[j] + W[jj-1] + floor(z_lo / 2^32)
+ z_lo = z_lo % 2^32
+ h_lo = g_lo; h_hi = g_hi
+ g_lo = f_lo; g_hi = f_hi
+ f_lo = e_lo; f_hi = e_hi
+ e_lo = z_lo + d_lo % 2^32
+ e_hi = z_hi + d_hi + floor(e_lo / 2^32)
+ e_lo = 0|((e_lo + 2^31) % 2^32 - 2^31)
+ d_lo = c_lo; d_hi = c_hi
+ c_lo = b_lo; c_hi = b_hi
+ b_lo = a_lo; b_hi = a_hi
+ z_lo = z_lo + (d_lo & c_lo ~ b_lo & (d_lo ~ c_lo)) % 2^32 + (b_lo>>28 ~ b_hi<<4 ~ b_lo<<30 ~ b_hi>>2 ~ b_lo<<25 ~ b_hi>>7) % 2^32
+ a_hi = z_hi + (d_hi & c_hi ~ b_hi & (d_hi ~ c_hi)) + (b_hi>>28 ~ b_lo<<4 ~ b_hi<<30 ~ b_lo>>2 ~ b_hi<<25 ~ b_lo>>7) + floor(z_lo / 2^32)
+ a_lo = 0|((z_lo + 2^31) % 2^32 - 2^31)
+ end
+ a_lo = h1_lo % 2^32 + a_lo % 2^32
+ h1_hi = h1_hi + a_hi + floor(a_lo / 2^32)
+ h1_lo = 0|((a_lo + 2^31) % 2^32 - 2^31)
+ a_lo = h2_lo % 2^32 + b_lo % 2^32
+ h2_hi = h2_hi + b_hi + floor(a_lo / 2^32)
+ h2_lo = 0|((a_lo + 2^31) % 2^32 - 2^31)
+ a_lo = h3_lo % 2^32 + c_lo % 2^32
+ h3_hi = h3_hi + c_hi + floor(a_lo / 2^32)
+ h3_lo = 0|((a_lo + 2^31) % 2^32 - 2^31)
+ a_lo = h4_lo % 2^32 + d_lo % 2^32
+ h4_hi = h4_hi + d_hi + floor(a_lo / 2^32)
+ h4_lo = 0|((a_lo + 2^31) % 2^32 - 2^31)
+ a_lo = h5_lo % 2^32 + e_lo % 2^32
+ h5_hi = h5_hi + e_hi + floor(a_lo / 2^32)
+ h5_lo = 0|((a_lo + 2^31) % 2^32 - 2^31)
+ a_lo = h6_lo % 2^32 + f_lo % 2^32
+ h6_hi = h6_hi + f_hi + floor(a_lo / 2^32)
+ h6_lo = 0|((a_lo + 2^31) % 2^32 - 2^31)
+ a_lo = h7_lo % 2^32 + g_lo % 2^32
+ h7_hi = h7_hi + g_hi + floor(a_lo / 2^32)
+ h7_lo = 0|((a_lo + 2^31) % 2^32 - 2^31)
+ a_lo = h8_lo % 2^32 + h_lo % 2^32
+ h8_hi = h8_hi + h_hi + floor(a_lo / 2^32)
+ h8_lo = 0|((a_lo + 2^31) % 2^32 - 2^31)
+ end
+ H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo
+ H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi
+ end
+
+ local function md5_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K, md5_next_shift = common_W, md5_K, md5_next_shift
+ local h1, h2, h3, h4 = H[1], H[2], H[3], H[4]
+ for pos = offs + 1, offs + size, 64 do
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack(">s) + b
+ s = md5_next_shift[s]
+ end
+ s = 32-5
+ for j = 17, 32 do
+ local F = (c ~ d & (b ~ c)) + a + K[j] + W[(5*j-4 & 15) + 1]
+ a = d
+ d = c
+ c = b
+ b = (F << 32-s | F>>s) + b
+ s = md5_next_shift[s]
+ end
+ s = 32-4
+ for j = 33, 48 do
+ local F = (b ~ c ~ d) + a + K[j] + W[(3*j+2 & 15) + 1]
+ a = d
+ d = c
+ c = b
+ b = (F << 32-s | F>>s) + b
+ s = md5_next_shift[s]
+ end
+ s = 32-6
+ for j = 49, 64 do
+ local F = (c ~ (b | ~d)) + a + K[j] + W[(j*7-7 & 15) + 1]
+ a = d
+ d = c
+ c = b
+ b = (F << 32-s | F>>s) + b
+ s = md5_next_shift[s]
+ end
+ h1 = a + h1
+ h2 = b + h2
+ h3 = c + h3
+ h4 = d + h4
+ end
+ H[1], H[2], H[3], H[4] = h1, h2, h3, h4
+ end
+
+ local function sha1_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W = common_W
+ local h1, h2, h3, h4, h5 = H[1], H[2], H[3], H[4], H[5]
+ for pos = offs + 1, offs + size, 64 do
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack(">i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4", str, pos)
+ for j = 17, 80 do
+ local a = W[j-3] ~ W[j-8] ~ W[j-14] ~ W[j-16]
+ W[j] = a << 1 ~ a >> 31
+ end
+ local a, b, c, d, e = h1, h2, h3, h4, h5
+ for j = 1, 20 do
+ local z = (a << 5 ~ a >> 27) + (d ~ b & (c ~ d)) + 0x5A827999 + W[j] + e -- constant = floor(2^30 * sqrt(2))
+ e = d
+ d = c
+ c = b << 30 ~ b >> 2
+ b = a
+ a = z
+ end
+ for j = 21, 40 do
+ local z = (a << 5 ~ a >> 27) + (b ~ c ~ d) + 0x6ED9EBA1 + W[j] + e -- 2^30 * sqrt(3)
+ e = d
+ d = c
+ c = b << 30 ~ b >> 2
+ b = a
+ a = z
+ end
+ for j = 41, 60 do
+ local z = (a << 5 ~ a >> 27) + ((b ~ c) & d ~ b & c) + 0x8F1BBCDC + W[j] + e -- 2^30 * sqrt(5)
+ e = d
+ d = c
+ c = b << 30 ~ b >> 2
+ b = a
+ a = z
+ end
+ for j = 61, 80 do
+ local z = (a << 5 ~ a >> 27) + (b ~ c ~ d) + 0xCA62C1D6 + W[j] + e -- 2^30 * sqrt(10)
+ e = d
+ d = c
+ c = b << 30 ~ b >> 2
+ b = a
+ a = z
+ end
+ h1 = a + h1
+ h2 = b + h2
+ h3 = c + h3
+ h4 = d + h4
+ h5 = e + h5
+ end
+ H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5
+ end
+
+ local keccak_format_i4i4 = build_keccak_format("i4i4")
+
+ local function keccak_feed(lanes_lo, lanes_hi, str, offs, size, block_size_in_bytes)
+ -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8
+ local RC_lo, RC_hi = sha3_RC_lo, sha3_RC_hi
+ local qwords_qty = block_size_in_bytes / 8
+ local keccak_format = keccak_format_i4i4[qwords_qty]
+ for pos = offs + 1, offs + size, block_size_in_bytes do
+ local dwords_from_message = {string_unpack(keccak_format, str, pos)}
+ for j = 1, qwords_qty do
+ lanes_lo[j] = lanes_lo[j] ~ dwords_from_message[2*j-1]
+ lanes_hi[j] = lanes_hi[j] ~ dwords_from_message[2*j]
+ end
+ local L01_lo, L01_hi, L02_lo, L02_hi, L03_lo, L03_hi, L04_lo, L04_hi, L05_lo, L05_hi, L06_lo, L06_hi, L07_lo, L07_hi, L08_lo, L08_hi,
+ L09_lo, L09_hi, L10_lo, L10_hi, L11_lo, L11_hi, L12_lo, L12_hi, L13_lo, L13_hi, L14_lo, L14_hi, L15_lo, L15_hi, L16_lo, L16_hi,
+ L17_lo, L17_hi, L18_lo, L18_hi, L19_lo, L19_hi, L20_lo, L20_hi, L21_lo, L21_hi, L22_lo, L22_hi, L23_lo, L23_hi, L24_lo, L24_hi, L25_lo, L25_hi =
+ lanes_lo[1], lanes_hi[1], lanes_lo[2], lanes_hi[2], lanes_lo[3], lanes_hi[3], lanes_lo[4], lanes_hi[4], lanes_lo[5], lanes_hi[5],
+ lanes_lo[6], lanes_hi[6], lanes_lo[7], lanes_hi[7], lanes_lo[8], lanes_hi[8], lanes_lo[9], lanes_hi[9], lanes_lo[10], lanes_hi[10],
+ lanes_lo[11], lanes_hi[11], lanes_lo[12], lanes_hi[12], lanes_lo[13], lanes_hi[13], lanes_lo[14], lanes_hi[14], lanes_lo[15], lanes_hi[15],
+ lanes_lo[16], lanes_hi[16], lanes_lo[17], lanes_hi[17], lanes_lo[18], lanes_hi[18], lanes_lo[19], lanes_hi[19], lanes_lo[20], lanes_hi[20],
+ lanes_lo[21], lanes_hi[21], lanes_lo[22], lanes_hi[22], lanes_lo[23], lanes_hi[23], lanes_lo[24], lanes_hi[24], lanes_lo[25], lanes_hi[25]
+ for round_idx = 1, 24 do
+ local C1_lo = L01_lo ~ L06_lo ~ L11_lo ~ L16_lo ~ L21_lo
+ local C1_hi = L01_hi ~ L06_hi ~ L11_hi ~ L16_hi ~ L21_hi
+ local C2_lo = L02_lo ~ L07_lo ~ L12_lo ~ L17_lo ~ L22_lo
+ local C2_hi = L02_hi ~ L07_hi ~ L12_hi ~ L17_hi ~ L22_hi
+ local C3_lo = L03_lo ~ L08_lo ~ L13_lo ~ L18_lo ~ L23_lo
+ local C3_hi = L03_hi ~ L08_hi ~ L13_hi ~ L18_hi ~ L23_hi
+ local C4_lo = L04_lo ~ L09_lo ~ L14_lo ~ L19_lo ~ L24_lo
+ local C4_hi = L04_hi ~ L09_hi ~ L14_hi ~ L19_hi ~ L24_hi
+ local C5_lo = L05_lo ~ L10_lo ~ L15_lo ~ L20_lo ~ L25_lo
+ local C5_hi = L05_hi ~ L10_hi ~ L15_hi ~ L20_hi ~ L25_hi
+ local D_lo = C1_lo ~ C3_lo<<1 ~ C3_hi>>31
+ local D_hi = C1_hi ~ C3_hi<<1 ~ C3_lo>>31
+ local T0_lo = D_lo ~ L02_lo
+ local T0_hi = D_hi ~ L02_hi
+ local T1_lo = D_lo ~ L07_lo
+ local T1_hi = D_hi ~ L07_hi
+ local T2_lo = D_lo ~ L12_lo
+ local T2_hi = D_hi ~ L12_hi
+ local T3_lo = D_lo ~ L17_lo
+ local T3_hi = D_hi ~ L17_hi
+ local T4_lo = D_lo ~ L22_lo
+ local T4_hi = D_hi ~ L22_hi
+ L02_lo = T1_lo>>20 ~ T1_hi<<12
+ L02_hi = T1_hi>>20 ~ T1_lo<<12
+ L07_lo = T3_lo>>19 ~ T3_hi<<13
+ L07_hi = T3_hi>>19 ~ T3_lo<<13
+ L12_lo = T0_lo<<1 ~ T0_hi>>31
+ L12_hi = T0_hi<<1 ~ T0_lo>>31
+ L17_lo = T2_lo<<10 ~ T2_hi>>22
+ L17_hi = T2_hi<<10 ~ T2_lo>>22
+ L22_lo = T4_lo<<2 ~ T4_hi>>30
+ L22_hi = T4_hi<<2 ~ T4_lo>>30
+ D_lo = C2_lo ~ C4_lo<<1 ~ C4_hi>>31
+ D_hi = C2_hi ~ C4_hi<<1 ~ C4_lo>>31
+ T0_lo = D_lo ~ L03_lo
+ T0_hi = D_hi ~ L03_hi
+ T1_lo = D_lo ~ L08_lo
+ T1_hi = D_hi ~ L08_hi
+ T2_lo = D_lo ~ L13_lo
+ T2_hi = D_hi ~ L13_hi
+ T3_lo = D_lo ~ L18_lo
+ T3_hi = D_hi ~ L18_hi
+ T4_lo = D_lo ~ L23_lo
+ T4_hi = D_hi ~ L23_hi
+ L03_lo = T2_lo>>21 ~ T2_hi<<11
+ L03_hi = T2_hi>>21 ~ T2_lo<<11
+ L08_lo = T4_lo>>3 ~ T4_hi<<29
+ L08_hi = T4_hi>>3 ~ T4_lo<<29
+ L13_lo = T1_lo<<6 ~ T1_hi>>26
+ L13_hi = T1_hi<<6 ~ T1_lo>>26
+ L18_lo = T3_lo<<15 ~ T3_hi>>17
+ L18_hi = T3_hi<<15 ~ T3_lo>>17
+ L23_lo = T0_lo>>2 ~ T0_hi<<30
+ L23_hi = T0_hi>>2 ~ T0_lo<<30
+ D_lo = C3_lo ~ C5_lo<<1 ~ C5_hi>>31
+ D_hi = C3_hi ~ C5_hi<<1 ~ C5_lo>>31
+ T0_lo = D_lo ~ L04_lo
+ T0_hi = D_hi ~ L04_hi
+ T1_lo = D_lo ~ L09_lo
+ T1_hi = D_hi ~ L09_hi
+ T2_lo = D_lo ~ L14_lo
+ T2_hi = D_hi ~ L14_hi
+ T3_lo = D_lo ~ L19_lo
+ T3_hi = D_hi ~ L19_hi
+ T4_lo = D_lo ~ L24_lo
+ T4_hi = D_hi ~ L24_hi
+ L04_lo = T3_lo<<21 ~ T3_hi>>11
+ L04_hi = T3_hi<<21 ~ T3_lo>>11
+ L09_lo = T0_lo<<28 ~ T0_hi>>4
+ L09_hi = T0_hi<<28 ~ T0_lo>>4
+ L14_lo = T2_lo<<25 ~ T2_hi>>7
+ L14_hi = T2_hi<<25 ~ T2_lo>>7
+ L19_lo = T4_lo>>8 ~ T4_hi<<24
+ L19_hi = T4_hi>>8 ~ T4_lo<<24
+ L24_lo = T1_lo>>9 ~ T1_hi<<23
+ L24_hi = T1_hi>>9 ~ T1_lo<<23
+ D_lo = C4_lo ~ C1_lo<<1 ~ C1_hi>>31
+ D_hi = C4_hi ~ C1_hi<<1 ~ C1_lo>>31
+ T0_lo = D_lo ~ L05_lo
+ T0_hi = D_hi ~ L05_hi
+ T1_lo = D_lo ~ L10_lo
+ T1_hi = D_hi ~ L10_hi
+ T2_lo = D_lo ~ L15_lo
+ T2_hi = D_hi ~ L15_hi
+ T3_lo = D_lo ~ L20_lo
+ T3_hi = D_hi ~ L20_hi
+ T4_lo = D_lo ~ L25_lo
+ T4_hi = D_hi ~ L25_hi
+ L05_lo = T4_lo<<14 ~ T4_hi>>18
+ L05_hi = T4_hi<<14 ~ T4_lo>>18
+ L10_lo = T1_lo<<20 ~ T1_hi>>12
+ L10_hi = T1_hi<<20 ~ T1_lo>>12
+ L15_lo = T3_lo<<8 ~ T3_hi>>24
+ L15_hi = T3_hi<<8 ~ T3_lo>>24
+ L20_lo = T0_lo<<27 ~ T0_hi>>5
+ L20_hi = T0_hi<<27 ~ T0_lo>>5
+ L25_lo = T2_lo>>25 ~ T2_hi<<7
+ L25_hi = T2_hi>>25 ~ T2_lo<<7
+ D_lo = C5_lo ~ C2_lo<<1 ~ C2_hi>>31
+ D_hi = C5_hi ~ C2_hi<<1 ~ C2_lo>>31
+ T1_lo = D_lo ~ L06_lo
+ T1_hi = D_hi ~ L06_hi
+ T2_lo = D_lo ~ L11_lo
+ T2_hi = D_hi ~ L11_hi
+ T3_lo = D_lo ~ L16_lo
+ T3_hi = D_hi ~ L16_hi
+ T4_lo = D_lo ~ L21_lo
+ T4_hi = D_hi ~ L21_hi
+ L06_lo = T2_lo<<3 ~ T2_hi>>29
+ L06_hi = T2_hi<<3 ~ T2_lo>>29
+ L11_lo = T4_lo<<18 ~ T4_hi>>14
+ L11_hi = T4_hi<<18 ~ T4_lo>>14
+ L16_lo = T1_lo>>28 ~ T1_hi<<4
+ L16_hi = T1_hi>>28 ~ T1_lo<<4
+ L21_lo = T3_lo>>23 ~ T3_hi<<9
+ L21_hi = T3_hi>>23 ~ T3_lo<<9
+ L01_lo = D_lo ~ L01_lo
+ L01_hi = D_hi ~ L01_hi
+ L01_lo, L02_lo, L03_lo, L04_lo, L05_lo = L01_lo ~ ~L02_lo & L03_lo, L02_lo ~ ~L03_lo & L04_lo, L03_lo ~ ~L04_lo & L05_lo, L04_lo ~ ~L05_lo & L01_lo, L05_lo ~ ~L01_lo & L02_lo
+ L01_hi, L02_hi, L03_hi, L04_hi, L05_hi = L01_hi ~ ~L02_hi & L03_hi, L02_hi ~ ~L03_hi & L04_hi, L03_hi ~ ~L04_hi & L05_hi, L04_hi ~ ~L05_hi & L01_hi, L05_hi ~ ~L01_hi & L02_hi
+ L06_lo, L07_lo, L08_lo, L09_lo, L10_lo = L09_lo ~ ~L10_lo & L06_lo, L10_lo ~ ~L06_lo & L07_lo, L06_lo ~ ~L07_lo & L08_lo, L07_lo ~ ~L08_lo & L09_lo, L08_lo ~ ~L09_lo & L10_lo
+ L06_hi, L07_hi, L08_hi, L09_hi, L10_hi = L09_hi ~ ~L10_hi & L06_hi, L10_hi ~ ~L06_hi & L07_hi, L06_hi ~ ~L07_hi & L08_hi, L07_hi ~ ~L08_hi & L09_hi, L08_hi ~ ~L09_hi & L10_hi
+ L11_lo, L12_lo, L13_lo, L14_lo, L15_lo = L12_lo ~ ~L13_lo & L14_lo, L13_lo ~ ~L14_lo & L15_lo, L14_lo ~ ~L15_lo & L11_lo, L15_lo ~ ~L11_lo & L12_lo, L11_lo ~ ~L12_lo & L13_lo
+ L11_hi, L12_hi, L13_hi, L14_hi, L15_hi = L12_hi ~ ~L13_hi & L14_hi, L13_hi ~ ~L14_hi & L15_hi, L14_hi ~ ~L15_hi & L11_hi, L15_hi ~ ~L11_hi & L12_hi, L11_hi ~ ~L12_hi & L13_hi
+ L16_lo, L17_lo, L18_lo, L19_lo, L20_lo = L20_lo ~ ~L16_lo & L17_lo, L16_lo ~ ~L17_lo & L18_lo, L17_lo ~ ~L18_lo & L19_lo, L18_lo ~ ~L19_lo & L20_lo, L19_lo ~ ~L20_lo & L16_lo
+ L16_hi, L17_hi, L18_hi, L19_hi, L20_hi = L20_hi ~ ~L16_hi & L17_hi, L16_hi ~ ~L17_hi & L18_hi, L17_hi ~ ~L18_hi & L19_hi, L18_hi ~ ~L19_hi & L20_hi, L19_hi ~ ~L20_hi & L16_hi
+ L21_lo, L22_lo, L23_lo, L24_lo, L25_lo = L23_lo ~ ~L24_lo & L25_lo, L24_lo ~ ~L25_lo & L21_lo, L25_lo ~ ~L21_lo & L22_lo, L21_lo ~ ~L22_lo & L23_lo, L22_lo ~ ~L23_lo & L24_lo
+ L21_hi, L22_hi, L23_hi, L24_hi, L25_hi = L23_hi ~ ~L24_hi & L25_hi, L24_hi ~ ~L25_hi & L21_hi, L25_hi ~ ~L21_hi & L22_hi, L21_hi ~ ~L22_hi & L23_hi, L22_hi ~ ~L23_hi & L24_hi
+ L01_lo = L01_lo ~ RC_lo[round_idx]
+ L01_hi = L01_hi ~ RC_hi[round_idx]
+ end
+ lanes_lo[1] = L01_lo; lanes_hi[1] = L01_hi
+ lanes_lo[2] = L02_lo; lanes_hi[2] = L02_hi
+ lanes_lo[3] = L03_lo; lanes_hi[3] = L03_hi
+ lanes_lo[4] = L04_lo; lanes_hi[4] = L04_hi
+ lanes_lo[5] = L05_lo; lanes_hi[5] = L05_hi
+ lanes_lo[6] = L06_lo; lanes_hi[6] = L06_hi
+ lanes_lo[7] = L07_lo; lanes_hi[7] = L07_hi
+ lanes_lo[8] = L08_lo; lanes_hi[8] = L08_hi
+ lanes_lo[9] = L09_lo; lanes_hi[9] = L09_hi
+ lanes_lo[10] = L10_lo; lanes_hi[10] = L10_hi
+ lanes_lo[11] = L11_lo; lanes_hi[11] = L11_hi
+ lanes_lo[12] = L12_lo; lanes_hi[12] = L12_hi
+ lanes_lo[13] = L13_lo; lanes_hi[13] = L13_hi
+ lanes_lo[14] = L14_lo; lanes_hi[14] = L14_hi
+ lanes_lo[15] = L15_lo; lanes_hi[15] = L15_hi
+ lanes_lo[16] = L16_lo; lanes_hi[16] = L16_hi
+ lanes_lo[17] = L17_lo; lanes_hi[17] = L17_hi
+ lanes_lo[18] = L18_lo; lanes_hi[18] = L18_hi
+ lanes_lo[19] = L19_lo; lanes_hi[19] = L19_hi
+ lanes_lo[20] = L20_lo; lanes_hi[20] = L20_hi
+ lanes_lo[21] = L21_lo; lanes_hi[21] = L21_hi
+ lanes_lo[22] = L22_lo; lanes_hi[22] = L22_hi
+ lanes_lo[23] = L23_lo; lanes_hi[23] = L23_hi
+ lanes_lo[24] = L24_lo; lanes_hi[24] = L24_hi
+ lanes_lo[25] = L25_lo; lanes_hi[25] = L25_hi
+ end
+ end
+
+ local function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W = common_W
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for pos = offs + 1, offs + size, 64 do
+ if str then
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack("> 16 | vC << 16
+ v8 = v8 + vC
+ v4 = v4 ~ v8
+ v4 = v4 >> 12 | v4 << 20
+ v0 = v0 + v4 + W[row[2]]
+ vC = vC ~ v0
+ vC = vC >> 8 | vC << 24
+ v8 = v8 + vC
+ v4 = v4 ~ v8
+ v4 = v4 >> 7 | v4 << 25
+ v1 = v1 + v5 + W[row[3]]
+ vD = vD ~ v1
+ vD = vD >> 16 | vD << 16
+ v9 = v9 + vD
+ v5 = v5 ~ v9
+ v5 = v5 >> 12 | v5 << 20
+ v1 = v1 + v5 + W[row[4]]
+ vD = vD ~ v1
+ vD = vD >> 8 | vD << 24
+ v9 = v9 + vD
+ v5 = v5 ~ v9
+ v5 = v5 >> 7 | v5 << 25
+ v2 = v2 + v6 + W[row[5]]
+ vE = vE ~ v2
+ vE = vE >> 16 | vE << 16
+ vA = vA + vE
+ v6 = v6 ~ vA
+ v6 = v6 >> 12 | v6 << 20
+ v2 = v2 + v6 + W[row[6]]
+ vE = vE ~ v2
+ vE = vE >> 8 | vE << 24
+ vA = vA + vE
+ v6 = v6 ~ vA
+ v6 = v6 >> 7 | v6 << 25
+ v3 = v3 + v7 + W[row[7]]
+ vF = vF ~ v3
+ vF = vF >> 16 | vF << 16
+ vB = vB + vF
+ v7 = v7 ~ vB
+ v7 = v7 >> 12 | v7 << 20
+ v3 = v3 + v7 + W[row[8]]
+ vF = vF ~ v3
+ vF = vF >> 8 | vF << 24
+ vB = vB + vF
+ v7 = v7 ~ vB
+ v7 = v7 >> 7 | v7 << 25
+ v0 = v0 + v5 + W[row[9]]
+ vF = vF ~ v0
+ vF = vF >> 16 | vF << 16
+ vA = vA + vF
+ v5 = v5 ~ vA
+ v5 = v5 >> 12 | v5 << 20
+ v0 = v0 + v5 + W[row[10]]
+ vF = vF ~ v0
+ vF = vF >> 8 | vF << 24
+ vA = vA + vF
+ v5 = v5 ~ vA
+ v5 = v5 >> 7 | v5 << 25
+ v1 = v1 + v6 + W[row[11]]
+ vC = vC ~ v1
+ vC = vC >> 16 | vC << 16
+ vB = vB + vC
+ v6 = v6 ~ vB
+ v6 = v6 >> 12 | v6 << 20
+ v1 = v1 + v6 + W[row[12]]
+ vC = vC ~ v1
+ vC = vC >> 8 | vC << 24
+ vB = vB + vC
+ v6 = v6 ~ vB
+ v6 = v6 >> 7 | v6 << 25
+ v2 = v2 + v7 + W[row[13]]
+ vD = vD ~ v2
+ vD = vD >> 16 | vD << 16
+ v8 = v8 + vD
+ v7 = v7 ~ v8
+ v7 = v7 >> 12 | v7 << 20
+ v2 = v2 + v7 + W[row[14]]
+ vD = vD ~ v2
+ vD = vD >> 8 | vD << 24
+ v8 = v8 + vD
+ v7 = v7 ~ v8
+ v7 = v7 >> 7 | v7 << 25
+ v3 = v3 + v4 + W[row[15]]
+ vE = vE ~ v3
+ vE = vE >> 16 | vE << 16
+ v9 = v9 + vE
+ v4 = v4 ~ v9
+ v4 = v4 >> 12 | v4 << 20
+ v3 = v3 + v4 + W[row[16]]
+ vE = vE ~ v3
+ vE = vE >> 8 | vE << 24
+ v9 = v9 + vE
+ v4 = v4 ~ v9
+ v4 = v4 >> 7 | v4 << 25
+ end
+ h1 = h1 ~ v0 ~ v8
+ h2 = h2 ~ v1 ~ v9
+ h3 = h3 ~ v2 ~ vA
+ h4 = h4 ~ v3 ~ vB
+ h5 = h5 ~ v4 ~ vC
+ h6 = h6 ~ v5 ~ vD
+ h7 = h7 ~ v6 ~ vE
+ h8 = h8 ~ v7 ~ vF
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ return bytes_compressed
+ end
+
+ local function blake2b_feed_128(H_lo, H_hi, str, offs, size, bytes_compressed, last_block_size, is_last_node)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ local W = common_W
+ local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8]
+ local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8]
+ for pos = offs + 1, offs + size, 128 do
+ if str then
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16],
+ W[17], W[18], W[19], W[20], W[21], W[22], W[23], W[24], W[25], W[26], W[27], W[28], W[29], W[30], W[31], W[32] =
+ string_unpack("> 24 | v4_hi << 8, v4_hi >> 24 | v4_lo << 8
+ k = row[2] * 2
+ v0_lo = v0_lo % 2^32 + v4_lo % 2^32 + W[k-1] % 2^32
+ v0_hi = v0_hi + v4_hi + floor(v0_lo / 2^32) + W[k]
+ v0_lo = 0|((v0_lo + 2^31) % 2^32 - 2^31)
+ vC_lo, vC_hi = vC_lo ~ v0_lo, vC_hi ~ v0_hi
+ vC_lo, vC_hi = vC_lo >> 16 | vC_hi << 16, vC_hi >> 16 | vC_lo << 16
+ v8_lo = v8_lo % 2^32 + vC_lo % 2^32
+ v8_hi = v8_hi + vC_hi + floor(v8_lo / 2^32)
+ v8_lo = 0|((v8_lo + 2^31) % 2^32 - 2^31)
+ v4_lo, v4_hi = v4_lo ~ v8_lo, v4_hi ~ v8_hi
+ v4_lo, v4_hi = v4_lo << 1 | v4_hi >> 31, v4_hi << 1 | v4_lo >> 31
+ k = row[3] * 2
+ v1_lo = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32
+ v1_hi = v1_hi + v5_hi + floor(v1_lo / 2^32) + W[k]
+ v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31)
+ vD_lo, vD_hi = vD_hi ~ v1_hi, vD_lo ~ v1_lo
+ v9_lo = v9_lo % 2^32 + vD_lo % 2^32
+ v9_hi = v9_hi + vD_hi + floor(v9_lo / 2^32)
+ v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31)
+ v5_lo, v5_hi = v5_lo ~ v9_lo, v5_hi ~ v9_hi
+ v5_lo, v5_hi = v5_lo >> 24 | v5_hi << 8, v5_hi >> 24 | v5_lo << 8
+ k = row[4] * 2
+ v1_lo = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32
+ v1_hi = v1_hi + v5_hi + floor(v1_lo / 2^32) + W[k]
+ v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31)
+ vD_lo, vD_hi = vD_lo ~ v1_lo, vD_hi ~ v1_hi
+ vD_lo, vD_hi = vD_lo >> 16 | vD_hi << 16, vD_hi >> 16 | vD_lo << 16
+ v9_lo = v9_lo % 2^32 + vD_lo % 2^32
+ v9_hi = v9_hi + vD_hi + floor(v9_lo / 2^32)
+ v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31)
+ v5_lo, v5_hi = v5_lo ~ v9_lo, v5_hi ~ v9_hi
+ v5_lo, v5_hi = v5_lo << 1 | v5_hi >> 31, v5_hi << 1 | v5_lo >> 31
+ k = row[5] * 2
+ v2_lo = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32
+ v2_hi = v2_hi + v6_hi + floor(v2_lo / 2^32) + W[k]
+ v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31)
+ vE_lo, vE_hi = vE_hi ~ v2_hi, vE_lo ~ v2_lo
+ vA_lo = vA_lo % 2^32 + vE_lo % 2^32
+ vA_hi = vA_hi + vE_hi + floor(vA_lo / 2^32)
+ vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31)
+ v6_lo, v6_hi = v6_lo ~ vA_lo, v6_hi ~ vA_hi
+ v6_lo, v6_hi = v6_lo >> 24 | v6_hi << 8, v6_hi >> 24 | v6_lo << 8
+ k = row[6] * 2
+ v2_lo = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32
+ v2_hi = v2_hi + v6_hi + floor(v2_lo / 2^32) + W[k]
+ v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31)
+ vE_lo, vE_hi = vE_lo ~ v2_lo, vE_hi ~ v2_hi
+ vE_lo, vE_hi = vE_lo >> 16 | vE_hi << 16, vE_hi >> 16 | vE_lo << 16
+ vA_lo = vA_lo % 2^32 + vE_lo % 2^32
+ vA_hi = vA_hi + vE_hi + floor(vA_lo / 2^32)
+ vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31)
+ v6_lo, v6_hi = v6_lo ~ vA_lo, v6_hi ~ vA_hi
+ v6_lo, v6_hi = v6_lo << 1 | v6_hi >> 31, v6_hi << 1 | v6_lo >> 31
+ k = row[7] * 2
+ v3_lo = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32
+ v3_hi = v3_hi + v7_hi + floor(v3_lo / 2^32) + W[k]
+ v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31)
+ vF_lo, vF_hi = vF_hi ~ v3_hi, vF_lo ~ v3_lo
+ vB_lo = vB_lo % 2^32 + vF_lo % 2^32
+ vB_hi = vB_hi + vF_hi + floor(vB_lo / 2^32)
+ vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31)
+ v7_lo, v7_hi = v7_lo ~ vB_lo, v7_hi ~ vB_hi
+ v7_lo, v7_hi = v7_lo >> 24 | v7_hi << 8, v7_hi >> 24 | v7_lo << 8
+ k = row[8] * 2
+ v3_lo = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32
+ v3_hi = v3_hi + v7_hi + floor(v3_lo / 2^32) + W[k]
+ v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31)
+ vF_lo, vF_hi = vF_lo ~ v3_lo, vF_hi ~ v3_hi
+ vF_lo, vF_hi = vF_lo >> 16 | vF_hi << 16, vF_hi >> 16 | vF_lo << 16
+ vB_lo = vB_lo % 2^32 + vF_lo % 2^32
+ vB_hi = vB_hi + vF_hi + floor(vB_lo / 2^32)
+ vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31)
+ v7_lo, v7_hi = v7_lo ~ vB_lo, v7_hi ~ vB_hi
+ v7_lo, v7_hi = v7_lo << 1 | v7_hi >> 31, v7_hi << 1 | v7_lo >> 31
+ k = row[9] * 2
+ v0_lo = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32
+ v0_hi = v0_hi + v5_hi + floor(v0_lo / 2^32) + W[k]
+ v0_lo = 0|((v0_lo + 2^31) % 2^32 - 2^31)
+ vF_lo, vF_hi = vF_hi ~ v0_hi, vF_lo ~ v0_lo
+ vA_lo = vA_lo % 2^32 + vF_lo % 2^32
+ vA_hi = vA_hi + vF_hi + floor(vA_lo / 2^32)
+ vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31)
+ v5_lo, v5_hi = v5_lo ~ vA_lo, v5_hi ~ vA_hi
+ v5_lo, v5_hi = v5_lo >> 24 | v5_hi << 8, v5_hi >> 24 | v5_lo << 8
+ k = row[10] * 2
+ v0_lo = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32
+ v0_hi = v0_hi + v5_hi + floor(v0_lo / 2^32) + W[k]
+ v0_lo = 0|((v0_lo + 2^31) % 2^32 - 2^31)
+ vF_lo, vF_hi = vF_lo ~ v0_lo, vF_hi ~ v0_hi
+ vF_lo, vF_hi = vF_lo >> 16 | vF_hi << 16, vF_hi >> 16 | vF_lo << 16
+ vA_lo = vA_lo % 2^32 + vF_lo % 2^32
+ vA_hi = vA_hi + vF_hi + floor(vA_lo / 2^32)
+ vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31)
+ v5_lo, v5_hi = v5_lo ~ vA_lo, v5_hi ~ vA_hi
+ v5_lo, v5_hi = v5_lo << 1 | v5_hi >> 31, v5_hi << 1 | v5_lo >> 31
+ k = row[11] * 2
+ v1_lo = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32
+ v1_hi = v1_hi + v6_hi + floor(v1_lo / 2^32) + W[k]
+ v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31)
+ vC_lo, vC_hi = vC_hi ~ v1_hi, vC_lo ~ v1_lo
+ vB_lo = vB_lo % 2^32 + vC_lo % 2^32
+ vB_hi = vB_hi + vC_hi + floor(vB_lo / 2^32)
+ vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31)
+ v6_lo, v6_hi = v6_lo ~ vB_lo, v6_hi ~ vB_hi
+ v6_lo, v6_hi = v6_lo >> 24 | v6_hi << 8, v6_hi >> 24 | v6_lo << 8
+ k = row[12] * 2
+ v1_lo = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32
+ v1_hi = v1_hi + v6_hi + floor(v1_lo / 2^32) + W[k]
+ v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31)
+ vC_lo, vC_hi = vC_lo ~ v1_lo, vC_hi ~ v1_hi
+ vC_lo, vC_hi = vC_lo >> 16 | vC_hi << 16, vC_hi >> 16 | vC_lo << 16
+ vB_lo = vB_lo % 2^32 + vC_lo % 2^32
+ vB_hi = vB_hi + vC_hi + floor(vB_lo / 2^32)
+ vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31)
+ v6_lo, v6_hi = v6_lo ~ vB_lo, v6_hi ~ vB_hi
+ v6_lo, v6_hi = v6_lo << 1 | v6_hi >> 31, v6_hi << 1 | v6_lo >> 31
+ k = row[13] * 2
+ v2_lo = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32
+ v2_hi = v2_hi + v7_hi + floor(v2_lo / 2^32) + W[k]
+ v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31)
+ vD_lo, vD_hi = vD_hi ~ v2_hi, vD_lo ~ v2_lo
+ v8_lo = v8_lo % 2^32 + vD_lo % 2^32
+ v8_hi = v8_hi + vD_hi + floor(v8_lo / 2^32)
+ v8_lo = 0|((v8_lo + 2^31) % 2^32 - 2^31)
+ v7_lo, v7_hi = v7_lo ~ v8_lo, v7_hi ~ v8_hi
+ v7_lo, v7_hi = v7_lo >> 24 | v7_hi << 8, v7_hi >> 24 | v7_lo << 8
+ k = row[14] * 2
+ v2_lo = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32
+ v2_hi = v2_hi + v7_hi + floor(v2_lo / 2^32) + W[k]
+ v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31)
+ vD_lo, vD_hi = vD_lo ~ v2_lo, vD_hi ~ v2_hi
+ vD_lo, vD_hi = vD_lo >> 16 | vD_hi << 16, vD_hi >> 16 | vD_lo << 16
+ v8_lo = v8_lo % 2^32 + vD_lo % 2^32
+ v8_hi = v8_hi + vD_hi + floor(v8_lo / 2^32)
+ v8_lo = 0|((v8_lo + 2^31) % 2^32 - 2^31)
+ v7_lo, v7_hi = v7_lo ~ v8_lo, v7_hi ~ v8_hi
+ v7_lo, v7_hi = v7_lo << 1 | v7_hi >> 31, v7_hi << 1 | v7_lo >> 31
+ k = row[15] * 2
+ v3_lo = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1] % 2^32
+ v3_hi = v3_hi + v4_hi + floor(v3_lo / 2^32) + W[k]
+ v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31)
+ vE_lo, vE_hi = vE_hi ~ v3_hi, vE_lo ~ v3_lo
+ v9_lo = v9_lo % 2^32 + vE_lo % 2^32
+ v9_hi = v9_hi + vE_hi + floor(v9_lo / 2^32)
+ v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31)
+ v4_lo, v4_hi = v4_lo ~ v9_lo, v4_hi ~ v9_hi
+ v4_lo, v4_hi = v4_lo >> 24 | v4_hi << 8, v4_hi >> 24 | v4_lo << 8
+ k = row[16] * 2
+ v3_lo = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1] % 2^32
+ v3_hi = v3_hi + v4_hi + floor(v3_lo / 2^32) + W[k]
+ v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31)
+ vE_lo, vE_hi = vE_lo ~ v3_lo, vE_hi ~ v3_hi
+ vE_lo, vE_hi = vE_lo >> 16 | vE_hi << 16, vE_hi >> 16 | vE_lo << 16
+ v9_lo = v9_lo % 2^32 + vE_lo % 2^32
+ v9_hi = v9_hi + vE_hi + floor(v9_lo / 2^32)
+ v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31)
+ v4_lo, v4_hi = v4_lo ~ v9_lo, v4_hi ~ v9_hi
+ v4_lo, v4_hi = v4_lo << 1 | v4_hi >> 31, v4_hi << 1 | v4_lo >> 31
+ end
+ h1_lo = h1_lo ~ v0_lo ~ v8_lo
+ h2_lo = h2_lo ~ v1_lo ~ v9_lo
+ h3_lo = h3_lo ~ v2_lo ~ vA_lo
+ h4_lo = h4_lo ~ v3_lo ~ vB_lo
+ h5_lo = h5_lo ~ v4_lo ~ vC_lo
+ h6_lo = h6_lo ~ v5_lo ~ vD_lo
+ h7_lo = h7_lo ~ v6_lo ~ vE_lo
+ h8_lo = h8_lo ~ v7_lo ~ vF_lo
+ h1_hi = h1_hi ~ v0_hi ~ v8_hi
+ h2_hi = h2_hi ~ v1_hi ~ v9_hi
+ h3_hi = h3_hi ~ v2_hi ~ vA_hi
+ h4_hi = h4_hi ~ v3_hi ~ vB_hi
+ h5_hi = h5_hi ~ v4_hi ~ vC_hi
+ h6_hi = h6_hi ~ v5_hi ~ vD_hi
+ h7_hi = h7_hi ~ v6_hi ~ vE_hi
+ h8_hi = h8_hi ~ v7_hi ~ vF_hi
+ end
+ H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo
+ H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi
+ return bytes_compressed
+ end
+
+ local function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ block_length = block_length or 64
+ local W = common_W
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H_in[1], H_in[2], H_in[3], H_in[4], H_in[5], H_in[6], H_in[7], H_in[8]
+ H_out = H_out or H_in
+ for pos = offs + 1, offs + size, 64 do
+ if str then
+ W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] =
+ string_unpack("> 16 | vC << 16
+ v8 = v8 + vC
+ v4 = v4 ~ v8
+ v4 = v4 >> 12 | v4 << 20
+ v0 = v0 + v4 + W[perm_blake3[j + 14]]
+ vC = vC ~ v0
+ vC = vC >> 8 | vC << 24
+ v8 = v8 + vC
+ v4 = v4 ~ v8
+ v4 = v4 >> 7 | v4 << 25
+ v1 = v1 + v5 + W[perm_blake3[j + 1]]
+ vD = vD ~ v1
+ vD = vD >> 16 | vD << 16
+ v9 = v9 + vD
+ v5 = v5 ~ v9
+ v5 = v5 >> 12 | v5 << 20
+ v1 = v1 + v5 + W[perm_blake3[j + 2]]
+ vD = vD ~ v1
+ vD = vD >> 8 | vD << 24
+ v9 = v9 + vD
+ v5 = v5 ~ v9
+ v5 = v5 >> 7 | v5 << 25
+ v2 = v2 + v6 + W[perm_blake3[j + 16]]
+ vE = vE ~ v2
+ vE = vE >> 16 | vE << 16
+ vA = vA + vE
+ v6 = v6 ~ vA
+ v6 = v6 >> 12 | v6 << 20
+ v2 = v2 + v6 + W[perm_blake3[j + 7]]
+ vE = vE ~ v2
+ vE = vE >> 8 | vE << 24
+ vA = vA + vE
+ v6 = v6 ~ vA
+ v6 = v6 >> 7 | v6 << 25
+ v3 = v3 + v7 + W[perm_blake3[j + 15]]
+ vF = vF ~ v3
+ vF = vF >> 16 | vF << 16
+ vB = vB + vF
+ v7 = v7 ~ vB
+ v7 = v7 >> 12 | v7 << 20
+ v3 = v3 + v7 + W[perm_blake3[j + 17]]
+ vF = vF ~ v3
+ vF = vF >> 8 | vF << 24
+ vB = vB + vF
+ v7 = v7 ~ vB
+ v7 = v7 >> 7 | v7 << 25
+ v0 = v0 + v5 + W[perm_blake3[j + 21]]
+ vF = vF ~ v0
+ vF = vF >> 16 | vF << 16
+ vA = vA + vF
+ v5 = v5 ~ vA
+ v5 = v5 >> 12 | v5 << 20
+ v0 = v0 + v5 + W[perm_blake3[j + 5]]
+ vF = vF ~ v0
+ vF = vF >> 8 | vF << 24
+ vA = vA + vF
+ v5 = v5 ~ vA
+ v5 = v5 >> 7 | v5 << 25
+ v1 = v1 + v6 + W[perm_blake3[j + 3]]
+ vC = vC ~ v1
+ vC = vC >> 16 | vC << 16
+ vB = vB + vC
+ v6 = v6 ~ vB
+ v6 = v6 >> 12 | v6 << 20
+ v1 = v1 + v6 + W[perm_blake3[j + 6]]
+ vC = vC ~ v1
+ vC = vC >> 8 | vC << 24
+ vB = vB + vC
+ v6 = v6 ~ vB
+ v6 = v6 >> 7 | v6 << 25
+ v2 = v2 + v7 + W[perm_blake3[j + 4]]
+ vD = vD ~ v2
+ vD = vD >> 16 | vD << 16
+ v8 = v8 + vD
+ v7 = v7 ~ v8
+ v7 = v7 >> 12 | v7 << 20
+ v2 = v2 + v7 + W[perm_blake3[j + 18]]
+ vD = vD ~ v2
+ vD = vD >> 8 | vD << 24
+ v8 = v8 + vD
+ v7 = v7 ~ v8
+ v7 = v7 >> 7 | v7 << 25
+ v3 = v3 + v4 + W[perm_blake3[j + 19]]
+ vE = vE ~ v3
+ vE = vE >> 16 | vE << 16
+ v9 = v9 + vE
+ v4 = v4 ~ v9
+ v4 = v4 >> 12 | v4 << 20
+ v3 = v3 + v4 + W[perm_blake3[j + 20]]
+ vE = vE ~ v3
+ vE = vE >> 8 | vE << 24
+ v9 = v9 + vE
+ v4 = v4 ~ v9
+ v4 = v4 >> 7 | v4 << 25
+ end
+ if wide_output then
+ H_out[ 9] = h1 ~ v8
+ H_out[10] = h2 ~ v9
+ H_out[11] = h3 ~ vA
+ H_out[12] = h4 ~ vB
+ H_out[13] = h5 ~ vC
+ H_out[14] = h6 ~ vD
+ H_out[15] = h7 ~ vE
+ H_out[16] = h8 ~ vF
+ end
+ h1 = v0 ~ v8
+ h2 = v1 ~ v9
+ h3 = v2 ~ vA
+ h4 = v3 ~ vB
+ h5 = v4 ~ vC
+ h6 = v5 ~ vD
+ h7 = v6 ~ vE
+ h8 = v7 ~ vF
+ end
+ H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ end
+
+ return XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64
+ ]=](md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sha3_RC_hi, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3)
+
+end
+
+XOR = XOR or XORA5
+
+if branch == "LIB32" or branch == "EMUL" then
+
+
+ -- implementation for Lua 5.1/5.2 (with or without bitwise library available)
+
+ function sha256_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K = common_W, sha2_K_hi
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for pos = offs, offs + size - 1, 64 do
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = ((a * 256 + b) * 256 + c) * 256 + d
+ end
+ for j = 17, 64 do
+ local a, b = W[j-15], W[j-2]
+ local a7, a18, b17, b19 = a / 2^7, a / 2^18, b / 2^17, b / 2^19
+ W[j] = (XOR(a7 % 1 * (2^32 - 1) + a7, a18 % 1 * (2^32 - 1) + a18, (a - a % 2^3) / 2^3) + W[j-16] + W[j-7]
+ + XOR(b17 % 1 * (2^32 - 1) + b17, b19 % 1 * (2^32 - 1) + b19, (b - b % 2^10) / 2^10)) % 2^32
+ end
+ local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8
+ for j = 1, 64 do
+ e = e % 2^32
+ local e6, e11, e7 = e / 2^6, e / 2^11, e * 2^7
+ local e7_lo = e7 % 2^32
+ local z = AND(e, f) + AND(-1-e, g) + h + K[j] + W[j]
+ + XOR(e6 % 1 * (2^32 - 1) + e6, e11 % 1 * (2^32 - 1) + e11, e7_lo + (e7 - e7_lo) / 2^32)
+ h = g
+ g = f
+ f = e
+ e = z + d
+ d = c
+ c = b
+ b = a % 2^32
+ local b2, b13, b10 = b / 2^2, b / 2^13, b * 2^10
+ local b10_lo = b10 % 2^32
+ a = z + AND(d, c) + AND(b, XOR(d, c)) +
+ XOR(b2 % 1 * (2^32 - 1) + b2, b13 % 1 * (2^32 - 1) + b13, b10_lo + (b10 - b10_lo) / 2^32)
+ end
+ h1, h2, h3, h4 = (a + h1) % 2^32, (b + h2) % 2^32, (c + h3) % 2^32, (d + h4) % 2^32
+ h5, h6, h7, h8 = (e + h5) % 2^32, (f + h6) % 2^32, (g + h7) % 2^32, (h + h8) % 2^32
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ end
+
+
+ function sha512_feed_128(H_lo, H_hi, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k]
+ local W, K_lo, K_hi = common_W, sha2_K_lo, sha2_K_hi
+ local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8]
+ local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8]
+ for pos = offs, offs + size - 1, 128 do
+ for j = 1, 16*2 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = ((a * 256 + b) * 256 + c) * 256 + d
+ end
+ for jj = 17*2, 80*2, 2 do
+ local a_hi, a_lo, b_hi, b_lo = W[jj-31], W[jj-30], W[jj-5], W[jj-4]
+ local b_hi_6, b_hi_19, b_hi_29, b_lo_19, b_lo_29, a_hi_1, a_hi_7, a_hi_8, a_lo_1, a_lo_8 =
+ b_hi % 2^6, b_hi % 2^19, b_hi % 2^29, b_lo % 2^19, b_lo % 2^29, a_hi % 2^1, a_hi % 2^7, a_hi % 2^8, a_lo % 2^1, a_lo % 2^8
+ local tmp1 = XOR((a_lo - a_lo_1) / 2^1 + a_hi_1 * 2^31, (a_lo - a_lo_8) / 2^8 + a_hi_8 * 2^24, (a_lo - a_lo % 2^7) / 2^7 + a_hi_7 * 2^25) % 2^32
+ + XOR((b_lo - b_lo_19) / 2^19 + b_hi_19 * 2^13, b_lo_29 * 2^3 + (b_hi - b_hi_29) / 2^29, (b_lo - b_lo % 2^6) / 2^6 + b_hi_6 * 2^26) % 2^32
+ + W[jj-14] + W[jj-32]
+ local tmp2 = tmp1 % 2^32
+ W[jj-1] = (XOR((a_hi - a_hi_1) / 2^1 + a_lo_1 * 2^31, (a_hi - a_hi_8) / 2^8 + a_lo_8 * 2^24, (a_hi - a_hi_7) / 2^7)
+ + XOR((b_hi - b_hi_19) / 2^19 + b_lo_19 * 2^13, b_hi_29 * 2^3 + (b_lo - b_lo_29) / 2^29, (b_hi - b_hi_6) / 2^6)
+ + W[jj-15] + W[jj-33] + (tmp1 - tmp2) / 2^32) % 2^32
+ W[jj] = tmp2
+ end
+ local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo
+ local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi
+ for j = 1, 80 do
+ local jj = 2*j
+ local e_lo_9, e_lo_14, e_lo_18, e_hi_9, e_hi_14, e_hi_18 = e_lo % 2^9, e_lo % 2^14, e_lo % 2^18, e_hi % 2^9, e_hi % 2^14, e_hi % 2^18
+ local tmp1 = (AND(e_lo, f_lo) + AND(-1-e_lo, g_lo)) % 2^32 + h_lo + K_lo[j] + W[jj]
+ + XOR((e_lo - e_lo_14) / 2^14 + e_hi_14 * 2^18, (e_lo - e_lo_18) / 2^18 + e_hi_18 * 2^14, e_lo_9 * 2^23 + (e_hi - e_hi_9) / 2^9) % 2^32
+ local z_lo = tmp1 % 2^32
+ local z_hi = AND(e_hi, f_hi) + AND(-1-e_hi, g_hi) + h_hi + K_hi[j] + W[jj-1] + (tmp1 - z_lo) / 2^32
+ + XOR((e_hi - e_hi_14) / 2^14 + e_lo_14 * 2^18, (e_hi - e_hi_18) / 2^18 + e_lo_18 * 2^14, e_hi_9 * 2^23 + (e_lo - e_lo_9) / 2^9)
+ h_lo = g_lo; h_hi = g_hi
+ g_lo = f_lo; g_hi = f_hi
+ f_lo = e_lo; f_hi = e_hi
+ tmp1 = z_lo + d_lo
+ e_lo = tmp1 % 2^32
+ e_hi = (z_hi + d_hi + (tmp1 - e_lo) / 2^32) % 2^32
+ d_lo = c_lo; d_hi = c_hi
+ c_lo = b_lo; c_hi = b_hi
+ b_lo = a_lo; b_hi = a_hi
+ local b_lo_2, b_lo_7, b_lo_28, b_hi_2, b_hi_7, b_hi_28 = b_lo % 2^2, b_lo % 2^7, b_lo % 2^28, b_hi % 2^2, b_hi % 2^7, b_hi % 2^28
+ tmp1 = z_lo + (AND(d_lo, c_lo) + AND(b_lo, XOR(d_lo, c_lo))) % 2^32
+ + XOR((b_lo - b_lo_28) / 2^28 + b_hi_28 * 2^4, b_lo_2 * 2^30 + (b_hi - b_hi_2) / 2^2, b_lo_7 * 2^25 + (b_hi - b_hi_7) / 2^7) % 2^32
+ a_lo = tmp1 % 2^32
+ a_hi = (z_hi + AND(d_hi, c_hi) + AND(b_hi, XOR(d_hi, c_hi)) + (tmp1 - a_lo) / 2^32
+ + XOR((b_hi - b_hi_28) / 2^28 + b_lo_28 * 2^4, b_hi_2 * 2^30 + (b_lo - b_lo_2) / 2^2, b_hi_7 * 2^25 + (b_lo - b_lo_7) / 2^7)) % 2^32
+ end
+ a_lo = h1_lo + a_lo
+ h1_lo = a_lo % 2^32
+ h1_hi = (h1_hi + a_hi + (a_lo - h1_lo) / 2^32) % 2^32
+ a_lo = h2_lo + b_lo
+ h2_lo = a_lo % 2^32
+ h2_hi = (h2_hi + b_hi + (a_lo - h2_lo) / 2^32) % 2^32
+ a_lo = h3_lo + c_lo
+ h3_lo = a_lo % 2^32
+ h3_hi = (h3_hi + c_hi + (a_lo - h3_lo) / 2^32) % 2^32
+ a_lo = h4_lo + d_lo
+ h4_lo = a_lo % 2^32
+ h4_hi = (h4_hi + d_hi + (a_lo - h4_lo) / 2^32) % 2^32
+ a_lo = h5_lo + e_lo
+ h5_lo = a_lo % 2^32
+ h5_hi = (h5_hi + e_hi + (a_lo - h5_lo) / 2^32) % 2^32
+ a_lo = h6_lo + f_lo
+ h6_lo = a_lo % 2^32
+ h6_hi = (h6_hi + f_hi + (a_lo - h6_lo) / 2^32) % 2^32
+ a_lo = h7_lo + g_lo
+ h7_lo = a_lo % 2^32
+ h7_hi = (h7_hi + g_hi + (a_lo - h7_lo) / 2^32) % 2^32
+ a_lo = h8_lo + h_lo
+ h8_lo = a_lo % 2^32
+ h8_hi = (h8_hi + h_hi + (a_lo - h8_lo) / 2^32) % 2^32
+ end
+ H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo
+ H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi
+ end
+
+
+ if branch == "LIB32" then
+
+ function md5_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K, md5_next_shift = common_W, md5_K, md5_next_shift
+ local h1, h2, h3, h4 = H[1], H[2], H[3], H[4]
+ for pos = offs, offs + size - 1, 64 do
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = ((d * 256 + c) * 256 + b) * 256 + a
+ end
+ local a, b, c, d = h1, h2, h3, h4
+ local s = 25
+ for j = 1, 16 do
+ local F = ROR(AND(b, c) + AND(-1-b, d) + a + K[j] + W[j], s) + b
+ s = md5_next_shift[s]
+ a = d
+ d = c
+ c = b
+ b = F
+ end
+ s = 27
+ for j = 17, 32 do
+ local F = ROR(AND(d, b) + AND(-1-d, c) + a + K[j] + W[(5*j-4) % 16 + 1], s) + b
+ s = md5_next_shift[s]
+ a = d
+ d = c
+ c = b
+ b = F
+ end
+ s = 28
+ for j = 33, 48 do
+ local F = ROR(XOR(XOR(b, c), d) + a + K[j] + W[(3*j+2) % 16 + 1], s) + b
+ s = md5_next_shift[s]
+ a = d
+ d = c
+ c = b
+ b = F
+ end
+ s = 26
+ for j = 49, 64 do
+ local F = ROR(XOR(c, OR(b, -1-d)) + a + K[j] + W[(j*7-7) % 16 + 1], s) + b
+ s = md5_next_shift[s]
+ a = d
+ d = c
+ c = b
+ b = F
+ end
+ h1 = (a + h1) % 2^32
+ h2 = (b + h2) % 2^32
+ h3 = (c + h3) % 2^32
+ h4 = (d + h4) % 2^32
+ end
+ H[1], H[2], H[3], H[4] = h1, h2, h3, h4
+ end
+
+ elseif branch == "EMUL" then
+
+ function md5_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W, K, md5_next_shift = common_W, md5_K, md5_next_shift
+ local h1, h2, h3, h4 = H[1], H[2], H[3], H[4]
+ for pos = offs, offs + size - 1, 64 do
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = ((d * 256 + c) * 256 + b) * 256 + a
+ end
+ local a, b, c, d = h1, h2, h3, h4
+ local s = 25
+ for j = 1, 16 do
+ local z = (AND(b, c) + AND(-1-b, d) + a + K[j] + W[j]) % 2^32 / 2^s
+ local y = z % 1
+ s = md5_next_shift[s]
+ a = d
+ d = c
+ c = b
+ b = y * 2^32 + (z - y) + b
+ end
+ s = 27
+ for j = 17, 32 do
+ local z = (AND(d, b) + AND(-1-d, c) + a + K[j] + W[(5*j-4) % 16 + 1]) % 2^32 / 2^s
+ local y = z % 1
+ s = md5_next_shift[s]
+ a = d
+ d = c
+ c = b
+ b = y * 2^32 + (z - y) + b
+ end
+ s = 28
+ for j = 33, 48 do
+ local z = (XOR(XOR(b, c), d) + a + K[j] + W[(3*j+2) % 16 + 1]) % 2^32 / 2^s
+ local y = z % 1
+ s = md5_next_shift[s]
+ a = d
+ d = c
+ c = b
+ b = y * 2^32 + (z - y) + b
+ end
+ s = 26
+ for j = 49, 64 do
+ local z = (XOR(c, OR(b, -1-d)) + a + K[j] + W[(j*7-7) % 16 + 1]) % 2^32 / 2^s
+ local y = z % 1
+ s = md5_next_shift[s]
+ a = d
+ d = c
+ c = b
+ b = y * 2^32 + (z - y) + b
+ end
+ h1 = (a + h1) % 2^32
+ h2 = (b + h2) % 2^32
+ h3 = (c + h3) % 2^32
+ h4 = (d + h4) % 2^32
+ end
+ H[1], H[2], H[3], H[4] = h1, h2, h3, h4
+ end
+
+ end
+
+
+ function sha1_feed_64(H, str, offs, size)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W = common_W
+ local h1, h2, h3, h4, h5 = H[1], H[2], H[3], H[4], H[5]
+ for pos = offs, offs + size - 1, 64 do
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = ((a * 256 + b) * 256 + c) * 256 + d
+ end
+ for j = 17, 80 do
+ local a = XOR(W[j-3], W[j-8], W[j-14], W[j-16]) % 2^32 * 2
+ local b = a % 2^32
+ W[j] = b + (a - b) / 2^32
+ end
+ local a, b, c, d, e = h1, h2, h3, h4, h5
+ for j = 1, 20 do
+ local a5 = a * 2^5
+ local z = a5 % 2^32
+ z = z + (a5 - z) / 2^32 + AND(b, c) + AND(-1-b, d) + 0x5A827999 + W[j] + e -- constant = floor(2^30 * sqrt(2))
+ e = d
+ d = c
+ c = b / 2^2
+ c = c % 1 * (2^32 - 1) + c
+ b = a
+ a = z % 2^32
+ end
+ for j = 21, 40 do
+ local a5 = a * 2^5
+ local z = a5 % 2^32
+ z = z + (a5 - z) / 2^32 + XOR(b, c, d) + 0x6ED9EBA1 + W[j] + e -- 2^30 * sqrt(3)
+ e = d
+ d = c
+ c = b / 2^2
+ c = c % 1 * (2^32 - 1) + c
+ b = a
+ a = z % 2^32
+ end
+ for j = 41, 60 do
+ local a5 = a * 2^5
+ local z = a5 % 2^32
+ z = z + (a5 - z) / 2^32 + AND(d, c) + AND(b, XOR(d, c)) + 0x8F1BBCDC + W[j] + e -- 2^30 * sqrt(5)
+ e = d
+ d = c
+ c = b / 2^2
+ c = c % 1 * (2^32 - 1) + c
+ b = a
+ a = z % 2^32
+ end
+ for j = 61, 80 do
+ local a5 = a * 2^5
+ local z = a5 % 2^32
+ z = z + (a5 - z) / 2^32 + XOR(b, c, d) + 0xCA62C1D6 + W[j] + e -- 2^30 * sqrt(10)
+ e = d
+ d = c
+ c = b / 2^2
+ c = c % 1 * (2^32 - 1) + c
+ b = a
+ a = z % 2^32
+ end
+ h1 = (a + h1) % 2^32
+ h2 = (b + h2) % 2^32
+ h3 = (c + h3) % 2^32
+ h4 = (d + h4) % 2^32
+ h5 = (e + h5) % 2^32
+ end
+ H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5
+ end
+
+
+ function keccak_feed(lanes_lo, lanes_hi, str, offs, size, block_size_in_bytes)
+ -- This is an example of a Lua function having 79 local variables :-)
+ -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8
+ local RC_lo, RC_hi = sha3_RC_lo, sha3_RC_hi
+ local qwords_qty = block_size_in_bytes / 8
+ for pos = offs, offs + size - 1, block_size_in_bytes do
+ for j = 1, qwords_qty do
+ local a, b, c, d = byte(str, pos + 1, pos + 4)
+ lanes_lo[j] = XOR(lanes_lo[j], ((d * 256 + c) * 256 + b) * 256 + a)
+ pos = pos + 8
+ a, b, c, d = byte(str, pos - 3, pos)
+ lanes_hi[j] = XOR(lanes_hi[j], ((d * 256 + c) * 256 + b) * 256 + a)
+ end
+ local L01_lo, L01_hi, L02_lo, L02_hi, L03_lo, L03_hi, L04_lo, L04_hi, L05_lo, L05_hi, L06_lo, L06_hi, L07_lo, L07_hi, L08_lo, L08_hi,
+ L09_lo, L09_hi, L10_lo, L10_hi, L11_lo, L11_hi, L12_lo, L12_hi, L13_lo, L13_hi, L14_lo, L14_hi, L15_lo, L15_hi, L16_lo, L16_hi,
+ L17_lo, L17_hi, L18_lo, L18_hi, L19_lo, L19_hi, L20_lo, L20_hi, L21_lo, L21_hi, L22_lo, L22_hi, L23_lo, L23_hi, L24_lo, L24_hi, L25_lo, L25_hi =
+ lanes_lo[1], lanes_hi[1], lanes_lo[2], lanes_hi[2], lanes_lo[3], lanes_hi[3], lanes_lo[4], lanes_hi[4], lanes_lo[5], lanes_hi[5],
+ lanes_lo[6], lanes_hi[6], lanes_lo[7], lanes_hi[7], lanes_lo[8], lanes_hi[8], lanes_lo[9], lanes_hi[9], lanes_lo[10], lanes_hi[10],
+ lanes_lo[11], lanes_hi[11], lanes_lo[12], lanes_hi[12], lanes_lo[13], lanes_hi[13], lanes_lo[14], lanes_hi[14], lanes_lo[15], lanes_hi[15],
+ lanes_lo[16], lanes_hi[16], lanes_lo[17], lanes_hi[17], lanes_lo[18], lanes_hi[18], lanes_lo[19], lanes_hi[19], lanes_lo[20], lanes_hi[20],
+ lanes_lo[21], lanes_hi[21], lanes_lo[22], lanes_hi[22], lanes_lo[23], lanes_hi[23], lanes_lo[24], lanes_hi[24], lanes_lo[25], lanes_hi[25]
+ for round_idx = 1, 24 do
+ local C1_lo = XOR(L01_lo, L06_lo, L11_lo, L16_lo, L21_lo)
+ local C1_hi = XOR(L01_hi, L06_hi, L11_hi, L16_hi, L21_hi)
+ local C2_lo = XOR(L02_lo, L07_lo, L12_lo, L17_lo, L22_lo)
+ local C2_hi = XOR(L02_hi, L07_hi, L12_hi, L17_hi, L22_hi)
+ local C3_lo = XOR(L03_lo, L08_lo, L13_lo, L18_lo, L23_lo)
+ local C3_hi = XOR(L03_hi, L08_hi, L13_hi, L18_hi, L23_hi)
+ local C4_lo = XOR(L04_lo, L09_lo, L14_lo, L19_lo, L24_lo)
+ local C4_hi = XOR(L04_hi, L09_hi, L14_hi, L19_hi, L24_hi)
+ local C5_lo = XOR(L05_lo, L10_lo, L15_lo, L20_lo, L25_lo)
+ local C5_hi = XOR(L05_hi, L10_hi, L15_hi, L20_hi, L25_hi)
+ local D_lo = XOR(C1_lo, C3_lo * 2 + (C3_hi % 2^32 - C3_hi % 2^31) / 2^31)
+ local D_hi = XOR(C1_hi, C3_hi * 2 + (C3_lo % 2^32 - C3_lo % 2^31) / 2^31)
+ local T0_lo = XOR(D_lo, L02_lo)
+ local T0_hi = XOR(D_hi, L02_hi)
+ local T1_lo = XOR(D_lo, L07_lo)
+ local T1_hi = XOR(D_hi, L07_hi)
+ local T2_lo = XOR(D_lo, L12_lo)
+ local T2_hi = XOR(D_hi, L12_hi)
+ local T3_lo = XOR(D_lo, L17_lo)
+ local T3_hi = XOR(D_hi, L17_hi)
+ local T4_lo = XOR(D_lo, L22_lo)
+ local T4_hi = XOR(D_hi, L22_hi)
+ L02_lo = (T1_lo % 2^32 - T1_lo % 2^20) / 2^20 + T1_hi * 2^12
+ L02_hi = (T1_hi % 2^32 - T1_hi % 2^20) / 2^20 + T1_lo * 2^12
+ L07_lo = (T3_lo % 2^32 - T3_lo % 2^19) / 2^19 + T3_hi * 2^13
+ L07_hi = (T3_hi % 2^32 - T3_hi % 2^19) / 2^19 + T3_lo * 2^13
+ L12_lo = T0_lo * 2 + (T0_hi % 2^32 - T0_hi % 2^31) / 2^31
+ L12_hi = T0_hi * 2 + (T0_lo % 2^32 - T0_lo % 2^31) / 2^31
+ L17_lo = T2_lo * 2^10 + (T2_hi % 2^32 - T2_hi % 2^22) / 2^22
+ L17_hi = T2_hi * 2^10 + (T2_lo % 2^32 - T2_lo % 2^22) / 2^22
+ L22_lo = T4_lo * 2^2 + (T4_hi % 2^32 - T4_hi % 2^30) / 2^30
+ L22_hi = T4_hi * 2^2 + (T4_lo % 2^32 - T4_lo % 2^30) / 2^30
+ D_lo = XOR(C2_lo, C4_lo * 2 + (C4_hi % 2^32 - C4_hi % 2^31) / 2^31)
+ D_hi = XOR(C2_hi, C4_hi * 2 + (C4_lo % 2^32 - C4_lo % 2^31) / 2^31)
+ T0_lo = XOR(D_lo, L03_lo)
+ T0_hi = XOR(D_hi, L03_hi)
+ T1_lo = XOR(D_lo, L08_lo)
+ T1_hi = XOR(D_hi, L08_hi)
+ T2_lo = XOR(D_lo, L13_lo)
+ T2_hi = XOR(D_hi, L13_hi)
+ T3_lo = XOR(D_lo, L18_lo)
+ T3_hi = XOR(D_hi, L18_hi)
+ T4_lo = XOR(D_lo, L23_lo)
+ T4_hi = XOR(D_hi, L23_hi)
+ L03_lo = (T2_lo % 2^32 - T2_lo % 2^21) / 2^21 + T2_hi * 2^11
+ L03_hi = (T2_hi % 2^32 - T2_hi % 2^21) / 2^21 + T2_lo * 2^11
+ L08_lo = (T4_lo % 2^32 - T4_lo % 2^3) / 2^3 + T4_hi * 2^29 % 2^32
+ L08_hi = (T4_hi % 2^32 - T4_hi % 2^3) / 2^3 + T4_lo * 2^29 % 2^32
+ L13_lo = T1_lo * 2^6 + (T1_hi % 2^32 - T1_hi % 2^26) / 2^26
+ L13_hi = T1_hi * 2^6 + (T1_lo % 2^32 - T1_lo % 2^26) / 2^26
+ L18_lo = T3_lo * 2^15 + (T3_hi % 2^32 - T3_hi % 2^17) / 2^17
+ L18_hi = T3_hi * 2^15 + (T3_lo % 2^32 - T3_lo % 2^17) / 2^17
+ L23_lo = (T0_lo % 2^32 - T0_lo % 2^2) / 2^2 + T0_hi * 2^30 % 2^32
+ L23_hi = (T0_hi % 2^32 - T0_hi % 2^2) / 2^2 + T0_lo * 2^30 % 2^32
+ D_lo = XOR(C3_lo, C5_lo * 2 + (C5_hi % 2^32 - C5_hi % 2^31) / 2^31)
+ D_hi = XOR(C3_hi, C5_hi * 2 + (C5_lo % 2^32 - C5_lo % 2^31) / 2^31)
+ T0_lo = XOR(D_lo, L04_lo)
+ T0_hi = XOR(D_hi, L04_hi)
+ T1_lo = XOR(D_lo, L09_lo)
+ T1_hi = XOR(D_hi, L09_hi)
+ T2_lo = XOR(D_lo, L14_lo)
+ T2_hi = XOR(D_hi, L14_hi)
+ T3_lo = XOR(D_lo, L19_lo)
+ T3_hi = XOR(D_hi, L19_hi)
+ T4_lo = XOR(D_lo, L24_lo)
+ T4_hi = XOR(D_hi, L24_hi)
+ L04_lo = T3_lo * 2^21 % 2^32 + (T3_hi % 2^32 - T3_hi % 2^11) / 2^11
+ L04_hi = T3_hi * 2^21 % 2^32 + (T3_lo % 2^32 - T3_lo % 2^11) / 2^11
+ L09_lo = T0_lo * 2^28 % 2^32 + (T0_hi % 2^32 - T0_hi % 2^4) / 2^4
+ L09_hi = T0_hi * 2^28 % 2^32 + (T0_lo % 2^32 - T0_lo % 2^4) / 2^4
+ L14_lo = T2_lo * 2^25 % 2^32 + (T2_hi % 2^32 - T2_hi % 2^7) / 2^7
+ L14_hi = T2_hi * 2^25 % 2^32 + (T2_lo % 2^32 - T2_lo % 2^7) / 2^7
+ L19_lo = (T4_lo % 2^32 - T4_lo % 2^8) / 2^8 + T4_hi * 2^24 % 2^32
+ L19_hi = (T4_hi % 2^32 - T4_hi % 2^8) / 2^8 + T4_lo * 2^24 % 2^32
+ L24_lo = (T1_lo % 2^32 - T1_lo % 2^9) / 2^9 + T1_hi * 2^23 % 2^32
+ L24_hi = (T1_hi % 2^32 - T1_hi % 2^9) / 2^9 + T1_lo * 2^23 % 2^32
+ D_lo = XOR(C4_lo, C1_lo * 2 + (C1_hi % 2^32 - C1_hi % 2^31) / 2^31)
+ D_hi = XOR(C4_hi, C1_hi * 2 + (C1_lo % 2^32 - C1_lo % 2^31) / 2^31)
+ T0_lo = XOR(D_lo, L05_lo)
+ T0_hi = XOR(D_hi, L05_hi)
+ T1_lo = XOR(D_lo, L10_lo)
+ T1_hi = XOR(D_hi, L10_hi)
+ T2_lo = XOR(D_lo, L15_lo)
+ T2_hi = XOR(D_hi, L15_hi)
+ T3_lo = XOR(D_lo, L20_lo)
+ T3_hi = XOR(D_hi, L20_hi)
+ T4_lo = XOR(D_lo, L25_lo)
+ T4_hi = XOR(D_hi, L25_hi)
+ L05_lo = T4_lo * 2^14 + (T4_hi % 2^32 - T4_hi % 2^18) / 2^18
+ L05_hi = T4_hi * 2^14 + (T4_lo % 2^32 - T4_lo % 2^18) / 2^18
+ L10_lo = T1_lo * 2^20 % 2^32 + (T1_hi % 2^32 - T1_hi % 2^12) / 2^12
+ L10_hi = T1_hi * 2^20 % 2^32 + (T1_lo % 2^32 - T1_lo % 2^12) / 2^12
+ L15_lo = T3_lo * 2^8 + (T3_hi % 2^32 - T3_hi % 2^24) / 2^24
+ L15_hi = T3_hi * 2^8 + (T3_lo % 2^32 - T3_lo % 2^24) / 2^24
+ L20_lo = T0_lo * 2^27 % 2^32 + (T0_hi % 2^32 - T0_hi % 2^5) / 2^5
+ L20_hi = T0_hi * 2^27 % 2^32 + (T0_lo % 2^32 - T0_lo % 2^5) / 2^5
+ L25_lo = (T2_lo % 2^32 - T2_lo % 2^25) / 2^25 + T2_hi * 2^7
+ L25_hi = (T2_hi % 2^32 - T2_hi % 2^25) / 2^25 + T2_lo * 2^7
+ D_lo = XOR(C5_lo, C2_lo * 2 + (C2_hi % 2^32 - C2_hi % 2^31) / 2^31)
+ D_hi = XOR(C5_hi, C2_hi * 2 + (C2_lo % 2^32 - C2_lo % 2^31) / 2^31)
+ T1_lo = XOR(D_lo, L06_lo)
+ T1_hi = XOR(D_hi, L06_hi)
+ T2_lo = XOR(D_lo, L11_lo)
+ T2_hi = XOR(D_hi, L11_hi)
+ T3_lo = XOR(D_lo, L16_lo)
+ T3_hi = XOR(D_hi, L16_hi)
+ T4_lo = XOR(D_lo, L21_lo)
+ T4_hi = XOR(D_hi, L21_hi)
+ L06_lo = T2_lo * 2^3 + (T2_hi % 2^32 - T2_hi % 2^29) / 2^29
+ L06_hi = T2_hi * 2^3 + (T2_lo % 2^32 - T2_lo % 2^29) / 2^29
+ L11_lo = T4_lo * 2^18 + (T4_hi % 2^32 - T4_hi % 2^14) / 2^14
+ L11_hi = T4_hi * 2^18 + (T4_lo % 2^32 - T4_lo % 2^14) / 2^14
+ L16_lo = (T1_lo % 2^32 - T1_lo % 2^28) / 2^28 + T1_hi * 2^4
+ L16_hi = (T1_hi % 2^32 - T1_hi % 2^28) / 2^28 + T1_lo * 2^4
+ L21_lo = (T3_lo % 2^32 - T3_lo % 2^23) / 2^23 + T3_hi * 2^9
+ L21_hi = (T3_hi % 2^32 - T3_hi % 2^23) / 2^23 + T3_lo * 2^9
+ L01_lo = XOR(D_lo, L01_lo)
+ L01_hi = XOR(D_hi, L01_hi)
+ L01_lo, L02_lo, L03_lo, L04_lo, L05_lo = XOR(L01_lo, AND(-1-L02_lo, L03_lo)), XOR(L02_lo, AND(-1-L03_lo, L04_lo)), XOR(L03_lo, AND(-1-L04_lo, L05_lo)), XOR(L04_lo, AND(-1-L05_lo, L01_lo)), XOR(L05_lo, AND(-1-L01_lo, L02_lo))
+ L01_hi, L02_hi, L03_hi, L04_hi, L05_hi = XOR(L01_hi, AND(-1-L02_hi, L03_hi)), XOR(L02_hi, AND(-1-L03_hi, L04_hi)), XOR(L03_hi, AND(-1-L04_hi, L05_hi)), XOR(L04_hi, AND(-1-L05_hi, L01_hi)), XOR(L05_hi, AND(-1-L01_hi, L02_hi))
+ L06_lo, L07_lo, L08_lo, L09_lo, L10_lo = XOR(L09_lo, AND(-1-L10_lo, L06_lo)), XOR(L10_lo, AND(-1-L06_lo, L07_lo)), XOR(L06_lo, AND(-1-L07_lo, L08_lo)), XOR(L07_lo, AND(-1-L08_lo, L09_lo)), XOR(L08_lo, AND(-1-L09_lo, L10_lo))
+ L06_hi, L07_hi, L08_hi, L09_hi, L10_hi = XOR(L09_hi, AND(-1-L10_hi, L06_hi)), XOR(L10_hi, AND(-1-L06_hi, L07_hi)), XOR(L06_hi, AND(-1-L07_hi, L08_hi)), XOR(L07_hi, AND(-1-L08_hi, L09_hi)), XOR(L08_hi, AND(-1-L09_hi, L10_hi))
+ L11_lo, L12_lo, L13_lo, L14_lo, L15_lo = XOR(L12_lo, AND(-1-L13_lo, L14_lo)), XOR(L13_lo, AND(-1-L14_lo, L15_lo)), XOR(L14_lo, AND(-1-L15_lo, L11_lo)), XOR(L15_lo, AND(-1-L11_lo, L12_lo)), XOR(L11_lo, AND(-1-L12_lo, L13_lo))
+ L11_hi, L12_hi, L13_hi, L14_hi, L15_hi = XOR(L12_hi, AND(-1-L13_hi, L14_hi)), XOR(L13_hi, AND(-1-L14_hi, L15_hi)), XOR(L14_hi, AND(-1-L15_hi, L11_hi)), XOR(L15_hi, AND(-1-L11_hi, L12_hi)), XOR(L11_hi, AND(-1-L12_hi, L13_hi))
+ L16_lo, L17_lo, L18_lo, L19_lo, L20_lo = XOR(L20_lo, AND(-1-L16_lo, L17_lo)), XOR(L16_lo, AND(-1-L17_lo, L18_lo)), XOR(L17_lo, AND(-1-L18_lo, L19_lo)), XOR(L18_lo, AND(-1-L19_lo, L20_lo)), XOR(L19_lo, AND(-1-L20_lo, L16_lo))
+ L16_hi, L17_hi, L18_hi, L19_hi, L20_hi = XOR(L20_hi, AND(-1-L16_hi, L17_hi)), XOR(L16_hi, AND(-1-L17_hi, L18_hi)), XOR(L17_hi, AND(-1-L18_hi, L19_hi)), XOR(L18_hi, AND(-1-L19_hi, L20_hi)), XOR(L19_hi, AND(-1-L20_hi, L16_hi))
+ L21_lo, L22_lo, L23_lo, L24_lo, L25_lo = XOR(L23_lo, AND(-1-L24_lo, L25_lo)), XOR(L24_lo, AND(-1-L25_lo, L21_lo)), XOR(L25_lo, AND(-1-L21_lo, L22_lo)), XOR(L21_lo, AND(-1-L22_lo, L23_lo)), XOR(L22_lo, AND(-1-L23_lo, L24_lo))
+ L21_hi, L22_hi, L23_hi, L24_hi, L25_hi = XOR(L23_hi, AND(-1-L24_hi, L25_hi)), XOR(L24_hi, AND(-1-L25_hi, L21_hi)), XOR(L25_hi, AND(-1-L21_hi, L22_hi)), XOR(L21_hi, AND(-1-L22_hi, L23_hi)), XOR(L22_hi, AND(-1-L23_hi, L24_hi))
+ L01_lo = XOR(L01_lo, RC_lo[round_idx])
+ L01_hi = L01_hi + RC_hi[round_idx] -- RC_hi[] is either 0 or 0x80000000, so we could use fast addition instead of slow XOR
+ end
+ lanes_lo[1] = L01_lo; lanes_hi[1] = L01_hi
+ lanes_lo[2] = L02_lo; lanes_hi[2] = L02_hi
+ lanes_lo[3] = L03_lo; lanes_hi[3] = L03_hi
+ lanes_lo[4] = L04_lo; lanes_hi[4] = L04_hi
+ lanes_lo[5] = L05_lo; lanes_hi[5] = L05_hi
+ lanes_lo[6] = L06_lo; lanes_hi[6] = L06_hi
+ lanes_lo[7] = L07_lo; lanes_hi[7] = L07_hi
+ lanes_lo[8] = L08_lo; lanes_hi[8] = L08_hi
+ lanes_lo[9] = L09_lo; lanes_hi[9] = L09_hi
+ lanes_lo[10] = L10_lo; lanes_hi[10] = L10_hi
+ lanes_lo[11] = L11_lo; lanes_hi[11] = L11_hi
+ lanes_lo[12] = L12_lo; lanes_hi[12] = L12_hi
+ lanes_lo[13] = L13_lo; lanes_hi[13] = L13_hi
+ lanes_lo[14] = L14_lo; lanes_hi[14] = L14_hi
+ lanes_lo[15] = L15_lo; lanes_hi[15] = L15_hi
+ lanes_lo[16] = L16_lo; lanes_hi[16] = L16_hi
+ lanes_lo[17] = L17_lo; lanes_hi[17] = L17_hi
+ lanes_lo[18] = L18_lo; lanes_hi[18] = L18_hi
+ lanes_lo[19] = L19_lo; lanes_hi[19] = L19_hi
+ lanes_lo[20] = L20_lo; lanes_hi[20] = L20_hi
+ lanes_lo[21] = L21_lo; lanes_hi[21] = L21_hi
+ lanes_lo[22] = L22_lo; lanes_hi[22] = L22_hi
+ lanes_lo[23] = L23_lo; lanes_hi[23] = L23_hi
+ lanes_lo[24] = L24_lo; lanes_hi[24] = L24_hi
+ lanes_lo[25] = L25_lo; lanes_hi[25] = L25_hi
+ end
+ end
+
+
+ function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ local W = common_W
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
+ for pos = offs, offs + size - 1, 64 do
+ if str then
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = ((d * 256 + c) * 256 + b) * 256 + a
+ end
+ end
+ local v0, v1, v2, v3, v4, v5, v6, v7 = h1, h2, h3, h4, h5, h6, h7, h8
+ local v8, v9, vA, vB, vC, vD, vE, vF = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4], sha2_H_hi[5], sha2_H_hi[6], sha2_H_hi[7], sha2_H_hi[8]
+ bytes_compressed = bytes_compressed + (last_block_size or 64)
+ local t0 = bytes_compressed % 2^32
+ local t1 = (bytes_compressed - t0) / 2^32
+ vC = XOR(vC, t0) -- t0 = low_4_bytes(bytes_compressed)
+ vD = XOR(vD, t1) -- t1 = high_4_bytes(bytes_compressed)
+ if last_block_size then -- flag f0
+ vE = -1 - vE
+ end
+ if is_last_node then -- flag f1
+ vF = -1 - vF
+ end
+ for j = 1, 10 do
+ local row = sigma[j]
+ v0 = v0 + v4 + W[row[1]]
+ vC = XOR(vC, v0) % 2^32 / 2^16
+ vC = vC % 1 * (2^32 - 1) + vC
+ v8 = v8 + vC
+ v4 = XOR(v4, v8) % 2^32 / 2^12
+ v4 = v4 % 1 * (2^32 - 1) + v4
+ v0 = v0 + v4 + W[row[2]]
+ vC = XOR(vC, v0) % 2^32 / 2^8
+ vC = vC % 1 * (2^32 - 1) + vC
+ v8 = v8 + vC
+ v4 = XOR(v4, v8) % 2^32 / 2^7
+ v4 = v4 % 1 * (2^32 - 1) + v4
+ v1 = v1 + v5 + W[row[3]]
+ vD = XOR(vD, v1) % 2^32 / 2^16
+ vD = vD % 1 * (2^32 - 1) + vD
+ v9 = v9 + vD
+ v5 = XOR(v5, v9) % 2^32 / 2^12
+ v5 = v5 % 1 * (2^32 - 1) + v5
+ v1 = v1 + v5 + W[row[4]]
+ vD = XOR(vD, v1) % 2^32 / 2^8
+ vD = vD % 1 * (2^32 - 1) + vD
+ v9 = v9 + vD
+ v5 = XOR(v5, v9) % 2^32 / 2^7
+ v5 = v5 % 1 * (2^32 - 1) + v5
+ v2 = v2 + v6 + W[row[5]]
+ vE = XOR(vE, v2) % 2^32 / 2^16
+ vE = vE % 1 * (2^32 - 1) + vE
+ vA = vA + vE
+ v6 = XOR(v6, vA) % 2^32 / 2^12
+ v6 = v6 % 1 * (2^32 - 1) + v6
+ v2 = v2 + v6 + W[row[6]]
+ vE = XOR(vE, v2) % 2^32 / 2^8
+ vE = vE % 1 * (2^32 - 1) + vE
+ vA = vA + vE
+ v6 = XOR(v6, vA) % 2^32 / 2^7
+ v6 = v6 % 1 * (2^32 - 1) + v6
+ v3 = v3 + v7 + W[row[7]]
+ vF = XOR(vF, v3) % 2^32 / 2^16
+ vF = vF % 1 * (2^32 - 1) + vF
+ vB = vB + vF
+ v7 = XOR(v7, vB) % 2^32 / 2^12
+ v7 = v7 % 1 * (2^32 - 1) + v7
+ v3 = v3 + v7 + W[row[8]]
+ vF = XOR(vF, v3) % 2^32 / 2^8
+ vF = vF % 1 * (2^32 - 1) + vF
+ vB = vB + vF
+ v7 = XOR(v7, vB) % 2^32 / 2^7
+ v7 = v7 % 1 * (2^32 - 1) + v7
+ v0 = v0 + v5 + W[row[9]]
+ vF = XOR(vF, v0) % 2^32 / 2^16
+ vF = vF % 1 * (2^32 - 1) + vF
+ vA = vA + vF
+ v5 = XOR(v5, vA) % 2^32 / 2^12
+ v5 = v5 % 1 * (2^32 - 1) + v5
+ v0 = v0 + v5 + W[row[10]]
+ vF = XOR(vF, v0) % 2^32 / 2^8
+ vF = vF % 1 * (2^32 - 1) + vF
+ vA = vA + vF
+ v5 = XOR(v5, vA) % 2^32 / 2^7
+ v5 = v5 % 1 * (2^32 - 1) + v5
+ v1 = v1 + v6 + W[row[11]]
+ vC = XOR(vC, v1) % 2^32 / 2^16
+ vC = vC % 1 * (2^32 - 1) + vC
+ vB = vB + vC
+ v6 = XOR(v6, vB) % 2^32 / 2^12
+ v6 = v6 % 1 * (2^32 - 1) + v6
+ v1 = v1 + v6 + W[row[12]]
+ vC = XOR(vC, v1) % 2^32 / 2^8
+ vC = vC % 1 * (2^32 - 1) + vC
+ vB = vB + vC
+ v6 = XOR(v6, vB) % 2^32 / 2^7
+ v6 = v6 % 1 * (2^32 - 1) + v6
+ v2 = v2 + v7 + W[row[13]]
+ vD = XOR(vD, v2) % 2^32 / 2^16
+ vD = vD % 1 * (2^32 - 1) + vD
+ v8 = v8 + vD
+ v7 = XOR(v7, v8) % 2^32 / 2^12
+ v7 = v7 % 1 * (2^32 - 1) + v7
+ v2 = v2 + v7 + W[row[14]]
+ vD = XOR(vD, v2) % 2^32 / 2^8
+ vD = vD % 1 * (2^32 - 1) + vD
+ v8 = v8 + vD
+ v7 = XOR(v7, v8) % 2^32 / 2^7
+ v7 = v7 % 1 * (2^32 - 1) + v7
+ v3 = v3 + v4 + W[row[15]]
+ vE = XOR(vE, v3) % 2^32 / 2^16
+ vE = vE % 1 * (2^32 - 1) + vE
+ v9 = v9 + vE
+ v4 = XOR(v4, v9) % 2^32 / 2^12
+ v4 = v4 % 1 * (2^32 - 1) + v4
+ v3 = v3 + v4 + W[row[16]]
+ vE = XOR(vE, v3) % 2^32 / 2^8
+ vE = vE % 1 * (2^32 - 1) + vE
+ v9 = v9 + vE
+ v4 = XOR(v4, v9) % 2^32 / 2^7
+ v4 = v4 % 1 * (2^32 - 1) + v4
+ end
+ h1 = XOR(h1, v0, v8)
+ h2 = XOR(h2, v1, v9)
+ h3 = XOR(h3, v2, vA)
+ h4 = XOR(h4, v3, vB)
+ h5 = XOR(h5, v4, vC)
+ h6 = XOR(h6, v5, vD)
+ h7 = XOR(h7, v6, vE)
+ h8 = XOR(h8, v7, vF)
+ end
+ H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ return bytes_compressed
+ end
+
+
+ function blake2b_feed_128(H_lo, H_hi, str, offs, size, bytes_compressed, last_block_size, is_last_node)
+ -- offs >= 0, size >= 0, size is multiple of 128
+ local W = common_W
+ local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8]
+ local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8]
+ for pos = offs, offs + size - 1, 128 do
+ if str then
+ for j = 1, 32 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = ((d * 256 + c) * 256 + b) * 256 + a
+ end
+ end
+ local v0_lo, v1_lo, v2_lo, v3_lo, v4_lo, v5_lo, v6_lo, v7_lo = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo
+ local v0_hi, v1_hi, v2_hi, v3_hi, v4_hi, v5_hi, v6_hi, v7_hi = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi
+ local v8_lo, v9_lo, vA_lo, vB_lo, vC_lo, vD_lo, vE_lo, vF_lo = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[5], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8]
+ local v8_hi, v9_hi, vA_hi, vB_hi, vC_hi, vD_hi, vE_hi, vF_hi = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4], sha2_H_hi[5], sha2_H_hi[6], sha2_H_hi[7], sha2_H_hi[8]
+ bytes_compressed = bytes_compressed + (last_block_size or 128)
+ local t0_lo = bytes_compressed % 2^32
+ local t0_hi = (bytes_compressed - t0_lo) / 2^32
+ vC_lo = XOR(vC_lo, t0_lo) -- t0 = low_8_bytes(bytes_compressed)
+ vC_hi = XOR(vC_hi, t0_hi)
+ -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes
+ if last_block_size then -- flag f0
+ vE_lo = -1 - vE_lo
+ vE_hi = -1 - vE_hi
+ end
+ if is_last_node then -- flag f1
+ vF_lo = -1 - vF_lo
+ vF_hi = -1 - vF_hi
+ end
+ for j = 1, 12 do
+ local row = sigma[j]
+ local k = row[1] * 2
+ local z = v0_lo % 2^32 + v4_lo % 2^32 + W[k-1]
+ v0_lo = z % 2^32
+ v0_hi = v0_hi + v4_hi + (z - v0_lo) / 2^32 + W[k]
+ vC_lo, vC_hi = XOR(vC_hi, v0_hi), XOR(vC_lo, v0_lo)
+ z = v8_lo % 2^32 + vC_lo % 2^32
+ v8_lo = z % 2^32
+ v8_hi = v8_hi + vC_hi + (z - v8_lo) / 2^32
+ v4_lo, v4_hi = XOR(v4_lo, v8_lo), XOR(v4_hi, v8_hi)
+ local z_lo, z_hi = v4_lo % 2^24, v4_hi % 2^24
+ v4_lo, v4_hi = (v4_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v4_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8
+ k = row[2] * 2
+ z = v0_lo % 2^32 + v4_lo % 2^32 + W[k-1]
+ v0_lo = z % 2^32
+ v0_hi = v0_hi + v4_hi + (z - v0_lo) / 2^32 + W[k]
+ vC_lo, vC_hi = XOR(vC_lo, v0_lo), XOR(vC_hi, v0_hi)
+ z_lo, z_hi = vC_lo % 2^16, vC_hi % 2^16
+ vC_lo, vC_hi = (vC_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vC_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16
+ z = v8_lo % 2^32 + vC_lo % 2^32
+ v8_lo = z % 2^32
+ v8_hi = v8_hi + vC_hi + (z - v8_lo) / 2^32
+ v4_lo, v4_hi = XOR(v4_lo, v8_lo), XOR(v4_hi, v8_hi)
+ z_lo, z_hi = v4_lo % 2^31, v4_hi % 2^31
+ v4_lo, v4_hi = z_lo * 2^1 + (v4_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v4_lo - z_lo) / 2^31 % 2^1
+ k = row[3] * 2
+ z = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1]
+ v1_lo = z % 2^32
+ v1_hi = v1_hi + v5_hi + (z - v1_lo) / 2^32 + W[k]
+ vD_lo, vD_hi = XOR(vD_hi, v1_hi), XOR(vD_lo, v1_lo)
+ z = v9_lo % 2^32 + vD_lo % 2^32
+ v9_lo = z % 2^32
+ v9_hi = v9_hi + vD_hi + (z - v9_lo) / 2^32
+ v5_lo, v5_hi = XOR(v5_lo, v9_lo), XOR(v5_hi, v9_hi)
+ z_lo, z_hi = v5_lo % 2^24, v5_hi % 2^24
+ v5_lo, v5_hi = (v5_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v5_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8
+ k = row[4] * 2
+ z = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1]
+ v1_lo = z % 2^32
+ v1_hi = v1_hi + v5_hi + (z - v1_lo) / 2^32 + W[k]
+ vD_lo, vD_hi = XOR(vD_lo, v1_lo), XOR(vD_hi, v1_hi)
+ z_lo, z_hi = vD_lo % 2^16, vD_hi % 2^16
+ vD_lo, vD_hi = (vD_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vD_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16
+ z = v9_lo % 2^32 + vD_lo % 2^32
+ v9_lo = z % 2^32
+ v9_hi = v9_hi + vD_hi + (z - v9_lo) / 2^32
+ v5_lo, v5_hi = XOR(v5_lo, v9_lo), XOR(v5_hi, v9_hi)
+ z_lo, z_hi = v5_lo % 2^31, v5_hi % 2^31
+ v5_lo, v5_hi = z_lo * 2^1 + (v5_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v5_lo - z_lo) / 2^31 % 2^1
+ k = row[5] * 2
+ z = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1]
+ v2_lo = z % 2^32
+ v2_hi = v2_hi + v6_hi + (z - v2_lo) / 2^32 + W[k]
+ vE_lo, vE_hi = XOR(vE_hi, v2_hi), XOR(vE_lo, v2_lo)
+ z = vA_lo % 2^32 + vE_lo % 2^32
+ vA_lo = z % 2^32
+ vA_hi = vA_hi + vE_hi + (z - vA_lo) / 2^32
+ v6_lo, v6_hi = XOR(v6_lo, vA_lo), XOR(v6_hi, vA_hi)
+ z_lo, z_hi = v6_lo % 2^24, v6_hi % 2^24
+ v6_lo, v6_hi = (v6_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v6_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8
+ k = row[6] * 2
+ z = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1]
+ v2_lo = z % 2^32
+ v2_hi = v2_hi + v6_hi + (z - v2_lo) / 2^32 + W[k]
+ vE_lo, vE_hi = XOR(vE_lo, v2_lo), XOR(vE_hi, v2_hi)
+ z_lo, z_hi = vE_lo % 2^16, vE_hi % 2^16
+ vE_lo, vE_hi = (vE_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vE_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16
+ z = vA_lo % 2^32 + vE_lo % 2^32
+ vA_lo = z % 2^32
+ vA_hi = vA_hi + vE_hi + (z - vA_lo) / 2^32
+ v6_lo, v6_hi = XOR(v6_lo, vA_lo), XOR(v6_hi, vA_hi)
+ z_lo, z_hi = v6_lo % 2^31, v6_hi % 2^31
+ v6_lo, v6_hi = z_lo * 2^1 + (v6_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v6_lo - z_lo) / 2^31 % 2^1
+ k = row[7] * 2
+ z = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1]
+ v3_lo = z % 2^32
+ v3_hi = v3_hi + v7_hi + (z - v3_lo) / 2^32 + W[k]
+ vF_lo, vF_hi = XOR(vF_hi, v3_hi), XOR(vF_lo, v3_lo)
+ z = vB_lo % 2^32 + vF_lo % 2^32
+ vB_lo = z % 2^32
+ vB_hi = vB_hi + vF_hi + (z - vB_lo) / 2^32
+ v7_lo, v7_hi = XOR(v7_lo, vB_lo), XOR(v7_hi, vB_hi)
+ z_lo, z_hi = v7_lo % 2^24, v7_hi % 2^24
+ v7_lo, v7_hi = (v7_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v7_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8
+ k = row[8] * 2
+ z = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1]
+ v3_lo = z % 2^32
+ v3_hi = v3_hi + v7_hi + (z - v3_lo) / 2^32 + W[k]
+ vF_lo, vF_hi = XOR(vF_lo, v3_lo), XOR(vF_hi, v3_hi)
+ z_lo, z_hi = vF_lo % 2^16, vF_hi % 2^16
+ vF_lo, vF_hi = (vF_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vF_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16
+ z = vB_lo % 2^32 + vF_lo % 2^32
+ vB_lo = z % 2^32
+ vB_hi = vB_hi + vF_hi + (z - vB_lo) / 2^32
+ v7_lo, v7_hi = XOR(v7_lo, vB_lo), XOR(v7_hi, vB_hi)
+ z_lo, z_hi = v7_lo % 2^31, v7_hi % 2^31
+ v7_lo, v7_hi = z_lo * 2^1 + (v7_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v7_lo - z_lo) / 2^31 % 2^1
+ k = row[9] * 2
+ z = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1]
+ v0_lo = z % 2^32
+ v0_hi = v0_hi + v5_hi + (z - v0_lo) / 2^32 + W[k]
+ vF_lo, vF_hi = XOR(vF_hi, v0_hi), XOR(vF_lo, v0_lo)
+ z = vA_lo % 2^32 + vF_lo % 2^32
+ vA_lo = z % 2^32
+ vA_hi = vA_hi + vF_hi + (z - vA_lo) / 2^32
+ v5_lo, v5_hi = XOR(v5_lo, vA_lo), XOR(v5_hi, vA_hi)
+ z_lo, z_hi = v5_lo % 2^24, v5_hi % 2^24
+ v5_lo, v5_hi = (v5_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v5_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8
+ k = row[10] * 2
+ z = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1]
+ v0_lo = z % 2^32
+ v0_hi = v0_hi + v5_hi + (z - v0_lo) / 2^32 + W[k]
+ vF_lo, vF_hi = XOR(vF_lo, v0_lo), XOR(vF_hi, v0_hi)
+ z_lo, z_hi = vF_lo % 2^16, vF_hi % 2^16
+ vF_lo, vF_hi = (vF_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vF_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16
+ z = vA_lo % 2^32 + vF_lo % 2^32
+ vA_lo = z % 2^32
+ vA_hi = vA_hi + vF_hi + (z - vA_lo) / 2^32
+ v5_lo, v5_hi = XOR(v5_lo, vA_lo), XOR(v5_hi, vA_hi)
+ z_lo, z_hi = v5_lo % 2^31, v5_hi % 2^31
+ v5_lo, v5_hi = z_lo * 2^1 + (v5_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v5_lo - z_lo) / 2^31 % 2^1
+ k = row[11] * 2
+ z = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1]
+ v1_lo = z % 2^32
+ v1_hi = v1_hi + v6_hi + (z - v1_lo) / 2^32 + W[k]
+ vC_lo, vC_hi = XOR(vC_hi, v1_hi), XOR(vC_lo, v1_lo)
+ z = vB_lo % 2^32 + vC_lo % 2^32
+ vB_lo = z % 2^32
+ vB_hi = vB_hi + vC_hi + (z - vB_lo) / 2^32
+ v6_lo, v6_hi = XOR(v6_lo, vB_lo), XOR(v6_hi, vB_hi)
+ z_lo, z_hi = v6_lo % 2^24, v6_hi % 2^24
+ v6_lo, v6_hi = (v6_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v6_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8
+ k = row[12] * 2
+ z = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1]
+ v1_lo = z % 2^32
+ v1_hi = v1_hi + v6_hi + (z - v1_lo) / 2^32 + W[k]
+ vC_lo, vC_hi = XOR(vC_lo, v1_lo), XOR(vC_hi, v1_hi)
+ z_lo, z_hi = vC_lo % 2^16, vC_hi % 2^16
+ vC_lo, vC_hi = (vC_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vC_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16
+ z = vB_lo % 2^32 + vC_lo % 2^32
+ vB_lo = z % 2^32
+ vB_hi = vB_hi + vC_hi + (z - vB_lo) / 2^32
+ v6_lo, v6_hi = XOR(v6_lo, vB_lo), XOR(v6_hi, vB_hi)
+ z_lo, z_hi = v6_lo % 2^31, v6_hi % 2^31
+ v6_lo, v6_hi = z_lo * 2^1 + (v6_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v6_lo - z_lo) / 2^31 % 2^1
+ k = row[13] * 2
+ z = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1]
+ v2_lo = z % 2^32
+ v2_hi = v2_hi + v7_hi + (z - v2_lo) / 2^32 + W[k]
+ vD_lo, vD_hi = XOR(vD_hi, v2_hi), XOR(vD_lo, v2_lo)
+ z = v8_lo % 2^32 + vD_lo % 2^32
+ v8_lo = z % 2^32
+ v8_hi = v8_hi + vD_hi + (z - v8_lo) / 2^32
+ v7_lo, v7_hi = XOR(v7_lo, v8_lo), XOR(v7_hi, v8_hi)
+ z_lo, z_hi = v7_lo % 2^24, v7_hi % 2^24
+ v7_lo, v7_hi = (v7_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v7_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8
+ k = row[14] * 2
+ z = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1]
+ v2_lo = z % 2^32
+ v2_hi = v2_hi + v7_hi + (z - v2_lo) / 2^32 + W[k]
+ vD_lo, vD_hi = XOR(vD_lo, v2_lo), XOR(vD_hi, v2_hi)
+ z_lo, z_hi = vD_lo % 2^16, vD_hi % 2^16
+ vD_lo, vD_hi = (vD_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vD_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16
+ z = v8_lo % 2^32 + vD_lo % 2^32
+ v8_lo = z % 2^32
+ v8_hi = v8_hi + vD_hi + (z - v8_lo) / 2^32
+ v7_lo, v7_hi = XOR(v7_lo, v8_lo), XOR(v7_hi, v8_hi)
+ z_lo, z_hi = v7_lo % 2^31, v7_hi % 2^31
+ v7_lo, v7_hi = z_lo * 2^1 + (v7_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v7_lo - z_lo) / 2^31 % 2^1
+ k = row[15] * 2
+ z = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1]
+ v3_lo = z % 2^32
+ v3_hi = v3_hi + v4_hi + (z - v3_lo) / 2^32 + W[k]
+ vE_lo, vE_hi = XOR(vE_hi, v3_hi), XOR(vE_lo, v3_lo)
+ z = v9_lo % 2^32 + vE_lo % 2^32
+ v9_lo = z % 2^32
+ v9_hi = v9_hi + vE_hi + (z - v9_lo) / 2^32
+ v4_lo, v4_hi = XOR(v4_lo, v9_lo), XOR(v4_hi, v9_hi)
+ z_lo, z_hi = v4_lo % 2^24, v4_hi % 2^24
+ v4_lo, v4_hi = (v4_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v4_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8
+ k = row[16] * 2
+ z = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1]
+ v3_lo = z % 2^32
+ v3_hi = v3_hi + v4_hi + (z - v3_lo) / 2^32 + W[k]
+ vE_lo, vE_hi = XOR(vE_lo, v3_lo), XOR(vE_hi, v3_hi)
+ z_lo, z_hi = vE_lo % 2^16, vE_hi % 2^16
+ vE_lo, vE_hi = (vE_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vE_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16
+ z = v9_lo % 2^32 + vE_lo % 2^32
+ v9_lo = z % 2^32
+ v9_hi = v9_hi + vE_hi + (z - v9_lo) / 2^32
+ v4_lo, v4_hi = XOR(v4_lo, v9_lo), XOR(v4_hi, v9_hi)
+ z_lo, z_hi = v4_lo % 2^31, v4_hi % 2^31
+ v4_lo, v4_hi = z_lo * 2^1 + (v4_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v4_lo - z_lo) / 2^31 % 2^1
+ end
+ h1_lo = XOR(h1_lo, v0_lo, v8_lo) % 2^32
+ h2_lo = XOR(h2_lo, v1_lo, v9_lo) % 2^32
+ h3_lo = XOR(h3_lo, v2_lo, vA_lo) % 2^32
+ h4_lo = XOR(h4_lo, v3_lo, vB_lo) % 2^32
+ h5_lo = XOR(h5_lo, v4_lo, vC_lo) % 2^32
+ h6_lo = XOR(h6_lo, v5_lo, vD_lo) % 2^32
+ h7_lo = XOR(h7_lo, v6_lo, vE_lo) % 2^32
+ h8_lo = XOR(h8_lo, v7_lo, vF_lo) % 2^32
+ h1_hi = XOR(h1_hi, v0_hi, v8_hi) % 2^32
+ h2_hi = XOR(h2_hi, v1_hi, v9_hi) % 2^32
+ h3_hi = XOR(h3_hi, v2_hi, vA_hi) % 2^32
+ h4_hi = XOR(h4_hi, v3_hi, vB_hi) % 2^32
+ h5_hi = XOR(h5_hi, v4_hi, vC_hi) % 2^32
+ h6_hi = XOR(h6_hi, v5_hi, vD_hi) % 2^32
+ h7_hi = XOR(h7_hi, v6_hi, vE_hi) % 2^32
+ h8_hi = XOR(h8_hi, v7_hi, vF_hi) % 2^32
+ end
+ H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo
+ H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi
+ return bytes_compressed
+ end
+
+
+ function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length)
+ -- offs >= 0, size >= 0, size is multiple of 64
+ block_length = block_length or 64
+ local W = common_W
+ local h1, h2, h3, h4, h5, h6, h7, h8 = H_in[1], H_in[2], H_in[3], H_in[4], H_in[5], H_in[6], H_in[7], H_in[8]
+ H_out = H_out or H_in
+ for pos = offs, offs + size - 1, 64 do
+ if str then
+ for j = 1, 16 do
+ pos = pos + 4
+ local a, b, c, d = byte(str, pos - 3, pos)
+ W[j] = ((d * 256 + c) * 256 + b) * 256 + a
+ end
+ end
+ local v0, v1, v2, v3, v4, v5, v6, v7 = h1, h2, h3, h4, h5, h6, h7, h8
+ local v8, v9, vA, vB = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4]
+ local vC = chunk_index % 2^32 -- t0 = low_4_bytes(chunk_index)
+ local vD = (chunk_index - vC) / 2^32 -- t1 = high_4_bytes(chunk_index)
+ local vE, vF = block_length, flags
+ for j = 1, 7 do
+ v0 = v0 + v4 + W[perm_blake3[j]]
+ vC = XOR(vC, v0) % 2^32 / 2^16
+ vC = vC % 1 * (2^32 - 1) + vC
+ v8 = v8 + vC
+ v4 = XOR(v4, v8) % 2^32 / 2^12
+ v4 = v4 % 1 * (2^32 - 1) + v4
+ v0 = v0 + v4 + W[perm_blake3[j + 14]]
+ vC = XOR(vC, v0) % 2^32 / 2^8
+ vC = vC % 1 * (2^32 - 1) + vC
+ v8 = v8 + vC
+ v4 = XOR(v4, v8) % 2^32 / 2^7
+ v4 = v4 % 1 * (2^32 - 1) + v4
+ v1 = v1 + v5 + W[perm_blake3[j + 1]]
+ vD = XOR(vD, v1) % 2^32 / 2^16
+ vD = vD % 1 * (2^32 - 1) + vD
+ v9 = v9 + vD
+ v5 = XOR(v5, v9) % 2^32 / 2^12
+ v5 = v5 % 1 * (2^32 - 1) + v5
+ v1 = v1 + v5 + W[perm_blake3[j + 2]]
+ vD = XOR(vD, v1) % 2^32 / 2^8
+ vD = vD % 1 * (2^32 - 1) + vD
+ v9 = v9 + vD
+ v5 = XOR(v5, v9) % 2^32 / 2^7
+ v5 = v5 % 1 * (2^32 - 1) + v5
+ v2 = v2 + v6 + W[perm_blake3[j + 16]]
+ vE = XOR(vE, v2) % 2^32 / 2^16
+ vE = vE % 1 * (2^32 - 1) + vE
+ vA = vA + vE
+ v6 = XOR(v6, vA) % 2^32 / 2^12
+ v6 = v6 % 1 * (2^32 - 1) + v6
+ v2 = v2 + v6 + W[perm_blake3[j + 7]]
+ vE = XOR(vE, v2) % 2^32 / 2^8
+ vE = vE % 1 * (2^32 - 1) + vE
+ vA = vA + vE
+ v6 = XOR(v6, vA) % 2^32 / 2^7
+ v6 = v6 % 1 * (2^32 - 1) + v6
+ v3 = v3 + v7 + W[perm_blake3[j + 15]]
+ vF = XOR(vF, v3) % 2^32 / 2^16
+ vF = vF % 1 * (2^32 - 1) + vF
+ vB = vB + vF
+ v7 = XOR(v7, vB) % 2^32 / 2^12
+ v7 = v7 % 1 * (2^32 - 1) + v7
+ v3 = v3 + v7 + W[perm_blake3[j + 17]]
+ vF = XOR(vF, v3) % 2^32 / 2^8
+ vF = vF % 1 * (2^32 - 1) + vF
+ vB = vB + vF
+ v7 = XOR(v7, vB) % 2^32 / 2^7
+ v7 = v7 % 1 * (2^32 - 1) + v7
+ v0 = v0 + v5 + W[perm_blake3[j + 21]]
+ vF = XOR(vF, v0) % 2^32 / 2^16
+ vF = vF % 1 * (2^32 - 1) + vF
+ vA = vA + vF
+ v5 = XOR(v5, vA) % 2^32 / 2^12
+ v5 = v5 % 1 * (2^32 - 1) + v5
+ v0 = v0 + v5 + W[perm_blake3[j + 5]]
+ vF = XOR(vF, v0) % 2^32 / 2^8
+ vF = vF % 1 * (2^32 - 1) + vF
+ vA = vA + vF
+ v5 = XOR(v5, vA) % 2^32 / 2^7
+ v5 = v5 % 1 * (2^32 - 1) + v5
+ v1 = v1 + v6 + W[perm_blake3[j + 3]]
+ vC = XOR(vC, v1) % 2^32 / 2^16
+ vC = vC % 1 * (2^32 - 1) + vC
+ vB = vB + vC
+ v6 = XOR(v6, vB) % 2^32 / 2^12
+ v6 = v6 % 1 * (2^32 - 1) + v6
+ v1 = v1 + v6 + W[perm_blake3[j + 6]]
+ vC = XOR(vC, v1) % 2^32 / 2^8
+ vC = vC % 1 * (2^32 - 1) + vC
+ vB = vB + vC
+ v6 = XOR(v6, vB) % 2^32 / 2^7
+ v6 = v6 % 1 * (2^32 - 1) + v6
+ v2 = v2 + v7 + W[perm_blake3[j + 4]]
+ vD = XOR(vD, v2) % 2^32 / 2^16
+ vD = vD % 1 * (2^32 - 1) + vD
+ v8 = v8 + vD
+ v7 = XOR(v7, v8) % 2^32 / 2^12
+ v7 = v7 % 1 * (2^32 - 1) + v7
+ v2 = v2 + v7 + W[perm_blake3[j + 18]]
+ vD = XOR(vD, v2) % 2^32 / 2^8
+ vD = vD % 1 * (2^32 - 1) + vD
+ v8 = v8 + vD
+ v7 = XOR(v7, v8) % 2^32 / 2^7
+ v7 = v7 % 1 * (2^32 - 1) + v7
+ v3 = v3 + v4 + W[perm_blake3[j + 19]]
+ vE = XOR(vE, v3) % 2^32 / 2^16
+ vE = vE % 1 * (2^32 - 1) + vE
+ v9 = v9 + vE
+ v4 = XOR(v4, v9) % 2^32 / 2^12
+ v4 = v4 % 1 * (2^32 - 1) + v4
+ v3 = v3 + v4 + W[perm_blake3[j + 20]]
+ vE = XOR(vE, v3) % 2^32 / 2^8
+ vE = vE % 1 * (2^32 - 1) + vE
+ v9 = v9 + vE
+ v4 = XOR(v4, v9) % 2^32 / 2^7
+ v4 = v4 % 1 * (2^32 - 1) + v4
+ end
+ if wide_output then
+ H_out[ 9] = XOR(h1, v8)
+ H_out[10] = XOR(h2, v9)
+ H_out[11] = XOR(h3, vA)
+ H_out[12] = XOR(h4, vB)
+ H_out[13] = XOR(h5, vC)
+ H_out[14] = XOR(h6, vD)
+ H_out[15] = XOR(h7, vE)
+ H_out[16] = XOR(h8, vF)
+ end
+ h1 = XOR(v0, v8)
+ h2 = XOR(v1, v9)
+ h3 = XOR(v2, vA)
+ h4 = XOR(v3, vB)
+ h5 = XOR(v4, vC)
+ h6 = XOR(v5, vD)
+ h7 = XOR(v6, vE)
+ h8 = XOR(v7, vF)
+ end
+ H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8
+ end
+
+end
+
+
+--------------------------------------------------------------------------------
+-- MAGIC NUMBERS CALCULATOR
+--------------------------------------------------------------------------------
+-- Q:
+-- Is 53-bit "double" math enough to calculate square roots and cube roots of primes with 64 correct bits after decimal point?
+-- A:
+-- Yes, 53-bit "double" arithmetic is enough.
+-- We could obtain first 40 bits by direct calculation of p^(1/3) and next 40 bits by one step of Newton's method.
+
+do
+ local function mul(src1, src2, factor, result_length)
+ -- src1, src2 - long integers (arrays of digits in base 2^24)
+ -- factor - small integer
+ -- returns long integer result (src1 * src2 * factor) and its floating point approximation
+ local result, carry, value, weight = {}, 0.0, 0.0, 1.0
+ for j = 1, result_length do
+ for k = math_max(1, j + 1 - #src2), math_min(j, #src1) do
+ carry = carry + factor * src1[k] * src2[j + 1 - k] -- "int32" is not enough for multiplication result, that's why "factor" must be of type "double"
+ end
+ local digit = carry % 2^24
+ result[j] = floor(digit)
+ carry = (carry - digit) / 2^24
+ value = value + digit * weight
+ weight = weight * 2^24
+ end
+ return result, value
+ end
+
+ local idx, step, p, one, sqrt_hi, sqrt_lo = 0, {4, 1, 2, -2, 2}, 4, {1}, sha2_H_hi, sha2_H_lo
+ repeat
+ p = p + step[p % 6]
+ local d = 1
+ repeat
+ d = d + step[d % 6]
+ if d*d > p then -- next prime number is found
+ local root = p^(1/3)
+ local R = root * 2^40
+ R = mul({R - R % 1}, one, 1.0, 2)
+ local _, delta = mul(R, mul(R, R, 1.0, 4), -1.0, 4)
+ local hi = R[2] % 65536 * 65536 + floor(R[1] / 256)
+ local lo = R[1] % 256 * 16777216 + floor(delta * (2^-56 / 3) * root / p)
+ if idx < 16 then
+ root = p^(1/2)
+ R = root * 2^40
+ R = mul({R - R % 1}, one, 1.0, 2)
+ _, delta = mul(R, R, -1.0, 2)
+ local hi = R[2] % 65536 * 65536 + floor(R[1] / 256)
+ local lo = R[1] % 256 * 16777216 + floor(delta * 2^-17 / root)
+ local idx = idx % 8 + 1
+ sha2_H_ext256[224][idx] = lo
+ sqrt_hi[idx], sqrt_lo[idx] = hi, lo + hi * hi_factor
+ if idx > 7 then
+ sqrt_hi, sqrt_lo = sha2_H_ext512_hi[384], sha2_H_ext512_lo[384]
+ end
+ end
+ idx = idx + 1
+ sha2_K_hi[idx], sha2_K_lo[idx] = hi, lo % K_lo_modulo + hi * hi_factor
+ break
+ end
+ until p % d == 0
+ until idx > 79
+end
+
+-- Calculating IVs for SHA512/224 and SHA512/256
+for width = 224, 256, 32 do
+ local H_lo, H_hi = {}
+ if HEX64 then
+ for j = 1, 8 do
+ H_lo[j] = XORA5(sha2_H_lo[j])
+ end
+ else
+ H_hi = {}
+ for j = 1, 8 do
+ H_lo[j] = XORA5(sha2_H_lo[j])
+ H_hi[j] = XORA5(sha2_H_hi[j])
+ end
+ end
+ sha512_feed_128(H_lo, H_hi, "SHA-512/"..tostring(width).."\128"..string_rep("\0", 115).."\88", 0, 128)
+ sha2_H_ext512_lo[width] = H_lo
+ sha2_H_ext512_hi[width] = H_hi
+end
+
+-- Constants for MD5
+do
+ local sin, abs, modf = math.sin, math.abs, math.modf
+ for idx = 1, 64 do
+ -- we can't use formula floor(abs(sin(idx))*2^32) because its result may be beyond integer range on Lua built with 32-bit integers
+ local hi, lo = modf(abs(sin(idx)) * 2^16)
+ md5_K[idx] = hi * 65536 + floor(lo * 2^16)
+ end
+end
+
+-- Constants for SHA-3
+do
+ local sh_reg = 29
+
+ local function next_bit()
+ local r = sh_reg % 2
+ sh_reg = XOR_BYTE((sh_reg - r) / 2, 142 * r)
+ return r
+ end
+
+ for idx = 1, 24 do
+ local lo, m = 0
+ for _ = 1, 6 do
+ m = m and m * m * 2 or 1
+ lo = lo + next_bit() * m
+ end
+ local hi = next_bit() * m
+ sha3_RC_hi[idx], sha3_RC_lo[idx] = hi, lo + hi * hi_factor_keccak
+ end
+end
+
+if branch == "FFI" then
+ sha2_K_hi = ffi.new("uint32_t[?]", #sha2_K_hi + 1, 0, unpack(sha2_K_hi))
+ sha2_K_lo = ffi.new("int64_t[?]", #sha2_K_lo + 1, 0, unpack(sha2_K_lo))
+ --md5_K = ffi.new("uint32_t[?]", #md5_K + 1, 0, unpack(md5_K))
+ if hi_factor_keccak == 0 then
+ sha3_RC_lo = ffi.new("uint32_t[?]", #sha3_RC_lo + 1, 0, unpack(sha3_RC_lo))
+ sha3_RC_hi = ffi.new("uint32_t[?]", #sha3_RC_hi + 1, 0, unpack(sha3_RC_hi))
+ else
+ sha3_RC_lo = ffi.new("int64_t[?]", #sha3_RC_lo + 1, 0, unpack(sha3_RC_lo))
+ end
+end
+
+
+--------------------------------------------------------------------------------
+-- MAIN FUNCTIONS
+--------------------------------------------------------------------------------
+
+local function sha256ext(width, message)
+ -- Create an instance (private objects for current calculation)
+ local H, length, tail = {unpack(sha2_H_ext256[width])}, 0.0, ""
+
+ local function partial(message_part)
+ if message_part then
+ if tail then
+ length = length + #message_part
+ local offs = 0
+ if tail ~= "" and #tail + #message_part >= 64 then
+ offs = 64 - #tail
+ sha256_feed_64(H, tail..sub(message_part, 1, offs), 0, 64)
+ tail = ""
+ end
+ local size = #message_part - offs
+ local size_tail = size % 64
+ sha256_feed_64(H, message_part, offs, size - size_tail)
+ tail = tail..sub(message_part, #message_part + 1 - size_tail)
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if tail then
+ local final_blocks = {tail, "\128", string_rep("\0", (-9 - length) % 64 + 1)}
+ tail = nil
+ -- Assuming user data length is shorter than (2^53)-9 bytes
+ -- Anyway, it looks very unrealistic that someone would spend more than a year of calculations to process 2^53 bytes of data by using this Lua script :-)
+ -- 2^53 bytes = 2^56 bits, so "bit-counter" fits in 7 bytes
+ length = length * (8 / 256^7) -- convert "byte-counter" to "bit-counter" and move decimal point to the left
+ for j = 4, 10 do
+ length = length % 1 * 256
+ final_blocks[j] = char(floor(length))
+ end
+ final_blocks = table_concat(final_blocks)
+ sha256_feed_64(H, final_blocks, 0, #final_blocks)
+ local max_reg = width / 32
+ for j = 1, max_reg do
+ H[j] = HEX(H[j])
+ end
+ H = table_concat(H, "", 1, max_reg)
+ end
+ return H
+ end
+ end
+
+ if message then
+ -- Actually perform calculations and return the SHA256 digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get SHA256 digest by invoking this function without an argument
+ return partial
+ end
+end
+
+
+local function sha512ext(width, message)
+ -- Create an instance (private objects for current calculation)
+ local length, tail, H_lo, H_hi = 0.0, "", {unpack(sha2_H_ext512_lo[width])}, not HEX64 and {unpack(sha2_H_ext512_hi[width])}
+
+ local function partial(message_part)
+ if message_part then
+ if tail then
+ length = length + #message_part
+ local offs = 0
+ if tail ~= "" and #tail + #message_part >= 128 then
+ offs = 128 - #tail
+ sha512_feed_128(H_lo, H_hi, tail..sub(message_part, 1, offs), 0, 128)
+ tail = ""
+ end
+ local size = #message_part - offs
+ local size_tail = size % 128
+ sha512_feed_128(H_lo, H_hi, message_part, offs, size - size_tail)
+ tail = tail..sub(message_part, #message_part + 1 - size_tail)
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if tail then
+ local final_blocks = {tail, "\128", string_rep("\0", (-17-length) % 128 + 9)}
+ tail = nil
+ -- Assuming user data length is shorter than (2^53)-17 bytes
+ -- 2^53 bytes = 2^56 bits, so "bit-counter" fits in 7 bytes
+ length = length * (8 / 256^7) -- convert "byte-counter" to "bit-counter" and move floating point to the left
+ for j = 4, 10 do
+ length = length % 1 * 256
+ final_blocks[j] = char(floor(length))
+ end
+ final_blocks = table_concat(final_blocks)
+ sha512_feed_128(H_lo, H_hi, final_blocks, 0, #final_blocks)
+ local max_reg = ceil(width / 64)
+ if HEX64 then
+ for j = 1, max_reg do
+ H_lo[j] = HEX64(H_lo[j])
+ end
+ else
+ for j = 1, max_reg do
+ H_lo[j] = HEX(H_hi[j])..HEX(H_lo[j])
+ end
+ H_hi = nil
+ end
+ H_lo = sub(table_concat(H_lo, "", 1, max_reg), 1, width / 4)
+ end
+ return H_lo
+ end
+ end
+
+ if message then
+ -- Actually perform calculations and return the SHA512 digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get SHA512 digest by invoking this function without an argument
+ return partial
+ end
+end
+
+
+local function md5(message)
+ -- Create an instance (private objects for current calculation)
+ local H, length, tail = {unpack(md5_sha1_H, 1, 4)}, 0.0, ""
+
+ local function partial(message_part)
+ if message_part then
+ if tail then
+ length = length + #message_part
+ local offs = 0
+ if tail ~= "" and #tail + #message_part >= 64 then
+ offs = 64 - #tail
+ md5_feed_64(H, tail..sub(message_part, 1, offs), 0, 64)
+ tail = ""
+ end
+ local size = #message_part - offs
+ local size_tail = size % 64
+ md5_feed_64(H, message_part, offs, size - size_tail)
+ tail = tail..sub(message_part, #message_part + 1 - size_tail)
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if tail then
+ local final_blocks = {tail, "\128", string_rep("\0", (-9 - length) % 64)}
+ tail = nil
+ length = length * 8 -- convert "byte-counter" to "bit-counter"
+ for j = 4, 11 do
+ local low_byte = length % 256
+ final_blocks[j] = char(low_byte)
+ length = (length - low_byte) / 256
+ end
+ final_blocks = table_concat(final_blocks)
+ md5_feed_64(H, final_blocks, 0, #final_blocks)
+ for j = 1, 4 do
+ H[j] = HEX(H[j])
+ end
+ H = gsub(table_concat(H), "(..)(..)(..)(..)", "%4%3%2%1")
+ end
+ return H
+ end
+ end
+
+ if message then
+ -- Actually perform calculations and return the MD5 digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get MD5 digest by invoking this function without an argument
+ return partial
+ end
+end
+
+
+local function sha1(message)
+ -- Create an instance (private objects for current calculation)
+ local H, length, tail = {unpack(md5_sha1_H)}, 0.0, ""
+
+ local function partial(message_part)
+ if message_part then
+ if tail then
+ length = length + #message_part
+ local offs = 0
+ if tail ~= "" and #tail + #message_part >= 64 then
+ offs = 64 - #tail
+ sha1_feed_64(H, tail..sub(message_part, 1, offs), 0, 64)
+ tail = ""
+ end
+ local size = #message_part - offs
+ local size_tail = size % 64
+ sha1_feed_64(H, message_part, offs, size - size_tail)
+ tail = tail..sub(message_part, #message_part + 1 - size_tail)
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if tail then
+ local final_blocks = {tail, "\128", string_rep("\0", (-9 - length) % 64 + 1)}
+ tail = nil
+ -- Assuming user data length is shorter than (2^53)-9 bytes
+ -- 2^53 bytes = 2^56 bits, so "bit-counter" fits in 7 bytes
+ length = length * (8 / 256^7) -- convert "byte-counter" to "bit-counter" and move decimal point to the left
+ for j = 4, 10 do
+ length = length % 1 * 256
+ final_blocks[j] = char(floor(length))
+ end
+ final_blocks = table_concat(final_blocks)
+ sha1_feed_64(H, final_blocks, 0, #final_blocks)
+ for j = 1, 5 do
+ H[j] = HEX(H[j])
+ end
+ H = table_concat(H)
+ end
+ return H
+ end
+ end
+
+ if message then
+ -- Actually perform calculations and return the SHA-1 digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get SHA-1 digest by invoking this function without an argument
+ return partial
+ end
+end
+
+
+local function keccak(block_size_in_bytes, digest_size_in_bytes, is_SHAKE, message)
+ -- "block_size_in_bytes" is multiple of 8
+ if type(digest_size_in_bytes) ~= "number" then
+ -- arguments in SHAKE are swapped:
+ -- NIST FIPS 202 defines SHAKE(message,num_bits)
+ -- this module defines SHAKE(num_bytes,message)
+ -- it's easy to forget about this swap, hence the check
+ error("Argument 'digest_size_in_bytes' must be a number", 2)
+ end
+ -- Create an instance (private objects for current calculation)
+ local tail, lanes_lo, lanes_hi = "", create_array_of_lanes(), hi_factor_keccak == 0 and create_array_of_lanes()
+ local result
+
+ local function partial(message_part)
+ if message_part then
+ if tail then
+ local offs = 0
+ if tail ~= "" and #tail + #message_part >= block_size_in_bytes then
+ offs = block_size_in_bytes - #tail
+ keccak_feed(lanes_lo, lanes_hi, tail..sub(message_part, 1, offs), 0, block_size_in_bytes, block_size_in_bytes)
+ tail = ""
+ end
+ local size = #message_part - offs
+ local size_tail = size % block_size_in_bytes
+ keccak_feed(lanes_lo, lanes_hi, message_part, offs, size - size_tail, block_size_in_bytes)
+ tail = tail..sub(message_part, #message_part + 1 - size_tail)
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if tail then
+ -- append the following bits to the message: for usual SHA-3: 011(0*)1, for SHAKE: 11111(0*)1
+ local gap_start = is_SHAKE and 31 or 6
+ tail = tail..(#tail + 1 == block_size_in_bytes and char(gap_start + 128) or char(gap_start)..string_rep("\0", (-2 - #tail) % block_size_in_bytes).."\128")
+ keccak_feed(lanes_lo, lanes_hi, tail, 0, #tail, block_size_in_bytes)
+ tail = nil
+ local lanes_used = 0
+ local total_lanes = floor(block_size_in_bytes / 8)
+ local qwords = {}
+
+ local function get_next_qwords_of_digest(qwords_qty)
+ -- returns not more than 'qwords_qty' qwords ('qwords_qty' might be non-integer)
+ -- doesn't go across keccak-buffer boundary
+ -- block_size_in_bytes is a multiple of 8, so, keccak-buffer contains integer number of qwords
+ if lanes_used >= total_lanes then
+ keccak_feed(lanes_lo, lanes_hi, "\0\0\0\0\0\0\0\0", 0, 8, 8)
+ lanes_used = 0
+ end
+ qwords_qty = floor(math_min(qwords_qty, total_lanes - lanes_used))
+ if hi_factor_keccak ~= 0 then
+ for j = 1, qwords_qty do
+ qwords[j] = HEX64(lanes_lo[lanes_used + j - 1 + lanes_index_base])
+ end
+ else
+ for j = 1, qwords_qty do
+ qwords[j] = HEX(lanes_hi[lanes_used + j])..HEX(lanes_lo[lanes_used + j])
+ end
+ end
+ lanes_used = lanes_used + qwords_qty
+ return
+ gsub(table_concat(qwords, "", 1, qwords_qty), "(..)(..)(..)(..)(..)(..)(..)(..)", "%8%7%6%5%4%3%2%1"),
+ qwords_qty * 8
+ end
+
+ local parts = {} -- digest parts
+ local last_part, last_part_size = "", 0
+
+ local function get_next_part_of_digest(bytes_needed)
+ -- returns 'bytes_needed' bytes, for arbitrary integer 'bytes_needed'
+ bytes_needed = bytes_needed or 1
+ if bytes_needed <= last_part_size then
+ last_part_size = last_part_size - bytes_needed
+ local part_size_in_nibbles = bytes_needed * 2
+ local result = sub(last_part, 1, part_size_in_nibbles)
+ last_part = sub(last_part, part_size_in_nibbles + 1)
+ return result
+ end
+ local parts_qty = 0
+ if last_part_size > 0 then
+ parts_qty = 1
+ parts[parts_qty] = last_part
+ bytes_needed = bytes_needed - last_part_size
+ end
+ -- repeats until the length is enough
+ while bytes_needed >= 8 do
+ local next_part, next_part_size = get_next_qwords_of_digest(bytes_needed / 8)
+ parts_qty = parts_qty + 1
+ parts[parts_qty] = next_part
+ bytes_needed = bytes_needed - next_part_size
+ end
+ if bytes_needed > 0 then
+ last_part, last_part_size = get_next_qwords_of_digest(1)
+ parts_qty = parts_qty + 1
+ parts[parts_qty] = get_next_part_of_digest(bytes_needed)
+ else
+ last_part, last_part_size = "", 0
+ end
+ return table_concat(parts, "", 1, parts_qty)
+ end
+
+ if digest_size_in_bytes < 0 then
+ result = get_next_part_of_digest
+ else
+ result = get_next_part_of_digest(digest_size_in_bytes)
+ end
+ end
+ return result
+ end
+ end
+
+ if message then
+ -- Actually perform calculations and return the SHA-3 digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get SHA-3 digest by invoking this function without an argument
+ return partial
+ end
+end
+
+
+local hex_to_bin, bin_to_hex, bin_to_base64, base64_to_bin
+do
+ function hex_to_bin(hex_string)
+ return (gsub(hex_string, "%x%x",
+ function (hh)
+ return char(tonumber(hh, 16))
+ end
+ ))
+ end
+
+ function bin_to_hex(binary_string)
+ return (gsub(binary_string, ".",
+ function (c)
+ return string_format("%02x", byte(c))
+ end
+ ))
+ end
+
+ local base64_symbols = {
+ ['+'] = 62, ['-'] = 62, [62] = '+',
+ ['/'] = 63, ['_'] = 63, [63] = '/',
+ ['='] = -1, ['.'] = -1, [-1] = '='
+ }
+ local symbol_index = 0
+ for j, pair in ipairs{'AZ', 'az', '09'} do
+ for ascii = byte(pair), byte(pair, 2) do
+ local ch = char(ascii)
+ base64_symbols[ch] = symbol_index
+ base64_symbols[symbol_index] = ch
+ symbol_index = symbol_index + 1
+ end
+ end
+
+ function bin_to_base64(binary_string)
+ local result = {}
+ for pos = 1, #binary_string, 3 do
+ local c1, c2, c3, c4 = byte(sub(binary_string, pos, pos + 2)..'\0', 1, -1)
+ result[#result + 1] =
+ base64_symbols[floor(c1 / 4)]
+ ..base64_symbols[c1 % 4 * 16 + floor(c2 / 16)]
+ ..base64_symbols[c3 and c2 % 16 * 4 + floor(c3 / 64) or -1]
+ ..base64_symbols[c4 and c3 % 64 or -1]
+ end
+ return table_concat(result)
+ end
+
+ function base64_to_bin(base64_string)
+ local result, chars_qty = {}, 3
+ for pos, ch in gmatch(gsub(base64_string, '%s+', ''), '()(.)') do
+ local code = base64_symbols[ch]
+ if code < 0 then
+ chars_qty = chars_qty - 1
+ code = 0
+ end
+ local idx = pos % 4
+ if idx > 0 then
+ result[-idx] = code
+ else
+ local c1 = result[-1] * 4 + floor(result[-2] / 16)
+ local c2 = (result[-2] % 16) * 16 + floor(result[-3] / 4)
+ local c3 = (result[-3] % 4) * 64 + code
+ result[#result + 1] = sub(char(c1, c2, c3), 1, chars_qty)
+ end
+ end
+ return table_concat(result)
+ end
+
+end
+
+
+local block_size_for_HMAC -- this table will be initialized at the end of the module
+
+local function pad_and_xor(str, result_length, byte_for_xor)
+ return gsub(str, ".",
+ function(c)
+ return char(XOR_BYTE(byte(c), byte_for_xor))
+ end
+ )..string_rep(char(byte_for_xor), result_length - #str)
+end
+
+local function hmac(hash_func, key, message)
+ -- Create an instance (private objects for current calculation)
+ local block_size = block_size_for_HMAC[hash_func]
+ if not block_size then
+ error("Unknown hash function", 2)
+ end
+ if #key > block_size then
+ key = hex_to_bin(hash_func(key))
+ end
+ local append = hash_func()(pad_and_xor(key, block_size, 0x36))
+ local result
+
+ local function partial(message_part)
+ if not message_part then
+ result = result or hash_func(pad_and_xor(key, block_size, 0x5C)..hex_to_bin(append()))
+ return result
+ elseif result then
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ else
+ append(message_part)
+ return partial
+ end
+ end
+
+ if message then
+ -- Actually perform calculations and return the HMAC of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading of a message
+ -- User should feed every chunk of the message as single argument to this function and finally get HMAC by invoking this function without an argument
+ return partial
+ end
+end
+
+
+local function xor_blake2_salt(salt, letter, H_lo, H_hi)
+ -- salt: concatenation of "Salt"+"Personalization" fields
+ local max_size = letter == "s" and 16 or 32
+ local salt_size = #salt
+ if salt_size > max_size then
+ error(string_format("For BLAKE2%s/BLAKE2%sp/BLAKE2X%s the 'salt' parameter length must not exceed %d bytes", letter, letter, letter, max_size), 2)
+ end
+ if H_lo then
+ local offset, blake2_word_size, xor = 0, letter == "s" and 4 or 8, letter == "s" and XOR or XORA5
+ for j = 5, 4 + ceil(salt_size / blake2_word_size) do
+ local prev, last
+ for _ = 1, blake2_word_size, 4 do
+ offset = offset + 4
+ local a, b, c, d = byte(salt, offset - 3, offset)
+ local four_bytes = (((d or 0) * 256 + (c or 0)) * 256 + (b or 0)) * 256 + (a or 0)
+ prev, last = last, four_bytes
+ end
+ H_lo[j] = xor(H_lo[j], prev and last * hi_factor + prev or last)
+ if H_hi then
+ H_hi[j] = xor(H_hi[j], last)
+ end
+ end
+ end
+end
+
+local function blake2s(message, key, salt, digest_size_in_bytes, XOF_length, B2_offset)
+ -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode)
+ -- key: (optional) binary string up to 32 bytes, by default empty string
+ -- salt: (optional) binary string up to 16 bytes, by default empty string
+ -- digest_size_in_bytes: (optional) integer from 1 to 32, by default 32
+ -- The last two parameters "XOF_length" and "B2_offset" are for internal use only, user must omit them (or pass nil)
+ digest_size_in_bytes = digest_size_in_bytes or 32
+ if digest_size_in_bytes < 1 or digest_size_in_bytes > 32 then
+ error("BLAKE2s digest length must be from 1 to 32 bytes", 2)
+ end
+ key = key or ""
+ local key_length = #key
+ if key_length > 32 then
+ error("BLAKE2s key length must not exceed 32 bytes", 2)
+ end
+ salt = salt or ""
+ local bytes_compressed, tail, H = 0.0, "", {unpack(sha2_H_hi)}
+ if B2_offset then
+ H[1] = XOR(H[1], digest_size_in_bytes)
+ H[2] = XOR(H[2], 0x20)
+ H[3] = XOR(H[3], B2_offset)
+ H[4] = XOR(H[4], 0x20000000 + XOF_length)
+ else
+ H[1] = XOR(H[1], 0x01010000 + key_length * 256 + digest_size_in_bytes)
+ if XOF_length then
+ H[4] = XOR(H[4], XOF_length)
+ end
+ end
+ if salt ~= "" then
+ xor_blake2_salt(salt, "s", H)
+ end
+
+ local function partial(message_part)
+ if message_part then
+ if tail then
+ local offs = 0
+ if tail ~= "" and #tail + #message_part > 64 then
+ offs = 64 - #tail
+ bytes_compressed = blake2s_feed_64(H, tail..sub(message_part, 1, offs), 0, 64, bytes_compressed)
+ tail = ""
+ end
+ local size = #message_part - offs
+ local size_tail = size > 0 and (size - 1) % 64 + 1 or 0
+ bytes_compressed = blake2s_feed_64(H, message_part, offs, size - size_tail, bytes_compressed)
+ tail = tail..sub(message_part, #message_part + 1 - size_tail)
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if tail then
+ if B2_offset then
+ blake2s_feed_64(H, nil, 0, 64, 0, 32)
+ else
+ blake2s_feed_64(H, tail..string_rep("\0", 64 - #tail), 0, 64, bytes_compressed, #tail)
+ end
+ tail = nil
+ if not XOF_length or B2_offset then
+ local max_reg = ceil(digest_size_in_bytes / 4)
+ for j = 1, max_reg do
+ H[j] = HEX(H[j])
+ end
+ H = sub(gsub(table_concat(H, "", 1, max_reg), "(..)(..)(..)(..)", "%4%3%2%1"), 1, digest_size_in_bytes * 2)
+ end
+ end
+ return H
+ end
+ end
+
+ if key_length > 0 then
+ partial(key..string_rep("\0", 64 - key_length))
+ end
+ if B2_offset then
+ return partial()
+ elseif message then
+ -- Actually perform calculations and return the BLAKE2s digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2s digest by invoking this function without an argument
+ return partial
+ end
+end
+
+local function blake2b(message, key, salt, digest_size_in_bytes, XOF_length, B2_offset)
+ -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode)
+ -- key: (optional) binary string up to 64 bytes, by default empty string
+ -- salt: (optional) binary string up to 32 bytes, by default empty string
+ -- digest_size_in_bytes: (optional) integer from 1 to 64, by default 64
+ -- The last two parameters "XOF_length" and "B2_offset" are for internal use only, user must omit them (or pass nil)
+ digest_size_in_bytes = floor(digest_size_in_bytes or 64)
+ if digest_size_in_bytes < 1 or digest_size_in_bytes > 64 then
+ error("BLAKE2b digest length must be from 1 to 64 bytes", 2)
+ end
+ key = key or ""
+ local key_length = #key
+ if key_length > 64 then
+ error("BLAKE2b key length must not exceed 64 bytes", 2)
+ end
+ salt = salt or ""
+ local bytes_compressed, tail, H_lo, H_hi = 0.0, "", {unpack(sha2_H_lo)}, not HEX64 and {unpack(sha2_H_hi)}
+ if B2_offset then
+ if H_hi then
+ H_lo[1] = XORA5(H_lo[1], digest_size_in_bytes)
+ H_hi[1] = XORA5(H_hi[1], 0x40)
+ H_lo[2] = XORA5(H_lo[2], B2_offset)
+ H_hi[2] = XORA5(H_hi[2], XOF_length)
+ else
+ H_lo[1] = XORA5(H_lo[1], 0x40 * hi_factor + digest_size_in_bytes)
+ H_lo[2] = XORA5(H_lo[2], XOF_length * hi_factor + B2_offset)
+ end
+ H_lo[3] = XORA5(H_lo[3], 0x4000)
+ else
+ H_lo[1] = XORA5(H_lo[1], 0x01010000 + key_length * 256 + digest_size_in_bytes)
+ if XOF_length then
+ if H_hi then
+ H_hi[2] = XORA5(H_hi[2], XOF_length)
+ else
+ H_lo[2] = XORA5(H_lo[2], XOF_length * hi_factor)
+ end
+ end
+ end
+ if salt ~= "" then
+ xor_blake2_salt(salt, "b", H_lo, H_hi)
+ end
+
+ local function partial(message_part)
+ if message_part then
+ if tail then
+ local offs = 0
+ if tail ~= "" and #tail + #message_part > 128 then
+ offs = 128 - #tail
+ bytes_compressed = blake2b_feed_128(H_lo, H_hi, tail..sub(message_part, 1, offs), 0, 128, bytes_compressed)
+ tail = ""
+ end
+ local size = #message_part - offs
+ local size_tail = size > 0 and (size - 1) % 128 + 1 or 0
+ bytes_compressed = blake2b_feed_128(H_lo, H_hi, message_part, offs, size - size_tail, bytes_compressed)
+ tail = tail..sub(message_part, #message_part + 1 - size_tail)
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if tail then
+ if B2_offset then
+ blake2b_feed_128(H_lo, H_hi, nil, 0, 128, 0, 64)
+ else
+ blake2b_feed_128(H_lo, H_hi, tail..string_rep("\0", 128 - #tail), 0, 128, bytes_compressed, #tail)
+ end
+ tail = nil
+ if XOF_length and not B2_offset then
+ if H_hi then
+ for j = 8, 1, -1 do
+ H_lo[j*2] = H_hi[j]
+ H_lo[j*2-1] = H_lo[j]
+ end
+ return H_lo, 16
+ end
+ else
+ local max_reg = ceil(digest_size_in_bytes / 8)
+ if H_hi then
+ for j = 1, max_reg do
+ H_lo[j] = HEX(H_hi[j])..HEX(H_lo[j])
+ end
+ else
+ for j = 1, max_reg do
+ H_lo[j] = HEX64(H_lo[j])
+ end
+ end
+ H_lo = sub(gsub(table_concat(H_lo, "", 1, max_reg), "(..)(..)(..)(..)(..)(..)(..)(..)", "%8%7%6%5%4%3%2%1"), 1, digest_size_in_bytes * 2)
+ end
+ H_hi = nil
+ end
+ return H_lo
+ end
+ end
+
+ if key_length > 0 then
+ partial(key..string_rep("\0", 128 - key_length))
+ end
+ if B2_offset then
+ return partial()
+ elseif message then
+ -- Actually perform calculations and return the BLAKE2b digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2b digest by invoking this function without an argument
+ return partial
+ end
+end
+
+local function blake2sp(message, key, salt, digest_size_in_bytes)
+ -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode)
+ -- key: (optional) binary string up to 32 bytes, by default empty string
+ -- salt: (optional) binary string up to 16 bytes, by default empty string
+ -- digest_size_in_bytes: (optional) integer from 1 to 32, by default 32
+ digest_size_in_bytes = digest_size_in_bytes or 32
+ if digest_size_in_bytes < 1 or digest_size_in_bytes > 32 then
+ error("BLAKE2sp digest length must be from 1 to 32 bytes", 2)
+ end
+ key = key or ""
+ local key_length = #key
+ if key_length > 32 then
+ error("BLAKE2sp key length must not exceed 32 bytes", 2)
+ end
+ salt = salt or ""
+ local instances, length, first_dword_of_parameter_block, result = {}, 0.0, 0x02080000 + key_length * 256 + digest_size_in_bytes
+ for j = 1, 8 do
+ local bytes_compressed, tail, H = 0.0, "", {unpack(sha2_H_hi)}
+ instances[j] = {bytes_compressed, tail, H}
+ H[1] = XOR(H[1], first_dword_of_parameter_block)
+ H[3] = XOR(H[3], j-1)
+ H[4] = XOR(H[4], 0x20000000)
+ if salt ~= "" then
+ xor_blake2_salt(salt, "s", H)
+ end
+ end
+
+ local function partial(message_part)
+ if message_part then
+ if instances then
+ local from = 0
+ while true do
+ local to = math_min(from + 64 - length % 64, #message_part)
+ if to > from then
+ local inst = instances[floor(length / 64) % 8 + 1]
+ local part = sub(message_part, from + 1, to)
+ length, from = length + to - from, to
+ local bytes_compressed, tail = inst[1], inst[2]
+ if #tail < 64 then
+ tail = tail..part
+ else
+ local H = inst[3]
+ bytes_compressed = blake2s_feed_64(H, tail, 0, 64, bytes_compressed)
+ tail = part
+ end
+ inst[1], inst[2] = bytes_compressed, tail
+ else
+ break
+ end
+ end
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if instances then
+ local root_H = {unpack(sha2_H_hi)}
+ root_H[1] = XOR(root_H[1], first_dword_of_parameter_block)
+ root_H[4] = XOR(root_H[4], 0x20010000)
+ if salt ~= "" then
+ xor_blake2_salt(salt, "s", root_H)
+ end
+ for j = 1, 8 do
+ local inst = instances[j]
+ local bytes_compressed, tail, H = inst[1], inst[2], inst[3]
+ blake2s_feed_64(H, tail..string_rep("\0", 64 - #tail), 0, 64, bytes_compressed, #tail, j == 8)
+ if j % 2 == 0 then
+ local index = 0
+ for k = j - 1, j do
+ local inst = instances[k]
+ local H = inst[3]
+ for i = 1, 8 do
+ index = index + 1
+ common_W_blake2s[index] = H[i]
+ end
+ end
+ blake2s_feed_64(root_H, nil, 0, 64, 64 * (j/2 - 1), j == 8 and 64, j == 8)
+ end
+ end
+ instances = nil
+ local max_reg = ceil(digest_size_in_bytes / 4)
+ for j = 1, max_reg do
+ root_H[j] = HEX(root_H[j])
+ end
+ result = sub(gsub(table_concat(root_H, "", 1, max_reg), "(..)(..)(..)(..)", "%4%3%2%1"), 1, digest_size_in_bytes * 2)
+ end
+ return result
+ end
+ end
+
+ if key_length > 0 then
+ key = key..string_rep("\0", 64 - key_length)
+ for j = 1, 8 do
+ partial(key)
+ end
+ end
+ if message then
+ -- Actually perform calculations and return the BLAKE2sp digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2sp digest by invoking this function without an argument
+ return partial
+ end
+
+end
+
+local function blake2bp(message, key, salt, digest_size_in_bytes)
+ -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode)
+ -- key: (optional) binary string up to 64 bytes, by default empty string
+ -- salt: (optional) binary string up to 32 bytes, by default empty string
+ -- digest_size_in_bytes: (optional) integer from 1 to 64, by default 64
+ digest_size_in_bytes = digest_size_in_bytes or 64
+ if digest_size_in_bytes < 1 or digest_size_in_bytes > 64 then
+ error("BLAKE2bp digest length must be from 1 to 64 bytes", 2)
+ end
+ key = key or ""
+ local key_length = #key
+ if key_length > 64 then
+ error("BLAKE2bp key length must not exceed 64 bytes", 2)
+ end
+ salt = salt or ""
+ local instances, length, first_dword_of_parameter_block, result = {}, 0.0, 0x02040000 + key_length * 256 + digest_size_in_bytes
+ for j = 1, 4 do
+ local bytes_compressed, tail, H_lo, H_hi = 0.0, "", {unpack(sha2_H_lo)}, not HEX64 and {unpack(sha2_H_hi)}
+ instances[j] = {bytes_compressed, tail, H_lo, H_hi}
+ H_lo[1] = XORA5(H_lo[1], first_dword_of_parameter_block)
+ H_lo[2] = XORA5(H_lo[2], j-1)
+ H_lo[3] = XORA5(H_lo[3], 0x4000)
+ if salt ~= "" then
+ xor_blake2_salt(salt, "b", H_lo, H_hi)
+ end
+ end
+
+ local function partial(message_part)
+ if message_part then
+ if instances then
+ local from = 0
+ while true do
+ local to = math_min(from + 128 - length % 128, #message_part)
+ if to > from then
+ local inst = instances[floor(length / 128) % 4 + 1]
+ local part = sub(message_part, from + 1, to)
+ length, from = length + to - from, to
+ local bytes_compressed, tail = inst[1], inst[2]
+ if #tail < 128 then
+ tail = tail..part
+ else
+ local H_lo, H_hi = inst[3], inst[4]
+ bytes_compressed = blake2b_feed_128(H_lo, H_hi, tail, 0, 128, bytes_compressed)
+ tail = part
+ end
+ inst[1], inst[2] = bytes_compressed, tail
+ else
+ break
+ end
+ end
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if instances then
+ local root_H_lo, root_H_hi = {unpack(sha2_H_lo)}, not HEX64 and {unpack(sha2_H_hi)}
+ root_H_lo[1] = XORA5(root_H_lo[1], first_dword_of_parameter_block)
+ root_H_lo[3] = XORA5(root_H_lo[3], 0x4001)
+ if salt ~= "" then
+ xor_blake2_salt(salt, "b", root_H_lo, root_H_hi)
+ end
+ for j = 1, 4 do
+ local inst = instances[j]
+ local bytes_compressed, tail, H_lo, H_hi = inst[1], inst[2], inst[3], inst[4]
+ blake2b_feed_128(H_lo, H_hi, tail..string_rep("\0", 128 - #tail), 0, 128, bytes_compressed, #tail, j == 4)
+ if j % 2 == 0 then
+ local index = 0
+ for k = j - 1, j do
+ local inst = instances[k]
+ local H_lo, H_hi = inst[3], inst[4]
+ for i = 1, 8 do
+ index = index + 1
+ common_W_blake2b[index] = H_lo[i]
+ if H_hi then
+ index = index + 1
+ common_W_blake2b[index] = H_hi[i]
+ end
+ end
+ end
+ blake2b_feed_128(root_H_lo, root_H_hi, nil, 0, 128, 128 * (j/2 - 1), j == 4 and 128, j == 4)
+ end
+ end
+ instances = nil
+ local max_reg = ceil(digest_size_in_bytes / 8)
+ if HEX64 then
+ for j = 1, max_reg do
+ root_H_lo[j] = HEX64(root_H_lo[j])
+ end
+ else
+ for j = 1, max_reg do
+ root_H_lo[j] = HEX(root_H_hi[j])..HEX(root_H_lo[j])
+ end
+ end
+ result = sub(gsub(table_concat(root_H_lo, "", 1, max_reg), "(..)(..)(..)(..)(..)(..)(..)(..)", "%8%7%6%5%4%3%2%1"), 1, digest_size_in_bytes * 2)
+ end
+ return result
+ end
+ end
+
+ if key_length > 0 then
+ key = key..string_rep("\0", 128 - key_length)
+ for j = 1, 4 do
+ partial(key)
+ end
+ end
+ if message then
+ -- Actually perform calculations and return the BLAKE2bp digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2bp digest by invoking this function without an argument
+ return partial
+ end
+
+end
+
+local function blake2x(inner_func, inner_func_letter, common_W_blake2, block_size, digest_size_in_bytes, message, key, salt)
+ local XOF_digest_length_limit, XOF_digest_length, chunk_by_chunk_output = 2^(block_size / 2) - 1
+ if digest_size_in_bytes == -1 then -- infinite digest
+ digest_size_in_bytes = math_huge
+ XOF_digest_length = floor(XOF_digest_length_limit)
+ chunk_by_chunk_output = true
+ else
+ if digest_size_in_bytes < 0 then
+ digest_size_in_bytes = -1.0 * digest_size_in_bytes
+ chunk_by_chunk_output = true
+ end
+ XOF_digest_length = floor(digest_size_in_bytes)
+ if XOF_digest_length >= XOF_digest_length_limit then
+ error("Requested digest is too long. BLAKE2X"..inner_func_letter.." finite digest is limited by (2^"..floor(block_size / 2)..")-2 bytes. Hint: you can generate infinite digest.", 2)
+ end
+ end
+ salt = salt or ""
+ if salt ~= "" then
+ xor_blake2_salt(salt, inner_func_letter) -- don't xor, only check the size of salt
+ end
+ local inner_partial = inner_func(nil, key, salt, nil, XOF_digest_length)
+ local result
+
+ local function partial(message_part)
+ if message_part then
+ if inner_partial then
+ inner_partial(message_part)
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if inner_partial then
+ local half_W, half_W_size = inner_partial()
+ half_W_size, inner_partial = half_W_size or 8
+
+ local function get_hash_block(block_no)
+ -- block_no = 0...(2^32-1)
+ local size = math_min(block_size, digest_size_in_bytes - block_no * block_size)
+ if size <= 0 then
+ return ""
+ end
+ for j = 1, half_W_size do
+ common_W_blake2[j] = half_W[j]
+ end
+ for j = half_W_size + 1, 2 * half_W_size do
+ common_W_blake2[j] = 0
+ end
+ return inner_func(nil, nil, salt, size, XOF_digest_length, floor(block_no))
+ end
+
+ local hash = {}
+ if chunk_by_chunk_output then
+ local pos, period, cached_block_no, cached_block = 0, block_size * 2^32
+
+ local function get_next_part_of_digest(arg1, arg2)
+ if arg1 == "seek" then
+ -- Usage #1: get_next_part_of_digest("seek", new_pos)
+ pos = arg2 % period
+ else
+ -- Usage #2: hex_string = get_next_part_of_digest(size)
+ local size, index = arg1 or 1, 0
+ while size > 0 do
+ local block_offset = pos % block_size
+ local block_no = (pos - block_offset) / block_size
+ local part_size = math_min(size, block_size - block_offset)
+ if cached_block_no ~= block_no then
+ cached_block_no = block_no
+ cached_block = get_hash_block(block_no)
+ end
+ index = index + 1
+ hash[index] = sub(cached_block, block_offset * 2 + 1, (block_offset + part_size) * 2)
+ size = size - part_size
+ pos = (pos + part_size) % period
+ end
+ return table_concat(hash, "", 1, index)
+ end
+ end
+
+ result = get_next_part_of_digest
+ else
+ for j = 1.0, ceil(digest_size_in_bytes / block_size) do
+ hash[j] = get_hash_block(j - 1.0)
+ end
+ result = table_concat(hash)
+ end
+ end
+ return result
+ end
+ end
+
+ if message then
+ -- Actually perform calculations and return the BLAKE2X digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2X digest by invoking this function without an argument
+ return partial
+ end
+end
+
+local function blake2xs(digest_size_in_bytes, message, key, salt)
+ -- digest_size_in_bytes:
+ -- 0..65534 = get finite digest as single Lua string
+ -- (-1) = get infinite digest in "chunk-by-chunk" output mode
+ -- (-2)..(-65534) = get finite digest in "chunk-by-chunk" output mode
+ -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode)
+ -- key: (optional) binary string up to 32 bytes, by default empty string
+ -- salt: (optional) binary string up to 16 bytes, by default empty string
+ return blake2x(blake2s, "s", common_W_blake2s, 32, digest_size_in_bytes, message, key, salt)
+end
+
+local function blake2xb(digest_size_in_bytes, message, key, salt)
+ -- digest_size_in_bytes:
+ -- 0..4294967294 = get finite digest as single Lua string
+ -- (-1) = get infinite digest in "chunk-by-chunk" output mode
+ -- (-2)..(-4294967294) = get finite digest in "chunk-by-chunk" output mode
+ -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode)
+ -- key: (optional) binary string up to 64 bytes, by default empty string
+ -- salt: (optional) binary string up to 32 bytes, by default empty string
+ return blake2x(blake2b, "b", common_W_blake2b, 64, digest_size_in_bytes, message, key, salt)
+end
+
+
+local function blake3(message, key, digest_size_in_bytes, message_flags, K, return_array)
+ -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode)
+ -- key: (optional) binary string up to 32 bytes, by default empty string
+ -- digest_size_in_bytes: (optional) by default 32
+ -- 0,1,2,3,4,... = get finite digest as single Lua string
+ -- (-1) = get infinite digest in "chunk-by-chunk" output mode
+ -- -2,-3,-4,... = get finite digest in "chunk-by-chunk" output mode
+ -- The last three parameters "message_flags", "K" and "return_array" are for internal use only, user must omit them (or pass nil)
+ key = key or ""
+ digest_size_in_bytes = digest_size_in_bytes or 32
+ message_flags = message_flags or 0
+ if key == "" then
+ K = K or sha2_H_hi
+ else
+ local key_length = #key
+ if key_length > 32 then
+ error("BLAKE3 key length must not exceed 32 bytes", 2)
+ end
+ key = key..string_rep("\0", 32 - key_length)
+ K = {}
+ for j = 1, 8 do
+ local a, b, c, d = byte(key, 4*j-3, 4*j)
+ K[j] = ((d * 256 + c) * 256 + b) * 256 + a
+ end
+ message_flags = message_flags + 16 -- flag:KEYED_HASH
+ end
+ local tail, H, chunk_index, blocks_in_chunk, stack_size, stack = "", {}, 0, 0, 0, {}
+ local final_H_in, final_block_length, chunk_by_chunk_output, result, wide_output = K
+ local final_compression_flags = 3 -- flags:CHUNK_START,CHUNK_END
+
+ local function feed_blocks(str, offs, size)
+ -- size >= 0, size is multiple of 64
+ while size > 0 do
+ local part_size_in_blocks, block_flags, H_in = 1, 0, H
+ if blocks_in_chunk == 0 then
+ block_flags = 1 -- flag:CHUNK_START
+ H_in, final_H_in = K, H
+ final_compression_flags = 2 -- flag:CHUNK_END
+ elseif blocks_in_chunk == 15 then
+ block_flags = 2 -- flag:CHUNK_END
+ final_compression_flags = 3 -- flags:CHUNK_START,CHUNK_END
+ final_H_in = K
+ else
+ part_size_in_blocks = math_min(size / 64, 15 - blocks_in_chunk)
+ end
+ local part_size = part_size_in_blocks * 64
+ blake3_feed_64(str, offs, part_size, message_flags + block_flags, chunk_index, H_in, H)
+ offs, size = offs + part_size, size - part_size
+ blocks_in_chunk = (blocks_in_chunk + part_size_in_blocks) % 16
+ if blocks_in_chunk == 0 then
+ -- completing the currect chunk
+ chunk_index = chunk_index + 1.0
+ local divider = 2.0
+ while chunk_index % divider == 0 do
+ divider = divider * 2.0
+ stack_size = stack_size - 8
+ for j = 1, 8 do
+ common_W_blake2s[j] = stack[stack_size + j]
+ end
+ for j = 1, 8 do
+ common_W_blake2s[j + 8] = H[j]
+ end
+ blake3_feed_64(nil, 0, 64, message_flags + 4, 0, K, H) -- flag:PARENT
+ end
+ for j = 1, 8 do
+ stack[stack_size + j] = H[j]
+ end
+ stack_size = stack_size + 8
+ end
+ end
+ end
+
+ local function get_hash_block(block_no)
+ local size = math_min(64, digest_size_in_bytes - block_no * 64)
+ if block_no < 0 or size <= 0 then
+ return ""
+ end
+ if chunk_by_chunk_output then
+ for j = 1, 16 do
+ common_W_blake2s[j] = stack[j + 16]
+ end
+ end
+ blake3_feed_64(nil, 0, 64, final_compression_flags, block_no, final_H_in, stack, wide_output, final_block_length)
+ if return_array then
+ return stack
+ end
+ local max_reg = ceil(size / 4)
+ for j = 1, max_reg do
+ stack[j] = HEX(stack[j])
+ end
+ return sub(gsub(table_concat(stack, "", 1, max_reg), "(..)(..)(..)(..)", "%4%3%2%1"), 1, size * 2)
+ end
+
+ local function partial(message_part)
+ if message_part then
+ if tail then
+ local offs = 0
+ if tail ~= "" and #tail + #message_part > 64 then
+ offs = 64 - #tail
+ feed_blocks(tail..sub(message_part, 1, offs), 0, 64)
+ tail = ""
+ end
+ local size = #message_part - offs
+ local size_tail = size > 0 and (size - 1) % 64 + 1 or 0
+ feed_blocks(message_part, offs, size - size_tail)
+ tail = tail..sub(message_part, #message_part + 1 - size_tail)
+ return partial
+ else
+ error("Adding more chunks is not allowed after receiving the result", 2)
+ end
+ else
+ if tail then
+ final_block_length = #tail
+ tail = tail..string_rep("\0", 64 - #tail)
+ if common_W_blake2s[0] then
+ for j = 1, 16 do
+ local a, b, c, d = byte(tail, 4*j-3, 4*j)
+ common_W_blake2s[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)
+ end
+ else
+ for j = 1, 16 do
+ local a, b, c, d = byte(tail, 4*j-3, 4*j)
+ common_W_blake2s[j] = ((d * 256 + c) * 256 + b) * 256 + a
+ end
+ end
+ tail = nil
+ for stack_size = stack_size - 8, 0, -8 do
+ blake3_feed_64(nil, 0, 64, message_flags + final_compression_flags, chunk_index, final_H_in, H, nil, final_block_length)
+ chunk_index, final_block_length, final_H_in, final_compression_flags = 0, 64, K, 4 -- flag:PARENT
+ for j = 1, 8 do
+ common_W_blake2s[j] = stack[stack_size + j]
+ end
+ for j = 1, 8 do
+ common_W_blake2s[j + 8] = H[j]
+ end
+ end
+ final_compression_flags = message_flags + final_compression_flags + 8 -- flag:ROOT
+ if digest_size_in_bytes < 0 then
+ if digest_size_in_bytes == -1 then -- infinite digest
+ digest_size_in_bytes = math_huge
+ else
+ digest_size_in_bytes = -1.0 * digest_size_in_bytes
+ end
+ chunk_by_chunk_output = true
+ for j = 1, 16 do
+ stack[j + 16] = common_W_blake2s[j]
+ end
+ end
+ digest_size_in_bytes = math_min(2^53, digest_size_in_bytes)
+ wide_output = digest_size_in_bytes > 32
+ if chunk_by_chunk_output then
+ local pos, cached_block_no, cached_block = 0.0
+
+ local function get_next_part_of_digest(arg1, arg2)
+ if arg1 == "seek" then
+ -- Usage #1: get_next_part_of_digest("seek", new_pos)
+ pos = arg2 * 1.0
+ else
+ -- Usage #2: hex_string = get_next_part_of_digest(size)
+ local size, index = arg1 or 1, 32
+ while size > 0 do
+ local block_offset = pos % 64
+ local block_no = (pos - block_offset) / 64
+ local part_size = math_min(size, 64 - block_offset)
+ if cached_block_no ~= block_no then
+ cached_block_no = block_no
+ cached_block = get_hash_block(block_no)
+ end
+ index = index + 1
+ stack[index] = sub(cached_block, block_offset * 2 + 1, (block_offset + part_size) * 2)
+ size = size - part_size
+ pos = pos + part_size
+ end
+ return table_concat(stack, "", 33, index)
+ end
+ end
+
+ result = get_next_part_of_digest
+ elseif digest_size_in_bytes <= 64 then
+ result = get_hash_block(0)
+ else
+ local last_block_no = ceil(digest_size_in_bytes / 64) - 1
+ for block_no = 0.0, last_block_no do
+ stack[33 + block_no] = get_hash_block(block_no)
+ end
+ result = table_concat(stack, "", 33, 33 + last_block_no)
+ end
+ end
+ return result
+ end
+ end
+
+ if message then
+ -- Actually perform calculations and return the BLAKE3 digest of a message
+ return partial(message)()
+ else
+ -- Return function for chunk-by-chunk loading
+ -- User should feed every chunk of input data as single argument to this function and finally get BLAKE3 digest by invoking this function without an argument
+ return partial
+ end
+end
+
+local function blake3_derive_key(key_material, context_string, derived_key_size_in_bytes)
+ -- key_material: (string) your source of entropy to derive a key from (for example, it can be a master password)
+ -- set to nil for feeding the key material in "chunk-by-chunk" input mode
+ -- context_string: (string) unique description of the derived key
+ -- digest_size_in_bytes: (optional) by default 32
+ -- 0,1,2,3,4,... = get finite derived key as single Lua string
+ -- (-1) = get infinite derived key in "chunk-by-chunk" output mode
+ -- -2,-3,-4,... = get finite derived key in "chunk-by-chunk" output mode
+ if type(context_string) ~= "string" then
+ error("'context_string' parameter must be a Lua string", 2)
+ end
+ local K = blake3(context_string, nil, nil, 32, nil, true) -- flag:DERIVE_KEY_CONTEXT
+ return blake3(key_material, nil, derived_key_size_in_bytes, 64, K) -- flag:DERIVE_KEY_MATERIAL
+end
+
+
+
+local sha = {
+ md5 = md5, -- MD5
+ sha1 = sha1, -- SHA-1
+ -- SHA-2 hash functions:
+ sha224 = function (message) return sha256ext(224, message) end, -- SHA-224
+ sha256 = function (message) return sha256ext(256, message) end, -- SHA-256
+ sha512_224 = function (message) return sha512ext(224, message) end, -- SHA-512/224
+ sha512_256 = function (message) return sha512ext(256, message) end, -- SHA-512/256
+ sha384 = function (message) return sha512ext(384, message) end, -- SHA-384
+ sha512 = function (message) return sha512ext(512, message) end, -- SHA-512
+ -- SHA-3 hash functions:
+ sha3_224 = function (message) return keccak((1600 - 2 * 224) / 8, 224 / 8, false, message) end, -- SHA3-224
+ sha3_256 = function (message) return keccak((1600 - 2 * 256) / 8, 256 / 8, false, message) end, -- SHA3-256
+ sha3_384 = function (message) return keccak((1600 - 2 * 384) / 8, 384 / 8, false, message) end, -- SHA3-384
+ sha3_512 = function (message) return keccak((1600 - 2 * 512) / 8, 512 / 8, false, message) end, -- SHA3-512
+ shake128 = function (digest_size_in_bytes, message) return keccak((1600 - 2 * 128) / 8, digest_size_in_bytes, true, message) end, -- SHAKE128
+ shake256 = function (digest_size_in_bytes, message) return keccak((1600 - 2 * 256) / 8, digest_size_in_bytes, true, message) end, -- SHAKE256
+ -- HMAC:
+ hmac = hmac, -- HMAC(hash_func, key, message) is applicable to any hash function from this module except SHAKE* and BLAKE*
+ -- misc utilities:
+ hex_to_bin = hex_to_bin, -- converts hexadecimal representation to binary string
+ bin_to_hex = bin_to_hex, -- converts binary string to hexadecimal representation
+ base64_to_bin = base64_to_bin, -- converts base64 representation to binary string
+ bin_to_base64 = bin_to_base64, -- converts binary string to base64 representation
+ -- old style names for backward compatibility:
+ hex2bin = hex_to_bin,
+ bin2hex = bin_to_hex,
+ base642bin = base64_to_bin,
+ bin2base64 = bin_to_base64,
+ -- BLAKE2 hash functions:
+ blake2b = blake2b, -- BLAKE2b (message, key, salt, digest_size_in_bytes)
+ blake2s = blake2s, -- BLAKE2s (message, key, salt, digest_size_in_bytes)
+ blake2bp = blake2bp, -- BLAKE2bp(message, key, salt, digest_size_in_bytes)
+ blake2sp = blake2sp, -- BLAKE2sp(message, key, salt, digest_size_in_bytes)
+ blake2xb = blake2xb, -- BLAKE2Xb(digest_size_in_bytes, message, key, salt)
+ blake2xs = blake2xs, -- BLAKE2Xs(digest_size_in_bytes, message, key, salt)
+ -- BLAKE2 aliases:
+ blake2 = blake2b,
+ blake2b_160 = function (message, key, salt) return blake2b(message, key, salt, 20) end, -- BLAKE2b-160
+ blake2b_256 = function (message, key, salt) return blake2b(message, key, salt, 32) end, -- BLAKE2b-256
+ blake2b_384 = function (message, key, salt) return blake2b(message, key, salt, 48) end, -- BLAKE2b-384
+ blake2b_512 = blake2b, -- 64 -- BLAKE2b-512
+ blake2s_128 = function (message, key, salt) return blake2s(message, key, salt, 16) end, -- BLAKE2s-128
+ blake2s_160 = function (message, key, salt) return blake2s(message, key, salt, 20) end, -- BLAKE2s-160
+ blake2s_224 = function (message, key, salt) return blake2s(message, key, salt, 28) end, -- BLAKE2s-224
+ blake2s_256 = blake2s, -- 32 -- BLAKE2s-256
+ -- BLAKE3 hash function
+ blake3 = blake3, -- BLAKE3 (message, key, digest_size_in_bytes)
+ blake3_derive_key = blake3_derive_key, -- BLAKE3_KDF(key_material, context_string, derived_key_size_in_bytes)
+}
+
+
+block_size_for_HMAC = {
+ [sha.md5] = 64,
+ [sha.sha1] = 64,
+ [sha.sha224] = 64,
+ [sha.sha256] = 64,
+ [sha.sha512_224] = 128,
+ [sha.sha512_256] = 128,
+ [sha.sha384] = 128,
+ [sha.sha512] = 128,
+ [sha.sha3_224] = 144, -- (1600 - 2 * 224) / 8
+ [sha.sha3_256] = 136, -- (1600 - 2 * 256) / 8
+ [sha.sha3_384] = 104, -- (1600 - 2 * 384) / 8
+ [sha.sha3_512] = 72, -- (1600 - 2 * 512) / 8
+}
+
+
+return sha
diff --git a/suricata/lua/tcpstore.lua b/suricata/lua/tcpstore.lua
new file mode 100644
index 0000000..cb077c9
--- /dev/null
+++ b/suricata/lua/tcpstore.lua
@@ -0,0 +1,62 @@
+-- Copyright (C) 2023 ANSSI
+-- SPDX-License-Identifier: GPL-3.0-only
+
+function init (args)
+ local needs = {}
+ needs["type"] = "streaming"
+ needs["filter"] = "tcp"
+ return needs
+end
+
+function setup (args)
+ -- tcpstore.log contains (flow_id, direction, sha256) tuples
+ local logfilename = SCLogPath() .. "/tcpstore.log"
+ logfile = assert(io.open(logfilename, "a"))
+
+ -- tcpstore folder contains raw data
+ foldername = SCLogPath() .. "/tcpstore/"
+ for i=0,255 do
+ istr = string.format("%02x", i)
+ os.execute("mkdir -p " .. foldername .. istr)
+ end
+
+ -- packer counter for each flow
+ flow_pkt_count = {}
+ flow_pkt_count_total = 0
+end
+
+function log (args)
+ local sha = require("suricata/lua/sha2")
+
+ -- create log entry
+ local flow_id = SCFlowId()
+ local flow_id_str = string.format("%.0f", flow_id)
+ if flow_pkt_count[flow_id] == nil then
+ flow_pkt_count[flow_id] = 0
+ else
+ flow_pkt_count[flow_id] = flow_pkt_count[flow_id] + 1
+ end
+ local count = flow_pkt_count[flow_id]
+ flow_pkt_count_total = flow_pkt_count_total + 1
+ local data, sb_open, sb_close, sb_ts, sb_tc = SCStreamingBuffer()
+ if #data == 0 then
+ return
+ end
+ local direction = "0"
+ if sb_tc then
+ direction = "1"
+ end
+ local hash = sha.sha256(data)
+ logfile:write(flow_id_str .. "," .. count .. "," .. direction .. "," .. hash .. "\n")
+
+ -- save data
+ local filename = foldername .. string.sub(hash, 1, 2) .. "/" .. hash
+ local datafile = assert(io.open(filename, "w"))
+ datafile:write(data)
+ datafile:close()
+end
+
+function deinit (args)
+ SCLogNotice("TCP flow logged: " .. flow_pkt_count_total)
+ logfile:close()
+end
diff --git a/suricata/lua/udpstore.lua b/suricata/lua/udpstore.lua
new file mode 100644
index 0000000..f4b35b3
--- /dev/null
+++ b/suricata/lua/udpstore.lua
@@ -0,0 +1,71 @@
+-- Copyright (C) 2023 ANSSI
+-- SPDX-License-Identifier: GPL-3.0-only
+
+function init (args)
+ local needs = {}
+ needs["type"] = "packet"
+ return needs
+end
+
+function setup (args)
+ -- udpstore.log contains (flow_id, direction, sha256) tuples
+ local logfilename = SCLogPath() .. "/udpstore.log"
+ logfile = assert(io.open(logfilename, "a"))
+
+ -- udpstore folder contains raw data
+ foldername = SCLogPath() .. "/udpstore/"
+ for i=0,255 do
+ istr = string.format("%02x", i)
+ os.execute("mkdir -p " .. foldername .. istr)
+ end
+
+ -- packer counter for each flow
+ flow_pkt_count = {}
+ flow_pkt_count_total = 0
+end
+
+function log (args)
+ local sha = require("suricata/lua/sha2")
+
+ -- drop if not UDP (17)
+ -- https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
+ local ipver, srcip, dstip, proto, sp, dp = SCPacketTuple()
+ if proto ~= 17 then
+ return
+ end
+
+ -- get packet direction
+ local ipver, srcip_flow, dstip_flow, proto, sp_flow, dp_flow = SCFlowTuple()
+ local direction = "1"
+ if srcip == srcip_flow and dstip == dstip_flow and sp == sp_flow and dp == dp_flow then
+ direction = "0"
+ end
+
+ -- create log entry
+ local flow_id = SCFlowId()
+ local flow_id_str = string.format("%.0f", flow_id)
+ if flow_pkt_count[flow_id] == nil then
+ flow_pkt_count[flow_id] = 0
+ else
+ flow_pkt_count[flow_id] = flow_pkt_count[flow_id] + 1
+ end
+ local count = flow_pkt_count[flow_id]
+ flow_pkt_count_total = flow_pkt_count_total + 1
+ local data = SCPacketPayload()
+ if #data == 0 then
+ return
+ end
+ local hash = sha.sha256(data)
+ logfile:write(flow_id_str .. "," .. count .. "," .. direction .. "," .. hash .. "\n")
+
+ -- save data
+ local filename = foldername .. string.sub(hash, 1, 2) .. "/" .. hash
+ local datafile = assert(io.open(filename, "w"))
+ datafile:write(data)
+ datafile:close()
+end
+
+function deinit (args)
+ SCLogNotice("UDP flow logged: " .. flow_pkt_count_total)
+ logfile:close()
+end
diff --git a/suricata/output/.gitkeep b/suricata/output/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/suricata/suricata.yaml b/suricata/suricata.yaml
new file mode 100644
index 0000000..0f28495
--- /dev/null
+++ b/suricata/suricata.yaml
@@ -0,0 +1,2168 @@
+%YAML 1.1
+---
+
+# Suricata configuration file. In addition to the comments describing all
+# options in this file, full documentation can be found at:
+# https://docs.suricata.io/en/latest/configuration/suricata-yaml.html
+
+# This configuration file generated by Suricata 7.0.0.
+suricata-version: "7.0"
+
+##
+## Step 1: Inform Suricata about your network
+##
+
+vars:
+ # more specific is better for alert accuracy and performance
+ address-groups:
+ HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"
+ #HOME_NET: "[192.168.0.0/16]"
+ #HOME_NET: "[10.0.0.0/8]"
+ #HOME_NET: "[172.16.0.0/12]"
+ #HOME_NET: "any"
+
+ EXTERNAL_NET: "!$HOME_NET"
+ #EXTERNAL_NET: "any"
+
+ HTTP_SERVERS: "$HOME_NET"
+ SMTP_SERVERS: "$HOME_NET"
+ SQL_SERVERS: "$HOME_NET"
+ DNS_SERVERS: "$HOME_NET"
+ TELNET_SERVERS: "$HOME_NET"
+ AIM_SERVERS: "$EXTERNAL_NET"
+ DC_SERVERS: "$HOME_NET"
+ DNP3_SERVER: "$HOME_NET"
+ DNP3_CLIENT: "$HOME_NET"
+ MODBUS_CLIENT: "$HOME_NET"
+ MODBUS_SERVER: "$HOME_NET"
+ ENIP_CLIENT: "$HOME_NET"
+ ENIP_SERVER: "$HOME_NET"
+
+ port-groups:
+ HTTP_PORTS: "80"
+ SHELLCODE_PORTS: "!80"
+ ORACLE_PORTS: 1521
+ SSH_PORTS: 22
+ DNP3_PORTS: 20000
+ MODBUS_PORTS: 502
+ FILE_DATA_PORTS: "[$HTTP_PORTS,110,143]"
+ FTP_PORTS: 21
+ GENEVE_PORTS: 6081
+ VXLAN_PORTS: 4789
+ TEREDO_PORTS: 3544
+
+##
+## Step 2: Select outputs to enable
+##
+
+# The default logging directory. Any log or output file will be
+# placed here if it's not specified with a full path name. This can be
+# overridden with the -l command line parameter.
+default-log-dir: /var/log/suricata/
+
+# Global stats configuration
+stats:
+ enabled: no
+ # The interval field (in seconds) controls the interval at
+ # which stats are updated in the log.
+ interval: 8
+ # Add decode events to stats.
+ #decoder-events: true
+ # Decoder event prefix in stats. Has been 'decoder' before, but that leads
+ # to missing events in the eve.stats records. See issue #2225.
+ #decoder-events-prefix: "decoder.event"
+ # Add stream events as stats.
+ #stream-events: false
+
+# Plugins -- Experimental -- specify the filename for each plugin shared object
+plugins:
+# - /path/to/plugin.so
+
+# Configure the type of alert (and other) logging you would like.
+outputs:
+ # a line based alerts log similar to Snort's fast.log
+ - fast:
+ enabled: no
+ filename: fast.log
+ append: yes
+ #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram'
+
+ # Extensible Event Format (nicknamed EVE) event log in JSON format
+ - eve-log:
+ enabled: yes
+ filetype: regular #regular|syslog|unix_dgram|unix_stream|redis
+ filename: eve.json
+ # Enable for multi-threaded eve.json output; output files are amended with
+ # an identifier, e.g., eve.9.json
+ #threaded: false
+ #prefix: "@cee: " # prefix to prepend to each log entry
+ # the following are valid when type: syslog above
+ #identity: "suricata"
+ #facility: local5
+ #level: Info ## possible levels: Emergency, Alert, Critical,
+ ## Error, Warning, Notice, Info, Debug
+ #ethernet: no # log ethernet header in events when available
+ #redis:
+ # server: 127.0.0.1
+ # port: 6379
+ # async: true ## if redis replies are read asynchronously
+ # mode: list ## possible values: list|lpush (default), rpush, channel|publish
+ # ## lpush and rpush are using a Redis list. "list" is an alias for lpush
+ # ## publish is using a Redis channel. "channel" is an alias for publish
+ # key: suricata ## key or channel to use (default to suricata)
+ # Redis pipelining set up. This will enable to only do a query every
+ # 'batch-size' events. This should lower the latency induced by network
+ # connection at the cost of some memory. There is no flushing implemented
+ # so this setting should be reserved to high traffic Suricata deployments.
+ # pipelining:
+ # enabled: yes ## set enable to yes to enable query pipelining
+ # batch-size: 10 ## number of entries to keep in buffer
+
+ # Include top level metadata. Default yes.
+ #metadata: no
+
+ # include the name of the input pcap file in pcap file processing mode
+ pcap-file: true
+
+ # Community Flow ID
+ # Adds a 'community_id' field to EVE records. These are meant to give
+ # records a predictable flow ID that can be used to match records to
+ # output of other tools such as Zeek (Bro).
+ #
+ # Takes a 'seed' that needs to be same across sensors and tools
+ # to make the id less predictable.
+
+ # enable/disable the community id feature.
+ community-id: false
+ # Seed value for the ID output. Valid values are 0-65535.
+ community-id-seed: 0
+
+ # HTTP X-Forwarded-For support by adding an extra field or overwriting
+ # the source or destination IP address (depending on flow direction)
+ # with the one reported in the X-Forwarded-For HTTP header. This is
+ # helpful when reviewing alerts for traffic that is being reverse
+ # or forward proxied.
+ xff:
+ enabled: no
+ # Two operation modes are available: "extra-data" and "overwrite".
+ mode: extra-data
+ # Two proxy deployments are supported: "reverse" and "forward". In
+ # a "reverse" deployment the IP address used is the last one, in a
+ # "forward" deployment the first IP address is used.
+ deployment: reverse
+ # Header name where the actual IP address will be reported. If more
+ # than one IP address is present, the last IP address will be the
+ # one taken into consideration.
+ header: X-Forwarded-For
+
+ types:
+ - alert:
+ # payload: yes # enable dumping payload in Base64
+ # payload-buffer-size: 4kb # max size of payload buffer to output in eve-log
+ # payload-printable: yes # enable dumping payload in printable (lossy) format
+ # packet: yes # enable dumping of packet (without stream segments)
+ # metadata: no # enable inclusion of app layer metadata with alert. Default yes
+ # http-body: yes # Requires metadata; enable dumping of HTTP body in Base64
+ # http-body-printable: yes # Requires metadata; enable dumping of HTTP body in printable format
+
+ # Enable the logging of tagged packets for rules using the
+ # "tag" keyword.
+ tagged-packets: yes
+ # Enable logging the final action taken on a packet by the engine
+ # (e.g: the alert may have action 'allowed' but the verdict be
+ # 'drop' due to another alert. That's the engine's verdict)
+ # verdict: yes
+ # app layer frames
+ - frame:
+ # disabled by default as this is very verbose.
+ enabled: no
+ - anomaly:
+ # Anomaly log records describe unexpected conditions such
+ # as truncated packets, packets with invalid IP/UDP/TCP
+ # length values, and other events that render the packet
+ # invalid for further processing or describe unexpected
+ # behavior on an established stream. Networks which
+ # experience high occurrences of anomalies may experience
+ # packet processing degradation.
+ #
+ # Anomalies are reported for the following:
+ # 1. Decode: Values and conditions that are detected while
+ # decoding individual packets. This includes invalid or
+ # unexpected values for low-level protocol lengths as well
+ # as stream related events (TCP 3-way handshake issues,
+ # unexpected sequence number, etc).
+ # 2. Stream: This includes stream related events (TCP
+ # 3-way handshake issues, unexpected sequence number,
+ # etc).
+ # 3. Application layer: These denote application layer
+ # specific conditions that are unexpected, invalid or are
+ # unexpected given the application monitoring state.
+ #
+ # By default, anomaly logging is enabled. When anomaly
+ # logging is enabled, applayer anomaly reporting is
+ # also enabled.
+ enabled: yes
+ #
+ # Choose one or more types of anomaly logging and whether to enable
+ # logging of the packet header for packet anomalies.
+ types:
+ # decode: no
+ # stream: no
+ # applayer: yes
+ #packethdr: no
+ - http:
+ extended: yes # enable this for extended logging information
+ # custom allows additional HTTP fields to be included in eve-log.
+ # the example below adds three additional fields when uncommented
+ #custom: [Accept-Encoding, Accept-Language, Authorization]
+ # set this value to one and only one from {both, request, response}
+ # to dump all HTTP headers for every HTTP request and/or response
+ dump-all-headers: both
+ - dns:
+ # This configuration uses the new DNS logging format,
+ # the old configuration is still available:
+ # https://docs.suricata.io/en/latest/output/eve/eve-json-output.html#dns-v1-format
+
+ # As of Suricata 5.0, version 2 of the eve dns output
+ # format is the default.
+ #version: 2
+
+ # Enable/disable this logger. Default: enabled.
+ #enabled: yes
+
+ # Control logging of requests and responses:
+ # - requests: enable logging of DNS queries
+ # - responses: enable logging of DNS answers
+ # By default both requests and responses are logged.
+ #requests: no
+ #responses: no
+
+ # Format of answer logging:
+ # - detailed: array item per answer
+ # - grouped: answers aggregated by type
+ # Default: all
+ #formats: [detailed, grouped]
+
+ # DNS record types to log, based on the query type.
+ # Default: all.
+ #types: [a, aaaa, cname, mx, ns, ptr, txt]
+ - tls:
+ extended: yes # enable this for extended logging information
+ # output TLS transaction where the session is resumed using a
+ # session id
+ #session-resumption: no
+ # custom controls which TLS fields that are included in eve-log
+ #custom: [subject, issuer, session_resumed, serial, fingerprint, sni, version, not_before, not_after, certificate, chain, ja3, ja3s]
+ - files:
+ force-magic: yes # force logging magic on all logged files
+ # force logging of checksums, available hash functions are md5,
+ # sha1 and sha256
+ force-hash: [sha256]
+ #- drop:
+ # alerts: yes # log alerts that caused drops
+ # flows: all # start or all: 'start' logs only a single drop
+ # # per flow direction. All logs each dropped pkt.
+ # Enable logging the final action taken on a packet by the engine
+ # (will show more information in case of a drop caused by 'reject')
+ # verdict: yes
+ - smtp:
+ #extended: yes # enable this for extended logging information
+ # this includes: bcc, message-id, subject, x_mailer, user-agent
+ # custom fields logging from the list:
+ # reply-to, bcc, message-id, subject, x-mailer, user-agent, received,
+ # x-originating-ip, in-reply-to, references, importance, priority,
+ # sensitivity, organization, content-md5, date
+ #custom: [received, x-mailer, x-originating-ip, relays, reply-to, bcc]
+ # output md5 of fields: body, subject
+ # for the body you need to set app-layer.protocols.smtp.mime.body-md5
+ # to yes
+ #md5: [body, subject]
+
+ #- dnp3
+ - ftp
+ - rdp
+ - nfs
+ - smb
+ - tftp
+ - ike
+ - dcerpc
+ - krb5
+ - bittorrent-dht
+ - snmp
+ - rfb
+ - sip
+ - quic
+ - dhcp:
+ enabled: yes
+ # When extended mode is on, all DHCP messages are logged
+ # with full detail. When extended mode is off (the
+ # default), just enough information to map a MAC address
+ # to an IP address is logged.
+ extended: yes
+ - ssh
+ - mqtt:
+ passwords: yes # enable output of passwords
+ - http2
+ - pgsql:
+ enabled: yes
+ passwords: yes # enable output of passwords. Disabled by default
+ #- stats:
+ # totals: yes # stats for all threads merged together
+ # threads: no # per thread stats
+ # deltas: no # include delta values
+ # bi-directional flows
+ - flow
+ # uni-directional flows
+ #- netflow
+
+ # Metadata event type. Triggered whenever a pktvar is saved
+ # and will include the pktvars, flowvars, flowbits and
+ # flowints.
+ #- metadata
+
+ # EXPERIMENTAL per packet output giving TCP state tracking details
+ # including internal state, flags, etc.
+ # This output is experimental, meant for debugging and subject to
+ # change in both config and output without any notice.
+ #- stream:
+ # all: false # log all TCP packets
+ # event-set: false # log packets that have a decoder/stream event
+ # state-update: false # log packets triggering a TCP state update
+ # spurious-retransmission: false # log spurious retransmission packets
+
+ # a line based log of HTTP requests (no alerts)
+ - http-log:
+ enabled: no
+ filename: http.log
+ append: yes
+ #extended: yes # enable this for extended logging information
+ #custom: yes # enable the custom logging format (defined by customformat)
+ #customformat: "%{%D-%H:%M:%S}t.%z %{X-Forwarded-For}i %H %m %h %u %s %B %a:%p -> %A:%P"
+ #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram'
+
+ # a line based log of TLS handshake parameters (no alerts)
+ - tls-log:
+ enabled: no # Log TLS connections.
+ filename: tls.log # File to store TLS logs.
+ append: yes
+ #extended: yes # Log extended information like fingerprint
+ #custom: yes # enabled the custom logging format (defined by customformat)
+ #customformat: "%{%D-%H:%M:%S}t.%z %a:%p -> %A:%P %v %n %d %D"
+ #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram'
+ # output TLS transaction where the session is resumed using a
+ # session id
+ #session-resumption: no
+
+ # output module to store certificates chain to disk
+ - tls-store:
+ enabled: no
+ #certs-log-dir: certs # directory to store the certificates files
+
+ # Packet log... log packets in pcap format. 3 modes of operation: "normal"
+ # "multi" and "sguil".
+ #
+ # In normal mode a pcap file "filename" is created in the default-log-dir,
+ # or as specified by "dir".
+ # In multi mode, a file is created per thread. This will perform much
+ # better, but will create multiple files where 'normal' would create one.
+ # In multi mode the filename takes a few special variables:
+ # - %n -- thread number
+ # - %i -- thread id
+ # - %t -- timestamp (secs or secs.usecs based on 'ts-format'
+ # E.g. filename: pcap.%n.%t
+ #
+ # Note that it's possible to use directories, but the directories are not
+ # created by Suricata. E.g. filename: pcaps/%n/log.%s will log into the
+ # per thread directory.
+ #
+ # Also note that the limit and max-files settings are enforced per thread.
+ # So the size limit when using 8 threads with 1000mb files and 2000 files
+ # is: 8*1000*2000 ~ 16TiB.
+ #
+ # In Sguil mode "dir" indicates the base directory. In this base dir the
+ # pcaps are created in the directory structure Sguil expects:
+ #
+ # $sguil-base-dir/YYYY-MM-DD/$filename.
+ #
+ # By default all packets are logged except:
+ # - TCP streams beyond stream.reassembly.depth
+ # - encrypted streams after the key exchange
+ #
+ - pcap-log:
+ enabled: no
+ filename: log.pcap
+
+ # File size limit. Can be specified in kb, mb, gb. Just a number
+ # is parsed as bytes.
+ limit: 1000mb
+
+ # If set to a value, ring buffer mode is enabled. Will keep maximum of
+ # "max-files" of size "limit"
+ max-files: 2000
+
+ # Compression algorithm for pcap files. Possible values: none, lz4.
+ # Enabling compression is incompatible with the sguil mode. Note also
+ # that on Windows, enabling compression will *increase* disk I/O.
+ compression: none
+
+ # Further options for lz4 compression. The compression level can be set
+ # to a value between 0 and 16, where higher values result in higher
+ # compression.
+ #lz4-checksum: no
+ #lz4-level: 0
+
+ mode: normal # normal, multi or sguil.
+
+ # Directory to place pcap files. If not provided the default log
+ # directory will be used. Required for "sguil" mode.
+ #dir: /nsm_data/
+
+ #ts-format: usec # sec or usec second format (default) is filename.sec usec is filename.sec.usec
+ use-stream-depth: no #If set to "yes" packets seen after reaching stream inspection depth are ignored. "no" logs all packets
+ honor-pass-rules: no # If set to "yes", flows in which a pass rule matched will stop being logged.
+ # Use "all" to log all packets or use "alerts" to log only alerted packets and flows or "tag"
+ # to log only flow tagged via the "tag" keyword
+ #conditional: all
+
+ # a full alert log containing much information for signature writers
+ # or for investigating suspected false positives.
+ - alert-debug:
+ enabled: no
+ filename: alert-debug.log
+ append: yes
+ #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram'
+
+ # Stats.log contains data from various counters of the Suricata engine.
+ - stats:
+ enabled: no
+ filename: stats.log
+ append: yes # append to file (yes) or overwrite it (no)
+ totals: yes # stats for all threads merged together
+ threads: no # per thread stats
+ #null-values: yes # print counters that have value 0. Default: no
+
+ # a line based alerts log similar to fast.log into syslog
+ - syslog:
+ enabled: no
+ # reported identity to syslog. If omitted the program name (usually
+ # suricata) will be used.
+ #identity: "suricata"
+ facility: local5
+ #level: Info ## possible levels: Emergency, Alert, Critical,
+ ## Error, Warning, Notice, Info, Debug
+
+ # Output module for storing files on disk. Files are stored in
+ # directory names consisting of the first 2 characters of the
+ # SHA256 of the file. Each file is given its SHA256 as a filename.
+ #
+ # When a duplicate file is found, the timestamps on the existing file
+ # are updated.
+ #
+ # Unlike the older filestore, metadata is not written by default
+ # as each file should already have a "fileinfo" record in the
+ # eve-log. If write-fileinfo is set to yes, then each file will have
+ # one more associated .json files that consist of the fileinfo
+ # record. A fileinfo file will be written for each occurrence of the
+ # file seen using a filename suffix to ensure uniqueness.
+ #
+ # To prune the filestore directory see the "suricatactl filestore
+ # prune" command which can delete files over a certain age.
+ - file-store:
+ version: 2
+ enabled: yes
+
+ # Set the directory for the filestore. Relative pathnames
+ # are contained within the "default-log-dir".
+ #dir: filestore
+
+ # Write out a fileinfo record for each occurrence of a file.
+ # Disabled by default as each occurrence is already logged
+ # as a fileinfo record to the main eve-log.
+ #write-fileinfo: yes
+
+ # Force storing of all files. Default: no.
+ force-filestore: yes
+
+ # Override the global stream-depth for sessions in which we want
+ # to perform file extraction. Set to 0 for unlimited; otherwise,
+ # must be greater than the global stream-depth value to be used.
+ #stream-depth: 0
+
+ # Uncomment the following variable to define how many files can
+ # remain open for filestore by Suricata. Default value is 0 which
+ # means files get closed after each write to the file.
+ #max-open-files: 1000
+
+ # Force logging of checksums: available hash functions are md5,
+ # sha1 and sha256. Note that SHA256 is automatically forced by
+ # the use of this output module as it uses the SHA256 as the
+ # file naming scheme.
+ #force-hash: [sha1, md5]
+ # NOTE: X-Forwarded configuration is ignored if write-fileinfo is disabled
+ # HTTP X-Forwarded-For support by adding an extra field or overwriting
+ # the source or destination IP address (depending on flow direction)
+ # with the one reported in the X-Forwarded-For HTTP header. This is
+ # helpful when reviewing alerts for traffic that is being reverse
+ # or forward proxied.
+ xff:
+ enabled: no
+ # Two operation modes are available, "extra-data" and "overwrite".
+ mode: extra-data
+ # Two proxy deployments are supported, "reverse" and "forward". In
+ # a "reverse" deployment the IP address used is the last one, in a
+ # "forward" deployment the first IP address is used.
+ deployment: reverse
+ # Header name where the actual IP address will be reported. If more
+ # than one IP address is present, the last IP address will be the
+ # one taken into consideration.
+ header: X-Forwarded-For
+
+ # Log TCP data after stream normalization
+ # Two types: file or dir:
+ # - file logs into a single logfile.
+ # - dir creates 2 files per TCP session and stores the raw TCP
+ # data into them.
+ # Use 'both' to enable both file and dir modes.
+ #
+ # Note: limited by "stream.reassembly.depth"
+ - tcp-data:
+ enabled: no
+ type: file
+ filename: tcp-data.log
+
+ # Log HTTP body data after normalization, de-chunking and unzipping.
+ # Two types: file or dir.
+ # - file logs into a single logfile.
+ # - dir creates 2 files per HTTP session and stores the
+ # normalized data into them.
+ # Use 'both' to enable both file and dir modes.
+ #
+ # Note: limited by the body limit settings
+ - http-body-data:
+ enabled: no
+ type: file
+ filename: http-data.log
+
+ # Lua Output Support - execute lua script to generate alert and event
+ # output.
+ # Documented at:
+ # https://docs.suricata.io/en/latest/output/lua-output.html
+ - lua:
+ enabled: yes
+ #scripts-dir: /etc/suricata/lua-output/
+ scripts:
+ - suricata/lua/tcpstore.lua
+ - suricata/lua/udpstore.lua
+
+# Logging configuration. This is not about logging IDS alerts/events, but
+# output about what Suricata is doing, like startup messages, errors, etc.
+logging:
+ # The default log level: can be overridden in an output section.
+ # Note that debug level logging will only be emitted if Suricata was
+ # compiled with the --enable-debug configure option.
+ #
+ # This value is overridden by the SC_LOG_LEVEL env var.
+ default-log-level: notice
+
+ # The default output format. Optional parameter, should default to
+ # something reasonable if not provided. Can be overridden in an
+ # output section. You can leave this out to get the default.
+ #
+ # This console log format value can be overridden by the SC_LOG_FORMAT env var.
+ #default-log-format: "%D: %S: %M"
+ #
+ # For the pre-7.0 log format use:
+ #default-log-format: "[%i] %t [%S] - (%f:%l) <%d> (%n) -- "
+
+ # A regex to filter output. Can be overridden in an output section.
+ # Defaults to empty (no filter).
+ #
+ # This value is overridden by the SC_LOG_OP_FILTER env var.
+ default-output-filter:
+
+ # Requires libunwind to be available when Suricata is configured and built.
+ # If a signal unexpectedly terminates Suricata, displays a brief diagnostic
+ # message with the offending stacktrace if enabled.
+ #stacktrace-on-signal: on
+
+ # Define your logging outputs. If none are defined, or they are all
+ # disabled you will get the default: console output.
+ outputs:
+ - console:
+ enabled: yes
+ # type: json
+ - file:
+ enabled: yes
+ level: info
+ filename: suricata.log
+ # format: "[%i - %m] %z %d: %S: %M"
+ # type: json
+ - syslog:
+ enabled: no
+ facility: local5
+ format: "[%i] <%d> -- "
+ # type: json
+
+
+##
+## Step 3: Configure common capture settings
+##
+## See "Advanced Capture Options" below for more options, including Netmap
+## and PF_RING.
+##
+
+# Linux high speed capture support
+af-packet:
+ - interface: eth0
+ # Number of receive threads. "auto" uses the number of cores
+ #threads: auto
+ # Default clusterid. AF_PACKET will load balance packets based on flow.
+ cluster-id: 99
+ # Default AF_PACKET cluster type. AF_PACKET can load balance per flow or per hash.
+ # This is only supported for Linux kernel > 3.1
+ # possible value are:
+ # * cluster_flow: all packets of a given flow are sent to the same socket
+ # * cluster_cpu: all packets treated in kernel by a CPU are sent to the same socket
+ # * cluster_qm: all packets linked by network card to a RSS queue are sent to the same
+ # socket. Requires at least Linux 3.14.
+ # * cluster_ebpf: eBPF file load balancing. See doc/userguide/capture-hardware/ebpf-xdp.rst for
+ # more info.
+ # Recommended modes are cluster_flow on most boxes and cluster_cpu or cluster_qm on system
+ # with capture card using RSS (requires cpu affinity tuning and system IRQ tuning)
+ # cluster_rollover has been deprecated; if used, it'll be replaced with cluster_flow.
+ cluster-type: cluster_flow
+ # In some fragmentation cases, the hash can not be computed. If "defrag" is set
+ # to yes, the kernel will do the needed defragmentation before sending the packets.
+ defrag: yes
+ # To use the ring feature of AF_PACKET, set 'use-mmap' to yes
+ #use-mmap: yes
+ # Lock memory map to avoid it being swapped. Be careful that over
+ # subscribing could lock your system
+ #mmap-locked: yes
+ # Use tpacket_v3 capture mode, only active if use-mmap is true
+ # Don't use it in IPS or TAP mode as it causes severe latency
+ #tpacket-v3: yes
+ # Ring size will be computed with respect to "max-pending-packets" and number
+ # of threads. You can set manually the ring size in number of packets by setting
+ # the following value. If you are using flow "cluster-type" and have really network
+ # intensive single-flow you may want to set the "ring-size" independently of the number
+ # of threads:
+ #ring-size: 2048
+ # Block size is used by tpacket_v3 only. It should set to a value high enough to contain
+ # a decent number of packets. Size is in bytes so please consider your MTU. It should be
+ # a power of 2 and it must be multiple of page size (usually 4096).
+ #block-size: 32768
+ # tpacket_v3 block timeout: an open block is passed to userspace if it is not
+ # filled after block-timeout milliseconds.
+ #block-timeout: 10
+ # On busy systems, set it to yes to help recover from a packet drop
+ # phase. This will result in some packets (at max a ring flush) not being inspected.
+ #use-emergency-flush: yes
+ # recv buffer size, increased value could improve performance
+ # buffer-size: 32768
+ # Set to yes to disable promiscuous mode
+ # disable-promisc: no
+ # Choose checksum verification mode for the interface. At the moment
+ # of the capture, some packets may have an invalid checksum due to
+ # the checksum computation being offloaded to the network card.
+ # Possible values are:
+ # - kernel: use indication sent by kernel for each packet (default)
+ # - yes: checksum validation is forced
+ # - no: checksum validation is disabled
+ # - auto: Suricata uses a statistical approach to detect when
+ # checksum off-loading is used.
+ # Warning: 'capture.checksum-validation' must be set to yes to have any validation
+ #checksum-checks: kernel
+ # BPF filter to apply to this interface. The pcap filter syntax applies here.
+ #bpf-filter: port 80 or udp
+ # You can use the following variables to activate AF_PACKET tap or IPS mode.
+ # If copy-mode is set to ips or tap, the traffic coming to the current
+ # interface will be copied to the copy-iface interface. If 'tap' is set, the
+ # copy is complete. If 'ips' is set, the packet matching a 'drop' action
+ # will not be copied.
+ #copy-mode: ips
+ #copy-iface: eth1
+ # For eBPF and XDP setup including bypass, filter and load balancing, please
+ # see doc/userguide/capture-hardware/ebpf-xdp.rst for more info.
+
+ # Put default values here. These will be used for an interface that is not
+ # in the list above.
+ - interface: default
+ #threads: auto
+ #use-mmap: no
+ #tpacket-v3: yes
+
+# Linux high speed af-xdp capture support
+af-xdp:
+ - interface: default
+ # Number of receive threads. "auto" uses least between the number
+ # of cores and RX queues
+ #threads: auto
+ #disable-promisc: false
+ # XDP_DRV mode can be chosen when the driver supports XDP
+ # XDP_SKB mode can be chosen when the driver does not support XDP
+ # Possible values are:
+ # - drv: enable XDP_DRV mode
+ # - skb: enable XDP_SKB mode
+ # - none: disable (kernel in charge of applying mode)
+ #force-xdp-mode: none
+ # During socket binding the kernel will attempt zero-copy, if this
+ # fails it will fallback to copy. If this fails, the bind fails.
+ # The bind can be explicitly configured using the option below.
+ # If configured, the bind will fail if not successful (no fallback).
+ # Possible values are:
+ # - zero: enable zero-copy mode
+ # - copy: enable copy mode
+ # - none: disable (kernel in charge of applying mode)
+ #force-bind-mode: none
+ # Memory alignment mode can vary between two modes, aligned and
+ # unaligned chunk modes. By default, aligned chunk mode is selected.
+ # select 'yes' to enable unaligned chunk mode.
+ # Note: unaligned chunk mode uses hugepages, so the required number
+ # of pages must be available.
+ #mem-unaligned: no
+ # The following options configure the prefer-busy-polling socket
+ # options. The polling time and budget can be edited here.
+ # Possible values are:
+ # - yes: enable (default)
+ # - no: disable
+ #enable-busy-poll: yes
+ # busy-poll-time sets the approximate time in microseconds to busy
+ # poll on a blocking receive when there is no data.
+ #busy-poll-time: 20
+ # busy-poll-budget is the budget allowed for packet batches
+ #busy-poll-budget: 64
+ # These two tunables are used to configure the Linux OS's NAPI
+ # context. Their purpose is to defer enabling of interrupts and
+ # instead schedule the NAPI context from a watchdog timer.
+ # The softirq NAPI will exit early, allowing busy polling to be
+ # performed. Successfully setting these tunables alongside busy-polling
+ # should improve performance.
+ # Defaults are:
+ #gro-flush-timeout: 2000000
+ #napi-defer-hard-irq: 2
+
+dpdk:
+ eal-params:
+ proc-type: primary
+
+ # DPDK capture support
+ # RX queues (and TX queues in IPS mode) are assigned to cores in 1:1 ratio
+ interfaces:
+ - interface: 0000:3b:00.0 # PCIe address of the NIC port
+ # Threading: possible values are either "auto" or number of threads
+ # - auto takes all cores
+ # in IPS mode it is required to specify the number of cores and the numbers on both interfaces must match
+ threads: auto
+ promisc: true # promiscuous mode - capture all packets
+ multicast: true # enables also detection on multicast packets
+ checksum-checks: true # if Suricata should validate checksums
+ checksum-checks-offload: true # if possible offload checksum validation to the NIC (saves Suricata resources)
+ mtu: 1500 # Set MTU of the device in bytes
+ # rss-hash-functions: 0x0 # advanced configuration option, use only if you use untested NIC card and experience RSS warnings,
+ # For `rss-hash-functions` use hexadecimal 0x01ab format to specify RSS hash function flags - DumpRssFlags can help (you can see output if you use -vvv option during Suri startup)
+ # setting auto to rss_hf sets the default RSS hash functions (based on IP addresses)
+
+ # To approximately calculate required amount of space (in bytes) for interface's mempool: mempool-size * mtu
+ # Make sure you have enough allocated hugepages.
+ # The optimum size for the packet memory pool (in terms of memory usage) is power of two minus one: n = (2^q - 1)
+ mempool-size: 65535 # The number of elements in the mbuf pool
+
+ # Mempool cache size must be lower or equal to:
+ # - RTE_MEMPOOL_CACHE_MAX_SIZE (by default 512) and
+ # - "mempool-size / 1.5"
+ # It is advised to choose cache_size to have "mempool-size modulo cache_size == 0".
+ # If this is not the case, some elements will always stay in the pool and will never be used.
+ # The cache can be disabled if the cache_size argument is set to 0, can be useful to avoid losing objects in cache
+ # If the value is empty or set to "auto", Suricata will attempt to set cache size of the mempool to a value
+ # that matches the previously mentioned recommendations
+ mempool-cache-size: 257
+ rx-descriptors: 1024
+ tx-descriptors: 1024
+ #
+ # IPS mode for Suricata works in 3 modes - none, tap, ips
+ # - none: IDS mode only - disables IPS functionality (does not further forward packets)
+ # - tap: forwards all packets and generates alerts (omits DROP action) This is not DPDK TAP
+ # - ips: the same as tap mode but it also drops packets that are flagged by rules to be dropped
+ copy-mode: none
+ copy-iface: none # or PCIe address of the second interface
+
+ - interface: default
+ threads: auto
+ promisc: true
+ multicast: true
+ checksum-checks: true
+ checksum-checks-offload: true
+ mtu: 1500
+ rss-hash-functions: auto
+ mempool-size: 65535
+ mempool-cache-size: 257
+ rx-descriptors: 1024
+ tx-descriptors: 1024
+ copy-mode: none
+ copy-iface: none
+
+
+# Cross platform libpcap capture support
+pcap:
+ - interface: eth0
+ # On Linux, pcap will try to use mmap'ed capture and will use "buffer-size"
+ # as total memory used by the ring. So set this to something bigger
+ # than 1% of your bandwidth.
+ #buffer-size: 16777216
+ #bpf-filter: "tcp and port 25"
+ # Choose checksum verification mode for the interface. At the moment
+ # of the capture, some packets may have an invalid checksum due to
+ # the checksum computation being offloaded to the network card.
+ # Possible values are:
+ # - yes: checksum validation is forced
+ # - no: checksum validation is disabled
+ # - auto: Suricata uses a statistical approach to detect when
+ # checksum off-loading is used. (default)
+ # Warning: 'capture.checksum-validation' must be set to yes to have any validation
+ #checksum-checks: auto
+ # With some accelerator cards using a modified libpcap (like Myricom), you
+ # may want to have the same number of capture threads as the number of capture
+ # rings. In this case, set up the threads variable to N to start N threads
+ # listening on the same interface.
+ #threads: 16
+ # set to no to disable promiscuous mode:
+ #promisc: no
+ # set snaplen, if not set it defaults to MTU if MTU can be known
+ # via ioctl call and to full capture if not.
+ #snaplen: 1518
+ # Put default values here
+ - interface: default
+ #checksum-checks: auto
+
+# Settings for reading pcap files
+pcap-file:
+ # Possible values are:
+ # - yes: checksum validation is forced
+ # - no: checksum validation is disabled
+ # - auto: Suricata uses a statistical approach to detect when
+ # checksum off-loading is used. (default)
+ # Warning: 'checksum-validation' must be set to yes to have checksum tested
+ checksum-checks: no
+
+# See "Advanced Capture Options" below for more options, including Netmap
+# and PF_RING.
+
+
+##
+## Step 4: App Layer Protocol configuration
+##
+
+# Configure the app-layer parsers.
+#
+# The error-policy setting applies to all app-layer parsers. Values can be
+# "drop-flow", "pass-flow", "bypass", "drop-packet", "pass-packet", "reject" or
+# "ignore" (the default).
+#
+# The protocol's section details each protocol.
+#
+# The option "enabled" takes 3 values - "yes", "no", "detection-only".
+# "yes" enables both detection and the parser, "no" disables both, and
+# "detection-only" enables protocol detection only (parser disabled).
+app-layer:
+ # error-policy: ignore
+ protocols:
+ telnet:
+ enabled: yes
+ rfb:
+ enabled: yes
+ detection-ports:
+ dp: 5900, 5901, 5902, 5903, 5904, 5905, 5906, 5907, 5908, 5909
+ mqtt:
+ enabled: yes
+ # max-msg-length: 1mb
+ # subscribe-topic-match-limit: 100
+ # unsubscribe-topic-match-limit: 100
+ # Maximum number of live MQTT transactions per flow
+ # max-tx: 4096
+ krb5:
+ enabled: yes
+ bittorrent-dht:
+ enabled: yes
+ snmp:
+ enabled: yes
+ ike:
+ enabled: yes
+ tls:
+ enabled: yes
+ detection-ports:
+ dp: 443
+
+ # Generate JA3 fingerprint from client hello. If not specified it
+ # will be disabled by default, but enabled if rules require it.
+ #ja3-fingerprints: auto
+
+ # What to do when the encrypted communications start:
+ # - default: keep tracking TLS session, check for protocol anomalies,
+ # inspect tls_* keywords. Disables inspection of unmodified
+ # 'content' signatures.
+ # - bypass: stop processing this flow as much as possible. No further
+ # TLS parsing and inspection. Offload flow bypass to kernel
+ # or hardware if possible.
+ # - full: keep tracking and inspection as normal. Unmodified content
+ # keyword signatures are inspected as well.
+ #
+ # For best performance, select 'bypass'.
+ #
+ #encryption-handling: default
+
+ pgsql:
+ enabled: yes
+ # Stream reassembly size for PostgreSQL. By default, track it completely.
+ stream-depth: 0
+ # Maximum number of live PostgreSQL transactions per flow
+ # max-tx: 1024
+ dcerpc:
+ enabled: yes
+ # Maximum number of live DCERPC transactions per flow
+ # max-tx: 1024
+ ftp:
+ enabled: yes
+ # memcap: 64mb
+ rdp:
+ #enabled: yes
+ ssh:
+ enabled: yes
+ #hassh: yes
+ http2:
+ enabled: yes
+ # Maximum number of live HTTP2 streams in a flow
+ #max-streams: 4096
+ # Maximum headers table size
+ #max-table-size: 65536
+ smtp:
+ enabled: yes
+ raw-extraction: no
+ # Configure SMTP-MIME Decoder
+ mime:
+ # Decode MIME messages from SMTP transactions
+ # (may be resource intensive)
+ # This field supersedes all others because it turns the entire
+ # process on or off
+ decode-mime: yes
+
+ # Decode MIME entity bodies (ie. Base64, quoted-printable, etc.)
+ decode-base64: yes
+ decode-quoted-printable: yes
+
+ # Maximum bytes per header data value stored in the data structure
+ # (default is 2000)
+ header-value-depth: 2000
+
+ # Extract URLs and save in state data structure
+ extract-urls: yes
+ # Scheme of URLs to extract
+ # (default is [http])
+ #extract-urls-schemes: [http, https, ftp, mailto]
+ # Log the scheme of URLs that are extracted
+ # (default is no)
+ #log-url-scheme: yes
+ # Set to yes to compute the md5 of the mail body. You will then
+ # be able to journalize it.
+ body-md5: no
+ # Configure inspected-tracker for file_data keyword
+ inspected-tracker:
+ content-limit: 100000
+ content-inspect-min-size: 32768
+ content-inspect-window: 4096
+ imap:
+ enabled: detection-only
+ smb:
+ enabled: yes
+ detection-ports:
+ dp: 139, 445
+ # Maximum number of live SMB transactions per flow
+ # max-tx: 1024
+
+ # Stream reassembly size for SMB streams. By default track it completely.
+ #stream-depth: 0
+
+ nfs:
+ enabled: yes
+ # max-tx: 1024
+ tftp:
+ enabled: yes
+ dns:
+ tcp:
+ enabled: yes
+ detection-ports:
+ dp: 53
+ udp:
+ enabled: yes
+ detection-ports:
+ dp: 53
+ http:
+ enabled: yes
+
+ # Byte Range Containers default settings
+ # byterange:
+ # memcap: 100mb
+ # timeout: 60
+
+ # memcap: Maximum memory capacity for HTTP
+ # Default is unlimited, values can be 64mb, e.g.
+
+ # default-config: Used when no server-config matches
+ # personality: List of personalities used by default
+ # request-body-limit: Limit reassembly of request body for inspection
+ # by http_client_body & pcre /P option.
+ # response-body-limit: Limit reassembly of response body for inspection
+ # by file_data, http_server_body & pcre /Q option.
+ #
+ # For advanced options, see the user guide
+
+
+ # server-config: List of server configurations to use if address matches
+ # address: List of IP addresses or networks for this block
+ # personality: List of personalities used by this block
+ #
+ # Then, all the fields from default-config can be overloaded
+ #
+ # Currently Available Personalities:
+ # Minimal, Generic, IDS (default), IIS_4_0, IIS_5_0, IIS_5_1, IIS_6_0,
+ # IIS_7_0, IIS_7_5, Apache_2
+ libhtp:
+ default-config:
+ personality: IDS
+
+ # Can be specified in kb, mb, gb. Just a number indicates
+ # it's in bytes.
+ request-body-limit: 100kb
+ response-body-limit: 100kb
+
+ # inspection limits
+ request-body-minimal-inspect-size: 32kb
+ request-body-inspect-window: 4kb
+ response-body-minimal-inspect-size: 40kb
+ response-body-inspect-window: 16kb
+
+ # response body decompression (0 disables)
+ response-body-decompress-layer-limit: 2
+
+ # auto will use http-body-inline mode in IPS mode, yes or no set it statically
+ http-body-inline: auto
+
+ # Decompress SWF files. Disabled by default.
+ # Two types: 'deflate', 'lzma', 'both' will decompress deflate and lzma
+ # compress-depth:
+ # Specifies the maximum amount of data to decompress,
+ # set 0 for unlimited.
+ # decompress-depth:
+ # Specifies the maximum amount of decompressed data to obtain,
+ # set 0 for unlimited.
+ swf-decompression:
+ enabled: no
+ type: both
+ compress-depth: 100kb
+ decompress-depth: 100kb
+
+ # Use a random value for inspection sizes around the specified value.
+ # This lowers the risk of some evasion techniques but could lead
+ # to detection change between runs. It is set to 'yes' by default.
+ #randomize-inspection-sizes: yes
+ # If "randomize-inspection-sizes" is active, the value of various
+ # inspection size will be chosen from the [1 - range%, 1 + range%]
+ # range
+ # Default value of "randomize-inspection-range" is 10.
+ #randomize-inspection-range: 10
+
+ # decoding
+ double-decode-path: no
+ double-decode-query: no
+
+ # Can enable LZMA decompression
+ #lzma-enabled: false
+ # Memory limit usage for LZMA decompression dictionary
+ # Data is decompressed until dictionary reaches this size
+ #lzma-memlimit: 1mb
+ # Maximum decompressed size with a compression ratio
+ # above 2048 (only LZMA can reach this ratio, deflate cannot)
+ #compression-bomb-limit: 1mb
+ # Maximum time spent decompressing a single transaction in usec
+ #decompression-time-limit: 100000
+
+ server-config:
+
+ #- apache:
+ # address: [192.168.1.0/24, 127.0.0.0/8, "::1"]
+ # personality: Apache_2
+ # # Can be specified in kb, mb, gb. Just a number indicates
+ # # it's in bytes.
+ # request-body-limit: 4096
+ # response-body-limit: 4096
+ # double-decode-path: no
+ # double-decode-query: no
+
+ #- iis7:
+ # address:
+ # - 192.168.0.0/24
+ # - 192.168.10.0/24
+ # personality: IIS_7_0
+ # # Can be specified in kb, mb, gb. Just a number indicates
+ # # it's in bytes.
+ # request-body-limit: 4096
+ # response-body-limit: 4096
+ # double-decode-path: no
+ # double-decode-query: no
+
+ # Note: Modbus probe parser is minimalist due to the limited usage in the field.
+ # Only Modbus message length (greater than Modbus header length)
+ # and protocol ID (equal to 0) are checked in probing parser
+ # It is important to enable detection port and define Modbus port
+ # to avoid false positives
+ modbus:
+ # How many unanswered Modbus requests are considered a flood.
+ # If the limit is reached, the app-layer-event:modbus.flooded; will match.
+ #request-flood: 500
+
+ enabled: yes
+ detection-ports:
+ dp: 502
+ # According to MODBUS Messaging on TCP/IP Implementation Guide V1.0b, it
+ # is recommended to keep the TCP connection opened with a remote device
+ # and not to open and close it for each MODBUS/TCP transaction. In that
+ # case, it is important to set the depth of the stream reassembling as
+ # unlimited (stream.reassembly.depth: 0)
+
+ # Stream reassembly size for modbus. By default track it completely.
+ stream-depth: 0
+
+ # DNP3
+ dnp3:
+ enabled: yes
+ detection-ports:
+ dp: 20000
+
+ # SCADA EtherNet/IP and CIP protocol support
+ enip:
+ enabled: yes
+ detection-ports:
+ dp: 44818
+ sp: 44818
+
+ ntp:
+ enabled: yes
+
+ quic:
+ enabled: yes
+
+ dhcp:
+ enabled: yes
+
+ sip:
+ enabled: yes
+
+# Limit for the maximum number of asn1 frames to decode (default 256)
+asn1-max-frames: 256
+
+# Datasets default settings
+datasets:
+ # Default fallback memcap and hashsize values for datasets in case these
+ # were not explicitly defined.
+ defaults:
+ #memcap: 100mb
+ #hashsize: 2048
+
+ rules:
+ # Set to true to allow absolute filenames and filenames that use
+ # ".." components to reference parent directories in rules that specify
+ # their filenames.
+ #allow-absolute-filenames: false
+
+ # Allow datasets in rules write access for "save" and
+ # "state". This is enabled by default, however write access is
+ # limited to the data directory.
+ #allow-write: true
+
+##############################################################################
+##
+## Advanced settings below
+##
+##############################################################################
+
+##
+## Run Options
+##
+
+# Run Suricata with a specific user-id and group-id:
+#run-as:
+# user: suri
+# group: suri
+
+security:
+ # if true, prevents process creation from Suricata by calling
+ # setrlimit(RLIMIT_NPROC, 0)
+ limit-noproc: true
+ # Use landlock security module under Linux
+ landlock:
+ enabled: no
+ directories:
+ #write:
+ # - /var/run/
+ # /usr and /etc folders are added to read list to allow
+ # file magic to be used.
+ read:
+ - /usr/
+ - /etc/
+ - /nix/store/9zi80g57g091a5qky6x3cmvmmb9zcfvq-suricata-7.0.0/etc/suricata/
+
+ lua:
+ # Allow Lua rules. Disabled by default.
+ #allow-rules: false
+
+# Some logging modules will use that name in event as identifier. The default
+# value is the hostname
+#sensor-name: suricata
+
+# Default location of the pid file. The pid file is only used in
+# daemon mode (start Suricata with -D). If not running in daemon mode
+# the --pidfile command line option must be used to create a pid file.
+#pid-file: /var/run/suricata.pid
+
+# Daemon working directory
+# Suricata will change directory to this one if provided
+# Default: "/"
+#daemon-directory: "/"
+
+# Umask.
+# Suricata will use this umask if it is provided. By default it will use the
+# umask passed on by the shell.
+#umask: 022
+
+# Suricata core dump configuration. Limits the size of the core dump file to
+# approximately max-dump. The actual core dump size will be a multiple of the
+# page size. Core dumps that would be larger than max-dump are truncated. On
+# Linux, the actual core dump size may be a few pages larger than max-dump.
+# Setting max-dump to 0 disables core dumping.
+# Setting max-dump to 'unlimited' will give the full core dump file.
+# On 32-bit Linux, a max-dump value >= ULONG_MAX may cause the core dump size
+# to be 'unlimited'.
+
+coredump:
+ max-dump: unlimited
+
+# If the Suricata box is a router for the sniffed networks, set it to 'router'. If
+# it is a pure sniffing setup, set it to 'sniffer-only'.
+# If set to auto, the variable is internally switched to 'router' in IPS mode
+# and 'sniffer-only' in IDS mode.
+# This feature is currently only used by the reject* keywords.
+host-mode: auto
+
+# Number of packets preallocated per thread. The default is 1024. A higher number
+# will make sure each CPU will be more easily kept busy, but may negatively
+# impact caching.
+#max-pending-packets: 1024
+
+# Runmode the engine should use. Please check --list-runmodes to get the available
+# runmodes for each packet acquisition method. Default depends on selected capture
+# method. 'workers' generally gives best performance.
+#runmode: autofp
+
+# Specifies the kind of flow load balancer used by the flow pinned autofp mode.
+#
+# Supported schedulers are:
+#
+# hash - Flow assigned to threads using the 5-7 tuple hash.
+# ippair - Flow assigned to threads using addresses only.
+# ftp-hash - Flow assigned to threads using the hash, except for FTP, so that
+# ftp-data flows will be handled by the same thread
+#
+#autofp-scheduler: hash
+
+# Preallocated size for each packet. Default is 1514 which is the classical
+# size for pcap on Ethernet. You should adjust this value to the highest
+# packet size (MTU + hardware header) on your system.
+#default-packet-size: 1514
+
+# Unix command socket that can be used to pass commands to Suricata.
+# An external tool can then connect to get information from Suricata
+# or trigger some modifications of the engine. Set enabled to yes
+# to activate the feature. In auto mode, the feature will only be
+# activated in live capture mode. You can use the filename variable to set
+# the file name of the socket.
+unix-command:
+ enabled: auto
+ #filename: custom.socket
+
+# Magic file. The extension .mgc is added to the value here.
+#magic-file: /usr/share/file/magic
+#magic-file:
+
+# GeoIP2 database file. Specify path and filename of GeoIP2 database
+# if using rules with "geoip" rule option.
+#geoip-database: /usr/local/share/GeoLite2/GeoLite2-Country.mmdb
+
+legacy:
+ uricontent: enabled
+
+##
+## Detection settings
+##
+
+# Set the order of alerts based on actions
+# The default order is pass, drop, reject, alert
+# action-order:
+# - pass
+# - drop
+# - reject
+# - alert
+
+# Define maximum number of possible alerts that can be triggered for the same
+# packet. Default is 15
+#packet-alert-max: 15
+
+# Exception Policies
+#
+# Define a common behavior for all exception policies.
+# In IPS mode, the default is drop-flow. For cases when that's not possible, the
+# engine will fall to drop-packet. To fallback to old behavior (setting each of
+# them individually, or ignoring all), set this to ignore.
+# All values available for exception policies can be used, and there is one
+# extra option: auto - which means drop-flow or drop-packet (as explained above)
+# in IPS mode, and ignore in IDS mode. Exception policy values are: drop-packet,
+# drop-flow, reject, bypass, pass-packet, pass-flow, ignore (disable).
+exception-policy: auto
+
+# IP Reputation
+#reputation-categories-file: /etc/suricata/iprep/categories.txt
+#default-reputation-path: /etc/suricata/iprep
+#reputation-files:
+# - reputation.list
+
+# When run with the option --engine-analysis, the engine will read each of
+# the parameters below, and print reports for each of the enabled sections
+# and exit. The reports are printed to a file in the default log dir
+# given by the parameter "default-log-dir", with engine reporting
+# subsection below printing reports in its own report file.
+engine-analysis:
+ # enables printing reports for fast-pattern for every rule.
+ rules-fast-pattern: yes
+ # enables printing reports for each rule
+ rules: yes
+
+#recursion and match limits for PCRE where supported
+pcre:
+ match-limit: 3500
+ match-limit-recursion: 1500
+
+##
+## Advanced Traffic Tracking and Reconstruction Settings
+##
+
+# Host specific policies for defragmentation and TCP stream
+# reassembly. The host OS lookup is done using a radix tree, just
+# like a routing table so the most specific entry matches.
+host-os-policy:
+ # Make the default policy windows.
+ windows: [0.0.0.0/0]
+ bsd: []
+ bsd-right: []
+ old-linux: []
+ linux: []
+ old-solaris: []
+ solaris: []
+ hpux10: []
+ hpux11: []
+ irix: []
+ macos: []
+ vista: []
+ windows2k3: []
+
+# Defrag settings:
+
+# The memcap-policy value can be "drop-packet", "pass-packet", "reject" or
+# "ignore" (which is the default).
+defrag:
+ memcap: 32mb
+ # memcap-policy: ignore
+ hash-size: 65536
+ trackers: 65535 # number of defragmented flows to follow
+ max-frags: 65535 # number of fragments to keep (higher than trackers)
+ prealloc: yes
+ timeout: 60
+
+# Enable defrag per host settings
+# host-config:
+#
+# - dmz:
+# timeout: 30
+# address: [192.168.1.0/24, 127.0.0.0/8, 1.1.1.0/24, 2.2.2.0/24, "1.1.1.1", "2.2.2.2", "::1"]
+#
+# - lan:
+# timeout: 45
+# address:
+# - 192.168.0.0/24
+# - 192.168.10.0/24
+# - 172.16.14.0/24
+
+# Flow settings:
+# By default, the reserved memory (memcap) for flows is 32MB. This is the limit
+# for flow allocation inside the engine. You can change this value to allow
+# more memory usage for flows.
+# The hash-size determines the size of the hash used to identify flows inside
+# the engine, and by default the value is 65536.
+# At startup, the engine can preallocate a number of flows, to get better
+# performance. The number of flows preallocated is 10000 by default.
+# emergency-recovery is the percentage of flows that the engine needs to
+# prune before clearing the emergency state. The emergency state is activated
+# when the memcap limit is reached, allowing new flows to be created, but
+# pruning them with the emergency timeouts (they are defined below).
+# If the memcap is reached, the engine will try to prune flows
+# with the default timeouts. If it doesn't find a flow to prune, it will set
+# the emergency bit and it will try again with more aggressive timeouts.
+# If that doesn't work, then it will try to kill the oldest flows using
+# last time seen flows.
+# The memcap can be specified in kb, mb, gb. Just a number indicates it's
+# in bytes.
+# The memcap-policy can be "drop-packet", "pass-packet", "reject" or "ignore"
+# (which is the default).
+
+flow:
+ memcap: 128mb
+ #memcap-policy: ignore
+ hash-size: 65536
+ prealloc: 10000
+ emergency-recovery: 30
+ #managers: 1 # default to one flow manager
+ #recyclers: 1 # default to one flow recycler thread
+
+# This option controls the use of VLAN ids in the flow (and defrag)
+# hashing. Normally this should be enabled, but in some (broken)
+# setups where both sides of a flow are not tagged with the same VLAN
+# tag, we can ignore the VLAN id's in the flow hashing.
+vlan:
+ use-for-tracking: true
+
+# This option controls the use of livedev ids in the flow (and defrag)
+# hashing. This is enabled by default and should be disabled if
+# multiple live devices are used to capture traffic from the same network
+livedev:
+ use-for-tracking: true
+
+# Specific timeouts for flows. Here you can specify the timeouts that the
+# active flows will wait to transit from the current state to another, on each
+# protocol. The value of "new" determines the seconds to wait after a handshake or
+# stream startup before the engine frees the data of that flow it doesn't
+# change the state to established (usually if we don't receive more packets
+# of that flow). The value of "established" is the amount of
+# seconds that the engine will wait to free the flow if that time elapses
+# without receiving new packets or closing the connection. "closed" is the
+# amount of time to wait after a flow is closed (usually zero). "bypassed"
+# timeout controls locally bypassed flows. For these flows we don't do any other
+# tracking. If no packets have been seen after this timeout, the flow is discarded.
+#
+# There's an emergency mode that will become active under attack circumstances,
+# making the engine to check flow status faster. This configuration variables
+# use the prefix "emergency-" and work similar as the normal ones.
+# Some timeouts doesn't apply to all the protocols, like "closed", for udp and
+# icmp.
+
+flow-timeouts:
+
+ default:
+ new: 30
+ established: 300
+ closed: 0
+ bypassed: 100
+ emergency-new: 10
+ emergency-established: 100
+ emergency-closed: 0
+ emergency-bypassed: 50
+ tcp:
+ new: 60
+ established: 600
+ closed: 60
+ bypassed: 100
+ emergency-new: 5
+ emergency-established: 100
+ emergency-closed: 10
+ emergency-bypassed: 50
+ udp:
+ new: 30
+ established: 300
+ bypassed: 100
+ emergency-new: 10
+ emergency-established: 100
+ emergency-bypassed: 50
+ icmp:
+ new: 30
+ established: 300
+ bypassed: 100
+ emergency-new: 10
+ emergency-established: 100
+ emergency-bypassed: 50
+
+# Stream engine settings. Here the TCP stream tracking and reassembly
+# engine is configured.
+#
+# stream:
+# memcap: 64mb # Can be specified in kb, mb, gb. Just a
+# # number indicates it's in bytes.
+# memcap-policy: ignore # Can be "drop-flow", "pass-flow", "bypass",
+# # "drop-packet", "pass-packet", "reject" or
+# # "ignore" default is "ignore"
+# checksum-validation: yes # To validate the checksum of received
+# # packet. If csum validation is specified as
+# # "yes", then packets with invalid csum values will not
+# # be processed by the engine stream/app layer.
+# # Warning: locally generated traffic can be
+# # generated without checksum due to hardware offload
+# # of checksum. You can control the handling of checksum
+# # on a per-interface basis via the 'checksum-checks'
+# # option
+# prealloc-sessions: 2048 # 2k sessions prealloc'd per stream thread
+# midstream: false # don't allow midstream session pickups
+# midstream-policy: ignore # Can be "drop-flow", "pass-flow", "bypass",
+# # "drop-packet", "pass-packet", "reject" or
+# # "ignore" default is "ignore"
+# async-oneside: false # don't enable async stream handling
+# inline: no # stream inline mode
+# drop-invalid: yes # in inline mode, drop packets that are invalid with regards to streaming engine
+# max-syn-queued: 10 # Max different SYNs to queue
+# max-synack-queued: 5 # Max different SYN/ACKs to queue
+# bypass: no # Bypass packets when stream.reassembly.depth is reached.
+# # Warning: first side to reach this triggers
+# # the bypass.
+# liberal-timestamps: false # Treat all timestamps as if the Linux policy applies. This
+# # means it's slightly more permissive. Enabled by default.
+#
+# reassembly:
+# memcap: 256mb # Can be specified in kb, mb, gb. Just a number
+# # indicates it's in bytes.
+# memcap-policy: ignore # Can be "drop-flow", "pass-flow", "bypass",
+# # "drop-packet", "pass-packet", "reject" or
+# # "ignore" default is "ignore"
+# depth: 1mb # Can be specified in kb, mb, gb. Just a number
+# # indicates it's in bytes.
+# toserver-chunk-size: 2560 # inspect raw stream in chunks of at least
+# # this size. Can be specified in kb, mb,
+# # gb. Just a number indicates it's in bytes.
+# toclient-chunk-size: 2560 # inspect raw stream in chunks of at least
+# # this size. Can be specified in kb, mb,
+# # gb. Just a number indicates it's in bytes.
+# randomize-chunk-size: yes # Take a random value for chunk size around the specified value.
+# # This lowers the risk of some evasion techniques but could lead
+# # to detection change between runs. It is set to 'yes' by default.
+# randomize-chunk-range: 10 # If randomize-chunk-size is active, the value of chunk-size is
+# # a random value between (1 - randomize-chunk-range/100)*toserver-chunk-size
+# # and (1 + randomize-chunk-range/100)*toserver-chunk-size and the same
+# # calculation for toclient-chunk-size.
+# # Default value of randomize-chunk-range is 10.
+#
+# raw: yes # 'Raw' reassembly enabled or disabled.
+# # raw is for content inspection by detection
+# # engine.
+#
+# segment-prealloc: 2048 # number of segments preallocated per thread
+#
+# check-overlap-different-data: true|false
+# # check if a segment contains different data
+# # than what we've already seen for that
+# # position in the stream.
+# # This is enabled automatically if inline mode
+# # is used or when stream-event:reassembly_overlap_different_data;
+# # is used in a rule.
+#
+stream:
+ memcap: 64mb
+ #memcap-policy: ignore
+ checksum-validation: yes # reject incorrect csums
+ #midstream: false
+ #midstream-policy: ignore
+ inline: auto # auto will use inline mode in IPS mode, yes or no set it statically
+ reassembly:
+ memcap: 256mb
+ #memcap-policy: ignore
+ depth: 50mb # reassemble 1mb into a stream
+ toserver-chunk-size: 2560
+ toclient-chunk-size: 2560
+ randomize-chunk-size: yes
+ #randomize-chunk-range: 10
+ #raw: yes
+ #segment-prealloc: 2048
+ #check-overlap-different-data: true
+
+# Host table:
+#
+# Host table is used by the tagging and per host thresholding subsystems.
+#
+host:
+ hash-size: 4096
+ prealloc: 1000
+ memcap: 32mb
+
+# IP Pair table:
+#
+# Used by xbits 'ippair' tracking.
+#
+#ippair:
+# hash-size: 4096
+# prealloc: 1000
+# memcap: 32mb
+
+# Decoder settings
+
+decoder:
+ # Teredo decoder is known to not be completely accurate
+ # as it will sometimes detect non-teredo as teredo.
+ teredo:
+ enabled: true
+ # ports to look for Teredo. Max 4 ports. If no ports are given, or
+ # the value is set to 'any', Teredo detection runs on _all_ UDP packets.
+ ports: $TEREDO_PORTS # syntax: '[3544, 1234]' or '3533' or 'any'.
+
+ # VXLAN decoder is assigned to up to 4 UDP ports. By default only the
+ # IANA assigned port 4789 is enabled.
+ vxlan:
+ enabled: true
+ ports: $VXLAN_PORTS # syntax: '[8472, 4789]' or '4789'.
+
+ # Geneve decoder is assigned to up to 4 UDP ports. By default only the
+ # IANA assigned port 6081 is enabled.
+ geneve:
+ enabled: true
+ ports: $GENEVE_PORTS # syntax: '[6081, 1234]' or '6081'.
+
+ # maximum number of decoder layers for a packet
+ # max-layers: 16
+
+##
+## Performance tuning and profiling
+##
+
+# The detection engine builds internal groups of signatures. The engine
+# allows us to specify the profile to use for them, to manage memory in an
+# efficient way keeping good performance. For the profile keyword you
+# can use the words "low", "medium", "high" or "custom". If you use custom,
+# make sure to define the values in the "custom-values" section.
+# Usually you would prefer medium/high/low.
+#
+# "sgh mpm-context", indicates how the staging should allot mpm contexts for
+# the signature groups. "single" indicates the use of a single context for
+# all the signature group heads. "full" indicates a mpm-context for each
+# group head. "auto" lets the engine decide the distribution of contexts
+# based on the information the engine gathers on the patterns from each
+# group head.
+#
+# The option inspection-recursion-limit is used to limit the recursive calls
+# in the content inspection code. For certain payload-sig combinations, we
+# might end up taking too much time in the content inspection code.
+# If the argument specified is 0, the engine uses an internally defined
+# default limit. When a value is not specified, there are no limits on the recursion.
+detect:
+ profile: medium
+ custom-values:
+ toclient-groups: 3
+ toserver-groups: 25
+ sgh-mpm-context: auto
+ inspection-recursion-limit: 3000
+ # If set to yes, the loading of signatures will be made after the capture
+ # is started. This will limit the downtime in IPS mode.
+ #delayed-detect: yes
+
+ prefilter:
+ # default prefiltering setting. "mpm" only creates MPM/fast_pattern
+ # engines. "auto" also sets up prefilter engines for other keywords.
+ # Use --list-keywords=all to see which keywords support prefiltering.
+ default: mpm
+
+ # the grouping values above control how many groups are created per
+ # direction. Port whitelisting forces that port to get its own group.
+ # Very common ports will benefit, as well as ports with many expensive
+ # rules.
+ grouping:
+ #tcp-whitelist: 53, 80, 139, 443, 445, 1433, 3306, 3389, 6666, 6667, 8080
+ #udp-whitelist: 53, 135, 5060
+
+ profiling:
+ # Log the rules that made it past the prefilter stage, per packet
+ # default is off. The threshold setting determines how many rules
+ # must have made it past pre-filter for that rule to trigger the
+ # logging.
+ #inspect-logging-threshold: 200
+ grouping:
+ dump-to-disk: false
+ include-rules: false # very verbose
+ include-mpm-stats: false
+
+# Select the multi pattern algorithm you want to run for scan/search the
+# in the engine.
+#
+# The supported algorithms are:
+# "ac" - Aho-Corasick, default implementation
+# "ac-bs" - Aho-Corasick, reduced memory implementation
+# "ac-ks" - Aho-Corasick, "Ken Steele" variant
+# "hs" - Hyperscan, available when built with Hyperscan support
+#
+# The default mpm-algo value of "auto" will use "hs" if Hyperscan is
+# available, "ac" otherwise.
+#
+# The mpm you choose also decides the distribution of mpm contexts for
+# signature groups, specified by the conf - "detect.sgh-mpm-context".
+# Selecting "ac" as the mpm would require "detect.sgh-mpm-context"
+# to be set to "single", because of ac's memory requirements, unless the
+# ruleset is small enough to fit in memory, in which case one can
+# use "full" with "ac". The rest of the mpms can be run in "full" mode.
+
+mpm-algo: auto
+
+# Select the matching algorithm you want to use for single-pattern searches.
+#
+# Supported algorithms are "bm" (Boyer-Moore) and "hs" (Hyperscan, only
+# available if Suricata has been built with Hyperscan support).
+#
+# The default of "auto" will use "hs" if available, otherwise "bm".
+
+spm-algo: auto
+
+# Suricata is multi-threaded. Here the threading can be influenced.
+threading:
+ set-cpu-affinity: no
+ # Tune cpu affinity of threads. Each family of threads can be bound
+ # to specific CPUs.
+ #
+ # These 2 apply to the all runmodes:
+ # management-cpu-set is used for flow timeout handling, counters
+ # worker-cpu-set is used for 'worker' threads
+ #
+ # Additionally, for autofp these apply:
+ # receive-cpu-set is used for capture threads
+ # verdict-cpu-set is used for IPS verdict threads
+ #
+ cpu-affinity:
+ - management-cpu-set:
+ cpu: [ 0 ] # include only these CPUs in affinity settings
+ - receive-cpu-set:
+ cpu: [ 0 ] # include only these CPUs in affinity settings
+ - worker-cpu-set:
+ cpu: [ "all" ]
+ mode: "exclusive"
+ # Use explicitly 3 threads and don't compute number by using
+ # detect-thread-ratio variable:
+ # threads: 3
+ prio:
+ low: [ 0 ]
+ medium: [ "1-2" ]
+ high: [ 3 ]
+ default: "medium"
+ #- verdict-cpu-set:
+ # cpu: [ 0 ]
+ # prio:
+ # default: "high"
+ #
+ # By default Suricata creates one "detect" thread per available CPU/CPU core.
+ # This setting allows controlling this behaviour. A ratio setting of 2 will
+ # create 2 detect threads for each CPU/CPU core. So for a dual core CPU this
+ # will result in 4 detect threads. If values below 1 are used, less threads
+ # are created. So on a dual core CPU a setting of 0.5 results in 1 detect
+ # thread being created. Regardless of the setting at a minimum 1 detect
+ # thread will always be created.
+ #
+ detect-thread-ratio: 1.0
+ #
+ # By default, the per-thread stack size is left to its default setting. If
+ # the default thread stack size is too small, use the following configuration
+ # setting to change the size. Note that if any thread's stack size cannot be
+ # set to this value, a fatal error occurs.
+ #
+ # Generally, the per-thread stack-size should not exceed 8MB.
+ #stack-size: 8mb
+
+# Luajit has a strange memory requirement, its 'states' need to be in the
+# first 2G of the process' memory.
+#
+# 'luajit.states' is used to control how many states are preallocated.
+# State use: per detect script: 1 per detect thread. Per output script: 1 per
+# script.
+luajit:
+ states: 128
+
+# Profiling settings. Only effective if Suricata has been built with
+# the --enable-profiling configure flag.
+#
+profiling:
+ # Run profiling for every X-th packet. The default is 1, which means we
+ # profile every packet. If set to 1024, one packet is profiled for every
+ # 1024 received. The sample rate must be a power of 2.
+ #sample-rate: 1024
+
+ # rule profiling
+ rules:
+
+ # Profiling can be disabled here, but it will still have a
+ # performance impact if compiled in.
+ enabled: yes
+ filename: rule_perf.log
+ append: yes
+
+ # Sort options: ticks, avgticks, checks, matches, maxticks
+ # If commented out all the sort options will be used.
+ #sort: avgticks
+
+ # Limit the number of sids for which stats are shown at exit (per sort).
+ limit: 10
+
+ # output to json
+ json: yes
+
+ # per keyword profiling
+ keywords:
+ enabled: yes
+ filename: keyword_perf.log
+ append: yes
+
+ prefilter:
+ enabled: yes
+ filename: prefilter_perf.log
+ append: yes
+
+ # per rulegroup profiling
+ rulegroups:
+ enabled: yes
+ filename: rule_group_perf.log
+ append: yes
+
+ # packet profiling
+ packets:
+
+ # Profiling can be disabled here, but it will still have a
+ # performance impact if compiled in.
+ enabled: yes
+ filename: packet_stats.log
+ append: yes
+
+ # per packet csv output
+ csv:
+
+ # Output can be disabled here, but it will still have a
+ # performance impact if compiled in.
+ enabled: no
+ filename: packet_stats.csv
+
+ # profiling of locking. Only available when Suricata was built with
+ # --enable-profiling-locks.
+ locks:
+ enabled: no
+ filename: lock_stats.log
+ append: yes
+
+ pcap-log:
+ enabled: no
+ filename: pcaplog_stats.log
+ append: yes
+
+##
+## Netfilter integration
+##
+
+# When running in NFQ inline mode, it is possible to use a simulated
+# non-terminal NFQUEUE verdict.
+# This permits sending all needed packet to Suricata via this rule:
+# iptables -I FORWARD -m mark ! --mark $MARK/$MASK -j NFQUEUE
+# And below, you can have your standard filtering ruleset. To activate
+# this mode, you need to set mode to 'repeat'
+# If you want a packet to be sent to another queue after an ACCEPT decision
+# set the mode to 'route' and set next-queue value.
+# On Linux >= 3.1, you can set batchcount to a value > 1 to improve performance
+# by processing several packets before sending a verdict (worker runmode only).
+# On Linux >= 3.6, you can set the fail-open option to yes to have the kernel
+# accept the packet if Suricata is not able to keep pace.
+# bypass mark and mask can be used to implement NFQ bypass. If bypass mark is
+# set then the NFQ bypass is activated. Suricata will set the bypass mark/mask
+# on packet of a flow that need to be bypassed. The Netfilter ruleset has to
+# directly accept all packets of a flow once a packet has been marked.
+nfq:
+# mode: accept
+# repeat-mark: 1
+# repeat-mask: 1
+# bypass-mark: 1
+# bypass-mask: 1
+# route-queue: 2
+# batchcount: 20
+# fail-open: yes
+
+#nflog support
+nflog:
+ # netlink multicast group
+ # (the same as the iptables --nflog-group param)
+ # Group 0 is used by the kernel, so you can't use it
+ - group: 2
+ # netlink buffer size
+ buffer-size: 18432
+ # put default value here
+ - group: default
+ # set number of packets to queue inside kernel
+ qthreshold: 1
+ # set the delay before flushing packet in the kernel's queue
+ qtimeout: 100
+ # netlink max buffer size
+ max-size: 20000
+
+##
+## Advanced Capture Options
+##
+
+# General settings affecting packet capture
+capture:
+ # disable NIC offloading. It's restored when Suricata exits.
+ # Enabled by default.
+ #disable-offloading: false
+ #
+ # disable checksum validation. Same as setting '-k none' on the
+ # command-line.
+ #checksum-validation: none
+
+# Netmap support
+#
+# Netmap operates with NIC directly in driver, so you need FreeBSD 11+ which has
+# built-in Netmap support or compile and install the Netmap module and appropriate
+# NIC driver for your Linux system.
+# To reach maximum throughput disable all receive-, segmentation-,
+# checksum- offloading on your NIC (using ethtool or similar).
+# Disabling TX checksum offloading is *required* for connecting OS endpoint
+# with NIC endpoint.
+# You can find more information at https://github.com/luigirizzo/netmap
+#
+netmap:
+ # To specify OS endpoint add plus sign at the end (e.g. "eth0+")
+ - interface: eth2
+ # Number of capture threads. "auto" uses number of RSS queues on interface.
+ # Warning: unless the RSS hashing is symmetrical, this will lead to
+ # accuracy issues.
+ #threads: auto
+ # You can use the following variables to activate netmap tap or IPS mode.
+ # If copy-mode is set to ips or tap, the traffic coming to the current
+ # interface will be copied to the copy-iface interface. If 'tap' is set, the
+ # copy is complete. If 'ips' is set, the packet matching a 'drop' action
+ # will not be copied.
+ # To specify the OS as the copy-iface (so the OS can route packets, or forward
+ # to a service running on the same machine) add a plus sign at the end
+ # (e.g. "copy-iface: eth0+"). Don't forget to set up a symmetrical eth0+ -> eth0
+ # for return packets. Hardware checksumming must be *off* on the interface if
+ # using an OS endpoint (e.g. 'ifconfig eth0 -rxcsum -txcsum -rxcsum6 -txcsum6' for FreeBSD
+ # or 'ethtool -K eth0 tx off rx off' for Linux).
+ #copy-mode: tap
+ #copy-iface: eth3
+ # Set to yes to disable promiscuous mode
+ # disable-promisc: no
+ # Choose checksum verification mode for the interface. At the moment
+ # of the capture, some packets may have an invalid checksum due to
+ # the checksum computation being offloaded to the network card.
+ # Possible values are:
+ # - yes: checksum validation is forced
+ # - no: checksum validation is disabled
+ # - auto: Suricata uses a statistical approach to detect when
+ # checksum off-loading is used.
+ # Warning: 'checksum-validation' must be set to yes to have any validation
+ #checksum-checks: auto
+ # BPF filter to apply to this interface. The pcap filter syntax apply here.
+ #bpf-filter: port 80 or udp
+ #- interface: eth3
+ #threads: auto
+ #copy-mode: tap
+ #copy-iface: eth2
+ # Put default values here
+ - interface: default
+
+# PF_RING configuration: for use with native PF_RING support
+# for more info see http://www.ntop.org/products/pf_ring/
+pfring:
+ - interface: eth0
+ # Number of receive threads. If set to 'auto' Suricata will first try
+ # to use CPU (core) count and otherwise RSS queue count.
+ threads: auto
+
+ # Default clusterid. PF_RING will load balance packets based on flow.
+ # All threads/processes that will participate need to have the same
+ # clusterid.
+ cluster-id: 99
+
+ # Default PF_RING cluster type. PF_RING can load balance per flow.
+ # Possible values are:
+ # - cluster_flow: 6-tuple:
+ # - cluster_inner_flow: 6-tuple:
+ # - cluster_inner_flow_2_tuple: 2-tuple:
+ # - cluster_inner_flow_4_tuple: 4-tuple:
+ # - cluster_inner_flow_5_tuple: 5-tuple:
+ # - cluster_round_robin (NOT RECOMMENDED)
+ cluster-type: cluster_flow
+
+ # bpf filter for this interface
+ #bpf-filter: tcp
+
+ # If bypass is set then the PF_RING hw bypass is activated, when supported
+ # by the network interface. Suricata will instruct the interface to bypass
+ # all future packets for a flow that need to be bypassed.
+ #bypass: yes
+
+ # Choose checksum verification mode for the interface. At the moment
+ # of the capture, some packets may have an invalid checksum due to
+ # the checksum computation being offloaded to the network card.
+ # Possible values are:
+ # - rxonly: only compute checksum for packets received by network card.
+ # - yes: checksum validation is forced
+ # - no: checksum validation is disabled
+ # - auto: Suricata uses a statistical approach to detect when
+ # checksum off-loading is used. (default)
+ # Warning: 'checksum-validation' must be set to yes to have any validation
+ #checksum-checks: auto
+ # Second interface
+ #- interface: eth1
+ # threads: 3
+ # cluster-id: 93
+ # cluster-type: cluster_flow
+ # Put default values here
+ - interface: default
+ #threads: 2
+
+# For FreeBSD ipfw(8) divert(4) support.
+# Please make sure you have ipfw_load="YES" and ipdivert_load="YES"
+# in /etc/loader.conf or kldload'ing the appropriate kernel modules.
+# Additionally, you need to have an ipfw rule for the engine to see
+# the packets from ipfw. For Example:
+#
+# ipfw add 100 divert 8000 ip from any to any
+#
+# N.B. This example uses "8000" -- this number must mach the values
+# you passed on the command line, i.e., -d 8000
+#
+ipfw:
+
+ # Reinject packets at the specified ipfw rule number. This config
+ # option is the ipfw rule number AT WHICH rule processing continues
+ # in the ipfw processing system after the engine has finished
+ # inspecting the packet for acceptance. If no rule number is specified,
+ # accepted packets are reinjected at the divert rule which they entered
+ # and IPFW rule processing continues. No check is done to verify
+ # this will rule makes sense so care must be taken to avoid loops in ipfw.
+ #
+ ## The following example tells the engine to reinject packets
+ # back into the ipfw firewall AT rule number 5500:
+ #
+ # ipfw-reinjection-rule-number: 5500
+
+
+napatech:
+ # When use_all_streams is set to "yes" the initialization code will query
+ # the Napatech service for all configured streams and listen on all of them.
+ # When set to "no" the streams config array will be used.
+ #
+ # This option necessitates running the appropriate NTPL commands to create
+ # the desired streams prior to running Suricata.
+ #use-all-streams: no
+
+ # The streams to listen on when auto-config is disabled or when and threading
+ # cpu-affinity is disabled. This can be either:
+ # an individual stream (e.g. streams: [0])
+ # or
+ # a range of streams (e.g. streams: ["0-3"])
+ #
+ streams: ["0-3"]
+
+ # Stream stats can be enabled to provide fine grain packet and byte counters
+ # for each thread/stream that is configured.
+ #
+ enable-stream-stats: no
+
+ # When auto-config is enabled the streams will be created and assigned
+ # automatically to the NUMA node where the thread resides. If cpu-affinity
+ # is enabled in the threading section. Then the streams will be created
+ # according to the number of worker threads specified in the worker-cpu-set.
+ # Otherwise, the streams array is used to define the streams.
+ #
+ # This option is intended primarily to support legacy configurations.
+ #
+ # This option cannot be used simultaneously with either "use-all-streams"
+ # or "hardware-bypass".
+ #
+ auto-config: yes
+
+ # Enable hardware level flow bypass.
+ #
+ hardware-bypass: yes
+
+ # Enable inline operation. When enabled traffic arriving on a given port is
+ # automatically forwarded out its peer port after analysis by Suricata.
+ #
+ inline: no
+
+ # Ports indicates which Napatech ports are to be used in auto-config mode.
+ # these are the port IDs of the ports that will be merged prior to the
+ # traffic being distributed to the streams.
+ #
+ # When hardware-bypass is enabled the ports must be configured as a segment.
+ # specify the port(s) on which upstream and downstream traffic will arrive.
+ # This information is necessary for the hardware to properly process flows.
+ #
+ # When using a tap configuration one of the ports will receive inbound traffic
+ # for the network and the other will receive outbound traffic. The two ports on a
+ # given segment must reside on the same network adapter.
+ #
+ # When using a SPAN-port configuration the upstream and downstream traffic
+ # arrives on a single port. This is configured by setting the two sides of the
+ # segment to reference the same port. (e.g. 0-0 to configure a SPAN port on
+ # port 0).
+ #
+ # port segments are specified in the form:
+ # ports: [0-1,2-3,4-5,6-6,7-7]
+ #
+ # For legacy systems when hardware-bypass is disabled this can be specified in any
+ # of the following ways:
+ #
+ # a list of individual ports (e.g. ports: [0,1,2,3])
+ #
+ # a range of ports (e.g. ports: [0-3])
+ #
+ # "all" to indicate that all ports are to be merged together
+ # (e.g. ports: [all])
+ #
+ # This parameter has no effect if auto-config is disabled.
+ #
+ ports: [0-1,2-3]
+
+ # When auto-config is enabled the hashmode specifies the algorithm for
+ # determining to which stream a given packet is to be delivered.
+ # This can be any valid Napatech NTPL hashmode command.
+ #
+ # The most common hashmode commands are: hash2tuple, hash2tuplesorted,
+ # hash5tuple, hash5tuplesorted and roundrobin.
+ #
+ # See Napatech NTPL documentation other hashmodes and details on their use.
+ #
+ # This parameter has no effect if auto-config is disabled.
+ #
+ hashmode: hash5tuplesorted
+
+##
+## Configure Suricata to load Suricata-Update managed rules.
+##
+
+default-rule-path: /var/lib/suricata/rules
+
+rule-files:
+ - suricata.rules
+
+##
+## Auxiliary configuration files.
+##
+
+classification-file: /dev/null
+reference-config-file: /dev/null
+threshold-file: /dev/null
+
+##
+## Include other configs
+##
+
+# Includes: Files included here will be handled as if they were in-lined
+# in this configuration file. Files with relative pathnames will be
+# searched for in the same directory as this configuration file. You may
+# use absolute pathnames too.
+#include:
+# - include1.yaml
+# - include2.yaml
diff --git a/webapp/.dockerignore b/webapp/.dockerignore
new file mode 100644
index 0000000..aa06303
--- /dev/null
+++ b/webapp/.dockerignore
@@ -0,0 +1,5 @@
+__pycache__
+.env
+.ruff_cache
+database
+grafana
diff --git a/webapp/Dockerfile b/webapp/Dockerfile
new file mode 100644
index 0000000..73f8299
--- /dev/null
+++ b/webapp/Dockerfile
@@ -0,0 +1,9 @@
+# Copyright (C) 2023 ANSSI
+# SPDX-License-Identifier: GPL-3.0-only
+
+FROM alpine:20230329
+RUN apk add --no-cache py3-jinja2 py3-uvloop && \
+ apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/testing py3-aiosqlite py3-starlette uvicorn
+COPY . /webapp
+WORKDIR /webapp
+CMD ["uvicorn", "--host", "0.0.0.0", "main:app"]
diff --git a/webapp/database.py b/webapp/database.py
new file mode 100644
index 0000000..de0c77f
--- /dev/null
+++ b/webapp/database.py
@@ -0,0 +1,314 @@
+# Copyright (C) 2023 ANSSI
+# SPDX-License-Identifier: GPL-3.0-only
+
+import asyncio
+import ipaddress
+import json
+import re
+import traceback
+from functools import lru_cache
+
+import aiosqlite
+
+# List of possible application-layer protocols in Suricata
+# suricata -c suricata.yaml --list-app-layer-protos
+SUPPORTED_PROTOCOLS = [
+ "bittorrent-dht",
+ "dcerpc",
+ "dhcp",
+ "dnp3",
+ "dns",
+ "enip",
+ "ftp",
+ "ftp-data",
+ "http",
+ "http2",
+ "ike",
+ "ikev2",
+ "imap",
+ "krb5",
+ "modbus",
+ "mqtt",
+ "nfs",
+ "ntp",
+ "pgsql",
+ "quic",
+ "rdp",
+ "rfb",
+ "sip",
+ "smb",
+ "smtp",
+ "snmp",
+ "ssh",
+ "telnet",
+ "tftp",
+ "tls",
+]
+
+# Collect pcap filename for each flow
+# This is an ugly hack to circumvent an upstream issue in Suricata
+flow_pcap: dict = {}
+
+
+@lru_cache
+def sc_ip_format(sc_ipaddr: str) -> str:
+ ip = ipaddress.ip_address(sc_ipaddr)
+ if ip.version == 6:
+ return f"[{ip.compressed}]"
+ else:
+ return f"{ip.compressed}"
+
+
+async def load_event(con, line: bytes) -> None:
+ """
+ Add one event from eve.json to the SQL database
+ Use regex rather than JSON parsing for performance reasons.
+ """
+ event_type = re.search(rb"\"event_type\":\"([^\"]+)\"", line).group(1).decode()
+ if event_type == "flow":
+ src_ip = re.search(rb"\"src_ip\":\"([^\"]+)\"", line).group(1).decode()
+ dest_ip = re.search(rb"\"dest_ip\":\"([^\"]+)\"", line).group(1).decode()
+ app_proto_m = re.search(rb"\"app_proto\":\"([^\"]+)\"", line)
+ app_proto = app_proto_m.group(1).decode() if app_proto_m else None
+ flow_id = re.search(rb"\"flow_id\":(\d+)", line).group(1).decode()
+ pcap_filename = re.search(rb"\"pcap_filename\":\"([^\"]+)\"", line).group(1)
+ assert not app_proto or app_proto in SUPPORTED_PROTOCOLS + [
+ "failed"
+ ], f"app_proto refers to an unsupported protocol: {app_proto}"
+ pcap_filename = flow_pcap.pop(flow_id, pcap_filename)
+ await con.execute(
+ "INSERT OR IGNORE INTO flow (id, src_ip, src_port, "
+ "dest_ip, dest_port, pcap_filename, proto, app_proto, "
+ "extra_data) "
+ "values(?1->>'flow_id', ?2, ?1->>'src_port', ?3, "
+ "?1->>'dest_port', ?4, ?1->>'proto', ?1->>'app_proto', ?1->'flow')",
+ (
+ line,
+ sc_ip_format(src_ip),
+ sc_ip_format(dest_ip),
+ pcap_filename.decode(),
+ ),
+ )
+ elif event_type in ["alert", "anomaly", "fileinfo"] + SUPPORTED_PROTOCOLS:
+ # Collect pcap_filename
+ flow_id = re.search(rb"\"flow_id\":(\d+)", line).group(1).decode()
+ pcap_filename = re.search(rb"\"pcap_filename\":\"([^\"]+)\"", line).group(1)
+ flow_pcap[flow_id] = pcap_filename
+
+ # Insert event
+ await con.execute(
+ f"INSERT OR IGNORE INTO '{event_type}' (flow_id, extra_data) "
+ f"values(?1->>'flow_id', ?1->'{event_type}')",
+ (line,),
+ )
+
+
+class Database:
+ def __init__(self, database_uri: str) -> None:
+ self.database_uri = database_uri
+ self.con = None
+
+ async def connect(self):
+ self.con = await aiosqlite.connect(self.database_uri, uri=True)
+ self.con.row_factory = aiosqlite.Row
+ # WAL journal mode allows multiple concurrent readers
+ await self.con.execute("PRAGMA journal_mode=wal")
+ await self.con.execute("PRAGMA synchronous=normal")
+ await self.init_database_structure()
+
+ async def close(self):
+ assert self.con is not None, "database connection closed"
+ await self.con.close()
+
+ async def execute(self, *args, **kwargs):
+ assert self.con is not None, "database connection closed"
+ return await self.con.execute(*args, **kwargs)
+
+ async def init_database_structure(self):
+ assert self.con is not None, "database connection closed"
+ # TODO: when SQLite 3.42 is broadly available, use UNIXEPOCH('subsec')
+ await self.con.executescript(
+ """
+ CREATE TABLE IF NOT EXISTS ctf_config (
+ id INTEGER PRIMARY KEY,
+ start_date TEXT,
+ ts_start INTEGER GENERATED ALWAYS
+ AS ((JULIANDAY(start_date) - 2440587.5) * 86400000),
+ tick_length INTEGER,
+ services TEXT
+ );
+ CREATE TABLE IF NOT EXISTS checkpoint (
+ id INTEGER PRIMARY KEY,
+ eve_idx INTEGER,
+ tcp_idx INTEGER,
+ udp_idx INTEGER
+ );
+ CREATE TABLE IF NOT EXISTS flow (
+ id INTEGER NOT NULL PRIMARY KEY,
+ ts_start INTEGER GENERATED ALWAYS
+ AS ((JULIANDAY(SUBSTR((extra_data->>'start'), 1, 26))
+ - 2440587.5) * 86400000) STORED,
+ ts_end INTEGER GENERATED ALWAYS
+ AS ((JULIANDAY(SUBSTR((extra_data->>'end'), 1, 26))
+ - 2440587.5) * 86400000) STORED,
+ src_ip TEXT NOT NULL,
+ src_port INTEGER,
+ src_ipport TEXT GENERATED ALWAYS
+ AS (src_ip || ':' || IFNULL(src_port, 'None')),
+ dest_ip TEXT NOT NULL,
+ dest_port INTEGER,
+ dest_ipport TEXT GENERATED ALWAYS
+ AS (dest_ip || ':' || IFNULL(dest_port, 'None')),
+ pcap_filename TEXT,
+ proto TEXT NOT NULL,
+ app_proto TEXT,
+ extra_data TEXT
+ );
+ CREATE TABLE IF NOT EXISTS alert (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ flow_id INTEGER NOT NULL,
+ tag TEXT GENERATED ALWAYS
+ AS (extra_data->>'metadata.tag[0]') STORED,
+ color TEXT GENERATED ALWAYS
+ AS (extra_data->>'metadata.color[0]') STORED,
+ extra_data TEXT,
+ FOREIGN KEY(flow_id) REFERENCES flow (id),
+ UNIQUE(flow_id, tag)
+ );
+ CREATE TABLE IF NOT EXISTS raw (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ flow_id INTEGER NOT NULL,
+ count INTEGER,
+ server_to_client INTEGER,
+ sha256 TEXT,
+ FOREIGN KEY(flow_id) REFERENCES flow (id),
+ UNIQUE(flow_id, count)
+ );
+ """
+ )
+ for e in ["anomaly", "fileinfo"] + SUPPORTED_PROTOCOLS:
+ await self.con.execute(
+ f"""
+ CREATE TABLE IF NOT EXISTS "{e}" (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ flow_id INTEGER NOT NULL,
+ timestamp INTEGER GENERATED ALWAYS
+ AS ((JULIANDAY(SUBSTR((extra_data->>'timestamp'), 1, 26))
+ - 2440587.5) * 86400000) STORED,
+ extra_data TEXT,
+ FOREIGN KEY(flow_id) REFERENCES flow (id),
+ UNIQUE(flow_id, timestamp)
+ );
+ """
+ )
+
+ # Create indexes for non-unique values
+ await self.con.executescript(
+ 'CREATE INDEX IF NOT EXISTS "flow_ts_start_idx" ON flow(ts_start);'
+ 'CREATE INDEX IF NOT EXISTS "flow_app_proto_idx" ON flow(app_proto);'
+ 'CREATE INDEX IF NOT EXISTS "flow_src_ipport_idx" ON flow(src_ipport);'
+ 'CREATE INDEX IF NOT EXISTS "flow_dest_ipport_idx" ON flow(dest_ipport);'
+ 'CREATE INDEX IF NOT EXISTS "alert_tag_idx" ON alert(tag);'
+ )
+ for e in ["alert", "anomaly", "fileinfo", "raw"] + SUPPORTED_PROTOCOLS:
+ await self.con.execute(
+ f'CREATE INDEX IF NOT EXISTS "{e}_flow_id_idx" ON "{e}"(flow_id);'
+ )
+
+ async def update_ctf_config(self, ctf_config: dict):
+ """
+ Update database with configuration given through env vars (for Grafana).
+ """
+ assert self.con is not None, "database connection closed"
+ await self.con.execute("BEGIN TRANSACTION")
+ services = json.dumps(ctf_config["services"])
+ await self.con.execute(
+ "INSERT OR REPLACE INTO ctf_config (id, start_date, tick_length, services) "
+ "values(1, ?, ?, ?)",
+ (ctf_config["start_date"], ctf_config["tick_length"], services),
+ )
+ await self.con.execute("COMMIT")
+
+ async def fill_database(self):
+ assert self.con is not None, "database connection closed"
+ await self.con.execute("BEGIN TRANSACTION")
+ cursor = await self.con.execute(
+ "SELECT eve_idx, tcp_idx, udp_idx FROM checkpoint"
+ )
+ eve_idx, tcp_idx, udp_idx = await cursor.fetchone() or [0, 0, 0]
+ if eve_idx == 0:
+ print("Starting initial eve.json import, please be patient...")
+
+ # eve.json contains one event per line
+ with open("../suricata/output/eve.json", "rb") as f:
+ f.seek(eve_idx)
+ line_count = 0
+ for line in f:
+ if not line:
+ break
+ try:
+ await load_event(self.con, line)
+ except (AttributeError, aiosqlite.OperationalError):
+ break # eve.json ends with a partial JSON
+ eve_idx += len(line)
+ line_count += 1
+ if line_count:
+ print(f"{line_count} events loaded from eve.json", flush=True)
+
+ with open("../suricata/output/tcpstore.log", "r") as f:
+ f.seek(tcp_idx)
+ line_count = 0
+ for line in f:
+ if not line or not line.endswith("\n"):
+ break
+ flow_id, count, server_to_client, h = line.strip().split(",")
+ await self.con.execute(
+ "INSERT OR IGNORE INTO raw (flow_id, count, server_to_client, "
+ "sha256) values(?, ?, ?, ?)",
+ (flow_id, int(count), int(server_to_client), h),
+ )
+ tcp_idx += len(line)
+ line_count += 1
+ if line_count:
+ print(f"{line_count} chunks loaded from tcpstore.log", flush=True)
+
+ with open("../suricata/output/udpstore.log", "r") as f:
+ f.seek(udp_idx)
+ line_count = 0
+ for line in f:
+ if not line or not line.endswith("\n"):
+ break
+ flow_id, count, server_to_client, h = line.strip().split(",")
+ await self.con.execute(
+ "INSERT OR IGNORE INTO raw (flow_id, count, server_to_client, "
+ "sha256) values(?, ?, ?, ?)",
+ (flow_id, int(count), int(server_to_client), h),
+ )
+ udp_idx += len(line)
+ line_count += 1
+ if line_count:
+ print(f"{line_count} chunks loaded from udpstore.log", flush=True)
+
+ await self.con.execute(
+ "INSERT OR REPLACE INTO checkpoint (id, eve_idx, tcp_idx, udp_idx) "
+ "values(1, ?, ?, ?)",
+ (eve_idx, tcp_idx, udp_idx),
+ )
+ await self.con.execute("COMMIT")
+
+ async def importer_task(self):
+ """
+ Load events from eve.json and fill SQL database
+ """
+ while True:
+ try:
+ await self.fill_database()
+ except FileNotFoundError:
+ await self.con.execute("ROLLBACK")
+ print("Suricata output not found, retrying in 1s")
+ except Exception:
+ print(traceback.format_exc(), flush=True)
+ return
+
+ # Sleeping 1 second before trying to pull new data again
+ await asyncio.sleep(1)
diff --git a/webapp/main.py b/webapp/main.py
new file mode 100644
index 0000000..db77330
--- /dev/null
+++ b/webapp/main.py
@@ -0,0 +1,286 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 ANSSI
+# SPDX-License-Identifier: GPL-3.0-only
+
+import asyncio
+import contextlib
+import json
+
+from starlette.applications import Starlette
+from starlette.config import Config
+from starlette.datastructures import CommaSeparatedStrings
+from starlette.exceptions import HTTPException
+from starlette.responses import JSONResponse
+from starlette.routing import Mount, Route
+from starlette.staticfiles import StaticFiles
+from starlette.templating import Jinja2Templates
+
+from database import Database
+
+
+def row_to_dict(row) -> dict:
+ row = dict(row)
+ extra_data = json.loads(row.pop("extra_data"))
+ row.update(extra_data)
+ return row
+
+
+async def index(request):
+ context = {
+ "request": request,
+ "ctf_config": CTF_CONFIG,
+ }
+ return templates.TemplateResponse("index.html", context)
+
+
+async def api_flow_list(request):
+ # Parse GET arguments
+ ts_to = request.query_params.get("to", str(int(1e10)))
+ services = request.query_params.getlist("service")
+ app_proto = request.query_params.get("app_proto")
+ tags = request.query_params.getlist("tag")
+ if not ts_to.isnumeric():
+ raise HTTPException(400)
+
+ # Query flows and associated tags using filters
+ query = """
+ WITH fsrvs AS (SELECT value FROM json_each(?1)),
+ ftags AS (SELECT value FROM json_each(?2))
+ SELECT id, ts_start, ts_end, dest_ipport, app_proto,
+ (SELECT GROUP_CONCAT(tag) FROM alert WHERE flow_id = flow.id) AS tags
+ FROM flow WHERE ts_start <= ?3 AND (?4 IS NULL OR app_proto = ?4)
+ """
+ if services == ["!"]:
+ # Filter flows related to no services
+ query += "AND NOT (src_ipport IN fsrvs OR dest_ipport IN fsrvs)"
+ services = sum(CTF_CONFIG["services"].values(), [])
+ elif services:
+ query += "AND (src_ipport IN fsrvs OR dest_ipport IN fsrvs)"
+ if tags:
+ # Relational division to get all flow_id matching all chosen tags
+ query += """
+ AND flow.id IN (
+ SELECT flow_id FROM alert WHERE tag IN ftags GROUP BY flow_id
+ HAVING COUNT(*) = (SELECT COUNT(*) FROM ftags)
+ )
+ """
+ query += " ORDER BY ts_start DESC LIMIT 100"
+
+ cursor = await database.execute(
+ query, (json.dumps(services), json.dumps(tags), int(ts_to) * 1000, app_proto)
+ )
+ rows = await cursor.fetchall()
+ flows = [dict(row) for row in rows]
+
+ # Fetch application protocols
+ cursor = await database.execute("SELECT DISTINCT app_proto FROM flow")
+ rows = await cursor.fetchall()
+ prs = [r["app_proto"] for r in rows if r["app_proto"] not in [None, "failed"]]
+
+ # Fetch tags
+ cursor = await database.execute(
+ "SELECT tag, color FROM alert GROUP BY tag ORDER BY color"
+ )
+ rows = await cursor.fetchall()
+ tags = [dict(row) for row in rows]
+
+ return JSONResponse(
+ {
+ "flows": flows,
+ "appProto": prs,
+ "tags": tags,
+ }
+ )
+
+
+async def api_flow_get(request):
+ flow_id = request.path_params["flow_id"]
+
+ # Query flow from database
+ cursor = await database.execute(
+ (
+ "SELECT id, ts_start, ts_end, src_ipport, dest_ipport, dest_port, "
+ "pcap_filename, proto, app_proto, extra_data "
+ "FROM flow WHERE id = ?"
+ ),
+ [flow_id],
+ )
+ flow = await cursor.fetchone()
+ if not flow:
+ raise HTTPException(404)
+ result = {"flow": row_to_dict(flow)}
+ app_proto = result["flow"].get("app_proto")
+
+ # Get associated fileinfo
+ # See https://docs.suricata.io/en/suricata-6.0.9/file-extraction/file-extraction.html
+ if app_proto in ["http", "http2", "smtp", "ftp", "nfs", "smb"]:
+ cursor = await database.execute(
+ "SELECT extra_data FROM fileinfo WHERE flow_id = ? ORDER BY id", [flow_id]
+ )
+ rows = await cursor.fetchall()
+ result["fileinfo"] = [row_to_dict(f) for f in rows]
+
+ # Get associated protocol metadata
+ if app_proto and app_proto != "failed":
+ q_proto = app_proto if app_proto != "http2" else "http"
+ cursor = await database.execute(
+ f"SELECT extra_data FROM {q_proto} WHERE flow_id = ? ORDER BY id",
+ [flow_id],
+ )
+ rows = await cursor.fetchall()
+ result[app_proto] = [row_to_dict(f) for f in rows]
+
+ # Get associated alert
+ if result["flow"]["alerted"]:
+ cursor = await database.execute(
+ "SELECT extra_data, color FROM alert WHERE flow_id = ? ORDER BY id",
+ [flow_id],
+ )
+ rows = await cursor.fetchall()
+ result["alert"] = [row_to_dict(f) for f in rows]
+
+ # Get associated TCP/UDP raw data
+ cursor = await database.execute(
+ "SELECT server_to_client, sha256 FROM raw WHERE flow_id = ? ORDER BY count",
+ [flow_id],
+ )
+ rows = await cursor.fetchall()
+ result["raw"] = [dict(f) for f in rows]
+
+ return JSONResponse(result)
+
+
+async def api_replay_http(request):
+ flow_id = request.path_params["flow_id"]
+
+ # Get HTTP events
+ cursor = await database.execute(
+ "SELECT flow_id, extra_data FROM http WHERE flow_id = ? ORDER BY id",
+ [flow_id],
+ )
+ rows = await cursor.fetchall()
+
+ # For each HTTP request, load client payload if it exists
+ data = []
+ for row in rows:
+ req = row_to_dict(row)
+ req["rq_content"] = None
+ if req["http_method"] in ["POST"]:
+ # Get associated fileinfo
+ cursor = await database.execute(
+ "SELECT extra_data FROM fileinfo WHERE flow_id = ? ORDER BY id",
+ [flow_id],
+ )
+ fileinfo_first_event = await cursor.fetchone()
+ if not fileinfo_first_event:
+ raise HTTPException(404)
+ sha256 = json.loads(fileinfo_first_event["extra_data"]).get("sha256")
+ if not sha256:
+ raise HTTPException(500)
+
+ # Load file
+ path = f"static/filestore/{sha256[:2]}/{sha256}"
+ with open(path, "rb") as f:
+ req["rq_content"] = f.read()
+ data.append(req)
+
+ context = {"request": request, "data": data, "services": CTF_CONFIG["services"]}
+ return templates.TemplateResponse(
+ "http-replay.py.jinja2", context, media_type="text/plain"
+ )
+
+
+async def api_replay_tcp(request):
+ flow_id = request.path_params["flow_id"]
+
+ # Get flow event
+ cursor = await database.execute(
+ "SELECT dest_ipport FROM flow WHERE id = ?",
+ [flow_id],
+ )
+ flow_event = await cursor.fetchone()
+ if not flow_event:
+ raise HTTPException(404)
+ ip, port = flow_event["dest_ipport"].rsplit(":", 1)
+ data = {
+ "flow_id": flow_id,
+ "ip": ip,
+ "port": port,
+ "dest_ipport": flow_event["dest_ipport"],
+ }
+
+ # Get associated TCP data
+ cursor = await database.execute(
+ "SELECT server_to_client, sha256 FROM raw WHERE flow_id = ? ORDER BY count",
+ [flow_id],
+ )
+ rows = await cursor.fetchall()
+ if not rows:
+ raise HTTPException(404)
+
+ # Load files
+ data["tcp_data"] = []
+ for row in rows:
+ sc, sha256 = row["server_to_client"], row["sha256"]
+ path = f"static/tcpstore/{sha256[:2]}/{sha256}"
+ with open(path, "rb") as f:
+ tcp_data = f.read()
+ if data["tcp_data"] and data["tcp_data"][-1][1] == sc and sc == 1:
+ # Concat servers messages together
+ data["tcp_data"][-1][0] += tcp_data
+ else:
+ data["tcp_data"].append([tcp_data, sc])
+
+ context = {"request": request, "data": data, "services": CTF_CONFIG["services"]}
+ return templates.TemplateResponse(
+ "tcp-replay.py.jinja2", context, media_type="text/plain"
+ )
+
+
+@contextlib.asynccontextmanager
+async def lifespan(app):
+ """
+ Open database on startup and launch importer in background.
+ Close database on exit.
+ """
+ await database.connect()
+ await database.update_ctf_config(CTF_CONFIG)
+ db_task = asyncio.create_task(database.importer_task())
+ yield
+ db_task.cancel()
+ await database.close()
+
+
+# Load configuration from environment variables, then .env file
+config = Config(".env")
+DEBUG = config("DEBUG", cast=bool, default=False)
+DATABASE_URL = config("DATABASE_URL", cast=str, default="file:database/database.db")
+CTF_CONFIG = {
+ "start_date": config("CTF_START_DATE", cast=str),
+ "tick_length": config("CTF_TICK_LENGTH", cast=int),
+ "services": {},
+}
+service_names = config("CTF_SERVICES", cast=CommaSeparatedStrings)
+for name in service_names:
+ ipports = config(f"CTF_SERVICE_{name.upper()}", cast=CommaSeparatedStrings)
+ CTF_CONFIG["services"][name] = list(ipports)
+
+# Define web application
+database = Database(DATABASE_URL)
+templates = Jinja2Templates(directory="templates")
+app = Starlette(
+ debug=DEBUG,
+ routes=[
+ Route("/", index),
+ Route("/api/flow", api_flow_list),
+ Route("/api/flow/{flow_id:int}", api_flow_get),
+ Route("/api/replay-http/{flow_id:int}", api_replay_http),
+ Route("/api/replay-tcp/{flow_id:int}", api_replay_tcp),
+ Mount(
+ "/static",
+ StaticFiles(directory="static", follow_symlink=True),
+ name="static",
+ ),
+ ],
+ lifespan=lifespan,
+)
diff --git a/webapp/static/assets/css/bootstrap.min.css b/webapp/static/assets/css/bootstrap.min.css
new file mode 100644
index 0000000..f5910ac
--- /dev/null
+++ b/webapp/static/assets/css/bootstrap.min.css
@@ -0,0 +1,6 @@
+@charset "UTF-8";/*!
+ * Bootstrap v5.3.2 (https://getbootstrap.com/)
+ * Copyright 2011-2023 The Bootstrap Authors
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(13, 110, 253, 0.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222,226,230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110,168,254;--bs-link-hover-color-rgb:139,185,254;--bs-code-color:#e685b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.66666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.66666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.66666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.66666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.66666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.66666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-emphasis-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-emphasis-color);--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), 0.05);--bs-table-active-color:var(--bs-emphasis-color);--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), 0.1);--bs-table-hover-color:var(--bs-emphasis-color);--bs-table-hover-bg:rgba(var(--bs-emphasis-color-rgb), 0.075);width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#a6b5cc;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#b5b6b7;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#a7b9b1;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#a6c3ca;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#ccc2a4;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#c6acae;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#c6c7c8;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#4d5154;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-clip:padding-box;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-moz-placeholder{color:var(--bs-secondary-color);opacity:1}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:transparent;border:solid transparent;border-width:var(--bs-border-width) 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon,none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);flex-shrink:0;width:1em;height:1em;margin-top:.25em;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:var(--bs-border-width) solid var(--bs-border-color);-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;-webkit-appearance:none;appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;-moz-appearance:none;appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control-plaintext::-moz-placeholder,.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:not(:-moz-placeholder-shown),.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:not(:-moz-placeholder-shown)~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control-plaintext~label::after,.form-floating>.form-control:focus~label::after,.form-floating>.form-control:not(:placeholder-shown)~label::after,.form-floating>.form-select~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:-webkit-autofill~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>.form-control:disabled~label,.form-floating>:disabled~label{color:#6c757d}.form-floating>.form-control:disabled~label::after,.form-floating>:disabled~label::after{background-color:var(--bs-secondary-bg)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(var(--bs-border-width) * -1);border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:var(--bs-form-valid-border-color)}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:var(--bs-form-valid-border-color)}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:var(--bs-form-valid-color)}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:var(--bs-form-invalid-border-color)}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:var(--bs-form-invalid-border-color)}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:var(--bs-form-invalid-color)}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:0.75rem;--bs-btn-padding-y:0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.15),0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49,132,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130,138,145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60,153,110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11,172,204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217,164,6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225,83,97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211,212,213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66,70,73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13,110,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108,117,125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25,135,84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13,202,240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255,193,7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220,53,69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248,249,250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33,37,41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:49,132,253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:0.5rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:var(--bs-tertiary-bg);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:0.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0;border-radius:var(--bs-dropdown-item-border-radius,0)}.dropdown-item:focus,.dropdown-item:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:calc(var(--bs-border-width) * -1)}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:calc(var(--bs-border-width) * -1)}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;background:0 0;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:0.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid transparent}.nav-underline .nav-link:focus,.nav-underline .nav-link:hover{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:0.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), 0.65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), 0.3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:transparent;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:rgba(255, 255, 255, 0.55);--bs-navbar-hover-color:rgba(255, 255, 255, 0.75);--bs-navbar-disabled-color:rgba(255, 255, 255, 0.25);--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:rgba(255, 255, 255, 0.1);--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:0.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), 0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:0.75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23052c65'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-focus-border-color:#86b7fe;--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:var(--bs-accordion-btn-focus-border-color);outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button,.accordion-flush .accordion-item .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.active>.page-link,.page-link.active{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.disabled>.page-link,.page-link.disabled{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(var(--bs-border-width) * -1)}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:0.65em;--bs-badge-padding-y:0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:var(--bs-secondary-bg);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:var(--bs-border-color);--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:0.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-tertiary-bg);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity:0.5;--bs-btn-close-hover-opacity:0.75;--bs-btn-close-focus-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:0.25;--bs-btn-close-white-filter:invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;border-radius:.375rem;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme=dark] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-header-border-color:var(--bs-border-color-translucent);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:0.5rem;--bs-modal-color: ;--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin:calc(-.5 * var(--bs-modal-header-padding-y)) calc(-.5 * var(--bs-modal-header-padding-x)) calc(-.5 * var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-footer,.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-footer,.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-footer,.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-footer,.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-footer,.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-footer,.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:0.5rem;--bs-tooltip-padding-y:0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:0.9;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-top>.popover-arrow::before{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-end>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::before{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-start>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark] .carousel .carousel-control-prev-icon,[data-bs-theme=dark].carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon{filter:invert(1) grayscale(100)}[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark] .carousel .carousel-caption,[data-bs-theme=dark].carousel .carousel-caption{color:#000}.spinner-border,.spinner-grow{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform 0.3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (max-width:575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{transform:none}.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media (min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (max-width:767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{transform:none}.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media (min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{transform:none}.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media (min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{transform:none}.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media (min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{transform:none}.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media (min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y))}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(var(--bs-secondary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb),var(--bs-bg-opacity,1))!important}.text-bg-info{color:#000!important;background-color:RGBA(var(--bs-info-rgb),var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#000!important;background-color:RGBA(var(--bs-warning-rgb),var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb),var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb),var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb),var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important}.link-primary:focus,.link-primary:hover{color:RGBA(10,88,202,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important}.link-secondary:focus,.link-secondary:hover{color:RGBA(86,94,100,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important}.link-success:focus,.link-success:hover{color:RGBA(20,108,67,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important}.link-info:focus,.link-info:hover{color:RGBA(61,213,243,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important}.link-warning:focus,.link-warning:hover{color:RGBA(255,205,57,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important}.link-danger:focus,.link-danger:hover{color:RGBA(176,42,55,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important}.link-light:focus,.link-light:hover{color:RGBA(249,250,251,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important}.link-dark:focus,.link-dark:hover{color:RGBA(26,30,33,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:focus,.link-body-emphasis:hover{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-underline-offset:0.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:focus-visible>.bi,.icon-link-hover:hover>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em,0,0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption),.visually-hidden:not(caption){position:absolute!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:var(--bs-border-width);min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-none{-o-object-fit:none!important;object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10{--bs-link-opacity:0.1}.link-opacity-10-hover:hover{--bs-link-opacity:0.1}.link-opacity-25{--bs-link-opacity:0.25}.link-opacity-25-hover:hover{--bs-link-opacity:0.25}.link-opacity-50{--bs-link-opacity:0.5}.link-opacity-50-hover:hover{--bs-link-opacity:0.5}.link-opacity-75{--bs-link-opacity:0.75}.link-opacity-75-hover:hover{--bs-link-opacity:0.75}.link-opacity-100{--bs-link-opacity:1}.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1{text-underline-offset:0.125em!important}.link-offset-1-hover:hover{text-underline-offset:0.125em!important}.link-offset-2{text-underline-offset:0.25em!important}.link-offset-2-hover:hover{text-underline-offset:0.25em!important}.link-offset-3{text-underline-offset:0.375em!important}.link-offset-3-hover:hover{text-underline-offset:0.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0{--bs-link-underline-opacity:0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10{--bs-link-underline-opacity:0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:0.1}.link-underline-opacity-25{--bs-link-underline-opacity:0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:0.25}.link-underline-opacity-50{--bs-link-underline-opacity:0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:0.5}.link-underline-opacity-75{--bs-link-underline-opacity:0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:0.75}.link-underline-opacity-100{--bs-link-underline-opacity:1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb),var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb),var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-bottom-left-radius:0!important;border-top-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-bottom-left-radius:50%!important;border-top-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-sm-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-sm-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-sm-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-sm-none{-o-object-fit:none!important;object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-sm-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-sm-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-sm-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-sm-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-sm-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-md-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-md-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-md-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-md-none{-o-object-fit:none!important;object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-md-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-md-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-md-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-md-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-md-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-lg-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-lg-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-lg-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-lg-none{-o-object-fit:none!important;object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-lg-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-lg-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-lg-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-lg-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-lg-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xl-none{-o-object-fit:none!important;object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xxl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xxl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xxl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xxl-none{-o-object-fit:none!important;object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xxl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xxl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xxl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xxl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xxl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}
+/*# sourceMappingURL=bootstrap.min.css.map */
\ No newline at end of file
diff --git a/webapp/static/assets/css/style.css b/webapp/static/assets/css/style.css
new file mode 100644
index 0000000..076b4a8
--- /dev/null
+++ b/webapp/static/assets/css/style.css
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 ANSSI
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/* Use Bootstrap icons */
+.bi {
+ fill: currentColor;
+ vertical-align: -.125em;
+}
+
+/* Disable Bootstrap blue box-shadow on open