[*] Initial commit

This commit is contained in:
Lorenzo Cogotti 2021-06-07 16:55:13 +02:00
commit b0ef4dd774
117 changed files with 29737 additions and 0 deletions

1
.clang-tidy Executable file
View File

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

51
.gitignore vendored Normal file
View File

@ -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

675
COPYING.GPL Normal file
View File

@ -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>.

166
COPYING.LESSER Normal file
View File

@ -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.

60
README.md Normal file
View File

@ -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.

1022
doc/Doxyfile.in Normal file

File diff suppressed because it is too large Load Diff

55
doc/HISTORY.md Normal file
View File

@ -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

3
doc/css/custom.css Normal file
View File

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

View File

@ -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;
}
}

1365
doc/css/doxygen-awesome.css Normal file

File diff suppressed because it is too large Load Diff

21
doc/meson.build Normal file
View File

@ -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)

170
lonetix/README.md Normal file
View File

@ -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).

349
lonetix/argv.c Normal file
View File

@ -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;
}

1235
lonetix/bgp/attribute.c Normal file

File diff suppressed because it is too large Load Diff

611
lonetix/bgp/bgp.c Normal file
View File

@ -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;
}

45
lonetix/bgp/bgp_local.h Normal file
View File

@ -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

177
lonetix/bgp/bytebuf.c Normal file
View File

@ -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;

82
lonetix/bgp/dump.c Normal file
View File

@ -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);
}

1085
lonetix/bgp/dump_isolario.c Normal file

File diff suppressed because it is too large Load Diff

751
lonetix/bgp/mrt.c Normal file
View File

@ -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;
}

100
lonetix/bgp/parameters.c Normal file
View File

@ -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;
}

518
lonetix/bgp/patricia.c Normal file
View File

@ -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));
}

191
lonetix/bgp/prefix.c Normal file
View File

@ -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;
}

838
lonetix/bgp/vm.c Normal file
View File

@ -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);
}

834
lonetix/bgp/vm_asmtch.c Normal file
View File

@ -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);
}

33
lonetix/bgp/vm_cdef.h Normal file
View File

@ -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

103
lonetix/bgp/vm_commsort.h Normal file
View File

@ -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

View File

@ -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);
}

303
lonetix/bgp/vm_dump.c Normal file
View File

@ -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--;
}
}

66
lonetix/bgp/vm_gccdef.h Normal file
View File

@ -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

98
lonetix/bgp/vm_optab.h Normal file
View File

@ -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

119
lonetix/bufio.c Normal file
View File

@ -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);
}

328
lonetix/cpr/bzip2.c Executable file
View File

@ -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);
}

387
lonetix/cpr/flate.c Executable file
View File

@ -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
}

354
lonetix/cpr/xz.c Executable file
View File

@ -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;
}
XzRet ret = LZMA_OK;
hn->xz.next_in = (Uint8 *) buf;
hn->xz.avail_in = nbytes;
while (hn->xz.avail_in > 0) {
if (hn->xz.avail_out == 0) {
Sint64 n = Xz_FlushData(hn);
if (n < -1) ret = XZ_EIO;
break; // short-write
}
// Disregard `hn->action` on write, we will flush upon Xz_Finish()
lzma_ret err = lzma_code(&hn->xz, LZMA_RUN);
if (err != LZMA_OK) {
ret = err;
break;
}
}
Xz_SetErrStat(ret);
return nbytes - hn->xz.avail_in;
}
Judgement Xz_Finish(XzStmHn hn)
{
if (!hn->encoding) {
Xz_SetErrStat(XZ_EBADSTREAM);
return NG;
}
lzma_ret err;
do {
// Flush LZMA to disk
err = lzma_code(&hn->xz, LZMA_FINISH);
if (err != LZMA_STREAM_END && err != LZMA_OK) {
Xz_SetErrStat(err);
return NG;
}
if (Xz_FlushData(hn) == -1) {
Xz_SetErrStat(XZ_EIO);
return NG;
}
} while (err != LZMA_STREAM_END);
if (hn->xz.avail_out != hn->bufsiz) {
Xz_SetErrStat(LZMA_BUF_ERROR);
return NG;
}
return OK;
}
void Xz_Close(XzStmHn hn)
{
if (hn->ops->Close)
hn->ops->Close(hn->streamp);
lzma_end(&hn->xz);
free(hn);
}

87
lonetix/include/df/argv.h Normal file
View File

@ -0,0 +1,87 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file argv.h
*
* Command line argument parsing library.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_ARGV_H_
#define DF_ARGV_H_
#include "utf/utfdef.h"
#ifdef _WIN32
// DOS-style options are preferred
#define OPTCHAR '/'
#define OPTSEP ':'
#else
// Unix-style options
#define OPTCHAR '-'
#define OPTSEP '='
#endif
typedef enum {
ARG_NONE, ///< `Optflag` takes no argument
ARG_REQ, ///< `Optflag` with mandatory argument
ARG_OPT ///< `Optflag` may take an optional argument
} Optarg;
typedef struct {
Rune opt; ///< Set to `\0` to signal `Optflag` list end
const char *longopt; ///< Optional long name for this option
const char *argName; ///< Human readable descriptive argument name, ignored if `hasArg == ARG_NONE`
const char *descr; ///< Human readable description for option, displayed in help message
Optarg hasArg; ///< Whether an argument to this `Optflag` is mandatory, optional or prohibited
// Following fields are altered by `Com_ArgParse()` when flag is
// encountered.
// If option doesn't appear inside `argv`, then following fields are left
// untouched (thus using them to provide default values is legal)
Boolean flagged; ///< Set to `TRUE` if option was found in command line
char *optarg; ///< Set to a pointer inside `argv` when argument for this option is found
} Optflag;
extern const char *com_progName; ///< If set to non-NULL string, it is used to provide a program name during `Com_ArgParse()`, instead of `argv[0]`
extern const char *com_synopsis; ///< If set to non-NULL string, provides a short synopsis for `Com_ArgParse()` usage message
extern const char *com_shortDescr; ///< If set to non-NULL string, provides a brief command summary for `Com_ArgParse()` usage message
extern const char *com_longDescr; ///< If set to non-NULL string, provides a long description for `Com_ArgParse()` usage message
/// Do not emit error messages or help message automatically to `stderr`.
#define ARG_QUIET BIT(0)
/// Do not perform GNU-like command flags reordering.
#define ARG_NOREORD BIT(1)
/// The provided `Optflag` list is ill-formed (e.g. option with `opt == '-' && longopt == NULL` was found)
#define OPT_BADLIST -1
/// Missing required option argument
#define OPT_ARGMISS -2
/// Excess argument inside long option that requires none (e.g. `--foo=bar`, but `foo->hasArg == ARG_NONE`)
#define OPT_EXCESSARG -3
/// Encountered unknown option
#define OPT_UNKNOWN -4
/// Parsing terminated because help option was requested (-h, -?, --help or platform specific equivalents)
#define OPT_HELP -5
/// Command line is ambiguous
#define OPT_BADARGV -6
/**
* Parse command line arguments, updating relevant option flags.
*
* \param [in] argc Argument count
* \param [in] argv Argument vector`
* \param [in,out] options Option flag list
* \param [in] flags Parsing flags (`ARG_*` bit mask)
*
* \return Number of argument parsed or an appropriate error value:
* * On success the number of parsed arguments is returned (>= 0)
* * If the help option was requested then `OPT_HELP` is returned
* * If argument list is ill-formed, an error code is returned (`OPT_*`)
*/
int Com_ArgParse(int argc, char **argv, Optflag *options, unsigned flags);
#endif

87
lonetix/include/df/bgp/asn.h Executable file
View File

@ -0,0 +1,87 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/asn.h
*
* Types, constant and utilities to work with ASN of various width.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGP_ASN_H_
#define DF_BGP_ASN_H_
#include "xpt.h"
/// 2 octet `AS_TRANS` constant, in network order (big-endian).
#define AS_TRANS BE16(23456)
/// 4 octet `AS_TRANS` constant, in network order (big-endian).
#define AS4_TRANS BE32(23456)
/// 2 bytes wide AS number (ASN16), in network order (big-endian).
typedef Uint16 Asn16;
/// 4 bytes wide AS number (ASN32), in network order (big-endian).
typedef Uint32 Asn32;
/**
* \brief Fat AS type capable of holding either a 4 octet (ASN32) or a 2 octet
* (ASN16) AS number, with additional flags indicating ASN properties.
*
* \note This type doesn't reflect any actual BGP ASN encoding, it is merely
* a convenience abstraction, useful when ASN properties should be bundled
* with the actual ASN.
*/
typedef Sint64 Asn; // NOTE: signed so negative values may be used for
// special purposes if necessary
/// `Asn` flag indicating that the currently stored ASN is 4 octet wide (ASN32).
#define ASN32BITFLAG BIT(62)
/**
* \brief Return an `Asn` holding an `Asn16` value.
*
* \note `asn` must be in network order (big endian).
*/
#define ASN16BIT(asn) ((Asn) ((Asn16) (asn)))
/**
* \brief Return an `Asn` holding a `Asn32`.
*
* \note `asn` should be in network order (big endian).
*/
#define ASN32BIT(asn) ((Asn) (ASN32BITFLAG | ((Asn) ((Asn32) asn))))
/// Test whether an `Asn` is holding a 4 octet wide ASN.
FORCE_INLINE Boolean ISASN32BIT(Asn asn)
{
return (asn & ASN32BITFLAG) != 0;
}
/**
* \brief Extract AS number stored inside an `Asn`.
*
* Returned ASN is extended to `Asn32` even if it was originally an `Asn16`,
* this allows code involving `Asn` to treat any ASN uniformly.
*
* \note Resulting ASN is in network byte order (big endian).
*/
FORCE_INLINE Asn32 ASN(Asn asn)
{
Asn32 res = asn & 0xffffffffuLL;
#if EDN_NATIVE != EDN_BE
// Shift _asn of 16 bits if this was originally ASN16,
// on LE machines this mimics a BE16 word to BE32 dword extension
res <<= (((asn & ASN32BITFLAG) == 0) << 4);
#endif
return res;
}
/// Test whether `asn` represents either `AS_TRANS` or `AS4_TRANS`.
FORCE_INLINE Boolean ISASTRANS(Asn asn)
{
return ASN(asn) == AS4_TRANS;
}
#endif

1198
lonetix/include/df/bgp/bgp.h Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/bytebuf.h
*
* Allocator optimized for trivial BGP workflows.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGP_MEMBUF_H_
#define DF_BGP_MEMBUF_H_
#include "mem.h"
/// Memory alignment for `Bgpbytebuf` allocations.
#define BGP_MEMBUF_ALIGN 4
// NOTE: Need at least 4 bytes alignment for TABLE_DUMPV2 peer lookup tables!
/**
* \brief Basic fixed-size packed single-threaded byte buffer.
*
* Nearly zero-overhead byte pool optimized for typical BGP message
* allocations. Returns chunks from an internal fixed-size byte buffer
*/
typedef struct {
size_t size; ///< Buffer block size in bytes
size_t pos; ///< Current position inside block
ALIGNED(BGP_MEMBUF_ALIGN, Uint8 base[FLEX_ARRAY]); ///< Block buffer
} Bgpbytebuf;
/**
* \brief Create `Bgpbytebuf` with statically sized fixed buffer
* (as opposed to a flexible array buffer).
*
* This is useful when the buffer should be placed in statically
* allocated variable, e.g.
* ```c
* static BGP_FIXBYTEBUF(4096) bgp_msgBuf = { 4096 };
* ```
*
* May also be used for a `typedef`:
* ```c
* typedef BGP_FIXBYTEBUF(1024) Bgpsmallbuf;
* ```
*
* Variables generated by this macro may be used
* as `allocp` of any API expecting a `MemOps` interface.
*/
#define BGP_FIXBYTEBUF(bufsiz) \
struct { \
size_t size; \
size_t pos; \
ALIGNED(BGP_MEMBUF_ALIGN, Uint8 base[bufsiz]); \
}
/// `MemOps` operating over `Bgpbytebuf`, use pointer to `Bgpbytebuf` as `allocp`.
extern const MemOps *const Mem_BgpBufOps;
#endif

90
lonetix/include/df/bgp/dump.h Executable file
View File

@ -0,0 +1,90 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/dump.h
*
* BGP message dump utilities.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGP_DUMP_H_
#define DF_BGP_DUMP_H_
#include "mrt.h"
/**
* \brief BGP message dump formatter.
*
* \note Calling the dump functions directly should be considered low-level,
* under normal circumstances use: `Bgp_DumpMsg()`, `Bgp_DumpMrtUpdate()`,
* `Bgp_DumpRib()`, and `Bgp_DumpRibv2()`.
*/
typedef struct {
Sint64 (*DumpMsg)(const Bgphdr *, unsigned,
void *, const StmOps *,
Bgpattrtab);
Sint64 (*DumpRibv2)(const Mrthdr *,
const Mrtpeerentv2 *, const Mrtribentv2 *,
void *, const StmOps *,
Bgpattrtab);
Sint64 (*DumpRib)(const Mrthdr *,
const Mrtribent *,
void *, const StmOps *,
Bgpattrtab);
Sint64 (*DumpBgp4mp)(const Mrthdr *,
void *, const StmOps *,
Bgpattrtab);
Sint64 (*DumpZebra)(const Mrthdr *,
void *, const StmOps *,
Bgpattrtab);
} BgpDumpfmt;
// Standard dump formatters
extern const BgpDumpfmt *const Bgp_IsolarioFmt; ///< Isolario `bgpscanner` like output format
extern const BgpDumpfmt *const Bgp_IsolarioFmtWc; ///< Isolario `bgpscanner` like output format, with colors
// extern const BgpDumpfmt *const Bgp_BgpdumpFmt; ///< `bgpdump` style output format
// extern const BgpDumpfmt *const Bgp_RawFmt; ///< output message raw bytes
// extern const BgpDumpfmt *const Bgp_HexFmt; ///< perform BGP message hexadecimal dump
// extern const BgpDumpfmt *const Bgp_CFmt; ///< outputs BGP message as a C-style array
FORCE_INLINE Sint64 Bgp_DumpMsg(Bgpmsg *msg,
void *streamp, const StmOps *ops,
const BgpDumpfmt *fmt)
{
extern Judgement _Bgp_SetErrStat(BgpRet,
const char *,
const char *,
unsigned long long,
unsigned);
Sint64 res = 0;
if (ops->Write && fmt->DumpMsg)
res = fmt->DumpMsg(BGP_HDR(msg), msg->flags, streamp, ops, msg->table);
else
_Bgp_SetErrStat(BGPENOERR, NULL, NULL, 0, 0);
return res;
}
Sint64 Bgp_DumpMrtUpdate(const Mrthdr *hdr,
void *streamp, const StmOps *ops,
const BgpDumpfmt *fmt);
Sint64 Bgp_DumpMrtRibv2(const Mrthdr *hdr,
const Mrtpeerentv2 *peer, const Mrtribentv2 *ent,
void *streamp, const StmOps *ops,
const BgpDumpfmt *fmt);
Sint64 Bgp_DumpMrtRib(const Mrthdr *hdr,
const Mrtribent *ent,
void *streamp, const StmOps *ops,
const BgpDumpfmt *fmt);
#endif

888
lonetix/include/df/bgp/mrt.h Executable file
View File

@ -0,0 +1,888 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/mrt.h
*
* Multithreaded Routing Toolkit (MRT) types and functions.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGP_MRT_H_
#define DF_BGP_MRT_H_
#include "bgp/bgp.h"
#include "stm.h"
/**
* \name MRT record types
*
* \note Values are in network order (big endian)
*
* @{
*/
#define MRT_NULL BE16(0) // Deprecated
#define MRT_START BE16(1) // Deprecated
#define MRT_DIE BE16(2) // Deprecated
#define MRT_I_AM_DEAD BE16(3) // Deprecated
#define MRT_PEER_DOWN BE16(4) // Deprecated
#define MRT_BGP BE16(5) // Deprecated, also known as ZEBRA_BGP
#define MRT_RIP BE16(6) // Deprecated
#define MRT_IDRP BE16(7) // Deprecated
#define MRT_RIPNG BE16(8) // Deprecated
#define MRT_BGP4PLUS BE16(9) // Deprecated
#define MRT_BGP4PLUS_01 BE16(10) // Deprecated
#define MRT_OSPFV2 BE16(11)
#define MRT_TABLE_DUMP BE16(12)
#define MRT_TABLE_DUMPV2 BE16(13)
#define MRT_BGP4MP BE16(16)
#define MRT_BGP4MP_ET BE16(17)
#define MRT_ISIS BE16(32)
#define MRT_ISIS_ET BE16(33)
#define MRT_OSPFV3 BE16(48)
#define MRT_OSPFV3_ET BE16(49)
/// 2-octets type for MRT record header type field, network byte order (big endian).
typedef Uint16 MrtType;
/** @} */
/**
* \name MRT subtypes enumeration for records of type BGP/ZEBRA
*
* \warning ZEBRA BGP has been deprecated in favor of BGP4MP.
*
* \see `MrtSubType`
*
* @{
*/
#define ZEBRA_NULL BE16(0)
#define ZEBRA_UPDATE BE16(1)
#define ZEBRA_PREF_UPDATE BE16(2)
#define ZEBRA_STATE_CHANGE BE16(3)
#define ZEBRA_SYNC BE16(4)
#define ZEBRA_OPEN BE16(5)
#define ZEBRA_NOTIFY BE16(6)
#define ZEBRA_KEEPALIVE BE16(7)
/** @} */
/**
* \name MRT subtypes for records of type BGP4MP
*
* BGP4MP is defined in [RFC 6396](https://tools.ietf.org/html/rfc6396#section-4.2),
* and extended by [RFC 8050](https://tools.ietf.org/html/rfc8050#page-2).
*
* \see [IANA BGP4MP Subtype Codes](https://www.iana.org/assignments/mrt/mrt.xhtml#BGP4MP-codes)
* \see `MrtSubType`
*
* @{
*/
#define BGP4MP_STATE_CHANGE BE16(0) ///< RFC 6396
#define BGP4MP_MESSAGE BE16(1) ///< RFC 6396
#define BGP4MP_ENTRY BE16(2) ///< Deprecated
#define BGP4MP_SNAPSHOT BE16(3) ///< Deprecated
#define BGP4MP_MESSAGE_AS4 BE16(4) ///< RFC 6396
#define BGP4MP_STATE_CHANGE_AS4 BE16(5) ///< RFC 6396
#define BGP4MP_MESSAGE_LOCAL BE16(6) ///< RFC 6396
#define BGP4MP_MESSAGE_AS4_LOCAL BE16(7) ///< RFC 6396
#define BGP4MP_MESSAGE_ADDPATH BE16(8) ///< RFC 8050
#define BGP4MP_MESSAGE_AS4_ADDPATH BE16(9) ///< RFC 8050
#define BGP4MP_MESSAGE_LOCAL_ADDPATH BE16(10) ///< RFC 8050
#define BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH BE16(11) ///< RFC 8050
/** @} */
/**
* \name MRT subtypes for records of type TABLE_DUMPV2
*
* Table Dump version 2 is defined in [RFC 6396](https://tools.ietf.org/html/rfc6396#section-4.3).
*
* \see [IANA Table Dump version 2 Subtype Codes](https://www.iana.org/assignments/mrt/mrt.xhtml#table-dump-v2-subtype-codes)
* \see `MrtSubType`
*
* @{
*/
#define TABLE_DUMPV2_PEER_INDEX_TABLE BE16(1) ///< RFC 6396
#define TABLE_DUMPV2_RIB_IPV4_UNICAST BE16(2) ///< RFC 6396
#define TABLE_DUMPV2_RIB_IPV4_MULTICAST BE16(3) ///< RFC 6396
#define TABLE_DUMPV2_RIB_IPV6_UNICAST BE16(4) ///< RFC 6396
#define TABLE_DUMPV2_RIB_IPV6_MULTICAST BE16(5) ///< RFC 6396
#define TABLE_DUMPV2_RIB_GENERIC BE16(6) ///< RFC 6396
#define TABLE_DUMPV2_GEO_PEER_TABLE BE16(7) ///< RFC 6397
#define TABLE_DUMPV2_RIB_IPV4_UNICAST_ADDPATH BE16(8) ///< RFC 8050
#define TABLE_DUMPV2_RIB_IPV4_MULTICAST_ADDPATH BE16(9) ///< RFC 8050
#define TABLE_DUMPV2_RIB_IPV6_UNICAST_ADDPATH BE16(10) ///< RFC 8050
#define TABLE_DUMPV2_RIB_IPV6_MULTICAST_ADDPATH BE16(11) ///< RFC 8050
#define TABLE_DUMPV2_RIB_GENERIC_ADDPATH BE16(12) ///< RFC 8050
/** @} */
/// Type for MRT header subtype field, 2-octets type, network byte order (big endian).
typedef Uint16 MrtSubType;
/// Lookup table for TABLE_DUMPV2 PEER_INDEX.
typedef struct {
// NOTE: Data in this table is accessed atomically,
// despite these fields being signed they are effectively treated as
// unsigned by the library.
Sint16 validCount; ///< Count of valid entries inside `offsets`
Sint16 peerCount; ///< Cached PEER_INDEX_TABLE peer count value
Sint32 offsets[FLEX_ARRAY]; ///< Offset table, `peerCount` entries
} Mrtpeertabv2;
/**
* \brief MRT record type.
*
* Behaves in a similar way to `Bgpmsg`.
* Performs allocations via possibly custom allocator defined in fields
* `allocp` and `memOps`.
* If those fields are left `NULL', it uses `Mem_StdOps`.
*/
typedef struct {
Uint8 *buf; ///< Raw MRT record contents
void *allocp; ///< Optional custom memory allocator
const MemOps *memOps; ///< Optional custom memory operations
/**
* Fast peer offset table, only meaningful for
* `TABLE_DUMPV2` `PEER_INDEX_TABLE`.
*
* \note This is an atomic pointer to a [Mrtpeertabv2](\ref Mrtpeertabv2).
*
* \warning Honest, this field **MUST NOT** be accessed directly.
*/
void *peerOffTab;
} Mrtrecord;
/// Move `Mrtrecord` contents from `src` to `dest`, leaving `src` empty.
FORCE_INLINE void MRT_MOVEREC(Mrtrecord *dest, Mrtrecord *src)
{
EXTERNC void *memcpy(void *, const void *, size_t);
memcpy(dest, src, sizeof(*dest));
src->buf = NULL;
src->peerOffTab = NULL;
}
// Retrieve `MemOps` associated with record `rec`.
FORCE_INLINE const MemOps *MRT_MEMOPS(const Mrtrecord *rec)
{
return rec->memOps ? rec->memOps : Mem_StdOps;
}
#define _MRT_EXHDRMASK ( \
BIT(BE16(MRT_BGP4MP_ET)) | \
BIT(BE16(MRT_ISIS_ET)) | \
BIT(BE16(MRT_OSPFV3_ET)) \
)
#define _TABLE_DUMPV2_RIBMASK ( \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_UNICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_MULTICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_UNICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_MULTICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_GENERIC)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_UNICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_MULTICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_UNICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_MULTICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_GENERIC_ADDPATH)) \
)
#define _TABLE_DUMPV2_V4RIBMASK ( \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_UNICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_MULTICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_UNICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_MULTICAST_ADDPATH)) \
)
#define _TABLE_DUMPV2_V6RIBMASK ( \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_UNICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_MULTICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_UNICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_MULTICAST_ADDPATH)) \
)
#define _TABLE_DUMPV2_UNICASTRIBMASK ( \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_UNICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_UNICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_UNICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_UNICAST_ADDPATH)) \
)
#define _TABLE_DUMPV2_MULTICASTRIBMASK ( \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_MULTICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_MULTICAST)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_MULTICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_MULTICAST_ADDPATH)) \
)
#define _TABLE_DUMPV2_ADDPATHRIBMASK ( \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_UNICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV4_MULTICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_UNICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_IPV6_MULTICAST_ADDPATH)) | \
BIT(BE16(TABLE_DUMPV2_RIB_GENERIC_ADDPATH)) \
)
#define _BGP4MP_AS4MASK ( \
BIT(BE16(BGP4MP_MESSAGE_AS4)) | \
BIT(BE16(BGP4MP_STATE_CHANGE_AS4)) | \
BIT(BE16(BGP4MP_MESSAGE_AS4_LOCAL)) | \
BIT(BE16(BGP4MP_MESSAGE_AS4_ADDPATH)) | \
BIT(BE16(BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH)) \
)
#define _BGP4MP_ADDPATHMASK ( \
BIT(BE16(BGP4MP_MESSAGE_ADDPATH)) | \
BIT(BE16(BGP4MP_MESSAGE_AS4_ADDPATH)) | \
BIT(BE16(BGP4MP_MESSAGE_LOCAL_ADDPATH)) | \
BIT(BE16(BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH)) \
)
#define _BGP4MP_MESSAGEMASK ( \
BIT(BE16(BGP4MP_MESSAGE)) | \
BIT(BE16(BGP4MP_MESSAGE_AS4)) | \
BIT(BE16(BGP4MP_MESSAGE_LOCAL)) | \
BIT(BE16(BGP4MP_MESSAGE_AS4_LOCAL)) | \
BIT(BE16(BGP4MP_MESSAGE_ADDPATH)) | \
BIT(BE16(BGP4MP_MESSAGE_AS4_ADDPATH)) | \
BIT(BE16(BGP4MP_MESSAGE_LOCAL_ADDPATH)) | \
BIT(BE16(BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH)) \
)
#define _ZEBRA_MESSAGEMASK ( \
BIT(BE16(ZEBRA_UPDATE)) | \
BIT(BE16(ZEBRA_OPEN)) | \
BIT(BE16(ZEBRA_NOTIFY)) | \
BIT(BE16(ZEBRA_KEEPALIVE)) \
)
/// Test whether a MRT record type provides extended precision timestamp header.
FORCE_INLINE Boolean MRT_ISEXHDRTYPE(MrtType type)
{
return BE16(type) <= 49 && (_MRT_EXHDRMASK & BIT(BE16(type))) != 0;
}
/// Test whether a MRT record type is BGP4MP.
FORCE_INLINE Boolean MRT_ISBGP4MP(MrtType type)
{
return type == MRT_BGP4MP || type == MRT_BGP4MP_ET;
}
/// Test whether a MRT record type is ISIS.
FORCE_INLINE Boolean MRT_ISISIS(MrtType type)
{
return type == MRT_ISIS || type == MRT_ISIS_ET;
}
/// Test whether a MRT record type is OSPFV3.
FORCE_INLINE Boolean MRT_ISOSPFV3(MrtType type)
{
return type == MRT_OSPFV3 || type == MRT_OSPFV3_ET;
}
/// Test whether a BGP4MP MRT record subtype contains 32-bits ASN.
FORCE_INLINE Boolean BGP4MP_ISASN32BIT(MrtSubType subtype)
{
return BE16(subtype) <= 11 && (_BGP4MP_AS4MASK & BIT(BE16(subtype))) != 0;
}
/// Test whether a BGP4MP MRT subtype belongs has ADD_PATH information.
FORCE_INLINE Boolean BGP4MP_ISADDPATH(MrtSubType subtype)
{
return BE16(subtype) <= 11 &&
(_BGP4MP_ADDPATHMASK & BIT(BE16(subtype))) != 0;
}
/**
* \brief Test whether a BGP4MP MRT subtype wraps a BGP message.
*
* \see `Bgp_UnwrapBgp4mp()`
*/
FORCE_INLINE Boolean BGP4MP_ISMESSAGE(MrtSubType subtype)
{
return BE16(subtype) <= 11 &&
(_BGP4MP_MESSAGEMASK & BIT(BE16(subtype))) != 0;
}
FORCE_INLINE Boolean BGP4MP_ISSTATECHANGE(MrtSubType subtype)
{
return subtype == BGP4MP_STATE_CHANGE ||
subtype == BGP4MP_STATE_CHANGE_AS4;
}
FORCE_INLINE Boolean TABLE_DUMPV2_ISRIB(MrtSubType subtype)
{
return BE16(subtype) <= 12 &&
(_TABLE_DUMPV2_RIBMASK & BIT(BE16(subtype))) != 0;
}
FORCE_INLINE Boolean TABLE_DUMPV2_ISIPV4RIB(MrtSubType subtype)
{
return BE16(subtype) <= 12 &&
(_TABLE_DUMPV2_V4RIBMASK & BIT(BE16(subtype))) != 0;
}
FORCE_INLINE Boolean TABLE_DUMPV2_ISIPV6RIB(MrtSubType subtype)
{
return BE16(subtype) <= 12 &&
(_TABLE_DUMPV2_V6RIBMASK & BIT(BE16(subtype))) != 0;
}
FORCE_INLINE Boolean TABLE_DUMPV2_ISGENERICRIB(MrtSubType subtype)
{
return subtype == TABLE_DUMPV2_RIB_GENERIC ||
subtype == TABLE_DUMPV2_RIB_GENERIC_ADDPATH;
}
FORCE_INLINE Boolean TABLE_DUMPV2_ISUNICASTRIB(MrtSubType subtype)
{
return BE16(subtype) <= 12 &&
(_TABLE_DUMPV2_UNICASTRIBMASK & BIT(BE16(subtype))) != 0;
}
FORCE_INLINE Boolean TABLE_DUMPV2_ISMULTICASTRIB(MrtSubType subtype)
{
return BE16(subtype) <= 12 &&
(_TABLE_DUMPV2_MULTICASTRIBMASK & BIT(BE16(subtype))) != 0;
}
FORCE_INLINE Boolean TABLE_DUMPV2_ISADDPATHRIB(MrtSubType subtype)
{
return BE16(subtype) <= 12 &&
(_TABLE_DUMPV2_ADDPATHRIBMASK & BIT(BE16(subtype))) != 0;
}
/**
* \brief Test whether a ZEBRA BGP message wraps BGP message.
*
* \see `Bgp_UnwrapZebra()`
*/
FORCE_INLINE Boolean ZEBRA_ISMESSAGE(MrtSubType subtype)
{
return BE16(subtype) <= 7 &&
(_ZEBRA_MESSAGEMASK & BIT(BE16(subtype))) != 0;
}
#pragma pack(push, 1)
/**
* \brief MRT header type.
*
* \warning **Misaligned struct**.
* \note Fields are in network order (big endian).
*/
typedef ALIGNED(1, struct) {
Uint32 timestamp; ///< Unix timestamp
MrtType type; ///< MRT record type
MrtSubType subtype; ///< MRT record subtype
Uint32 len; ///< Record length in bytes **not including** header itself
} Mrthdr;
/**
* \brief Extended MRT header, includes microseconds information.
*
* \warning **Misaligned struct**.
* \note Fields are in network order (big endian).
*
* \see `Mrthdr`
*/
typedef ALIGNED(1, struct) {
Uint32 timestamp;
MrtType type;
MrtSubType subtype;
Uint32 len;
Uint32 microsecs;
} Mrthdrex;
typedef ALIGNED(1, struct) {
Uint32 peerBgpId; ///< Peer BGP identifier
Uint16 viewNameLen; ///< `viewName` field length in chars
char viewName[FLEX_ARRAY]; ///< Optional view name, **not** `\0` terminated
// Uint16 peerCount;
// Mrtpeerent peers[FLEX_ARRAY];
} Mrtpeeridx;
#define MRT_PEER_IP6 BIT(0)
#define MRT_PEER_ASN32BIT BIT(1)
typedef ALIGNED(1, struct) {
Uint8 type;
Uint32 bgpId;
union {
struct {
Ipv4adr addr;
Asn16 asn;
} a16v4;
struct {
Ipv4adr addr;
Asn32 asn;
} a32v4;
struct {
Ipv6adr addr;
Asn16 asn;
} a16v6;
struct {
Ipv6adr addr;
Asn32 asn;
} a32v6;
};
} Mrtpeerentv2;
FORCE_INLINE Boolean MRT_ISPEERIPV6(Uint8 type)
{
return (type & MRT_PEER_IP6) != 0;
}
FORCE_INLINE Boolean MRT_ISPEERASN32BIT(Uint8 type)
{
return (type & MRT_PEER_ASN32BIT) != 0;
}
FORCE_INLINE Asn MRT_GETPEERADDR(Ipadr *dest, const Mrtpeerentv2 *peer)
{
switch (peer->type & (MRT_PEER_IP6|MRT_PEER_ASN32BIT)) {
case MRT_PEER_IP6|MRT_PEER_ASN32BIT:
dest->family = IP6;
dest->v6 = peer->a32v6.addr;
return ASN32BIT(peer->a32v6.asn);
case MRT_PEER_IP6:
dest->family = IP6;
dest->v6 = peer->a16v6.addr;
return ASN16BIT(peer->a16v6.asn);
case MRT_PEER_ASN32BIT:
dest->family = IP4;
dest->v4 = peer->a32v4.addr;
return ASN32BIT(peer->a32v4.asn);
default:
dest->family = IP4;
dest->v4 = peer->a16v4.addr;
return ASN16BIT(peer->a16v4.asn);
}
}
#define _MRT_RIBENT_FIELDS(IpT) \
IpT prefix; \
Uint8 prefixLen; \
Uint8 status; \
Uint32 originatedTime; \
IpT peerAddr; \
Uint16 peerAs; \
Uint16 attrLen
/**
* \brief Legacy TABLE_DUMP RIB entry.
*
* \warning **Misaligned struct**.
* \note Fields are in network order (big endian).
*/
typedef ALIGNED(1, struct) {
Uint16 viewno;
Uint16 seqno;
union {
struct {
_MRT_RIBENT_FIELDS(Ipv4adr);
// Uint8 attrs[FLEX_ARRAY];
} v4;
struct {
_MRT_RIBENT_FIELDS(Ipv6adr);
// Uint8 attrs[FLEX_ARRAY];
} v6;
};
} Mrtribent;
FORCE_INLINE Boolean RIB_GETPFX(Afi afi,
RawPrefix *dest,
const Mrtribent *rib)
{
EXTERNC void *memcpy(void *, const void *, size_t);
switch (afi) {
case AFI_IP:
if (rib->v4.prefixLen > IPV4_WIDTH)
return FALSE;
dest->width = rib->v4.prefixLen;
memcpy(dest->bytes, &rib->v4.prefix, PFXLEN(rib->v4.prefixLen));
return TRUE;
case AFI_IP6:
if (rib->v6.prefixLen > IPV6_WIDTH)
return FALSE;
dest->width = rib->v6.prefixLen;
memcpy(dest->bytes, &rib->v6.prefix, PFXLEN(rib->v6.prefixLen));
return TRUE;
default:
UNREACHABLE;
return FALSE;
}
}
FORCE_INLINE Uint32 RIB_GETORIGINATED(Afi afi, const Mrtribent *rib)
{
switch (afi) {
case AFI_IP: return rib->v4.originatedTime;
case AFI_IP6: return rib->v6.originatedTime;
default:
UNREACHABLE;
return 0;
}
}
FORCE_INLINE Asn RIB_GETPEERADDR(Afi afi, Ipadr *dest, const Mrtribent *rib)
{
switch (afi) {
case AFI_IP:
dest->family = afi;
dest->v4 = rib->v4.peerAddr;
return ASN16BIT(rib->v4.peerAs);
case AFI_IP6:
dest->family = afi;
dest->v6 = rib->v6.peerAddr;
return ASN16BIT(rib->v6.peerAs);
default:
UNREACHABLE;
return -1;
}
}
// NOTE: RIB AFI is stored inside `Mrthdr subtype` field.
FORCE_INLINE Bgpattrseg *RIB_GETATTRIBS(Afi afi, const Mrtribent *rib)
{
switch (afi) {
case AFI_IP: return (Bgpattrseg *) &rib->v4.attrLen;
case AFI_IP6: return (Bgpattrseg *) &rib->v6.attrLen;
default: UNREACHABLE; return NULL;
}
}
typedef ALIGNED(1, struct) {
Uint32 seqno;
union {
RawPrefix nlri;
struct {
Afi afi;
Safi safi;
RawPrefix nlri;
} gen;
};
} Mrtribhdrv2;
FORCE_INLINE Mrtribhdrv2 *RIBV2_HDR(const Mrthdr *hdr)
{
return (Mrtribhdrv2 *) (hdr + 1);
}
typedef ALIGNED(1, struct) {
Uint16 entryCount;
Uint8 entries[FLEX_ARRAY];
} Mrtribentriesv2;
/**
* \brief TABLE_DUMPV2 RIB entry.
*
* \warning **Misaligned struct**.
* \note Fields are in network order (big endian).
*/
typedef ALIGNED(1, struct) {
Uint16 peerIndex; ///< `Mrtpeerentv2` index inside PEER_INDEX_TABLE
Uint32 originatedTime;
Uint8 data[FLEX_ARRAY];
} Mrtribentv2;
FORCE_INLINE Bgpattrseg *RIBV2_GETATTRIBS(MrtSubType subtype,
const Mrtribentv2 *rib)
{
return (Bgpattrseg *) (TABLE_DUMPV2_ISADDPATHRIB(subtype) ?
rib->data + 4 :
rib->data);
}
FORCE_INLINE Uint32 RIBV2_GETPATHID(const Mrtribentv2 *ent)
{
EXTERNC void *memcpy(void *, const void *, size_t);
Uint32 pathid;
memcpy(&pathid, ent->data, sizeof(pathid));
return pathid;
}
FORCE_INLINE void RIBV2_SETPATHID(Mrtribentv2 *ent, Uint32 pathid)
{
EXTERNC void *memcpy(void *, const void *, size_t);
memcpy(ent->data, &pathid, sizeof(pathid));
}
FORCE_INLINE void RIBV2_GETNLRI(MrtSubType subtype,
Prefix *dest,
const Mrtribhdrv2 *hdr,
const Mrtribentv2 *ent)
{
EXTERNC void *memcpy(void *, const void *, size_t);
if (TABLE_DUMPV2_ISGENERICRIB(subtype)) {
dest->afi = hdr->gen.afi;
dest->safi = hdr->gen.safi;
memcpy(PLAINPFX(dest), &hdr->gen.nlri, 1 + PFXLEN(hdr->gen.nlri.width));
} else {
dest->afi = TABLE_DUMPV2_ISIPV6RIB(subtype) ?
AFI_IP6 :
AFI_IP;
dest->safi = TABLE_DUMPV2_ISMULTICASTRIB(subtype) ?
SAFI_MULTICAST :
SAFI_UNICAST;
memcpy(PLAINPFX(dest), &hdr->nlri, 1 + PFXLEN(hdr->nlri.width));
}
dest->isAddPath = TABLE_DUMPV2_ISADDPATHRIB(subtype);
dest->pathId = (dest->isAddPath) ? RIBV2_GETPATHID(ent) : 0;
}
#define _MRT_BGP4MP_COMFIELDS(AsnT) \
AsnT peerAs, localAs; \
Uint16 iface; \
Afi afi
#define _MRT_BGP4MP_HDRFIELDS(AsnT, IpT) \
_MRT_BGP4MP_COMFIELDS(AsnT); \
IpT peerAddr, localAddr
/// Common header fields to all BGP4MP MRT records.
typedef ALIGNED(1, union) {
struct {
_MRT_BGP4MP_COMFIELDS(Asn32);
} a32;
struct {
_MRT_BGP4MP_COMFIELDS(Asn16);
} a16;
struct {
_MRT_BGP4MP_HDRFIELDS(Asn16, Ipv4adr);
} a16v4;
struct {
_MRT_BGP4MP_HDRFIELDS(Asn16, Ipv6adr);
} a16v6;
struct {
_MRT_BGP4MP_HDRFIELDS(Asn32, Ipv4adr);
} a32v4;
struct {
_MRT_BGP4MP_HDRFIELDS(Asn32, Ipv6adr);
} a32v6;
} Bgp4mphdr;
FORCE_INLINE Bgp4mphdr *BGP4MP_HDR(const Mrthdr *hdr)
{
return (hdr->subtype == MRT_BGP4MP_ET) ?
(Bgp4mphdr *) ((const Mrthdrex *) hdr + 1) :
(Bgp4mphdr *) (hdr + 1);
}
FORCE_INLINE Asn BGP4MP_GETPEERADDR(MrtSubType subtype,
Ipadr *dest,
const Bgp4mphdr *bgp4mp)
{
if (BGP4MP_ISASN32BIT(subtype)) {
switch (bgp4mp->a32.afi) {
case AFI_IP:
dest->family = IP4;
dest->v4 = bgp4mp->a32v4.peerAddr;
break;
case AFI_IP6:
dest->family = IP6;
dest->v6 = bgp4mp->a32v6.peerAddr;
break;
default:
UNREACHABLE;
break;
}
return ASN32BIT(bgp4mp->a32.peerAs);
} else {
switch (bgp4mp->a16.afi) {
case AFI_IP:
dest->family = IP4;
dest->v4 = bgp4mp->a16v4.peerAddr;
break;
case AFI_IP6:
dest->family = IP6;
dest->v6 = bgp4mp->a16v6.peerAddr;
break;
default:
UNREACHABLE;
break;
}
return ASN16BIT(bgp4mp->a16.peerAs);
}
}
FORCE_INLINE Asn BGP4MP_GETLOCALADDR(MrtSubType subtype,
Ipadr *dest,
const Bgp4mphdr *bgp4mp)
{
if (BGP4MP_ISASN32BIT(subtype)) {
switch (bgp4mp->a32.afi) {
case AFI_IP:
dest->family = IP4;
dest->v4 = bgp4mp->a32v4.localAddr;
break;
case AFI_IP6:
dest->family = IP6;
dest->v6 = bgp4mp->a32v6.localAddr;
break;
default:
UNREACHABLE;
return -1;
}
return ASN32BIT(bgp4mp->a32.localAs);
} else {
switch (bgp4mp->a16.afi) {
case AFI_IP:
dest->family = IP4;
dest->v4 = bgp4mp->a16v4.localAddr;
break;
case AFI_IP6:
dest->family = IP6;
dest->v6 = bgp4mp->a16v6.localAddr;
break;
default:
UNREACHABLE;
return -1;
}
return ASN16BIT(bgp4mp->a16.localAs);
}
}
/// BGP4MP message providing BGP Finite State Machine (FSM) state change information.
typedef ALIGNED(1, union) {
Bgp4mphdr hdr;
struct {
_MRT_BGP4MP_HDRFIELDS(Asn16, Ipv4adr);
BgpFsmState oldState, newState;
} a16v4;
struct {
_MRT_BGP4MP_HDRFIELDS(Asn16, Ipv6adr);
BgpFsmState oldState, newState;
} a16v6;
struct {
_MRT_BGP4MP_HDRFIELDS(Asn32, Ipv4adr);
BgpFsmState oldState, newState;
} a32v4;
struct {
_MRT_BGP4MP_HDRFIELDS(Asn32, Ipv6adr);
BgpFsmState oldState, newState;
} a32v6;
} Bgp4mpstatchng;
typedef ALIGNED(1, struct) {
Uint16 peerAs;
Ipv4adr peerAddr;
} Zebrahdr;
FORCE_INLINE Zebrahdr *ZEBRA_HDR(const Mrthdr *hdr)
{
return (Zebrahdr *) (hdr + 1);
}
typedef ALIGNED(1, struct) {
Zebrahdr hdr;
Uint16 localAs;
Ipv4adr localAddr;
Uint8 msg[FLEX_ARRAY];
} Zebramsghdr;
typedef ALIGNED(1, struct) {
Zebrahdr hdr;
BgpFsmState oldState, newState;
} Zebrastatchng;
#pragma pack(pop)
/// MRT record header size in bytes.
#define MRT_HDRSIZ (4uLL + 2uLL + 2uLL + 4uLL)
/// Extended timestamp MRT record header size in bytes.
#define MRT_EXHDRSIZ (MRT_HDRSIZ + 4uLL)
STATIC_ASSERT(MRT_HDRSIZ == sizeof(Mrthdr), "MRT_HDRSIZ vs Mrthdr size mismatch");
STATIC_ASSERT(MRT_EXHDRSIZ == sizeof(Mrthdrex), "MRT_EXHDRSIZ vs Mrthdrex size mismatch");
/// Retrieve a pointer to a MRT record header.
FORCE_INLINE Mrthdr *MRT_HDR(const Mrtrecord *rec)
{
return (Mrthdr *) rec->buf;
}
typedef struct {
Uint8 *base, *lim;
Uint8 *ptr;
Uint16 peerCount;
Uint16 nextIdx;
} Mrtpeeriterv2;
typedef struct {
Uint8 *base, *lim;
Uint8 *ptr;
Boolean8 isAddPath;
Uint16 entryCount;
Uint16 nextIdx;
} Mrtribiterv2;
Judgement Bgp_MrtFromBuf(Mrtrecord *rec, const void *data, size_t nbytes);
Judgement Bgp_ReadMrt(Mrtrecord *rec, void *streamp, const StmOps *ops);
void Bgp_ClearMrt(Mrtrecord *rec);
// =========================================
// TABLE_DUMPV2 - PEER_INDEX_TABLE
Mrtpeeridx *Bgp_GetMrtPeerIndex(Mrtrecord *rec);
void *Bgp_GetMrtPeerIndexPeers(Mrtrecord *rec, size_t *peersCount, size_t *nbytes);
Judgement Bgp_StartMrtPeersv2(Mrtpeeriterv2 *it, Mrtrecord *rec);
Mrtpeerentv2 *Bgp_NextMrtPeerv2(Mrtpeeriterv2 *it);
Mrtpeerentv2 *Bgp_GetMrtPeerByIndex(Mrtrecord *rec, Uint16 idx);
// =========================================
// TABLE_DUMPV2 - RIB SubTypes
Mrtribhdrv2 *Bgp_GetMrtRibHdrv2(Mrtrecord *rec, size_t *nbytes);
Mrtribentriesv2 *Bgp_GetMrtRibEntriesv2(Mrtrecord *rec, size_t *nbytes);
Judgement Bgp_StartMrtRibEntriesv2(Mrtribiterv2 *it, Mrtrecord *rec);
Mrtribentv2 *Bgp_NextRibEntryv2(Mrtribiterv2 *rec);
// =========================================
// BGP4MP
Bgp4mphdr *Bgp_GetBgp4mpHdr(Mrtrecord *rec, size_t *nbytes);
Judgement Bgp_UnwrapBgp4mp(Mrtrecord *rec, Bgpmsg *msg, unsigned flags);
// =========================================
// DEPRECATED - TABLE_DUMP
Mrtribent *Bgp_GetMrtRibHdr(Mrtrecord *rec);
void *Bgp_GetMrtRibEntry(Mrtrecord *rec, size_t *nbytes);
// =========================================
// DEPRECATED - ZEBRA
Zebrahdr *Bgp_GetZebraHdr(Mrtrecord *rec, size_t *nbytes);
Judgement Bgp_UnwrapZebra(Mrtrecord *rec, Bgpmsg *msg, unsigned flags);
// ==========================================
// RIB attribute segment encoding formats
Judgement Bgp_StartAllRibv2NextHops(Nexthopiter *it,
const Mrthdr *hdr,
const Bgpattrseg *tpa,
Bgpattrtab tab);
Judgement Bgp_RebuildMsgFromRib(const Prefix *nlri,
const Bgpattrseg *tpa,
Bgpmsg *msg,
unsigned flags);
#endif

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/patricia.h
*
* PATRICIA trie implementation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGP_PATRICIA_H_
#define DF_BGP_PATRICIA_H_
#include "bgp/prefix.h"
/// Opaque type, trie memory chunk block.
typedef struct Patblock Patblock;
/// Opaque type, concrete trie node.
typedef union Patnode Patnode;
/// PATRICIA trie.
typedef struct {
Afi afi; ///< AFI type
unsigned nprefixes; ///< Prefixes count stored inside trie
Patnode *head; ///< Trie root node
Patblock *blocks; ///< PATRICIA memory blocks
Patnode *freeBins[IPV6_SIZE / 4]; ///< Fast free bins
} Patricia;
RawPrefix *Pat_Insert(Patricia *trie, const RawPrefix *pfx);
Boolean Pat_Remove(Patricia *trie, const RawPrefix *pfx);
RawPrefix *Pat_SearchExact(const Patricia *trie, const RawPrefix *pfx);
Boolean Pat_IsSubnetOf(const Patricia *trie, const RawPrefix *pfx);
Boolean Pat_IsSupernetOf(const Patricia *trie, const RawPrefix *pfx);
Boolean Pat_IsRelatedOf(const Patricia *trie, const RawPrefix *pfx);
/// Reset `trie` and free all memory.
void Pat_Clear(Patricia *trie);
#endif

