[*] Initial commit

pull/2/head
Lorenzo Cogotti 4 years ago
commit b0ef4dd774

@ -0,0 +1 @@
Checks: 'clang-*,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling'

51
.gitignore vendored

@ -0,0 +1,51 @@
# meson build directory
build/
# exclude test data folders
test/*/
# external projects, if any
subprojects/*/
# hidden files
.*
!.clang-format
!.clang-tidy
!.gitignore
!.gitmodules
# KDevelop projects
*.kdev4
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app

@ -0,0 +1,675 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 <https://www.gnu.org/licenses/>.
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:
<program> Copyright (C) <year> <name of author>
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
<https://www.gnu.org/licenses/>.
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
<https://www.gnu.org/licenses/why-not-lgpl.html>.

@ -0,0 +1,166 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser 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
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

@ -0,0 +1,60 @@
µbgpsuite -- Micro BGP Suite and Utility library
================================================
# Introduction
`µbgpsuite`, the Micro BGP Suite and Utility library, is a project
aiming to provide a low level library and utilities for
high-performance and flexible network analysis, with special
focus on the Border Gateway Protocol (BGP).
The purpose of this suite is establishing a friendly environment
for research and experimentation of useful data study
techniques to improve network health.
# lonetix -- Low-overhead Networking programming Interface
The project is centered around `lonetix`, a low overhead and
low level networking library written in C.
It provides a set of general functionality to implement the suite utilities.
`lonetix` principles are:
- efficiency: `lonetix` has to be fast and versatile;
- predictability: data structures and functions should be predictable
and reflect the actual protocol, abstraction should not degenerate
into alienation;
- zero copy and zero overhead: be friendly to your target CPU and cache,
you never know just how fast or poweful the target platform will be,
ideally `lonetix` should be capable of performing useful work on embedded
systems as well as full fledged power systems alike;
- lean: try to be self-contained and only introduce dependencies when strictly
necessary.
Extensive documentation of `lonetix` and its API is available.
# Utilities
`lonetix` is the building block of `bgpgrep`, this far our single
utility -- but more of them are coming, right?
`bgpgrep` performs fast and reliable analysis of MRT dumps
collected by most Route Collecting projects. It takes a different
turn compared to most similar tools, in that it provides extensive
filtering utilities, in order to extrapolate only relevant data
out of each MRT dump (and incidentally save quite some time).
In-depth documentation of `bgpgrep` is available in its man page.
# License
The Micro BGP Suite is free software.
You can redistribute the `lonetix` library and/or modify it under the terms of the
GNU Lesser General Public License.
You can redistribute any utility and/or modify it under the terms of the
GNU General Public License.
The Micro BGP Suite 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 license terms for
more details.
See `COPYING.LESSER` for the GNU Lesser General Public License terms,
and `COPYING.GPL` for the GNU General Public License terms.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,55 @@
Micro BGP Suite history
=======================
This document summarizes a bit of history of the project, it tells you anything
you didn't want to know about `ubgpsuite` and never cared to ask.
`ubgpsuite` was created as an evolution over `bgpscanner` and its companion
library, `isocore`. `bgpscanner` was originally developed by me starting in 2017
as part of the Isolario Project, under the Institute of Informatics and
Telematics of the Italian National Research Council
[IIT-CNR](https://www.iit.cnr.it/).
`bgpscanner` was covered by the MIT license terms and is still available
(as of May 2021) at Isolario's
[website](https://isolario.it/web_content/php/site_content/tools.php).
Despite the fact that `bgpscanner` code was developed mostly by me, the
help of the Isolario team, their patience, and their knowledge about BGP's
nuts and bolts was invaluable to get it rolling.
By mid 2019, my collaboration with IIT-CNR has ceased.
In an attempt to further the concepts behind `bgpscanner` and keep experimenting
with them, I started the `ubgpsuite` project. At this time I was involved with
[Alpha Cogs](https://www.alphacogs.com), a company I co-founded, so I undertook
`ubgpsuite` development and got it moving forward with its financial support.
`ubgpsuite` was born by a partial rewrite of `bgpscanner`, taking advantage of
the fact that there was no more interoperability constrain with a larger
project -- `isocore` was originally intended to be used by other
components inside the Isolario project as well, but that wasn't the
case anymore. This allowed some minor improvement to the codebase, but the
overall software architecture was unchanged. Though `ubgpsuite` was relicensed
under the terms of LGPLv3+ for library code and GPLv3+ for utilities and tools.
The choice was motivated by my intention to keep the project free
(as in freedom) forever, considering that the only reason I was able to continue
developing the code I authored at IIT-CNR and keep the project alive was
its original open source license.
Unfortunately a chronical lack of time, due to more pressing company priorities,
has hit the fan during that year and most of 2020. But finally,
by the end of 2020, I got the chance to dedicate some time to the project.
As a matter of fact, I left Alpha Cogs and devoted a bit of myself to
move `ubgpsuite` and other projects I cared about out of stagnation.
`ubgpsuite` was then outside Alpha Cogs and became the first of
DoubleFourteen Code Forge projects -- 1414° for short, an initiative I
founded to research and develop free software, in the hope of improving
the software ecosystem.
On 2021, a total rewrite of `ubgpsuite` was complete and ready for release,
with this document included. History of the project will thereafter remain
unread.
Keep developing the code you love and enjoy,
---
Lorenzo Cogotti

@ -0,0 +1,3 @@
:root {
--note-color-darker: #8e7618;
}

@ -0,0 +1,108 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
:root {
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
* Make sure it is wide enought to contain the page title (logo + title + version)
*/
--side-nav-fixed-width: 350px;
--menu-display: none;
--top-height: 120px;
}
@media screen and (min-width: 768px) {
:root {
--searchbar-background: var(--page-background-color);
}
#side-nav {
min-width: var(--side-nav-fixed-width);
max-width: var(--side-nav-fixed-width);
top: var(--top-height);
}
#nav-tree, #side-nav {
height: calc(100vh - var(--top-height)) !important;
}
#nav-tree {
padding: 0;
}
#top {
display: block;
border-bottom: none;
height: var(--top-height);
margin-bottom: calc(0px - var(--top-height));
max-width: var(--side-nav-fixed-width);
background: var(--side-nav-background);
}
#main-nav {
float: left;
}
.ui-resizable-handle {
cursor: default;
width: 1px !important;
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
}
#nav-path {
position: fixed;
right: 0;
left: var(--side-nav-fixed-width);
bottom: 0;
width: auto;
}
#doc-content {
height: calc(100vh - 31px) !important;
padding-bottom: calc(3 * var(--spacing-large));
padding-top: calc(var(--top-height) - 80px);
box-sizing: border-box;
margin-left: var(--side-nav-fixed-width) !important;
}
#MSearchBox {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
}
#MSearchField {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
}
#MSearchResultsWindow {
left: var(--spacing-medium) !important;
right: auto;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,21 @@
cdata = configuration_data({
'TOP_SRCDIR': meson.source_root(),
'TOP_BUILDDIR': meson.build_root(),
'OUTPUT_DIR': meson.build_root() / 'doc',
'VERSION': meson.project_version(),
'PROJECT_NAME': meson.project_name(),
})
doxyfile = configure_file(input : 'Doxyfile.in',
output : 'Doxyfile',
configuration : cdata,
install : false)
doc_target = custom_target('doc',
build_by_default : false,
build_always_stale : true,
console : true,
command : [ doxygen, doxyfile ],
output : [ 'doc' ])
alias_target('doc', doc_target)

