mirror of
https://codeberg.org/1414codeforge/ubgpsuite.git
synced 2025-04-12 17:41:17 +02:00
[*] Initial commit
This commit is contained in:
commit
b0ef4dd774
1
.clang-tidy
Executable file
1
.clang-tidy
Executable file
@ -0,0 +1 @@
|
||||
Checks: 'clang-*,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling'
|
51
.gitignore
vendored
Normal file
51
.gitignore
vendored
Normal 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
675
COPYING.GPL
Normal 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
166
COPYING.LESSER
Normal 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
60
README.md
Normal 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
1022
doc/Doxyfile.in
Normal file
File diff suppressed because it is too large
Load Diff
55
doc/HISTORY.md
Normal file
55
doc/HISTORY.md
Normal 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
3
doc/css/custom.css
Normal file
@ -0,0 +1,3 @@
|
||||
:root {
|
||||
--note-color-darker: #8e7618;
|
||||
}
|
108
doc/css/doxygen-awesome-sidebar-only.css
Normal file
108
doc/css/doxygen-awesome-sidebar-only.css
Normal 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
1365
doc/css/doxygen-awesome.css
Normal file
File diff suppressed because it is too large
Load Diff
21
doc/meson.build
Normal file
21
doc/meson.build
Normal 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
170
lonetix/README.md
Normal 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
349
lonetix/argv.c
Normal 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
1235
lonetix/bgp/attribute.c
Normal file
File diff suppressed because it is too large
Load Diff
611
lonetix/bgp/bgp.c
Normal file
611
lonetix/bgp/bgp.c
Normal 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
45
lonetix/bgp/bgp_local.h
Normal 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
177
lonetix/bgp/bytebuf.c
Normal 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
82
lonetix/bgp/dump.c
Normal 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
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
751
lonetix/bgp/mrt.c
Normal 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
100
lonetix/bgp/parameters.c
Normal 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
518
lonetix/bgp/patricia.c
Normal 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
191
lonetix/bgp/prefix.c
Normal 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
838
lonetix/bgp/vm.c
Normal 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
834
lonetix/bgp/vm_asmtch.c
Normal 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
33
lonetix/bgp/vm_cdef.h
Normal 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
103
lonetix/bgp/vm_commsort.h
Normal 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
|
442
lonetix/bgp/vm_communities.c
Normal file
442
lonetix/bgp/vm_communities.c
Normal 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
303
lonetix/bgp/vm_dump.c
Normal 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
66
lonetix/bgp/vm_gccdef.h
Normal 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
98
lonetix/bgp/vm_optab.h
Normal 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
119
lonetix/bufio.c
Normal 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
328
lonetix/cpr/bzip2.c
Executable 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
387
lonetix/cpr/flate.c
Executable 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
354
lonetix/cpr/xz.c
Executable 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
87
lonetix/include/df/argv.h
Normal 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
87
lonetix/include/df/bgp/asn.h
Executable 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
1198
lonetix/include/df/bgp/bgp.h
Executable file
File diff suppressed because it is too large
Load Diff
62
lonetix/include/df/bgp/bytebuf.h
Executable file
62
lonetix/include/df/bgp/bytebuf.h
Executable 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
90
lonetix/include/df/bgp/dump.h
Executable 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
888
lonetix/include/df/bgp/mrt.h
Executable 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
|
44
lonetix/include/df/bgp/patricia.h
Executable file
44
lonetix/include/df/bgp/patricia.h
Executable 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
222
lonetix/include/df/bgp/prefix.h
Executable 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
385
lonetix/include/df/bgp/vm.h
Executable 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
250
lonetix/include/df/bgp/vmintrin.h
Executable 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
162
lonetix/include/df/bufio.h
Normal 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
347
lonetix/include/df/chkint.h
Normal 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
134
lonetix/include/df/cpr/bzip2.h
Executable file
@ -0,0 +1,134 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
/**
|
||||
* \file cpr/bzip2.h
|
||||
*
|
||||
* Burrows–Wheeler 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
121
lonetix/include/df/cpr/flate.h
Executable 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
118
lonetix/include/df/cpr/xz.h
Executable 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
475
lonetix/include/df/lexer.h
Normal 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
85
lonetix/include/df/mem.h
Normal 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
|
154
lonetix/include/df/mem_file.h
Normal file
154
lonetix/include/df/mem_file.h
Normal 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
191
lonetix/include/df/numlib.h
Normal 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
|
45
lonetix/include/df/srcloc.h
Normal file
45
lonetix/include/df/srcloc.h
Normal 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
140
lonetix/include/df/stm.h
Normal 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
198
lonetix/include/df/strlib.h
Normal 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
104
lonetix/include/df/sys/con.h
Executable 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
59
lonetix/include/df/sys/dbg.h
Executable 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
132
lonetix/include/df/sys/endian.h
Executable 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
253
lonetix/include/df/sys/fs.h
Executable 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
58
lonetix/include/df/sys/fsdef.h
Executable 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
|
136
lonetix/include/df/sys/interlocked.h
Executable file
136
lonetix/include/df/sys/interlocked.h
Executable 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
|
63
lonetix/include/df/sys/interlocked_intrin_msvc.h
Executable file
63
lonetix/include/df/sys/interlocked_intrin_msvc.h
Executable 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"
|
||||
|
191
lonetix/include/df/sys/interlocked_ops_gcc.h
Executable file
191
lonetix/include/df/sys/interlocked_ops_gcc.h
Executable 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
|
0
lonetix/include/df/sys/interlocked_ops_msvc.h
Executable file
0
lonetix/include/df/sys/interlocked_ops_msvc.h
Executable file
178
lonetix/include/df/sys/ip.h
Executable file
178
lonetix/include/df/sys/ip.h
Executable 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
154
lonetix/include/df/sys/sys.h
Executable 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
157
lonetix/include/df/sys/vt100.h
Executable 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
92
lonetix/include/df/utf/utf.h
Executable 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
27
lonetix/include/df/utf/utfdef.h
Executable 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
491
lonetix/include/df/xpt.h
Normal 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
1577
lonetix/lexer.c
Normal file
File diff suppressed because it is too large
Load Diff
35
lonetix/mem.c
Normal file
35
lonetix/mem.c
Normal 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
198
lonetix/mem_file.c
Normal 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
561
lonetix/numlib_atof.c
Normal 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
213
lonetix/numlib_atoi.c
Normal 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
96
lonetix/numlib_fltp.h
Normal 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
343
lonetix/numlib_ftoa.c
Normal 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
80
lonetix/numlib_itoa.c
Normal 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
67
lonetix/stm.c
Normal 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
40
lonetix/stricmp.c
Normal 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
81
lonetix/strncatz.c
Normal 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
59
lonetix/strncpyz.c
Normal 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
42
lonetix/strnicmp.c
Normal 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
143
lonetix/sys/con_unix.c
Executable 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
41
lonetix/sys/con_windows.c
Executable 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
138
lonetix/sys/dbg_unix.c
Executable 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
12
lonetix/sys/dbg_windows.c
Executable 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
239
lonetix/sys/fs_common.c
Executable 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
364
lonetix/sys/fs_unix.c
Executable 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
159
lonetix/sys/fs_windows.c
Executable 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
129
lonetix/sys/ip_common.c
Executable 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
30
lonetix/sys/ip_unix.c
Executable 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
30
lonetix/sys/ip_windows.c
Executable 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
213
lonetix/sys/sys_common.c
Executable 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
40
lonetix/sys/sys_local.h
Executable 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
25
lonetix/sys/sys_unix.c
Executable 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
187
lonetix/utf/rune.c
Executable 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
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
Loading…
x
Reference in New Issue
Block a user