222
lonetix/include/df/bgp/prefix.h Executable file
View File

@ -0,0 +1,222 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/prefix.h
*
* Network prefixes types and utilities.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGP_PREFIX_H_
#define DF_BGP_PREFIX_H_
#include "sys/ip.h"
/**
* \brief `Afi` values.
*
* \see [Address family numbers](https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml)
*
* \note Address family numbers are in network order (big endian).
*
* @{
*/
#define AFI_IP BE16(1)
#define AFI_IP6 BE16(2)
/**
* \brief Address Family Identifier, as defined by the BGP protocol.
*
* \note Address family numbers are in network order (big endian).
*/
typedef Uint16 Afi;
/** @} */
/**
* \brief `Safi` values.
*
* \see [SAFI namespace](https://www.iana.org/assignments/safi-namespace/safi-namespace.xhtml)
* \see [RFC 4760](http://www.iana.org/go/rfc4760)
*
* @{
*/
#define SAFI_UNICAST U8_C(1)
#define SAFI_MULTICAST U8_C(2)
/// Subsequent Address Family Identifier, as defined by the BGP protocol.
typedef Uint8 Safi;
/** @} */
#pragma pack(push, 1)
/**
* \brief BGP prefix with PATH ID information
*
* \warning **Misaligned struct**.
* \note Fields are in network order (big endian).
*/
typedef ALIGNED(1, struct) {
Uint32 pathId; ///< Path identifier
Uint8 width; ///< Prefix length in bits
/**
* \brief Prefix content `union`.
*
* \warning Only `PFXLEN(width)` bytes are relevant.
*/
union {
Uint8 bytes[16]; ///< Prefix raw bytes
Uint16 words[8]; ///< Prefix contents as a sequence of 16-bits values
Uint32 dwords[4]; ///< Prefix contents as a sequence of 32-bits values
Ipv4adr v4; ///< Prefix as a **full** IPv4 address
Ipv6adr v6; ///< Prefix as a **full** IPv6 address
};
} ApRawPrefix;
/**
* \brief BGP prefix with no PATH ID information.
*
* \warning **Misaligned struct**.
* \note Fields are in network order (big endian).
*/
typedef ALIGNED(1, struct) {
Uint8 width; ///< Prefix length in bits
/**
* \brief Prefix content `union`.
*
* \warning Only `PFXLEN(width)` bytes are relevant.
*/
union {
Uint8 bytes[16]; ///< Prefix raw bytes
Uint16 words[8]; ///< Prefix contents as a sequence of 16-bits values
Uint32 dwords[4]; ///< Prefix contents as a sequence of 32-bits values
Ipv4adr v4; ///< Prefix as a **full** IPv4 address
Ipv6adr v6; ///< Prefix as a **full** IPv6 address
};
} RawPrefix;
/**
* \brief "Fat" prefix structure, contains both the actual data and metadata about the BGP prefix itself.
*
* The structure doesn't reflect the actual BGP protocol format,
* it is used whenever a raw BGP data pointer isn't sufficient
* to convey enough context for a prefix (e.g. iterating every prefix available
* inside a BGP message).
*
* \warning **Misaligned struct**.
* \note Fields are in network order (big endian).
*
* \note Given the significant amount of metadata, it
* should be used sparingly, raw prefixes should be preferred
* whenever possible to save the overhead.
*/
typedef ALIGNED(1, struct) {
Boolean8 isAddPath; ///< Whether the path identifier is meaningful or not
Afi afi; ///< Prefix address family
Safi safi; ///< Prefix subsequent AFI
Uint32 pathId; ///< Path identifier (only meaningful if `isAddPath` is `TRUE`)
Uint8 width; ///< Prefix width, in bits (maximum legal value depends on AFI)
/**
* \brief Prefix content `union`.
*
* \warning Only `PFXLEN(width)` bytes are relevant.
*/
union {
Uint8 bytes[16]; ///< Prefix raw bytes
Uint16 words[8]; ///< Prefix contents as a sequence of 16-bits values
Uint32 dwords[4]; ///< Prefix contents as a sequence of 32-bits values
Ipv4adr v4; ///< Prefix as a **full** IPv4 address
Ipv6adr v6; ///< Prefix as a **full** IPv6 address
};
} Prefix;
#pragma pack(pop)
/// Calculate prefix length in bytes from bit width.
#define PFXLEN(width) ((size_t) (((width) >> 3) + (((width) & 7) != 0)))
/**
* \brief Return pointer to `RawPrefix` portion out of `ApRawPrefix`, `Prefix` or `RawPrefix` itself.
*
* Cast operation is useful to discard PATH ID information where irrelevant.
*/
#define PLAINPFX(prefix) ((RawPrefix *) (&(prefix)->width))
/// Return pointer to `ApRawPrefix` out of `Prefix` or `ApRawPrefix` itself.
#define APPFX(prefix) ((ApRawPrefix *) (&(prefix)->pathId))
/// Maximum length of an IPv6 prefix encoded as a string.
#define PFX6_STRLEN (IPV6_STRLEN + 1 + 3)
/// Maximum length of an IPv4 prefix encoded as a string.
#define PFX4_STRLEN (IPV4_STRLEN + 1 + 2)
/// Maximum length of an IPv6 prefix with PATH ID, encoded as a string.
#define APPFX6_STRLEN (10 + 1 + PFX6_STRLEN)
/// Maximum length of an IPv4 prefix with PATH ID, encoded as a string.
#define APPFX4_STRLEN (10 + 1 + PFX4_STRLEN)
/// Maximum length of a prefix encoded as a string.
#define PFX_STRLEN PFX6_STRLEN
/// Maximum length of a prefix with PATH ID, encoded as a string.
#define APPFX_STRLEN APPFX6_STRLEN
/**
* Convert a `RawPrefix` of the specified `Afi` to its string representation.
*
* \return Pointer to the trailing `\0` inside `dest`.
*
* \note Assumes `dest` is large enough to hold result (use `[PFX_STRLEN + 1]`)
*/
char *Bgp_PrefixToString(Afi afi, const RawPrefix *pfx, char *dest);
/**
* Convert an `ApRawPrefix` of the specified `Afi` to its string representation.
*
* \return Pointer to the trailing `\0` inside `dest`.
*
* \note Assumes `dest` is large enough to hold result (use `[APPFX_STRLEN + 1]`)
*/
char *Bgp_ApPrefixToString(Afi afi, const ApRawPrefix *pfx, char *dest);
/**
* \brief Convert string with format `address/width` to `Prefix`.
*
* \return `OK` on success, `NG` on invalid prefix string.
*
* \note Does not handle PATH ID, may only return plain prefixes.
*/
Judgement Bgp_StringToPrefix(const char *s, Prefix *dest);
/**
* \brief Direct iterator over raw prefix data.
*
* \note `struct` should be considered opaque.
*/
typedef struct {
Afi afi;
Safi safi;
Boolean8 isAddPath;
Uint8 *base, *lim;
Uint8 *ptr;
} Prefixiter;
/**
* \brief Start iterating `nbytes` bytes from `data` for prefixes of the specified `afi` and `safi`.
*
* \return `OK` on success, `NG` on error. Sets BGP error, see `Bgp_GetErrStat()`.
*/
Judgement Bgp_StartPrefixes(Prefixiter *it, Afi afi, Safi safi, const void *data, size_t nbytes, Boolean isAddPath);
/**
* \brief Get current prefix and advance iterator.
*
* \return Current prefix on success, depending on `isAddPath` prefix type
* may be either `RawPrefix` or `ApRawPrefix`. `NULL` on iteration end or
* error. Sets BGP error, see `Bgp_GetErrStat()`.
*/
void *Bgp_NextPrefix(Prefixiter *it);
#endif

385
lonetix/include/df/bgp/vm.h Executable file
View File

@ -0,0 +1,385 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm.h
*
* BGP message filtering engine.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGP_VM_H_
#define DF_BGP_VM_H_
#include "bgp.h"
/**
* \name BGP VM bytecode
*
* \brief Filtering engine instruction OPCODEs and arguments.
*
* Bytecode format is:
* ```
* LSB MSB (NATIVE endianness)
* +--------+--------+
* | OPCODE | ARG |
* +--------+--------+
* 0 16 bit
*
* (2 bytes per instruction)
* ```
*
* Bytecode follows native machine endianness, it is NOT endian independent.
*
* @{
*/
/// BGP filter VM OPCODE
typedef Uint8 Bgpvmopc;
/// BGP filter VM bytecode instruction
typedef Uint16 Bgpvmbytec;
/// Returns a VM bytecode with the specified opcode and argument.
#define BGP_VMOP(opc, arg) ((Bgpvmbytec) ((((Uint8) (arg)) << 8) | ((Bgpvmopc) opc)))
/// Extract OPCODE from the specified bytecode.
FORCE_INLINE Bgpvmopc BGP_VMOPC(Bgpvmbytec bytec)
{
return (Bgpvmopc) (bytec & 0xff);
}
/// Extract direct argument from the specified bytecode.
FORCE_INLINE Uint8 BGP_VMOPARG(Bgpvmbytec bytec)
{
return (bytec >> 8);
}
/// NO-OPERATION - does nothing and moves on
#define BGP_VMOP_NOP U8_C(0)
/// LOAD - Pushes `ARG` constant on stack, interpreting it as a `Sint8`
#define BGP_VMOP_LOAD U8_C(1)
/// LOAD UNSIGNED - Pushes `ARG` constant on stack, interpreting it as an `Uint8`
#define BGP_VMOP_LOADU U8_C(2)
/// LOAD NULL - Push a `NULL` address on stack
#define BGP_VMOP_LOADN U8_C(3)
/// LOADK - Push a new constant (K) on stack, `ARG` is the index inside `K`
#define BGP_VMOP_LOADK U8_C(4)
/// CALL - invoke an external native function
#define BGP_VMOP_CALL U8_C(5)
/// BLOCK OPEN - Push a new matching block (used to implement AND/OR chains)
#define BGP_VMOP_BLK U8_C(6)
/// END BLOCK - Pops the current matching block
#define BGP_VMOP_ENDBLK U8_C(7)
/// TAG LAST MATCH - Tags last operation's match with `ARG`
#define BGP_VMOP_TAG U8_C(8)
/// NOT - Boolean negate the topmost stack element
#define BGP_VMOP_NOT U8_C(9)
/// CONDITIONAL FAIL If TRUE - Fail the current matching `BLK` if topmost stack element is non-zero
#define BGP_VMOP_CFAIL U8_C(10)
/// CONDITIONAL PASS If TRUE - Pass the current matching `BLK` if topmost stack element is non-zero
#define BGP_VMOP_CPASS U8_C(11)
/// Jump if zero - Skip over a positive number of instructions if topmost stack element is 0.
#define BGP_VMOP_JZ U8_C(12)
/// Jump if non-zero - Skip over a positive number of instructions if topmost stack element is not 0.
#define BGP_VMOP_JNZ U8_C(13)
/// CHECK TYPE - ARG is the `BgpType` to test against
#define BGP_VMOP_CHKT U8_C(14)
/// CHECK ATTRIBUTE - ARG is the `BgpAttrCode` to test for existence
#define BGP_VMOP_CHKA U8_C(15)
#define BGP_VMOP_EXCT U8_C(16)
#define BGP_VMOP_SUPN U8_C(17)
#define BGP_VMOP_SUBN U8_C(18)
/// RELATED - Tests whether the BGP message contains prefixes related with the provided ones
#define BGP_VMOP_RELT U8_C(19)
/// Returns `TRUE` if `opc` belongs to an instruction operating on NETwork prefixes.
FORCE_INLINE Boolean BGP_ISVMOPNET(Bgpvmopc opc)
{
return opc >= BGP_VMOP_EXCT && opc <= BGP_VMOP_RELT;
}
/// AS PATH MATCH - Tests BGP message AS PATH against a match expression
#define BGP_VMOP_ASMTCH U8_C(20)
/// FAST AS PATH MATCH - AS PATH test using precompiled AS PATH match expression
#define BGP_VMOP_FASMTC U8_C(21)
/// COMMUNITY MATCH - COMMUNITY test using a precompiled COMMUNITY match expression
#define BGP_VMOP_COMTCH U8_C(22)
/// ALL COMMUNITY MATCH - Like COMTCH, but requires all communities to be present inside message
#define BGP_VMOP_ACOMTC U8_C(23)
/// END - Terminate VM execution with the latest result
#define BGP_VMOP_END U8_C(24)
// #define BGP_VMOP_MOVK MOVE K - Move topmost K index to ARG K index
// #define BGP_VMOP_DISCRD DISCARD - discard vm->curMatch if any
// Bytecode `ARG` values for `NET` class instructions
#define BGP_VMOPA_NLRI U8_C(0)
#define BGP_VMOPA_MPREACH U8_C(1)
#define BGP_VMOPA_ALL_NLRI U8_C(2)
#define BGP_VMOPA_WITHDRAWN U8_C(3)
#define BGP_VMOPA_MPUNREACH U8_C(4)
#define BGP_VMOPA_ALL_WITHDRAWN U8_C(5)
// Special `Asn` values for `ASMTCH` and `FASMTC` instructions
#define ASNNOTFLAG BIT(61)
/// Matches any ASN **except** the provided one.
#define ASNNOT(asn) ((Asn) ((asn) | ASNNOTFLAG))
/// Tests whether `asn` is a negative ASN match.
FORCE_INLINE Boolean ISASNNOT(Asn asn)
{
return (asn & ASNNOTFLAG) != 0;
}
/// Match with the AS_PATH start (^)
#define ASN_START ((Asn) 0x0000000100000000LL)
/// Match with the AS PATH end ($).
#define ASN_END ((Asn) 0x0000000200000000LL)
/// Match with any ASN (.).
#define ASN_ANY ((Asn) 0x0000000300000000LL)
/// Match zero or more ASN (*).
#define ASN_STAR ((Asn) 0x0000000400000000LL)
/// Match with zero or one ASN (?).
#define ASN_QUEST ((Asn) 0x0000000500000000LL)
/// Match the previous ASN one or more times (+).
#define ASN_PLUS ((Asn) 0x0000000600000000LL)
/// Introduce a new group (opening paren for expressions like `( a b c )`)
#define ASN_NEWGRP ((Asn) 0x0000000700000000LL)
/// Introduce alternative inside matching expression (pipe symbol for expressions like `( a b | b c )`)
#define ASN_ALT ((Asn) 0x0000000800000000LL)
/// Terminate a group expression (closing paren for expressions like `( a b c )`)
#define ASN_ENDGRP ((Asn) 0x0000000900000000LL)
/** @} */
/**
* \brief Stack slot.
*
* The VM stack is a sequence of `Bgpvmval` cells,
* interpreted by each instruction as dictated by the opcode.
*/
typedef union Bgpvmval Bgpvmval;
union Bgpvmval {
void *ptr; ///< Value as a pointer
Sint64 val; ///< Value as a signed integral
};
/// Match info for AS matching expressions.
typedef struct Bgpvmasmatch Bgpvmasmatch;
struct Bgpvmasmatch {
Bgpvmasmatch *next;
Aspathiter spos;
Aspathiter epos;
};
/// Optimization modes for some matching instructions (e.g. BGP_VMOP_COMTCH/BGP_VMOP_ACOMTC).
typedef enum {
BGP_VMOPT_NONE, ///< Do not optimize.
// For COMTCH/ACOMTC
BGP_VMOPT_ASSUME_COMTCH, ///< Assume instruction is going to be `COMTCH`
BGP_VMOPT_ASSUME_ACOMTC ///< Assume instruction is going to be `ACOMTC`
} BgpVmOpt;
typedef struct Bgpmatchcomm Bgpmatchcomm;
struct Bgpmatchcomm {
Boolean8 maskHi; // don't match HI (match of type *:N)
Boolean8 maskLo; // don't match LO (match of type N:*)
Bgpcomm c;
};
/**
* \brief Matching operation result on a BGP message.
*
* Collect relevant information on a matching operation, including the
* direct byte range and position inside BGP message data.
*
* This structure may be used to further process BGP data after the filtering
* is complete. A `Bgpvmmatch` structure is generated for several BGP VM
* OPCODEs, and is only valid up to the next [Bgp_VmExec()](@ref Bgp_VmExec)
* call on the same VM that originated them.
*/
typedef struct Bgpvmmatch Bgpvmmatch;
struct Bgpvmmatch {
Uint32 pc; ///< instruction index that originated this match
Boolean8 isMatching; ///< whether this result declares a match or a mismatch
Boolean8 isPassing; ///< whether this result produced a `PASS` or a `FAIL` inside filter
Uint8 tag; ///< optional tag id for this match (as set by `TAG`)
Uint8 *base, *lim; ///< relevant BGP message segment, if any
void *pos; ///< pointer to detailed match-specific information
Bgpvmmatch *nextMatch; ///< next match in chain (`NULL` if this is the last element)
};
/// Filtering engine error code (a subset of [BgpRet](@ref BgpRet).
typedef Sint8 BgpvmRet;
/// Maximum number of VM constants inside [Bgpvm](@ref Bgpvm).
#define MAXBGPVMK 256
/// Maximum number of VM callable functions inside [Bgpvm](@ref Bgpvm).
#define MAXBGPVMFN 32
/// Maximum allowed nested grouping levels inside a `BGP_VMOP_ASMTCH`.
#define MAXBGPVMASNGRP 32
/**
* \brief Bytecode-based virtual machine operating on BGP messages.
*
* Extensible programmable BGP message matching and filtering engine.
*/
typedef struct Bgpvm Bgpvm;
struct Bgpvm {
Uint32 pc; ///< VM program counter inside `prog`
Uint32 nblk; ///< nested conditional block count
Uint32 nmatches; ///< current execution matches count (length of the `matches` list)
Uint16 si; ///< VM Stack index
Uint16 nk; ///< count of constants (K) available in `k`
Uint8 nfuncs; ///< count of functions (FN) available in `funcs`
Boolean8 setupFailed; ///< whether a `Bgp_VmEmit()` or `Bgp_VmPermAlloc()` on this VM ever failed.
Boolean8 isRunning; ///< whether the VM is being executed
BgpvmRet errCode; ///< whether the VM encountered an error
Uint32 hLowMark; ///< VM heap low memory mark
Uint32 hHighMark; ///< VM heap high memory mark
Uint32 hMemSiz; ///< VM heap size in bytes
Uint32 progLen; ///< bytecode program instruction count
Uint32 progCap; ///< bytecode segment instruction capacity
Bgpmsg *msg; ///< current BGP message being processed
Bgpvmbytec *prog; ///< VM program bytecode, `progLen` instructions (`prog[progLen]` is always `BGP_VMOP_END`)
/**
* Filtering VM heap, managed as follows:
* ```
* hLowMark hHighMark
* heap -+ v v
* v STACK grows upwards .-.-> <.-.-. TEMP grows downwards
* +=====================\-------------------/=============+
* | PERM ALLOCS | STACK > < TEMP ALLOCS |
* +=====================/-------------------\=============+
* [--------------------- hMemSiz -------------------------]
*
* PERM ALLOCS: Allocations that last forever (until the VM is freed)
* - such allocations CANNOT happen while VM is running
*
* TEMP ALLOCS: Allocations that last up to the next Bgp_VmExec()
* - such allocations may only take place while VM is running
* ```
*/
void *heap;
Bgpvmmatch *curMatch; ///< current match being updated during VM execution
Bgpvmmatch *matches; ///< matches produced during execution (contains `nmatches` elements)
Bgpvmval k[MAXBGPVMK]; ///< VM constants (K), `nk` allocated
void (*funcs[MAXBGPVMFN])(Bgpvm *); ///< VM functions (FN), `nfuncs` allocated
};
/// Clear the `errCode` error flag on `vm`.
FORCE_INLINE void BGP_VMCLRERR(Bgpvm *vm)
{
vm->errCode = BGPENOERR;
}
/// Clear the `setupFailed` error flag on `vm`.
FORCE_INLINE void BGP_VMCLRSETUP(Bgpvm *vm)
{
vm->setupFailed = FALSE;
}
FORCE_INLINE Sint32 Bgp_VmNewk(Bgpvm *vm)
{
if (vm->nk >= MAXBGPVMK)
return -1;
return vm->nk++;
}
FORCE_INLINE Sint32 Bgp_VmNewFn(Bgpvm *vm)
{
if (vm->nfuncs >= MAXBGPVMFN)
return -1;
return vm->nfuncs++;
}
FORCE_INLINE Sint32 BGP_VMSETKA(Bgpvm *vm, Sint32 kidx, void *ptr)
{
if (kidx >= 0)
vm->k[kidx].ptr = ptr;
return kidx;
}
FORCE_INLINE Sint32 BGP_VMSETK(Bgpvm *vm, Sint32 kidx, Sint64 val)
{
if (kidx >= 0)
vm->k[kidx].val = val;
return kidx;
}
FORCE_INLINE Sint32 BGP_VMSETFN(Bgpvm *vm, Sint32 idx, void (*fn)(Bgpvm *))
{
if (idx >= 0)
vm->funcs[idx] = fn;
return idx;
}
/**
* \brief Initialize a new VM with the specified heap size.
*
* \return `OK` on success, `NG` on out of memory, sets BGP error, VM error,
* and, on failure, VM setup failure flag.
*/
Judgement Bgp_InitVm(Bgpvm *vm, size_t heapSiz);
/**
* \brief Emit a VM bytecode instruction to `vm`.
*
* \return `OK` if instruction was added successfully, `NG` on out of memory,
* sets BGP error, VM error and, on failure, VM setup failure flag.
*
* \note Emitting `BGP_VMOP_END` has no effect.
*/
Judgement Bgp_VmEmit(Bgpvm *vm, Bgpvmbytec bytec);
/**
* \brief Precompile an AS PATH match expression for use with BGP_VMOP_FASMTC.
*
* \return Pointer suitable as the argument of `BGP_VMOP_FASMTC` instruction
* on success, `NULL` on failure.
* Precompiled expression is stored inside `vm` permanent heap.
*/
void *Bgp_VmCompileAsMatch(Bgpvm *vm, const Asn *match, size_t n);
void *Bgp_VmCompileCommunityMatch(Bgpvm *vm, const Bgpmatchcomm *match, size_t n, BgpVmOpt opt);
/**
* \brief Perform a permanent heap allocation of `size` bytes to `vm`.
*
* \return `vm` heap pointer to memory zone on success, `NULL` on failure,
* sets BGP error, VM error, and, on failure, `vm` setup failure flag.
*
* \note This function may only be called if `vm` is not executing!
*/
void *Bgp_VmPermAlloc(Bgpvm *vm, size_t size);
void *Bgp_VmTempAlloc(Bgpvm *vm, size_t size);
void Bgp_VmTempFree(Bgpvm *vm, size_t size);
/**
* \brief Execute `vm` bytecode on `msg`.
*
* \return `TRUE` if `vm` terminated with PASS, `FALSE` if it terminated on
* FAIL, or if an error was encountered. Sets BGP error and VM error.
*/
Boolean Bgp_VmExec(Bgpvm *vm, Bgpmsg *msg);
/// Print `vm` bytecode dump to stream.
void Bgp_VmDumpProgram(Bgpvm *vm, void *streamp, const StmOps *ops);
/// Reset `vm` state, but keep allocated memory for further setup.
void Bgp_ResetVm(Bgpvm *vm);
/// Clear `vm` and free all memory.
void Bgp_ClearVm(Bgpvm *vm);
#endif

250
lonetix/include/df/bgp/vmintrin.h Executable file
View File