@ -0,0 +1,170 @@
Low-overhead Networking library Interface
=============================================
# Introdution and philosophy
`lonetix` is a general, performance oriented, C networking library.
Its field of application leans towards high-performance analysis of
Border Gateway Protocol (BGP) data.
`lonetix` is somewhat opinionated, its principles are:
- efficiency: `lonetix` has to be fast and versatile;
- predictability: data structures and functions should be predictable
and reflect the actual protocol, abstraction should not degenerate
into alienation;
- zero copy and zero overhead: be friendly to your target CPU and cache,
you never know just how fast or poweful the target platform will be,
ideally `lonetix` should be capable of performing useful work on embedded
systems as well as full fledged power systems alike;
- lean: try to be self-contained and only introduce dependencies when
necessary.
Following sections further elaborate on these points.
## Efficiency
Network analysis is usually thought as a computationally intensive task,
involving powerful machines capable of crunching large datasets.
Fast prototyping is tipically preferred over carefully planned, optimized code
and tools. Our belief is that careful optimizations, good algorithms and general
libraries may empower scientific research to productively elaborate data faster.
Efficient algorithms and tools make previously prohibitive tasks plausible.
Some researchers have no access to powerful workstations, though devices
commonly available to the general public are capable enough to perform
interesting network analysis tasks.
Good tools should not restrict research, they should encourage it.
Efficiency should be a guiding principle behind `lonetix`, and the main
reason for choosing C as a language.
## Predictability
`lonetix` is a relatively low-level C library. As such it deals with
common software engineering problems. In contrast with common opinion, C has
sufficient means to define a decent level of abstraction.
Powerful abstractions have to be formalized, documented, explained and learned.
Once this process is complete, powerful abstractions need to be used correctly.
Therefore, powerful abstractions need expensive engineering and comprehensive
documentation, they imply a learning curve and a period of practice.
Abstraction is a key software engineering concept only valuable if worthwhile.
Excessive abstraction may distract too much from the intent of a programming
interface, making it more obfuscated, less obvious, thus less predictable.
Additionally, it makes it harder for a programmer using them to guess or
estimate their performance penalty -- a particularly undesirable feature
in a scenario where such estimate could be crucial.
Whenever possible an interface should be transparent to the programmer,
essential, immediate in conveying its purpose and model, keep its field of
application clear and confined.
`lonetix` builds complex abstraction only in face of a sufficient gain.
Simplicity is a virtue, and obvious solutions need less explaination.
Oftentimes, solving a large problem with clear straight to the point code
is testament of a solid approach.
## Zero copy
`lonetix` deals, for the most part, with BGP messages.
Decoding them, ensuring their integrity and accessing their fields
conveniently and efficiently is central to the usefulness of the library.
Most libraries that read messages encoded in a particular protocol,
take the common approach of introducing a decode phase upfront,
with the intent of transforming its raw data into a more palatable
representation for the library.
The obvious advantages of this approach are:
- an efficient resulting data structure that makes it easy to access every
message field when needed;
- any data integrity error is detected upfront, during the transformation.
Situation is specular for message writing.
This approach comes with its own set of disadvantages, though:
- an initial decoding phase implies the whole message is scanned at least
once to organize it in the new data structure, even when only a single field of
the entire message would be relevant to the user;
- CPU architectures greatly benefit from cache reuse, introducing a decode
phase upfront that moves data around from a plain byte buffer to a complex
data structure is usually bad news to the CPU cache;
- more data structures generally imply more memory allocations;
- translating raw data to the target data structure and back may
require more complex API and implementation than providing equivalent
facilities to access raw data directly.
These reasons motivated `lonetix` to explore a more trivial zero-copy approach:
whenever possible `lonetix` should work with raw BGP messages and require no
unnecessary data copy.
Do note that this approach is not perfect either. We simply believe that the
tradeoff is to `lonetix` advantage, and a zero copy approach fits better in
a performance-oriented and predictable library.
Same considerations apply to any portions of the library facing similar
situations (MRT data, other network protocols, etc...).
## Zero overhead
`lonetix` provides comprehensive facilities for network analysis and additional
utility functions for a wide variety of common tasks
(including string utilities, text parsing, etc...).
Library users should not be burdened with overhead for functionality they don't
need.
By design `lonetix` should be modular and require no runtime overhead
(such as background threads, `atexit()` hooks, or static initialization)
unless deemed as positively and unmistakably unavoidable.
`lonetix` is a **static library**, making it possible for the compiler to
strip any unused code from the resulting binary.
Careful coding should always allow to compile the library with
full optimizations on, including Link Time Optimization
(whether the binary should be optimized for space or
speed should be configurable by the user).
## Lean
No external dependency should be introduced unless strictly necessary.
This helps improving portability and makes `lonetix` usable even under
constrained environments.
`lonetix` **does not pursue strict ABI or API stability**.
Given that `lonetix` is a static library, keeping ABI stability is unnecessary.
Strict API stability tends to clutter libraries with large amounts of
legacy code, `lonetix` strives for incremental code improvement.
This sometimes calls for changes to the API and minor interface variations.
Users wishing for specific features from older versions that have been
evicted or changed on current ones, may fetch the older versions and link to
them.
Though API stability is not guaranteed, it should not be broken deliberately,
viable code migration paths should be offered when possible, for sensible use
cases.
# Documentation and examples
Complete project documentation is currently work in progress.
Extensive `Doxygen` API documentation is available for most of the library.
We also believe code should be clear, understandable and idiomatic, so
you can check out the code of any utility using `lonetix`
(for example `bgpgrep`) as a reference of how to take advantage of the library.
# License
`lonetix` is free software. You can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License version 3 as published
by the Free Software Foundation, or, at your choice, any subsequent version
of the same license. `lonetix` 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 license terms for
more details.
See `COPYING.LESSER` under the Micro BGP Suite root project directory for the
GNU Lesser General Public License terms, or see the
[Free Software Foundation website](https://www.gnu.org/licenses/lgpl-3.0.txt).

@ -0,0 +1,349 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file argv.c
*
* Portable command line argument parsing implementation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "argv.h"
#include "sys/con.h"
#include "utf/utf.h"
#include <assert.h>
#include <stdlib.h> // for getenv() on __GNUC__
#include <string.h>
const char *com_progName = NULL;
const char *com_synopsis = NULL;
const char *com_shortDescr = NULL;
const char *com_longDescr = NULL;
#define OPT_ISLONLY(flag) ((flag)->opt == '-')
#define OPT_HASLONGNAM(flag) ((flag)->longopt != NULL)
#define OPT_ARGNAME(flag) (((flag)->argName) ? (flag)->argName : "arg")
static void PrintHelpMessage(const char *prog, const Optflag *options)
{
if (!prog)
return; // quiet parsing
Sys_Printf(STDOUT, "Usage: %s", prog);
if (com_synopsis)
Sys_Printf(STDOUT, " %s", com_synopsis);
Sys_Print(STDOUT, "\n");
if (com_shortDescr)
Sys_Printf(STDOUT, "%s\n", com_shortDescr);
if (com_longDescr)
Sys_Printf(STDOUT, "\n%s\n\n", com_longDescr);
char buf[MAXUTF + 1];
size_t n;
for (const Optflag *flag = options; flag->opt != '\0'; flag++) {
Sys_Print(STDOUT, " ");
// Single char option name
if (OPT_ISLONLY(flag)) {
// Long option only, leave 2 chars for alignment purposes
assert(flag->longopt);
Sys_Print(STDOUT, " ");
} else {
// Write down single-char option first
n = runetochar(buf, flag->opt);
buf[n] = '\0';
Sys_Printf(STDOUT, "-%s", buf);
}
// Long name and (optional) argument
if (flag->longopt) {
// Write comma if necessary, or leave 2 spaces for alignment
Sys_Print(STDOUT, OPT_ISLONLY(flag) ? " " : ", ");
Sys_Printf(STDOUT, "--%s", flag->longopt);
// Append argument with a leading = sign
switch (flag->hasArg) {
default: assert(FALSE); break;
case ARG_NONE: break;
case ARG_OPT: Sys_Printf(STDOUT, "[=%s]", OPT_ARGNAME(flag)); break;
case ARG_REQ: Sys_Printf(STDOUT, "=%s", OPT_ARGNAME(flag)); break;
}
} else {
// Output argument, short options have no leading =
switch (flag->hasArg) {
default: assert(FALSE); break;
case ARG_NONE: break;
case ARG_OPT: Sys_Printf(STDOUT, " [%s]", OPT_ARGNAME(flag)); break;
case ARG_REQ: Sys_Printf(STDOUT, " <%s>", OPT_ARGNAME(flag)); break;
}
}
if (flag->descr)
Sys_Printf(STDOUT, "\t%s", flag->descr);
Sys_Print(STDOUT, "\n");
}
// Append help options
Sys_Print(STDOUT, " -h, --help\tPrint this help message\n");
Sys_Print(STDOUT, " -?\tEquivalent to -h\n");
}
// If `prog` is not NULL, output an excess argument error (only called for long options).
static void PrintExcessArgError(const char *prog,
const Optflag *flag,
const char *arg)
{
if (!prog)
return; // quiet parse
assert(flag->longopt);
Sys_Printf(STDERR, "%s: Option --%s takes no argument: --%s=%s", prog, flag->longopt, flag->longopt, arg);
}
static void PrintMissingArgError(const char *prog, const Optflag *flag, Boolean longName)
{
if (!prog)
return; // quiet parse
char buf[MAXUTF+1];
const char *nam = NULL;
const char *pfx = (longName) ? "--" : "-";
if (longName)
nam = flag->longopt;
else {
size_t n = runetochar(buf, flag->opt);
buf[n] = '\0';
nam = buf;
}
Sys_Printf(STDERR, "%s: Option", prog);
if (nam)
Sys_Printf(STDERR, " %s%s", pfx, nam);
Sys_Print(STDERR, " requires mandatory argument");
if (flag->argName)
Sys_Printf(STDERR, " <%s>", flag->argName);
Sys_Printf(STDERR, ": %s%s\n", pfx, nam);
}
CHECK_PRINTF(2, 0) static void PrintBadCmdLineError(const char *prog,
const char *fmt,
...)
{
if (!prog)
return; // quiet parsing
va_list va;
va_start(va, fmt);
Sys_Printf(STDERR, "%s: ", prog);
Sys_VPrintf(STDERR, fmt, va);
Sys_Print(STDERR, "\n");
va_end(va);
}
static Optflag *FindLongFlag(const char *p, char **optarg, const char *prog, Optflag *options)
{
char *arg = strchr(p, '=');
*optarg = arg;
char *name;
if (arg) {
size_t n = arg - p;
name = (char *) alloca(n + 1);
memcpy(name, p, n);
name[n] = '\0';
} else
name = (char *) p; // safe
for (Optflag *flag = options; flag->opt != '\0'; flag++) {
if (OPT_HASLONGNAM(flag) && strcmp(flag->longopt, name) == 0) {
flag->flagged = TRUE;
return flag;
}
}
if (prog)
Sys_Printf(STDERR, "%s: Unrecognized option: --%s\n", prog, name);
return NULL;
}
static Optflag *FindFlag(Rune r, const char *prog, Optflag *flags)
{
for (Optflag *flag = flags; flag->opt != '\0'; flag++) {
// NOTE: options with long names are skipped (-- is end of argument list)
if (flag->opt == r) {
flag->flagged = TRUE;
return flag;
}
}
if (prog) {
char buf[MAXUTF + 1];
size_t n = runetochar(buf, r);
buf[n] = '\0';
Sys_Printf(STDERR, "%s: Unrecognized option: -%s\n", prog, buf);
}
return NULL;
}
#ifdef __GNUC__
static void ReorderArgv(int argc, char **argv, const Optflag *options)
{
if (getenv("POSIXLY_CORRECT"))
return; // don't mess with argv is POSIX behavior is requested
USED(argc); USED(argv); USED(options);
// TODO
}
#endif
int Com_ArgParse(int argc, char **argv, Optflag *options, unsigned flags)
{
Optflag *flag;
char *p, *optarg;
// Initial setup
char *prog = NULL; // FindFlag*() functions won't log on NULL `prog`
if ((flags & ARG_QUIET) == 0) {
// Extract program name, so parsing outputs meaningful messages
prog = (char *) com_progName;
if (!prog) {
// Generate from argv[0]
prog = argv[0]; // TODO: basename(argv[0]);
}
}
#ifdef __GNUC__
/* GNU allows program options in any order with respect to program
* arguments, for example:
* ```c
* program file -cv
* ```
* According to POSIX both `file` and `-cv` are program arguments.
* According to GNU `-cv` are options, `file` is a program argument.
*
* To do this GNU getopt() implicitly reorders `argv`.
*/
if ((flags & ARG_NOREORD) == 0)
ReorderArgv(argc, argv, options);
#endif
// Parse argument list
int optind;
for (optind = 1; optind < argc; optind++) {
p = argv[optind];
if (strcmp(p, "--") == 0) {
optind++;
break; // explicit end of command list
}
if (p[0] == '-' && p[1] == '-') {
// GNU style long option
p += 2;
if (strcmp(p, "help") == 0) {
PrintHelpMessage(prog, options);
return OPT_HELP;
}
flag = FindLongFlag(p, &optarg, prog, options);
if (!flag)
return OPT_UNKNOWN;
if (!optarg && flag->hasArg)
optarg = argv[++optind]; // fetch argument from `argv`
if (optarg && flag->hasArg == ARG_NONE) {
PrintExcessArgError(prog, flag, optarg);
return OPT_EXCESSARG;
}
if (!optarg && flag->hasArg == ARG_REQ) {
PrintMissingArgError(prog, flag, /*longName=*/TRUE);
return OPT_ARGMISS;
}
flag->optarg = optarg;
} else if (p[0] == '-' && p[1] != '\0') {
// Unix-style single char option
p++;
do {
Rune r;
p += chartorune(&r, p);
if (r == '-') {
/* This can happen in events like: -a-c
* where a->hasArg == ARG_NONE
*
* There is no way to fix this ambiguity safely:
* * if -c is an argument to -a then it is OPT_EXCESSARG error
* * if -a -- -c problematic because it's counterintuitive
* for the user
* (one could get surprising -c program arguments)
* * if we take it as -a --(force as option) -c it would be
* dangerous (sometimes -- is an option, sometimes an
* end of option list)
*
* NOTE: getopt() appears to do the last, and it's
* horrific
*/
PrintBadCmdLineError(prog, "Ambiguous '-' in short options list: %s", argv[optind]);
return OPT_BADARGV;
}
// Handle single char help requests
if (r == '?' || r == 'h') {
PrintHelpMessage(prog, options);
return OPT_HELP;
}
flag = FindFlag(r, prog, options);
if (!flag)
return OPT_UNKNOWN;
optarg = NULL;
if (flag->hasArg)
optarg = (*p != '\0') ? p : argv[++optind];
if (!optarg && flag->hasArg == ARG_REQ) {
PrintMissingArgError(prog, flag, /*longName=*/FALSE);
return OPT_ARGMISS;
}
flag->optarg = optarg;
} while (*p != '\0');
}
#if 0 && defined(_WIN32) // TODO
else if (p[0] == '/' && p[1] != '\0') {
// DOS style option
}
#endif
else {
// End of argument list, break the loop
break;
}
}
return optind;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,611 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/bgp.c
*
* BGP message decoding.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "sys/sys_local.h"
#include "sys/dbg.h"
#include "sys/endian.h"
#include "sys/ip.h"
#include "sys/con.h"
#include "argv.h"
#include "numlib.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const Uint8 bgp_marker[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
STATIC_ASSERT(sizeof(bgp_marker) == BGP_MARKER_LEN, "Malformed 'bgp_marker'");
static THREAD_LOCAL BgpErrStat bgp_errs;
const char *Bgp_ErrorString(BgpRet code)
{
switch (code) {
case BGPEBADVM: return "Attempting operation on BGP VM under error state";
case BGPEVMNOPROG: return "Attempt to execute BGP VM with empty bytecode";
case BGPEVMBADCOMTCH: return "Bad COMMUNITY match expression";
case BGPEVMASMTCHESIZE: return "BGP VM AS_PATH match expression too complex";
case BGPEVMASNGRPLIM: return "BGP VM AS_PATH match expression group limit hit";
case BGPEVMBADASMTCH: return "Bad AS_PATH match expression";
case BGPEVMBADJMP: return "BGP VM jump instruction target is out of bounds";
case BGPEVMILL: return "Illegal instruction in BGP VM bytecode";
case BGPEVMOOM: return "BGP VM heap memory exhausted";
case BGPEVMBADENDBLK: return "Encountered ENDBLK with no corresponding BLK";
case BGPEVMUFLOW: return "BGP VM stack underflow";
case BGPEVMOFLOW: return "BGP VM stack overflow";
case BGPEVMBADFN: return "CALL instruction index is out of bounds";
case BGPEVMBADK: return "LOADK instruction index is out of bounds";
case BGPEVMMSGERR: return "Error encountered during BGP message access";
case BGPEVMBADOP: return "BGP VM instruction has invalid operand";
case BGPENOERR: return "No error";
case BGPEIO: return "Input/Output error";
case BGPEBADTYPE: return "Provided BGP message has inconsistent type";
case BGPENOADDPATH: return "Provided BGP message contains no ADD_PATH information";
case BGPEBADATTRTYPE: return "Provided BGP attribute has inconsistent type";
case BGPEBADMARKER: return "BGP message marker mismatch";
case BGPENOMEM: return "Memory allocation failure";
case BGPETRUNCMSG: return "Truncated BGP message";
case BGPEOVRSIZ: return "Oversized BGP message";
case BGPEBADOPENLEN: return "BGP OPEN message has inconsistent length";
case BGPEDUPNLRIATTR: return "Duplicate NLRI attribute detected inside UPDATE message";
case BGPEBADPFXWIDTH: return "Bad prefix width";
case BGPETRUNCPFX: return "Truncated prefix";
case BGPETRUNCATTR: return "Truncated BGP attribute";
case BGPEBADAGGR: return "Malformed AGGREGATOR attribute";
case BGPEBADAGGR4: return "Malformed AS4_AGGREGATOR attribute";
case BGPEAFIUNSUP: return "Unsupported Address Family Identifier";
case BGPESAFIUNSUP: return "Unsupported Subsequent Address Family Identifier";
case BGPEBADMRTTYPE: return "Provided MRT record has inconsistent type";
case BGPETRUNCMRT: return "Truncated MRT record";
case BGPEBADPEERIDXCNT: return "TABLE_DUMPV2 Peer Index Table record has incoherent peer entry count";
case BGPETRUNCPEERV2: return "Truncated peer entry in TABLE_DUMPV2 Peer Index Table record";
case BGPEBADRIBV2CNT: return "TABLE_DUMPV2 RIB record has incoherent RIB entry count";
case BGPETRUNCRIBV2: return "Truncated entry in TABLE_DUMPV2 RIB record";
case BGPEBADRIBV2MPREACH: return "TABLE_DUMPV2 RIB record contains an illegal MP_REACH attribute";
case BGPERIBNOMPREACH: return "IPv6 RIB entry lacks MP_REACH_NLRI attribute";
case BGPEBADPEERIDX: return "Peer entry index is out of bounds";
default: return "Unknown BGP error";
}
}
static NOINLINE NORETURN void Bgp_Quit(BgpRet code, Srcloc *loc, void *obj)
{
USED(obj);
loc->call_depth++; // include Bgp_Quit() itself
if (com_progName) {
Sys_Print(STDERR, com_progName);
Sys_Print(STDERR, ": ");
}
Sys_Print(STDERR, "Terminate called in response to an unrecoverable BGP error.\n\t");
if (loc) {
#ifndef NDEBUG
if (loc->filename && loc->line > 0) {
char buf[64];
Utoa(loc->line, buf);
Sys_Print(STDERR, "Error occurred in file ");
Sys_Print(STDERR, loc->filename);
Sys_Print(STDERR, ":");
Sys_Print(STDERR, buf);
Sys_Print(STDERR, "\n\t");
}
#endif
if (loc->func) {
Sys_Print(STDERR, "[");
Sys_Print(STDERR, loc->func);
Sys_Print(STDERR, "()]: ");
}
}
Sys_Print(STDERR, Bgp_ErrorString(code));
Sys_Print(STDERR, ".\n");
exit(EXIT_FAILURE);
}
Judgement _Bgp_SetErrStat(BgpRet code,
const char *filename,
const char *func,
unsigned long long line,
unsigned depth)
{
// Don't clobber error code on BGP message access error inside filtering VM
if (code != BGPEVMMSGERR)
bgp_errs.code = code;
if (code == BGPENOERR)
return OK; // usual case
void (*err_func)(BgpRet, Srcloc *, void *);
Srcloc loc;
err_func = bgp_errs.func;
if (err_func) {
loc.filename = filename;
loc.func = func;
loc.line = line;
loc.call_depth = depth + 1;
if (err_func == BGP_ERR_QUIT)
err_func = Bgp_Quit;
err_func(code, &loc, bgp_errs.obj);
}
return NG;
}
void Bgp_SetErrFunc(void (*func)(BgpRet, Srcloc *, void *),
void *arg)
{
bgp_errs.func = func;
bgp_errs.obj = arg;
}
BgpRet Bgp_GetErrStat(BgpErrStat *stat)
{
if (stat)
*stat = bgp_errs;
return bgp_errs.code;
}
Uint16 Bgp_CheckMsgHdr(const void *data,
size_t nbytes,
Boolean allowExtendedSize)
{
if (nbytes < BGP_HDRSIZ) {
Bgp_SetErrStat(BGPETRUNCMSG);
return 0;
}
const Bgphdr *hdr = (const Bgphdr *) data;
if (memcmp(hdr->marker, bgp_marker, BGP_MARKER_LEN) != 0) {
Bgp_SetErrStat(BGPEBADMARKER);
return 0;
}
Uint16 len = beswap16(hdr->len);
if (len < BGP_HDRSIZ) {
Bgp_SetErrStat(BGPETRUNCMSG);
return 0;
}
if (len > BGP_MSGSIZ && !allowExtendedSize) {
Bgp_SetErrStat(BGPEOVRSIZ);
return 0;
}
if (len > nbytes) {
Bgp_SetErrStat(BGPETRUNCMSG);
return 0;
}
return len;
}
Judgement Bgp_MsgFromBuf(Bgpmsg *msg,
const void *data,
size_t nbytes,
unsigned flags)
{
// Immediately initialize flags (mask away superflous ones)
msg->flags = flags & (BGPF_ADDPATH|BGPF_ASN32BIT|BGPF_EXMSG|BGPF_UNOWNED);
// Check header data for correctness
Uint16 len = Bgp_CheckMsgHdr(data, nbytes, BGP_ISEXMSG(msg->flags));
if (len == 0)
return NG; // error already set by Bgp_CheckHdr()
if (BGP_ISUNOWNED(msg->flags))
msg->buf = (Uint8 *) data; // don't copy data over
else {
// Copy over relevant data
const MemOps *memOps = BGP_MEMOPS(msg);
msg->buf = (Uint8 *) memOps->Alloc(msg->allocp, len, NULL);
if (!msg->buf)
return Bgp_SetErrStat(BGPENOMEM);
memcpy(msg->buf, data, len);
}
BGP_CLRATTRTAB(msg->table);
return Bgp_SetErrStat(BGPENOERR);
}
Judgement Bgp_ReadMsg(Bgpmsg *msg,
void *streamp,
const StmOps *ops,
unsigned flags)
{
// Immediately initialize flags (mask away superflous ones)
msg->flags = flags & (BGPF_ADDPATH|BGPF_ASN32BIT|BGPF_EXMSG|BGPF_UNOWNED);
Uint8 buf[BGP_HDRSIZ];
Sint64 n = ops->Read(streamp, buf, BGP_HDRSIZ);
if (n == 0) {
// precisely at end of stream, no error, just no more BGP messages
Bgp_SetErrStat(BGPENOERR);
return NG;
}
if (n < 0)
return Bgp_SetErrStat(BGPEIO);
if ((size_t) n != BGP_HDRSIZ)
return Bgp_SetErrStat(BGPEIO);
// Retrieve memory allocator
const MemOps *memOps = BGP_MEMOPS(msg);
// Check header and allocate message
Uint16 len = Bgp_CheckMsgHdr(buf, BGP_HDRSIZ, BGP_ISEXMSG(msg->flags));
if (len == 0)
return NG;
msg->buf = (Uint8 *) memOps->Alloc(msg->allocp, len, NULL);
if (!msg->buf)
return Bgp_SetErrStat(BGPENOMEM);
// Copy over the message
memcpy(msg->buf, buf, BGP_HDRSIZ);
len -= BGP_HDRSIZ;
n = ops->Read(streamp, msg->buf + BGP_HDRSIZ, len);
if (n < 0) {
Bgp_SetErrStat(BGPEIO);
goto fail;
}
if ((size_t) n != len) {
Bgp_SetErrStat(BGPEIO);
goto fail;
}
BGP_CLRATTRTAB(msg->table);
return Bgp_SetErrStat(BGPENOERR);
fail:
memOps->Free(msg->allocp, msg->buf);
return NG;
}
#define BGP_OPEN_MINSIZ (BGP_HDRSIZ + 1uLL + 2uLL + 2uLL + 4uLL + 1uLL)
Bgpopen *Bgp_GetMsgOpen(Bgpmsg *msg)
{
Bgphdr *hdr = BGP_HDR(msg);
if (hdr->type != BGP_OPEN) {
Bgp_SetErrStat(BGPEBADTYPE);
return NULL;
}
size_t len = beswap16(hdr->len);
if (len < BGP_OPEN_MINSIZ) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgpopen *open = (Bgpopen *) hdr;
size_t nbytes = BGP_OPEN_MINSIZ + open->parmsLen;
if (nbytes != len) {
Bgp_SetErrStat((nbytes > len) ? BGPETRUNCMSG : BGPEBADOPENLEN);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return open;
}
#define BGP_UPDATE_MINSIZ (BGP_HDRSIZ + 2uLL + 2uLL)
Bgpupdate *Bgp_GetMsgUpdate(Bgpmsg *msg)
{
Bgphdr *hdr = BGP_HDR(msg);
if (hdr->type != BGP_UPDATE) {
Bgp_SetErrStat(BGPEBADTYPE);
return NULL;
}
Uint16 len = beswap16(hdr->len);
if (len < BGP_UPDATE_MINSIZ) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return (Bgpupdate *) hdr;
}
static Boolean Bgp_SwitchMpIterator(Bgpmpiter *it)
{
if (!it->nextAttr)
return FALSE; // no additional attribute to iterate, we're done
// Switch iterator
const Bgpmpfam *family = Bgp_GetMpFamily(it->nextAttr); // sets error
if (!family)
return FALSE; // corrupted attribute
size_t nbytes;
void *routes = Bgp_GetMpRoutes(it->nextAttr, &nbytes);
if (!routes)
return FALSE; // corrupted message
// Begin subsequent iteration
Afi afi = family->afi;
Safi safi = family->safi;
if (Bgp_StartPrefixes(&it->rng, afi, safi, routes, nbytes, it->rng.isAddPath) != OK)
return FALSE; // shouldn't happen
// Switch complete, clear attribute and move on
it->nextAttr = NULL;
return TRUE;
}
Judgement Bgp_StartMsgWithdrawn(Prefixiter *it, Bgpmsg *msg)
{
Bgpupdate *update = Bgp_GetMsgUpdate(msg);
if (!update)
return NG;
Bgpwithdrawnseg *withdrawn = Bgp_GetUpdateWithdrawn(update);
if (!withdrawn)
return NG;
return Bgp_StartPrefixes(it, AFI_IP, SAFI_UNICAST,
withdrawn->nlri, beswap16(withdrawn->len),
BGP_ISADDPATH(msg->flags));
}
Judgement Bgp_StartAllMsgWithdrawn(Bgpmpiter *it, Bgpmsg *msg)
{
it->nextAttr = Bgp_GetMsgAttribute(msg, BGP_ATTR_MP_UNREACH_NLRI);
if (!it->nextAttr && Bgp_GetErrStat(NULL))
return NG;
if (Bgp_StartMsgWithdrawn(&it->rng, msg) != OK)
return NG;
return OK;
}
void Bgp_InitMpWithdrawn(Bgpmpiter *it,
const Bgpwithdrawnseg *withdrawn,
const Bgpattr *mpUnreach,
Boolean isAddPath)
{
it->nextAttr = (Bgpattr *) mpUnreach;
Bgp_StartPrefixes(&it->rng,
AFI_IP, SAFI_UNICAST,
withdrawn->nlri, beswap16(withdrawn->len),
isAddPath);
}
void Bgp_InitMpNlri(Bgpmpiter *it,
const void *nlri,
size_t nbytes,
const Bgpattr *mpReach,
Boolean isAddPath)
{
it->nextAttr = (Bgpattr *) mpReach;
Bgp_StartPrefixes(&it->rng, AFI_IP, SAFI_UNICAST, nlri, nbytes, isAddPath);
}
Judgement Bgp_StartMsgNlri(Prefixiter *it, Bgpmsg *msg)
{
Bgpupdate *update = Bgp_GetMsgUpdate(msg);
if (!update)
return NG;
size_t nbytes;
void *nlri = Bgp_GetUpdateNlri(update, &nbytes);
if (!nlri)
return NG;
return Bgp_StartPrefixes(it, AFI_IP, SAFI_UNICAST,
nlri, nbytes,
BGP_ISADDPATH(msg->flags));
}
Judgement Bgp_StartAllMsgNlri(Bgpmpiter *it, Bgpmsg *msg)
{
it->nextAttr = Bgp_GetMsgAttribute(msg, BGP_ATTR_MP_REACH_NLRI);
if (!it->nextAttr && Bgp_GetErrStat(NULL))
return NG;
if (Bgp_StartMsgNlri(&it->rng, msg) != OK)
return NG;
return OK;
}
Prefix *Bgp_NextMpPrefix(Bgpmpiter *it)
{
void *rawPfx;
do {
rawPfx = Bgp_NextPrefix(&it->rng); // sets error
if (!rawPfx) {
// Swap iterator if necessary and try again
if (Bgp_GetErrStat(NULL))
return NULL;
if (!Bgp_SwitchMpIterator(it))
return NULL;
}
} while (!rawPfx);
// Extended prefix info
Prefix *cur = &it->pfx;
cur->afi = it->rng.afi;
cur->safi = it->rng.safi;
if (it->rng.isAddPath) {
// ADD-PATH enabled prefix
const ApRawPrefix *pfx = (const ApRawPrefix *) rawPfx;
cur->isAddPath = TRUE;
cur->pathId = pfx->pathId;
cur->width = pfx->width;
memcpy(cur->bytes, pfx->bytes, PFXLEN(pfx->width));
} else {
// Regular prefix
const RawPrefix *pfx = (const RawPrefix *) rawPfx;
cur->isAddPath = FALSE;
cur->pathId = 0;
cur->width = pfx->width;
memcpy(cur->bytes, pfx->bytes, PFXLEN(pfx->width));
}
return cur;
}
#define BGP_NOTIFICATION_MINSIZ (BGP_HDRSIZ + 1uLL + 1uLL)
Bgpnotification *Bgp_GetNotification(Bgpmsg *msg)
{
Bgphdr *hdr = BGP_HDR(msg);
if (hdr->type != BGP_NOTIFICATION) {
Bgp_SetErrStat(BGPEBADTYPE);
return NULL;
}
Uint16 len = beswap16(hdr->len);
if (len < BGP_NOTIFICATION_MINSIZ) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return (Bgpnotification *) hdr;
}
Bgpparmseg *Bgp_GetParmsFromMemory(const void *data, size_t size)
{
const size_t BGP_OPEN_PARMSOFF = BGP_OPEN_MINSIZ - BGP_HDRSIZ;
if (size < BGP_OPEN_PARMSOFF) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgpparmseg *parms = (Bgpparmseg *) ((Uint8 *) data + BGP_OPEN_PARMSOFF - 1);
size_t nbytes = BGP_OPEN_PARMSOFF + parms->len;
if (nbytes != size) {
Bgp_SetErrStat((nbytes > size) ? BGPETRUNCMSG : BGPEBADOPENLEN);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return parms;
}
Bgpparmseg *Bgp_GetOpenParms(const Bgpopen *open)
{
assert(open->hdr.type == BGP_OPEN);
size_t len = beswap16(open->hdr.len) - BGP_HDRSIZ;
return Bgp_GetParmsFromMemory(&open->version, len);
}
Bgpwithdrawnseg *Bgp_GetWithdrawnFromMemory(const void *data, size_t size)
{
if (size < 2uLL) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgpwithdrawnseg *withdrawn = (Bgpwithdrawnseg *) data;
size -= 2;
if (size < beswap16(withdrawn->len) + 2uLL) { // also accounts for TPA length
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return withdrawn;
}
Bgpwithdrawnseg *Bgp_GetUpdateWithdrawn(const Bgpupdate *msg)
{
assert(msg->hdr.type == BGP_UPDATE);
size_t len = beswap16(msg->hdr.len);
return Bgp_GetWithdrawnFromMemory(msg->data, len - BGP_HDRSIZ);
}
Bgpattrseg *Bgp_GetAttributesFromMemory(const void *data, size_t size)
{
Bgpwithdrawnseg *withdrawn = Bgp_GetWithdrawnFromMemory(data, size);
if (!withdrawn)
return NULL; // sets error
size_t withdrawnLen = beswap16(withdrawn->len);
Bgpattrseg *tpa = (Bgpattrseg *) &withdrawn->nlri[withdrawnLen];
size_t tpaLen = beswap16(tpa->len);
if (size < 2uLL + withdrawnLen + 2uLL + tpaLen) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
return tpa; // error already cleared
}
Bgpattrseg *Bgp_GetUpdateAttributes(const Bgpupdate *msg)
{
assert(msg->hdr.type == BGP_UPDATE);
size_t len = beswap16(msg->hdr.len);
return Bgp_GetAttributesFromMemory(msg->data, len - BGP_HDRSIZ);
}
void *Bgp_GetNlriFromMemory(const void *nlri, size_t size, size_t *nbytes)
{
Bgpattrseg *tpa = Bgp_GetAttributesFromMemory(nlri, size);
if (!tpa)
return NULL; // error already set
size_t tpaLen = beswap16(tpa->len);
if (nbytes) {
size_t offset = &tpa->attrs[tpaLen] - (Uint8 *) nlri;
*nbytes = size - offset;
}
return &tpa->attrs[tpaLen];
}
void *Bgp_GetUpdateNlri(const Bgpupdate *msg, size_t *nbytes)
{
assert(msg->hdr.type == BGP_UPDATE);
size_t len = beswap16(msg->hdr.len);
return Bgp_GetNlriFromMemory(msg->data, len - BGP_HDRSIZ, nbytes);
}
void Bgp_ClearMsg(Bgpmsg *msg)
{
if (!BGP_ISUNOWNED(msg->flags))
BGP_MEMOPS(msg)->Free(msg->allocp, msg->buf);
msg->flags = 0;
msg->buf = NULL;
}

@ -0,0 +1,45 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/bgp_local.h
*
* Private BGP library header.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGP_LOCAL_H_
#define DF_BGP_LOCAL_H_
#include "bgp/mrt.h"
// Low level prefix operations
void Bgp_InitMpWithdrawn(Bgpmpiter *it, const Bgpwithdrawnseg *withdrawn, const Bgpattr *mpUnreach, Boolean isAddPath);
void Bgp_InitMpNlri(Bgpmpiter *it, const void *data, size_t nbytes, const Bgpattr *mpReach, Boolean isAddPath);
// Low level BGP operations
Uint16 Bgp_CheckMsgHdr(const void *data, size_t nbytes, Boolean allowExtendedSize);
Bgpparmseg *Bgp_GetParmsFromMemory(const void *data, size_t size);
Bgpwithdrawnseg *Bgp_GetWithdrawnFromMemory(const void *data, size_t size);
Bgpattrseg *Bgp_GetAttributesFromMemory(const void *data, size_t size);
void *Bgp_GetNlriFromMemory(const void *nlri, size_t size, size_t *nbytes);
// Extension in attribute.c special iteration on attributes
/// Non-caching variant of `Bgp_NextAttribute()`, doesn't update `it->table`.
Bgpattr *Bgp_NcNextAttribute(Bgpattriter *it);
#define Bgp_SetErrStat(code) \
_Bgp_SetErrStat(code, __FILE__, __func__, __LINE__, 0)
NOINLINE Judgement _Bgp_SetErrStat(BgpRet code,
const char *filename,
const char *func,
unsigned long long line,
unsigned depth);
#endif

@ -0,0 +1,177 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/bytebuf.c
*
* Trivial BGP memory allocator for basic BGP workloads.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bytebuf.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
STATIC_ASSERT(BGP_MEMBUF_ALIGN >= 4, "bytebuf.c assumes Uint32 header");
#define USEDBIT BIT(0)
#define MAXBUFCHUNKSIZ 0xffffffffuLL
#define BLKSIZ(ptr) ((*(Uint32 *) (ptr)) & ~USEDBIT)
#define ISUSED(ptr) (((*(Uint32 *) (ptr)) & USEDBIT) != 0)
#define SETUSED(ptr) ((void) ((*(Uint32 *) (ptr)) |= USEDBIT))
#define CLRUSED(ptr) ((void) ((*(Uint32 *) (ptr)) &= ~USEDBIT))
static Boolean Mem_IsInBuffer(Bgpbytebuf *buf, void *ptr)
{
return (Uint8 *) ptr >= buf->base &&
(Uint8 *) ptr < buf->base + buf->size;
}
static Uint8 *Mem_FindPrevChunk(Bgpbytebuf *buf, void *chunk)
{
assert(Mem_IsInBuffer(buf, chunk));
Uint8 *p = buf->base;
while (p < (Uint8 *) chunk) {
size_t siz = BLKSIZ(p);
if (p + siz == (Uint8 *) chunk)
return p;
p += siz;
}
return NULL;
}
static void Mem_BgpFree(void *allocator, void *ptr)
{
Bgpbytebuf *buf = (Bgpbytebuf *) allocator;
// Regular free() for out of buffer allocations
if (!Mem_IsInBuffer(buf, ptr)) {
free(ptr);
return;
}
// Get pointer to chunk
Uint8 *p = (Uint8 *) ptr - 4;
assert(ISUSED(p));
// Get buffer limit
Uint8 *lim = buf->base + buf->pos;
Uint32 siz = BLKSIZ(p);
CLRUSED(p); // toggle off USEDBIT
// Find successor if any
Uint8 *next = p + siz;
if (next < lim && !ISUSED(next)) {
// Merge forward
siz += BLKSIZ(next);
*(Uint32 *) p = siz;
}
// Find predecessor, if any
Uint8 *prev = Mem_FindPrevChunk(buf, p);
if (prev && !ISUSED(prev)) {
// Merge backwards
siz += BLKSIZ(prev);
p = prev;
*(Uint32 *) p = siz;
}
// Move position backwards when freeing last block
if (p + siz == lim)
buf->pos -= siz;
}
static void *Mem_BgpDoRealloc(Bgpbytebuf *buf, void *oldp, size_t nbytes)
{
// Use plain realloc() if we're not managing a buffered pointer
if (!Mem_IsInBuffer(buf, oldp))
return realloc(oldp, nbytes);
assert(IS_PTR_ALIGNED(oldp, BGP_MEMBUF_ALIGN));
Uint8 *ptr = (Uint8 *) oldp - 4;
assert(ISUSED(ptr));
Uint32 oldSiz = BLKSIZ(ptr);
assert(buf->pos >= oldSiz);
size_t siz = 4 + ALIGN(nbytes, BGP_MEMBUF_ALIGN);
if (oldSiz >= siz) {
// Shrink operation, free up the trailing part if we're the last chunk
if (ptr + oldSiz == buf->base + buf->pos) {
*(Uint32 *) ptr = siz | USEDBIT;
buf->pos -= (oldSiz - siz);
}
return oldp;
}
// May only grow a chunk if this is the last one and we don't overflow
if (ptr + oldSiz != buf->base + buf->pos)
return NULL;
size_t newPos = buf->pos + (siz - oldSiz);
if (newPos > buf->size)
return NULL;
// Ok to grow the chunk
*(Uint32 *) ptr = siz | USEDBIT;
buf->pos = newPos;
return oldp;
}
static void *Mem_BgpDoAlloc(Bgpbytebuf *buf, size_t nbytes)
{
// Use plain malloc() for large allocations or when out of buffer space
size_t siz = 4 + ALIGN(nbytes, BGP_MEMBUF_ALIGN);
if (buf->pos + siz > buf->size || siz > MAXBUFCHUNKSIZ)
return malloc(nbytes);
// Return the next chunk available
Uint32 *ptr = (Uint32 *) (buf->base + buf->pos);
buf->pos += siz;
assert((siz & USEDBIT) == 0);
*ptr++ = siz | USEDBIT;
return ptr;
}
static void *Mem_BgpAlloc(void *allocator, size_t nbytes, void *oldp)
{
Bgpbytebuf *buf = (Bgpbytebuf *) allocator;
// Handle common allocations with no `oldp`
if (!oldp)
return Mem_BgpDoAlloc(buf, nbytes);
// Attempt memory reuse
void *ptr = Mem_BgpDoRealloc(buf, oldp, nbytes);
if (ptr)
return ptr;
// Fallback to simple allocation+memcpy()
ptr = Mem_BgpDoAlloc(buf, nbytes);
if (ptr) {
memcpy(ptr, oldp, nbytes);
Mem_BgpFree(buf, oldp);
}
return ptr;
}
static const MemOps mem_bgpBufTable = {
Mem_BgpAlloc,
Mem_BgpFree
};
const MemOps *const Mem_BgpBufOps = &mem_bgpBufTable;

@ -0,0 +1,82 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/dump.c
*
* General BGP dump functions wrappers.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "bgp/dump.h"
#include "sys/endian.h"
#define CALLFMT(fn, ...) \
((fn) ? (fn(__VA_ARGS__)) : ((Sint64) Bgp_SetErrStat(BGPENOERR)))
Sint64 Bgp_DumpMrtUpdate(const Mrthdr *hdr,
void *streamp, const StmOps *ops,
const BgpDumpfmt *fmt)
{
Bgpattrtab table;
if (!ops->Write) {
Bgp_SetErrStat(BGPENOERR);
return 0;
}
BGP_CLRATTRTAB(table);
if (MRT_ISBGP4MP(hdr->type)) {
return CALLFMT(fmt->DumpBgp4mp, hdr, streamp, ops, table);
} else if (hdr->type == MRT_BGP) {
return CALLFMT(fmt->DumpZebra, hdr, streamp, ops, table);
} else {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return -1;
}
}
Sint64 Bgp_DumpMrtRibv2(const Mrthdr *hdr,
const Mrtpeerentv2 *peer, const Mrtribentv2 *ent,
void *streamp, const StmOps *ops,
const BgpDumpfmt *fmt)
{
Bgpattrtab table;
if (!ops->Write) {
Bgp_SetErrStat(BGPENOERR);
return 0;
}
if (hdr->type != MRT_TABLE_DUMPV2 || !TABLE_DUMPV2_ISRIB(hdr->subtype)) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return -1;
}
BGP_CLRATTRTAB(table);
return CALLFMT(fmt->DumpRibv2, hdr, peer, ent, streamp, ops, table);
}
Sint64 Bgp_DumpMrtRib(const Mrthdr *hdr,
const Mrtribent *ent,
void *streamp, const StmOps *ops,
const BgpDumpfmt *fmt)
{
Bgpattrtab table;
if (!ops->Write) {
Bgp_SetErrStat(BGPENOERR);
return 0;
}
if (hdr->type != MRT_TABLE_DUMP) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return -1;
}
BGP_CLRATTRTAB(table);
return CALLFMT(fmt->DumpRib, hdr, ent, streamp, ops, table);
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,751 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/mrt.c
*
* Deals with Multi-Threaded Routing Toolkit (MRT) format.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "sys/endian.h"
#include "sys/interlocked.h"
#include <assert.h>
#include <string.h>
static void *MRT_DATAPTR(const Mrtrecord *rec)
{
return rec->buf + MRT_HDRSIZ + (MRT_ISEXHDRTYPE(MRT_HDR(rec)->type) << 2);
}
Judgement Bgp_MrtFromBuf(Mrtrecord *rec, const void *buf, size_t nbytes)
{
if (nbytes < MRT_HDRSIZ)
return Bgp_SetErrStat(BGPETRUNCMRT);
const Mrthdr *hdr = (const Mrthdr *) buf;
size_t left = beswap32(hdr->len);
if (MRT_ISEXHDRTYPE(hdr->type))
left += 4; // account for extended timestamp
size_t siz = sizeof(*hdr) + left;
if (siz > nbytes)
return Bgp_SetErrStat(BGPETRUNCMRT);
const MemOps *memOps = MRT_MEMOPS(rec);
rec->buf = (Uint8 *) memOps->Alloc(rec->allocp, siz, NULL);
if (!rec->buf)
return Bgp_SetErrStat(BGPENOMEM);
rec->peerOffTab = NULL;
memcpy(rec->buf, buf, siz);
return Bgp_SetErrStat(BGPENOERR);
}
Judgement Bgp_ReadMrt(Mrtrecord *rec, void *streamp, const StmOps *ops)
{
Mrthdr hdr;
// Read header
Sint64 n = ops->Read(streamp, &hdr, sizeof(hdr));
if (n == 0) {
// Precisely at end of file, no error, just no more records
Bgp_SetErrStat(BGPENOERR);
return NG;
}
if (n < 0)
return Bgp_SetErrStat(BGPEIO);
if ((size_t) n != sizeof(hdr))
return Bgp_SetErrStat(BGPEIO);
size_t left = beswap32(hdr.len);
if (MRT_ISEXHDRTYPE(hdr.type))
left += 4; // account for extended timestamp
// Allocate buffer
// NOTE: MRT header length doesn't account for header size itself
size_t siz = sizeof(hdr) + left;
const MemOps *memOps = MRT_MEMOPS(rec);
rec->buf = (Uint8 *) memOps->Alloc(rec->allocp, siz, NULL);
if (!rec->buf)
return Bgp_SetErrStat(BGPENOMEM);
// Populate buffer
memcpy(rec->buf, &hdr, sizeof(hdr));
n = ops->Read(streamp, rec->buf + sizeof(hdr), left);
if (n < 0) {
Bgp_SetErrStat(BGPEIO);
goto fail;
}
if ((size_t) n != left) {
Bgp_SetErrStat(BGPEIO);
goto fail;
}
rec->peerOffTab = NULL;
return Bgp_SetErrStat(BGPENOERR);
fail:
memOps->Free(rec->allocp, rec->buf);
return NG;
}
#define MRT_PEERIDX_MINSIZ (4 + 2 + 2)
Mrtpeeridx *Bgp_GetMrtPeerIndex(Mrtrecord *rec)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_TABLE_DUMPV2 || hdr->subtype != TABLE_DUMPV2_PEER_INDEX_TABLE) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
// Basic size check (only for fixed size portion)
size_t len = beswap32(hdr->len);
size_t siz = MRT_PEERIDX_MINSIZ;
if (len < siz) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
// NOTE: PEER_INDEX_TABLE cannot have extended timestamp
assert(!MRT_ISEXHDRTYPE(hdr->subtype));
Mrtpeeridx *peerIdx = (Mrtpeeridx *) (hdr + 1);
// View Name field size check
siz += beswap16(peerIdx->viewNameLen);
if (len < siz) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return peerIdx;
}
void *Bgp_GetMrtPeerIndexPeers(Mrtrecord *rec, size_t *peersCount, size_t *nbytes)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_TABLE_DUMPV2 || hdr->subtype != TABLE_DUMPV2_PEER_INDEX_TABLE) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
// Basic size check
size_t len = beswap32(hdr->len);
size_t off = 4; // BGP Identifier
if (len < off + 2) { // view name length
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
// NOTE: PEER_INDEX_TABLE cannot have extended timestamp
assert(!MRT_ISEXHDRTYPE(hdr->subtype));
Mrtpeeridx *peerIdx = (Mrtpeeridx *) (hdr + 1);
// View Name size check
off += 2 + beswap16(peerIdx->viewNameLen); // skip view name
if (len < off + 2) { // entry count
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
// Calculate relevant sizes and return peers chunk
if (peersCount) {
Uint16 count;
memcpy(&count, (Uint8 *) peerIdx + off, sizeof(count));
*peersCount = beswap16(count);
}
off += 2; // skip peers count
if (nbytes)
*nbytes = len - off;
return (Uint8 *) peerIdx + off;
}
static size_t MRT_PEERENTSIZ(const Mrtpeerentv2 *ent)
{
size_t len = 1 + 4;
len += MRT_ISPEERASN32BIT(ent->type) ? 4 : 2;
len += MRT_ISPEERIPV6(ent->type) ? IPV6_SIZE : IPV4_SIZE;
return len;
}
static Mrtpeertabv2 *Bgp_GetPeerOffsetTable(Mrtrecord *rec)
{
Mrtpeertabv2 *tab;
while (TRUE) {
tab = (Mrtpeertabv2 *) Smp_AtomicLoadPtrAcq(&rec->peerOffTab);
if (tab)
break; // already allocated
// Must allocate the table anew
size_t peerCount;
if (!Bgp_GetMrtPeerIndexPeers(rec, &peerCount, /*nbytes=*/NULL))
return NULL; // bad record type or corrupted PEER_INDEX_TABLE
const MemOps *memOps = MRT_MEMOPS(rec);
tab = (Mrtpeertabv2 *) memOps->Alloc(rec->allocp, offsetof(Mrtpeertabv2, offsets[peerCount]), NULL);
if (!tab) {
Bgp_SetErrStat(BGPENOMEM);
return NULL;
}
Smp_AtomicStore16Rx(&tab->validCount, 0);
Smp_AtomicStore16Rx(&tab->peerCount, peerCount);
if (Smp_AtomicCasPtrRel(&rec->peerOffTab, NULL, tab))
break; // all good
memOps->Free(rec->allocp, tab); // ...somebody just allocated the table for us
}
return tab;
}
Mrtpeerentv2 *Bgp_GetMrtPeerByIndex(Mrtrecord *rec, Uint16 idx)
{
// NOTE: no extended timestamp TABLE_DUMPV2 exists, so we can simplify
// record access
Mrtpeertabv2 *tab = Bgp_GetPeerOffsetTable(rec);
if (!tab)
return NULL;
// If we have a `Mrtpeertabv2` we're positively sure that `Mrtpeeridx`
// is well formed.
const Mrthdr *hdr = MRT_HDR(rec);
const Mrtpeeridx *peerIdx = (const Mrtpeeridx *) (hdr + 1);
size_t baseOff = MRT_HDRSIZ + MRT_PEERIDX_MINSIZ + beswap16(peerIdx->viewNameLen);
// IMPORTANT INVARIANT: `tab->validCount` may change, but will only ever be
// incremented.
Uint16 validCount = Smp_AtomicLoad16Acq(&tab->validCount);
if (idx < validCount) {
// FAST PATH: Offset was cached
Uint32 off = Smp_AtomicLoad32Rx(&tab->offsets[idx]);
Bgp_SetErrStat(BGPENOERR);
return (Mrtpeerentv2 *) (rec->buf + baseOff + off);
}
// SLOW PATH: Must scan PEER_INDEX_TABLE and update offsets
// Check that a valid peer was actually requested
Uint16 peerCount = Smp_AtomicLoad16Rx(&tab->peerCount);
if (idx >= peerCount) {
Bgp_SetErrStat(BGPEBADPEERIDX);
return NULL;
}
/* NOTE: We cheat a bit:
* - if we have a `peerOffTab`, then we know PEER_INDEX_TABLE header is
* well formed, so we can build a `Mrtpeeriterv2` confidently
* without checking data integrity;
* - we know the data was well formed at least up to the last valid
* peer entry, so we can resume iteration there;
*/
Mrtpeeriterv2 it;
Mrtpeerentv2 *ent;
Uint32 lastOff;
// Initialize iterator to last known offset
if (validCount == 0)
lastOff = 0;
else {
lastOff = Smp_AtomicLoad32Rx(&tab->offsets[validCount - 1]);
ent = (Mrtpeerentv2 *) (rec->buf + baseOff + lastOff);
lastOff += MRT_PEERENTSIZ(ent);
}
it.base = rec->buf + baseOff;
it.lim = rec->buf + MRT_HDRSIZ + beswap32(hdr->len);
it.ptr = it.base + lastOff;
it.peerCount = peerCount;
it.nextIdx = validCount;
// Keep iterating to find the new entry, update table in the process
/* NOTE: We don't care if we concurrently write offsets to the table
* while some other thread also updates that, we know we'll be writing
* the same offsets in the same slots there.
*/
Uint16 newValidCount = validCount;
do {
ent = Bgp_NextMrtPeerv2(&it);
if (!ent)
return NULL; // error status already set by iterator
Uint32 off = (Uint8 *) ent - it.base;
Smp_AtomicStore32Rx(&tab->offsets[newValidCount], off);
newValidCount++;
} while (idx >= newValidCount);
// Signal what we've done to the world, don't update anything
// if somebody else changed the table under our feet.
Smp_AtomicCas16Rel(&tab->validCount, validCount, newValidCount);
return ent; // success status already set by iterator
}
Judgement Bgp_StartMrtPeersv2(Mrtpeeriterv2 *it, Mrtrecord *rec)
{
size_t peerCount, nbytes;
void *peers = (Uint8 *) Bgp_GetMrtPeerIndexPeers(rec, &peerCount, &nbytes);
if (!peers)
return NG;
it->base = (Uint8 *) peers;
it->lim = it->base + nbytes;
it->ptr = it->base;
it->peerCount = peerCount;
it->nextIdx = 0;
return OK; // success already set
}
Mrtpeerentv2 *Bgp_NextMrtPeerv2(Mrtpeeriterv2 *it)
{
if (it->ptr >= it->lim) {
// End of iteration, check for correct peer count
if (it->nextIdx == it->peerCount)
Bgp_SetErrStat(BGPENOERR);
else
Bgp_SetErrStat(BGPEBADPEERIDXCNT);
return NULL;
}
size_t left = it->lim - it->ptr;
assert(left > 0);
Mrtpeerentv2 *ent = (Mrtpeerentv2 *) it->ptr;
size_t len = MRT_PEERENTSIZ(ent);
if (left < len) {
Bgp_SetErrStat(BGPETRUNCPEERV2);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
it->ptr += len;
it->nextIdx++;
return ent;
}
Mrtribhdrv2 *Bgp_GetMrtRibHdrv2(Mrtrecord *rec, size_t *nbytes)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_TABLE_DUMPV2) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
Mrtribhdrv2 *rib = RIBV2_HDR(hdr);
size_t len = beswap32(hdr->len);
size_t offset = 0;
offset += 4; // sequence number
size_t maxPfxWidth;
if (TABLE_DUMPV2_ISGENERICRIB(hdr->subtype)) {
offset += 2 + 1; // AFI, SAFI
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
switch (rib->gen.afi) {
case AFI_IP: maxPfxWidth = IPV4_WIDTH; break;
case AFI_IP6: maxPfxWidth = IPV6_WIDTH; break;
default:
Bgp_SetErrStat(BGPEAFIUNSUP);
return NULL;
}
if (rib->gen.safi != SAFI_UNICAST && rib->gen.safi != SAFI_MULTICAST) {
Bgp_SetErrStat(BGPESAFIUNSUP);
return NULL;
}
} else if (TABLE_DUMPV2_ISIPV4RIB(hdr->subtype)) {
maxPfxWidth = IPV4_WIDTH;
} else if (TABLE_DUMPV2_ISIPV6RIB(hdr->subtype)) {
maxPfxWidth = IPV6_WIDTH;
} else {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
const RawPrefix *pfx = (const RawPrefix *) ((Uint8 *) rib + offset);
offset++; // prefix width
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
if (pfx->width > maxPfxWidth) {
Bgp_SetErrStat(BGPEBADPFXWIDTH);
return NULL;
}
offset += PFXLEN(pfx->width);
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
if (nbytes)
*nbytes = offset;
return rib;
}
Mrtribentriesv2 *Bgp_GetMrtRibEntriesv2(Mrtrecord *rec, size_t *nbytes)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_TABLE_DUMPV2) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
if (!TABLE_DUMPV2_ISRIB(hdr->subtype)) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
size_t len = beswap32(hdr->len);
size_t offset;
Mrtribhdrv2 *rib = Bgp_GetMrtRibHdrv2(rec, &offset);
if (!rib)
return NULL; // error already set
Mrtribentriesv2 *ents = (Mrtribentriesv2 *) ((Uint8 *) rib + offset);
offset += 2; // entries count
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
if (nbytes)
*nbytes = len - offset;
return ents;
}
Judgement Bgp_StartMrtRibEntriesv2(Mrtribiterv2 *it, Mrtrecord *rec)
{
size_t nbytes;
Mrtribentriesv2 *ents = Bgp_GetMrtRibEntriesv2(rec, &nbytes);
if (!ents)
return NG;
it->base = ents->entries;
it->lim = it->base + nbytes;
it->ptr = it->base;
it->isAddPath = TABLE_DUMPV2_ISADDPATHRIB(MRT_HDR(rec)->subtype);
it->entryCount = beswap16(ents->entryCount);
it->nextIdx = 0;
return OK;
}
Mrtribentv2 *Bgp_NextRibEntryv2(Mrtribiterv2 *it)
{
if (it->ptr >= it->lim) {
if (it->nextIdx == it->entryCount)
Bgp_SetErrStat(BGPENOERR);
else
Bgp_SetErrStat(BGPEBADRIBV2CNT);
return NULL;
}
size_t left = it->lim - it->ptr;
assert(left > 0);
Mrtribentv2 *ent = (Mrtribentv2 *) it->ptr;
size_t offset = 2 + 4; // peer index, originated time
if (it->isAddPath)
offset += 4; // path id
if (left < offset + 2) { // attributes length
Bgp_SetErrStat(BGPETRUNCRIBV2);
return NULL;
}
Bgpattrseg *tpa = (Bgpattrseg *) ((Uint8 *) ent + offset);
offset += 2 + beswap16(tpa->len);
if (left < offset) {
Bgp_SetErrStat(BGPETRUNCRIBV2);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
it->ptr += offset;
it->nextIdx++;
return ent;
}
Bgp4mphdr *Bgp_GetBgp4mpHdr(Mrtrecord *rec, size_t *nbytes)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (!MRT_ISBGP4MP(hdr->type)) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
size_t len = beswap32(hdr->len);
size_t offset = 0;
Afi afi;
Bgp4mphdr *bgp4mp = (Bgp4mphdr *) MRT_DATAPTR(rec);
if (BGP4MP_ISASN32BIT(hdr->subtype)) {
offset += 2 * 4;
offset += 2 + 2;
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
afi = bgp4mp->a32.afi;
} else {
offset += 2 * 2;
offset += 2 + 2;
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
afi = bgp4mp->a16.afi;
}
switch (afi) {
case AFI_IP: offset += 2 * IPV4_SIZE; break;
case AFI_IP6: offset += 2 * IPV6_SIZE; break;
default:
Bgp_SetErrStat(BGPEAFIUNSUP);
return NULL;
}
if (BGP4MP_ISSTATECHANGE(hdr->subtype))
offset += 2 * 2;
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
if (nbytes)
*nbytes = offset;
return bgp4mp;
}
Judgement Bgp_UnwrapBgp4mp(Mrtrecord *rec, Bgpmsg *dest, unsigned flags)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (!MRT_ISBGP4MP(hdr->type))
return Bgp_SetErrStat(BGPEBADMRTTYPE);
if (!BGP4MP_ISMESSAGE(hdr->subtype))
return Bgp_SetErrStat(BGPEBADMRTTYPE);
Uint32 len = beswap32(hdr->len);
Uint8 *base = (Uint8 *) MRT_DATAPTR(rec);
// Skip header
size_t siz = BGP4MP_ISASN32BIT(hdr->subtype) ? 2*4 : 2*2; // skip ASN
siz += 2; // skip interface index
Afi afi;
if (len < siz + sizeof(afi))
return Bgp_SetErrStat(BGPETRUNCMRT);
// Skip AFI and addresses
memcpy(&afi, base + siz, sizeof(afi));
siz += sizeof(afi);
switch (afi) {
case AFI_IP:
siz += 2 * sizeof(Ipv4adr);
break;
case AFI_IP6:
siz += 2 * sizeof(Ipv6adr);
break;
default:
return Bgp_SetErrStat(BGPEAFIUNSUP);
}
if (len < siz)
return Bgp_SetErrStat(BGPETRUNCMRT);
size_t msgsiz = len - siz;
void *buf = base + siz;
// Mask away ignored flags
flags &= ~(BGPF_ADDPATH|BGPF_ASN32BIT);
// ...and automatically reset them as defined by BGP4MP subtype
if (BGP4MP_ISASN32BIT(hdr->subtype))
flags |= BGPF_ASN32BIT;
if (BGP4MP_ISADDPATH(hdr->subtype))
flags |= BGPF_ADDPATH;
// Unwrap BGP message
return Bgp_MsgFromBuf(dest, buf, msgsiz, flags);
}
#define TABLE_DUMP_MINSIZ (2 + 2 /*+ PFX*/ + 1 + 1 + 4 /*+ IP*/ + 2 + 2)
Mrtribent *Bgp_GetMrtRibHdr(Mrtrecord *rec)
{
Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_TABLE_DUMP) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
size_t len = beswap32(hdr->len);
size_t siz = TABLE_DUMP_MINSIZ - 2; // do not include attribute length
switch (hdr->subtype) {
case AFI_IP: siz += 2 * IPV4_SIZE; break;
case AFI_IP6: siz += 2 * IPV6_SIZE; break;
default:
Bgp_SetErrStat(BGPEAFIUNSUP);
return NULL;
}
if (len < siz + 2) { // include attribute length in size check
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
// NOTE: TABLE_DUMP has no extended timestamp variant
Mrtribent *ent = (Mrtribent *) (hdr + 1);
Uint16 attrLen;
memcpy(&attrLen, (Uint8 *) ent + siz, sizeof(attrLen));
siz += 2; // now include offset
siz += beswap16(attrLen);
if (len < siz) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return (Mrtribent *) (hdr + 1);
}
Zebrahdr *Bgp_GetZebraHdr(Mrtrecord *rec, size_t *nbytes)
{
Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_BGP) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
size_t len = beswap32(hdr->len);
size_t offset = 2 + IPV4_SIZE;
if (ZEBRA_ISMESSAGE(hdr->subtype))
offset += 2 + IPV4_SIZE;
else if (hdr->subtype == ZEBRA_STATE_CHANGE)
offset += 2 * 2;
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
if (nbytes)
*nbytes = offset;
// NOTE: Legacy ZEBRA type doesn't have extended timestamp variants
return (Zebrahdr *) (hdr + 1);
}
#define ZEBRA_MSGSIZ (2uLL + IPV4_SIZE + 2uLL + IPV4_SIZE)
Judgement Bgp_UnwrapZebra(Mrtrecord *rec, Bgpmsg *dest, unsigned flags)
{
Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_BGP)
return Bgp_SetErrStat(BGPEBADMRTTYPE);
// Resolve BGP type from ZEBRA subtype
BgpType type;
switch (hdr->subtype) {
case ZEBRA_UPDATE: type = BGP_UPDATE; break;
case ZEBRA_OPEN: type = BGP_OPEN; break;
case ZEBRA_KEEPALIVE: type = BGP_KEEPALIVE; break;
case ZEBRA_NOTIFY: type = BGP_NOTIFICATION; break;
default: return Bgp_SetErrStat(BGPEBADMRTTYPE);
}
Zebramsghdr *zebra = (Zebramsghdr *) (hdr + 1); // NOTE: ZEBRA doesn't have extended timestamp variants
size_t len = beswap32(hdr->len);
if (len < ZEBRA_MSGSIZ)
return Bgp_SetErrStat(BGPETRUNCMRT);
// ZEBRA dumps don't include BGP message header
size_t zebralen = len - ZEBRA_MSGSIZ;
size_t msglen = BGP_HDRSIZ + zebralen;
// Validate message size
if (msglen > BGP_EXMSGSIZ || (msglen > BGP_MSGSIZ && !BGP_ISEXMSG(flags)))
return Bgp_SetErrStat(BGPEOVRSIZ);
// Allocate new message
const MemOps *ops = BGP_MEMOPS(dest);
Bgphdr *msg = (Bgphdr *) ops->Alloc(dest->allocp, msglen, NULL);
if (!msg)
return Bgp_SetErrStat(BGPENOMEM);
// Build a valid BGP header
memset(msg->marker, 0xff, sizeof(msg->marker));
msg->len = beswap16(msglen);
msg->type = type;
memcpy(msg + 1, zebra->msg, zebralen);
// Populate `dest`
dest->buf = (Uint8 *) msg;
dest->flags = flags & BGPF_EXMSG; // only acceptable flag
BGP_CLRATTRTAB(dest->table);
return Bgp_SetErrStat(BGPENOERR);
}
void Bgp_ClearMrt(Mrtrecord *rec)
{
const MemOps *memOps = MRT_MEMOPS(rec);
memOps->Free(rec->allocp, rec->buf);
memOps->Free(rec->allocp, rec->peerOffTab);
rec->buf = NULL;
rec->peerOffTab = NULL;
}