@ -0,0 +1,250 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vmintrin.h
*
* BGP VM engine operation intrinsics.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* Utilities in this file are meant for low level VM interaction,
* usually to implement actual VM extensions.
*/
#ifndef DF_BGP_VMINTRIN_H_
#define DF_BGP_VMINTRIN_H_
#include "bgp/vm.h"
/// Get current VM program counter.
FORCE_INLINE Uint32 BGP_VMCURPC(Bgpvm *vm)
{
return vm->pc - 1; // PC always references *next* instruction
}
/// Get VM stack base pointer.
FORCE_INLINE Bgpvmval *BGP_VMSTK(Bgpvm *vm)
{
return (Bgpvmval *) ((Uint8 *) vm->heap + vm->hLowMark);
}
/// Get stack value at index `idx`, -1 is topmost value, -2 is second to topmost...
FORCE_INLINE Bgpvmval *BGP_VMSTKGET(Bgpvm *vm, Sint32 idx)
{
Bgpvmval *stk = BGP_VMSTK(vm);
return &stk[vm->si + idx];
}
/// Equivalent to `BGP_VMSTKGET()`, but returns stack content as `Sint64`.
FORCE_INLINE Sint64 BGP_VMPEEK(Bgpvm *vm, Sint32 idx)
{
return BGP_VMSTKGET(vm, idx)->val;
}
/// Equivalent to `BGP_VMSTKGET()`, but returns stack content as `void *`.
FORCE_INLINE void *BGP_VMPEEKA(Bgpvm *vm, Sint32 idx)
{
return BGP_VMSTKGET(vm, idx)->ptr;
}
/// Pop `n` values from VM stack, **assumes stack is large enough**.
FORCE_INLINE void BGP_VMPOPN(Bgpvm *vm, Uint32 n)
{
vm->si -= n;
}
/// Pop topmost stack value in VM, returning its value as `Sint64`, **assumes stack is not empty**.
FORCE_INLINE Sint64 BGP_VMPOP(Bgpvm *vm)
{
Bgpvmval *stk = BGP_VMSTK(vm);
return stk[--vm->si].val;
}
/// Like `BGP_VMPOP()`, but returns value as `void *`.
FORCE_INLINE void *BGP_VMPOPA(Bgpvm *vm)
{
Bgpvmval *stk = BGP_VMSTK(vm);
return stk[--vm->si].ptr;
}
/// Push `v` to stack, **assumes enough stack space is available**.
FORCE_INLINE void BGP_VMPUSH(Bgpvm *vm, Sint64 v)
{
Bgpvmval *stk = BGP_VMSTK(vm);
stk[vm->si++].val = v;
}
/// Like `BGP_VMPUSH()`, but pushes a pointer.
FORCE_INLINE void BGP_VMPUSHA(Bgpvm *vm, void *p)
{
Bgpvmval *stk = BGP_VMSTK(vm);
stk[vm->si++].ptr = p;
}
/// Ensure at least `n` elements may be popped from the stack.
FORCE_INLINE Boolean BGP_VMCHKSTKSIZ(Bgpvm *vm, Uint32 n)
{
if (vm->si < n) {
vm->errCode = BGPEVMUFLOW;
return FALSE;
}
return TRUE;
}
/// Ensure at least `n` elements may be pushed to the stack.
FORCE_INLINE Boolean BGP_VMCHKSTK(Bgpvm *vm, Uint32 n)
{
size_t siz = vm->si + n;
siz *= sizeof(Bgpvmval);
if (vm->hHighMark - vm->hLowMark < siz) {
vm->errCode = BGPEVMOFLOW;
return FALSE;
}
return TRUE;
}
/**
* \brief Test whether `vm->msg` header type matches `type`.
*
* \return Pointer to message header on successful match, `NULL`
* otherwise.
*/
FORCE_INLINE Bgphdr *BGP_VMCHKMSGTYPE(Bgpvm *vm, BgpType type)
{
Bgphdr *hdr = BGP_HDR(vm->msg);
return (hdr->type == type) ? hdr : NULL;
}
Judgement Bgp_VmStoreMsgTypeMatch(Bgpvm *vm, Boolean);
void Bgp_VmStoreMatch(Bgpvm *vm);
/// Implement `LOAD`.
FORCE_INLINE void Bgp_VmDoLoad(Bgpvm *vm, Sint8 val)
{
if (!BGP_VMCHKSTK(vm, 1))
return;
BGP_VMPUSH(vm, val);
}
/// Implement `LOADU`.
FORCE_INLINE void Bgp_VmDoLoadu(Bgpvm *vm, Uint8 val)
{
if (!BGP_VMCHKSTK(vm, 1))
return;
BGP_VMPUSH(vm, val);
}
/// Implement `LOADK` of `vm->k[idx]`.
FORCE_INLINE void Bgp_VmDoLoadk(Bgpvm *vm, Uint8 idx)
{
if (idx >= vm->nk) {
vm->errCode = BGPEVMBADK;
return;
}
if (!BGP_VMCHKSTK(vm, 1))
return;
Bgpvmval *stk = BGP_VMSTK(vm);
stk[vm->si++] = vm->k[idx];
}
/// Implement `LOADN`.
FORCE_INLINE void Bgp_VmDoLoadn(Bgpvm *vm)
{
if (!BGP_VMCHKSTK(vm, 1)) {
vm->errCode = BGPEVMOFLOW;
return;
}
BGP_VMPUSHA(vm, NULL);
}
/// Break out of current `BLK`.
FORCE_INLINE void Bgp_VmDoBreak(Bgpvm *vm)
{
Bgpvmopc opc;
do
opc = BGP_VMOPC(vm->prog[vm->pc++]);
while (opc != BGP_VMOP_ENDBLK && opc != BGP_VMOP_END);
if (opc == BGP_VMOP_ENDBLK)
vm->nblk--;
}
/// Execute `CALL` of function `vm->funcs[idx]`.
FORCE_INLINE void Bgp_VmDoCall(Bgpvm *vm, Uint8 idx)
{
void (*fn)(Bgpvm *);
if (idx >= vm->nfuncs) {
vm->errCode = BGPEVMBADFN;
return;
}
fn = vm->funcs[idx];
if (fn) fn(vm);
}
/// Implement `CPASS` (Conditional `PASS` if `TRUE`).
Boolean Bgp_VmDoCpass(Bgpvm *vm);
/// Implement `CFAIL` (Conditional `FAIL` if `TRUE`).
Boolean Bgp_VmDoCfail(Bgpvm *vm);
/// Implement `TAG` instruction with argument `tag`.
FORCE_INLINE void Bgp_VmDoTag(Bgpvm *vm, Uint8 tag)
{
vm->curMatch->tag = tag;
}
/**
* \brief Implements VM `NOT` instruction.
*
* Negate stack topmost value.
*/
FORCE_INLINE void Bgp_VmDoNot(Bgpvm *vm)
{
// Expected STACK:
// -1: Any value interpreted as Sint64
if (!BGP_VMCHKSTKSIZ(vm, 1))
return;
Bgpvmval *v = BGP_VMSTKGET(vm, -1);
v->val = !v->val;
}
/// Implements `CHKT` with argument `type`.
void Bgp_VmDoChkt(Bgpvm *vm, BgpType type);
/// Implements `CHKA` with argument `code`.
void Bgp_VmDoChka(Bgpvm *vm, BgpAttrCode code);
void Bgp_VmDoExct(Bgpvm *vm, Uint8 arg);
void Bgp_VmDoSupn(Bgpvm *vm, Uint8 arg);
void Bgp_VmDoSubn(Bgpvm *vm, Uint8 arg);
void Bgp_VmDoRelt(Bgpvm *vm, Uint8 arg);
void Bgp_VmDoAsmtch(Bgpvm *vm);
void Bgp_VmDoFasmtc(Bgpvm *vm);
void Bgp_VmDoComtch(Bgpvm *vm);
void Bgp_VmDoAcomtc(Bgpvm *vm);
#endif

162
lonetix/include/df/bufio.h Normal file
View File

@ -0,0 +1,162 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bufio.h
*
* Buffered stream writing utilities.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BUFIO_H_
#define DF_BUFIO_H_
#include "stm.h"
#include <stdarg.h>
/// `Stmbuf` buffer size in bytes
#define STM_BUFSIZ 8192uLL
/**
* Buffered output helper structure.
*
* A small `struct` holding an output buffer to help
* and reduce calls to a stream's `Write()` operation.
*/
typedef struct {
Sint64 total; ///< Total bytes flushed to output
Uint32 len; ///< Bytes currently buffered
char buf[STM_BUFSIZ]; ///< Output buffer
void *streamp; ///< Output stream pointer
const StmOps *ops; ///< Output stream operations
} Stmbuf;
/**
* Flush the buffer to output stream.
*
* \return On success returns the **total** bytes written to output
* stream since last call to `Bufio_Init()`,
* that is the value stored inside `sb->total` field after the flush
* operations. Otherwise returns -1.
*
* \note Partial flushes are possible on partial writes, in which case
* some amount of data will remain buffered in `sb` and may be
* flushed later on; `sb->total` and `sb->len` will still be updated
* consistently.
*/
Sint64 Bufio_Flush(Stmbuf *sb);
/**
* Initialize the buffer for writing to `streamp` using the
* `ops` stream operations.
*
* \param [out] sb Buffer to be initialized, must not be `NULL`
* \param [in] streamp Output stream pointer
* \param [in] ops Output stream operations, must not be `NULL`
* and must provide a `Write()` operation
*/
FORCE_INLINE void Bufio_Init(Stmbuf *sb,
void *streamp,
const StmOps *ops)
{
sb->total = 0;
sb->len = 0;
sb->streamp = streamp;
sb->ops = ops;
}
/**
* Write a value to buffer, formatted as string.
*
* \param [in,out] sb Buffer to write to, must not be `NULL`
* \param [in] val Value to be stringified and written to `sb`
*
* \return Number of bytes written to buffer on success,
* -1 on error.
*
* @{
* \fn Sint64 Bufio_Putu(Stmbuf *, unsigned long long)
* \fn Sint64 Bufio_Putx(Stmbuf *, unsigned long long)
* \fn Sint64 Bufio_Puti(Stmbuf *, long long)
* \fn Sint64 Bufio_Putf(Stmbuf *, double)
* @}
*/
Sint64 Bufio_Putu(Stmbuf *sb, unsigned long long val);
Sint64 Bufio_Putx(Stmbuf *sb, unsigned long long val);
Sint64 Bufio_Puti(Stmbuf *sb, long long val);
Sint64 Bufio_Putf(Stmbuf *sb, double val);
/**
* Write a single character to `sb`.
*
* \return Number of bytes written to `sb` on success (equals to 1),
* -1 on error.
*
* \note `\0` may be written and buffered like any other `char`.
*/
FORCE_INLINE Sint64 Bufio_Putc(Stmbuf *sb, char c)
{
if (sb->len == sizeof(sb->buf) && Bufio_Flush(sb) == -1)
return -1;
sb->buf[sb->len++] = c;
return 1;
}
/**
* \def Bufio_Putsn
*
* Write a fixed amount of characters from a string to buffer.
*
* \param [in,out] sb Buffer to write to, must not be `NULL`
* \param [in] s String to pick the characters from
* \param [in] nbytes Bytes count to be written to `sb`
*
* \return Number of bytes written to `sb` on success (equal to
* `nbytes`), -1 on error.
*/
Sint64 _Bufio_Putsn(Stmbuf *, const char *, size_t);
#ifdef __GNUC__
// Optimize to call Bufio_Putc() if 'nbytes' is statically known to be 1
// NOTE: Avoids needless EOLN overhead on Unix
#define Bufio_Putsn(sb, s, nbytes) ( \
(__builtin_constant_p(nbytes) && (nbytes) == 1) ? \
Bufio_Putc(sb, (s)[0]) : \
_Bufio_Putsn(sb, s, nbytes) \
)
#else
#define Bufio_Putsn(sb, s, nbytes) _Bufio_Putsn(sb, s, nbytes)
#endif
/**
* Write string to buffer.
*
* \return Number of bytes written to `sb` on success (equal
* to string length), -1 on error.
*/
FORCE_INLINE Sint64 Bufio_Puts(Stmbuf *sb, const char *s)
{
EXTERNC size_t strlen(const char *); // avoids #include
return Bufio_Putsn(sb, s, strlen(s));
}
/**
* `printf()`-like formatted text print to buffer.
*
* Write formatted string to buffer, like regular `fprintf()`.
*
* \return Number of bytes written to `sb` on success, -1 on error.
*
* @{
* \fn Sint64 Bufio_Printf(Stmbuf *, const char *, ...)
* \fn Sint64 Bufio_Vprintf(Stmbuf *, const char *, va_list)
* @}
*/
CHECK_PRINTF(2, 3) Sint64 Bufio_Printf(Stmbuf *, const char *, ...);
CHECK_PRINTF(2, 0) Sint64 Bufio_Vprintf(Stmbuf *, const char *, va_list);
#endif

347
lonetix/include/df/chkint.h Normal file
View File

@ -0,0 +1,347 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file chkint.h
*
* Overflow checked integer arithmetics.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* This API was modeled after D Language `core.checkedint` module,
* available under the [Boost License 1.0](http://www.boost.org/LICENSE_1_0.txt),
* and originally written by Walter Bright.
*
* \see [How Should You Write a Fast Integer Overflow Check?](https://blog.regehr.org/archives/1139)
* \see [D Language Phobos documentation](https://dlang.org/phobos/core_checkedint.html)
*/
#ifndef DF_CHKINT_H_
#define DF_CHKINT_H_
#include "xpt.h"
#include <limits.h>
// Define this to force portable C implementation of the checked int API
// #define DF_C_ONLY_CHKINT
FORCE_INLINE long long Chk_NoAddll(long long lhs,
long long rhs,
Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
long long res;
*ovrflw |= __builtin_saddll_overflow(lhs, rhs, &res);
return res;
#else
long long res = (unsigned long long) lhs + (unsigned long long) rhs;
*ovrflw |= ((lhs < 0 && rhs < 0 && res >= 0) ||
(lhs >= 0 && rhs >= 0 && res < 0));
return res;
#endif
}
FORCE_INLINE long Chk_NoAddl(long lhs, long rhs, Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
long res;
*ovrflw |= __builtin_saddl_overflow(lhs, rhs, &res);
return res;
#elif LONG_MAX == LLONG_MAX
return Chk_NoAddll(lhs, rhs, ovrflw);
#else
long long res = (long long) lhs + (long long) rhs;
*ovrflw |= (res < LONG_MIN || res > LONG_MAX);
return (int) res;
#endif
}
FORCE_INLINE int Chk_NoAdd(int lhs, int rhs, Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
int res;
*ovrflw |= __builtin_sadd_overflow(lhs, rhs, &res);
return res;
#elif INT_MAX == LONG_MAX
return Chk_NoAddl(lhs, rhs, ovrflw);
#else
long res = (long) lhs + (long) rhs;
*ovrflw |= (res < INT_MIN || res > INT_MAX);
return (int) res;
#endif
}
FORCE_INLINE unsigned long long Chk_NoAddull(unsigned long long lhs,
unsigned long long rhs,
Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
unsigned long long res;
*ovrflw |= __builtin_uaddll_overflow(lhs, rhs, &res);
return res;
#else
unsigned long long res = lhs + rhs;
*ovrflw |= (res < lhs || res < rhs);
return res;
#endif
}
FORCE_INLINE unsigned long Chk_NoAddul(unsigned long lhs,
unsigned long rhs,
Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
unsigned long res;
*ovrflw |= __builtin_uaddl_overflow(lhs, rhs, &res);
return res;
#else
unsigned long res = lhs + rhs;
*ovrflw |= (res < lhs || res < rhs);
return res;
#endif
}
FORCE_INLINE unsigned Chk_NoAddu(unsigned lhs, unsigned rhs, Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
unsigned res;
*ovrflw |= __builtin_uadd_overflow(lhs, rhs, &res);
return res;
#else
unsigned res = lhs + rhs;
*ovrflw |= (res < lhs || res < rhs);
return res;
#endif
}
FORCE_INLINE long long Chk_NoNegll(long long rhs, Boolean *ovrflw)
{
*ovrflw |= (rhs == LLONG_MIN);
return -rhs;
}
FORCE_INLINE long Chk_NoNegl(long rhs, Boolean *ovrflw)
{
*ovrflw |= (rhs == LONG_MIN);
return -rhs;
}
FORCE_INLINE int Chk_NoNeg(int rhs, Boolean *ovrflw)
{
*ovrflw |= (rhs == INT_MIN);
return -rhs;
}
FORCE_INLINE long long Chk_NoSubll(long long lhs,
long long rhs,
Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
long long res;
*ovrflw |= __builtin_ssubll_overflow(lhs, rhs, &res);
return res;
#else
long long res = (unsigned long long) lhs - (unsigned long long) rhs;
*ovrflw |= ((lhs < 0 && rhs >= 0 && res >= 0) ||
(lhs >= 0 && rhs < 0 && (res < 0 || rhs == LLONG_MIN)));
return res;
#endif
}
FORCE_INLINE long Chk_NoSubl(long lhs, long rhs, Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
long res;
*ovrflw |= __builtin_ssubl_overflow(lhs, rhs, &res);
return res;
#elif LONG_MAX == LLONG_MAX
return Chk_NoSubll(lhs, rhs, ovrflw);
#else
long long res = (long long) lhs - (long long) rhs;
*ovrflw |= (res < LONG_MIN || res > LONG_MAX);
return res;
#endif
}
FORCE_INLINE int Chk_NoSub(int lhs, int rhs, Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
int res;
*ovrflw |= __builtin_ssub_overflow(lhs, rhs, &res);
return res;
#elif INT_MAX == LONG_MAX
return Chk_NoSubl(lhs, rhs, &rhs);
#else
long res = (long) lhs - (long) rhs;
*ovrflw |= (res < INT_MIN || res > INT_MAX);
return res;
#endif
}
FORCE_INLINE unsigned long long Chk_NoSubull(unsigned long long lhs,
unsigned long long rhs,
Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
unsigned long long _res;
*ovrflw |= __builtin_usubll_overflow(lhs, rhs, &res);
return res;
#else
*ovrflw |= (lhs < rhs);
return lhs - rhs;
#endif
}
FORCE_INLINE unsigned long Chk_NoSubul(unsigned long lhs,
unsigned long rhs,
Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
unsigned long res;
*ovrflw |= __builtin_usubl_overflow(lhs, rhs, &res);
return res;
#else
*ovrflw |= (lhs < rhs);
return lhs - rhs;
#endif
}
FORCE_INLINE unsigned Chk_NoSubu(unsigned lhs, unsigned rhs, Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
unsigned res;
*ovrflw |= __builtin_usub_overflow(lhs, rhs, &res);
return res;
#else
*ovrflw |= (lhs < rhs);
return lhs - rhs;
#endif
}
FORCE_INLINE long long Chk_NoMulll(long long lhs,
long long rhs,
Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
long long res;
*ovrflw |= __builtin_smulll_overflow(lhs, rhs, &res);
return res;
#else
long long res = (unsigned long long) lhs * (unsigned long long) rhs;
*ovrflw |= ((lhs & (~1LL)) != 0 &&
(res == rhs ? res : (res / lhs) != rhs));
return res;
#endif
}
FORCE_INLINE long Chk_NoMull(long lhs, long rhs, Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
long res;
*ovrflw |= __builtin_smull_overflow(lhs, rhs, &res);
return res;
#elif LONG_MAX == LLONG_MAX
return Chk_NoMulll(lhs, rhs, ovrflw);
#else
long long res = (long long) lhs * (long long) rhs;
*ovrflw |= (res < LONG_MIN || res > LONG_MAX);
return res;
#endif
}
FORCE_INLINE int Chk_NoMul(int lhs, int rhs, Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
int res;
*ovrflw |= __builtin_smul_overflow(lhs, rhs, &res);
return res;
#elif INT_MAX == LONG_MAX
return Chk_NoMull(lhs, rhs, ovrflw);
#else
long res = (long) lhs * (long) rhs;
*ovrflw |= (res < INT_MIN || res > INT_MAX);
return res;
#endif
}
FORCE_INLINE unsigned long long Chk_NoMulull(unsigned long long lhs,
unsigned long long rhs,
Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
unsigned long long res = 0;
*ovrflw |= __builtin_umulll_overflow(lhs, rhs, &res);
return res;
#else
unsigned long long res = lhs * rhs;
*ovrflw |= ((lhs | rhs) >> 32 && lhs && res / lhs != rhs);
return res;
#endif
}
FORCE_INLINE unsigned long Chk_NoMulul(unsigned long lhs,
unsigned long rhs,
Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
unsigned long res;
*ovrflw |= __builtin_umull_overflow(lhs, rhs, &res);
return res;
#elif ULONG_MAX == ULLONG_MAX
return Chk_NoMulull(lhs, rhs, ovrflw);
#else
unsigned long long res = (unsigned long long) lhs *
(unsigned long long) rhs;
*ovrflw |= ((res >> (sizeof(unsigned long)*CHAR_BIT)) != 0);
return res;
#endif
}
FORCE_INLINE unsigned Chk_NoMulu(unsigned lhs,
unsigned rhs,
Boolean *ovrflw)
{
#if defined(__GNUC__) && !defined(DF_C_ONLY_CHKINT)
unsigned res;
*ovrflw |= __builtin_umul_overflow(lhs, rhs, &res);
return res;
#elif UINT_MAX == ULONG_MAX
return Chk_NoMulul(lhs, rhs, ovrflw);
#else
unsigned long res = (unsigned long) lhs * (unsigned long) rhs;
*ovrflw |= ((res >> (sizeof(unsigned)*CHAR_BIT)) != 0);
return res;
#endif
}
#endif

134
lonetix/include/df/cpr/bzip2.h Executable file
View File

@ -0,0 +1,134 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file cpr/bzip2.h
*
* BurrowsWheeler bzip2 compression streaming support library.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_CPR_BZIP2_H_
#define DF_CPR_BZIP2_H_
#include "stm.h"
/// Bzip2 stream handle.
typedef struct Bzip2StmObj *Bzip2StmHn;
/**
* \brief BZip2 Compression options.
*
* \see `Bz2_InitCompress()`
*/
typedef struct {
/**
* \brief Compression level, from 1 to 9 inclusive.
*
* Higher values imply better compression, at the price of speed loss
* and memory consumption.
* Out of range values are silently clamped to allowed range.
*/
int compression;
/// Compressor load factor.
int factor;
/// Compressor buffer size in bytes, 0 to use suggested value.
size_t bufsiz;
/**
* \brief Debugging message verbosity.
*
* Values higher than 0 make the stream log debug messages to standard
* error, debugging level goes from 0 to 4 inclusive.
* Larger values are silently truncated to the maximum allowed.
*/
unsigned verbose;
} Bzip2CprOpts;
/**
* \brief Decompression options.
*
* \see `Bz2_InitDecompress()`
*/
typedef struct {
/// Decompressor buffer size in bytes, 0 to use suggested value.
size_t bufsiz;
/// Conserve memory during decompression, in spite of speed.
Boolean low_mem;
/**
* \brief Debugging message verbosity.
*
* Values higher than 0 make the stream log debug messages to standard
* error, debugging level goes from 0 to 4 inclusive.
* Larger values are implicitly truncated to the maximum allowed.
*/
unsigned verbose;
} Bzip2DecOpts;
/// BZip2 result status, returned by `Bzip2_GetErrStat()`.
typedef int Bzip2Ret; // 0 == OK
/// Implementation of `StmOps` for BZip2 compression/decompression.
extern const StmOps *const Bzip2_StmOps;
/// Non-closing variant of `Bzip2_StmOps`.
extern const StmOps *const Bzip2_NcStmOps;
/// Return last BZip2 operation result.
Bzip2Ret Bzip2_GetErrStat(void);
/// Convert `Bzip2Ret` value to human readable string.
const char *Bzip2_ErrorString(Bzip2Ret ret);
/**
* \brief Open stream for compression.
*
* \param [in,out] streamp Output stream for compressed data
* \param [in] ops Write operations interface for `streamp`, must not be `NULL` and provide `Write()`
* \param [in] opts Compression options, may be `NULL` for defaults
*
* \return The BZip2 compressor handle on success, `NULL` on failure.
*/
Bzip2StmHn Bzip2_OpenCompress(void *streamp, const StmOps *ops, const Bzip2CprOpts *opts);
/**
* \brief Open a stream for decompressing.
*
* \param [in,out] streamp Input stream for BZip2 compressed data
* \param [in] ops Read operations interface for `streamp`, must not be `NULL` and provide `Read()`
* \param [in] opts Decompression options, may be `NULL` for defaults
*
* \return The BZip2 decompressor handle on success, `NULL` on failure.
*/
Bzip2StmHn Bzip2_OpenDecompress(void *streamp, const StmOps *ops, const Bzip2DecOpts *opts);
/**
* \brief Decompress `nbytes` bytes from `hn` to `buf`.
*
* \return Number of actual bytes written to `buf`, 0 on end of stream,
* -1 on error.
*/
Sint64 Bzip2_Read(Bzip2StmHn hn, void *buf, size_t nbytes);
/**
* \brief Compress `nbytes` bytes from `buf` to `hn`.
*
* \return Number of bytes actually written to `hn`, which may be less
* than `nbytes`, -1 on error.
*
* \note Compression should be finalized with `Bzip2_Finish()` once all
* data is written.
*/
Sint64 Bzip2_Write(Bzip2StmHn hn, const void *buf, size_t nbytes);
/**
* \brief Flush Bzip2 encoder.
*
* Should be called before closing a BZip2 encoder.
*
* \param [in,out] hn Stream to be finalized, must not be `NULL`
*
* \return `OK` on success, `NG` on failure.
*/
Judgement Bzip2_Finish(Bzip2StmHn hn);
/// Close a Bzip2 stream.
void Bzip2_Close(Bzip2StmHn hn);
#endif

121
lonetix/include/df/cpr/flate.h Executable file
View File

@ -0,0 +1,121 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file cpr/flate.h
*
* Compressor DEFLATE and inflate stream implementation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* \see [RFC 1950](https://tools.ietf.org/html/rfc1950)
* \see [RFC 1951](https://tools.ietf.org/html/rfc1951)
* \see [RFC 1952](https://tools.ietf.org/html/rfc1952)
*/
#ifndef DF_CPR_FLATE_H_
#define DF_CPR_FLATE_H_
#include "stm.h"
/// DEFLATE or inflate stream handle.
typedef struct ZlibStmObj *ZlibStmHn;
/// Supported DEFLATE data formats.
typedef enum {
ZFMT_RFC1952, ///< gzip compression, see [RFC 1952](https://tools.ietf.org/html/rfc1952)
ZFMT_RFC1951, ///< Original deflate format, see [RFC 1951](https://tools.ietf.org/html/rfc1951)
ZFMT_RFC1950 ///< Zlib format, see [RFC 1950](https://tools.ietf.org/html/rfc1950)
} Zlibfmt;
/// Inflate (decompression) options.
typedef struct {
unsigned win_bits; ///< Compression window size in bits
Zlibfmt format; ///< Input DEFLATE encoding format
size_t bufsiz; ///< Input buffer size in bytes, 0 for default
} InflateOpts;
/// DEFLATE (compression) options.
typedef struct {
int compression; ///< Compression, range `[0-9]` (0 = none, 9 = best)
unsigned win_bits; ///< Compression window size in bits
Zlibfmt format; ///< Output DEFLATE format
size_t bufsiz; ///< Output buffer size in bytes, leave to 0 for default
} DeflateOpts;
/// Zlib result status.
typedef Sint64 ZlibRet; // 0 == OK
/**
* \brief Implementation of the `StmOps` interface for DEFLATE streams.
*
* Passing these interfaces to any API working with streams allows it to
* operate on DEFLATE streams.
* `Zlib_StmOps` implements the `Close()` method, while the non-closing
* `Zlib_NcStmOps` leaves `Close()` to `NULL`, effectively preventing any
* attempt to close such stream. Use this variant when this behavior is
* desirable (e.g. streams similar to `stdout` or `stdin`).
*
* @{
* \var Zlib_StmOps
* \var Zlib_NcStmOps
* @}
*/
extern const StmOps *const Zlib_StmOps;
extern const StmOps *const Zlib_NcStmOps;
/// Return last Zlib operation's return status.
ZlibRet Zlib_GetErrStat(void);
/// Convert `ZlibRet` result to human readable string.
const char *Zlib_ErrorString(ZlibRet res);
/**
* \brief Start Zlib decompression over a stream.
*
* \param [in,out] streamp Compressed input source stream
* \param [in] ops Read operations over `streamp`, must not be `NULL` and provide `Read()`
* \param [in] opts Decompression options, `NULL` for defaults
*
* \return Opened Zlib handle on success, `NULL` on failure.
*/
ZlibStmHn Zlib_InflateOpen(void *streamp, const StmOps *ops, const InflateOpts *opts);
/**
* \brief Start Zlib compression over a stream.
*
* \param [in,out] streamp Destination for compressed output
* \param [in] ops Write operations over `ßtreamp`, must not be `NULL` and provide `Write()`
* \param [in] opts Compression options, `NULL` for defaults.
*
* \return Opened Zlib handle on success, `NULL` on failure.
*/
ZlibStmHn Zlib_DeflateOpen(void *streamp, const StmOps *ops, const DeflateOpts *opts);
/**
* \brief Decompress `nbytes` bytes from `hn` into `buf`.
*
* \return Number of bytes actually written to `buf`, at most `nbytes`,
* 0 on end of stream, -1 on error.
*/
Sint64 Zlib_Read(ZlibStmHn hn, void *buf, size_t nbytes);
/**
* \brief Compresses `nbytes` bytes from `buf` to `hn`.
*
* \return Count of bytes actually compressed to `hn`, at most `nbytes`,
* -1 on error.
*
* \note Compression should be finalized with `Zlib_Finish()` once all
* data is written.
*/
Sint64 Zlib_Write(ZlibStmHn hn, const void *buf, size_t nbytes);
/**
* \brief Finalize DEFLATE compression.
*
* \return `OK` on success, `NG` otherwise.
*/
Judgement Zlib_Finish(ZlibStmHn hn);
/// Close Zlib stream handle.
void Zlib_Close(ZlibStmHn hn);
#endif

118
lonetix/include/df/cpr/xz.h Executable file
View File

@ -0,0 +1,118 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file cpr/xz.h
*
* Compressors, LZMA/LZMA2 encoding and decoding implementation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_CPR_XZ_H_
#define DF_CPR_XZ_H_
#include "stm.h"
/// LZMA/LZMA2 stream handle.
typedef struct XzStmObj *XzStmHn;
/// LZMA/LZMA2 data integrity checksum algorithms.
typedef enum {
XZCHK_NONE = 0, ///< Do not include any data checksum
XZCHK_CRC32 = 1, ///< Cyclic Redundancy Check, 32-bit
XZCHK_CRC64 = 4, ///< Cyclic Redundancy Check, 64-bit
XZCHK_SHA256 = 10 ///< Secure Hash Algorithm, 256 bit
} Xzchk;
/// LZMA/LZMA2 compressor flags.
typedef struct {
unsigned compress; ///< Compression level, range [0-9]
Boolean extreme; ///< Severely sacrifice speed for compression
Xzchk chk; ///< Checksum algorithm to use
size_t bufsiz; ///< Output buffer size in bytes
} XzEncOpts;
/// LZMA/LZMA2 decompression flags.
typedef struct {
Uint64 memlimit; ///< Decoder memory usage limit
Boolean no_concat; ///< Do not support concatenated xz streams
Boolean no_chk; ///< Disregard data checksum during decoding
size_t bufsiz; ///< Input buffer size in bytes
} XzDecOpts;
/// LZMA operation status result.
typedef int XzRet; // 0 == OK
/**
* \brief Implementation of the `StmOps` interface for LZMA streams.
*
* Allows any API working with streams to function with LZMA streams.
* `Xz_StmOps` implements the `Close()` method, while the non-closing
* `Xz_NcStmOps` leaves `Close()` to `NULL`, preventing any
* attempt to close the stream. Use this variant when such behavior is
* desirable (e.g. streams similar to `stdout` or `stdin`).
*
* @{
* \var Xz_StmOps
* \var Xz_NcStmOps
* @}
*/
extern const StmOps *const Xz_StmOps;
extern const StmOps *const Xz_NcStmOps;
/// Retrieve last operation's result status.
XzRet Xz_GetErrStat(void);
/// Convert `XzRet` to human readable string.
const char *Xz_ErrorString(XzRet res);
/**
* \brief Open stream for compression.
*
* \param [in,out] streamp Output stream for compressed LZMA data
* \param [in] ops Write operations interface for `streamp`, must not be `NULL` and provide `Write()`
* \param [in] opts Compression options, may be `NULL` for defaults
*
* \return The LZMA compressor handle on success, `NULL` on failure.
*/
XzStmHn Xz_OpenCompress(void *streamp, const StmOps *ops, const XzEncOpts *opts);
/**
* \brief Open an LZMA stream for decompressing.
*
* \param [in,out] streamp Input stream for LZMA compressed data
* \param [in] ops Read operations interface for `streamp`, must not be `NULL` and provide `Read()`
* \param [in] opts Decompression options, may be `NULL` for defaults
*
* \return The LZMA decompressor handle on success, `NULL` on failure.
*/
XzStmHn Xz_OpenDecompress(void *streamp, const StmOps *ops, const XzDecOpts *opts);
/**
* \brief Decompress `nbytes` bytes from `hn` into `buf`.
*
* \return Number of bytes actually written to `buf`, at most `nbytes`,
* 0 on end of stream, -1 on error.
*/
Sint64 Xz_Read(XzStmHn hn, void *buf, size_t nbytes);
/**
* \brief Compresses `nbytes` bytes from `buf` to `hn`.
*
* \return Count of bytes actually compressed to `hn`, at most `nbytes`,
* -1 on error.
*
* \note Compression should be finalized with `Xz_Finish()` once all
* data is written.
*/
Sint64 Xz_Write(XzStmHn hn, const void *buf, size_t nbytes);
/**
* \brief Finalize LZMA compression.
*
* \return `OK` on success, `NG` otherwise.
*/
Judgement Xz_Finish(XzStmHn hn);
/// Close LZMA stream.
void Xz_Close(XzStmHn hn);
#endif

475
lonetix/include/df/lexer.h Normal file
View File

@ -0,0 +1,475 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file lexer.h
*
* C-compliant non-allocating UTF-8 text lexer.
*
* \author Lorenzo Cogotti
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
*/
#ifndef DF_LEXER_H_
#define DF_LEXER_H_
#include "utf/utfdef.h"
/// Maximum allowed token length inside text parsed by `Lex`.
#define MAXTOKLEN 256
/// String token type
#define TT_STRING U16_C(1)
/// Literal token type
#define TT_LITERAL U16_C(2)
/// Numeric token type
#define TT_NUMBER U16_C(3)
/// Token type for names or identifiers
#define TT_NAME U16_C(4)
/// Punctuation token type
#define TT_PUNCT U16_C(5)
/**
* Token subtype flags for `TT_NUMBER`
*
* @{
*/
#define TT_INT BIT(0) ///< integer
#define TT_DEC BIT(1) ///< decimal number
#define TT_HEX BIT(2) ///< hexadecimal number
#define TT_OCT BIT(3) ///< octal number
#define TT_BIN BIT(4) ///< binary number
#define TT_LONG BIT(5) ///< long int
#define TT_LLONG BIT(6) ///< long long int
#define TT_UNSIGNED BIT(7) ///< unsigned int
#define TT_FLOAT BIT(8) ///< floating point number
#define TT_SINGLE_PREC BIT(9) ///< float
#define TT_DOUBLE_PREC BIT(10) ///< double
#define TT_EXT_PREC BIT(11) ///< long double
#define TT_INF BIT(12) ///< infinite 1.#INF
#define TT_INDEF BIT(13) ///< indefinite 1.#IND
#define TT_NAN BIT(14) ///< NaN
#define TT_IPADDR BIT(15) ///< ip address (address may still be ill-formed, e.g. `102948.22.999.1`)
#define TT_IPV4 BIT(16) ///< ipv4 address format
#define TT_IPV6 BIT(17) ///< ipv6 address format
#define TT_IPV6LIT BIT(18) ///< ipv6 address is expressed as literal (e.g. `[2001:db8:a::123]`)
#define TT_IPV6ZONE BIT(19) ///< ipv6 address contains a zone index/string (e.g. `fe80::1ff:fe23:4567:890a%3`)
#define TT_IPPORT BIT(20) ///< ip address includes a port
/** @} */
/**
* Token flags
*
* @{
*/
/// Indicates `Tok` originally exceeded `MAXTOKLEN` and was consequently truncated.
#define TT_TRUNC BIT(15)
/** @} */
/// Lexer punctuation token descriptor (text -> token `subtype`).
typedef struct Punctuation Punctuation;
struct Punctuation {
const char *p; ///< NULL for last element in punctuation list.
Uint32 id; ///< Puntuation identifier (returned in `Tok->subtype`)
};
// punctuation ids
#define P_RSHIFT_ASSIGN 1
#define P_LSHIFT_ASSIGN 2
#define P_PARMS 3
#define P_PRECOMPMERGE 4
#define P_LOGIC_AND 5
#define P_LOGIC_OR 6
#define P_LOGIC_GEQ 7
#define P_LOGIC_LEQ 8
#define P_LOGIC_EQ 9
#define P_LOGIC_UNEQ 10
#define P_MUL_ASSIGN 11
#define P_DIV_ASSIGN 12
#define P_MOD_ASSIGN 13
#define P_ADD_ASSIGN 14
#define P_SUB_ASSIGN 15
#define P_INC 16
#define P_DEC 17
#define P_BIN_AND_ASSIGN 18
#define P_BIN_OR_ASSIGN 19
#define P_BIN_XOR_ASSIGN 20
#define P_RSHIFT 21
#define P_LSHIFT 22
#define P_POINTERREF 23
#define P_MUL 24
#define P_DIV 25
#define P_MOD 26
#define P_ADD 27
#define P_SUB 28
#define P_ASSIGN 29
#define P_BIN_AND 30
#define P_BIN_OR 31
#define P_BIN_XOR 32
#define P_BIN_NOT 33
#define P_LOGIC_NOT 34
#define P_LOGIC_GREATER 35
#define P_LOGIC_LESS 36
#define P_REF 37
#define P_COMMA 38
#define P_SEMICOLON 39
#define P_COLON 40
#define P_QUESTIONMARK 41
#define P_PARENOPEN 42
#define P_PARENCLOSE 43
#define P_BRACEOPEN 44
#define P_BRACECLOSE 45
#define P_SQBRACKETOPEN 46
#define P_SQBRACKETCLOSE 47
#define P_BACKSLASH 48
#define P_PRECOMP 49
#define P_DOLLAR 50
/**
* \brief Token returned by `Lex`.
*
* Contains token text and information.
*/
typedef struct Tok Tok;
struct Tok {
Uint16 type;
Uint16 flags;
Uint32 subtype;
unsigned linesCrossed;
unsigned spacesBeforeToken;
unsigned line;
long long intvalue;
double floatvalue;
Tok *nextToken;
char text[MAXTOKLEN]; // NOTE: last element to allow partial allocation
};
/// Disregard lexer errors
#define L_NOERR BIT(0)
/// Disregard lexer warnings
#define L_NOWARN BIT(1)
/// Disregard both errors and warnings
#define L_QUIET (L_NOERR | L_NOWARN)
/// Use console colors when reporting errors and warnings
#define L_COLORED BIT(2)
/// Parse all tokens as strings, instead of breaking them using full-fledged C rules
#define L_STRONLY BIT(3)
/// Allow file paths within tokens
#define L_ALLOWPATHS BIT(4)
/// Do not allow escapes within strings
#define L_NOSTRESC BIT(5)
/// Do not concatenate consecutive strings
#define L_NOSTRCAT BIT(6)
/// Concatenate strings separated by a backslash+newline
#define L_ALLOWBACKSLASHSTRCAT BIT(7)
/// Allow multichar literals
#define L_ALLOWMULTICHARLIT BIT(8)
/// Accepts IP addresses (parsed as `TT_NUMBER`)
#define L_ALLOWIPADDR BIT(9)
/// IP addresses with port numbers, IPv6 literals or zone ids won't be accepted,
/// only meaningful if used with `L_ALLOWIPADDR`.
#define L_PLAINIPADDRONLY BIT(10)
/// Allow special floating point exception tokens (0.#INF, 0.#IND).
#define L_ALLOWFLOATEXC BIT(10)
/// Allow truncating tokens exceeding `MAXTOKLEN`.
#define L_ALLOWTRUNC BIT(11)
/// Do not search base `#include` paths (used by PC library).
#define L_NOBASEINCLUDES BIT(12)
/// Special callback, invokes immediate program termination after reporting a lexer message
#define LEX_QUIT ((void (*)(Lex *, const char *, void *)) -1)
/// Special callback, makes the lexer ignore the the warning or error
/// (same behavior as `L_NOERR` and `L_NOWARN`, but as an explicit callback).
#define LEX_IGN ((void (*)(Lex *, const char *, void *)) 0)
/// Special callback, makes the lexer print an error or warning message to `stderr`,
/// doesn't terminate execution.
#define LEX_WARN ((void (*)(Lex *, const char *, void *)) 1)
/**
* \brief A lexer, breaks text into single tokens, keeping track of the current position.
*
* \note This struct should be considered opaque.
*/
typedef struct Lex Lex;
struct Lex {
char *pos, *lim;
unsigned line;
Uint16 flags;
Boolean8 hasError;
Boolean8 hasBufferedToken;
Rune nr;
const Punctuation *puncts;
void *obj;
void (*Error)(Lex *, const char *, void *);
void (*Warn)(Lex *, const char *, void *);
Lex *nextLexer;
Tok buf;
char name[MAXTOKLEN];
};
/// Register callbacks for lexer warning and error triggers.
FORCE_INLINE void SetLexerErrorFunc(Lex *p,
void (*errf)(Lex *, const char *, void *),
void (*warnf)(Lex *, const char *, void *),
void *obj)
{
p->Error = errf;
p->Warn = warnf;
p->obj = obj;
}
/**
* \brief Set parsing session name and initial line number.
*
* \param [out] p A lexer, must not be `NULL`
* \param [in] name Name for this parsing session
* \param [in] line Initial line number, 0 is implicitly changed to 1
*/
void BeginLexerSession(Lex *p, const char *name, unsigned line);
/**
* \brief Setup lexer to parse text, sized.
*
* \param [out] p A lexer, must not be `NULL`
* \param [in] text Text to be parsed, must have at least `n` chars
* \param [in] n Number of chars in `text`
*/
void SetLexerTextn(Lex *p, const char *text, size_t n);
/**
* \brief Setup lexer to parse text.
*
* \param [out] p A lexer, must not be `NULL`
* \param [in] text `NUL` terminated text to be parsed
*/
FORCE_INLINE void SetLexerText(Lex *p, const char *text)
{
EXTERNC size_t strlen(const char *);
SetLexerTextn(p, text, strlen(text));
}
/**
* \brief Change lexer flags.
*
* \param [out] p A lexer, must not be `NULL`
* \param [in] flags New flags for the lexer
*/
FORCE_INLINE void SetLexerFlags(Lex *p, unsigned flags)
{
p->flags = flags;
}
/// Retrieve current lexer flags.
FORCE_INLINE unsigned GetLexerFlags(Lex *p)
{
return p->flags;
}
/// Trigger an error over a lexer.
CHECK_PRINTF(2, 3) void LexerError(Lex *p, const char *fmt, ...);
/**
* Trigger a warning over a lexer.
*/
CHECK_PRINTF(2, 3) void LexerWarning(Lex *p, const char *fmt, ...);
/// Test whether a lexer reached the end.
FORCE_INLINE Boolean IsLexerEndOfFile(Lex *p)
{
return (p->pos >= p->lim || *p->pos == '\0') && !p->hasBufferedToken;
}
/// Test whether a lexer encountered an error.
FORCE_INLINE Boolean HasLexerError(Lex *p)
{
return p->hasError;
}
/**
* \brief Read and return next token.
*
* \param [in,out] p A lexer, must not be `NULL`
* \param [out] dest Storage for the returned token, must not be `NULL`
*
* \return If a new token has been read, then `tok->text` is returned,
* `NULL` is returned if a parsing error has been encountered,
* or no more tokens are available.
*/
char *Lex_ReadToken(Lex *p, Tok *dest);
/**
* \brief Read and return next token in the same line.
*
* This is a variant of `Lex_ReadToken()` useful to implement
* a C Preprocessor, it avoids parsing spanning more than one line.
* `\` followed by a newline is recognized and treated as a regular
* space.
*/
char *Lex_ReadTokenOnLine(Lex *p, Tok *dest);
/**
* \brief Expects an integral token, reading and returning its value.
*
* \param [in,out] p A lexer, must not be `NULL`
* \param [in] optionalSign Allow an optional `+` or `-` sign before the
* token, if set to `FALSE` only unsigned
* integers are allowed.
*
* \return The token value, 0 on error, use `HasLexerError()` to distinguish
* between actual 0 and error value.
*/
long long Lex_ParseInt(Lex *p, Boolean optionalSign);
/**
* \brief Expects a boolean token, reading and returning its value.
*
* \param [in,out] p A lexer, must not be `NULL`
* \param [in] allowNumeric Convert numeric values to booleans, 0 for
* `FALSE`, any other numeric value for `TRUE`
*
* \return The boolean value, `FALSE` on error or end of file,
* use `HasLexerError()` or `IsLexerEndOfFile()` to distinguish
* between actual `FALSE` and error value.
*/
Boolean Lex_ParseBool(Lex *p, Boolean allowNumeric);
/**
* \brief Expects a floating point token, reading and returning its value.
*
* \param [in,out] p A lexer, must not be `NULL`
* \param [in] optionalSign Allow an optional `+` or `-` sign before the
* token, if set to `FALSE` only non-negative
* values are allowed.
*
* \return The float value, 0 on error or end of file,
* use `HasLexerError()` or `IsLexerEndOfFile()` to distinguish
* between actual 0 and error value.
*/
double Lex_ParseFloat(Lex *p, Boolean optionalSign);
/**
* \brief Read a one dimensional matrix (vector of length `n`) from `p` into `dest`.
*
* Matrix format is:
* ```
* (x y z w ...)
* ```
*
* \return `TRUE` on success, `FALSE` on error.
*/
Boolean Lex_ParseMatrix1(Lex *p, float *dest, size_t n);
/**
* \brief `Lex_ParseMatrix1()` variant for two dimensional matrixes.
*
* Matrix format is:
* ```
* ((x0 y0 z0 w0 ...) (x1 y1 z1 w1 ...) ...)
* ```
*/
Boolean Lex_ParseMatrix2(Lex *p, float *dest, size_t n, size_t m);
/// `Lex_ParseMatrix1()` variant for tridimensional matrixes.
Boolean Lex_ParseMatrix3(Lex *p, float *dest, size_t n, size_t m, size_t u);
/// Discard any buffered token and any in text token up to a new line.
void Lex_SkipLine(Lex *p);
/**
* \brief Skip every token until `tok` is encountered.
*
* \param [in,out] p A lexer, must not be `NULL`
* \param [in] tok Token to look for, must not be `NULL`
*
* \return `tok` on success, `NULL` on error or end of file.
*/
char *Lex_SkipUntil(Lex *p, const char *tok);
/**
* \brief Expect and skip section enclosed within braces.
*
* Braced sections are enclosed by punctuation tokens of id `P_BRACEOPEN` and
* `P_BRACECLOSE`.
*
* \param [in,out] p A lexer, must not be `NULL`
* \param [in] parseFirstBrace Whether the function should expect the next
* token to be the first brace of the section
* (`TRUE`) or it should assume the first brace
* has already been parsed (`FALSE`).
*
* \return `TRUE` if section was skipped successfully, `FALSE` on error
* (either unbalanced braces or unexpected token).
*/
Boolean Lex_SkipBracedSection(Lex *p, Boolean parseFirstBrace);
/**
* \brief Expect a token, matching and returning it, raises error on mismatch.
*
* \param [in,out] p A lexer, must not be `NULL`
* \param [in] tok Token to be expected, must not be `NULL`
*
* \return On success `tok` is returned, on error `NULL`.
*/
char *Lex_MatchToken(Lex *p, const char *tok);
/**
* \brief Expect any token, raises an error if none is found.
*
* \param [in,out] p A lexer, must not be `NULL`
* \param [out] dest Storage for returned token, must not be `NULL`
*
* \return On success `tok->text` is returned, on error `NULL`.
*/
char *Lex_MatchAnyToken(Lex *p, Tok *dest);
/**
* \brief Expect a token of a specific `type` and `subtype`, raise error on mismatch.
*
* \param [in,out] p A lexer, must not be `NULL`
* \param [out] dest Storage for returned token, must not be `NULL`
* \param [in] type Token type to be expected
* \param [in] subtype Subtype mask for the expected token
*
* \return On success `tok->text`, `NULL` otherwise.
*/
char *Lex_MatchTokenType(Lex *p, Tok *dest, int type, unsigned subtype);
/**
* Check whether next token matches `tok`.
*
* If token matches it is read from `p` and returned, as in `Lex_ReadToken()`,
* otherwise `p` is left unaltered (except for parsing errors).
*/
char *Lex_CheckToken(Lex *p, const char *tok);
/// Similar to `Lex_CheckToken()`, but matches by token `type` and `subtype`.
char *Lex_CheckTokenType(Lex *, Tok *dest, int type, unsigned subtype);
/**
* \brief Peek next token from `p` and test whether it matches with `tok`.
*
* In no case next token is consumed from `p`, lexer is left unaltered
* (except for parsing errors).
*/
char *Lex_PeekToken(Lex *p, const char *tok);
/// Similar to `Lex_PeekToken()`, but matches by token `type` and `subtype`.
char *Lex_PeekTokenType(Lex *p, Tok *dest, int type, unsigned subtype);
/**
* \brief Place a token back into the lexer.
*
* Only one token may be placed back into the lexer at a time,
* it will be returned back on the next call to `Lex_ReadToken()`.
*/
void Lex_UngetToken(Lex *p, const Tok *tok);
#endif

85
lonetix/include/df/mem.h Normal file
View File

@ -0,0 +1,85 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file mem.h
*
* Common allocator interface.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_MEM_H_
#define DF_MEM_H_
#include "xpt.h"
/**
* \brief Memory allocator interface.
*
* This is a common interface `struct` used by functions or types
* that accomodate for custom memory allocation policies.
*
* \note The behavior described here for the `Alloc` and `Free` functions is
* a sensible contract that should be respected by most allocators.
* Still, special purpose allocators may tweak it to fit very
* specific situations, e.g. an allocator optimized for a specific use may
* ignore all calls to `Free`, or may interpret a `Free` of chunks never
* returned by `Alloc` as hints to grow its available memory pool,
* a sensitive allocator may choose to only return zeroed memory on
* new chunks and zero them out as soon as they're `Free`d.
* Such allocators should be restricted to special circumstances, while
* the behavior described here should provide the general rule.
*/
typedef struct {
/**
* \brief Allocate or reallocate a memory chunk.
*
* The `Alloc` function takes an optional `allocp`, which may represent
* the allocator state. Some allocators don't require any state or
* provide a global one, in which case `allocp` may be `NULL`.
* The returned chunk is required to be at least `size` bytes large.
* The `oldp` argument is `NULL` for new allocations, but may also be
* a pointer to an existing chunk previously returned by the allocator,
* which is the case for shrink or grow requests. The allocator may
* enlarge or shrink the chunk referenced by `oldp` to reduce
* fragmentation.
* On shrink and grow requests the data up to the minimum value between
* `size` and the old chunk size is preserved
*
* \param [in,out] allocp Allocator state pointer, `NULL` if allocator has no state
* \param [in] size Requested memory chunk size, in bytes
* \param [in,out] oldp Old memory chunk pointer, `NULL` if a new allocation is
* requested
*
* \return One of the following:
* * a pointer to a possibly uninitialized memory chunk on successful new
* allocation,
* * a pointer to the resized memory block, which may reside
* in a different location rather than `oldp`, in the event of
* a successful shrink or grow request,
* * `NULL` on allocation failure.
*/
void *(*Alloc)(void *allocp, size_t size, void *oldp);
/**
* \brief Free a memory chunk.
*
* The `Free` function takes an optional `allocp`, which may
* represent the allocator state, with the same semantics as `Alloc`,
* and a pointer previously returned by `Alloc`, and marks the chunk
* referenced by it as free for future allocations.
* Any allocator should silently ignore requests to free the `NULL`
* pointer.
*
* \param [in,out] allocp Allocator state pointer, `NULL` if allocator has no
* state
* \param [in] p Pointer to a memory chunk previously returned by `Alloc`,
* if `NULL` calling this function is a NOP.
*/
void (*Free)(void *allocp, void *p);
} MemOps;
/// Plain `MemOps` using regular `realloc()` and `free()`, use `NULL` for `allocp`.
extern const MemOps *const Mem_StdOps;
#endif

View File

@ -0,0 +1,154 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file mem_file.h
*
* Manage raw memory byte buffers as an I/O stream file-like resource.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* Memory file resources are regular raw-bytes buffer that may be read,
* written and seeked like regular files, implementing the
* `StmOps` interface. Memory buffer may be managed into a variety of ways
* giving the API some flexibility, the policy is determined by a set of flags,
* specified upon initialization (and possibly changed on the fly).
* When writing to a memory file the buffer will tipically be
* `realloc()`ated as needed, the `granularity` field provides a hint
* to reduce excessive reallocations (buffer shall be `realloc()`ated to
* multiples of such value). Buffer reallocation may be opted out altoghether
* by toggling the `MEM_FILE_NOGROWBIT` flag, which turns any attempt
* of writing more bytes than currently available into a short-write.
* Memory buffers may also be initialized in read-only or write-only mode,
* enabling the API to reliably manage `const` qualified buffers or
* write-only memory zones.
*/
#ifndef DF_MEM_FILE_H_
#define DF_MEM_FILE_H_
#include "stm.h"
/// Default `MemFile` reallocation granularity.
#define MEM_FILE_GRAN (16u * 1024)
/// Allow write operations.
#define MEM_FILE_WRBIT BIT(0)
/// Allow read operations.
#define MEM_FILE_RDBIT BIT(1)
/**
* \brief `MemFile` whose `OWN` flag is set own the memory buffer,
* `free()`ing it upon close and `realloc()`ating it as necessary.
*/
#define MEM_FILE_OWNBIT BIT(2)
/**
* \brief `MemFile` with `NOGROW` flag won't reallocate
* their buffer, consequently short-writes are not treated as errors.
*/
#define MEM_FILE_NOGROWBIT BIT(3)
/**
* \brief A file-like memory buffer.
*
* Provides functionality to operate on a memory chunk in a stream-like fashion,
*/
typedef struct {
char *buf; ///< memory buffer `MemFile` operates on
size_t pos; ///< current position inside `buf`
size_t nbytes; ///< `buf` length, in bytes
size_t cap; ///< `buf` actual capacity, in bytes
size_t gran; ///< reallocation granularity in bytes, must be a power of 2
unsigned flags; ///< operating mode flags
} MemFile;
/// Stream operations on `MemFile`, including `Close()`.
extern const StmOps *const Stm_MemFileOps;
/// Non `Close()`-ing stream operations on `MemFile`.
extern const StmOps *const Stm_NcMemFileOps;
/**
* \brief Perform basic initialization of a `MemFile`, with an empty
* initial buffer.
*
* The resulting memory file resource shall be initialized according to
* function arguments and its buffer shall be `NULL`.
*
* \param [out] stm Pointer to a memory file, must not be `NULL`
* \param [in] gran Reallocation granularity in bytes, must be a power of 2
* \param [in] flags Operating mode flags for the newly initialized memory file
*/
void Stm_InitMemFile(MemFile *stm, size_t gran, unsigned flags);
/**
* \brief Initialize `MemFile` from an existing buffer.
*
* \param [out] stm Pointer to a memory file, must not be `NULL`
* \param [in] buf Read-only buffer to be used, must hold at least `nbytes` bytes
* \param [in] nbytes `buf` size in bytes
* \param [in] gran Memory file reallocation granularity, in bytes, must be a power of 2
* \param [in] flags Memory file resource operating mode flags
*/
void Stm_MemFileFromBuf(MemFile *stm, void *buf, size_t nbytes, size_t gran, unsigned flags);
/**
* \brief Initialize a `MemFile` for read over an existing memory buffer.
*
* Resulting memory file resource is implicitly initialized as read-only,
* its buffer won't grow, won't be reallocated, nor it shall be `free()`-d upon
* close.
*
* \param [out] stm Pointer to a memory file, must not be `NULL`
* \param [in] buf Read-only buffer to be used, must hold at least `nbytes` bytes
* \param [in] nbytes `buf` size in bytes
*
* \note No check is made to ensure the provided buffer is NUL-terminated,
* the resulting `MemFile` is initialized with the provided buffer
* as-is. If such characteristic is important, it is the caller's
* responsibility to ensure that.
*/
void Stm_RoMemFileFromBuf(MemFile *, const void *, size_t);
/**
* \brief Take ownership of the buffer managed by a `MemFile`.
*
* \return A pointer to the managed buffer, whose ownership is transferred
* to the caller, thus the caller shall be responsible for its
* deallocation (if necessary).
*
* \note It is not always necessary to call `free()` on the returned buffer,
* since memory files may operate even on static or stack-allocated
* buffers (see `Stm_MemFileFromBuf()` for example).
*/
FORCE_INLINE char *Stm_TakeMemFileBuf(MemFile *stm)
{
char *buf = stm->buf;
stm->flags &= ~MEM_FILE_OWNBIT;
stm->buf = NULL;
stm->nbytes = stm->cap = 0;
return buf;
}
/**
* \brief Read `nbytes` bytes from `stm` into `buf`.
*
* \return Number of bytes actually read from `stm`, -1 on error.
*/
Sint64 Stm_MemFileRead(MemFile *stm, void *buf, size_t nbytes);
/**
* \brief Write `nbytes` bytes from `buf` into `stm`.
*
* \return Number of bytes actually written to `stm`, -1 on error.
*/
Sint64 Stm_MemFileWrite(MemFile *stm, const void *buf, size_t nbytes);
/**
* \brief Set `stm` file pointer, offsetting it of `pos` bytes from `whence`
*
* \return New file cursor position, -1 on error.
*/
Sint64 Stm_MemFileSeek(MemFile *stm, Sint64 pos, SeekMode whence);
/// Close `stm`, `free()`-ing buffer if necessary.
void Stm_MemFileClose(MemFile *stm);
#endif

191
lonetix/include/df/numlib.h Normal file
View File

@ -0,0 +1,191 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file numlib.h
*
* Fast locale independent conversion from numerics (integer or floating point
* types) to ASCII and back.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_NUMLIB_H_
#define DF_NUMLIB_H_
#include "xpt.h"
/**
* \brief Integral compile-time costant representing an upper-bound estimate
* of the number of digit-characters necessary to hold the **decimal**
* representation of `typ`.
*
* Can be used as such:
* ```c
* char buf[1 + DIGS(int) + 1]; // sign + digits + '\0'
*
* Itoa(INT_MAX, buf);
* ```
*
* Approximation is derived from the following formula, where `MAX_INT` is
* the maximum integral value representable by `typ`:
* ```
* N = ceil(log10(MAX_INT)) ->
* N = floor(log10(MAX_INT)) + 1 ->
* (as base-256 logarithm)
* N = floor( log256(MAX_INT) / log256(10) ) + 1 ->
*
* N <= floor( sizeof(typ) * 2.40824 ) + 1 ->
*
* N ~= 241 * sizeof(typ) / 100 + 1
* ```
*
* \param typ An **integer** type or expression
*
* \author [John Bollinger](https://stackoverflow.com/a/43789115)
*/
#define DIGS(typ) ((241 * sizeof(typ)) / 100 + 1)
// #define DIGS(typ) (sizeof(typ) * CHAR_BIT) gross upper-bound approx
/**
* \brief Integral compile-time constant representing an upper-bound estimate
* of the number of digit-characters necessary to hold the **hexadecimal**
* representation of `typ`.
*
* \param typ An **integer** type or expression
*/
#define XDIGS(typ) (sizeof(typ) * 2)
/**
* \brief Maximum number of characters returned by `Ftoa()`,
* **excluding terminating `\0` char**.
*
* Range of double (IEEE-754 `binary64`): `[1.7E-308 ... 1.7E308]`
* - 1 char for sign
* - 309 digits for integer part
* - 1 char for mantissa dot
* - 37 chars for mantissa
*/
#define DOUBLE_STRLEN (309 + 39)
STATIC_ASSERT(sizeof(double) <= 8, "DOUBLE_STRLEN might be inaccurate on this platform");
// should actually make sure we also have IEEE-754 floats...
/**
* \brief Unsigned integer to ASCII conversion.
*
* Destination buffer is assumed to be large enough to hold the
* result. A storage of `DIGS(x) + 1` chars is guaranteed
* to be sufficient. Resulting string is always `NUL` terminated.
*
* \param [in] x Value to be converted
* \param [out] dest Destination string, must not be `\0`
*
* \return Pointer to the trailing `\0` char inside `dest`, useful
* for further string concatenation.
*/
char *Utoa(unsigned long long x, char *dest);
/**
* \brief Signed integer to ASCII conversion.
*
* Destination buffer is assumed to be large enough to hold the result.
* A storage of `1 + DIGS(x) + 1` chars, accounting for the sign
* character, is guaranteed to be sufficient.
*
* \param [in] x Value to be converted
* \param [out] dest Destination string, must not be `NULL`
*
* \return Pointer to the trailing `\0` char in `dest`, useful for further
* string concatenation.
*/
char *Itoa(long long x, char *dest);
/**
* \brief Unsigned integer to hexadecimal lowercase ASCII string.
*
* Destination buffer is assumed to be large enough to hold the result.
* A storage of `XDIGS(x) + 1` chars is guaranteed to be
* sufficient. Resulting string is always `\0` terminated.
*
* \param [in] x Value to be converted
* \param [out] dest Destination string, must not be `NULL`
*
* \return Pointer to the trailing `\0` char in `dest`, useful for further
* string concatenation.
*
* \note No `0x` prefix is prepended to resulting string.
*/
char *Xtoa(unsigned long long x, char *dest);
/**
* \brief Floating point number to scientific notation string.
*
* Destination string is assumed to be large enough to store the conversion
* result, a buffer of size `DOUBLE_STRLEN + 1` is guaranteed to be large
* enough for it. Result is always `\0` terminated.
*
* \param [in] x Floating point number to be converted
* \param [out] dest Destination for result string, must not be `NULL`
*
* \return Pointer to the trailing `\0` char inside result, useful for
* further string concatenation.
*/
char *Ftoa(double x, char *dest);
/// Numeric conversion outcomes.
typedef enum {
NCVENOERR = 0, ///< Conversion successful
NCVEBADBASE, ///< The specified numeric base is out of range
NCVENOTHING, ///< No legal numeric data in input string
NCVEOVERFLOW, ///< Numeric input too large for target integer type
NCVEUNDERFLOW ///< Numeric input too small for target integer type
} NumConvRet;
/**
* \brief ASCII to integer conversion.
*
* If `base` is 0 then the actual numeric base is guessed from input string
* prefix according to an extended C convention:
*
* Numeric prefix | Base
* ---------------|---------------------
* __0__ | octal, base 8
* __0x__ | hexadecimal, base 16
* __0b__ | binary, base 2
* __otherwise__ | decimal, base 10
*
* \param [in] s Input string to be converted, must not be `NULL`
* \param [out] endp Storage where end pointer is returned, may be `NULL`
* \param [in] base Integer conversion base, a value between 0 and 36 inclusive
* \param [out] outcome Storage where conversion outcome is returned, may be `NULL`
*
* \return Conversion result, 0 on error (use `outcome` to detect error).
*
* @{
* \fn unsigned long long Atoull(const char *, char **, unsigned, NumConvRet *)
* \fn unsigned long Atoul(const char *, char **, unsigned, NumConvRet *)
* \fn unsigned Atou(const char *, char **, unsigned, NumConvRet *)
* \fn long long Atoll(const char *, char **, unsigned, NumConvRet *)
* \fn long Atol(const char *, char **, unsigned, NumConvRet *)
* \fn int Atoi(const char *, char **, unsigned, NumConvRet *)
* @}
*/
unsigned long long Atoull(const char *s, char **endp, unsigned base, NumConvRet *outcome);
unsigned long Atoul(const char *s, char **endp, unsigned base, NumConvRet *outcome);
unsigned Atou(const char *s, char **endp, unsigned base, NumConvRet *outcome);
long long Atoll(const char *s, char **endp, unsigned base, NumConvRet *outcome);
long Atol(const char *s, char **endp, unsigned base, NumConvRet *outcome);
int Atoi(const char *s, char **endp, unsigned base, NumConvRet *outcome);
/**
* \brief ASCII to floating point conversion.
*
* \param [in] s Input string to be converted, must not be `NULL`
* \param [out] endp Storage where end pointer is returned, must not be `NULL`
* \param [out] outcome Storage where conversion outcome is returned, may be `NULL`
*
* \return Conversion result, 0 on error (use `outcome` to detect error).
*/
double Atof(const char *s, char **endp, NumConvRet *outcome);
#endif

View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file srcloc.h
*
* Source location information type.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_SRCLOC_H_
#define DF_SRCLOC_H_
#include "xpt.h"
/**
* Source location information, useful to pack toghether
* information from `__FILE__`, `__LINE__`, and `__func__` for error reporting
* purposes.
*/
typedef struct {
const char *filename; ///< Filename the error was raised from
unsigned long long line; ///< Line that triggered the error
const char *func; ///< Function that raised the error
unsigned call_depth; ///< Call depth, number of functions skipped for backtrace
} Srcloc;
/**
* Declare a `Srcloc` variable named `var` containing location
* info about next, current, or previous source line.
*
* \param var Identifier name to assign to the newly created variable
*
* @{
* @def SRCLOC_NEXT_LINE
* @def SRCLOC_THIS_LINE
* @def SRCLOC_PREV_LINE
* @}
*/
#define SRCLOC_NEXT_LINE(var) Srcloc var = { __FILE__, __LINE__ + 1, __func__, 0 }
#define SRCLOC_THIS_LINE(var) Srcloc var = { __FILE__, __LINE__, __func__, 0 }
#define SRCLOC_PREV_LINE(var) Srcloc var = { __FILE__, __LINE__ - 1, __func__, 0 }
#endif

140
lonetix/include/df/stm.h Normal file
View File

@ -0,0 +1,140 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file stm.h
*
* General stream I/O definitions and utilities.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_STM_H_
#define DF_STM_H_
#include "sys/fsdef.h" // Fildes and SeekMode definitions
/**
* \brief I/O stream operations interface.
*
* Defines an interface to work with an abstract I/O stream.
*/
typedef struct {
/**
* \brief Read bytes from the stream.
*
* Function pointer may be `NULL` on write-only streams.
*
* \param streamp Stream handle
* \param buf Destination buffer
* \param nbytes_ Bytes count to be read from `streamp` to `buf`
*
* \return Number of bytes actually read from the stream,
* which may be less than the requested number of bytes.
* If no bytes are available (`EOF`) function returns 0.
* On error `-1` is returned.
*
* \see For reference `Sys_Fread()`
*/
Sint64 (*Read)(void *streamp, void *buf, size_t nbytes);
/**
* \brief Write bytes to the stream.
*
* Function pointer may be `NULL` on read-only streams.
*
* \param streamp Stream handle
* \param buf buffer to be written, holds at least `nbytes` bytes
* \param nbytes Bytes count to be written to `hn`
*
* \return Number of bytes actually written to `streamp`, which
* may be less than `nbytes`, -1 on error.
*
* \see For reference `Sys_Fwrite()`
*/
Sint64 (*Write)(void *streamp, const void *buf, size_t nbytes);
/**
* \brief Seek inside the stream.
*
* Function pointer may be `NULL` on non-seekable streams.
*
* \param streamp Stream handle
* \param off Seek offset in bytes
* \param whence Seek mode
*
* \return The final cursor position on success, -1 on error.
*
* \see For reference [Sys_Fseek()](@ref Sys_Fseek)
*/
Sint64 (*Seek)(void *streamp, Sint64 off, SeekMode whence);
/**
* \brief Retrieve current position inside stream.
*
* Function pointer may be `NULL` on non-seekable streams.
*
* \param streamp Stream handle
*
* \return Current cursor position inside `streamp`, -1 on error.
*
* \see For reference `Sys_Ftell()`
*/
Sint64 (*Tell)(void *streamp);
/**
* \brief Finalizes writes to stream.
*
* Function pointer may be `NULL` in read-only streams or when such
* operation is unavailable or meaningless.
*
* \param streamp Stream handle
*
* \return`OK` on success`NG` otherwise.
*/
Judgement (*Finish)(void *streamp);
/**
* \brief Closes the stream and free its resources.
*
* Function pointer may be `NULL` if such operation is not necessary.
*
* \param streamp Stream handle to be closed
*
* \see For reference `Sys_Fclose()`
*/
void (*Close)(void *streamp);
} StmOps;
/**
* \brief Implementation of `StmOps` over `Fildes`.
*
* May be used to enable any function accepting `StmOps` to
* work with `Fildes`.
* Complete ownership is provided by `Stm_FildesOps`, non-closing
* access is provided by `Stm_NcFildesOps`.
* `Finish()` function performs a full file sync to disk
* (as in: `Sys_Fsync()` with a `TRUE` `fullSync` flag).
*
* @{
* \var Stm_FildesOps
* \var Stm_NcFildesOps
* @}
*/
extern const StmOps *const Stm_FildesOps;
extern const StmOps *const Stm_NcFildesOps;
/**
* \brief Obtain a stream pointer from a file descriptor.
*
* \param [in] fd File descriptor
*
* \return Pointer suitable to be used as the `streamp` parameter for
* a `StmOps` interface.
*
* \see `Stm_FildesOps`, `Stm_NcFildesOps`
*
* \note Returned pointer may very well be `NULL`.
*/
FORCE_INLINE void *STM_FILDES(Fildes fd)
{
STATIC_ASSERT(sizeof(fd) <= sizeof(Sintptr), "STM_FILDES() ill formed on this platform");
return ((void *) ((Sintptr) fd));
}
#endif

198
lonetix/include/df/strlib.h Normal file
View File

@ -0,0 +1,198 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file strlib.h
*
* Additional ASCII string and char classification utility library.
*
* \copyright The DoubleFourteen Code Forge (c) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_STRLIB_H_
#define DF_STRLIB_H_
#include "xpt.h"
/// Test whether `c` is a digit `char`.
FORCE_INLINE Boolean Df_isdigit(char c)
{
return c >= '0' && c <= '9';
}
/// Test whether `c` is an uppercase ASCII `char`.
FORCE_INLINE Boolean Df_isupper(char c)
{
return c >= 'A' && c <= 'Z';
}
/// Test whether `c` is a lowercase ASCII `char`.
FORCE_INLINE Boolean Df_islower(char c)
{
return c >= 'a' && c <= 'z';
}
/// Test whether `c` is an ASCII space `char`.
FORCE_INLINE Boolean Df_isspace(char c)
{
return c == ' ' || (c >= '\t' && c <= '\n');
}
/// Test whether `c` is an alphabetic ASCII `char`.
FORCE_INLINE Boolean Df_isalpha(char c)
{
return Df_islower(c) || Df_isupper(c);
}
/// Test whether `c` is an alphanumeric ASCII `char`.
FORCE_INLINE Boolean Df_isalnum(char c)
{
return Df_isdigit(c) || Df_isalpha(c);
}
/**
* \brief Convert `c` to uppercase ASCII `char`.
*
* \return Uppercase `c`, if `c` is lowecase, otherwise
* returns `c` itself.
*/
FORCE_INLINE char Df_toupper(char c)
{
// Toggle off lowercase bit if c is lowercase
return c & ~(Df_islower(c) << 5);
}
/**
* \brief Convert `c` to lowercase ASCII `char`.
*
* \return Lowercase `c`, if `c` is uppercase, otherwise returns `c` itself.
*/
FORCE_INLINE char Df_tolower(char c)
{
// Only toggle lowercase bit if c is uppercase
return c | (Df_isupper(c) << 5);
}
/// Test whether `c` is an hexadecimal digit `char`.
FORCE_INLINE Boolean Df_ishexdigit(char c)
{
char lc = Df_tolower(c);
return Df_isdigit(c) || (lc >= 'a' && lc <= 'f');
}
/**
* \brief Concatenate `dest` with `s`, writing at most `n` `char`s to `dest`
* (including `\0`).
*
* \return `strlen(dest) + strlen(s)` before concatenation, that is:
* the length of the concatenated string without truncation,
* truncation occurred if return value `>= n`.
*
* \note Function always terminates `dest` with `\0`.
*/
size_t Df_strncatz(char *dest, const char *s, size_t n);
/**
* \brief Copy at most `n` `char`s from `s` to `dest` (including `\0`).
*
* \return The length of `s`, truncation occurred if return value `>= n`.
*
* \note Function always terminates `dest` with `\0`.
*/
size_t Df_strncpyz(char *dest, const char *s, size_t n);
/// Compare ASCII strings `a` and `b` ignoring case.
int Df_stricmp(const char *a, const char *b);
/// Compare at most `n` chars from ASCII strings `a` and `b`, ignoring case.
int Df_strnicmp(const char *a, const char *b, size_t n);
/// Convert ASCII string to lowercase, returns `s` itself.
FORCE_INLINE char *Df_strlwr(char *s)
{
char *p = s;
char c;
while ((c = *p) != '\0')
*p++ = Df_tolower(c);
return s;
}
/// Convert ASCII string to uppercase, returns `s` itself.
FORCE_INLINE char *Df_strupr(char *s)
{
char *p = s;
char c;
while ((c = *p) != '\0')
*p++ = Df_toupper(c);
return s;
}
/// Trim trailing whitespaces in-place, returns `s` itself.
INLINE char *Df_strtrimr(char *s)
{
EXTERNC size_t strlen(const char *);
size_t n = strlen(s);
while (n > 0 && Df_isspace(s[n-1])) n--;
s[n] = '\0';
return s;
}
/// Trim leading whitespaces in-place, returns `s` itself.
INLINE char *Df_strtriml(char *s)
{
char *p1 = s, *p2 = s;
while (Df_isspace(*p1)) p1++;
while ((*p2++ = *p1++) != '\0');
return s;
}
/// Trim leading and trailing whitespaces in-place, returns `s` itself.
INLINE char *Df_strtrim(char *s)
{
Df_strtrimr(s);
return Df_strtriml(s);
}
/**
* \brief Pad string to left with `c` up to `n` ASCII chars.
*
* \return Resulting string length.
*/
INLINE size_t Df_strpadl(char *s, char c, size_t n)
{
EXTERNC size_t strlen(const char *);
EXTERNC void *memmove(void *, const void *, size_t);
EXTERNC void *memset(void *, int, size_t);
size_t len = strlen(s);
if (len < n) {
memmove(s + n - len, s, len + 1);
memset(s, c, n - len);
len = n;
}
return len;
}
/**
* \brief Pad string to right with `c` up to `n` ASCII chars.
*
* \return Resulting string length.
*/
INLINE size_t Df_strpadr(char *s, char c, size_t n)
{
EXTERNC size_t strlen(const char *);
size_t i = strlen(s);
while (i < n) s[i++] = c;
s[i] = '\0';
return i;
}
#endif

104
lonetix/include/df/sys/con.h Executable file
View File

@ -0,0 +1,104 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/con.h
*
* System console interface.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* Provides basic functionality to print to system console and
* test for supported capabilities.
*/
#ifndef DF_SYS_CON_H_
#define DF_SYS_CON_H_
#include "stm.h"
#include <stdarg.h>
/**
* \typedef ConHn
* \brief Platform console handle descriptor.
*
* \def STDIN
* \brief Platform Standard Input (`stdin`) handle.
* \def STDOUT
* \brief Platform Standard Output (`stdout`) handle.
* \def STDERR
* \brief Platform Standard Error (`stderr`) handle.
*
* \fn Fildes CON_FILDES(ConHn)
*
* \brief Convert a `ConHn` to a `Fildes`, making the console handle usable with
* regular platform file API.
*/
#ifdef _WIN32
typedef Uint32 /*DWORD*/ ConHn;
#define STDIN ((ConHn) -10) // STD_INPUT_HANDLE
#define STDOUT ((ConHn) -11) // STD_OUTPUT_HANDLE
#define STDERR ((ConHn) -12) // STD_ERROR_HANDLE
Fildes CON_FILDES(ConHn hn);
#else
typedef int ConHn;
#define STDIN ((ConHn) 0)
#define STDOUT ((ConHn) 1)
#define STDERR ((ConHn) 2)
FORCE_INLINE Fildes CON_FILDES(ConHn hn)
{
return (Fildes) hn; // trivial on Unix
}
#endif
/**
* \brief Convert a `ConHn` to a `streamp` pointer for `StmOps`.
*
* \see `Stm_ConOps`
*/
FORCE_INLINE void *STM_CONHN(ConHn hn)
{
STATIC_ASSERT(sizeof(Sintptr) >= sizeof(void *), "ConHn ill formed on this platform");
return (void *) ((Sintptr) hn);
}
/**
* \brief `StmOps` interface operating on console output.
*
* There is no `Close()` function for consoles.
*/
extern const StmOps *const Stm_ConOps;
/**
* Print string to console.
*
* \note No newline is appended.
*/
void Sys_Print(ConHn hn, const char *s);
/// Formatted print to console handle.
CHECK_PRINTF(2, 3) void Sys_Printf(ConHn hn, const char *fmt, ...);
/// `Sys_Printf()` variant using `va_list`.
CHECK_PRINTF(2, 0) void Sys_VPrintf(ConHn hn, const char *fmt, va_list va);
/**
* \brief Read at most `nbytes` characters from `hn` to `buf`, input is *not* `\0` terminated.
*
* \return Count of `char`s written to `buf`.
*/
size_t Sys_Read(ConHn hn, char *buf, size_t nbytes);
/// Non-Blocking variant of `Sys_Read()`.
size_t Sys_NbRead(ConHn hn, char *buf, size_t nbytes); // NON-BLOCKING
/// Test whether `hn` supports VT100 commands.
Boolean Sys_IsVt100Console(ConHn hn);
#endif

59
lonetix/include/df/sys/dbg.h Executable file
View File

@ -0,0 +1,59 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/dbg.h
*
* Debugging utilities to retrieve stack trace and symbol names.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_SYS_DBG_H_
#define DF_SYS_DBG_H_
#include "sys/con.h"
/// Helper union to cast `void *` to function pointer - be sad if you use it.
typedef union {
void *sym; ///< Function as a `void *`
void (*func)(void); ///< Function pointer.
} Funsym;
STATIC_ASSERT(sizeof(void *) == sizeof(void (*)(void)), "Ill-formed Funsym for target platform");
/// Test whether the current process is being executed under a debugger.
Boolean Sys_IsDebuggerPresent(void);
/**
* \brief Get a symbol's name, if available.
*
* \param [in] sym Symbol whose name is needed.
*
* \return A pointer to a statically allocated thread-local buffer containing
* a `\0` terminated symbol name on success, `NULL` if no name could be
* retrieved.
*/
char *Sys_GetSymbolName(void *sym);
/**
* \brief Dump at most `n` backtrace entries to `dest`.
*
* Returned backtrace does not include `Sys_GetBacktrace()`
* itself, and has the caller as the topmost symbol.
*
* \return Number of entries actually returned to `dest` on success,
* 0 when no backtrace information is available.
*/
size_t Sys_GetBacktrace(void **dest, size_t n);
/**
* \brief Get the caller's caller.
*
* Useful for quick debugging:
* ```c
* printf("Called by: %s\n", Sys_GetSymbolName(Sys_GetCaller()));
* ```
*/
void *Sys_GetCaller(void);
/// Dump backtrace to console (typically `STDERR`).
void Sys_DumpBacktrace(ConHn hn, void **trace, size_t n);
#endif

132
lonetix/include/df/sys/endian.h Executable file
View File

@ -0,0 +1,132 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/endian.h
*
* Architecture specific byteswap utilities.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_SYS_ENDIAN_H_
#define DF_SYS_ENDIAN_H_
#include "xpt.h"
#ifdef _MSC_VER
#include <stdlib.h> // _byteswap_*()
#endif
/// Helper `union` to access bit representation of `float`.
typedef union {
float f32;
Uint32 bits;
} Floatbits;
/// Helper `union` to access bit representation of `double`.
typedef union {
double f64;
Uint64 bits;
} Doublebits;
STATIC_ASSERT(sizeof(Floatbits) == sizeof(Uint32), "float vs Uint32 size mismatch");
STATIC_ASSERT(sizeof(Doublebits) == sizeof(Uint64), "double vs Uint64 size mismatch");
/// Swap bytes inside 16-bits word.
FORCE_INLINE Uint16 bswap16(Uint16 x)
{
#ifdef _MSC_VER
return _byteswap_ushort(x);
#elif defined(__GNUC__)
return __builtin_bswap16(x);
#else
return BSWAP16(x);
#endif
}
/// Swap bytes inside 32-bits dword.
FORCE_INLINE Uint32 bswap32(Uint32 x)
{
#ifdef _MSC_VER
return _byteswap_ulong(x);
#elif defined(__GNUC__)
return __builtin_bswap32(x);
#else
return BSWAP32(x);
#endif
}
/// Swap bytes inside 64-bits quadword.
FORCE_INLINE Uint64 bswap64(Uint64 x)
{
#ifdef _MSC_VER
return _byteswap_uint64(x);
#elif defined(__GNUC__)
return __builtin_bswap64(x);
#else
return BSWAP64(x);
#endif
}
/// `bswap16()` if target isn't little-endian.
FORCE_INLINE Uint16 leswap16(Uint16 x)
{
#if EDN_NATIVE == EDN_LE
return x;
#else
return bswap16(x);
#endif
}
/// `bswap16()` if target isn't big-endian.
FORCE_INLINE Uint16 beswap16(Uint16 x)
{
#if EDN_NATIVE == EDN_BE
return x;
#else
return bswap16(x);
#endif
}
/// `bswap32()` if target isn't little-endian.
FORCE_INLINE Uint32 leswap32(Uint32 x)
{
#if EDN_NATIVE == EDN_LE
return x;
#else
return bswap32(x);
#endif
}
/// `bswap32()` if target isn't big-endian.
FORCE_INLINE Uint32 beswap32(Uint32 x)
{
#if EDN_NATIVE == EDN_BE
return x;
#else
return bswap32(x);
#endif
}
/// `bswap64()` if target isn't little-endian.
FORCE_INLINE Uint64 leswap64(Uint64 x)
{
#if EDN_NATIVE == EDN_LE
return x;
#else
return bswap64(x);
#endif
}
/// `bswap64()` if target isn't big-endian.
FORCE_INLINE Uint64 beswap64(Uint64 x)
{
#if EDN_NATIVE == EDN_BE
return x;
#else
return bswap64(x);
#endif
}
#endif

253
lonetix/include/df/sys/fs.h Executable file
View File

@ -0,0 +1,253 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/fs.h
*
* Portable low-level filesystem layer
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* Achieves portable low-level, close to operating system, filesystem
* functionality. Functionality includes:
* - file I/O
* - file usage hints
* - file creation and removal
* - directory listing, creation and removal
* - path utilities.
*
* No library file buffering is attempted, nor any kind of text-based I/O.
* Paths are UTF-8.
*/
#ifndef DF_SYS_FS_H_
#define DF_SYS_FS_H_
#include "sys/fsdef.h" // platform-specific defs, including `Fildes`
/// File access mode for `Sys_Fopen()`.
typedef enum {
FM_READ, ///< Read-only access
FM_WRITE, ///< Write-only access
FM_APPEND, ///< File-append access
FM_EXCL, ///< Exclusive creation access
FM_TEMP ///< Temporary scratch file creation
} FopenMode;
/// Access pattern is sequential.
#define FH_SEQ BIT(0)
/// Access pattern is random.
#define FH_RAND BIT(1)
/// Avoid filesystem cache buffers with this file.
#define FH_UNBUF BIT(2)
/// File data is accessed once and never reused.
#define FH_NOREUSE BIT(3)
/**
* \brief Open a descriptor handle to a regular or special file.
*
* Every successfully opened file descriptor must be closed using
* `Sys_Fclose()` when no longer necessary.
*
* \param [in] filename Path to file, must not be `NULL`
* \param [in] mode File access mode
* \param [in] hints File access hints (`FH_*` bit mask)
*
* \return Opened file descriptor on success, `FILDES_BAD` on failure.
*/
Fildes Sys_Fopen(const char *filename, FopenMode mode, unsigned hints);
/**
* \brief Move file's cursor to the specified offset.
*
* \param [in] fd Opened file descriptor
* \param [in] offset Offset to move the cursor to, according to `whence`, in bytes
* \param [in] whence Seek mode
*
* \return New cursor position on success, -1 on failure or non-seekable file.
*/
Sint64 Sys_Fseek(Fildes fd, Sint64 offset, SeekMode whence);
/// Retrieve current file cursor position, returns -1 on failure or non-seekable file.
Sint64 Sys_Ftell(Fildes fd);
/// Get current file size, in bytes, -1 on error.
Sint64 Sys_FileSize(Fildes fd);
/**
* \brief Write raw bytes to file.
*
* \param [in] fd Opened, writable, file descriptor
* \param [in] buf Containing at least `nbytes` bytes of data
* \param [in] nbytes Bytes count in `buf` to be written to `fd`
*
* \return Number of bytes actually written on `fd`, possibly less
* than `nbytes` on short-write. -1 on failure.
*/
Sint64 Sys_Fwrite(Fildes fd, const void *buf, size_t nbytes);
/**
* \brief Read raw bytes from file.
*
* \param [in] fd Opened, readable, file descriptor
* \param [out] buf Destination buffer for the read operation
* \param [in] nbytes Bytes count to read from `fd` inside `buf`
*
* \return Number of bytes actually read from `fd`, possibly less than `nbytes`,
* 0 is returned on `EOF` condition. -1 on failure.
*/
Sint64 Sys_Fread(Fildes fd, void *buf, size_t nbytes);
/**
* \brief Truncate or grow file size to its current cursor position.
*
* \return `OK` on success, and file is altered as follows,
* - if file size has been truncated excess data is lost;
* - if file has grown in size (file cursor was beyond the original size),
* new content is unspecified.
* On failure returns `NG` and file is unaltered.
*/
Judgement Sys_SetEof(Fildes fd);
/**
* \brief Synchronize file data to disk, flushing buffers.
*
* Some systems require `fd` to be writable, whether `Sys_Fsync()` supports
* a read-only descriptor is system specific.
* Some systems allow optimized syncs that only guarantee read operations
* consistency afterwards, this optimization is used on such systems when
* `fullSync` is `FALSE`. otherwise `fullSync` is ignored (as if always `TRUE`).
* A call to `Sys_Fsync()` with `fullSync` to `TRUE` causes the system
* to sync disk data with `fd` in its entirety.
*
* \param [in] fd Opened file descriptor
* \param [in] fullSync Whether a full sync should occur
*
* \return `OK` on success, `NG` on failure or on systems where
* there is no mean to force file data sync with disk.
*/
Judgement Sys_Fsync(Fildes fd, Boolean fullSync);
/// Close opened file descriptor.
void Sys_Fclose(Fildes fd);
/**
* \brief List directory contents.
*
* `pat` | Meaning
* ---------|------------------------------------------
* __NULL__ | Equivalent to `""`
* "" | No filtering
* / | Only return subdirectories
* .* | Only return files whose extension matches
*
* \param [in] path Path to directory, must not be `NULL`
* \param [out] nfiles Location to store returned file count, may be `NULL` if unimportant
* \param [in] pat Optional pattern to filter directory files, may be `NULL`
*
* \return `malloc()`ed string list containing matching files,
* must be `free()`d by the caller when no longer necessary.
* A single `free()` on the returned list is sufficient to
* release it entirely.
*/
char **Sys_ListFiles(const char *path, unsigned *nfiles, const char *pat);
/**
* \brief Create directory.
*
* \param [in] path Directory creation path, must not be `NULL`
*
* \return `OK` on success, and directory is created.
* Creating already existing directories is a success.
* `NG` on failure.
*/
Judgement Sys_Mkdir(const char *path);
/**
* \brief Rename a file.
*
* Different systems may impose different restrictions on
* rename operations, in particular when the operation crosses
* different devices in the filesystem.
* The only portable and safe way to rename a file (or, in this
* specific scenario, *move* it) is copying it over `newPath`,
* and remove `path` on success, but the basic `rename` operation is
* usually safe, unexpensive and atomic under the same device.
* `Sys_Rename()` works on directories as well.
*
* \param [in] path Path to the file to be renamed, must not be `NULL`
* \param [in] newPath New name for the file, destination directory must exist
*
* \return `OK` on success, `NG` otherwise.
*/
Judgement Sys_Rename(const char *path, const char *newPath);
/**
* \brief Remove a file or empty directory.
*
* \param [in] path Path to file or directory to be removed, must not be `NULL`
*
* \return `OK` on success, `NG` otherwise.
*/
Judgement Sys_Remove(const char *path);
// Path utilities
/// Retrieve the absolute file extension (leftmost not leading dot in basename).
char *Sys_GetAbsoluteFileExtension(const char *path);
/// Retrieve the file extension (rightmost not leading dot in basename).
char *Sys_GetFileExtension(const char *path);
/**
* \brief Set `path` file extension to `ext`.
*
* \param [in,out] path UTF-8 path, must not be `NULL`
* \param [in] ext File extension, including dot, must not be `NULL`
*
* \return Pointer to the extension inside `path`.
*
* \note Assumes `path` is is large enough to hold the result.
*/
char *Sys_SetFileExtension(char *path, const char *ext);
/**
* \brief Removes extension from `path` if it matches `ext`.
*
* \param [in,out] path UTF-8 path, must not be `NULL`
* \param [in] ext File extension, including dot, leave to `""` or `NULL`
* to remove any extension
*
* \return Length of the resulting path, in chars.
*/
size_t Sys_StripFileExtension(char *path, const char *ext);
/**
* \brief If file in `path` has no extension yet, set it to `ext`.
*
* \param [in,out] path UTF-8 path, must not be `NULL`
* \param [in] ext Default extension to be set, including dot, must not be `NULL`
*
* \return Pointer to the extension inside `path`
*
* \note Assums `path` is large enough to hold the result.
*/
char *Sys_DefaultFileExtension(char *path, const char *ext);
/**
* Strip initial portion of `path` if it matches `basePath`.
*
* \param [in,out] path UTF-8 path, must not be `NULL`
* \param [in] basePath Initial path to be removed, use `""` or `NULL` to strip the entire path and leave only file (Unix `basename()`)
*
* \return Length of the resulting path, in chars.
*/
size_t Sys_StripPath(char *path, const char *basePath);
/// Return `path` depth (number of path components).
size_t Sys_PathDepth(const char *path);
/**
* \brief Strip leading slashes and change any path separator to `/`.
*
* \return Resulting path length, in chars.
*/
size_t Sys_ConvertPath(char *path);
/**
* \brief Change any path separator to a single `PATH_SEP`.
*
* \return Resulting path length, in chars.
*/
size_t Sys_ReplaceSeps(char *path);
/// Case-sensitive UTF-8 path comparison, regardless of separators.
int Sys_PathCompare(const char *a, const char *b);
// XXX int Sys_PathCompareNoCase(const char *a, const char *b);
// XXX int Sys_PathCompareNoCaseAscii(const char *a, const char *b);
#endif

58
lonetix/include/df/sys/fsdef.h Executable file
View File

@ -0,0 +1,58 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/fsdef.h
*
* Platform-specific filesystem types and definitions.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_SYS_FSDEF_H_
#define DF_SYS_FSDEF_H_
#include "xpt.h"
/**
* \typedef Fildes
* \brief Platform specific file handle (`HANDLE` on Windows, `int` elsewhere).
*
* \def FILDES_BAD
* \brief Bad file descriptor.
*
* \def PATH_SEP
* \brief Path separator character (`\` on Windows, `/` elsewhere).
*
* \def EOLN
* \brief Text file newline sequence (`\r\n` on Windows, `\n` elsewhere).
*/
#ifdef _WIN32
typedef void *Fildes;
#define FILDES_BAD 0
#define PATH_SEP '\\'
#define EOLN "\r\n"
#else
typedef int Fildes;
#define FILDES_BAD -1
#define PATH_SEP '/'
#define EOLN "\n"
#endif
/// I/O stream seek modes.
typedef enum {
SK_SET, ///< Seek from beginning of stream
SK_CUR, ///< Seek from current position
SK_END ///< Seek from stream end
} SeekMode;
#endif

View File

@ -0,0 +1,136 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/interlocked.h
*
* Lock-free atomic operations on integers and pointers.
*
* \copyright The DoubleFourteen Code Forge (c) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_SYS_INTERLOCKED_H_
#define DF_SYS_INTERLOCKED_H_
#include "xpt.h"
/**
* \fn void Smp_AtomicStore(long *p, long v)
* \fn void Smp_AtomicStoreRx(long *p, long v)
* \fn void Smp_AtomicStoreRel(long *p, long v)
* \fn void Smp_AtomicStorePtr(void **p, void *v)
* \fn void Smp_AtomicStorePtrRx(void **p, void *v)
* \fn void Smp_AtomicStorePtrRel(void **p, void *v)
* \fn void Smp_AtomicStore8(Sint8 *p, Sint8 v)
* \fn void Smp_AtomicStore8Rx(Sint8 *p, Sint8 v)
* \fn void Smp_AtomicStore8Rel(Sint8 *p, Sint8 v)
* \fn void Smp_AtomicStore16(Sint16 *p, Sint16 v)
* \fn void Smp_AtomicStore16Rx(Sint16 *p, Sint16 v)
* \fn void Smp_AtomicStore16Rel(Sint16 *p, Sint16 v)
* \fn void Smp_AtomicStore32(Sint32 *p, Sint32 v)
* \fn void Smp_AtomicStore32Rx(Sint32 *p, Sint32 v)
* \fn void Smp_AtomicStore32Rel(Sint32 *p, Sint32 v)
* \fn void Smp_AtomicStore64(Sint64 *p, Sint64 v)
* \fn void Smp_AtomicStore64Rx(Sint64 *p, Sint64 v)
* \fn void Smp_AtomicStore64Rel(Sint64 *p, Sint64 v)
*
* Atomic store operation to a variable or pointer.
*
* \note Only a subset of the fixed-size variants may be available on
* specific architectures.
*/
#ifdef _MSC_VER
#include "interlocked_intrin_msvc.h"
#include "interlocked_ops_msvc.h"
#else
#if __GCC_ATOMIC_LONG_LOCK_FREE != 2
#error "interlocked.h requires lock-free atomics on long!"
#endif
#if __GCC_ATOMIC_POINTER_LOCK_FREE != 2
#error "interlocked.h requires lock-free atomics on void *!"
#endif
/******************************************************************************
* ATOMICS ON FIXED SIZE INTEGERS *
******************************************************************************/
#if __GCC_ATOMIC_CHAR_LOCK_FREE == 2
#define INTERLOCKED_INT8
#define INTERLOCKED_TYPE Sint8
#define INTERLOCKED_SUFFIX 8
#include "interlocked_ops_gcc.h"
#undef INTERLOCKED_TYPE
#undef INTERLOCKED_SUFFIX
#endif /* INTERLOCKED_INT8 */
#if (__SIZEOF_SHORT__ == 2 && __GCC_ATOMIC_SHORT_LOCK_FREE == 2) || \
(__SIZEOF_INT__ == 2 && __GCC_ATOMIC_INT_LOCK_FREE == 2)
#define INTERLOCKED_INT16
#define INTERLOCKED_TYPE Sint16
#define INTERLOCKED_SUFFIX 16
#include "interlocked_ops_gcc.h"
#undef INTERLOCKED_TYPE
#undef INTERLOCKED_SUFFIX
#endif /* INTERLOCKED_INT16 */
#if (__SIZEOF_INT__ == 4 && __GCC_ATOMIC_INT_LOCK_FREE == 2) || \
(__SIZEOF_LONG__ == 4)
#define INTERLOCKED_INT32
#define INTERLOCKED_TYPE Sint32
#define INTERLOCKED_SUFFIX 32
#include "interlocked_ops_gcc.h"
#undef INTERLOCKED_TYPE
#undef INTERLOCKED_SUFFIX
#endif /* INTERLOCKED_INT32 */
#if (__SIZEOF_LONG_LONG__ == 8 && __GCC_ATOMIC_LONG_LONG_LOCK_FREE == 2) || \
(__SIZEOF_LONG__ == 8)
#define INTERLOCKED_INT64
#define INTERLOCKED_TYPE Sint64
#define INTERLOCKED_SUFFIX 64
#include "interlocked_ops_gcc.h"
#undef INTERLOCKED_TYPE
#undef INTERLOCKED_SUFFIX
#endif /* INTERLOCKED_INT64 */
/******************************************************************************
* ATOMICS ON LONG INTEGERS *
******************************************************************************/
#define INTERLOCKED_TYPE long
#define INTERLOCKED_SUFFIX
#include "interlocked_ops_gcc.h"
#undef INTERLOCKED_TYPE
#undef INTERLOCKED_SUFFIX
/******************************************************************************
* ATOMICS ON POINTERS *
******************************************************************************/
#define INTERLOCKED_TYPE void *
#define INTERLOCKED_SUFFIX Ptr
#define INTERLOCKED_NO_ARIT
#include "interlocked_ops_gcc.h"
#undef INTERLOCKED_TYPE
#undef INTERLOCKED_SUFFIX
#undef INTERLOCKED_NO_ARIT
#endif
#endif

View File

@ -0,0 +1,63 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/interlocked_intrin_msvc.h
*
* MSVC-specific intrinsics for interlocked operations.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_SYS_INTERLOCKED_H_
#error "use interlocked.h, do not include interlocked_intrin_msvc.h directly"
#endif
#pragma intrinsic(_InterlockedCompareExchange)
#pragma intrinsic(_InterlockedExchangeAdd)
#pragma intrinsic(_InterlockedExchange)
#pragma intrinsic(_InterlockedCompareExchangePointer)
#pragma intrinsic(_InterlockedExchangePointer)
#pragma intrinsic(_InterlockedCompareExchange8)
#pragma intrinsic(_InterlockedExchangeAdd8)
#pragma intrinsic(_InterlockedExchange8)
#pragma intrinsic(_InterlockedCompareExchange16)
#pragma intrinsic(_InterlockedExchangeAdd16)
#pragma intrinsic(_InterlockedExchange16)
#pragma intrinsic(_InterlockedAnd16)
#pragma intrinsic(_InterlockedOr16)
#pragma intrinsic(_InterlockedXor16)
#if (defined(_M_IX86) && _M_IX86 >= 500) || defined(_M_AMD64) || defined(_M_IA64) || defined(_M_ARM)
#pragma intrinsic(_InterlockedCompareExchange64)
#pragma intrinsic(_InterlockedExchangeAdd64)
#pragma intrinsic(_InterlockedExchange64)
#endif
#ifdef _M_ARM
#pragma intrinsic(_InterlockedCompareExchange_nf)
#pragma intrinsic(_InterlockedCompareExchange_acq)
#pragma intrinsic(_InterlockedCompareExchange_rel)
#pragma intrinsic(_InterlockedCompareExchangePointer_nf)
#pragma intrinsic(_InterlockedCompareExchangePointer_acq)
#pragma intrinsic(_InterlockedCompareExchangePointer_rel)
#pragma intrinsic(_InterlockedCompareExchange8_nf)
#pragma intrinsic(_InterlockedCompareExchange8_acq)
#pragma intrinsic(_InterlockedCompareExchange8_rel)
#pragma intrinsic(_InterlockedCompareExchange16_nf)
#pragma intrinsic(_InterlockedCompareExchange16_acq)
#pragma intrinsic(_InterlockedCompareExchange16_rel)
#pragma intrinsic(_InterlockedCompareExchange64_nf)
#pragma intrinsic(_InterlockedCompareExchange64_acq)
#pragma intrinsic(_InterlockedCompareExchange64_rel)
#endif /* _M_ARM */
#include <intrin.h>
#error "Sorry, not implemented yet"

View File

@ -0,0 +1,191 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/interlocked_ops_gcc.h
*
* Generates interlocked functions from GCC intrinsics, based on some macros.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* This file should be `#include`d from `interlocked.h`, on GNUC compilers.
* It generated interlocked primitives based on `INTERLOCKED_TYPE`,
* `INTERLOCKED_SUFFIX` macros.
* `#define`ing `INTERLOCKED_NO_ARIT` disables arithmetic functions
* generation.
* File may be `#include`d multiple times.
*/
#ifndef DF_SYS_INTERLOCKED_H_
#error "Use interlocked.h, do not include interlocked_gcc_ops.h directly"
#endif
#ifndef INTERLOCKED_TYPE
#error "Please define INTERLOCKED_TYPE for atomic operation target type"
#endif
#ifndef INTERLOCKED_SUFFIX
#error "Please define INTERLOCKED_SUFFIX for atomic operation suffix"
#endif
#define _PASTE(a, b) a ## b
#define _XPASTE(a, b) _PASTE(a, b)
#define _FNAME(wk, name, memory_order) \
_XPASTE(_XPASTE(Smp_ ## wk ## Atomic ## name, INTERLOCKED_SUFFIX), memory_order)
#define _FOP(op, memory_order, ...) \
__atomic_ ## op (__VA_ARGS__, __ATOMIC_ ## memory_order)
#define _FCAS(wkflag, succ_memory_order, fail_memory_order, ...) \
__atomic_compare_exchange_n(__VA_ARGS__, wkflag, __ATOMIC_ ## succ_memory_order, __ATOMIC_ ## fail_memory_order)
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Load,) (INTERLOCKED_TYPE *_p)
{
return _FOP(load_n, SEQ_CST, _p);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Load,Rx) (INTERLOCKED_TYPE *_p)
{
return _FOP(load_n, RELAXED, _p);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Load,Acq) (INTERLOCKED_TYPE *_p)
{
return _FOP(load_n, ACQUIRE, _p);
}
FORCE_INLINE void _FNAME(,Store,) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
_FOP(store_n, SEQ_CST, _p, _v);
}
FORCE_INLINE void _FNAME(,Store,Rx) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
_FOP(store_n, RELAXED, _p, _v);
}
FORCE_INLINE void _FNAME(,Store,Rel) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
_FOP(store_n, RELEASE, _p, _v);
}
#ifndef INTERLOCKED_NO_ARIT
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Add,) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(add_fetch, ACQ_REL, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Add,Rx) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(add_fetch, RELAXED, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Add,Acq) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(add_fetch, ACQUIRE, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Add,Rel) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(add_fetch, RELEASE, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Sub,) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(sub_fetch, ACQ_REL, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Sub,Rx) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(sub_fetch, RELAXED, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Sub,Acq) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(sub_fetch, ACQUIRE, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Sub,Rel) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(sub_fetch, RELEASE, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Incr,) (INTERLOCKED_TYPE *_p)
{
return _FOP(fetch_add, ACQ_REL, _p, 1);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Incr,Rx) (INTERLOCKED_TYPE *_p)
{
return _FOP(fetch_add, RELAXED, _p, 1);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Incr,Acq) (INTERLOCKED_TYPE *_p)
{
return _FOP(fetch_add, ACQUIRE, _p, 1);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Incr,Rel) (INTERLOCKED_TYPE *_p)
{
return _FOP(fetch_add, RELEASE, _p, 1);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Decr,) (INTERLOCKED_TYPE *_p)
{
return _FOP(fetch_sub, ACQ_REL, _p, 1);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Decr,Rx) (INTERLOCKED_TYPE *_p)
{
return _FOP(fetch_sub, RELAXED, _p, 1);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Decr,Acq) (INTERLOCKED_TYPE *_p)
{
return _FOP(fetch_sub, ACQUIRE, _p, 1);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Decr,Rel) (INTERLOCKED_TYPE *_p)
{
return _FOP(fetch_sub, RELEASE, _p, 1);
}
#endif /* INTERLOCKED_NO_ARIT */
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Xchng,) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(exchange_n, ACQ_REL, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Xchng,Rx) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(exchange_n, RELAXED, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Xchng,Acq) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(exchange_n, ACQUIRE, _p, _v);
}
FORCE_INLINE INTERLOCKED_TYPE _FNAME(,Xchng,Rel) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _v)
{
return _FOP(exchange_n, RELEASE, _p, _v);
}
FORCE_INLINE Boolean _FNAME(,Cas,) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _e, INTERLOCKED_TYPE _v)
{
return _FCAS(FALSE, ACQ_REL, ACQUIRE, _p, &_e, _v);
}
FORCE_INLINE Boolean _FNAME(,Cas,Rx) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _e, INTERLOCKED_TYPE _v)
{
return _FCAS(FALSE, RELAXED, RELAXED, _p, &_e, _v);
}
FORCE_INLINE Boolean _FNAME(,Cas,Acq) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _e, INTERLOCKED_TYPE _v)
{
return _FCAS(FALSE, ACQUIRE, ACQUIRE, _p, &_e, _v);
}
FORCE_INLINE Boolean _FNAME(,Cas,Rel) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _e, INTERLOCKED_TYPE _v)
{
return _FCAS(FALSE, RELEASE, RELAXED, _p, &_e, _v);
}
FORCE_INLINE Boolean _FNAME(Wk,Cas,) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _e, INTERLOCKED_TYPE _v)
{
return _FCAS(TRUE, ACQ_REL, ACQUIRE, _p, &_e, _v);
}
FORCE_INLINE Boolean _FNAME(Wk,Cas,Rx) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _e, INTERLOCKED_TYPE _v)
{
return _FCAS(TRUE, RELAXED, RELAXED, _p, &_e, _v);
}
FORCE_INLINE Boolean _FNAME(Wk,Cas,Acq) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _e, INTERLOCKED_TYPE _v)
{
return _FCAS(TRUE, ACQUIRE, ACQUIRE, _p, &_e, _v);
}
FORCE_INLINE Boolean _FNAME(Wk,Xchng,Rel) (INTERLOCKED_TYPE *_p, INTERLOCKED_TYPE _e, INTERLOCKED_TYPE _v)
{
return _FCAS(TRUE, RELEASE, RELAXED, _p, &_e, _v);
}
#undef _PASTE
#undef _XPASTE
#undef _FNAME
#undef _FOP
#undef _FCAS