@ -0,0 +1,100 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/parameters.c
*
* Deals with BGP OPEN parameters.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
Judgement Bgp_StartMsgParms(Bgpparmiter *it, Bgpmsg *msg)
{
const Bgpopen *open = Bgp_GetMsgOpen(msg);
if (!open)
return NG; // error already set
Bgp_StartParms(it, BGP_OPENPARMS(open));
return OK; // error already cleared
}
void Bgp_StartParms(Bgpparmiter *it, const Bgpparmseg *p)
{
it->ptr = (Uint8 *) p->parms;
it->base = it->ptr;
it->lim = it->base + p->len;
}
Bgpparm *Bgp_NextParm(Bgpparmiter *it)
{
if (it->ptr >= it->lim) {
Bgp_SetErrStat(BGPENOERR);
return NULL;
}
Bgpparm *p = (Bgpparm *) it->ptr;
size_t left = it->lim - it->ptr;
if (left < 2uLL || left < 2uLL + p->len) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
it->ptr += 2 + p->len;
Bgp_SetErrStat(BGPENOERR);
return p;
}
Judgement Bgp_StartMsgCaps(Bgpcapiter *it, Bgpmsg *msg)
{
if (Bgp_StartMsgParms(&it->pi, msg) != OK)
return NG; // error already set
// Set starting point so Bgp_NextCap() scans for next parameter
it->base = it->lim = it->ptr = it->pi.ptr;
return OK;
}
void Bgp_StartCaps(Bgpcapiter *it, const Bgpparmseg *parms)
{
Bgp_StartParms(&it->pi, parms);
// Set starting point so Bgp_NextCap() scans for next parameter
it->base = it->lim = it->ptr = it->pi.ptr;
}
Bgpcap *Bgp_NextCap(Bgpcapiter *it)
{
if (it->ptr >= it->lim) {
// Try to find another CAPABILITY parameter
Bgpparm *p;
while ((p = Bgp_NextParm(&it->pi)) != NULL) {
if (p->code == BGP_PARM_CAPABILITY)
break;
}
if (!p) {
// Scanned all parameters, nothing found
Bgp_SetErrStat(BGPENOERR);
return NULL;
}
// Setup for reading new capabilities from `p`
it->base = (Uint8 *) p + 2;
it->lim = it->base + p->len;
it->ptr = it->base;
}
// Return current capability and move over
size_t left = it->lim - it->ptr;
Bgpcap *cap = (Bgpcap *) it->ptr;
if (left < 2uLL || left < 2uLL + cap->len) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
it->ptr += 2 + cap->len;
Bgp_SetErrStat(BGPENOERR);
return cap;
}