View File

178
lonetix/include/df/sys/ip.h Executable file
View File

@ -0,0 +1,178 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/ip.h
*
* Internet Protocol (IP) definitions and types.
*
* \copyright The DoubleFourteen Code Forge (C) All Right Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_SYS_IP_H_
#define DF_SYS_IP_H_
#include "xpt.h"
/// Known Internet Protocol address family types enumeration.
typedef enum {
IP4, ///< Internet Protocol version 4
IP6 ///< Internet Protocol version 6
} IpType;
/// Internet Protocol version 4 (IPv4) address, big endian order.
typedef union {
Uint8 bytes[4]; ///< Address bytes representation
Uint16 words[2]; ///< Address as a pair of 16-bits words
Uint32 dword; ///< Address as a single 32-bits dword (big endian)
} Ipv4adr;
/// Size of an IPv4 address in bytes.
#define IPV4_SIZE 4
/// Bits inside an IPv4 address.
#define IPV4_WIDTH (IPV4_SIZE * 8)
STATIC_ASSERT(sizeof(Ipv4adr) == IPV4_SIZE, "Bad Ipv4adr size");
/// Size of a string to make an IPv4 address, **excluding** trailing `\0`.
#define IPV4_STRLEN 16
/// IPv4 wildcard address static initializer: `0.0.0.0`.
#define IPV4_ANY_INIT { { 0x00, 0x00, 0x00, 0x00 } }
/// IPv4 loopback address static initializer: `127.0.0.1`.
#define IPV4_LOOPBACK_INIT { { 0x7f, 0x00, 0x00, 0x01 } }
/// IPv4 broadcast address static initializer: `255.255.255.255`.
#define IPV4_BROADCAST_INIT { { 0xff, 0xff, 0xff, 0xff } }
/**
* \brief Convert `Ipv4adr` to its string representation.
*
* The destination buffer **is assumed to be large enough to store
* the resulting string**, a buffer of `IPV4_STRLEN + 1`
* chars is safe to use as the `dest` argument.
*
* \param [in] adr Address to be converted, must not be `NULL`
* \param [out] dest Destination storage for string representation, must not be `NULL`
*
* \return Pointer to the trailing `\0` `char` inside `dest`, useful
* for further string concatenation.
*/
char *Ipv4_AdrToString(const Ipv4adr *adr, char *dest);
/**
* \brief Convert an address from its string representation to `Ipv4adr`.
*
* \return `OK` if conversion was successful, `NG` if address string
* does not represent a valid IP.
*/
Judgement Ipv4_StringToAdr(const char *address, Ipv4adr *dest);
/// Compare IPv4 addresses for equality.
FORCE_INLINE Boolean Ipv4_Equal(const Ipv4adr *a, const Ipv4adr *b)
{
return a->dword == b->dword;
}
/// Internet Protocol version 6 address.
typedef union {
Uint8 bytes[16]; ///< Address as raw bytes
Uint16 words[8]; ///< Address as short words sequence
Uint32 dwords[4]; ///< Address as dwords sequence
} Ipv6adr;
/// Size of an IPv6 address in bytes.
#define IPV6_SIZE 16
/// Bits inside an IPv6 address.
#define IPV6_WIDTH (IPV6_SIZE * 8)
STATIC_ASSERT(sizeof(Ipv6adr) == IPV6_SIZE, "Bad Ipv6adr size");
/// Size of a string to make an IPv6 address, **excluding** trailing `\0`.
#define IPV6_STRLEN 46
/// Static initializer for an UNSPECIFIED Ipv6 address.
#define IPV6_UNSPEC_INIT { { \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 \
} }
/// Static initializer for a LOOPBACK Ipv6 address.
#define IPV6_LOOPBACK_INIT { { \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 \
} }
/// Test whether an IPv6 address represents a mapped IPv4 address.
#define IS_IPV6_V4MAPPED(v6) \
((v6)->dwords[0] == 0 && \
(v6)->dwords[1] == 0 && \
(v6)->dwords[2] == BE32(0xffff))
/// Test whether an IPv6 address is multicast.
#define IS_IPV6_MULTICAST(v6) ((v6)->bytes[0] == 0xff)
/**
* \brief Convert an IPv6 address to its string representation, destination
* storage should be `[IPV6_STRLEN + 1]`.
*
* \return Pointer to the trailing `\0` inside `dest`.
*/
char *Ipv6_AdrToString(const Ipv6adr *adr, char *dest);
/**
* \brief Convert IPv6 address string to `Ipv6adr`.
*
* \return `OK` on success, `NG` if `address` does not represent a valid IPv6 address.
*/
Judgement Ipv6_StringToAdr(const char *address, Ipv6adr *dest);
/// Compare two IPv6 addresses for equality.
FORCE_INLINE Boolean Ipv6_Equal(const Ipv6adr *a, const Ipv6adr *b)
{
return a->dwords[0] == b->dwords[0] && a->dwords[1] == b->dwords[1] &&
a->dwords[2] == b->dwords[2] && a->dwords[3] == b->dwords[3];
}
/// Generic IP address.
typedef struct {
IpType family; ///< Currently stored address family
union {
Uint8 bytes[IPV6_SIZE]; ///< Address as raw bytes, for convenient initialization
Ipv4adr v4; ///< IPv4 address, if `family == IP4`
Ipv6adr v6; ///< IPv6 address, if `family == IP6`
Uint32 dword; ///< As IPv4 single dword, for macro compat
Uint16 words[8]; ///< As IPv4/IPv6 word sequence, for macro compat
Uint32 dwords[4]; ///< As IPv6 dwords sequence, for macro compat
};
} Ipadr;
/**
* \brief Convert a generic IP address to its string representation.
*
* \note A destination string of length `IPV6_STRLEN + 1`
* is capable of storing an address string for any address family.
*/
char *Ip_AdrToString(const Ipadr *adr, char *dest);
/**
* \brief Convert IP string representation to `Ipadr`.
*
* \return `OK` on success, `NG` if `address` is not a valid IP address.
*/
Judgement Ip_StringToAdr(const char *address, Ipadr *dest);
/// Compare generic addresses for equality.
FORCE_INLINE Boolean Ip_Equal(const Ipadr *a, const Ipadr *b)
{
if (a->family != b->family)
return FALSE;
switch (a->family) {
case IP4: return Ipv4_Equal(&a->v4, &b->v4);
case IP6: return Ipv6_Equal(&a->v6, &b->v6);
default: UNREACHABLE; return FALSE;
}
}
#endif

154
lonetix/include/df/sys/sys.h Executable file
View File

@ -0,0 +1,154 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/sys.h
*
* Miscellaneous system functionality.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_SYS_ERR_H_
#define DF_SYS_ERR_H_
#include "srcloc.h"
#include <stdarg.h>
#ifdef _MSC_VER
// for Sys_WipeMemory()
#include <intrin.h>
#pragma intrinsic(_ReadWriteBarrier)
#endif
/**
* \typedef SysRet
* \brief Platform-specific error code for system operations.
*/
typedef int SysRet; // errno type
/**
* \brief Error status structure.
*
* Stores latest operation result and current error handling callback.
*/
typedef struct {
/// Latest operation result status.
SysRet code;
/**
* \brief Error handler callback.
*
* \param [in] code System error code
* \param [in] reason Human readable concise message providing context to error, never `NULL`
* \param [in] loc Source location where error occurred, `NULL` if unavailable
* \param [in] obj User defined additional data forwarded to callback unaltered
*/
void (*func)(SysRet code, const char *reason, Srcloc *loc, void *obj);
/// Additional user data to be forwarded to `func`.
void *obj;
} SysErrStat;
/**
* \brief Abort error handler callback.
*
* This error handler terminates execution abnormally,
* attempting to log an error message as accurate as possible using system
* specific facilities.
*
* If this handler is registered, execution shall terminate as soon as an
* exceptional error condition is encountered in the system layer.
*/
#define SYS_ERR_ABORT ((void (*)(SysRet, const char *, Srcloc *, void *)) -1)
/**
* \brief Ignoring error handler callback.
*
* If this handler is registered any system error is ignored.
*/
#define SYS_ERR_IGN ((void (*)(SysRet, const char *, Srcloc *, void *)) 0)
/**
* \brief Terminating error handler callback.
*
* Terminates execution providing sensible error message, it differs
* from `SYS_ERR_ABORT` in that no exceptional termination semantics is
* implied, but rather an unsuccessful event that caused immediate exit
* (no core dump or stack traces are left behind).
*/
#define SYS_ERR_QUIT ((void (*)(SysRet, const char *, Srcloc *, void *)) 1)
/**
* \brief Install a system error handler callback.
*
* Initially installed handler is `SYS_ERR_IGN`,
* thus any error is effectively ignored unless a new handler is installed.
*
* Once installed, the error callback is called immediately for every
* exceptional error condition encountered inside system layer.
*
* Error handlers are thread-local, thus different threads may implement
* appropriate error handling policy without interfering with each other.
*
* \param [in] func Error handler function
* \param [in] obj Custom user-provided object passed unaltered upon callback
*
* \see `SysErrStat` for callback documentation
*/
void Sys_SetErrFunc(void (*func)(SysRet, const char *, Srcloc *, void *), void *obj);
/**
* Retrieve the system error status.
*
* \param [out] stat Storage for returned error status, may be `NULL`
*
* \return Last operation result status.
*/
SysRet Sys_GetErrStat(SysErrStat *stat);
/**
* Trigger an out of memory error.
*
* @note Depending on the current error policy, execution may
* very well continue after call returns.
*/
#define Sys_OutOfMemory() _Sys_OutOfMemory(__FILE__, __func__, __LINE__, 0)
// NOTE: implementation detail, should always be called through `Sys_OutOfMemory()`.
NOINLINE void _Sys_OutOfMemory(const char *,
const char *,
unsigned long long,
unsigned);
/**
* \brief Zero-out `nbytes` bytes inside `data`, preventing compiler optimization.
*
* A compiler might optimize-out `memset()` or similar operations if `data`
* is never read again.
* This function ensures such operation is never optimized away, which
* may be useful to zero-out sensitive data.
*/
FORCE_INLINE void Sys_WipeMemory(volatile void *data, size_t nbytes)
{
#if defined(_MSC_VER) || defined(__GNUC__)
EXTERNC void *memset(void *, int, size_t);
memset((void *) data, 0, nbytes);
// Compiler fence
#ifdef _MSC_VER
_ReadWriteBarrier();
#else
__asm__ __volatile__ ("" : : : "memory");
#endif
#else
// Slow but portable
volatile Uint8 *p = (volatile Uint8 *) data;
while (nbytes--)
*p++ = 0x00;
#endif
}
/// Sleep *at least* for the specified amount of **milliseconds**.
void Sys_SleepMillis(Uint32 millis);
#endif

157
lonetix/include/df/sys/vt100.h Executable file
View File

@ -0,0 +1,157 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/vt100.h
*
* VT100 compliant control code sequences.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_SYS_VT100_H_
#define DF_SYS_VT100_H_
#ifndef STR
#define STR(x) #x
#endif
#ifndef XSTR
#define XSTR(x) STR(x)
#endif
/// VT100 escape character constant, prefixed to any command
#define VTESC "\x1b"
/// Operating System Command marker, immediately following ESC
#define VTOSC "]"
/// CSI marker, immediately following ESC
#define VTCSI "["
/// Enabling command suffix
#define VTENB "h"
/// Disabling command suffix
#define VTDIS "l"
/// Designate Character Set - DEC Line mode drawing
#define VTDCSLIN VTESC "(0"
/// Designate Character Set - US ASCII (default) mode
#define VTDCSCHR VTESC "(B"
/// Soft Terminal Reset (reset terminal to default state)
#define VTSRST VTESC VTCSI "!p"
/// Cursor blink command mnemonic
#define ATT160 "?12"
/// Text Cursor Enable Mode command mnemonic
#define DECTCEM "?25"
/// Start cursor blinking
#define VTBLKENB VTESC VTCSI ATT160 VTENB
/// Stop cursor blinking
#define VTBLKDIS VTESC VTCSI ATT160 VTDIS
/// Show cursor inside console
#define VTCURSHW VTESC VTCSI DECTCEM VTENB
/// Hide console cursor
#define VTCURHID VTESC VTCSI DECTCEM VTDIS
/// Console Screen Buffer command mnemonic
#define DECSCRB "?1049"
/// Switch to Alternate Screen buffer
#define VTALTSCR VTESC VTCSI DECSCRB VTENB
/// Switch to Main Screen buffer
#define VTMAINSCR VTESC VTCSI DECSCRB VTDIS
/// Set Graphic Rendition command mnemonic
#define SGR "m"
/// Set Graphics Rendition to `N` (N is any of the SGR constants)
#define VTSGR(n) VTESC VTCSI XSTR(n) SGR
/// Obtain the corresponding background color code from a foreground color
#define VT_TOBG(c) ((c) + 10)
/// Obtain the corresponding bold/emphasized variant from a foreground or background color
#define VT_TOBLD(c) ((c) + 60)
/// Reset graphics rendition to its default mode (both foreground and background)
#define VTDFLT 0
/// Enable bold text/emphasis
#define VTBLD 1
/// Disable bold text/emphasis
#define VTNOBLD 22
/// Enable text underline
#define VTUND 4
/// Disable text underline
#define VTNOUND 24
/// Invert foreground and background color
#define VTINV 7
/// Restore foreground and background to their normal value
#define VTNOINV 27
/// Black color code (foreground)
#define VTBLK 30
/// Red color code (foreground)
#define VTRED 31
/// Green color code (foreground)
#define VTGRN 32
/// Yellow color code (foreground)
#define VTYEL 33
/// Blue color code (foreground)
#define VTBLUE 34
/// Magenta color code (foreground)
#define VTMAGN 35
/// Cyan color code (foreground)
#define VTCYAN 36
/// White color code (foreground)
#define VTWHIT 37
/// Code to restore foreground color to its default value
#define VTFGDFLT 39
#define VTBGBLK 40
#define VTBGRED 41
#define VTBGGRN 42
#define VTBGYEL 43
#define VTBGBLUE 44
#define VTBGMAGN 45
#define VTBGCYAN 46
#define VTBGWHIT 47
#define VTBGDFLT 49
#define VTBLK_BLD 90
#define VTRED_BLD 91
#define VTGRN_BLD 92
#define VTYEL_BLD 93
#define VTBLUE_BLD 94
#define VTMAGN_BLD 95
#define VTCYAN_BLD 96
#define VTWHIT_BLD 97
#define VTBGBLK_BLD 100
#define VTBGRED_BLD 101
#define VTBGGRN_BLD 102
#define VTBGYEL_BLD 103
#define VTBGBLUE_BLD 104
#define VTBGMAGN_BLD 105
#define VTBGCYAN_BLD 106
#define VTBGWHIT_BLD 107
/// Erase in Display command mnemonic
#define ED "J"
/// Erase in Line command mnemonic
#define EL "K"
/// Erase in Display with command parameter `n`
#define VTED(n) VTESC VTCSI XSTR(n) ED
/// Erase in Line with command parameter `n`
#define VTEL(n) VTESC VTCSI XSTR(n) EL
/// Erase from cursor (inclusive) to end of display/line
#define VTCUR 0
/// Erase from the beginning of the line/display to end
#define VTSET 1
/// Erase everything in line/display
#define VTALL 2
#endif

92
lonetix/include/df/utf/utf.h Executable file
View File

@ -0,0 +1,92 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file utf/utf.h
*
* UTF-8 decoding and encoding functionality.
*
* \author Russ Cox
* \author Rob Pike
* \author Ken Thompson
* \author Lorenzo Cogotti
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved.
*
* This API is derived by work authored by Russ Cox - namely the Unix port of the Plan 9
* UTF-8 library, originally written by Rob Pike and Ken Thompson.
*
* Original license terms follow:
* ```
* Copyright © 2021 Plan 9 Foundation
*
* 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.
* ```
* The original libutf library is available at: https://9fans.github.io/plan9port/unix/libutf.tgz
*/
#ifndef DF_UTF_H_
#define DF_UTF_H_
#include "utf/utfdef.h"
/**
* \brief Convert the first UTF-8 rune inside `\0` terminated string `str` to a `Rune` in `dest`.
*
* \return Number of bytes read from `str` for the returned `Rune`.
*
* \note Returned bytes are usually equivalent to `runelen()` over the returned `Rune`,
* but values may differ in case of a decoding error. In that case `¢hartorune()` returns `RUNE_ERR`,
* and returns 1. This allows the caller to skip one byte and move on with the decoding.
*/
size_t chartorune(Rune *dest, const char *str);
/// Inverse of `chartorune()`.
size_t runetochar(char *dest, Rune r);
/// Calculate the number of bytes necessary to encode `r`.
size_t runelen(Rune r);
/// Calculate the number of bytes necessary to encode the first `n` runes referenced by `r`.
size_t runenlen(const Rune *r, size_t n);
/// Test whether the first `n` bytes referenced by `src` form at least one `Rune`.
Boolean fullrune(const char *src, size_t n);
/// Convert `r` to lowercase.
Rune tolowerrune(Rune r);
/// Convert 'r` to uppercase.
Rune toupperrune(Rune r);
/// Convert `r` to titlecase.
Rune totitlerune(Rune r);
/// Test whether `r` is a lowercase UTF-8 rune.
Boolean islowerrune(Rune r);
/// Test whether `r` is an uppercase UTF-8 rune.
Boolean isupperrune(Rune r);
/// Test whether `r` represents an alphabetic UTF-8 rune.
Boolean isalpharune(Rune r);
/// Test wheter `r` is a title-case UTF-8 rune.
Boolean istitlerune(Rune r);
/// Test whether `r` represents a space UTF-8 rune.
Boolean isspacerune(Rune r);
/// Return the number of runes inside the `\0` terminated UTF-8 string `s`.
size_t utflen(const char *s);
/// Find the first occurrence of `r` inside the `\0' terminated UTF-8 string `s`, `NULL` if not found.
char *utfrune(const char *s, Rune r);
/// Find the last occurrence of `r` inside the `\0` terminated UTF-8 string `s`, `NULL` if not found.
char *utfrrune(const char *s, Rune r);
/// Find the first occurrence of the UTF-8 `\0` terminated UTF-8 string `needle` inside `haystack`, `NULL` if not found.
char *utfutf(const char *haystack, const char *needle);
#endif

27
lonetix/include/df/utf/utfdef.h Executable file
View File

@ -0,0 +1,27 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file utf/utfdef.h
*
* UTF-8 types and macro constants definitions.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_UTFDEF_H_
#define DF_UTFDEF_H_
#include "xpt.h"
/// 32-bits unsigned type capable of holding any UTF-8 character.
typedef Uint32 Rune;
#define MAXUTF 4u ///< Maximum bytes per `Rune`
#define RUNE_SYNC 0x80 ///< Cannot represent part of a UTF sequence (<)
#define RUNE_SELF 0x80 ///< `Rune` and UTF sequences are the same (<)
#define RUNE_ERR 0xfffdu ///< Decoding error in UTF
#define MAXRUNE 0x10ffffu ///< Maximum `Rune` value
#define BOM 0xefbbbfu ///< UTF-8 BOM marker
#endif

491
lonetix/include/df/xpt.h Normal file
View File

@ -0,0 +1,491 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file xpt.h
*
* Cross platform types and definitions.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* Defines fixed-size integer types and other general utility types and
* other macros that are ought to be available and equivalent across any
* supported platform.
*
* This header keeps its dependencies at a bare minimum, and **MUST NOT**
* excessively pollute the namespace.
*
* Including this header makes symbols from `stddef.h` and `stdint.h` visible.
*/
#ifndef DF_XPT_H_
#define DF_XPT_H_
#include <stddef.h>
#include <stdint.h>
// Extern function declarations for inline functions on header
/**
* \def EXTERNC
*
* Expands to `extern` or `extern "C"` depending on whether or not the compiler
* supports C++.
*
* This is useful to declare `extern` C functions inside inline functions
* code within .h files, in order to avoid a full fledged `#include`.
*/
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC extern
#endif
// Boolean and fixed size types
typedef int Boolean; ///< A boolean value, legal values are `TRUE` or `FALSE`
typedef int ToggleState; ///< Switch toggle state, legal values are `ON` or `OFF`
typedef int Judgement; ///< Operation result outcome, legal values are `OK` or `NG`
/// Boolean values (`true`, `false`).
enum BooleanLogic {
FALSE = 0, ///< `false` boolean value, guaranteed to be 0.
TRUE = 1 ///< `true` boolean value, guaranteed to be 1.
};
/// Switch toggle states (`on`, `off`).
enum BooleanSwitch {
OFF = 0, ///< `off` value, guaranteed to be 0.
ON = 1 ///< `on` value, guaranteed to be 1.
};
/// Operation success states (success, failure).
enum BooleanJudgement {
OK = 0, ///< Success state, guaranteed to be 0 (no errors).
NG = -1 ///< Failure state, negative value, equals to -1 (no-good).
};
typedef char Boolean8; ///< Packed boolean type, may hold the same values as `Boolean`
typedef int8_t Sint8; ///< Fixed-size signed 8-bits integer type
typedef uint8_t Uint8; ///< Fixed-size unsigned 8-bits integer type
typedef int16_t Sint16; ///< Fixed-size signed 16-bits integer type
typedef uint16_t Uint16; ///< Fixed-size unsigned 16-bits integer type
typedef int32_t Sint32; ///< Fixed-size signed 32-bits integer type
typedef uint32_t Uint32; ///< Fixed-size unsigned 32-bits integer type
typedef int64_t Sint64; ///< Fixed-size signed 64-bits integer type
typedef uint64_t Uint64; ///< Fixed-size unsigned 64-bits integer type
typedef intptr_t Sintptr; ///< Fixed-size signed type large enough to store a pointer
typedef uintptr_t Uintptr; ///< Fixed-size unsigned type large enough to store a pointer
// Fixed size compile time constants
#define S8_C(c) INT8_C(c) ///< Expands constant `c` to a signed 8 bit integer literal
#define U8_C(c) UINT8_C(c) ///< Expands constant `c` to an unsigned 8 bit integer literal
#define S16_C(c) INT16_C(c) ///< Expands constant `c` to a signed 16 bit integer literal
#define U16_C(c) UINT16_C(c) ///< Expands constant `c` to an unsigned 16 bit integer literal
#define S32_C(c) INT32_C(c) ///< Expands constant `c` to a signed 32 bit integer literal
#define U32_C(c) UINT32_C(c) ///< Expands constant `c` to an unsigned 32 bit integer literal
#define S64_C(c) INT64_C(c) ///< Expands constant `c` to a signed 64 bit integer literal
#define U64_C(c) UINT64_C(c) ///< Expands constant `c` to an unsigned 64 bit integer literal
// Compiler support for thread-locals, over/under-alignment,
// inline and no-return functions
// NOTE: we don't support TinyCC because there's no TLS support there
#ifdef __TINYC__
#error "Sorry, no TLS support available on Tiny C Compiler"
#endif
/**
* \def THREAD_LOCAL
*
* \brief Declares a static variable as thread local.
*
* \def ALIGNED(a, what)
*
* \brief Force type, variable, or member alignment.
*
* \param a Required alignment, must be a positive power of two and a compile-time integer literal.
* \param what What should be aligned, a variable, type or member
*
* \def NORETURN
*
* \brief Declare a function shall never return to its caller (like `exit()`).
*
* \warning Behavior is undefined if function does return.
*
* \def UNREACHABLE
*
* \brief Mark code as unreachable.
*
* \warning Behavior is undefined if code is actually reached during execution.
*
* \def FORCE_INLINE
*
* \brief Require function inlining, regardless of compiler policy.
*
* \note Function may be inlined even in debug builds.
*
* \def INLINE
*
* \brief Suggest a function should be inlined, compiler may still choose not to.
*
* \def NOINLINE
*
* \brief Force the compiler **not** to inline a function,
* regardless of its own policy.
*/
#ifdef _MSC_VER
#define THREAD_LOCAL __declspec(thread)
#else
#define THREAD_LOCAL __thread
#endif
#ifdef _MSC_VER
#define ALIGNED(a, what) __declspec(align(a)) what
#else
#define ALIGNED(a, what) what __attribute__((__aligned__(a)))
#endif
#ifdef _MSC_VER
#define NORETURN __declspec(noreturn)
#else
#define NORETURN __attribute__((__noreturn__))
#endif
#ifdef _MSC_VER
#define UNREACHABLE __assume(0)
#elif defined(__GNUC__)
#define UNREACHABLE __builtin_unreachable()
#else
#define UNREACHABLE ((void) 0) /*NOTREACHED*/
#endif
#ifdef _MSC_VER
#define FORCE_INLINE __forceinline
#define INLINE __inline
#define NOINLINE __declspec(noinline)
#elif defined(__GNUC__)
#define FORCE_INLINE static __inline__ __attribute__((__always_inline__, __unused__))
#define INLINE static __inline__ __attribute__((__unused__))
#define NOINLINE __attribute__((__noinline__))
#else
// this is a crude fallback and actually loses quite a bit of the intended semantics...
#if 0
#error "No FORCE_INLINE and NOINLINE for this platform"
#endif
#define FORCE_INLINE static inline
#define INLINE static inline
#define NOINLINE
#endif
// Compiler checks
/**
* \def CHECK_PRINTF
*
* \brief Compiler check on `printf()`-like function arguments.
*
* If compiler supports it, check at compile-time that `printf()` and `vprintf()`
* like formatted message arguments are valid.
*
* \param f `const char *` message template argument index (1 is first argument)
* \param a Variadic arguments start (`...` argument index), use 0 if function takes a `va_list` (1 is first argument)
*
* \def STATIC_ASSERT
*
* \brief Compile time assertion.
*
* If compiler supports it, test a condition at compile time, aborting
* compilation on failure.
*
* \param what Expression to be evaluated at compile time
* \param msg Failure message
*/
#ifdef __GNUC__
#define CHECK_PRINTF(f, a) __attribute__((__format__(__printf__, f, a)))
#else
#define CHECK_PRINTF(f, a)
#endif
#ifndef __cplusplus
#define STATIC_ASSERT(what, msg) _Static_assert(what, msg)
#else
#define STATIC_ASSERT(what, msg) static_assert(what, msg)
#endif
// NOTE: if we ever need packing just use the MSVC style #pragma pack(push, N)
// + #pragma pack(pop), it's pretty much universal.
// Platform suitable memory alignment type
/**
* \def ALIGNMENT
*
* \brief Platform memory alignment required for primitive types.
*
* \note This alignment is only safe for primitive and trivial struct types,
* but may be insufficient for SIMD types or overaligned types.
*/
#ifdef _MSC_VER
#ifdef _WIN32
#define ALIGNMENT 4
#else
#define ALIGNMENT 8
#endif
#elif defined(__GNUC__)
#define ALIGNMENT __BIGGEST_ALIGNMENT__
#else
// conservative guess
#define ALIGNMENT 16
#endif
/**
* \brief Perform or test alignment of size `x` or pointer `p` to `align` bytes.
*
* \param x Unsigned integral size in bytes
* \param p A pointer
* \param align Unsigned integral power of two specifying alignment
*
* \warning `align` must be a power of 2, macro arguments are evaluated more
* than once.
*
* @{
* \def ALIGN
* \def ALIGN_DOWN
* \def ALIGN_PTR
* \def ALIGN_PTR_DOWN
*
* \def IS_ALIGNED
* \def IS_PTR_ALIGNED
* @}
*/
#define ALIGN(x, align) \
(((x) + ((align) - 1)) & ~((align) - 1))
#define ALIGN_DOWN(x, align) \
ALIGN((x) - ((align) - 1), align)
#define ALIGN_PTR(p, align) \
((void *) ALIGN((uintptr_t) (p), align))
#define ALIGN_PTR_DOWN(p, align) \
((void *) ALIGN_DOWN((uintptr_t) (p), align))
#define IS_ALIGNED(x, align) (((x) & ((align) - 1)) == 0)
#define IS_PTR_ALIGNED(p, align) IS_ALIGNED((Uintptr) p, align)
/**
* \def alloca
*
* \brief Dynamic memory allocation on stack.
*
* \param size Size to be allocated, in bytes, unsigned integral value
*
* \warning Allocating large chunks of memory on stack is a sure one-way ticket
* to a stack overflow, there is no way to indicate memory allocation
* failures for `alloca()`.
*/
#ifdef _MSC_VER
#define alloca(size) _alloca(size)
#else
#define alloca(size) __builtin_alloca(size)
#endif
/**
* \def ARRAY_SIZE
*
* \brief Array element count.
*
* \param array Array to be sized
*/
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
#endif
/**
* \def BIT
*
* \brief Return unsigned integer constant with a single bit set.
*
* \param idx Bit index to be set (0 is LSB)
*/
#ifndef BIT
#define BIT(idx) (1uLL << (idx))
#endif
/**
* \def USED
*
* Suppress unused variable warnings for `x`.
*
* \param x Identifier to mark as used
*/
#ifndef USED
#define USED(x) ((void) (x))
#endif
/**
* \def EDN_LE
*
* \brief Constant identifying little-endian byte ordering (LSB first).
*
* \def EDN_BE
*
* \brief Constant identifying big-endian byte ordering (MSB first).
*
* \def EDN_NATIVE
*
* \brief Constant identifying native byte ordering,
* equals either `EDN_BE` or `EDN_LE`.
*/
#ifdef _WIN32
#define EDN_LE 0
#define EDN_BE 1
#define EDN_NATIVE EDN_LE
#else
#define EDN_LE __ORDER_LITTLE_ENDIAN__
#define EDN_BE __ORDER_BIG_ENDIAN__
#define EDN_NATIVE __BYTE_ORDER__
#endif
#if EDN_NATIVE != EDN_LE && EDN_NATIVE != EDN_BE
#error "Unsupported platform endianness"
#endif
/**
* \brief Swaps bytes inside 16, 32 and 64 bits unsigned integers.
*
* Truncates `x` to the required amount of bytes, if necessary,
* and reverses each byte around. The value is then returned.
* If `x` is a compile-time constant the result is also a constant.
*
* \param x Unsigned integer value to be byte-swapped
*
* \warning `x` is evaluated multiple times.
*
* @{
* \def BSWAP16
* \def BSWAP32
* \def BSWAP64
* @}
*/
#ifndef BSWAP16
#define BSWAP16(x) ((Uint16) ( \
(((x) & 0xff00u) >> 8) | (((x) & 0x00ffu) << 8) \
))
#endif
#ifndef BSWAP32
#define BSWAP32(x) ((Uint32) ( \
(((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >> 8) | \
(((x) & 0x0000ff00u) << 8) | (((x) & 0x000000ffu) << 24) \
))
#endif
#ifndef BSWAP64
#define BSWAP64(x) ((Uint64) ( \
(((x) & 0xff00000000000000uLL) >> 56) | \
(((x) & 0x00ff000000000000uLL) >> 40) | \
(((x) & 0x0000ff0000000000uLL) >> 24) | \
(((x) & 0x000000ff00000000uLL) >> 8) | \
(((x) & 0x00000000ff000000uLL) << 8) | \
(((x) & 0x0000000000ff0000uLL) << 24) | \
(((x) & 0x000000000000ff00uLL) << 40) | \
(((x) & 0x00000000000000ffuLL) << 56) \
))
#endif
/**
* \brief Declare a fixed-size big or little endian constant.
*
* Following macros may be used to declare fixed size
* constant of a specific endianness at compile time.
* Original value is swapped if destination endianness
* is not match the host endianness. These macros are usable for
* variables as well, but functions declared in `sys/endian.h` should
* be preferred whenever possible (i.e. use these macros on variables
* only inside headers to reduce `#include`s).
*
* \param x Constant or variable, which is truncated to the required size.
*
* \return `x` as a constant or variable byte-swapped as needed.
*
* \warning `x` is evaluated multiple times.
*
* @{
* \def BE16
* \def BE32
* \def BE64
* \def LE16
* \def LE32
* \def LE64
* @}
*/
#if EDN_NATIVE == EDN_LE
#define BE16(x) BSWAP16(x)
#define BE32(x) BSWAP32(x)
#define BE64(x) BSWAP64(x)
#define LE16(x) ((Uint16) (x))
#define LE32(x) ((Uint32) (x))
#define LE64(x) ((Uint64) (x))
#else
#define BE16(x) ((Uint16) (x))
#define BE32(x) ((Uint32) (x))
#define BE64(x) ((Uint64) (x))
#define LE16(x) BSWAP16(x)
#define LE32(x) BSWAP32(x)
#define LE64(x) BSWAP64(x)
#endif
/**
* \brief Minimum, maximum and clamped to range values.
*
* \param x, y Generic comparable values
* \param a, b Clamping range, inclusive, `a` must be less than or equal to `b`
*
* \warning Arguments are evaluated more than once.
*
* @{
* \def MIN
* \def MAX
* \def CLAMP
* @}
*/
#ifndef MIN
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif
#ifndef MAX
#define MAX(x, y) (((x) < (y)) ? (y) : (x))
#endif
#ifndef CLAMP
#define CLAMP(x, a, b) MAX(a, MIN(x, b))
#endif
/**
* \def ABS
*
* \brief Absolute value of `x` using ternary `?:` operator.
*
* \param x An expression comparable with 0
*
* \warning `x` is evaluated more than once.
*/
#ifndef ABS
#define ABS(x) (((x) >= 0) ? (x) : -(x))
#endif
/**
* \def FLEX_ARRAY
*
* \brief Clarity macro to convey that a trailing array member inside
* a `struct` is an array of arbitrary size.
*/
#ifdef __cplusplus
#define FLEX_ARRAY 1
#elif defined(_MSC_VER) || defined(__GNUC__)
#define FLEX_ARRAY
#else
#define FLEX_ARRAY 1
#endif
#endif

1577
lonetix/lexer.c Normal file

File diff suppressed because it is too large Load Diff

35
lonetix/mem.c Normal file
View File

@ -0,0 +1,35 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file mem.c
*
* `MemOps` interface using `malloc()`, `realloc()` and `free()`.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "mem.h"
#include <stdlib.h>
static void *Mem_Alloc(void *allocp, size_t nbytes, void *oldp)
{
USED(allocp);
return realloc(oldp, nbytes);
}
static void Mem_Free(void *allocp, void *ptr)
{
USED(allocp);
free(ptr);
}
static const MemOps mem_stdTable = {
Mem_Alloc,
Mem_Free
};
const MemOps *const Mem_StdOps = &mem_stdTable;

198
lonetix/mem_file.c Normal file
View File

@ -0,0 +1,198 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file mem_file.c
*
* Implements operations over `MemFile` and its `StmOps` interface.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/sys.h"
#include "mem_file.h"
#include <stdlib.h>
#include <string.h>
static Judgement Stm_MemFileGrow(MemFile *stm, size_t size)
{
char *buf;
size = ALIGN(size, stm->gran);
if ((stm->flags & MEM_FILE_OWNBIT) == 0) {
// Original buffer not owned by `file`, allocate anew
buf = (char *) malloc(size);
if (!buf) {
Sys_OutOfMemory();
return NG;
}
memcpy(buf, stm->buf, stm->nbytes);
} else {
// May just reallocate the old one
buf = (char *) realloc(stm->buf, size);
if (!buf) {
Sys_OutOfMemory();
return NG;
}
}
stm->buf = buf;
stm->cap = size;
// Buffer is now owned, regardless of the previous state
stm->flags |= MEM_FILE_OWNBIT;
return OK;
}
static Sint64 Stm_OpMemFileRead(void *streamp, void *buf, size_t nbytes)
{
return Stm_MemFileRead((MemFile *) streamp, buf, nbytes);
}
static Sint64 Stm_OpMemFileWrite(void *streamp, const void *buf, size_t nbytes)
{
return Stm_MemFileWrite((MemFile *) streamp, buf, nbytes);
}
static Sint64 Stm_OpMemFileSeek(void *streamp, Sint64 off, SeekMode whence)
{
return Stm_MemFileSeek((MemFile *) streamp, off, whence);
}
static Sint64 Stm_OpMemFileTell(void *streamp)
{
return ((MemFile *) streamp)->pos;
}
static Judgement Stm_OpMemFileFinish(void *streamp)
{
USED(streamp);
return OK; // NOP
}
static void Stm_OpMemFileClose(void *streamp)
{
Stm_MemFileClose((MemFile *) streamp);
}
static const StmOps mem_stmOps = {
Stm_OpMemFileRead,
Stm_OpMemFileWrite,
Stm_OpMemFileSeek,
Stm_OpMemFileTell,
Stm_OpMemFileFinish,
Stm_OpMemFileClose
};
static const StmOps mem_ncStmOps = {
Stm_OpMemFileRead,
Stm_OpMemFileWrite,
Stm_OpMemFileSeek,
Stm_OpMemFileTell,
Stm_OpMemFileFinish,
NULL
};
const StmOps *const Stm_MemFileOps = &mem_stmOps;
const StmOps *const Stm_NcMemFileOps = &mem_ncStmOps;
void Stm_InitMemFile(MemFile *stm, size_t gran, unsigned flags)
{
memset(stm, 0, sizeof(*stm));
stm->gran = gran;
flags &= ~MEM_FILE_NOGROWBIT;
stm->flags = flags | MEM_FILE_WRBIT;
}
void Stm_MemFileFromBuf(MemFile *stm,
void *buf,
size_t nbytes,
size_t gran,
unsigned flags)
{
memset(stm, 0, sizeof(*stm));
stm->buf = (char *) buf;
stm->cap = nbytes;
stm->gran = gran;
stm->flags = flags | MEM_FILE_WRBIT;
}
void Stm_RoMemFileFromBuf(MemFile *stm, const void *buf, size_t nbytes)
{
memset(stm, 0, sizeof(*stm));
stm->buf = (char *) buf; // safe, MEM_FILE_RDBIT
stm->nbytes = nbytes;
stm->flags = MEM_FILE_RDBIT | MEM_FILE_NOGROWBIT;
}
Sint64 Stm_MemFileRead(MemFile *stm, void *buf, size_t nbytes)
{
if ((stm->flags & MEM_FILE_RDBIT) == 0)
return -1;
size_t nleft = stm->nbytes - stm->pos;
if (nbytes > nleft)
nbytes = nleft;
memcpy(buf, &stm->buf[stm->pos], nbytes);
stm->pos += nbytes;
return nbytes;
}
Sint64 Stm_MemFileWrite(MemFile *stm, const void *buf, size_t nbytes)
{
if ((stm->flags & MEM_FILE_WRBIT) == 0)
return -1;
size_t navail = stm->cap - stm->nbytes;
size_t nreq = nbytes + 1; // for trailing '\0'
if (navail < nreq) {
if ((stm->flags & MEM_FILE_NOGROWBIT) == 0) {
// grow buffer
if (Stm_MemFileGrow(stm, nreq) != OK)
return -1;
} else
nbytes = navail; // short write
}
memcpy(&stm->buf[stm->pos], buf, nbytes);
stm->pos += nbytes;
stm->nbytes += nbytes;
if (stm->pos == stm->nbytes)
stm->buf[stm->pos] = '\0'; // always NUL-terminate
return nbytes;
}
Sint64 Stm_MemFileSeek(MemFile *stm, Sint64 off, SeekMode whence)
{
switch (whence) {
case SK_SET:
break;
case SK_CUR:
off += stm->pos;
break;
case SK_END:
off += stm->nbytes;
break;
default: return -1;
}
// Make sure cursor isn't set out of bounds
stm->pos = CLAMP(off, 0, (Sint64) stm->nbytes);
return stm->pos;
}
void Stm_MemFileClose(MemFile *stm)
{
if (stm->flags & MEM_FILE_OWNBIT)
free(stm->buf);
}

561
lonetix/numlib_atof.c Normal file
View File