@ -0,0 +1,518 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/patricia.c
*
* Implements PATRICIA trie utilities.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/patricia.h"
#include "sys/sys.h" // for Sys_OutOfMemory()
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define PAT_BLOCK_ALIGN_SHIFT 2
#define PAT_BLOCK_ALIGN (1uLL << PAT_BLOCK_ALIGN_SHIFT)
#define PAT_BLOCK_SIZE 2048
struct Patblock {
Patblock *nextBlock;
Uint32 freeOfs;
ALIGNED(PAT_BLOCK_ALIGN, Uint8 buf[PAT_BLOCK_SIZE]);
};
/**
* \brief Trie node, contains node and prefix info.
*
* A node may be either:
* - Used - node is in use and part of the trie.
* - Unused - node is inside free node list and may be reclaimed on further
* node insertion.
*/
union Patnode {
// Following struct is significant when node is used.
struct {
Patnode *parent; // NOTE: LSB used to mark glue nodes
Patnode *children[2];
// Prefix part follows...
Uint8 width;
Uint8 bytes[FLEX_ARRAY];
};
// Following field is significant when node is unused.
Patnode *nextFree;
};
static Patnode *Pat_NodeForPrefix(const RawPrefix *pfx)
{
return (Patnode *) ((Uint8 *) pfx - offsetof(Patnode, width));
}
static Boolean Pat_IsNodeGlue(const Patnode *n)
{
return (((Uintptr) n->parent) & 1uLL) != 0;
}
static Patnode *Pat_GetNodeParent(const Patnode *n)
{
return (Patnode *) ((Uintptr) n->parent & ~1uLL);
}
static void Pat_SetNodeParent(Patnode *n, Patnode *parent)
{
n->parent = (Patnode *) ((Uintptr) parent | (((Uintptr) n->parent) & 1uLL));
}
static void Pat_SetNodeGlue(Patnode *n)
{
n->parent = (Patnode *) ((Uintptr) n->parent | 1uLL);
}
static void Pat_ResetNodeGlue(Patnode *n)
{
n->parent = (Patnode *) ((Uintptr) n->parent & ~1uLL);
}
static Patnode *Pat_AllocNode(Patricia *trie, Uint8 width)
{
Patnode *n;
Patblock *block;
size_t siz, len;
unsigned idx;
// Calculate block size and lookup inside free cache
len = PFXLEN(width);
assert(len <= IPV6_SIZE);
idx = len >> PAT_BLOCK_ALIGN_SHIFT;
siz = ALIGN(offsetof(Patnode, bytes[len]), PAT_BLOCK_ALIGN);
n = trie->freeBins[idx];
if (n) {
// ...free list cache hit
trie->freeBins[idx] = n->nextFree;
goto return_node;
}
// Need to allocate a new node
block = trie->blocks;
if (!block || block->freeOfs + siz > PAT_BLOCK_SIZE) {
// Must allocate a new block altoghether
block = (Patblock *) malloc(sizeof(*block));
if (!block) {
Sys_OutOfMemory();
return NULL; // too bad...
}
block->freeOfs = 0;
block->nextBlock = trie->blocks;
trie->blocks = block;
}
n = (Patnode *) (block->buf + block->freeOfs);
block->freeOfs += siz;
return_node:
n->parent = NULL;
n->children[0] = n->children[1] = NULL;
n->width = width;
return n;
}
static void Pat_FreeNode(Patricia *trie, Patnode *n)
{
// Place inside free cache bins
unsigned idx = PFXLEN(n->width) >> PAT_BLOCK_ALIGN_SHIFT;
n->nextFree = trie->freeBins[idx];
trie->freeBins[idx] = n;
}
RawPrefix *Pat_Insert(Patricia *trie, const RawPrefix *pfx)
{
Patnode *n;
unsigned maxWidth;
assert(trie->afi == AFI_IP || trie->afi == AFI_IP6);
maxWidth = (trie->afi == AFI_IP6) ? IPV6_WIDTH : IPV4_WIDTH;
assert(pfx->width <= maxWidth);
n = trie->head;
if (!n) {
// First node ever, create trie head node
n = Pat_AllocNode(trie, pfx->width);
if (!n)
return NULL;
memcpy(n->bytes, pfx->bytes, PFXLEN(pfx->width));
// Place it in `trie`
trie->head = n;
trie->nprefixes++;
return PLAINPFX(n);
}
while (n->width < pfx->width || Pat_IsNodeGlue(n)) {
int bit = (n->width < maxWidth) &&
(pfx->bytes[n->width >> 3] & (0x80 >> (n->width & 0x07)));
if (!n->children[bit])
break;
n = n->children[bit];
}
unsigned checkBit = MIN(n->width, pfx->width);
unsigned differBit = 0;
#if 1
// unoptimized version
unsigned r;
for (unsigned i = 0, z = 0; z < checkBit; i++, z += 8) {
r = (pfx->bytes[i] ^ n->bytes[i]);
if (r == 0) {
differBit = z + 8;
continue;
}
unsigned j;
for (j = 0; j < 8; j++)
if (r & (0x80 >> j))
break;
differBit = z + j;
break;
}
#else
/* TODO possible optimization:
* example 32 bit portion with different endianness:
LSB (visit using LSB->MSB) MSB (LE)
01000000 00000000 00000100 00000000
MSB (visit using MSB->LSB) LSB (BE)
leftmost bit is:
-> 32 - bsr32() (BE)
-> bsf32() - 1 (LE)
*/
for (unsigned i = 0, z = 0; z < checkBit; i++, z += 32) {
Uint32 r = (pfx->u32[i] ^ n->u32[i]);
if (r == 0) {
differBit = z + 32;
continue;
}
unsigned j = (EDN_NATIVE == EDN_BIG) ? 32 - bsr32(r) : bsf32(r) - 1; // clz(beswap32(r));
differBit = z + j;
break;
}
#endif
if (differBit > checkBit)
differBit = checkBit;
Patnode *parent = Pat_GetNodeParent(n);
while (parent && parent->width >= differBit) {
n = parent;
parent = Pat_GetNodeParent(n);
}
if (differBit == pfx->width && n->width == pfx->width) {
if (Pat_IsNodeGlue(n)) {
// Replace glue node
Pat_ResetNodeGlue(n);
memcpy(n->bytes, pfx->bytes, PFXLEN(pfx->width));
}
trie->nprefixes++;
return PLAINPFX(n);
}
// Must allocate new node
Patnode *newNode = Pat_AllocNode(trie, pfx->width);
if (!newNode)
return NULL; // out of memory
memcpy(newNode->bytes, pfx->bytes, PFXLEN(pfx->width));
trie->nprefixes++;
if (n->width == differBit) {
Pat_SetNodeParent(newNode, n);
int bit = (n->width < maxWidth) &&
(pfx->bytes[n->width >> 3] & (0x80 >> (n->width & 0x07)));
n->children[bit] = newNode;
return PLAINPFX(newNode);
}
if (pfx->width == differBit) {
int bit = (pfx->width < maxWidth) &&
(n->bytes[pfx->width >> 3] & (0x80 >> (pfx->width & 0x07)));
newNode->children[bit] = n;
Pat_SetNodeParent(newNode, n);
parent = Pat_GetNodeParent(n);
if (!parent)
trie->head = newNode;
else if (parent->children[1] == n) {
int bit = (parent->children[1] == n);
parent->children[bit] = n;
}
Pat_SetNodeParent(n, newNode);
} else {
Patnode *glue = Pat_AllocNode(trie, differBit);
if (!glue)
return NULL;
parent = Pat_GetNodeParent(n);
glue->parent = parent;
Pat_SetNodeGlue(glue);
int bit = (differBit < maxWidth) &&
(pfx->bytes[differBit >> 3] & (0x80 >> (differBit & 0x07)));
glue->children[bit] = newNode;
glue->children[!bit] = n;
newNode->parent = glue;
if (!parent)
trie->head = glue;
else {
int bit = (parent->children[1] == n);
parent->children[bit] = glue;
}
Pat_SetNodeParent(n, glue);
}
return PLAINPFX(newNode);
}
static Boolean Ip_CompWithMask(const Uint8 *a, const Uint8 *b, Uint8 mask)
{
unsigned n = mask / 8;
if (memcmp(a, b, n) == 0) {
unsigned m = ~0u << (8 - (mask % 8));
if ((mask & 0x7) == 0 || (a[n] & m) == (b[n] & m))
return TRUE;
}
return FALSE;
}
RawPrefix *Pat_SearchExact(const Patricia *trie, const RawPrefix *pfx)
{
const Patnode *n = trie->head;
if (!n)
return NULL;
while (n->width < pfx->width) {
int bit = (pfx->bytes[n->width >> 3] & (0x80 >> (n->width & 0x07))) != 0;
n = n->children[bit];
if (!n)
return NULL;
}
if (n->width > pfx->width || Pat_IsNodeGlue(n))
return NULL;
if (Ip_CompWithMask(n->bytes, pfx->bytes, pfx->width))
return PLAINPFX(n);
return NULL;
}
Boolean Pat_IsSubnetOf(const Patricia *trie, const RawPrefix *pfx)
{
const Patnode *n = trie->head;
while (n && n->width < pfx->width) {
if (!Pat_IsNodeGlue(n))
return Ip_CompWithMask(n->bytes, pfx->bytes, n->width);
int bit = (pfx->bytes[n->width >> 3] & (0x80 >> (n->width & 0x07))) != 0;
n = n->children[bit];
}
return n && !Pat_IsNodeGlue(n) &&
n->width <= pfx->width &&
Ip_CompWithMask(n->bytes, pfx->bytes, pfx->width);
}
Boolean Pat_IsSupernetOf(const Patricia *trie, const RawPrefix *pfx)
{
Patnode *start = trie->head;
while (start && start->width < pfx->width) {
int bit = (pfx->bytes[start->width >> 3] & (0x80 >> (start->width & 0x07))) != 0;
start = start->children[bit];
}
Patnode *node;
Patnode *stack[128+1];
Patnode **sp = stack;
Patnode *next = start;
while ((node = next) != NULL) {
if (!Pat_IsNodeGlue(node)) {
if (Ip_CompWithMask(node->bytes, pfx->bytes, pfx->width))
return TRUE;
break;
}
if (next->children[0]) {
if (next->children[1])
*sp++ = next->children[1];
next = next->children[0];
} else if (next->children[1]) {
next = next->children[1];
} else if (sp != stack) {
next = *(sp--);
} else {
next = NULL;
}
}
return FALSE;
}
Boolean Pat_IsRelatedOf(const Patricia *trie, const RawPrefix *pfx)
{
Patnode *start = trie->head;
while (start && start->width < pfx->width) {
if (!Pat_IsNodeGlue(start) && Ip_CompWithMask(start->bytes, pfx->bytes, start->width))
return TRUE;
int bit = (pfx->bytes[start->width >> 3] & (0x80 >> (start->width & 0x07))) != 0;
start = start->children[bit];
}
Patnode *node;
Patnode *stack[128+1];
Patnode **sp = stack;
Patnode *next = start;
while ((node = next) != NULL) {
if (!Pat_IsNodeGlue(node) && Ip_CompWithMask(node->bytes, pfx->bytes, pfx->width))
return TRUE;
if (next->children[0]) {
if (next->children[1])
*sp++ = next->children[1];
next = next->children[0];
} else if (next->children[1]) {
next = next->children[1];
} else if (sp != stack) {
next = *(sp--);
} else {
next = NULL;
}
}
return FALSE;
}
Boolean Pat_Remove(Patricia *trie, const RawPrefix *pfx)
{
RawPrefix *res = Pat_SearchExact(trie, pfx);
if (!res)
return FALSE;
Patnode *n = Pat_NodeForPrefix(res);
if (!n)
return FALSE;
trie->nprefixes--;
if (n->children[0] && n->children[1]) {
Pat_SetNodeGlue(n);
return TRUE;
}
Patnode *parent, *pparent;
Patnode *child;
int bit;
parent = Pat_GetNodeParent(n);
if (!n->children[0] && !n->children[1]) {
Pat_FreeNode(trie, n);
if (!parent) {
trie->head = NULL;
return TRUE;
}
bit = (parent->children[1] == n);
parent->children[bit] = NULL;
child = parent->children[!bit];
if (!Pat_IsNodeGlue(parent))
return TRUE;
// If here, then parent is glue, we need to remove them both
pparent = Pat_GetNodeParent(parent);
if (!pparent) {
trie->head = child;
} else {
bit = (pparent->children[1] == parent);
pparent->children[bit] = child;
}
Pat_SetNodeParent(child, pparent);
Pat_FreeNode(trie, parent);
return TRUE;
}
bit = (n->children[1] != NULL);
child = n->children[bit];
Pat_SetNodeParent(child, parent);
Pat_FreeNode(trie, n);
if (!parent) {
trie->head = child;
return TRUE;
}
bit = (parent->children[1] == n);
parent->children[bit] = child;
return TRUE;
}
void Pat_Clear(Patricia *trie)
{
while (trie->blocks) {
Patblock *t = trie->blocks;
trie->blocks = t->nextBlock;
free(t);
}
trie->afi = 0;
trie->nprefixes = 0;
trie->head = NULL;
memset(trie->freeBins, 0, sizeof(trie->freeBins));
}

@ -0,0 +1,191 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/prefix.c
*
* Deal with network prefixes.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "sys/endian.h"
#include "sys/ip.h"
#include "numlib.h"
#include <assert.h>
#include <string.h>
// ===========================================================================
// Some performance oriented macros to avoid branching during prefix iteration
/// Calculate the minimum size of a possibly ADD_PATH enabled prefix.
#define MINPFXSIZ(isAddPath) \
((((isAddPath) != 0) << 2) + 1)
/// Extract the prefix portion out of a possibly ADD_PATH enabled prefix pointer.
#define RAWPFXPTR(base, isAddPath) \
((RawPrefix *) ((Uint8 *) (base) + (((isAddPath) != 0) << 2)))
/// Calculate maximum prefix width in bits given an address family
#define MAXPFXWIDTH(family) (((family) == AFI_IP6) ? IPV6_WIDTH : IPV4_WIDTH) // simple CMOV
// ===========================================================================
char *Bgp_PrefixToString(Afi afi, const RawPrefix *prefix, char *dest)
{
Ipv4adr adr;
Ipv6adr adr6;
switch (afi) {
case AFI_IP:
memset(&adr, 0, sizeof(adr));
memcpy(&adr, prefix->bytes, PFXLEN(prefix->width));
dest = Ipv4_AdrToString(&adr, dest);
break;
case AFI_IP6:
memset(&adr6, 0, sizeof(adr6));
memcpy(&adr6, prefix->bytes, PFXLEN(prefix->width));
dest = Ipv6_AdrToString(&adr6, dest);
break;
default:
return NULL; // invalid argument
}
*dest++ = '/';
dest = Utoa(prefix->width, dest);
return dest;
}
char *Bgp_ApPrefixToString(Afi afi, const ApRawPrefix *prefix, char *dest)
{
// NOTE: Test early to avoid polluting `dest` in case of invalid argument;
// hopefully compilers will flatten this function to
// eliminate duplicate test inside switch
if (afi != AFI_IP && afi != AFI_IP6)
return NULL; // invalid argument
dest = Utoa(beswap32(prefix->pathId), dest);
*dest++ = ' ';
return Bgp_PrefixToString(afi, PLAINPFX(prefix), dest);
}
Judgement Bgp_StringToPrefix(const char *s, Prefix *dest)
{
Ipadr adr;
unsigned width;
NumConvRet res;
const char *ptr = s;
while (*ptr != '/' && *ptr != '\0') ptr++;
size_t len = ptr - s;
char *buf = (char *) alloca(len + 1);
memcpy(buf, s, len);
buf[len] = '\0';
if (Ip_StringToAdr(buf, &adr) != OK)
return NG; // Bad IP string
if (*ptr == '/') {
ptr++; // skip '/' separator
char *eptr;
width = Atou(ptr, &eptr, 10, &res);
if (res != NCVENOERR || *eptr != '\0')
return NG;
} else
width = (adr.family == IP6) ? IPV6_WIDTH : IPV4_WIDTH; // implicit full prefix
switch (adr.family) {
case IP4:
if (width > IPV4_WIDTH) return NG; // illegal prefix length
dest->afi = AFI_IP;
break;
case IP6:
if (width > IPV6_WIDTH) return NG; // illegal prefix length
dest->afi = AFI_IP6;
break;
default:
UNREACHABLE;
return NG;
}
dest->isAddPath = FALSE;
dest->width = width;
memcpy(dest->bytes, adr.bytes, PFXLEN(width));
return OK;
}
Judgement Bgp_StartPrefixes(Prefixiter *it,
Afi afi,
Safi safi,
const void *data,
size_t nbytes,
Boolean isAddPath)
{
if (afi != AFI_IP && afi != AFI_IP6) {
Bgp_SetErrStat(BGPEAFIUNSUP);
return NG;
}
if (safi != SAFI_UNICAST && safi != SAFI_MULTICAST) {
Bgp_SetErrStat(BGPESAFIUNSUP);
return NG;
}
it->afi = afi;
it->safi = safi;
it->isAddPath = isAddPath;
it->base = (Uint8 *) data;
it->lim = it->base + nbytes;
it->ptr = it->base;
Bgp_SetErrStat(BGPENOERR);
return OK;
}
void *Bgp_NextPrefix(Prefixiter *it)
{
if (it->ptr >= it->lim) {
Bgp_SetErrStat(BGPENOERR);
return NULL; // end of iteration
}
// Basic check for prefix initial bytes
size_t left = it->lim - it->ptr;
size_t siz = MINPFXSIZ(it->isAddPath);
if (left < siz) {
Bgp_SetErrStat(BGPETRUNCPFX);
return NULL;
}
// Adjust a pointer to skip Path identifier info if necessary
const RawPrefix *rawPfx = RAWPFXPTR(it->ptr, it->isAddPath);
if (rawPfx->width > MAXPFXWIDTH(it->afi)) {
Bgp_SetErrStat(BGPEBADPFXWIDTH);
return NULL;
}
// Ensure all the necessary prefix bytes are present
siz += PFXLEN(rawPfx->width);
if (left < siz) {
Bgp_SetErrStat(BGPETRUNCPFX);
return NULL;
}
// All good, advance and return
void *pfx = it->ptr;
it->ptr += siz;
Bgp_SetErrStat(BGPENOERR);
return pfx;
}