@ -0,0 +1,561 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file numlib_atof.c
*
* Implements ASCII to float conversion.
*
* \copyright The Plan9 Authors
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* Algorithm based on Plan9 strtod(), licensed under LUCENT PUBLIC LICENSE:
* Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights Reserved.
*
* Original source code available at: https://9p.io/sources/plan9/
*/
#include "numlib.h"
#include <math.h>
#include <string.h>
/*
* This routine will convert to arbitrary precision
* floating point entirely in multi-precision fixed.
* The answer is the closest floating point number to
* the given decimal number. Exactly half way are
* rounded ala IEEE rules.
* Method is to scale input decimal between .500 and .999...
* with external power of 2, then binary search for the
* closest mantissa to this decimal number.
* Nmant is is the required precision. (53 for ieee dp)
* Nbits is the max number of bits/word. (must be <= 28)
* Prec is calculated - the number of words of fixed mantissa.
*/
#define Nbits 28 // bits safely represented in an unsigned long
#define Nmant 53 // bits of precision required
#define Bias 1022
#define Prec ((Nmant+Nbits+1) / Nbits) // words of Nbits each to represent mantissa
#define Sigbit (1uLL << (Prec*Nbits-Nmant)) // first significant bit of Prec-th word
#define Ndig 1500
#define One (1uL << Nbits)
#define Half (One >> 1)
#define Maxe 310
#define Fsign BIT(0) // found -
#define Fesign BIT(1) // found e-
#define Fdpoint BIT(2) // found .
enum {
S0 = 0, // _ _S0 +S1 #S2 .S3
S1, // _+ #S2 .S3
S2, // _+# #S2 .S4 eS5
S3, // _+. #S4
S4, // _+#.# #S4 eS5
S5, // _+#.#e +S6 #S7
S6, // _+#.#e+ #S7
S7, // _+#.#e+# #S7
};
typedef struct {
int bp;
int siz;
const char *cmp;
} Tab;
static unsigned long umuldiv(unsigned long a, unsigned long b, unsigned long c)
{
return ((unsigned long long) a * (unsigned long long) b) / c;
}
static void frnorm(unsigned long *f)
{
int i, c;
c = 0;
for (i = Prec-1; i > 0; i--) {
f[i] += c;
c = f[i] >> Nbits;
f[i] &= One-1;
}
f[0] += c;
}
static int fpcmp(char *a, unsigned long *f)
{
unsigned long tf[Prec];
int i, d, c;
for (i = 0; i < Prec; i++)
tf[i] = f[i];
while (TRUE) {
// tf *= 10
for (i = 0; i < Prec; i++)
tf[i] = tf[i] * 10;
frnorm(tf);
d = (tf[0] >> Nbits) + '0';
tf[0] &= One-1;
// Compare next digit
c = *a;
if (c == 0) {
if ('0' < d)
return -1;
if (tf[0] != 0)
goto cont;
for (i = 1; i < Prec; i++) {
if (tf[i] != 0)
goto cont;
}
return 0;
}
if (c > d)
return +1;
if (c < d)
return -1;
a++;
cont:;
}
}
static void _divby(char *a, int *na, int b)
{
int n, c;
char *p;
p = a;
n = 0;
while (n >> b == 0) {
c = *a++;
if (c == 0) {
while (n) {
c = n * 10;
if (c>>b)
break;
n = c;
}
goto xx;
}
n = n*10 + c-'0';
(*na)--;
}
while (TRUE) {
c = n >> b;
n -= c << b;
*p++ = c + '0';
c = *a++;
if (c == 0)
break;
n = n*10 + c-'0';
}
(*na)++;
xx:
while (n) {
n = n * 10;
c = n >> b;
n -= c << b;
*p++ = c + '0';
(*na)++;
}
*p = '\0';
}
static void divby(char *a, int *na, int b)
{
while (b > 9) {
_divby(a, na, 9);
a[*na] = 0;
b -= 9;
}
if (b > 0)
_divby(a, na, b);
}
static const Tab tab1[] = {
{ 1, 0, "" },
{ 3, 1, "7" },
{ 6, 2, "63" },
{ 9, 3, "511" },
{ 13, 4, "8191" },
{ 16, 5, "65535" },
{ 19, 6, "524287" },
{ 23, 7, "8388607" },
{ 26, 8, "67108863" },
{ 27, 9, "134217727" }
};
static void divascii(char *a, int *na, int *dp, int *bp)
{
int b, d;
const Tab *t;
d = *dp;
if (d >= (int) ARRAY_SIZE(tab1))
d = ARRAY_SIZE(tab1)-1;
t = tab1 + d;
b = t->bp;
if (memcmp(a, t->cmp, t->siz) > 0)
d--;
*dp -= d;
*bp += b;
divby(a, na, b);
}
static void mulby(char *a, char *p, char *q, int b)
{
int n, c;
n = 0;
*p = 0;
while (TRUE) {
q--;
if (q < a)
break;
c = *q - '0';
c = (c << b) + n;
n = c/10;
c -= n*10;
p--;
*p = c + '0';
}
while (n) {
c = n;
n = c/10;
c -= n*10;
p--;
*p = c + '0';
}
}
static const Tab tab2[] = {
{ 1, 1, "" }, // dp = 0-0
{ 3, 3, "125" },
{ 6, 5, "15625" },
{ 9, 7, "1953125" },
{ 13, 10, "1220703125" },
{ 16, 12, "152587890625" },
{ 19, 14, "19073486328125" },
{ 23, 17, "11920928955078125" },
{ 26, 19, "1490116119384765625" },
{ 27, 19, "7450580596923828125" } // dp 8-9
};
static void mulascii(char *a, int *na, int *dp, int *bp)
{
char *p;
int d, b;
const Tab *t;
d = -*dp;
if (d >= (int) ARRAY_SIZE(tab2))
d = ARRAY_SIZE(tab2) - 1;
t = tab2 + d;
b = t->bp;
if (memcmp(a, t->cmp, t->siz) < 0)
d--;
p = a + *na;
*bp -= b;
*dp += d;
*na += d;
mulby(a, p+d, p, b);
}
static Boolean xcmp(const char *a, const char *b)
{
int c1, c2;
while ((c1 = *b++) != '\0') {
c2 = *a++;
// Make c2 lowercase
c2 |= ((c2 >= 'A' && c2 <= 'Z') << 5);
if (c1 != c2)
return FALSE;
}
return TRUE;
}
double Atof(const char *s, char **endp, NumConvRet *outcome)
{
NumConvRet result;
unsigned long low[Prec], hig[Prec], mid[Prec];
unsigned long num, den;
const char *sp;
double d;
int ona, bp, c, i;
char a[Ndig];
unsigned flag = 0; // Fsign, Fesign, Fdpoint
int na = 0; // number of digits of a[]
int dp = 0; // na of decimal point
int ex = 0; // exponent
int state = S0;
if (!outcome)
outcome = &result;
*outcome = NCVENOERR; // assume conversion is successful
for (sp = s;; sp++) {
c = *sp;
if (c >= '0' && c <= '9') {
switch (state) {
default:
UNREACHABLE;
break;
case S0:
case S1:
case S2:
state = S2;
break;
case S3:
case S4:
state = S4;
break;
case S5:
case S6:
case S7:
state = S7;
ex = ex*10 + (c-'0');
continue;
}
if (na == 0 && c == '0') {
dp--;
continue;
}
if (na < Ndig-50)
a[na++] = c;
continue;
}
switch (c) {
case '-':
if (state == S0)
flag |= Fsign;
else
flag |= Fesign;
// FALLTHROUGH
case '+':
if (state == S0)
state = S1;
else if (state == S5)
state = S6;
else
break; // syntax
continue;
case '.':
flag |= Fdpoint;
dp = na;
if (state == S0 || state == S1) {
state = S3;
continue;
}
if (state == S2) {
state = S4;
continue;
}
break;
case 'e':
case 'E':
if (state == S2 || state == S4) {
state = S5;
continue;
}
break;
default:
break;
}
break;
}
// Clean up return char-pointer
switch (state) {
case S0:
if (xcmp(sp, "nan")) {
if (endp)
*endp = (char *) sp + 3;
goto retnan;
}
// FALLTHROUGH
case S1:
if (xcmp(sp, "infinity")) {
if (endp)
*endp = (char *) sp + 8;
goto retinf;
}
if (xcmp(sp, "inf")) {
if (endp)
*endp = (char *) sp + 3;
goto retinf;
}
// FALLTHROUGH
case S3:
if (endp)
*endp = (char *) sp;
*outcome = NCVENOTHING;
goto ret0; // no digits found
case S6:
sp--; // back over +-
// FALLTHROUGH
case S5:
sp--; // back over e
break;
}
if (endp)
*endp = (char *) sp;
if (flag & Fdpoint) {
while (na > 0 && a[na-1] == '0')
na--;
}
if (na == 0)
goto ret0; // zero
a[na] = 0;
if (!(flag & Fdpoint))
dp = na;
if (flag & Fesign)
ex = -ex;
dp += ex;
if (dp < -Maxe-Nmant/3) // actually -Nmant*log(2)/log(10), but Nmant/3 close enough
goto ret0; // underflow by exp
else if (dp > +Maxe)
goto retinf; // overflow by exp
// Normalize the decimal ascii number
// to range .[5-9][0-9]* e0
bp = 0; // binary exponent
while (dp > 0)
divascii(a, &na, &dp, &bp);
while (dp < 0 || a[0] < '5')
mulascii(a, &na, &dp, &bp);
a[na] = '\0';
// Very small numbers are represented using
// bp = -Bias+1. adjust accordingly.
if (bp < -Bias+1) {
ona = na;
divby(a, &na, -bp-Bias+1);
if (na < ona) {
memmove(a+ona-na, a, na);
memset(a, '0', ona-na);
na = ona;
}
a[na] = '\0';
bp = -Bias+1;
}
// Close approx by naive conversion
num = 0;
den = 1;
for (i = 0; i < 9 && (c = a[i]) != '\0'; i++) {
num = num*10 + (c - '0');
den *= 10;
}
low[0] = umuldiv(num, One, den);
hig[0] = umuldiv(num+1, One, den);
for (i = 1; i < Prec; i++) {
low[i] = 0;
hig[i] = One-1;
}
// Binary search for closest mantissa
while (TRUE) {
// mid = (hig + low) / 2
c = 0;
for (i = 0; i < Prec; i++) {
mid[i] = hig[i] + low[i];
if (c)
mid[i] += One;
c = mid[i] & 1;
mid[i] >>= 1;
}
frnorm(mid);
// Compare
c = fpcmp(a, mid);
if (c > 0) {
c = 1;
for (i = 0; i < Prec; i++) {
if (low[i] != mid[i]) {
c = 0;
low[i] = mid[i];
}
}
if (c)
break; // between mid and hig
continue;
}
if (c < 0) {
for (i = 0; i < Prec; i++)
hig[i] = mid[i];
continue;
}
// Only hard part is if even/odd roundings wants to go up
c = mid[Prec-1] & (Sigbit-1);
if (c == Sigbit/2 && (mid[Prec-1] & Sigbit) == 0)
mid[Prec-1] -= c;
break; // exactly mid
}
// Normal rounding applies
c = mid[Prec-1] & (Sigbit-1);
mid[Prec-1] -= c;
if (c >= (int) Sigbit/2) {
mid[Prec-1] += Sigbit;
frnorm(mid);
}
d = 0;
for (i = 0; i < Prec; i++)
d = d*One + mid[i];
if (flag & Fsign)
d = -d;
d = ldexp(d, bp - Prec*Nbits);
return d;
ret0:
return 0;
retnan:
return NAN;
retinf:
return (flag & Fsign) ? -INFINITY : INFINITY;
}

213
lonetix/numlib_atoi.c Normal file
View File

@ -0,0 +1,213 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file numlib_atoi.c
*
* Implements ASCII to integer conversion.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "numlib.h"
#include <assert.h>
#include <limits.h>
static int digitval(char ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
else if (ch >= 'A' && ch <= 'Z')
return ch - 'A' + 10;
else if (ch >= 'a' && ch <= 'z')
return ch - 'a' + 10;
else
return -1;
}
static unsigned long long ParseInt(const char *s,
char **endptr,
unsigned base,
int *signp,
NumConvRet *outcome)
{
int d;
// Single optional + or -
char c;
int sign;
const char *startptr = s;
unsigned long long u = 0;
sign = 1;
if (base > 36) {
*outcome = NCVEBADBASE;
goto done;
}
c = *s;
if (c == '-' || c == '+') {
sign -= (c == '-') << 1;
s++;
}
// Determine base
if (base == 0) {
if (s[0] == '0') {
if (s[1] == 'x' || s[1] == 'X') {
s += 2;
base = 16;
} else if (s[1] == 'b' || s[1] == 'B') {
s += 2;
base = 2;
} else {
s++;
base = 8;
}
} else {
base = 10;
}
} else if (base == 16) {
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
s += 2;
} else if (base == 2) {
if (s[0] == '0' && (s[1] == 'b' || s[1] == 'B'))
s += 2;
}
// Convert numeric value
*outcome = NCVENOERR; // assume successful conversion
while ((d = digitval(*s)) >= 0 && d < (int) base) {
unsigned long long v = u * base + d;
if (u > v) {
// Overflow
v = ULLONG_MAX;
*outcome = NCVEOVERFLOW;
}
u = v;
s++;
}
if (s == startptr)
*outcome = NCVENOTHING;
done:
if (endptr)
*endptr = (char *) s;
*signp = sign;
return u;
}
unsigned long long Atoull(const char *s,
char **endp,
unsigned base,
NumConvRet *outcome)
{
NumConvRet result;
if (!outcome)
outcome = &result;
int sign;
unsigned long long mag = ParseInt(s, endp, base, &sign, outcome);
if (sign < 0)
*outcome = NCVEUNDERFLOW;
return sign * mag;
}
unsigned long Atoul(const char *s,
char **endp,
unsigned base,
NumConvRet *outcome)
{
NumConvRet result;
if (!outcome)
outcome = &result;
int sign;
unsigned long long mag = ParseInt(s, endp, base, &sign, outcome);
if (mag > ULONG_MAX)
*outcome = NCVEOVERFLOW;
if (sign < 0)
*outcome = NCVEUNDERFLOW;
return sign * mag;
}
unsigned Atou(const char *s, char **endp, unsigned base, NumConvRet *outcome)
{
NumConvRet result;
if (!outcome)
outcome = &result;
int sign;
unsigned long long mag = ParseInt(s, endp, base, &sign, outcome);
if (mag > UINT_MAX)
*outcome = NCVEOVERFLOW;
if (sign < 0)
*outcome = NCVEUNDERFLOW;
return sign * mag;
}
long long Atoll(const char *s, char **endp, unsigned base, NumConvRet *outcome)
{
NumConvRet result;
if (!outcome)
outcome = &result;
int sign;
unsigned long long mag = ParseInt(s, endp, base, &sign, outcome);
if (sign > 0 && mag > LLONG_MAX) {
*outcome = NCVEOVERFLOW;
return LLONG_MAX;
}
if (sign < 0 && mag > -((unsigned long long) LLONG_MIN)) {
*outcome = NCVEUNDERFLOW;
return LLONG_MIN;
}
return sign * mag;
}
long Atol(const char *s, char **endp, unsigned base, NumConvRet *outcome)
{
NumConvRet result;
if (!outcome)
outcome = &result;
int sign;
unsigned long long mag = ParseInt(s, endp, base, &sign, outcome);
if (sign > 0 && mag > LONG_MAX) {
*outcome = NCVEOVERFLOW;
return LONG_MAX;
}
if (sign < 0 && mag > -((unsigned long) LONG_MIN)) {
*outcome = NCVEUNDERFLOW;
return LONG_MIN;
}
return sign * mag;
}
int Atoi(const char *s, char **endp, unsigned base, NumConvRet *outcome)
{
NumConvRet result;
if (!outcome)
outcome = &result;
int sign;
unsigned long long mag = ParseInt(s, endp, base, &sign, outcome);
if (sign > 0 && mag > INT_MAX) {
*outcome = NCVEOVERFLOW;
return INT_MAX;
}
if (sign < 0 && mag > -((unsigned) INT_MIN)) {
*outcome = NCVEUNDERFLOW;
return INT_MIN;
}
return sign * mag;
}

96
lonetix/numlib_fltp.h Normal file
View File

@ -0,0 +1,96 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file numlib_fltp.h
*
* Lookup table and function to `#include`d in `numlib_ftoa.c`.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#define npowers 87
#define steppowers 8
#define firstpower -348 // 10 ^ -348
#define expmax -32
#define expmin -60
typedef struct {
Uint64 frac;
int exp;
} Fp;
static const Fp powers_ten[] = {
{ 18054884314459144840uLL, -1220 }, { 13451937075301367670uLL, -1193 },
{ 10022474136428063862uLL, -1166 }, { 14934650266808366570uLL, -1140 },
{ 11127181549972568877uLL, -1113 }, { 16580792590934885855uLL, -1087 },
{ 12353653155963782858uLL, -1060 }, { 18408377700990114895uLL, -1034 },
{ 13715310171984221708uLL, -1007 }, { 10218702384817765436uLL, -980 },
{ 15227053142812498563uLL, -954 }, { 11345038669416679861uLL, -927 },
{ 16905424996341287883uLL, -901 }, { 12595523146049147757uLL, -874 },
{ 9384396036005875287uLL, -847 }, { 13983839803942852151uLL, -821 },
{ 10418772551374772303uLL, -794 }, { 15525180923007089351uLL, -768 },
{ 11567161174868858868uLL, -741 }, { 17236413322193710309uLL, -715 },
{ 12842128665889583758uLL, -688 }, { 9568131466127621947uLL, -661 },
{ 14257626930069360058uLL, -635 }, { 10622759856335341974uLL, -608 },
{ 15829145694278690180uLL, -582 }, { 11793632577567316726uLL, -555 },
{ 17573882009934360870uLL, -529 }, { 13093562431584567480uLL, -502 },
{ 9755464219737475723uLL, -475 }, { 14536774485912137811uLL, -449 },
{ 10830740992659433045uLL, -422 }, { 16139061738043178685uLL, -396 },
{ 12024538023802026127uLL, -369 }, { 17917957937422433684uLL, -343 },
{ 13349918974505688015uLL, -316 }, { 9946464728195732843uLL, -289 },
{ 14821387422376473014uLL, -263 }, { 11042794154864902060uLL, -236 },
{ 16455045573212060422uLL, -210 }, { 12259964326927110867uLL, -183 },
{ 18268770466636286478uLL, -157 }, { 13611294676837538539uLL, -130 },
{ 10141204801825835212uLL, -103 }, { 15111572745182864684uLL, -77 },
{ 11258999068426240000uLL, -50 }, { 16777216000000000000uLL, -24 },
{ 12500000000000000000uLL, 3 }, { 9313225746154785156uLL, 30 },
{ 13877787807814456755uLL, 56 }, { 10339757656912845936uLL, 83 },
{ 15407439555097886824uLL, 109 }, { 11479437019748901445uLL, 136 },
{ 17105694144590052135uLL, 162 }, { 12744735289059618216uLL, 189 },
{ 9495567745759798747uLL, 216 }, { 14149498560666738074uLL, 242 },
{ 10542197943230523224uLL, 269 }, { 15709099088952724970uLL, 295 },
{ 11704190886730495818uLL, 322 }, { 17440603504673385349uLL, 348 },
{ 12994262207056124023uLL, 375 }, { 9681479787123295682uLL, 402 },
{ 14426529090290212157uLL, 428 }, { 10748601772107342003uLL, 455 },
{ 16016664761464807395uLL, 481 }, { 11933345169920330789uLL, 508 },
{ 17782069995880619868uLL, 534 }, { 13248674568444952270uLL, 561 },
{ 9871031767461413346uLL, 588 }, { 14708983551653345445uLL, 614 },
{ 10959046745042015199uLL, 641 }, { 16330252207878254650uLL, 667 },
{ 12166986024289022870uLL, 694 }, { 18130221999122236476uLL, 720 },
{ 13508068024458167312uLL, 747 }, { 10064294952495520794uLL, 774 },
{ 14996968138956309548uLL, 800 }, { 11173611982879273257uLL, 827 },
{ 16649979327439178909uLL, 853 }, { 12405201291620119593uLL, 880 },
{ 9242595204427927429uLL, 907 }, { 13772540099066387757uLL, 933 },
{ 10261342003245940623uLL, 960 }, { 15290591125556738113uLL, 986 },
{ 11392378155556871081uLL, 1013 }, { 16975966327722178521uLL, 1039 },
{ 12648080533535911531uLL, 1066 }
};
static Fp find_cachedpow10(int exp, int *k)
{
const double one_log_ten = 0.30102999566398114;
int approx = -(exp + npowers) * one_log_ten;
int idx = (approx - firstpower) / steppowers;
while (TRUE) {
int current = exp + powers_ten[idx].exp + 64;
if (current < expmin) {
idx++;
continue;
}
if (current > expmax) {
idx--;
continue;
}
*k = (firstpower + idx * steppowers);
break;
}
return powers_ten[idx];
}

343
lonetix/numlib_ftoa.c Normal file
View File

@ -0,0 +1,343 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file numlib_ftoa.c
*
* Float to ASCII conversion.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* This code is based on Florian Loitsch paper
* **Printing Floating-Point Numbers Quickly and Accurately with Integers**,
* algorithm used is Grisu2.
*
* \see [Printing Floating-Point Numbers Quickly and Accurately with Integers](https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf)
*/
#include "numlib.h"
#include "sys/endian.h"
#include "numlib_fltp.h"
#include <string.h>
#define fracmask 0x000fffffffffffffuLL
#define expmask 0x7ff0000000000000uLL
#define hiddenbit 0x0010000000000000uLL
#define signmask 0x8000000000000000uLL
#define expbias (1023 + 52)
static const Uint64 tens[] = {
10000000000000000000uLL, 1000000000000000000uLL, 100000000000000000uLL,
10000000000000000uLL, 1000000000000000uLL, 100000000000000uLL,
10000000000000uLL, 1000000000000uLL, 100000000000uLL,
10000000000uLL, 1000000000uLL, 100000000uLL,
10000000uLL, 1000000uLL, 100000uLL,
10000uLL, 1000uLL, 100uLL,
10uLL, 1uLL
};
static Uint64 get_dbits(double d)
{
Doublebits dbl;
dbl.f64 = d;
return dbl.bits;
}
static Fp build_fp(double d)
{
Uint64 bits = get_dbits(d);
Fp fp;
fp.frac = bits & fracmask;
fp.exp = (bits & expmask) >> 52;
if(fp.exp) {
fp.frac += hiddenbit;
fp.exp -= expbias;
} else {
fp.exp = -expbias + 1;
}
return fp;
}
static void normalize(Fp *fp)
{
while ((fp->frac & hiddenbit) == 0) {
fp->frac <<= 1;
fp->exp--;
}
int shift = 64 - 52 - 1;
fp->frac <<= shift;
fp->exp -= shift;
}
static void get_normalized_boundaries(Fp *fp, Fp *lower, Fp *upper)
{
upper->frac = (fp->frac << 1) + 1;
upper->exp = fp->exp - 1;
while ((upper->frac & (hiddenbit << 1)) == 0) {
upper->frac <<= 1;
upper->exp--;
}
int u_shift = 64 - 52 - 2;
upper->frac <<= u_shift;
upper->exp = upper->exp - u_shift;
int l_shift = fp->frac == hiddenbit ? 2 : 1;
lower->frac = (fp->frac << l_shift) - 1;
lower->exp = fp->exp - l_shift;
lower->frac <<= lower->exp - upper->exp;
lower->exp = upper->exp;
}
static Fp multiply(Fp *a, Fp *b)
{
const Uint64 lomask = 0x00000000FFFFFFFFuLL;
Uint64 ah_bl = (a->frac >> 32) * (b->frac & lomask);
Uint64 al_bh = (a->frac & lomask) * (b->frac >> 32);
Uint64 al_bl = (a->frac & lomask) * (b->frac & lomask);
Uint64 ah_bh = (a->frac >> 32) * (b->frac >> 32);
Uint64 tmp = (ah_bl & lomask) + (al_bh & lomask) + (al_bl >> 32);
// round up
tmp += 1U << 31;
Fp fp = {
ah_bh + (ah_bl >> 32) + (al_bh >> 32) + (tmp >> 32),
a->exp + b->exp + 64
};
return fp;
}
static void round_digit(char *digits,
int ndigits,
Uint64 delta,
Uint64 rem,
Uint64 kappa,
Uint64 frac)
{
while ((rem < frac && delta - rem >= kappa) &&
(rem + kappa < frac || frac - rem > rem + kappa - frac)) {
digits[ndigits - 1]--;
rem += kappa;
}
}
static int generate_digits(Fp* fp, Fp *upper, Fp* lower, char *digits, int *K)
{
Uint64 wfrac = upper->frac - fp->frac;
Uint64 delta = upper->frac - lower->frac;
Fp one;
one.frac = 1uLL << -upper->exp;
one.exp = upper->exp;
Uint64 part1 = upper->frac >> -one.exp;
Uint64 part2 = upper->frac & (one.frac - 1);
int idx = 0, kappa = 10;
// 1000000000
for (const Uint64 *divp = tens + 10; kappa > 0; divp++) {
Uint64 div = *divp;
unsigned digit = part1 / div;
if (digit || idx)
digits[idx++] = digit + '0';
part1 -= digit * div;
kappa--;
Uint64 tmp = (part1 <<-one.exp) + part2;
if (tmp <= delta) {
*K += kappa;
round_digit(digits, idx, delta, tmp, div << -one.exp, wfrac);
return idx;
}
}
// 10
const Uint64 *unit = tens + 18;
while (TRUE) {
part2 *= 10;
delta *= 10;
kappa--;
unsigned digit = part2 >> -one.exp;
if (digit || idx)
digits[idx++] = digit + '0';
part2 &= one.frac - 1;
if (part2 < delta) {
*K += kappa;
round_digit(digits, idx, delta, part2, one.frac, wfrac * *unit);
break;
}
unit--;
}
return idx;
}
static int grisu2(double d, char *digits, int *K)
{
Fp w = build_fp(d);
Fp lower, upper;
get_normalized_boundaries(&w, &lower, &upper);
normalize(&w);
int k;
Fp cp = find_cachedpow10(upper.exp, &k);
w = multiply(&w, &cp);
upper = multiply(&upper, &cp);
lower = multiply(&lower, &cp);
lower.frac++;
upper.frac--;
*K = -k;
return generate_digits(&w, &upper, &lower, digits, K);
}
static int emit_digits(char *digits,
int ndigits,
char *dest,
int K,
Boolean neg)
{
int exp = ABS(K + ndigits - 1);
// Write plain integer
if (K >= 0 && (exp < (ndigits + 7))) {
memcpy(dest, digits, ndigits);
memset(dest + ndigits, '0', K);
return ndigits + K;
}
// Write decimal w/o scientific notation
if (K < 0 && (K > -7 || exp < 4)) {
int offset = ndigits - ABS(K);
if (offset <= 0) {
// fp < 1.0 -> write leading zero
offset = -offset;
dest[0] = '0';
dest[1] = '.';
memset(dest + 2, '0', offset);
memcpy(dest + offset + 2, digits, ndigits);
return ndigits + 2 + offset;
} else {
// fp > 1.0
memcpy(dest, digits, offset);
dest[offset] = '.';
memcpy(dest + offset + 1, digits + offset, ndigits - offset);
return ndigits + 1;
}
}
// write decimal w/ scientific notation
ndigits = MIN(ndigits, 18 - neg);
int idx = 0;
dest[idx++] = digits[0];
if (ndigits > 1) {
dest[idx++] = '.';
memcpy(dest + idx, digits + 1, ndigits - 1);
idx += ndigits - 1;
}
dest[idx++] = 'e';
char sign = K + ndigits - 1 < 0 ? '-' : '+';
dest[idx++] = sign;
int cent = 0;
if (exp > 99) {
cent = exp / 100;
dest[idx++] = cent + '0';
exp -= cent * 100;
}
if (exp > 9) {
int dec = exp / 10;
dest[idx++] = dec + '0';
exp -= dec * 10;
} else if (cent) {
dest[idx++] = '0';
}
dest[idx++] = exp % 10 + '0';
return idx;
}
static int filter_special(double fp, char *dest)
{
if (fp == 0.0) {
dest[0] = '0';
return 1;
}
Uint64 bits = get_dbits(fp);
Boolean nan = (bits & expmask) == expmask;
if (!nan)
return 0;
if (bits & fracmask) {
dest[0] = 'n'; dest[1] = 'a'; dest[2] = 'n';
} else {
dest[0] = 'i'; dest[1] = 'n'; dest[2] = 'f';
}
return 3;
}
char *Ftoa(double d, char *dest)
{
char digits[18];
int str_len = 0;
Boolean neg = FALSE;
if (get_dbits(d) & signmask) {
dest[0] = '-';
str_len++;
neg = TRUE;
}
int spec = filter_special(d, dest + str_len);
str_len += spec;
if (spec == 0) {
int K = 0;
int ndigits = grisu2(d, digits, &K);
str_len += emit_digits(digits, ndigits, dest + str_len, K, neg);
}
dest[str_len] = '\0';
return dest + str_len;
}

80
lonetix/numlib_itoa.c Normal file
View File

@ -0,0 +1,80 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file numlib_itoa.c
*
* Integer to ASCII conversion.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "numlib.h"
#include <string.h>
// XXX(micia): benchmark memcpy()-less version, something with rep stos
// amount of data copied over is very small, so it would probably pay off
// leaving memcpy() out
char *Utoa(unsigned long long x, char *dest)
{
char buf[DIGS(x) + 1];
size_t n;
char *p = buf + sizeof(buf);
*--p = '\0';
do {
*--p = '0' + (x % 10);
x /= 10;
} while (x != 0);
n = (buf + sizeof(buf)) - p;
memcpy(dest, p, n);
return dest + n - 1;
}
char *Itoa(long long x, char *dest)
{
char buf[1 + DIGS(x) + 1];
unsigned long long ax; // NOTE keep this unsigned!
size_t n;
char *p = buf + sizeof(buf);
ax = ABS(x); // also handles LLONG_MIN, ax being unsigned
// NOTE: this is still a signed overflow, which is technically
// undefined in standard C
*--p = '\0';
do {
*--p = '0' + (ax % 10);
ax /= 10;
} while (ax != 0);
if (x < 0)
*--p = '-';
n = (buf + sizeof(buf)) - p;
memcpy(dest, p, n);
return dest + n - 1;
}
char *Xtoa(unsigned long long x, char *dest)
{
char buf[XDIGS(x) + 1];
size_t n;
char *p = buf + sizeof(buf);
*--p = '\0';
do {
*--p = "0123456789abcdef"[x & 0xf];
x >>= 4;
} while (x != 0);
n = (buf + sizeof(buf)) - p;
memcpy(dest, p, n);
return dest + n - 1;
}

67
lonetix/stm.c Normal file
View File

@ -0,0 +1,67 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file stm.c
*
* Common `StmOps`implementation over `Fildes`.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "stm.h"
#include "sys/fs.h"
#define STM_TOFILDES(fd) ((Fildes) ((Sintptr) (fd)))
static Sint64 Stm_Fread(void *streamp, void *buf, size_t nbytes)
{
return Sys_Fread(STM_TOFILDES(streamp), buf, nbytes);
}
static Sint64 Stm_Fwrite(void *streamp, const void *buf, size_t nbytes)
{
return Sys_Fwrite(STM_TOFILDES(streamp), buf, nbytes);
}
static Sint64 Stm_Fseek(void *streamp, Sint64 off, SeekMode whence)
{
return Sys_Fseek(STM_TOFILDES(streamp), off, whence);
}
static Sint64 Stm_Ftell(void *streamp)
{
return Sys_Ftell(STM_TOFILDES(streamp));
}
static Judgement Stm_Fsync(void *streamp)
{
return Sys_Fsync(STM_TOFILDES(streamp), /*fullSync=*/TRUE);
}
static void Stm_Fclose(void *streamp)
{
Sys_Fclose(STM_TOFILDES(streamp));
}
static const StmOps fildes_ops = {
Stm_Fread,
Stm_Fwrite,
Stm_Fseek,
Stm_Ftell,
Stm_Fsync,
Stm_Fclose
};
static const StmOps nc_fildes_ops = {
Stm_Fread,
Stm_Fwrite,
Stm_Fseek,
Stm_Ftell,
Stm_Fsync,
NULL
};
const StmOps *const Stm_FildesOps = &fildes_ops;
const StmOps *const Stm_NcFildesOps = &nc_fildes_ops;

40
lonetix/stricmp.c Normal file
View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file stricmp.c
*
* Implements `Df_stricmp()`.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "strlib.h"
int Df_stricmp(const char *a, const char *b)
{
int c1, c2;
do {
c1 = *a++;
c2 = *b++;
// Avoid converting case unless char differ
int d = c1 - c2;
while (d != 0) {
if (c1 <= 'Z' && c1 >= 'A') {
d += ('a' - 'A');
if (d == 0)
break;
}
if (c2 <= 'Z' && c2 >= 'A') {
d -= ('a' - 'A');
if (d == 0)
break;
}
return ((d >= 0) << 1) - 1;
}
} while (c1 != '\0');
return 0; // strings are equal
}

81
lonetix/strncatz.c Normal file
View File

@ -0,0 +1,81 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file strncatz.c
*
* Implements `Df_strncatz()`.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
// THIS CODE IS DERIVED FROM OpenBSD strlcat.c
// ORIGINAL LICENSE TERMS FOLLOW:
/* $OpenBSD: strlcat.c,v 1.2 1999/06/17 16:28:58 millert Exp $ */
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// ORIGINAL LICENSE TERMS END ***
#include "strlib.h"
#include <string.h>
size_t Df_strncatz(char *dst, const char *src, size_t n)
{
char *d = dst;
const char *s = src;
size_t left = n;
size_t dlen;
// Find the end of dst and adjust bytes left but don't go past end
while (left-- != 0 && *d != '\0') d++;
dlen = d - dst;
left = n - dlen;
if (left == 0)
return dlen + strlen(s);
while (*s != '\0') {
if (left != 1) {
*d++ = *s;
left--;
}
s++;
}
*d = '\0';
return dlen + (s - src); // count does not include NUL
}

59
lonetix/strncpyz.c Normal file
View File

@ -0,0 +1,59 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file strncpyz.c
*
* Implements `Df_strncpyz()`.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
// THIS CODE IS DERIVED FROM OpenBSD strlcpy.c
// ORIGINAL LICENSE TERMS FOLLOW:
/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */
/*
* Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// ORIGINAL LICENSE TERMS END ***
#include "strlib.h"
size_t Df_strncpyz(char *dst, const char *src, size_t n)
{
const char *osrc = src;
size_t nleft = n;
// Copy as many bytes as will fit
if (nleft > 0) {
while (--nleft != 0) {
if ((*dst++ = *src++) == '\0')
break;
}
}
// Not enough room in dst, add NUL and traverse rest of src
if (nleft == 0) {
if (n > 0)
*dst = '\0'; // NUL-terminate dst
while (*src++ != '\0');
}
return src - osrc - 1; // count does not include NUL
}

42
lonetix/strnicmp.c Normal file
View File

@ -0,0 +1,42 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file strnicmp.c
*
* Implements `Df_strnicmp()`.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "strlib.h"
int Df_strnicmp(const char *a, const char *b, size_t n)
{
int c1, c2;
do {
if (!n--)
return 0; // strings are equal until end point
c1 = *a++;
c2 = *b++;
int d = c1 - c2;
while (d != 0) {
if (c1 <= 'Z' && c1 >= 'A') {
d += ('a' - 'A');
if (d == 0)
break;
}
if (c2 <= 'Z' && c2 >= 'A') {
d -= ('a' - 'A');
if (d == 0)
break;
}
return ((d >= 0) << 1) - 1;
}
} while (c1 != '\0');
return 0; // strings are equal
}

143
lonetix/sys/con_unix.c Executable file
View File

@ -0,0 +1,143 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/con_unix.c
*
* Implements Unix console Input/Output.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/con.h"
#include "sys/fs.h"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int STM_TOFILDES(void *streamp)
{
return (int) ((Sintptr) streamp);
}
static Sint64 Sys_OpConRead(void *streamp, void *buf, size_t nbytes)
{
return Sys_Fread(STM_TOFILDES(streamp), buf, nbytes);
}
static Sint64 Sys_OpConWrite(void *streamp, const void *buf, size_t nbytes)
{
return Sys_Fwrite(STM_TOFILDES(streamp), buf, nbytes);
}
static Judgement Sys_OpConFinish(void *streamp)
{
USED(streamp);
return OK; // NOP
}
static const StmOps con_stmOps = {
Sys_OpConRead,
Sys_OpConWrite,
NULL,
NULL,
Sys_OpConFinish,
NULL
};
const StmOps *const Stm_ConOps = &con_stmOps;
void Sys_Print(ConHn hn, const char *s)
{
(void) write(hn, s, strlen(s));
}
void Sys_VPrintf(ConHn hn, const char *fmt, va_list va)
{
va_list vc;
int n1, n2;
char *buf;
va_copy(vc, va);
n1 = vsnprintf(NULL, 0, fmt, vc);
va_end(vc);
if (n1 <= 0)
return;
buf = (char *) alloca(n1 + 1);
n2 = vsnprintf(buf, n1 + 1, fmt, va);
if (n2 <= 0)
return;
assert(n2 == n1);
(void) write(hn, buf, n2);
}
void Sys_Printf(ConHn hn, const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
Sys_VPrintf(hn, fmt, va);
va_end(va);
}
size_t Sys_Read(ConHn hn, char *buf, size_t n)
{
ssize_t nr = read(hn, buf, n);
if (nr < 0)
nr = 0;
return nr;
}
size_t Sys_NbRead(ConHn hn, char *buf, size_t n)
{
fd_set rfds;
struct timeval tv;
FD_ZERO(&rfds);
FD_SET(hn, &rfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(1 + hn, &rfds, NULL, NULL, &tv) != 1)
return 0;
ssize_t nr = read(hn, buf, n);
if (nr < 0)
nr = 0;
return nr;
}
Boolean Sys_IsVt100Console(ConHn hn)
{
static const char *const knownTerms[] = {
"xterm", "xterm-color", "xterm-256color",
"screen", "screen-256color", "tmux",
"tmux-256color", "rxvt-unicode", "rxvt-unicode-256color",
"linux", "cygwin"
};
const char *term = getenv("TERM");
if (!term)
return FALSE;
size_t i;
for (i = 0; i < ARRAY_SIZE(knownTerms); i++) {
if (strcmp(term, knownTerms[i]) == 0)
break;
}
return i != ARRAY_SIZE(knownTerms) && isatty(hn);
}

41
lonetix/sys/con_windows.c Executable file
View File

@ -0,0 +1,41 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/con_windows.c
*
* Implement Windows console Input/Output.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/con.h"
#include <windows.h>
#include <stdio.h>
#error "Sorry, not fully implemented yet!"
Fildes CON_FILDES(ConHn hn)
{
return GetStdHandle(hn);
}
void Sys_VPrintf(ConHn hn, const char *fmt, va_list va)
{
HANDLE con = GetStdHandle(hn);
if (!con)
return; // No console associated yet
// FIXME implement
}
void Sys_Printf(ConHn hn, const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
Sys_VPrintf(hn, fmt, va);
va_end(va);
}

138
lonetix/sys/dbg_unix.c Executable file
View File

@ -0,0 +1,138 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/dbg_unix.c
*
* Implements debugging utilities for Unix platforms.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/dbg.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef __GNUC__
#include <execinfo.h>
#endif
Boolean Sys_IsDebuggerPresent(void)
{
#ifdef __linux__
// Read process status file
char buf[4096];
int fd = open("/proc/self/status", O_RDONLY);
if (fd < 0)
return FALSE;
ssize_t n = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (n < 0)
return FALSE;
buf[n] = '\0';
const char *tracer = "TracerPid:\t";
const char *pos = strstr(buf, tracer);
if (!pos)
return FALSE;
pos += strlen(tracer);
pid_t pid = atol(pos);
return pid > 0;
#else
// TODO MacOS, BSD via sysctl()
// http://developer.apple.com/qa/qa2004/qa1361.html
// https://github.com/chromium/chromium/blob/master/base/debug/debugger_posix.cc
return FALSE;
#endif
}
size_t Sys_GetBacktrace(void **dest, size_t n)
{
USED(dest);
USED(n);
#ifdef __GNUC__
if (n > (size_t) (INT_MAX - 1))
n = INT_MAX - 1;
void **trace = (void **) alloca(sizeof(*trace) * (n + 1));
int res = backtrace(trace, n + 1);
if (res > 0) {
// Exclude ourselves from the trace
res--;
memcpy(dest, trace + 1, res * sizeof(*dest));
return res;
}
#endif
return 0;
}
void *Sys_GetCaller(void)
{
#ifdef __GNUC__
void *trace[3]; // ourselves, caller, caller's caller
int n = backtrace(trace, ARRAY_SIZE(trace));
if (n == ARRAY_SIZE(trace))
return trace[2];
#endif
return NULL;
}
char *Sys_GetSymbolName(void *sym)
{
USED(sym);
#ifdef __GNUC__
static THREAD_LOCAL char buf[128];
char **names = backtrace_symbols(&sym, 1);
if (names) {
size_t n = strlen(names[0]);
if (n > sizeof(buf) - 1) {
// Truncate symbol
n = sizeof(buf) - 4;
memcpy(buf, names[0], n);
buf[n+0] = '.'; buf[n+1] = '.'; buf[n+2] = '.';
buf[n+3] = '\0';
} else {
memcpy(buf, names[0], n + 1);
}
free(names);
return buf;
}
#endif
return NULL;
}
void Sys_DumpBacktrace(ConHn hn, void **trace, size_t n)
{
USED(hn);
USED(trace);
USED(n);
#ifdef __GNUC__
backtrace_symbols_fd(trace, MIN(n, (size_t) INT_MAX), hn);
#endif
}

12
lonetix/sys/dbg_windows.c Executable file
View File

@ -0,0 +1,12 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/dbg_windows.c
*
* Implements debugging utilities over Windows platforms.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#error "Sorry, not implemented yet!"

239
lonetix/sys/fs_common.c Executable file
View File

@ -0,0 +1,239 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "sys/fs.h"
#include "strlib.h"
#include <assert.h>
#include <string.h>
static Boolean IsSep(char c)
{
return c == '/' || c == '\\' || c == ':';
}
char *Sys_GetAbsoluteFileExtension(const char *path)
{
char *p = (char *) path;
char *sep = p, *extp = p;
// FIXME make it more reliable
while (*p != '\0') {
if (*p == '.' && extp == sep)
extp = p;
if (IsSep(*p))
extp = sep = p;
p++;
}
if (extp == sep)
extp = p; // no extensions in path
return extp;
}
char *Sys_GetFileExtension(const char *path)
{
char *p = (char *) path;
char *sep = p, *extp = p;
// FIXME make it more reliable
while (*p != '\0') {
if (*p == '.')
extp = p;
if (IsSep(*p))
sep = extp = p;
p++;
}
if (extp == sep)
extp = p; // no extensions in path
return extp;
}
char *Sys_SetFileExtension(char *path, const char *ext)
{
assert(*ext == '.' && *(ext+1) != '\0');
char *extp = Sys_GetFileExtension(path);
return strcpy(extp, ext);
}
size_t Sys_StripFileExtension(char *path, const char *ext)
{
if (!ext)
ext = "";
assert(*ext == '\0' || (*ext == '.' && *(ext+1) != '\0'));
char *extp = Sys_GetFileExtension(path);
if (*ext == '\0' || Df_stricmp(ext, extp) == 0)
*extp = '\0';
return extp - path;
}
char *Sys_DefaultFileExtension(char *path, const char *ext)
{
assert(*ext == '.' && *(ext+1) != '\0');
char *extp = Sys_GetFileExtension(path);
if (*extp == '\0')
strcpy(extp, ext);
return extp;
}
static size_t Sys_StripPathFast(char *path)
{
size_t len = strlen(path);
size_t i = len;
while (i-- > 0) {
if (IsSep(path[i])) {
size_t start = i + 1;
size_t n = len - start;
memmove(path, path + start, n + 1);
return n;
}
}
return len;
}
size_t Sys_StripPath(char *path, const char *basePath)
{
if (!basePath || *basePath == '\0')
return Sys_StripPathFast(path); // fast case, no basePath
int c1, c2;
size_t i = 0, j = 0;
while (TRUE) {
c1 = path[i];
c2 = basePath[j];
if (c1 == '\\' || c1 == ':' || c1 == '\0')
c1 = '/';
if (c2 == '\\' || c2 == ':' || c2 == '\0')
c2 = '/';
if (c1 != c2)
return i + strlen(path + i);
while (IsSep(path[i])) i++;
while (IsSep(basePath[j])) j++;
if (basePath[j] == '\0')
break;
i++;
j++;
}
size_t n = strlen(path + i);
memmove(path, path + i, n + 1);
return n;
}
size_t Sys_PathDepth(const char *path)
{
size_t n = 0;
const char *p = path;
while (IsSep(*p))
p++;
while (TRUE) {
// Skip anything that isn't a separator
while (*p != '\0' && !IsSep(*p))
p++;
if (*p == '\0')
break;
n++;
// Skip any consecutive separator
while (IsSep(*p))
p++;
}
return n;
}
size_t Sys_ConvertPath(char *path)
{
// Strip leading slashes
char *start = path;
while (*start != '\0') {
if (IsSep(*start))
break;
start++;
}
// Convert path separators to '/'
char *end = start;
while (*end != '\0') {
if (*end == '\\' || *end == ':')
*end = '/';
end++;
}
// Move everything to the beginning of the string
size_t n = end - start;
memmove(path, start, n + 1);
return n;
}
size_t Sys_ReplaceSeps(char *path)
{
Boolean lastWasSep = FALSE;
char *p = path;
while (*p != '\0') {
if (IsSep(*p)) {
if (!lastWasSep) {
*p = PATH_SEP;
lastWasSep = TRUE;
} else {
memmove(p, p + 1, strlen(p)); // NOTE: no +1
}
} else {
lastWasSep = FALSE;
}
p++;
}
return p - path;
}
int Sys_PathCompare(const char *p1, const char *p2)
{
int c1, c2;
do {
c1 = *p1++;
c2 = *p2++;
if (IsSep(c1)) {
c1 = '/';
while (IsSep(*p1)) p1++;
}
if (IsSep(c2)) {
c2 = '/';
while (IsSep(*p2)) p2++;
}
} while (c1 == c2 && c1 != '\0');
return c1 - c2;
}

364
lonetix/sys/fs_unix.c Executable file
View File

@ -0,0 +1,364 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "sys/sys_local.h"
#include "sys/fs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
Fildes Sys_Fopen(const char *path, FopenMode mode, unsigned flags)
{
Fildes fd;
errno = 0;
// Open file
switch (mode) {
case FM_READ:
fd = open(path, O_RDONLY);
break;
case FM_WRITE:
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
break;
case FM_APPEND:
fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0666);
break;
case FM_EXCL:
fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0666);
break;
case FM_TEMP: {
#ifdef __linux__
// Linux supports O_TMPFILE for safer temporary file creation
fd = open(path, O_WRONLY | O_EXCL | O_TMPFILE, 0600);
if (fd >= 0)
break; // success
if (errno == ENOENT || errno == ENOTDIR || errno == EISDIR)
break; // file not found - don't fallback
if (errno != EOPNOTSUPP)
break; // error - don't fallback
// Fallback to regular mkstemp()
#endif
size_t n = strlen(path);
char *buf = (char *) alloca(n + 1 + 6 + 1);
if (sprintf(buf, "%s/XXXXXX", path) < 0) {
Sys_SetErrStat(errno, "sprintf() failed");
return FILDES_BAD;
}
fd = mkstemp(buf);
break;
}
default:
Sys_SetErrStat(EINVAL, "Bad value for 'mode'");
return FILDES_BAD;
}
if (errno == ENOENT || errno == ENOTDIR || errno == EISDIR)
errno = 0; // file not found - not an error
Sys_SetErrStat(errno, "open()/mkstemp()");
// Apply hints
if (fd >= 0) {
if (flags & FH_SEQ)
posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
if (flags & FH_RAND)
posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);
if (flags & FH_NOREUSE)
posix_fadvise(fd, 0, 0, POSIX_FADV_NOREUSE);
}
return fd;
}
Sint64 Sys_Fread(Fildes fd, void *buf, size_t nbytes)
{
Sint64 n;
errno = 0;
n = read(fd, buf, nbytes);
Sys_SetErrStat(errno, "read()");
return n;
}
Sint64 Sys_Fwrite(Fildes fd, const void *buf, size_t nbytes)
{
Sint64 n;
errno = 0;
n = write(fd, buf, nbytes);
Sys_SetErrStat(errno, "write() failed");
return n;
}
Sint64 Sys_Ftell(Fildes fd)
{
Sint64 pos;
errno = 0;
pos = lseek(fd, 0, SEEK_CUR);
if (pos < 0 && errno != ESPIPE)
errno = 0; // lseek() unsupported, but not a system error
Sys_SetErrStat(errno, "lseek()");
return pos;
}
Sint64 Sys_Fseek(Fildes fd, Sint64 off, SeekMode whence)
{
Sint64 pos;
int mode;
switch (whence) {
case SK_SET: mode = SEEK_SET; break;
case SK_CUR: mode = SEEK_CUR; break;
case SK_END: mode = SEEK_END; break;
default:
Sys_SetErrStat(EINVAL, "Bad value for 'whence'");
return -1;
}
errno = 0;
pos = lseek(fd, off, mode);
if (pos < 0 && errno == ESPIPE)
errno = 0; // lseek() unsupported, but not a system error
Sys_SetErrStat(errno, "lseek()");
return pos;
}
Sint64 Sys_FileSize(Fildes fd)
{
Sint64 size = -1;
struct stat buf;
errno = 0;
if (fstat(fd, &buf) == 0)
size = buf.st_size;
Sys_SetErrStat(errno, "fstat()");
return size;
}
Judgement Sys_SetEof(Fildes fd)
{
Sint64 pos = Sys_Ftell(fd);
if (pos < 0)
return NG; // Sys_Ftell() already sets error
ftruncate(fd, pos);
return Sys_SetErrStat(errno, "ftruncate()");
}
Judgement Sys_Fsync(Fildes fd, Boolean fullSync)
{
errno = 0;
if (fullSync)
fsync(fd);
else
fdatasync(fd);
return Sys_SetErrStat(errno, "fsync()/fdatasync()");
}
void Sys_Fclose(Fildes fd)
{
errno = 0;
close(fd);
Sys_SetErrStat(errno, "close()");
}
typedef struct FileList FileList;
struct FileList {
FileList *next;
size_t len;
char name[1]; // dynamically sized, len+1
};
#ifdef _DIRENT_HAVE_D_TYPE
// May save some syscalls
#define ISUNK(ent) ((ent)->d_type == DT_UNKNOWN)
#define ISDIR(ent) ((ent)->d_type == DT_DIR)
#define ISLNK(ent) ((ent)->d_type == DT_LNK)
#else
// ...as if DT_UNKNOWN is always set
#define ISUNK(ent) (1)
#define ISDIR(ent) (0)
#define ISLNK(ent) (0)
#endif
char **Sys_ListFiles(const char *path, unsigned *nfiles, const char *pat)
{
DIR *dir = opendir(path);
if (!dir) {
if (errno != ENOENT && errno != ENOTDIR)
Sys_SetErrStat(errno, "opendir()");
return NULL;
}
if (!pat)
pat = "";
Boolean dirOnly = (strcmp(pat, "/") == 0);
size_t elen = strlen(pat);
// Scan directory
FileList *entries = NULL;
unsigned count = 0;
size_t nchars = 0;
struct stat st;
struct dirent *ent;
char **files;
char *namep;
errno = 0;
while ((ent = readdir(dir)) != NULL) {
// Skip special entries
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
size_t len = strlen(ent->d_name);
if (dirOnly) {
// Filter anything other than subdirectories
Boolean isDir;
if (ISUNK(ent) || ISLNK(ent)) {
// Need a full stat() on entry
if (fstatat(dirfd(dir), ent->d_name, &st, 0) != 0) {
Sys_SetErrStat(errno, "fstatat()");
goto fail;
}
isDir = S_ISDIR(st.st_mode);
} else {
// May take advantage of dirent's d_type
isDir = ISDIR(ent);
}
if (!isDir)
continue;
} else {
// Filter by extension pattern
if (elen >= len)
continue;
if (strcmp(ent->d_name + len - elen, pat) != 0)
continue;
}
// Update counters
count++;
nchars += len + 1;
// ...and remember this entry
FileList *e = (FileList *) alloca(sizeof(*e) + len); // includes '\0'
e->next = entries;
e->len = len;
memcpy(e->name, ent->d_name, len + 1);
entries = e;
}
if (errno != 0) {
Sys_SetErrStat(errno, "readdir()");
goto fail;
}
// Allocate result, single malloc(), +1 for NULL sentinel
files = (char **) malloc(sizeof(*files) * (count + 1) + nchars);
if (!files) {
Sys_OutOfMemory();
goto fail;
}
closedir(dir); // safe, can't fail anymore
// Collect files in buffer
namep = (char *) (files + count + 1);
for (unsigned i = 0; i < count; i++) {
files[i] = namep;
size_t n = entries->len + 1;
memcpy(namep, entries->name, n);
namep += n;
entries = entries->next;
}
files[count] = NULL; // NULL-terminate the file list
if (nfiles)
*nfiles = count;
return files;
fail:
closedir(dir);
return NULL;
}
Judgement Sys_Mkdir(const char *path)
{
errno = 0;
if (mkdir(path, 0755) != 0) {
int err = errno; // save errno - avoid overriding by stat()
if (err == EEXIST) {
// Make sure this is actually a directory
struct stat buf;
if (stat(path, &buf) == 0 && S_ISDIR(buf.st_mode))
err = 0; // all good
}
errno = err;
}
return Sys_SetErrStat(errno, "mkdir()");
}
Judgement Sys_Rename(const char *path, const char *new_path)
{
errno = 0;
rename(path, new_path);
return Sys_SetErrStat(errno, "rename()");
}
Judgement Sys_Remove(const char *path)
{
errno = 0;
remove(path);
// TODO check not found
return Sys_SetErrStat(errno, "remove()");
}

159
lonetix/sys/fs_windows.c Executable file
View File

@ -0,0 +1,159 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/fs_windows.c
*
* System specific filesystem utilities over Windows.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/fs.h"
#include "sys/err_local.h"
#include <errno.h>
#include <io.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#error "Sorry, not fully implemented yet!"
typedef union {
wchar_t wstr[_MAX_PATH + 1];
char str[(_MAX_PATH + 1) * sizeof(wchar_t)];
} Pathbuf;
static THREAD_LOCAL Pathbuf pathbuf;
static wchar_t *Sys_Utf8PathToW(const char *path)
{
int n = MultiByteToWideChar(CP_UTF8, 0, dir, -1, pathbuf.wstr, ARRAY_SIZE(pathbuf.wstr));
if (n < 0) {
Sys_SetErrStat(GetLastError(), "MultiByteToWideChar() failed");
return NULL;
}
return pathbuf.wstr;
}
static char *Sys_WPathToUtf8(const wchar_t *path)
{
int n = WideCharToMultiByte(CP_UTF16, 0, path, -1, pathbuf.str, sizeof(pathbuf.str));
if (n < 0) {
Sys_GetErrStat(GetLastError(), "WideCharToMultiByte() failed");
return NULL;
}
return pathbuf.str;
}
Judgement Sys_SetEof(Fildes fd)
{
if (!SetEndOfFile(fd)) {
Sys_SetErrStat(GetLastError(), "SetEndOfFile() failed");
return NG;
}
return OK;
}
void Sys_Fclose(Fildes fd)
{
CloseHandle(fd);
}
typedef struct FileList dfFileList;
struct FileList {
FileList *next;
unsigned len;
char name[1]; // dynamically sized len+1
};
char **Sys_ListFiles(const char *dir, unsigned *nfiles, const char *pat)
{
if (!pat)
pat = "";
int flag;
if (strcmp(pat, "/") == 0) {
pat = "";
flag = _A_SUBDIR;
} else {
flag = 0;
}
char *search = (char *) alloca(strlen(dir) + 1 + 1 + strlen(pat) + 1);
if (sprintf(search, "%s\\*%s", dir, pat) < 0) {
Sys_SetErrStat(errno, "sprintf() failed");
return NULL;
}
wchar_t *wsearch = Sys_Utf8PathToW(search);
if (!wsearch)
return NULL; // error already set
FileList *entries = NULL;
unsigned count = 0;
size_t nchars = 0;
errno = 0;
struct _wfinddata_t finddata;
intptr_t findhn = _wfindfirst(wsearch, &finddata);
if (findhn != -1) {
// Scan directory
do {
if (finddata.attrib & flag) {
// remember this file
char *path = Sys_WPathToUtf8(finddata.name);
size_t n = strlen(path);
count++;
nchars += n + 1;
FileList *e = (FileList *) alloca(sizeof(*e) + n);
e->next = entries;
e->len = n;
memcpy(e->name, path, n + 1);
entries = e;
}
} while (_wfindnext(findhn, &finddata) == 0);
}
if (errno != 0) {
Sys_SetErrStat(errno, "_wfindfirst()/_wfindnext()");
goto fail;
}
_findclose(findhn);
char **files = (char **) malloc(sizeof(*files) * (count + 1) + nchars);
if (!files) {
Sys_OutOfMemory();
return NULL;
}
char *namep = (char *) (files + count + 1);
for (unsigned i = 0; i < count; i++) {
files[i] = namep;
size_t n = entries->len + 1;
memcpy(namep, entries->name, n);
namep += n;
entries = entries->next;
}
files[count] = NULL;
if (nfiles)
*nfiles = count;
return files;
fail:
_findclose(findhn);
return NULL;
}

129
lonetix/sys/ip_common.c Executable file
View File

@ -0,0 +1,129 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/ip_common.c
*
* Cross-system Internet Protocol functionality implementation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/ip.h"
#include "sys/endian.h"
#include "numlib.h"
#include <string.h>
char *Ipv4_AdrToString(const Ipv4adr *adr, char *dest)
{
char *ptr = dest;
ptr = Utoa(adr->bytes[0], ptr); *ptr++ = '.';
ptr = Utoa(adr->bytes[1], ptr); *ptr++ = '.';
ptr = Utoa(adr->bytes[2], ptr); *ptr++ = '.';
ptr = Utoa(adr->bytes[3], ptr);
return ptr;
}
char *Ipv6_AdrToString(const Ipv6adr *adr, char *dest)
{
char *ptr = dest;
if (IS_IPV6_V4MAPPED(adr)) {
// IPv4 mapped to IPv6 (starts with 0:0:0:0:0:0:ffff)
memcpy(ptr, "::ffff:", 7);
ptr += 7;
ptr = Utoa(adr->bytes[0], ptr); *ptr++ = '.';
ptr = Utoa(adr->bytes[1], ptr); *ptr++ = '.';
ptr = Utoa(adr->bytes[2], ptr); *ptr++ = '.';
ptr = Utoa(adr->bytes[3], ptr);
} else {
// Regular IPv6
ptr = Xtoa(beswap16(adr->words[0]), ptr); *ptr++ = ':';
ptr = Xtoa(beswap16(adr->words[1]), ptr); *ptr++ = ':';
ptr = Xtoa(beswap16(adr->words[2]), ptr); *ptr++ = ':';
ptr = Xtoa(beswap16(adr->words[3]), ptr); *ptr++ = ':';
ptr = Xtoa(beswap16(adr->words[4]), ptr); *ptr++ = ':';
ptr = Xtoa(beswap16(adr->words[5]), ptr); *ptr++ = ':';
ptr = Xtoa(beswap16(adr->words[6]), ptr); *ptr++ = ':';
ptr = Xtoa(beswap16(adr->words[7]), ptr);
// Replace longest /(^0|:)[:0]{2,}/ with "::"
unsigned i, j, n, best, max;
for (i = best = 0, max = 2; dest[i] != '\0'; i++) {
if (i > 0 && dest[i] != ':')
continue;
// Count the number of consecutive 0 in this substring
n = 0;
for (j = i; dest[j] != '\0'; j++) {
if (dest[j] != ':' && dest[j] != '0')
break;
n++;
}
// Store the position if this is the best we've seen so far
if (n > max) {
best = i;
max = n;
}
}
ptr = dest + i;
if (max > 3) {
// Compress the string
dest[best] = ':';
dest[best + 1] = ':';
unsigned len = i - best - max;
memmove(&dest[best + 2], &dest[best + max], len + 1);
ptr = &dest[best + 2 + len];
}
}
return ptr;
}
char *Ip_AdrToString(const Ipadr *addr, char *dest)
{
switch (addr->family) {
case IP4: return Ipv4_AdrToString(&addr->v4, dest);
case IP6: return Ipv6_AdrToString(&addr->v6, dest);
default: return NULL;
}
}
static Boolean Ip_IsStringIpv4(const char *s)
{
// A well formed IPv4 address contains one dot in chars [1,3]
// x.
// xx.
// xxx.
for (int i = 1; i < 4; i++) {
if (s[i-1] == '\0')
return FALSE;
if (s[i] == '.')
return TRUE;
}
return FALSE;
}
Judgement Ip_StringToAdr(const char *s, Ipadr *dest)
{
if (Ip_IsStringIpv4(s)) {
if (Ipv4_StringToAdr(s, &dest->v4) != OK)
return NG;
dest->family = IP4;
} else {
if (Ipv6_StringToAdr(s, &dest->v6) != OK)
return NG;
dest->family = IP6;
}
return OK;
}

30
lonetix/sys/ip_unix.c Executable file
View File

@ -0,0 +1,30 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/ip_unix.c
*
* System specific Internet Protocol functionality implementation over Unix.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/ip.h"
#include <arpa/inet.h>
Judgement Ipv4_StringToAdr(const char *s, Ipv4adr *dest)
{
if (inet_pton(AF_INET, s, dest->bytes) != 1)
return NG;
return OK;
}
Judgement Ipv6_StringToAdr(const char *s, Ipv6adr *dest)
{
if (inet_pton(AF_INET6, s, dest->bytes) != 1)
return NG;
return OK;
}

30
lonetix/sys/ip_windows.c Executable file
View File

@ -0,0 +1,30 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/ip_windows.c
*
* System specific Internet Protocol functionality implementation over Windows.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/ip.h"
#include <ws2tcpip.h>
Judgement Ipv4_StringToAdr(const char *s, Ipv4adr *dest)
{
if (InetPtonA(AF_INET, s, dest->bytes) != 1)
return NG;
return OK;
}
Judgement Ipv6_StringToAdr(const char *s, Ipv6adr *dest)
{
if (InetPtonA(AF_INET6, s, dest->bytes) != 1)
return NG;
return OK;
}

213
lonetix/sys/sys_common.c Executable file
View File

@ -0,0 +1,213 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/sys_common.c
*
* Cross-system utilities for system layer and error management.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/sys_local.h"
#include "sys/dbg.h"
#include "argv.h"
#include "numlib.h"
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAXTRACE 64
// Define this to always omit backtrace from Sys_Abort()
// #define NSTKTRACE
static THREAD_LOCAL SysErrStat sys_errs;
static void ReportError(SysRet code,
const char *reason,
const Srcloc *loc,
Boolean includeFileLoc)
{
const char *err = strerror(code);
Sys_Print(STDERR, "\t");
if (loc) {
if (includeFileLoc && 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");
}
if (loc->func) {
Sys_Print(STDERR, "[");
Sys_Print(STDERR, loc->func);
Sys_Print(STDERR, "()]: ");
}
}
Sys_Print(STDERR, reason);
if (err) {
Sys_Print(STDERR, ": ");
Sys_Print(STDERR, err);
}
Sys_Print(STDERR, ".\n");
}
NORETURN NOINLINE static void Sys_Abort(SysRet code,
const char *reason,
Srcloc *loc,
void *obj)
{
USED(obj);
loc->call_depth++; // include Sys_Abort() as well
if (com_progName) {
Sys_Print(STDERR, com_progName);
Sys_Print(STDERR, ": ");
}
Sys_Print(STDERR, "FATAL ERROR - Execution aborted in response to an unrecoverable system error.\n");
ReportError(code, reason, loc, /*includeFileLoc=*/TRUE);
#ifndef NSTKTRACE
void *trace[MAXTRACE + 1]; // +1 for detecting overflow
unsigned call_depth = loc->call_depth;
unsigned ntrace = Sys_GetBacktrace(trace, ARRAY_SIZE(trace));
if (ntrace > call_depth) {
ntrace -= call_depth; // omit error propagation from trace
Sys_Print(STDERR, "\nBacktrace:\n");
Sys_DumpBacktrace(STDERR, &trace[call_depth], MIN(ntrace, MAXTRACE));
if (ntrace > MAXTRACE)
Sys_Print(STDERR, "... [additional frames omitted]\n");
} else {
Sys_Print(STDERR, "No backtrace information available.\n");
}
#endif
#ifndef NDEBUG
if (Sys_IsDebuggerPresent())
raise(SIGTRAP); // bail out to debugger
#endif
abort();
}
NORETURN NOINLINE static void Sys_Quit(SysRet code,
const char *reason,
Srcloc *loc,
void *obj)
{
USED(obj);
loc->call_depth++; // include Sys_Quit() as well
if (com_progName) {
Sys_Print(STDERR, com_progName);
Sys_Print(STDERR, ": ");
}
Sys_Print(STDERR, "Terminate called in response to an unrecoverable system error\n");
Boolean includeFileLoc = FALSE;
#ifndef NDEBUG
includeFileLoc = TRUE;
#endif
ReportError(code, reason, loc, includeFileLoc);
_Exit(EXIT_FAILURE);
}
Judgement _Sys_SetErrStat(SysRet code,
const char *reason,
const char *filename,
const char *func,
unsigned long long line,
unsigned depth)
{
sys_errs.code = code;
if (code == 0)
return OK;
void (*err_func)(SysRet, const char *, Srcloc *, void *);
Srcloc loc;
err_func = sys_errs.func;
// Remap SYS_ERR_ABORT and SYS_ERR_QUIT to correct function
if (err_func == SYS_ERR_ABORT)
err_func = Sys_Abort;
if (err_func == SYS_ERR_QUIT)
err_func = Sys_Quit;
if (err_func) {
loc.filename = filename;
loc.func = func;
loc.line = line;
loc.call_depth = depth + 1; // +1 for this function
err_func(code, reason, &loc, sys_errs.obj);
}
return NG;
}
void _Sys_OutOfMemory(const char *file,
const char *func,
unsigned long long line,
unsigned depth)
{
depth++; // include _Sys_OutOfMemory() as well
_Sys_SetErrStat(ENOMEM, "Out of memory", file, func, line, depth);
}
void _Sys_FatalError(SysRet code,
const char *reason,
const char *file,
const char *func,
unsigned long long line,
unsigned depth)
{
depth++; // include _Sys_FatalError() as well
Srcloc loc;
loc.filename = file;
loc.func = func;
loc.line = line;
loc.call_depth = depth;
Sys_Abort(code, reason, &loc, NULL);
}
SysRet Sys_GetErrStat(SysErrStat *stat)
{
if (stat)
*stat = sys_errs;
return sys_errs.code;
}
void Sys_SetErrFunc(void (*func)(SysRet, const char *, Srcloc *, void *),
void *obj)
{
sys_errs.func = func;
sys_errs.obj = obj;
}

40
lonetix/sys/sys_local.h Executable file
View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/sys_local.h
*
* Private functionality for system layer and error reporting.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_SYS_LOCAL_H_
#define DF_SYS_LOCAL_H_
#include "sys/sys.h"
/// Set system error code and message.
#define Sys_SetErrStat(code, msg) \
_Sys_SetErrStat(code, msg, __FILE__, __func__, __LINE__, 0)
// NOTE: implementation detail, should only be called through `Sys_SetErrStat()`.
NOINLINE Judgement _Sys_SetErrStat(SysRet code,
const char *msg,
const char *filename,
const char *func,
unsigned long long line,
unsigned depth);
/// Abort execution with error.
#define Sys_FatalError(code, msg) \
_Sys_FatalError(code, msg, __FILE__, __func__, __LINE__, 0)
NORETURN NOINLINE void _Sys_FatalError(SysRet code,
const char *msg,
const char *filename,
const char *func,
unsigned long long line,
unsigned depth);
#endif

25
lonetix/sys/sys_unix.c Executable file
View File

@ -0,0 +1,25 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file sys/sys_unix.c
*
* General system specific functionality implementation over Unix.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "sys/sys_local.h"
#include <time.h>
void Sys_SleepMillis(Uint32 millis)
{
struct timespec ts;
ts.tv_sec = millis / 1000;
ts.tv_nsec = (millis % 1000) * 1000000uLL;
int res;
do res = nanosleep(&ts, &ts); while (res != 0); // res != 0 -> EINTR
}

187
lonetix/utf/rune.c Executable file
View File

@ -0,0 +1,187 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file utf/rune.c
*
* Implements `char`s to `Rune` conversion functions and back.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
/*
* This work is derived from Plan 9 libutf, see utf/utf.h for details.
* Original license terms follow:
*
* The authors of this software are Rob Pike and Ken Thompson.
* Copyright (c) 2002 by Lucent Technologies.
* Permission to use, copy, modify, and distribute this software for any
* purpose without fee is hereby granted, provided that this entire notice
* is included in all copies of any software which is or includes a copy
* or modification of this software and in all copies of the supporting
* documentation for such software.
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
* ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
*
* The original libutf library is available at: https://9fans.github.io/plan9port/unix/libutf.tgz
*/
#include "utf/utf.h"
#define BIT1 7
#define BITX 6
#define BIT2 5
#define BIT3 4
#define BIT4 3
#define BIT5 2
#define T1 (((1u << (BIT1+1)) - 1u) ^ 0xffu) /* 0000 0000 */
#define TX (((1u << (BITX+1)) - 1u) ^ 0xffu) /* 1000 0000 */
#define T2 (((1u << (BIT2+1)) - 1u) ^ 0xffu) /* 1100 0000 */
#define T3 (((1u << (BIT3+1)) - 1u) ^ 0xffu) /* 1110 0000 */
#define T4 (((1u << (BIT4+1)) - 1u) ^ 0xffu) /* 1111 0000 */
#define T5 (((1u << (BIT5+1)) - 1u) ^ 0xffu) /* 1111 1000 */
#define RUNE1 ((U32_C(1) << (BIT1 + 0*BITX)) - 1u) /* 0000 0000 0000 0000 0111 1111 */
#define RUNE2 ((U32_C(1) << (BIT2 + 1*BITX)) - 1u) /* 0000 0000 0000 0111 1111 1111 */
#define RUNE3 ((U32_C(1) << (BIT3 + 2*BITX)) - 1u) /* 0000 0000 1111 1111 1111 1111 */
#define RUNE4 ((U32_C(1) << (BIT4 + 3*BITX)) - 1u) /* 0011 1111 1111 1111 1111 1111 */
#define MASKX ((1u << BITX) - 1u) /* 0011 1111 */
#define TESTX (MASKX ^ 0xFFu) /* 1100 0000 */
size_t chartorune(Rune *dest, const char *str)
{
// One character sequence
// 00000-0007F => T1
Rune c = *(const Uint8 *) str;
if (c < TX) {
*dest = c;
return 1;
}
// Two character sequence
// 0080-07FF => T2 Tx
Rune c1 = *(const Uint8 *) (str+1) ^ TX;
if (c1 & TESTX)
goto bad;
if (c < T3) {
if (c < T2)
goto bad;
Rune r = ((c << BITX) | c1) & RUNE2;
if (r <= RUNE1)
goto bad;
*dest = r;
return 2;
}
// Three character sequence
// 0800-FFFF => T3 Tx Tx
Rune c2 = *(const Uint8 *) (str+2) ^ TX;
if (c2 & TESTX)
goto bad;
if (c < T4) {
Rune r = ((((c << BITX) | c1) << BITX) | c2) & RUNE3;
if (r <= RUNE2)
goto bad;
*dest = r;
return 3;
}
// Four character sequence
// 10000-10FFFF => T4 Tx Tx Tx
Rune c3 = *(const Uint8*) (str+3) ^ TX;
if (c3 & TESTX)
goto bad;
if (c < T5) {
Rune r = ((((((c << BITX) | c1) << BITX) | c2) << BITX) | c3) & RUNE4;
if (r <= RUNE3)
goto bad;
if (r > MAXRUNE)
goto bad;
*dest = r;
return 4;
}
// Decoding error
bad:
*dest = RUNE_ERR;
return 1;
}
size_t runetochar(char *dest, Rune r)
{
// One character sequence
// 00000-0007F => 00-7F
if (r <= RUNE1) {
dest[0] = r;
return 1;
}
// Two character sequence
// 00080-007FF => T2 Tx
if (r <= RUNE2) {
dest[0] = T2 | (r >> 1*BITX);
dest[1] = TX | (r & MASKX);
return 2;
}
// Three character sequence
// 00800-0FFFF => T3 Tx Tx
if (r > MAXRUNE)
r = RUNE_ERR;
if (r <= RUNE3) {
dest[0] = T3 | (r >> 2*BITX);
dest[1] = TX | ((r >> 1*BITX) & MASKX);
dest[2] = TX | (r & MASKX);
return 3;
}
// Four character sequence
// 010000-1FFFFF => T4 Tx Tx Tx
dest[0] = T4 | (r >> 3*BITX);
dest[1] = TX | ((r >> 2*BITX) & MASKX);
dest[2] = TX | ((r >> 1*BITX) & MASKX);
dest[3] = TX | (r & MASKX);
return 4;
}
size_t runelen(Rune r)
{
return 1 + (r > RUNE1) + (r > RUNE2) + (r > RUNE3 && r <= MAXRUNE);
}
size_t runenlen(const Rune *r, size_t n)
{
size_t nb = 0;
while (n--)
nb += runelen(*r++);
return nb;
}
Boolean fullrune(const char *str, size_t n)
{
if (n == 0)
return FALSE;
Rune c = *(const Uint8 *) str;
if (c < TX)
return TRUE;
if (c < T3)
return n >= 2;
if (c < T4)
return n >= 3;
return n >= 4;
}

BIN
lonetix/utf/runetype.c Executable file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More