@ -0,0 +1,838 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm.c
*
* BGP VM initialization and execution loop.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "bgp/patricia.h"
#include "bgp/vmintrin.h"
#include "sys/endian.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#if defined(__GNUC__) && !defined(DF_BGP_VM_NO_COMPUTED_GOTO)
#define DF_BGP_VM_USES_COMPUTED_GOTO
#include "bgp/vm_gccdef.h"
#else
#include "bgp/vm_cdef.h"
#endif
#define BGP_VM_MINHEAPSIZ (4 * 1024)
#define BGP_VM_STKSIZ (4 * 1024)
#define BGP_VM_GROWPROGN 128
/* During VM execution instructions update the current BGP message
* match. But sometimes the match is irrelevant (think about something
* like:
*
* ```
* LOADU 1
* NOT
* CPASS
* ```
*
* This bytecode doesn't examine any actual BGP message segment,
* to simplify instructions, whenever an irrelevant match is being produced,
* the following static variable is referenced by `vm->curMatch`,
* to provide a dummy match that can be updated at will
* by any VM and is always discarded by `Bgp_VmStoreMatch()`.
*/
static Bgpvmmatch discardMatch;
Judgement Bgp_InitVm(Bgpvm *vm, size_t heapSiz)
{
size_t siz = BGP_VM_STKSIZ + MAX(heapSiz, BGP_VM_MINHEAPSIZ);
siz = ALIGN(siz, ALIGNMENT);
assert(siz <= 0xffffffffuLL);
void *heap = malloc(siz);
if (!heap)
return Bgp_SetErrStat(BGPENOMEM);
memset(vm, 0, sizeof(*vm));
vm->heap = heap;
vm->hMemSiz = siz;
vm->hHighMark = siz;
return Bgp_SetErrStat(BGPENOERR);
}
Judgement Bgp_VmEmit(Bgpvm *vm, Bgpvmbytec bytec)
{
assert(!vm->isRunning);
BGP_VMCLRERR(vm);
if (BGP_VMOPC(bytec) == BGP_VMOP_END)
return Bgp_SetErrStat(BGPENOERR); // ignore useless emit
if (vm->progLen + 1 >= vm->progCap) {
// Grow the VM program segment
size_t newSiz = vm->progCap + BGP_VM_GROWPROGN;
Bgpvmbytec *newProg = (Bgpvmbytec *) realloc(vm->prog, newSiz * sizeof(*newProg));
if (!newProg) {
// Flag the VM as bad
vm->setupFailed = TRUE;
vm->errCode = BGPENOMEM;
return Bgp_SetErrStat(BGPENOMEM);
}
vm->prog = newProg;
vm->progCap = newSiz;
}
// Append instruction and follow it with BGP_VMOP_END
vm->prog[vm->progLen++] = bytec;
vm->prog[vm->progLen] = BGP_VMOP_END;
return Bgp_SetErrStat(BGPENOERR);
}
void *Bgp_VmPermAlloc(Bgpvm *vm, size_t size)
{
assert(!vm->isRunning);
BGP_VMCLRERR(vm);
size = ALIGN(size, ALIGNMENT);
if (vm->hLowMark + size > vm->hMemSiz) {
// Flag the VM as bad
vm->setupFailed = TRUE;
vm->errCode = BGPEVMOOM;
Bgp_SetErrStat(BGPEVMOOM);
return NULL;
}
void *ptr = (Uint8 *) vm->heap + vm->hLowMark;
vm->hLowMark += size;
Bgp_SetErrStat(BGPENOERR);
return ptr;
}
void *Bgp_VmTempAlloc(Bgpvm *vm, size_t size)
{
assert(vm->isRunning);
size = ALIGN(size, ALIGNMENT);
size_t stksiz = vm->si * sizeof(Bgpvmval);
if (vm->hLowMark + stksiz + size > vm->hHighMark) UNLIKELY {
// NOTE: VM is being executed, don't set BGP error state
// it will be updated by Bgp_VmExec() as needed
vm->errCode = BGPEVMOOM;
return NULL;
}
assert(vm->hHighMark >= size);
vm->hHighMark -= size;
return (Uint8 *) vm->heap + vm->hHighMark;
}
void Bgp_VmTempFree(Bgpvm *vm, size_t size)
{
assert(vm->isRunning);
size = ALIGN(size, ALIGNMENT);
assert(size + vm->hHighMark <= vm->hMemSiz);
vm->hHighMark += size;
}
Boolean Bgp_VmExec(Bgpvm *vm, Bgpmsg *msg)
{
// Fundamental sanity checks
assert(!vm->isRunning);
if (vm->setupFailed) UNLIKELY {
vm->errCode = BGPEBADVM;
goto cant_run;
}
if (!vm->prog) UNLIKELY {
vm->errCode = BGPEVMNOPROG;
goto cant_run;
}
// Setup initial VM state
Boolean result = TRUE; // assume PASS unless CFAIL says otherwise
vm->pc = 0;
vm->si = 0;
vm->nblk = 0;
vm->nmatches = 0;
vm->hHighMark = vm->hMemSiz;
vm->msg = msg;
vm->curMatch = &discardMatch;
vm->matches = NULL;
vm->errCode = BGPENOERR;
// Populate computed goto table if necessary
#ifdef DF_BGP_VM_USES_COMPUTED_GOTO
#include "bgp/vm_optab.h"
#endif
// Execute bytecode according to the #included vm_<impl>def.h
Bgpvmbytec ir; // Instruction Register
vm->isRunning = TRUE;
while (TRUE) {
// FETCH stage
FETCH(ir, vm);
// DECODE-DISPATCH stage
DISPATCH(BGP_VMOPC(ir)) {
// EXECUTE stage
EXECUTE(NOP): UNLIKELY;
break;
EXECUTE(LOAD):
Bgp_VmDoLoad(vm, (Sint8) BGP_VMOPARG(ir));
break;
EXECUTE(LOADU):
Bgp_VmDoLoadu(vm, BGP_VMOPARG(ir));
break;
EXECUTE(LOADN):
Bgp_VmDoLoadn(vm);
break;
EXECUTE(LOADK):
Bgp_VmDoLoadk(vm, BGP_VMOPARG(ir));
break;
EXECUTE(CALL):
Bgp_VmDoCall(vm, BGP_VMOPARG(ir));
break;
EXECUTE(BLK):
vm->nblk++;
break;
EXECUTE(ENDBLK):
if (vm->nblk == 0) UNLIKELY {
vm->errCode = BGPEVMBADENDBLK;
goto terminate;
}
vm->nblk--;
break;
EXECUTE(TAG):
Bgp_VmDoTag(vm, BGP_VMOPARG(ir));
break;
EXECUTE(NOT):
Bgp_VmDoNot(vm);
EXPECT(CFAIL, ir, vm);
EXPECT(CPASS, ir, vm);
break;
EXECUTE(CFAIL):
if (Bgp_VmDoCfail(vm)) {
result = FALSE; // immediate terminate on FAIL
goto terminate;
}
break;
EXECUTE(CPASS):
if (Bgp_VmDoCpass(vm))
goto terminate; // immediate PASS
break;
EXECUTE(JZ):
if (!BGP_VMCHKSTKSIZ(vm, 1)) UNLIKELY
break;
if (!BGP_VMPEEK(vm, -1)) {
// Zero, do jump
vm->pc += BGP_VMOPARG(ir);
if (vm->pc > vm->progLen) UNLIKELY
vm->errCode = BGPEVMBADJMP; // jump target out of bounds
} else
BGP_VMPOP(vm); // no jump, pop the stack and move on
break;
EXECUTE(JNZ):
if (!BGP_VMCHKSTKSIZ(vm, 1)) UNLIKELY
break;
if (BGP_VMPEEK(vm, -1)) {
// Non-Zero, do jump
vm->pc += BGP_VMOPARG(ir);
if (vm->pc > vm->progLen) UNLIKELY
vm->errCode = BGPEVMBADJMP; // jump target out of bounds
} else
BGP_VMPOP(vm); // no jump, pop the stack and move on
break;
EXECUTE(CHKT):
Bgp_VmDoChkt(vm, (BgpType) BGP_VMOPARG(ir));
break;
EXECUTE(CHKA):
Bgp_VmDoChka(vm, (BgpAttrCode) BGP_VMOPARG(ir));
break;
EXECUTE(EXCT):
Bgp_VmDoExct(vm, BGP_VMOPARG(ir));
break;
EXECUTE(SUPN):
Bgp_VmDoSupn(vm, BGP_VMOPARG(ir));
break;
EXECUTE(SUBN):
Bgp_VmDoSubn(vm, BGP_VMOPARG(ir));
break;
EXECUTE(RELT):
Bgp_VmDoRelt(vm, BGP_VMOPARG(ir));
break;
EXECUTE(ASMTCH):
Bgp_VmDoAsmtch(vm);
break;
EXECUTE(FASMTC):
Bgp_VmDoFasmtc(vm);
break;
EXECUTE(COMTCH):
Bgp_VmDoComtch(vm);
break;
EXECUTE(ACOMTC):
Bgp_VmDoAcomtc(vm);
break;
EXECUTE(END): UNLIKELY;
// Implicitly PASS current match
vm->curMatch->isPassing = TRUE;
goto terminate;
EXECUTE_SIGILL: UNLIKELY;
vm->errCode = BGPEVMILL;
break;
}
if (vm->errCode) UNLIKELY
goto terminate; // error encountered, abort execution
}
terminate:
vm->curMatch = NULL; // prevent accidental access outside Bgp_VmExec()
vm->isRunning = FALSE;
if (Bgp_SetErrStat(vm->errCode) != OK) UNLIKELY
result = FALSE;
return result;
cant_run:
Bgp_SetErrStat(vm->errCode);
return FALSE;
}
Judgement Bgp_VmStoreMsgTypeMatch(Bgpvm *vm, Boolean isMatching)
{
vm->curMatch = (Bgpvmmatch *) Bgp_VmTempAlloc(vm, sizeof(*vm->curMatch));
if (!vm->curMatch) UNLIKELY
return NG;
Bgphdr *hdr = BGP_HDR(vm->msg);
vm->curMatch->pc = BGP_VMCURPC(vm);
vm->curMatch->tag = 0;
vm->curMatch->base = (Uint8 *) hdr;
vm->curMatch->lim = (Uint8 *) (hdr + 1);
vm->curMatch->pos = &hdr->type;
vm->curMatch->isMatching = isMatching;
Bgp_VmStoreMatch(vm);
return OK;
}
void Bgp_VmStoreMatch(Bgpvm *vm)
{
assert(vm->isRunning);
if (vm->curMatch == &discardMatch)
return; // discard store request
// Prepend match to matches list, still keep the `curMatch` pointer
// around in case result is updated by following instructions (e.g. NOT)
vm->curMatch->nextMatch = vm->matches;
vm->matches = vm->curMatch;
vm->nmatches++;
}
Boolean Bgp_VmDoCpass(Bgpvm *vm)
{
/* POPS:
* -1: Last operation Boolean result (as a Sint64, 0 for FALSE)
*
* PUSHES:
* * On PASS result:
* - TRUE
* * Otherwise:
* - Nothing.
*
* SIDE-EFFECTS:
* - Breaks current BLK on PASS
* - Updates `curMatch->isPassing` flag accordingly
*/
if (!BGP_VMCHKSTKSIZ(vm, 1)) UNLIKELY
return FALSE; // error, let vm->errCode handle this
Boolean shouldTerm = FALSE; // unless proven otherwise
// If stack top is non-zero we FAIL
if (BGP_VMPEEK(vm, -1)) {
// Leave TRUE on stack and break current BLK, this is a PASS
vm->curMatch->isPassing = TRUE;
if (vm->nblk > 0)
Bgp_VmDoBreak(vm);
else
shouldTerm = TRUE; // no more BLK
} else {
// Pop the stack and move on, no PASS
vm->curMatch->isPassing = FALSE;
BGP_VMPOP(vm);
}
// Current match information has been collected,
// discard anything up to the next relevant operation
vm->curMatch = &discardMatch;
return shouldTerm;
}
Boolean Bgp_VmDoCfail(Bgpvm *vm)
{
/* POPS:
* -1: Last operation Boolean result (as a Sint64, 0 for FALSE)
*
* PUSHES:
* * On FAIL result:
* - FALSE
* * Otherwise:
* - Nothing.
*
* SIDE-EFFECTS:
* - Breaks current BLK on FAIL
* - Updates `curMatch->isPassing` flag accordingly
*/
if (!BGP_VMCHKSTKSIZ(vm, 1)) UNLIKELY
return FALSE; // error, let vm->errCode handle this
Boolean shouldTerm = FALSE; // unless proven otherwise
// If stack top is non-zero we FAIL
Bgpvmval *v = BGP_VMSTKGET(vm, -1);
if (v->val) {
// Push FALSE and break current BLK, this is a FAIL
vm->curMatch->isPassing = FALSE;
v->val = FALSE;
if (vm->nblk > 0)
Bgp_VmDoBreak(vm);
else
shouldTerm = TRUE; // no more BLK
} else {
// Pop the stack and move on, no FAIL
vm->curMatch->isPassing = TRUE;
BGP_VMPOP(vm);
}
// Current match information has been collected,
// discard anything up to the next relevant operation
vm->curMatch = &discardMatch;
return shouldTerm;
}
void Bgp_VmDoChkt(Bgpvm *vm, BgpType type)
{
/* PUSHES:
* TRUE if type matches, FALSE otherwise
*/
if (!BGP_VMCHKSTK(vm, 1)) UNLIKELY
return;
Boolean isMatching = (BGP_VMCHKMSGTYPE(vm, type) != NULL);
BGP_VMPUSH(vm, isMatching);
Bgp_VmStoreMsgTypeMatch(vm, isMatching);
}
void Bgp_VmDoChka(Bgpvm *vm, BgpAttrCode code)
{
/* PUSHES:
* TRUE if attribute exists inside UPDATE message, FALSE otherwise
*/
if (!BGP_VMCHKSTK(vm, 1)) UNLIKELY
return;
Bgpupdate *update = (Bgpupdate *) BGP_VMCHKMSGTYPE(vm, BGP_UPDATE);
if (!update) {
Bgp_VmStoreMsgTypeMatch(vm, /*isMatching=*/FALSE);
return;
}
// Attribute lookup
Bgpattrseg *tpa = Bgp_GetUpdateAttributes(update);
if (!tpa) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgpattr *attr = Bgp_GetUpdateAttribute(tpa, code, vm->msg->table);
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Boolean isMatching = (attr != NULL);
BGP_VMPUSH(vm, isMatching);
// Create a new match
vm->curMatch = (Bgpvmmatch *) Bgp_VmTempAlloc(vm, sizeof(*vm->curMatch));
if (!vm->curMatch) UNLIKELY
return;
vm->curMatch->pc = BGP_VMCURPC(vm);
vm->curMatch->tag = 0;
vm->curMatch->base = tpa->attrs;
vm->curMatch->lim = &tpa->attrs[beswap16(tpa->len)];
vm->curMatch->pos = attr;
vm->curMatch->isMatching = isMatching;
Bgp_VmStoreMatch(vm);
}
static Judgement Bgp_VmStartNets(Bgpvm *vm, Bgpmpiter *it, Uint8 mode)
{
if (!BGP_VMCHKMSGTYPE(vm, BGP_UPDATE)) {
Bgp_VmStoreMsgTypeMatch(vm, /*isMatching=*/FALSE);
return NG;
}
switch (mode) {
case BGP_VMOPA_NLRI:
Bgp_StartMsgNlri(&it->rng, vm->msg);
it->nextAttr = NULL;
break;
case BGP_VMOPA_WITHDRAWN:
Bgp_StartMsgWithdrawn(&it->rng, vm->msg);
it->nextAttr = NULL;
break;
case BGP_VMOPA_ALL_NLRI:
Bgp_StartAllMsgNlri(it, vm->msg);
break;
case BGP_VMOPA_ALL_WITHDRAWN:
Bgp_StartAllMsgWithdrawn(it, vm->msg);
break;
default: UNLIKELY;
vm->errCode = BGPEVMBADOP;
return NG;
}
if (Bgp_GetErrStat(NULL)) UNLIKELY {
vm->errCode = BGPEVMMSGERR;
return NG;
}
return OK;
}
static void Bgp_VmCollectNetMatch(Bgpvm *vm, Bgpmpiter *it)
{
// Push on stack first --
// we know we have at least one available spot on the stack for
// any network operation (we POP at least one Patricia address from it).
// Perform the temporary allocation afterwards.
// This saves one check on stack space
BGP_VMPUSH(vm, TRUE);
vm->curMatch = (Bgpvmmatch *) Bgp_VmTempAlloc(vm, sizeof(*vm->curMatch));
if (!vm->curMatch) UNLIKELY
return;
vm->curMatch->pc = BGP_VMCURPC(vm);
vm->curMatch->tag = 0;
vm->curMatch->base = BGP_CURMPBASE(it);
vm->curMatch->lim = BGP_CURMPLIM(it);
vm->curMatch->pos = BGP_CURMPPFX(it);
vm->curMatch->isMatching = TRUE;
Bgp_VmStoreMatch(vm);
}
static void Bgp_VmNetMatchFailed(Bgpvm *vm)
{
BGP_VMPUSH(vm, FALSE); // NOTE: See `Bgp_VmCollectNetMatch()`
vm->curMatch = (Bgpvmmatch *) Bgp_VmTempAlloc(vm, sizeof(*vm->curMatch));
if (!vm->curMatch) UNLIKELY
return;
vm->curMatch->pc = BGP_VMCURPC(vm);
vm->curMatch->tag = 0;
vm->curMatch->base = NULL;
vm->curMatch->lim = NULL;
vm->curMatch->pos = NULL;
vm->curMatch->isMatching = FALSE;
Bgp_VmStoreMatch(vm);
}
static const Patricia emptyTrie4 = { AFI_IP };
static const Patricia emptyTrie6 = { AFI_IP6 };
void Bgp_VmDoExct(Bgpvm *vm, Uint8 arg)
{
// POPS:
// -1: AFI_IP6 Patricia (may be NULL)
// -2: AFI_IP Patricia (may be NULL)
//
// PUSHES:
// TRUE on EXACT match of any network prefix in message, FALSE otherwise
if (!BGP_VMCHKSTKSIZ(vm, 2)) UNLIKELY
return;
Bgpmpiter it;
const Patricia *trie6 = (Patricia *) BGP_VMPOPA(vm);
const Patricia *trie = (Patricia *) BGP_VMPOPA(vm);
if (!trie6) trie6 = &emptyTrie6;
if (!trie) trie = &emptyTrie4;
if (trie->afi != AFI_IP || trie6->afi != AFI_IP6) UNLIKELY {
vm->errCode = BGPEVMBADOP;
return;
}
if (Bgp_VmStartNets(vm, &it, arg) != OK) UNLIKELY
return; // error already set
Prefix *pfx;
while ((pfx = Bgp_NextMpPrefix(&it)) != NULL) {
RawPrefix *match = NULL;
// Select appropriate PATRICIA
switch (pfx->afi) {
case AFI_IP:
match = Pat_SearchExact(trie, PLAINPFX(pfx));
break;
case AFI_IP6:
match = Pat_SearchExact(trie6, PLAINPFX(pfx));
break;
default: UNREACHABLE; return;
}
if (match) {
Bgp_VmCollectNetMatch(vm, &it);
return;
}
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgp_VmNetMatchFailed(vm);
}
void Bgp_VmDoSubn(Bgpvm *vm, Uint8 arg)
{
// POPS:
// -1: AFI_IP6 Patricia (may be NULL)
// -2: AFI_IP Patricia (may be NULL)
//
// PUSHES:
// TRUE on SUBN match of any network prefix in message, FALSE otherwise
if (!BGP_VMCHKSTKSIZ(vm, 2)) UNLIKELY
return;
Bgpmpiter it;
const Patricia *trie6 = (Patricia *) BGP_VMPOPA(vm);
const Patricia *trie = (Patricia *) BGP_VMPOPA(vm);
if (!trie6) trie6 = &emptyTrie6;
if (!trie) trie = &emptyTrie4;
if (trie->afi != AFI_IP || trie6->afi != AFI_IP6 ) UNLIKELY {
vm->errCode = BGPEVMBADOP;
return;
}
if (Bgp_VmStartNets(vm, &it, arg) != OK) UNLIKELY
return; // error already set
Prefix *pfx;
while ((pfx = Bgp_NextMpPrefix(&it)) != NULL) {
Boolean isMatching = FALSE;
// Select appropriate PATRICIA
switch (pfx->afi) {
case AFI_IP:
isMatching = Pat_IsSubnetOf(trie, PLAINPFX(pfx));
break;
case AFI_IP6:
isMatching = Pat_IsSubnetOf(trie6, PLAINPFX(pfx));
break;
default: UNREACHABLE; return;
}
if (isMatching) {
Bgp_VmCollectNetMatch(vm, &it);
return;
}
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgp_VmNetMatchFailed(vm);
}
void Bgp_VmDoSupn(Bgpvm *vm, Uint8 arg)
{
// POPS:
// -1: AFI_IP6 Patricia (may be NULL)
// -2: AFI_IP Patricia (may be NULL)
//
// PUSHES:
// TRUE on SUPN match of any network prefix in message, FALSE otherwise
if (!BGP_VMCHKSTKSIZ(vm, 2)) UNLIKELY
return;
Bgpmpiter it;
const Patricia *trie6 = (Patricia *) BGP_VMPOPA(vm);
const Patricia *trie = (Patricia *) BGP_VMPOPA(vm);
if (!trie6) trie6 = &emptyTrie6;
if (!trie) trie = &emptyTrie4;
if (trie->afi != AFI_IP || trie6->afi != AFI_IP6) UNLIKELY {
vm->errCode = BGPEVMBADOP;
return;
}
if (Bgp_VmStartNets(vm, &it, arg) != OK) UNLIKELY
return; // error already set
Prefix *pfx;
while ((pfx = Bgp_NextMpPrefix(&it)) != NULL) {
Boolean isMatching = FALSE;
// Select appropriate PATRICIA
switch (pfx->afi) {
case AFI_IP:
isMatching = Pat_IsSupernetOf(trie, PLAINPFX(pfx));
break;
case AFI_IP6:
isMatching = Pat_IsSupernetOf(trie6, PLAINPFX(pfx));
break;
default: UNREACHABLE; return;
}
if (isMatching) {
Bgp_VmCollectNetMatch(vm, &it);
return;
}
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgp_VmNetMatchFailed(vm);
}
void Bgp_VmDoRelt(Bgpvm *vm, Uint8 arg)
{
// POPS:
// -1: AFI_IP6 Patricia (may be NULL)
// -2: AFI_IP Patricia (may be NULL)
//
// PUSHES:
// TRUE on SUPN match of any network prefix in message, FALSE otherwise
if (!BGP_VMCHKSTKSIZ(vm, 2)) UNLIKELY
return;
Bgpmpiter it;
const Patricia *trie6 = (Patricia *) BGP_VMPOPA(vm);
const Patricia *trie = (Patricia *) BGP_VMPOPA(vm);
if (!trie6) trie6 = &emptyTrie6;
if (!trie) trie = &emptyTrie4;
if (trie->afi != AFI_IP || trie6->afi != AFI_IP6) UNLIKELY {
vm->errCode = BGPEVMBADOP;
return;
}
if (Bgp_VmStartNets(vm, &it, arg) != OK) UNLIKELY
return; // error already set
Prefix *pfx;
while ((pfx = Bgp_NextMpPrefix(&it)) != NULL) {
Boolean isMatching = FALSE;
// Select appropriate PATRICIA
switch (pfx->afi) {
case AFI_IP:
isMatching = Pat_IsRelatedOf(trie, PLAINPFX(pfx));
break;
case AFI_IP6:
isMatching = Pat_IsRelatedOf(trie6, PLAINPFX(pfx));
break;
default: UNREACHABLE; return;
}
if (isMatching) {
Bgp_VmCollectNetMatch(vm, &it);
return;
}
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgp_VmNetMatchFailed(vm);
}
void Bgp_ResetVm(Bgpvm *vm)
{
assert(!vm->isRunning);
vm->nk = 0;
vm->nfuncs = 0;
vm->nmatches = 0;
vm->progLen = 0;
vm->hLowMark = 0;
vm->hHighMark = vm->hMemSiz;
BGP_VMCLRSETUP(vm);
BGP_VMCLRERR(vm);
memset(vm->k, 0, sizeof(vm->k));
memset(vm->funcs, 0, sizeof(vm->funcs));
vm->matches = NULL;
}
void Bgp_ClearVm(Bgpvm *vm)
{
assert(!vm->isRunning);
free(vm->heap);
free(vm->prog);
}

@ -0,0 +1,834 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_asmtch.c
*
* Implements ASMTCH and FASMTC, and ASN match IR compilation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* This code is a modified version of the Plan9 regexp library matching algorithm.
* The Plan9 regexp library is available under the Lucent Public License
* at: https://9fans.github.io/plan9port/unix/libregexp9.tgz
*
* \see [Regular Expression Matching Can Be Simple And Fast](https://swtch.com/~rsc/regexp/regexp1.html)
*/
#include "bgp/vmintrin.h"
#include "sys/endian.h"
#include <assert.h>
#include <setjmp.h>
#include <string.h>
// Define this to dump the compiled AS MATCH expressions to stderr
//#define DF_DEBUG_ASMTCH
#ifdef NDEBUG
// Force DF_DEBUG_ASMTCH off on release build
#undef DF_DEBUG_ASMTCH
#endif
#ifdef DF_DEBUG_ASMTCH
#include "sys/con.h"
#endif
#define AS 126
#define NAS 127
#define START 128 // start, used for marker on stack
#define RPAR 129 // right parens, )
#define LPAR 130 // left parens, (
#define ALT 131 // alternation, |
#define CAT 132 // concatentation, implicit operator
#define STAR 133 // closure, *
#define PLUS 134 // a+ == aa*
#define QUEST 135 // a? == a|nothing, i.e. 0 or 1 a's
#define ANY 192 // any character except newline, .
#define NOP 193 // no operation
#define BOL 194 // beginning of line, ^
#define EOL 195 // end of line, $
#define STOP 255 // terminate: match found
#define BOTTOM (START - 1)
#define NSTACK 32
#define LISTSIZ 10
#define BIGLISTSIZ (32 * LISTSIZ)
#define ASNBOLFLAG BIT(60) // used to mark the initial ASN, so that BOL operator works
#define ASN_EOL -1 // equals to -1 returned by Bgp_NextAsPath()
// Automaton instruction.
typedef struct Nfainst Nfainst;
struct Nfainst{
int type; // instruction type, any of the macros above
union { // NOTE: keep `next` and `left` in the same union!
Nfainst *next; // next instruction in chain
Nfainst *left; // left output state, for ALT instructions
};
union {
Uint32 asn; // ASN match, for AS/NAS instructions
Uint16 grpid; // match group id, for LPAR/RPAR instructions
Nfainst *right; // right output state, for ALT instructions
};
};
// A block of instructions (used during NFA construction)
typedef struct Nfanode Nfanode;
struct Nfanode {
Nfainst *first, *last;
};
// Start and end position of a subexpression match
typedef struct {
Aspathiter spos;
Aspathiter epos;
} Nfamatch;
typedef struct Nfastate Nfastate;
struct Nfastate {
Nfainst *ip;
Nfamatch se[MAXBGPVMASNGRP];
};
// State list used during simulation (clist, nlist)
typedef struct Nfalist Nfalist;
struct Nfalist {
unsigned ns, lim;
Nfastate list[FLEX_ARRAY];
};
typedef struct Nfacomp Nfacomp;
struct Nfacomp {
Nfainst *basep; // base instruction pointer
Nfainst *freep; // next free instruction pointer
Nfanode andstack[NSTACK]; // operands stack
Uint16 grpidstack[NSTACK]; // subexpression id stack
Uint8 opstack[NSTACK]; // operators stack
Boolean8 lastWasAnd; // whether last encountered term was an operand
Uint16 nands, nops, ngrpids; // counters inside stacks
Uint16 nparens; // currently encountered open parens counter
Uint16 curgrpid; // next group id
jmp_buf oops; // compilation error jump
};
typedef struct Nfa Nfa;
struct Nfa {
Aspathiter spos; // Current ASN iterator start position
Aspathiter cur; // Current ASN iterator position
unsigned nmatches;
Nfamatch se[MAXBGPVMASNGRP];
};
static NORETURN void comperr(Nfacomp *nc, BgpvmRet err)
{
assert(err != BGPENOERR);
longjmp(nc->oops, err);
}
static Nfainst *newinst(Nfacomp *nc, int t)
{
Nfainst *i = nc->freep++;
i->type = t;
i->left = i->right = NULL;
return i;
}
static void pushator(Nfacomp *nc, Asn t)
{
if (nc->nops == NSTACK)
comperr(nc, BGPEVMBADASMTCH);
nc->opstack[nc->nops++] = t;
nc->grpidstack[nc->ngrpids++] = nc->curgrpid;
}
static Nfanode *pushand(Nfacomp *nc, Nfainst *f, Nfainst *l)
{
if (nc->nands == NSTACK)
comperr(nc, BGPEVMBADASMTCH);
Nfanode *n = &nc->andstack[nc->nands++];
n->first = f;
n->last = l;
return n;
}
static Nfanode *popand(Nfacomp *nc)
{
if(nc->nands == 0)
comperr(nc, BGPEVMBADASMTCH);
return &nc->andstack[--nc->nands];
}
static Uint8 popator(Nfacomp *nc)
{
if (nc->nops == 0)
comperr(nc, BGPEVMBADASMTCH);
--nc->ngrpids;
return nc->opstack[--nc->nops];
}
static void evaluntil(Nfacomp *nc, int prio)
{
Nfanode *op1, *op2;
Nfainst *inst1, *inst2;
while (prio == RPAR || nc->opstack[nc->nops-1] >= prio) {
switch (popator(nc)) {
default: UNREACHABLE;
case LPAR:
op1 = popand(nc);
inst2 = newinst(nc, RPAR);
inst2->grpid = nc->grpidstack[nc->ngrpids-1];
op1->last->next = inst2;
inst1 = newinst(nc, LPAR);
inst1->grpid = nc->grpidstack[nc->ngrpids-1];
inst1->next = op1->first;
pushand(nc, inst1, inst2);
return;
case ALT:
op2 = popand(nc), op1 = popand(nc);
inst2 = newinst(nc, NOP);
op2->last->next = inst2;
op1->last->next = inst2;
inst1 = newinst(nc, ALT);
inst1->right = op1->first;
inst1->left = op2->first;
pushand(nc, inst1, inst2);
break;
case CAT:
op2 = popand(nc), op1 = popand(nc);
op1->last->next = op2->first;
pushand(nc, op1->first, op2->last);
break;
case STAR:
op2 = popand(nc);
inst1 = newinst(nc, ALT);
op2->last->next = inst1;
inst1->right = op2->first;
pushand(nc, inst1, inst1);
break;
case PLUS:
op2 = popand(nc);
inst1 = newinst(nc, ALT);
op2->last->next = inst1;
inst1->right = op2->first;
pushand(nc, op2->first, inst1);
break;
case QUEST:
op2 = popand(nc);
inst1 = newinst(nc, ALT);
inst2 = newinst(nc, NOP);
inst1->left = inst2;
inst1->right = op2->first;
op2->last->next = inst2;
pushand(nc, inst1, inst2);
break;
}
}
}
static void operator(Nfacomp *nc, int op)
{
if (op == RPAR) {
if (nc->nparens == 0)
comperr(nc, BGPEVMBADASMTCH);
nc->nparens--;
}
if (op == LPAR) {
nc->curgrpid++;
if (nc->curgrpid == MAXBGPVMASNGRP)
comperr(nc, BGPEVMBADASMTCH);
nc->nparens++;
if (nc->lastWasAnd)
operator(nc, CAT); // add implicit CAT before group
} else
evaluntil(nc, op);
if (op != RPAR)
pushator(nc, op);
// Some operators behave like operands
nc->lastWasAnd = (op == STAR || op == QUEST || op == PLUS || op == RPAR);
}
static void operand(Nfacomp *nc, int t, Asn32 asn)
{
if (nc->lastWasAnd)
operator(nc, CAT); // add implicit CAT
Nfainst *i = newinst(nc, t);
if (t == AS || t == NAS)
i->asn = asn;
pushand(nc, i, i);
nc->lastWasAnd = TRUE;
}
static void compinit(Nfacomp *nc, Nfainst *dest)
{
nc->nands = nc->nops = nc->ngrpids = 0;
nc->nparens = 0;
nc->curgrpid = 0;
nc->lastWasAnd = FALSE;
nc->basep = nc->freep = dest;
}
static Nfainst *compile(Nfacomp *nc, const Asn *expression, size_t n)
{
pushator(nc, BOTTOM);
for (size_t i = 0; i < n; i++) {
Asn asn = expression[i];
switch (asn) {
case ASN_START: operand(nc, BOL, 0); break;
case ASN_END: operand(nc, EOL, 0); break;
case ASN_ANY: operand(nc, ANY, 0); break;
case ASN_STAR: operator(nc, STAR); break;
case ASN_QUEST: operator(nc, QUEST); break;
case ASN_PLUS: operator(nc, PLUS); break;
case ASN_NEWGRP: operator(nc, LPAR); break;
case ASN_ALT: operator(nc, ALT); break;
case ASN_ENDGRP: operator(nc, RPAR); break;
default:
if (ISASNNOT(asn)) operand(nc, NAS, ASN(asn));
else operand(nc, AS, ASN(asn));
break;
}
}
evaluntil(nc, START);
operand(nc, STOP, 0);
evaluntil(nc, START);
if (nc->nparens != 0)
comperr(nc, BGPEVMBADASMTCH);
return nc->andstack[nc->nands - 1].first;
}
static void optimize(Bgpvm *vm, Nfacomp *nc, size_t bufsiz)
{
assert(IS_ALIGNED(bufsiz, ALIGNMENT));
assert(vm->hLowMark >= bufsiz);
// Get rid of NOP chains
for (Nfainst *i = nc->basep; i->type != STOP; i++) {
Nfainst *j = i->next;
while (j->type == NOP)
j = j->next;
i->next = j;
}
// Initial program allocation is an upperbound, release excess memory
size_t siz = (nc->freep - nc->basep) * sizeof(*nc->basep);
size_t alsiz = ALIGN(siz, ALIGNMENT);
assert(alsiz <= bufsiz);
vm->hLowMark -= (bufsiz - alsiz);
assert(IS_ALIGNED(vm->hLowMark, ALIGNMENT));
}
#ifdef DF_DEBUG_ASMTCH
static void dumpprog(const Nfainst *prog)
{
const Nfainst *i = prog;
while (TRUE) {
Sys_Printf(STDERR, "%d:\t%#2x", (int) (i - prog), (unsigned) i->type);
switch (i->type) {
case ALT:
Sys_Printf(STDERR, "\t%d\t%d", (int) (i->left - prog), (int) (i->right - prog));
break;
case AS:
Sys_Printf(STDERR, "\tASN(%lu)\t%d", (unsigned long) beswap32(i->asn), (int) (i->next - prog));
break;
case NAS:
Sys_Printf(STDERR, "\t!ASN(%lu)\t%d", (unsigned long) beswap32(i->asn), (int) (i->next - prog));
break;
case LPAR: case RPAR:
Sys_Printf(STDERR, "\tGRP(%d)", (int) i->grpid);
// FALLTHROUGH
default:
Sys_Printf(STDERR, "\t%d", (int) (i->next - prog));
break;
case NOP: case STOP:
break;
}
Sys_Print(STDERR, "\n");
if (i->type == STOP)
break;
i++;
}
}
#endif
// `TRUE` if `pos` comes before `m` starting position
static Boolean isbefore(const Aspathiter *pos, const Nfamatch *m)
{
return BGP_CURASINDEX(pos) < BGP_CURASINDEX(&m->spos);
}
// `TRUE` if `a` starts at the same position as `b`, but terminates after it
static Boolean islongermatch(const Nfamatch *a, const Nfamatch *b)
{
return BGP_CURASINDEX(&a->spos) == BGP_CURASINDEX(&b->spos) &&
BGP_CURASINDEX(&a->epos) > BGP_CURASINDEX(&b->epos);
}
/* An invalid AS INDEX, there can't be a 65535 AS index,
* Given that an AS is at least 2 bytes wide and a legal TPA segment
* is at most of 64K (though in practice its even smaller)
*/
#define BADASIDX 0xffffu
static void clearmatch(Nfamatch *m)
{
m->spos.asIdx = BADASIDX;
m->epos.asIdx = 0;
}
static Boolean isnullmatch(const Nfamatch *m)
{
return BGP_CURASINDEX(&m->spos) == BADASIDX;
}
static void copymatches(Nfamatch *dest, const Nfamatch *src)
{
do *dest++ = *src; while (!isnullmatch(src++));
}
static Boolean addstartinst(Nfalist *clist, Nfainst *start, const Nfa *nfa)
{
Nfastate *s;
// Don't add the instruction twice
for (unsigned i = 0; i < clist->ns; i++) {
s = &clist->list[i];
if (s->ip == start) {
if (isbefore(&nfa->spos, &s->se[0])) {
// Move match position
s->se[0].spos = nfa->spos;
s->se[0].epos.asIdx = 0; // so any end pos is accepted
clearmatch(&s->se[1]);
}
return TRUE;
}
}
if (clist->ns == clist->lim)
return FALSE;
// Append to list
s = &clist->list[clist->ns++];
s->ip = start;
s->se[0].spos = nfa->spos;
s->se[0].epos.asIdx = 0; // so any end pos is accepted
clearmatch(&s->se[1]);
return TRUE;
}
static Boolean addinst(Nfalist *nlist, Nfainst *in, const Nfamatch *se)
{
Nfastate *s;
// Don't add the same instruction twice
for (unsigned i = 0; i < nlist->ns; i++) {
s = &nlist->list[i];
if (s->ip == in) {
if (isbefore(&se[0].spos, &s->se[0]))
copymatches(s->se, se);
return TRUE;
}
}
if (nlist->ns == nlist->lim)
return FALSE; // instruction list overflow
// Append to list
s = &nlist->list[nlist->ns++];
s->ip = in;
copymatches(s->se, se);
return TRUE;
}
static void newmatch(Nfastate *s, Nfa *nfa)
{
// Accept the new match if it is the first one, or it comes before
// a previous match, or if it is a longer match than the previous one
if (nfa->nmatches == 0 ||
isbefore(&s->se[0].spos, &nfa->se[0]) ||
islongermatch(&s->se[0], &nfa->se[0])) {
copymatches(nfa->se, s->se);
}
nfa->nmatches++;
}
static Judgement step(Nfalist *clist, Asn asn, Nfalist *nlist, Nfa *nfa)
{
nlist->ns = 0;
for (unsigned i = 0; i < clist->ns; i++) {
Nfastate *s = &clist->list[i];
Nfainst *ip = s->ip;
eval_next:
switch (ip->type) {
default: UNREACHABLE;
case NOP:
ip = ip->next;
goto eval_next;
case AS:
if (ip->asn == ASN(asn) && !addinst(nlist, ip->next, s->se))
return NG;
break;
case NAS:
if (ip->asn != ASN(asn) && !addinst(nlist, ip->next, s->se))
return NG;
break;
case ANY:
if (asn != ASN_EOL && !addinst(nlist, ip->next, s->se))
return NG;
break;
case BOL:
if (asn & ASNBOLFLAG) {
ip = ip->next;
goto eval_next;
}
break;
case EOL:
if (asn == ASN_EOL) {
ip = ip->next;
goto eval_next;
}
break;
case LPAR:
for (unsigned i = 0; i < ip->grpid; i++)
assert(!isnullmatch(&s->se[i]));
assert(isnullmatch(&s->se[ip->grpid]));
s->se[ip->grpid].spos = nfa->spos;
clearmatch(&s->se[ip->grpid+1]);
ip = ip->next;
goto eval_next;
case ALT:
// Evaluate right branch later IN THIS LIST
if (!addinst(clist, ip->right, s->se))
return NG;
ip = ip->left; // take left branch now
goto eval_next;
case RPAR:
for (unsigned i = 0; i < ip->grpid; i++)
assert(!isnullmatch(&s->se[i]));
assert(!isnullmatch(&s->se[ip->grpid]));
assert( isnullmatch(&s->se[ip->grpid+1]));
s->se[ip->grpid].epos = nfa->cur;
ip = ip->next;
goto eval_next;
case STOP:
// *** MATCH ***
s->se[0].epos = nfa->cur;
newmatch(s, nfa);
break;
}
}
return BGPENOERR;
}
static void collect(Bgpvm *vm, const Nfa *nfa)
{
Boolean isMatching = (nfa->nmatches > 0);
BGP_VMPUSH(vm, isMatching);
vm->curMatch = (Bgpvmmatch *) Bgp_VmTempAlloc(vm, sizeof(*vm->curMatch));
if (!vm->curMatch)
return; // out of memory
Bgpattrseg *tpa = Bgp_GetUpdateAttributes(BGP_MSGUPDATE(vm->msg));
assert(tpa != NULL);
// Generate matches list
Bgpvmasmatch *matches = NULL;
Bgpvmasmatch **pmatches = &matches;
for (unsigned i = 0; !isnullmatch(&nfa->se[i]); i++) {
const Nfamatch *src = &nfa->se[i];
Bgpvmasmatch *dest = (Bgpvmasmatch *) Bgp_VmTempAlloc(vm, sizeof(*dest));
if (!dest)
return; // out of memory
dest->next = *pmatches;
dest->spos = src->spos;
dest->epos = src->epos;
*pmatches = dest;
pmatches = &dest->next;
}
vm->curMatch->pc = BGP_VMCURPC(vm);
vm->curMatch->tag = 0;
vm->curMatch->base = tpa->attrs;
vm->curMatch->lim = &tpa->attrs[beswap16(tpa->len)];
vm->curMatch->pos = matches;
vm->curMatch->isMatching = isMatching;
}
static BgpvmRet execute(Bgpvm *vm, Nfainst *program, unsigned listlen, Nfa *nfa)
{
// Prepare AS PATH iterator
BgpvmRet err = BGPENOERR; // unless found otherwise
if (Bgp_StartMsgRealAsPath(&nfa->cur, vm->msg) != OK) {
vm->errCode = BGPEVMMSGERR;
return vm->errCode;
}
// Setup state lists
Nfalist *clist, *nlist, *t;
size_t listsiz = offsetof(Nfalist, list[listlen]);
if (listlen > LISTSIZ) {
// Allocate on temporary memory
clist = (Nfalist *) Bgp_VmTempAlloc(vm, listsiz);
nlist = (Nfalist *) Bgp_VmTempAlloc(vm, listsiz);
} else {
// Allocate on stack
clist = (Nfalist *) alloca(listsiz);
nlist = (Nfalist *) alloca(listsiz);
}
if (!clist || !nlist)
return vm->errCode;
clist->lim = nlist->lim = listlen;
// Simulate NFA, execute once per ASN (including ASN_BOL and ASN_EOL)
nfa->nmatches = 0; // clear result list
clist->ns = 0; // clear current list for the first time
clearmatch(&nfa->se[0]); // by default no match
Asn asn, flag = ASNBOLFLAG; // first ASN is marked as BOL
do {
// Copy initial position to start
nfa->spos = nfa->cur;
// Always include first instruction if no match took place yet
if (nfa->nmatches == 0 && !addstartinst(clist, program, nfa)) {
// State list overflow
err = BGPEVMASMTCHESIZE;
break;
}
// Fetch new ASN
asn = Bgp_NextAsPath(&nfa->cur);
if (asn == -1 && Bgp_GetErrStat(NULL)) {
err = BGPEVMMSGERR;
break;
}
// Advance NFA evaluating the current ASN
if (step(clist, asn | flag, nlist, nfa) != OK) {
// List overflow
err = BGPEVMASMTCHESIZE;
break;
}
t = clist, clist = nlist, nlist = t; // swap lists
flag = 0; // no more the first ASN
} while (asn != -1);
if (listlen > LISTSIZ) {
Bgp_VmTempFree(vm, listsiz);
Bgp_VmTempFree(vm, listsiz);
}
vm->errCode = err;
return err;
}
void *Bgp_VmCompileAsMatch(Bgpvm *vm, const Asn *expression, size_t n)
{
Nfainst *buf, *prog;
// NOTE: Bgp_VmPermAlloc() already clears VM error and asserts !vm->isRunning
const size_t maxsiz = ALIGN(6 * n * sizeof(*buf), ALIGNMENT);
// we request an already aligned chunk
// so optimize() can make accurate memory adjustments
buf = Bgp_VmPermAlloc(vm, maxsiz);
if (!buf)
return NULL;
Nfacomp nc;
compinit(&nc, buf);
int err;
if ((err = setjmp(nc.oops)) != 0) {
vm->errCode = err;
vm->hLowMark -= maxsiz; // release permanent allocation
return NULL;
}
prog = compile(&nc, expression, n);
optimize(vm, &nc, maxsiz);
#ifdef DF_DEBUG_ASMTCH
dumpprog(prog);
#endif
// vm->errCode = BGPENOERR; - already set by Bgp_VmPermAlloc()
return prog;
}
void Bgp_VmDoAsmtch(Bgpvm *vm)
{
/* POPS:
* -1: Asn match array length
* -2: Address to Asn match array
*
* PUSHES:
* TRUE on successful match, FALSE otherwise
*/
Nfacomp nc;
Nfa nfa;
Nfainst *buf, *prog;
if (!BGP_VMCHKSTK(vm, 2))
return;
// Pop arguments from stack
Sint64 n = BGP_VMPOP(vm);
const Asn *match = (const Asn *) BGP_VMPOPA(vm);
if (n <= 0 || match == NULL) {
vm->errCode = BGPEVMBADASMTCH;
return;
}
if (!BGP_VMCHKMSGTYPE(vm, BGP_UPDATE)) {
Bgp_VmStoreMsgTypeMatch(vm, /*isMatching=*/FALSE);
BGP_VMPUSH(vm, FALSE);
return;
}
// Compile on the fly on temporary memory
const size_t maxsiz = 6 * n * sizeof(*buf);
buf = (Nfainst *) Bgp_VmTempAlloc(vm, maxsiz);
if (!buf)
return;
compinit(&nc, buf);
int err; // compilation status
if ((err = setjmp(nc.oops)) != 0) {
vm->errCode = err;
return;
}
prog = compile(&nc, match, n);
#ifdef DF_DEBUG_ASMTCH
dumpprog(prog);
#endif
BgpvmRet status = execute(vm, prog, LISTSIZ, &nfa);
if (status == BGPEVMASMTCHESIZE)
status = execute(vm, prog, BIGLISTSIZ, &nfa);
Bgp_VmTempFree(vm, maxsiz);
if (status == BGPENOERR)
collect(vm, &nfa);
}
void Bgp_VmDoFasmtc(Bgpvm *vm)
{
/* POPS:
* -1: Precompiled NFA instructions
*
* PUSHES:
* TRUE on successful match, FALSE otherwise
*/
Nfa nfa;
if (!BGP_VMCHKSTK(vm, 1))
return;
Nfainst *prog = (Nfainst *) BGP_VMPOPA(vm);
if (!prog) {
vm->errCode = BGPEVMBADASMTCH;
return;
}
if (!BGP_VMCHKMSGTYPE(vm, BGP_UPDATE)) {
Bgp_VmStoreMsgTypeMatch(vm, /*isMatching=*/FALSE);
BGP_VMPUSH(vm, FALSE);
return;
}
BgpvmRet status = execute(vm, prog, LISTSIZ, &nfa);
if (status == BGPEVMASMTCHESIZE)
status = execute(vm, prog, BIGLISTSIZ, &nfa);
if (status == BGPENOERR)
collect(vm, &nfa);
}

@ -0,0 +1,33 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_cdef.c
*
* Portable implementation for BGP VM execution loop
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* Plain C switch based FETCH-DECODE-DISPATCH-EXECUTE BGP filtering
* engine VM implementation
*
* \note File should be `#include`d by bgp/vm.c
*/
#ifdef DF_BGP_VMDEF_H_
#error "Only one vm_<impl>def.h file may be #include-d"
#endif
#define DF_BGP_VMDEF_H_
#define LIKELY
#define UNLIKELY
#define FETCH(ir, vm) (ir = (vm)->prog[(vm)->pc++])
#define EXPECT(opcode, ir, vm) ((void) 0)
#define DISPATCH(opcode) switch (opcode)
#define EXECUTE(opcode) case BGP_VMOP_ ## opcode
#define EXECUTE_SIGILL default

@ -0,0 +1,103 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_commsort.h
*
* Generic basic sorting and binary searching over unsigned integer arrays.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* The following defines a bunch of static functions to sort
* and search basic integer arrays.
*
* `#define` `UINT_TYPE` with an unsigned <= 4 bytes and `FNSUFFIX`
* before inclusion.
*
* \note No guards, file `#include`d by bgp/vm_communities.c
*/
#define _CAT(X, Y) X ## Y
#define _XCAT(X, Y) _CAT(X, Y)
#define _MANGLE(FN) _XCAT(FN, FNSUFFIX)
static Sint64 _MANGLE(BinarySearch) (const UINT_TYPE *arr,
Uint32 n,
UINT_TYPE v)
{
Uint32 len = n;
Uint32 mid = n;
Sint64 off = 0;
while (mid > 0) {
mid = len >> 1;
if (arr[off+mid] <= v)
off += mid;
len -= mid;
}
return (off < n && arr[off] == v) ? off : -1;
}
static void _MANGLE(Radix) (int off,
const UINT_TYPE *src,
Uint32 n,
UINT_TYPE *dest)
{
const Uint8 *sortKey;
Uint32 index[256];
Uint32 count[256] = { 0 };
for (Uint32 i = 0; i < n; i++) {
sortKey = ((const Uint8 *) &src[i]) + off;
count[*sortKey]++;
}
index[0] = 0;
for (Uint32 i = 1; i < 256; i++)
index[i] = index[i-1] + count[i-1];
for (Uint32 i = 0; i < n; i++) {
sortKey = ((const Uint8 *) &src[i]) + off;
dest[index[*sortKey]++] = src[i];
}
}
static void _MANGLE(RadixSort) (UINT_TYPE *arr, Uint32 n)
{
UINT_TYPE *scratch = (UINT_TYPE *) alloca(n * sizeof(*scratch));
STATIC_ASSERT(sizeof(UINT_TYPE) % 2 == 0, "?!");
if (EDN_NATIVE == EDN_LE) {
for (unsigned i = 0; i < sizeof(UINT_TYPE); i += 2) {
_MANGLE(Radix) (i + 0, arr, n, scratch);
_MANGLE(Radix) (i + 1, scratch, n, arr);
}
} else {
for (unsigned i = sizeof(UINT_TYPE); i > 0; i -= 2) {
_MANGLE(Radix) (i - 1, arr, n, scratch);
_MANGLE(Radix) (i - 2, scratch, n, arr);
}
}
}
static Uint32 _MANGLE(Uniq) (UINT_TYPE *arr, Uint32 n)
{
Uint32 i, j;
if (n == 0) return 0;
for (i = 0, j = 1; j < n; j++) {
if (arr[i] != arr[j])
arr[++i] = arr[j];
}
return ++i;
}
#undef _MANGLE
#undef _XCAT
#undef _CAT

@ -0,0 +1,442 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_communities.c
*
* BGP VM COMTCH, ACOMTC instructions and COMMUNITY index.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "bgp/vmintrin.h"
#include "sys/endian.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
BgpVmOpt opt;
Uint32 hiOnlyCount;
Uint32 loOnlyCount;
Uint32 fullCount;
Uint32 bitsetWords;
Uint32 *bitset;
Uint16 *hiOnly; // parital match on community hi
Uint16 *loOnly; // partial match on community lo
Uint32 full[]; // full matches on whole community codes
// Uint32 bitset[]; <- order preserves alignment requirements
// Uint16 hi[];
// Uint16 lo[];
} Bgpcommidx;
FORCE_INLINE size_t BITSETWIDTH(const Bgpcommidx *idx)
{
return idx->hiOnlyCount + idx->loOnlyCount + idx->fullCount;
}
FORCE_INLINE size_t BITSETLEN(size_t width)
{
return (width >> 5) + ((width & 0x1f) != 0);
}
FORCE_INLINE size_t FULLBITIDX(const Bgpcommidx *idx, Uint32 i)
{
USED(idx);
return i;
}
FORCE_INLINE size_t HIBITIDX(const Bgpcommidx *idx, Uint32 i)
{
return (size_t) idx->fullCount + i;
}
FORCE_INLINE size_t LOBITIDX(const Bgpcommidx *idx, Uint32 i)
{
return (size_t) idx->fullCount + idx->hiOnlyCount + i;
}
FORCE_INLINE Boolean ISBITSET(const Uint32 *bitset, size_t idx)
{
return (bitset[idx >> 5] & (1u << (idx & 0x1f))) != 0;
}
FORCE_INLINE void SETBIT(Uint32 *bitset, size_t idx)
{
bitset[idx >> 5] |= (1u << (idx & 0x1f));
}
FORCE_INLINE void CLRBITSET(Uint32 *bitset, size_t len)
{
memset(bitset, 0, len * sizeof(*bitset));
}
#ifdef __GNUC__
FORCE_INLINE unsigned FindFirstSet(Uint32 x)
{
STATIC_ASSERT(sizeof(x) == sizeof(int), "__builtin_ffs() operates on int");
return __builtin_ffs(x);
}
#else
FORCE_INLINE unsigned FindFirstSet(Uint32 x)
{
if (x == 0) return 0;
unsigned n = 0;
if ((x & 0x0000ffffu) == 0) n += 16, x >>= 16;
if ((x & 0x000000ffu) == 0) n += 8, x >>= 8;
if ((x & 0x0000000fu) == 0) n += 4, x >>= 4;
if ((x & 0x00000003u) == 0) n += 2, x >>= 2;
if ((x & 0x00000001u) == 0) n += 1;
return ++n;
}
#endif
static size_t FFZ(const Uint32 *bitset, size_t len)
{
size_t i;
assert(len > 0);
len--;
for (i = 0; i < len && bitset[i] == 0xffffffffu; i++);
size_t n = i << 6;
n += FindFirstSet(~bitset[i]) - 1;
return n;
}
#define UINT_TYPE Uint16
#define FNSUFFIX 16
#include "bgp/vm_commsort.h"
#undef UINT_TYPE
#undef FNSUFFIX
#define UINT_TYPE Uint32
#define FNSUFFIX 32
#include "bgp/vm_commsort.h"
#undef UINT_TYPE
#undef FNSUFFIX
static Boolean MatchCommunity(const Bgpcomm *c, const Bgpcommidx *idx)
{
return BinarySearch16(idx->hiOnly, idx->hiOnlyCount, c->hi) >= 0 ||
BinarySearch16(idx->loOnly, idx->loOnlyCount, c->lo) >= 0 ||
BinarySearch32(idx->full, idx->fullCount, c->code) >= 0;
}
static void OptimizeComtch(Bgpcommidx *idx)
{
// Remove every full match more specific than an existing partial match.
// NOTE: Assumes arrays have been sorted and Uniq()d
Uint32 i, j;
Bgpcomm c;
for (i = 0, j = 0; i < idx->fullCount; i++) {
c.code = idx->full[i];
if (BinarySearch16(idx->hiOnly, idx->hiOnlyCount, c.hi) >= 0 ||
BinarySearch16(idx->loOnly, idx->loOnlyCount, c.lo) >= 0)
continue;
idx->full[j++] = idx->full[i];
}
idx->fullCount = j;
}
static void OptimizeAcomtc(Bgpcommidx *idx)
{
// Remove every partial match less specific than an existing full match
// NOTE: Assumes arrays have been sorted and Uniq()d
Uint32 i, j;
// Mark redundant entries in bitset
CLRBITSET(idx->bitset, idx->bitsetWords);
for (i = 0; i < idx->fullCount; i++) {
Sint64 pos;
Bgpcomm c;
c.code = idx->full[i];
pos = BinarySearch16(idx->hiOnly, idx->hiOnlyCount, c.hi);
if (pos >= 0)
SETBIT(idx->bitset, HIBITIDX(idx, pos));
pos = BinarySearch16(idx->loOnly, idx->loOnlyCount, c.lo);
if (pos >= 0)
SETBIT(idx->bitset, LOBITIDX(idx, pos));
}
// Remove redundant entries
for (i = 0, j = 0; i < idx->hiOnlyCount; i++) {
if (!ISBITSET(idx->bitset, HIBITIDX(idx, i)))
idx->hiOnly[j++] = idx->hiOnly[i];
}
idx->hiOnlyCount = j;
for (i = 0, j = 0; i < idx->loOnlyCount; i++) {
if (!ISBITSET(idx->bitset, LOBITIDX(idx, i)))
idx->loOnly[j++] = idx->loOnly[i];
}
idx->loOnlyCount = j;
}
static void CompactIndex(Bgpvm *vm, Bgpcommidx *idx, size_t idxSize)
{
size_t offset = offsetof(Bgpcommidx, full[idx->fullCount]);
size_t bitsetSiz = idx->bitsetWords * sizeof(*idx->bitset);
size_t hiSiz = idx->hiOnlyCount * sizeof(*idx->hiOnly);
size_t loSiz = idx->loOnlyCount * sizeof(*idx->loOnly);
Uint8 *ptr = (Uint8 *) idx + offset;
idx->bitset = (Uint32 *) memmove(ptr, idx->bitset, bitsetSiz);
ptr += bitsetSiz;
idx->hiOnly = (Uint16 *) memmove(ptr, idx->hiOnly, hiSiz);
ptr += hiSiz;
idx->loOnly = (Uint16 *) memmove(ptr, idx->loOnly, loSiz);
ptr += loSiz;
size_t siz = ptr - (Uint8 *) idx;
siz = ALIGN(siz, ALIGNMENT);
offset = idxSize - siz;
vm->hLowMark -= offset;
}
void *Bgp_VmCompileCommunityMatch(Bgpvm *vm,
const Bgpmatchcomm *match,
size_t n,
BgpVmOpt opt)
{
// NOTE: Bgp_VmPermAlloc() already clears VM error and asserts !vm->isRunning
Sint32 nlow = 0, nhigh = 0, nfull = 0, nbitswords = 0;
for (size_t i = 0; i < n; i++) {
const Bgpmatchcomm *m = &match[i];
if (m->maskLo && m->maskHi) {
vm->errCode = BGPEVMBADCOMTCH;
return NULL;
}
if (m->maskLo)
nhigh++;
else if (m->maskHi)
nlow++;
else
nfull++;
}
Bgpcommidx *idx;
size_t offBits = offsetof(Bgpcommidx, full[nfull]);
if (opt != BGP_VMOPT_ASSUME_COMTCH)
nbitswords = BITSETLEN(nlow + nhigh + nfull); // must allocate bitset
size_t offHigh = offBits + nbitswords * sizeof(*idx->bitset);
size_t offLow = offHigh + nhigh * sizeof(*idx->hiOnly);
size_t nbytes = offLow + nlow * sizeof(*idx->loOnly);
nbytes = ALIGN(nbytes, ALIGNMENT);
idx = Bgp_VmPermAlloc(vm, nbytes);
if (!idx)
return NULL;
idx->bitset = (Uint32 *) ((Uint8 *) idx + offBits);
idx->hiOnly = (Uint16 *) ((Uint8 *) idx + offHigh);
idx->loOnly = (Uint16 *) ((Uint8 *) idx + offLow);
idx->opt = opt;
idx->bitsetWords = nbitswords;
idx->hiOnlyCount = idx->loOnlyCount = idx->fullCount = 0;
for (size_t i = 0; i < n; i++) {
const Bgpmatchcomm *m = &match[i];
if (m->maskLo)
idx->hiOnly[idx->hiOnlyCount++] = m->c.hi;
else if (m->maskHi)
idx->loOnly[idx->loOnlyCount++] = m->c.lo;
else
idx->full[idx->fullCount++] = m->c.code;
}
// Sort lookup arrays
RadixSort16(idx->hiOnly, idx->hiOnlyCount);
RadixSort16(idx->loOnly, idx->loOnlyCount);
RadixSort32(idx->full, idx->fullCount);
// Optimize tables
idx->hiOnlyCount = Uniq16(idx->hiOnly, idx->hiOnlyCount);
idx->loOnlyCount = Uniq16(idx->loOnly, idx->loOnlyCount);
idx->fullCount = Uniq32(idx->full, idx->fullCount);
// Discard redundant entries
switch (opt) {
case BGP_VMOPT_ASSUME_COMTCH: OptimizeComtch(idx); break;
case BGP_VMOPT_ASSUME_ACOMTC: OptimizeAcomtc(idx); break;
default:
case BGP_VMOPT_NONE:
break;
}
// Free-up excess memory after optimization
CompactIndex(vm, idx, nbytes);
return idx;
}
static Bgpattr *Bgp_VmDoComSetup(Bgpvm *vm, Bgpcommiter *it, BgpAttrCode code)
{
Bgpupdate *update = (Bgpupdate *) BGP_VMCHKMSGTYPE(vm, BGP_UPDATE);
if (!update) {
Bgp_VmStoreMsgTypeMatch(vm, /*isMatching=*/FALSE);
return NULL;
}
Bgpattrseg *tpa = Bgp_GetUpdateAttributes(update);
if (!tpa) {
vm->errCode = BGPEVMMSGERR;
return NULL;
}
Bgpattr *attr = Bgp_GetUpdateAttribute(tpa, code, vm->msg->table);
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return NULL;
}
if (attr)
Bgp_StartCommunity(it, attr);
return attr;
}
void Bgp_VmDoComtch(Bgpvm *vm)
{
if (!BGP_VMCHKSTKSIZ(vm, 1))
return;
Bgpcommidx *idx = (Bgpcommidx *) BGP_VMPOPA(vm);
if (!idx || idx->opt == BGP_VMOPT_ASSUME_ACOMTC) {
vm->errCode = BGPEVMBADCOMTCH; // TODO: BGPEVMBADCOMIDX;
return;
}
Boolean isMatching = FALSE; // unless found otherwise
Bgpcommiter it;
Bgpattr *attr = Bgp_VmDoComSetup(vm, &it, BGP_ATTR_COMMUNITY);
if (vm->errCode)
return;
if (!attr)
goto done;
Bgpcomm *c;
while ((c = Bgp_NextCommunity(&it)) != NULL) {
if (MatchCommunity(c, idx)) {
isMatching = TRUE;
break;
}
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
done:
BGP_VMPUSH(vm, isMatching);
}
static void ScMatchCommunityAndSetBit(const Bgpcomm *c, Bgpcommidx *idx)
{
Sint64 pos = BinarySearch16(idx->hiOnly, idx->hiOnlyCount, c->hi);
if (pos >= 0) {
SETBIT(idx->bitset, HIBITIDX(idx, pos));
return;
}
pos = BinarySearch16(idx->loOnly, idx->loOnlyCount, c->lo);
if (pos >= 0) {
SETBIT(idx->bitset, LOBITIDX(idx, pos));
return;
}
pos = BinarySearch32(idx->full, idx->fullCount, c->code);
if (pos >= 0)
SETBIT(idx->bitset, FULLBITIDX(idx, pos));
}
static void MatchCommunityAndSetBit(const Bgpcomm *c, Bgpcommidx *idx)
{
Sint64 pos = BinarySearch16(idx->hiOnly, idx->hiOnlyCount, c->hi);
if (pos >= 0)
SETBIT(idx->bitset, HIBITIDX(idx, pos));
pos = BinarySearch16(idx->loOnly, idx->loOnlyCount, c->lo);
if (pos >= 0)
SETBIT(idx->bitset, LOBITIDX(idx, pos));
pos = BinarySearch32(idx->full, idx->fullCount, c->code);
if (pos >= 0)
SETBIT(idx->bitset, FULLBITIDX(idx, pos));
}
static Boolean Bgp_VmDoAcomtcFast(Bgpcommidx *idx, Bgpcommiter *it)
{
Bgpcomm *c;
while ((c = Bgp_NextCommunity(it)) != NULL)
ScMatchCommunityAndSetBit(c, idx);
return FFZ(idx->bitset, idx->bitsetWords) == BITSETWIDTH(idx);
}
static Boolean Bgp_VmDoAcomtcSlow(Bgpcommidx *idx, Bgpcommiter *it)
{
Bgpcomm *c;
while ((c = Bgp_NextCommunity(it)) != NULL)
MatchCommunityAndSetBit(c, idx);
return FFZ(idx->bitset, idx->bitsetWords) == BITSETWIDTH(idx);
}
void Bgp_VmDoAcomtc(Bgpvm *vm)
{
if (!BGP_VMCHKSTKSIZ(vm, 1))
return;
Bgpcommidx *idx = (Bgpcommidx *) BGP_VMPOPA(vm);
if (!idx || idx->opt == BGP_VMOPT_ASSUME_COMTCH) {
vm->errCode = BGPEVMBADCOMTCH; // TODO: BGPEVMBADCOMIDX;
return;
}
Boolean isMatching = FALSE;
Bgpcommiter it;
Bgpattr *attr = Bgp_VmDoComSetup(vm, &it, BGP_ATTR_COMMUNITY);
if (vm->errCode)
return;
if (!attr)
goto done;
CLRBITSET(idx->bitset, idx->bitsetWords);
if (idx->opt == BGP_VMOPT_ASSUME_ACOMTC)
isMatching = Bgp_VmDoAcomtcFast(idx, &it);
else
isMatching = Bgp_VmDoAcomtcSlow(idx, &it);
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
done:
BGP_VMPUSH(vm, isMatching);
}

@ -0,0 +1,303 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_dump.c
*
* BGP VM bytecode dump.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/vmintrin.h"
#include "sys/dbg.h"
#include "numlib.h"
#include "strlib.h"
#include <assert.h>
#include <string.h>
#define LINEDIGS 5
#define MAXOPCSTRLEN 6
#define CODELINELEN 50
#define MAXINDENT 8
#define INDENTLEN 2
#define COMMENTLEN 70
static const char *OpcString(Bgpvmopc opc)
{
// NOTE: Invariant: strlen(return) <= MAXOPCSTRLEN
switch (opc) {
case BGP_VMOP_NOP: return "NOP";
case BGP_VMOP_LOAD: return "LOAD";
case BGP_VMOP_LOADU: return "LOADU";
case BGP_VMOP_LOADN: return "LOADN";
case BGP_VMOP_LOADK: return "LOADK";
case BGP_VMOP_CALL: return "CALL";
case BGP_VMOP_BLK: return "BLK";
case BGP_VMOP_ENDBLK: return "ENDBLK";
case BGP_VMOP_TAG: return "TAG";
case BGP_VMOP_NOT: return "NOT";
case BGP_VMOP_CFAIL: return "CFAIL";
case BGP_VMOP_CPASS: return "CPASS";
case BGP_VMOP_JZ: return "JZ";
case BGP_VMOP_JNZ: return "JNZ";
case BGP_VMOP_CHKT: return "CHKT";
case BGP_VMOP_CHKA: return "CHKA";
case BGP_VMOP_EXCT: return "EXCT";
case BGP_VMOP_SUPN: return "SUPN";
case BGP_VMOP_SUBN: return "SUBN";
case BGP_VMOP_RELT: return "RELT";
case BGP_VMOP_ASMTCH: return "ASMTCH";
case BGP_VMOP_FASMTC: return "FASMTC";
case BGP_VMOP_COMTCH: return "COMTCH";
case BGP_VMOP_ACOMTC: return "ACOMTC";
case BGP_VMOP_END: return "END";
default: return "???";
}
}
static const char *BgpTypeString(BgpType typ)
{
switch (typ) {
case BGP_OPEN: return "OPEN";
case BGP_UPDATE: return "UPDATE";
case BGP_NOTIFICATION: return "NOTIFICATION";
case BGP_KEEPALIVE: return "KEEPALIVE";
case BGP_ROUTE_REFRESH: return "ROUTE_REFRESH";
case BGP_CLOSE: return "CLOSE";
default: return NULL;
}
}
static const char *BgpAttrString(BgpAttrCode code)
{
switch (code) {
case BGP_ATTR_ORIGIN: return "ORIGIN";
case BGP_ATTR_AS_PATH: return "AS_PATH";
case BGP_ATTR_NEXT_HOP: return "NEXT_HOP";
case BGP_ATTR_MULTI_EXIT_DISC: return "MULTI_EXIT_DISC";
case BGP_ATTR_LOCAL_PREF: return "LOCAL_PREF";
case BGP_ATTR_ATOMIC_AGGREGATE: return "ATOMIC_AGGREGATE";
case BGP_ATTR_AGGREGATOR: return "AGGREGATOR";
case BGP_ATTR_COMMUNITY: return "COMMUNITY";
case BGP_ATTR_ORIGINATOR_ID: return "ORIGINATOR_ID";
case BGP_ATTR_CLUSTER_LIST: return "CLUSTER_LIST";
case BGP_ATTR_DPA: return "DPA";
case BGP_ATTR_ADVERTISER: return "ADVERTISER";
case BGP_ATTR_RCID_PATH_CLUSTER_ID: return "RCID_PATH_CLUSTER_ID";
case BGP_ATTR_MP_REACH_NLRI: return "MP_REACH_NLRI";
case BGP_ATTR_MP_UNREACH_NLRI: return "MP_UNREACH_NLRI";
case BGP_ATTR_EXTENDED_COMMUNITY: return "EXTENDED_COMMUNITY";
case BGP_ATTR_AS4_PATH: return "AS4_PATH";
case BGP_ATTR_AS4_AGGREGATOR: return "AS4_AGGREGATOR";
case BGP_ATTR_SAFI_SSA: return "SAFI_SSA";
case BGP_ATTR_CONNECTOR: return "CONNECTOR";
case BGP_ATTR_AS_PATHLIMIT: return "AS_PATHLIMIT";
case BGP_ATTR_PMSI_TUNNEL: return "PMSI_TUNNEL";
case BGP_ATTR_TUNNEL_ENCAPSULATION: return "TUNNEL_ENCAPSULATION";
case BGP_ATTR_TRAFFIC_ENGINEERING: return "TRAFFIC_ENGINEERING";
case BGP_ATTR_IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITY: return "IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITY";
case BGP_ATTR_AIGP: return "AIGP";
case BGP_ATTR_PE_DISTINGUISHER_LABELS: return "PE_DISTINGUISHER_LABELS";
case BGP_ATTR_ENTROPY_LEVEL_CAPABILITY: return "ENTROPY_LEVEL_CAPABILITY";
case BGP_ATTR_LS: return "LS";
case BGP_ATTR_LARGE_COMMUNITY: return "LARGE_COMMUNITY";
case BGP_ATTR_BGPSEC_PATH: return "BGPSEC_PATH";
case BGP_ATTR_COMMUNITY_CONTAINER: return "COMMUNITY_CONTAINER";
case BGP_ATTR_PREFIX_SID: return "PREFIX_SID";
case BGP_ATTR_SET: return "SET";
default: return NULL;
}
}
static const char *NetOpArgString(Uint8 opa)
{
switch (opa) {
case BGP_VMOPA_NLRI: return "NLRI";
case BGP_VMOPA_MPREACH: return "MP_REACH_NLRI";
case BGP_VMOPA_ALL_NLRI: return "ALL_NLRI";
case BGP_VMOPA_WITHDRAWN: return "WITHDRAWN";
case BGP_VMOPA_MPUNREACH: return "MP_UNREACH_NLRI";
case BGP_VMOPA_ALL_WITHDRAWN: return "ALL_WITHDRAWN";
default: return NULL;
}
}
static char *ExplainJump(char *buf, Uint32 ip, Uint8 disp, Uint32 progLen)
{
char *p = buf;
strcpy(p, "to line: "); p += 9;
Uint32 target = ip + 1;
target += 1 + disp;
p = Utoa(target, p);
if (target > progLen)
strcpy(p, " (JUMP TARGET OUT OF BOUNDS!)");
return buf;
}
static char *Indent(char *p, Bgpvmopc opc, int level)
{
int n = CLAMP(level, 0, MAXINDENT);
int last = n - 1;
for (int i = 0; i < n; i++) {
*p++ = (opc == BGP_VMOP_ENDBLK && i == last) ? '+' : '|';
for (int j = 1; j < INDENTLEN; j++)
*p++ = (opc == BGP_VMOP_ENDBLK && i == last) ? '-' : ' ';
}
return p;
}
static char *CommentCodeLine(char *line, const char *comment)
{
char *p = line;
p += Df_strpadr(p, ' ', CODELINELEN);
*p++ = ' ';
*p++ = ';'; *p++ = ' ';
size_t n = strlen(comment);
if (3 + n >= COMMENTLEN) {
n = COMMENTLEN - 3 - 3;
memcpy(p, comment, n);
p += n;
*p++ = '.'; *p++ = '.'; *p++ = '.';
} else {
memcpy(p, comment, n);
p += n;
}
*p = '\0';
return p;
}
void Bgp_VmDumpProgram(Bgpvm *vm, void *streamp, const StmOps *ops)
{
char explainbuf[64];
char buf[256];
int indent = 0;
// NOTE: <= so it includes trailing END
for (Uint32 ip = 0; ip <= vm->progLen; ip++) {
Bgpvmbytec ir = vm->prog[ip];
Bgpvmopc opc = BGP_VMOPC(ir);
Uint8 opa = BGP_VMOPARG(ir);
const char *opcnam = OpcString(opc);
assert(strlen(opcnam) <= MAXOPCSTRLEN);
char *p = buf;
// Line number
Utoa(ip+1, p);
p += Df_strpadl(p, '0', LINEDIGS);
*p++ = ':';
*p++ = ' ';
// Instruction hex dump
*p++ = '0';
*p++ = 'x';
Xtoa(ir, p);
p += Df_strpadl(p, '0', XDIGS(ir));
*p++ = ' ';
// Code indent
p = Indent(p, opc, indent);
// Opcode
strcpy(p, opcnam);
p += Df_strpadr(p, ' ', MAXOPCSTRLEN);
// Instruction argument
const char *opastr = NULL;
switch (opc) {
case BGP_VMOP_LOAD:
*p++ = ' ';
p = Itoa((Sint8) opa, p);
break;
case BGP_VMOP_LOADU:
case BGP_VMOP_JZ:
case BGP_VMOP_JNZ:
*p++ = ' ';
p = Utoa(opa, p);
if (opc == BGP_VMOP_JZ || opc == BGP_VMOP_JNZ)
opastr = ExplainJump(explainbuf, ip, opa, vm->progLen);
break;
case BGP_VMOP_TAG:
case BGP_VMOP_CHKT:
case BGP_VMOP_CHKA:
case BGP_VMOP_EXCT:
case BGP_VMOP_SUBN:
case BGP_VMOP_SUPN:
case BGP_VMOP_RELT:
*p++ = ' ';
*p++ = '0'; *p++ = 'x';
Xtoa(opa, p);
p += Df_strpadl(p, '0', XDIGS(opa));
if (opc == BGP_VMOP_CHKT)
opastr = BgpTypeString(opa);
else if (opc == BGP_VMOP_CHKA)
opastr = BgpAttrString(opa);
else
opastr = NetOpArgString(opa);
break;
case BGP_VMOP_LOADK:
*p++ = ' ';
*p++ = 'K';
*p++ = '[';
p = Utoa(opa, p);
*p++ = ']';
*p = '\0';
break;
case BGP_VMOP_CALL:
*p++ = ' ';
*p++ = 'F';
*p++ = 'N';
*p++ = '[';
p = Utoa(opa, p);
*p++ = ']';
*p = '\0';
if (opa < vm->nfuncs) {
Funsym fsym;
fsym.func = (void (*)(void)) vm->funcs[opa];
opastr = Sys_GetSymbolName(fsym.sym);
}
break;
default:
break;
}
// Optional comment after CODELINELEN columns
if (opastr)
p = CommentCodeLine(buf, opastr);
// Flush line (no need for '\0')
*p++ = '\n';
ops->Write(streamp, buf, p - buf);
// Update indent
if (opc == BGP_VMOP_BLK)
indent++;
if (opc == BGP_VMOP_ENDBLK)
indent--;
}
}

@ -0,0 +1,66 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_gccdef.h
*
* `#define`s for GNUC optimized BGP VM execution loop.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* \note This file should be `#include`d by `bgp/vm.c`
*/
#ifdef DF_BGP_VMDEF_H_
#error "Only one vm_<impl>def.h file may be #include-d"
#endif
#define DF_BGP_VMDEF_H_
#define _CONCAT(x, y) x ## y
#define _XCONCAT(x, y) _CONCAT(x, y)
#ifdef __clang__
// No __attribute__ on labels in CLANG
#define LIKELY
#else
#define LIKELY \
_XCONCAT(_BRANCH_PREDICT_HINT, __COUNTER__): \
__attribute__((__hot__, __unused__))
#endif
#ifdef __clang__
// No __attribute__ on labels in CLANG
#define UNLIKELY
#else
#define UNLIKELY \
_XCONCAT(_BRANCH_PREDICT_HINT, __COUNTER__): \
__attribute__((__cold__, __unused__))
#endif
#define FETCH(ir, vm) (ir = (vm)->prog[(vm)->pc++])
#define EXPECT(opcode, ir, vm) \
do { \
if (__builtin_expect( \
BGP_VMOPC((vm)->prog[(vm)->pc]) == BGP_VMOP_ ## opcode, \
1 \
)) { \
ir = (vm)->prog[(vm)->pc++]; \
goto EX_ ## opcode; \
} \
} while (0)
#define DISPATCH(opcode) \
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wpedantic\"") \
goto *bgp_vmOpTab[opcode]; \
_Pragma("GCC diagnostic pop") \
switch (opcode) // This keeps consistency with regular vm_cdef.h
#define EXECUTE(opcode) case BGP_VMOP_ ## opcode: EX_ ## opcode
#define EXECUTE_SIGILL default: EX_SIGILL

@ -0,0 +1,98 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_optab.h
*
* Computed goto table for GNUC optimized BGP VM execution loop.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* File defines a GNUC-specific computed goto table.
*
* It can be used as an alternative to a regular switch to
* accelerate the BGP filtering engine execution loop (Bgp_VmExec()).
*
* Constraints:
* - Array length: 256 (8-bit OPCODE width)
* - Label address MUST follow the convention EX_<OPCODE NAME> (e.g. EX_NOP, EX_EXCT)
* - Any unused OPCODE MUST be set to EX_SIGILL
*
* Operations on the computed goto table are defined in: bgp/vm_gccdef.h
*
* \see [GCC Documentation](https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html)
*
* \warning KEEP TABLE IN SYNC WITH OPCODES IN: bgp/vm.h
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#pragma GCC diagnostic ignored "-Woverride-init"
static void *const bgp_vmOpTab[256] = {
// Following clears everything else in the array to SIGILL,
// 8 instructions per line (256 &&EX_SIGILL)
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
// Following initializes valid OPCODEs
[BGP_VMOP_NOP] = &&EX_NOP,
[BGP_VMOP_LOAD] = &&EX_LOAD,
[BGP_VMOP_LOADU] = &&EX_LOADU,
[BGP_VMOP_LOADN] = &&EX_LOADN,
[BGP_VMOP_LOADK] = &&EX_LOADK,
[BGP_VMOP_CALL] = &&EX_CALL,
[BGP_VMOP_BLK] = &&EX_BLK,
[BGP_VMOP_ENDBLK] = &&EX_ENDBLK,
[BGP_VMOP_TAG] = &&EX_TAG,
[BGP_VMOP_NOT] = &&EX_NOT,
[BGP_VMOP_CFAIL] = &&EX_CFAIL,
[BGP_VMOP_CPASS] = &&EX_CPASS,
[BGP_VMOP_JZ] = &&EX_JZ,
[BGP_VMOP_JNZ] = &&EX_JNZ,
[BGP_VMOP_CHKT] = &&EX_CHKT,
[BGP_VMOP_CHKA] = &&EX_CHKA,
[BGP_VMOP_EXCT] = &&EX_EXCT,
[BGP_VMOP_SUPN] = &&EX_SUPN,
[BGP_VMOP_SUBN] = &&EX_SUBN,
[BGP_VMOP_RELT] = &&EX_RELT,
[BGP_VMOP_ASMTCH] = &&EX_ASMTCH,
[BGP_VMOP_FASMTC] = &&EX_FASMTC,
[BGP_VMOP_COMTCH] = &&EX_COMTCH,
[BGP_VMOP_ACOMTC] = &&EX_ACOMTC,
[BGP_VMOP_END] = &&EX_END
};
#pragma GCC diagnostic pop

@ -0,0 +1,119 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bufio.c
*
* I/O stream buffering utilities implementation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/sys_local.h" // for Sys_SetErrStat() - vsnprintf()
#include "bufio.h"
#include "numlib.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
Sint64 Bufio_Flush(Stmbuf *sb)
{
assert(sb->ops->Write);
while (sb->len > 0) {
Sint64 n = sb->ops->Write(sb->streamp, sb->buf, sb->len);
if (n < 0)
return NG;
memmove(sb->buf, sb->buf + n, sb->len - n);
sb->len -= n;
sb->total += n;
}
return sb->total;
}
Sint64 _Bufio_Putsn(Stmbuf *sb, const char *s, size_t nbytes)
{
if (sb->len + nbytes > sizeof(sb->buf) && Bufio_Flush(sb) == -1)
return -1;
if (nbytes > sizeof(sb->buf))
return sb->ops->Write(sb->streamp, sb, nbytes);
memcpy(sb->buf + sb->len, s, nbytes);
sb->len += nbytes;
return nbytes;
}
Sint64 Bufio_Putu(Stmbuf *sb, unsigned long long val)
{
char buf[DIGS(val) + 1];
char *eptr = Utoa(val, buf);
return Bufio_Putsn(sb, buf, eptr - buf);
}
Sint64 Bufio_Putx(Stmbuf *sb, unsigned long long val)
{
char buf[XDIGS(val) + 1];
char *eptr = Xtoa(val, buf);
return Bufio_Putsn(sb, buf, eptr - buf);
}
Sint64 Bufio_Puti(Stmbuf *sb, long long val)
{
char buf[1 + DIGS(val) + 1];
char *eptr = Itoa(val, buf);
return Bufio_Putsn(sb, buf, eptr - buf);
}
Sint64 Bufio_Putf(Stmbuf *sb, double val)
{
char buf[DOUBLE_STRLEN + 1];
char *eptr = Ftoa(val, buf);
return Bufio_Putsn(sb, buf, eptr - buf);
}
Sint64 Bufio_Printf(Stmbuf *sb, const char *fmt, ...)
{
va_list va;
Sint64 n;
va_start(va, fmt);
n = Bufio_Vprintf(sb, fmt, va);
va_end(va);
return n;
}
Sint64 Bufio_Vprintf(Stmbuf *sb, const char *fmt, va_list va)
{
va_list vc;
char *buf;
int n1, n2;
va_copy(vc, va);
n1 = vsnprintf(NULL, 0, fmt, vc);
va_end(vc);
if (n1 < 0) {
Sys_SetErrStat(errno, "vsnprintf() failed");
return -1;
}
buf = (char *) alloca(n1 + 1);
n2 = vsnprintf(buf, n1 + 1, fmt, va);
if (n2 < 0) {
Sys_SetErrStat(errno, "vsnprintf() failed");
return -1;
}
assert(n1 == n2);
return Bufio_Putsn(sb, buf, n2);
}

@ -0,0 +1,328 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file cpr/bzip2.c
*
* Interfaces with `libbzip2` and implements BZ2 compressor/decompressor.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "cpr/bzip2.h"
#include <bzlib.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
typedef struct Bzip2StmObj Bzip2StmObj;
struct Bzip2StmObj {
bz_stream bz2;
void *streamp;
const StmOps *ops;
unsigned bufsiz;
Boolean8 compressing;
char buf[FLEX_ARRAY]; // `bufsiz' bytes
};
#define BZIP2_EBADSTREAM 0xffff
#define BZIP2_BUFSIZ (32 * 1024)
#define MAKESINT64(lo32, hi32) \
((Sint64) ((Uint64) (lo32) | (((Uint64) hi32) << 32)))
static Sint64 Bzip2_FlushData(Bzip2StmHn hn)
{
size_t nbytes = hn->bufsiz - hn->bz2.avail_out;
Sint64 n = hn->ops->Write(hn->streamp, hn->buf, nbytes);
if (n <= 0)
return -1;
size_t left = nbytes - n;
memmove(hn->buf, hn->buf + n, left);
hn->bz2.next_out = hn->buf + left;
hn->bz2.avail_out = hn->bufsiz - left;
return n;
}
static Sint64 Bzip2_StmRead(void *streamp, void *buf, size_t nbytes)
{
return Bzip2_Read((Bzip2StmHn) streamp, buf, nbytes);
}
static Sint64 Bzip2_StmWrite(void *streamp, const void *buf, size_t nbytes)
{
return Bzip2_Write((Bzip2StmHn) streamp, buf, nbytes);
}
static Sint64 Bzip2_StmTell(void *streamp)
{
Bzip2StmHn hn = (Bzip2StmHn) streamp;
return hn->compressing ?
MAKESINT64(hn->bz2.total_out_lo32, hn->bz2.total_out_hi32) :
MAKESINT64(hn->bz2.total_in_lo32, hn->bz2.total_in_hi32);
}
static Judgement Bzip2_StmFinish(void *streamp)
{
return Bzip2_Finish((Bzip2StmHn) streamp);
}
static void Bzip2_StmClose(void *streamp)
{
Bzip2_Close((Bzip2StmHn) streamp);
}
static const StmOps bzip2_stmOps = {
Bzip2_StmRead,
Bzip2_StmWrite,
NULL,
Bzip2_StmTell,
Bzip2_StmFinish,
Bzip2_StmClose
};
static const StmOps bzip2_ncStmOps = {
Bzip2_StmRead,
Bzip2_StmWrite,
NULL,
Bzip2_StmTell,
Bzip2_StmFinish,
NULL
};
const StmOps *const Bzip2_StmOps = &bzip2_stmOps;
const StmOps *const Bzip2_NcStmOps = &bzip2_ncStmOps;
static THREAD_LOCAL Bzip2Ret bzip2_errStat = 0;
static void Bzip2_SetErrStat(Bzip2Ret ret)
{
bzip2_errStat = ret;
}
Bzip2Ret Bzip2_GetErrStat(void)
{
return bzip2_errStat;
}
const char *Bzip2_ErrorString(Bzip2Ret ret)
{
switch (ret) {
case BZIP2_EBADSTREAM: return "Bad stream operation";
case BZ_OK: return "Success";
case BZ_SEQUENCE_ERROR: return "Sequence error";
case BZ_PARAM_ERROR: return "Invalid parameter";
case BZ_MEM_ERROR: return "Memory allocation failure";
case BZ_DATA_ERROR: return "Data integrity error";
case BZ_DATA_ERROR_MAGIC: return "Stream magic number mismatch";
case BZ_IO_ERROR: return "I/O error";
case BZ_UNEXPECTED_EOF: return "Unexpected compressed stream end";
case BZ_OUTBUFF_FULL: return "Output buffer full";
case BZ_CONFIG_ERROR: return "Bzip2 library configuration error";
default: return "Unknown BZ2 error";
}
}
Bzip2StmHn Bzip2_OpenCompress(void *streamp,
const StmOps *ops,
const Bzip2CprOpts *opts)
{
const Bzip2CprOpts defOpts = { 0, 0, 0, 0 };
if (!opts)
opts = &defOpts;
if (!ops->Write) {
Bzip2_SetErrStat(BZIP2_EBADSTREAM);
return NULL;
}
int compression = CLAMP(opts->compression, 0, 9);
if (compression == 0)
compression = 9; // default value
int verbosity = CLAMP(opts->verbose, 0, 4);
int factor = opts->factor;
size_t bufsiz = MIN(opts->bufsiz, INT_MAX);
if (bufsiz == 0)
bufsiz = BZIP2_BUFSIZ;
Bzip2StmObj *hn = (Bzip2StmObj *) malloc(offsetof(Bzip2StmObj, buf) + bufsiz);
if (!hn) {
Bzip2_SetErrStat(BZ_MEM_ERROR);
return NULL;
}
memset(&hn->bz2, 0, sizeof(hn->bz2));
hn->streamp = streamp;
hn->ops = ops;
hn->bufsiz = bufsiz;
hn->compressing = TRUE;
int err = BZ2_bzCompressInit(&hn->bz2, compression, verbosity, factor);
if (err != BZ_OK) {
Bzip2_SetErrStat(err);
free(hn);
return NULL;
}
hn->bz2.next_out = hn->buf;
hn->bz2.avail_out = hn->bufsiz;
Bzip2_SetErrStat(BZ_OK);
return hn;
}
Bzip2StmHn Bzip2_OpenDecompress(void *streamp,
const StmOps *ops,
const Bzip2DecOpts *opts)
{
const Bzip2DecOpts defOpts = { 0, 0, FALSE };
if (!opts)
opts = &defOpts;
if (!ops->Read) {
Bzip2_SetErrStat(BZIP2_EBADSTREAM);
return NULL;
}
int small = opts->low_mem;
int verbosity = CLAMP(opts->verbose, 0, 4);
size_t bufsiz = MIN(opts->bufsiz, INT_MAX);
if (bufsiz == 0)
bufsiz = BZIP2_BUFSIZ;
Bzip2StmObj *hn = (Bzip2StmObj *) malloc(offsetof(Bzip2StmObj, buf[bufsiz]));
if (!hn) {
Bzip2_SetErrStat(BZ_MEM_ERROR);
return NULL;
}
memset(&hn->bz2, 0, sizeof(hn->bz2));
hn->streamp = streamp;
hn->ops = ops;
hn->bufsiz = bufsiz;
hn->compressing = FALSE;
int err = BZ2_bzDecompressInit(&hn->bz2, verbosity, small);
if (err != BZ_OK) {
Bzip2_SetErrStat(err);
free(hn);
return NULL;
}
Bzip2_SetErrStat(BZ_OK);
return hn;
}
Sint64 Bzip2_Read(Bzip2StmHn hn, void *buf, size_t nbytes)
{
if (hn->compressing) {
Bzip2_SetErrStat(BZIP2_EBADSTREAM);
return -1;
}
Bzip2Ret ret = BZ_OK;
hn->bz2.next_out = (char *) buf;
hn->bz2.avail_out = nbytes;
while (hn->bz2.avail_out > 0) {
if (hn->bz2.avail_in == 0) {
Sint64 n = hn->ops->Read(hn->streamp, hn->buf, hn->bufsiz);
if (n <= 0) {
if (n < 0) ret = BZ_IO_ERROR;
break; // EOF
}
hn->bz2.next_in = hn->buf;
hn->bz2.avail_in = n;
}
int err = BZ2_bzDecompress(&hn->bz2);
if (err == BZ_STREAM_END)
break;
if (err != BZ_OK) {
ret = err;
break;
}
}
Bzip2_SetErrStat(ret);
return nbytes - hn->bz2.avail_out;
}
Sint64 Bzip2_Write(Bzip2StmHn hn, const void *buf, size_t nbytes)
{
if (!hn->compressing) {
Bzip2_SetErrStat(BZIP2_EBADSTREAM);
return -1;
}
Bzip2Ret ret = BZ_OK;
hn->bz2.next_in = (char *) buf; // safe
hn->bz2.avail_in = nbytes;
while (hn->bz2.avail_in > 0) {
if (hn->bz2.avail_out == 0) {
Sint64 n = Bzip2_FlushData(hn);
if (n <= 0) {
if (n < 0) ret = BZ_IO_ERROR;
break;
}
}
int err = BZ2_bzCompress(&hn->bz2, BZ_RUN);
if (err != BZ_RUN_OK) {
ret = err;
break;
}
}
Bzip2_SetErrStat(ret);
return nbytes - hn->bz2.avail_in;
}
Judgement Bzip2_Finish(Bzip2StmHn hn)
{
if (!hn->compressing) {
Bzip2_SetErrStat(BZIP2_EBADSTREAM);
return NG;
}
int err;
do {
// Call BZ2_bzCompress() repeatedly with BZ_FINISH to consume all data
err = BZ2_bzCompress(&hn->bz2, BZ_FINISH);
if (err != BZ_STREAM_END && err != BZ_FINISH_OK) {
Bzip2_SetErrStat(err);
return NG;
}
if (Bzip2_FlushData(hn) == -1) {
Bzip2_SetErrStat(BZ_IO_ERROR);
return NG;
}
} while (err != BZ_STREAM_END);
Bzip2_SetErrStat(BZ_OK);
return OK;
}
void Bzip2_Close(Bzip2StmHn hn)
{
if (hn->ops->Close)
hn->ops->Close(hn->streamp);
if (hn->compressing)
BZ2_bzCompressEnd(&hn->bz2);
else
BZ2_bzDecompressEnd(&hn->bz2);
free(hn);
}

@ -0,0 +1,387 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file cpr/flate.c
*
* Interfaces with `zlib` and implements INFLATE/DEFLATE.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "cpr/flate.h"
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
typedef struct ZlibStmObj ZlibStmObj;
struct ZlibStmObj {
z_stream zs;
void *streamp;
const StmOps *ops;
unsigned bufsiz;
Boolean8 deflating;
Uint8 buf[FLEX_ARRAY]; // `bufsiz` bytes
};
#define ZSTM_BUFSIZ (32 * 1024)
#define ZSTM_EIO -1LL
#define ZSTM_EBADSTREAM -2LL
static Sint64 Zlib_FlushData(ZlibStmHn hn)
{
size_t nbytes = hn->bufsiz - hn->zs.avail_out;
Sint64 n = hn->ops->Write(hn->streamp, hn->buf, nbytes);
if (n == -1)
return -1;
size_t left = nbytes - n;
memmove(hn->buf, hn->buf + n, left);
hn->zs.next_out = hn->buf + left;
hn->zs.avail_out = hn->bufsiz - left;
return n;
}
static Sint64 Zlib_StmRead(void *streamp, void *buf, size_t nbytes)
{
return Zlib_Read((ZlibStmHn) streamp, buf, nbytes);
}
static Sint64 Zlib_StmWrite(void *streamp, const void *buf, size_t nbytes)
{
return Zlib_Write((ZlibStmHn) streamp, buf, nbytes);
}
static Sint64 Zlib_StmTell(void *streamp)
{
ZlibStmHn hn = (ZlibStmHn) streamp;
return hn->deflating ? hn->zs.total_out : hn->zs.total_in;
}
static Judgement Zlib_StmFinish(void *streamp)
{
return Zlib_Finish((ZlibStmHn) streamp);
}
static void Zlib_StmClose(void *streamp)
{
Zlib_Close((ZlibStmHn) streamp);
}
static const StmOps zlib_stmOps = {
Zlib_StmRead,
Zlib_StmWrite,
NULL,
Zlib_StmTell,
Zlib_StmFinish,
Zlib_StmClose
};
static const StmOps zlib_ncStmOps = {
Zlib_StmRead,
Zlib_StmWrite,
NULL,
Zlib_StmTell,
Zlib_StmFinish,
NULL
};
const StmOps *const Zlib_StmOps = &zlib_stmOps;
const StmOps *const Zlib_NcStmOps = &zlib_ncStmOps;
static THREAD_LOCAL ZlibRet zlib_errStat = 0;
static void Zlib_SetErrStat(ZlibRet ret)
{
if (ret == Z_ERRNO) {
Sint64 err = errno;
ret |= (Sint32) (err << 32);
}
zlib_errStat = ret;
}
ZlibRet Zlib_GetErrStat(void)
{
return zlib_errStat;
}
const char *Zlib_ErrorString(ZlibRet ret)
{
if (ret == ZSTM_EIO)
return "I/O error";
if (ret == ZSTM_EBADSTREAM)
return "Bad stream operation";
Sint32 zerrno = (Sint32) (ret & 0xffffffffu);
Sint32 err = (Sint32) (ret >> 32);
switch (zerrno) {
case Z_OK: return "Success";
case Z_ERRNO: return strerror(err);
case Z_STREAM_ERROR: return "Stream error";
case Z_DATA_ERROR: return "Data error";
case Z_MEM_ERROR: return "Memory allocation failure";
case Z_BUF_ERROR: return "Buffer error";
case Z_VERSION_ERROR: return "Zlib version error";
default: return "Unknown Zlib error";
}
}
ZlibStmHn Zlib_InflateOpen(void *streamp,
const StmOps *ops,
const InflateOpts *opts)
{
const InflateOpts default_opts = {
15,
ZFMT_RFC1952,
ZSTM_BUFSIZ
};
if (!ops->Write) {
Zlib_SetErrStat(ZSTM_EBADSTREAM);
return NULL;
}
if (!opts)
opts = &default_opts;
int wbits = CLAMP(opts->win_bits, 8, 15);
// Mangle window bits according to the required RFC
switch (opts->format) {
case ZFMT_RFC1951:
wbits = -wbits;
break;
case ZFMT_RFC1950:
break;
case ZFMT_RFC1952:
default:
wbits += 16;
break;
}
size_t bufsiz = MIN(opts->bufsiz, INT_MAX); // safety to avoid short reads
if (bufsiz == 0)
bufsiz = ZSTM_BUFSIZ;
ZlibStmObj *hn = (ZlibStmObj *) malloc(offsetof(ZlibStmObj, buf[bufsiz]));
if (!hn) {
Zlib_SetErrStat(Z_MEM_ERROR);
return NULL;
}
memset(&hn->zs, 0, sizeof(hn->zs));
int err = inflateInit2(&hn->zs, wbits);
if (err != Z_OK) {
Zlib_SetErrStat(err);
free(hn);
return NULL;
}
hn->streamp = streamp;
hn->ops = ops;
hn->bufsiz = bufsiz;
hn->deflating = FALSE;
Zlib_SetErrStat(Z_OK);
return hn;
}
ZlibStmHn Zlib_DeflateOpen(void *streamp,
const StmOps *ops,
const DeflateOpts *opts)
{
const DeflateOpts default_opts = {
Z_DEFAULT_COMPRESSION,
15,
ZFMT_RFC1952,
ZSTM_BUFSIZ
};
if (!ops->Read) {
Zlib_SetErrStat(ZSTM_EBADSTREAM);
return NULL;
}
if (!opts)
opts = &default_opts;
// Setup open options
int compression = opts->compression;
if (compression != Z_DEFAULT_COMPRESSION)
compression = CLAMP(compression, 0, 9);
int wbits = CLAMP(opts->win_bits, 8, 15);
// Mangle window bits according to the required RFC
switch (opts->format) {
case ZFMT_RFC1951:
wbits = -wbits;
break;
case ZFMT_RFC1950:
break;
case ZFMT_RFC1952:
default:
wbits += 16;
break;
}
size_t bufsiz = MIN(opts->bufsiz, INT_MAX); // safety to avoid short reads
if (bufsiz == 0)
bufsiz = ZSTM_BUFSIZ;
ZlibStmObj *hn = (ZlibStmObj *) malloc(offsetof(ZlibStmObj, buf[bufsiz]));
if (!hn) {
Zlib_SetErrStat(Z_MEM_ERROR);
return NULL;
}
memset(&hn->zs, 0, sizeof(hn->zs));
hn->streamp = streamp;
hn->ops = ops;
hn->bufsiz = bufsiz;
hn->deflating = TRUE;
int err = deflateInit2(
&hn->zs,
compression,
Z_DEFLATED,
wbits,
8,
Z_DEFAULT_STRATEGY
);
if (err != Z_OK) {
Zlib_SetErrStat(err);
free(hn);
return NULL;
}
hn->zs.next_out = hn->buf;
hn->zs.avail_out = hn->bufsiz;
Zlib_SetErrStat(Z_OK);
return hn;
}
Sint64 Zlib_Read(ZlibStmHn hn, void *buf, size_t nbytes)
{
if (hn->deflating) {
Zlib_SetErrStat(ZSTM_EBADSTREAM);
return -1;
}
ZlibRet ret = Z_OK; // unless found otherwise
hn->zs.next_out = (Uint8 *) buf;
hn->zs.avail_out = nbytes;
while (hn->zs.avail_out > 0) {
if (hn->zs.avail_in == 0) {
// Fill buffer
Sint64 n = hn->ops->Read(hn->streamp, hn->buf, hn->bufsiz);
if (n <= 0) {
if (n < 0) ret = ZSTM_EIO;
break;
}
hn->zs.next_in = hn->buf;
hn->zs.avail_in = n;
}
int err = inflate(&hn->zs, Z_NO_FLUSH);
if (err == Z_NEED_DICT)
err = Z_DATA_ERROR;
if (err != Z_OK && err != Z_STREAM_END) {
ret = err;
break;
}
}
Zlib_SetErrStat(ret);
return nbytes - hn->zs.avail_out;
}
Sint64 Zlib_Write(ZlibStmHn hn, const void *buf, size_t nbytes)
{
if (!hn->deflating) {
Zlib_SetErrStat(ZSTM_EBADSTREAM);
return -1;
}
ZlibRet ret = Z_OK;
hn->zs.next_in = (Uint8 *) buf;
hn->zs.avail_in = nbytes;
while (hn->zs.avail_in > 0) {
if (hn->zs.avail_out == 0) {
Sint64 n = Zlib_FlushData(hn);
if (n <= 0) {
if (n < 0) ret = ZSTM_EIO;
break; // short-write
}
}
int err = deflate(&hn->zs, Z_NO_FLUSH);
if (err == Z_NEED_DICT)
err = Z_DATA_ERROR;
if (err != Z_OK) {
ret = err;
break;
}
}
Zlib_SetErrStat(ret);
return nbytes - hn->zs.avail_in;
}
Judgement Zlib_Finish(ZlibStmHn hn)
{
if (!hn->deflating) {
Zlib_SetErrStat(ZSTM_EBADSTREAM);
return NG;
}
int err;
do {
err = deflate(&hn->zs, Z_FINISH);
if (err != Z_STREAM_END && err != Z_BUF_ERROR && err != Z_OK) {
Zlib_SetErrStat(err);
return NG;
}
if (Zlib_FlushData(hn) == -1) {
Zlib_SetErrStat(ZSTM_EIO);
return NG;
}
} while (err != Z_STREAM_END);
if (hn->zs.avail_out != hn->bufsiz) {
Zlib_SetErrStat(Z_BUF_ERROR);
return NG;
}
Zlib_SetErrStat(Z_OK);
return OK;
}
void Zlib_Close(ZlibStmHn hn)
{
// Close stream
if (hn->ops->Close)
hn->ops->Close(hn->streamp);
// Finalize Z_stream
if (hn->deflating)
deflateEnd(&hn->zs);
else
inflateEnd(&hn->zs);
free(hn); // Free memory
}

@ -0,0 +1,354 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file cpr/xz.c
*
* Interfaces with `liblzma` and implements LZMA compressor/decompressor.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "cpr/xz.h"
#include <assert.h>
#include <limits.h>
#include <lzma.h>
#include <stdlib.h>
#include <string.h>
// some equivalency we assume to be true
STATIC_ASSERT((lzma_check) XZCHK_NONE == LZMA_CHECK_NONE,
"Incorrect XZCHK_NONE definition");
STATIC_ASSERT((lzma_check) XZCHK_CRC32 == LZMA_CHECK_CRC32,
"Incorrect XZCHK_CRC32 definition");
STATIC_ASSERT((lzma_check) XZCHK_CRC64 == LZMA_CHECK_CRC64,
"Incorrect XZCHK_CRC64 definition");
STATIC_ASSERT((lzma_check) XZCHK_SHA256 == LZMA_CHECK_SHA256,
"Incorrect XZCHK_SHA256 definition");
typedef struct XzStmObj XzStmObj;
struct XzStmObj {
lzma_stream xz;
void *streamp;
const StmOps *ops;
unsigned bufsiz;
lzma_action action;
Boolean8 encoding;
Uint8 buf[FLEX_ARRAY]; // `bufsiz` bytes
};
#define XZ_BUFSIZ (32 * 1024)
#define XZ_EIO -2
#define XZ_EBADSTREAM -1
static Sint64 Xz_FlushData(XzStmHn hn)
{
size_t nbytes = hn->bufsiz - hn->xz.avail_out;
Sint64 n = hn->ops->Write(hn->streamp, hn->buf, nbytes);
if (n < 0)
return -1;
size_t left = nbytes - n;
memmove(hn->buf, hn->buf + n, left);
hn->xz.next_out = hn->buf + left;
hn->xz.avail_out = hn->bufsiz - left;
return n;
}
static Sint64 Xz_StmRead(void *streamp, void *buf, size_t nbytes)
{
return Xz_Read((XzStmHn) streamp, buf, nbytes);
}
static Sint64 Xz_StmWrite(void *streamp, const void *buf, size_t nbytes)
{
return Xz_Write((XzStmHn) streamp, buf, nbytes);
}
static Sint64 Xz_StmTell(void *streamp)
{
XzStmHn hn = (XzStmHn) streamp;
return hn->encoding ? hn->xz.total_out : hn->xz.total_in;
}
static Judgement Xz_StmFinish(void *streamp)
{
return Xz_Finish((XzStmHn) streamp);
}
static void Xz_StmClose(void *streamp)
{
Xz_Close((XzStmHn) streamp);
}
static const StmOps xz_stmOps = {
Xz_StmRead,
Xz_StmWrite,
NULL,
Xz_StmTell,
Xz_StmFinish,
Xz_StmClose
};
static const StmOps xz_ncStmOps = {
Xz_StmRead,
Xz_StmWrite,
NULL,
Xz_StmTell,
Xz_StmFinish,
NULL
};
const StmOps *const Xz_StmOps = &xz_stmOps;
const StmOps *const Xz_NcStmOps = &xz_ncStmOps;
static THREAD_LOCAL XzRet xz_errStat = LZMA_OK;
static void Xz_SetErrStat(XzRet ret)
{
xz_errStat = ret;
}
XzRet Xz_GetErrStat(void)
{
return xz_errStat;
}
const char *Xz_ErrorString(XzRet ret)
{
assert(ret != LZMA_NO_CHECK);
assert(ret != LZMA_GET_CHECK);
switch (ret) {
case XZ_EIO: return "I/O error";
case XZ_EBADSTREAM: return "Bad stream operation";
case LZMA_OK: return "Success";
case LZMA_UNSUPPORTED_CHECK: return "Cannot calculate the integrity check";
case LZMA_MEM_ERROR: return "Memory allocation failure";
case LZMA_MEMLIMIT_ERROR: return "Memory usage limit was reached";
case LZMA_FORMAT_ERROR: return "Unrecognized file format";
case LZMA_OPTIONS_ERROR: return "Invalid or unsupported options";
case LZMA_DATA_ERROR: return "Data is corrupt";
case LZMA_BUF_ERROR: return "No progress is possible";
case LZMA_PROG_ERROR: return "Programming error";
default: return "Unknown error";
}
}
XzStmHn Xz_OpenCompress(void *streamp,
const StmOps *ops,
const XzEncOpts *opts)
{
const XzEncOpts default_opts = {
6,
FALSE,
XZCHK_CRC32,
XZ_BUFSIZ
};
if (!opts)
opts = &default_opts;
Uint32 compression = MIN(opts->compress, 9);
Uint32 presets = 0;
if (opts->extreme)
presets |= LZMA_PRESET_EXTREME;
size_t bufsiz = MIN(opts->bufsiz, INT_MAX);
if (bufsiz == 0)
bufsiz = XZ_BUFSIZ;
XzStmObj *hn = (XzStmObj *) malloc(offsetof(XzStmObj, buf[bufsiz]));
if (!hn) {
Xz_SetErrStat(LZMA_MEM_ERROR);
return NULL;
}
memset(&hn->xz, 0, sizeof(hn->xz));
hn->streamp = streamp;
hn->ops = ops;
hn->bufsiz = bufsiz;
hn->action = LZMA_RUN; // ...actually ignored for encoding buffers
hn->encoding = TRUE;
lzma_ret err = lzma_easy_encoder(
&hn->xz,
compression | presets,
(lzma_check) opts->chk
);
if (err != LZMA_OK) {
Xz_SetErrStat(err);
free(hn);
return NULL;
}
hn->xz.next_out = hn->buf;
hn->xz.avail_out = hn->bufsiz;
Xz_SetErrStat(LZMA_OK);
return hn;
}
XzStmHn Xz_OpenDecompress(void *streamp,
const StmOps *ops,
const XzDecOpts *opts)
{
const XzDecOpts default_opts = {
U64_C(0xffffffffffffffff),
FALSE,
FALSE,
XZ_BUFSIZ
};
if (!opts)
opts = &default_opts;
Uint32 mask = LZMA_CONCATENATED;
if (opts->no_concat)
mask &= ~LZMA_CONCATENATED;
#ifdef LZMA_IGNORE_CHECK
if (opts->no_chk)
mask |= LZMA_IGNORE_CHECK;
#endif
size_t bufsiz = MIN(opts->bufsiz, INT_MAX);
if (bufsiz == 0)
bufsiz = XZ_BUFSIZ;
XzStmObj *hn = (XzStmObj *) malloc(offsetof(XzStmObj, buf[bufsiz]));
if (!hn) {
Xz_SetErrStat(LZMA_MEM_ERROR);
return NULL;
}
memset(&hn->xz, 0, sizeof(hn->xz));
hn->streamp = streamp;
hn->ops = ops;
hn->action = LZMA_RUN; // used to force LZMA_FINISH on EOF
hn->bufsiz = bufsiz;
hn->encoding = FALSE;
lzma_ret err = lzma_auto_decoder(&hn->xz, opts->memlimit, mask);
if (err != LZMA_OK) {
Xz_SetErrStat(err);
free(hn);
return NULL;
}
Xz_SetErrStat(LZMA_OK);
return hn;
}
Sint64 Xz_Read(XzStmHn hn, void *buf, size_t nbytes)
{
if (hn->encoding) {
Xz_SetErrStat(XZ_EBADSTREAM);
return -1;
}
XzRet ret = LZMA_OK;
hn->xz.next_out = (Uint8 *) buf;
hn->xz.avail_out = nbytes;
while (hn->xz.avail_out > 0) {
if (hn->xz.avail_in == 0) {
Sint64 n = hn->ops->Read(hn->streamp, hn->buf, hn->bufsiz);
if (n <= 0) {
if (n < 0) {
ret = XZ_EIO;
break;
}
hn->action = LZMA_FINISH;
}
hn->xz.next_in = hn->buf;
hn->xz.avail_in = n;
}
lzma_ret err = lzma_code(&hn->xz, hn->action);
if (err == LZMA_STREAM_END)
break; // NOTE: shouldn't happen for a stream to end before EOF...
if (err != LZMA_OK) {
ret = err;
break;
}
}
Xz_SetErrStat(ret);
return nbytes - hn->xz.avail_out;
}
Sint64 Xz_Write(XzStmHn hn, const void *buf, size_t nbytes)
{
if (!hn->encoding) {
Xz_SetErrStat(XZ_EBADSTREAM);
return -1;
}