diff options
Diffstat (limited to 'src/aclk')
68 files changed, 2441 insertions, 3876 deletions
diff --git a/src/aclk/README.md b/src/aclk/README.md index 0a260868c..450f9dced 100644 --- a/src/aclk/README.md +++ b/src/aclk/README.md @@ -4,13 +4,13 @@ The Agent-Cloud link (ACLK) is the mechanism responsible for securely connecting through Netdata Cloud. The ACLK establishes an outgoing secure WebSocket (WSS) connection to Netdata Cloud on port `443`. The ACLK is encrypted, safe, and _is only established if you connect your node_. -The Cloud App lives at app.netdata.cloud which currently resolves to the following list of IPs: +The Cloud App lives at app.netdata.cloud which currently resolves to the following list of IPs: - 54.198.178.11 - 44.207.131.212 -- 44.196.50.41 +- 44.196.50.41 -> ### Caution +> **Caution** > >This list of IPs can change without notice, we strongly advise you to whitelist following domains `app.netdata.cloud`, `mqtt.netdata.cloud`, if this is not an option in your case always verify the current domain resolution (e.g via the `host` command). @@ -28,106 +28,18 @@ However, to be able to offer the stunning visualizations and advanced functional ## Enable and configure the ACLK -The ACLK is enabled by default, with its settings automatically configured and stored in the Agent's memory. No file is -created at `/var/lib/netdata/cloud.d/cloud.conf` until you either connect a node or create it yourself. The default -configuration uses two settings: - -```conf -[global] - enabled = yes - cloud base url = https://app.netdata.cloud -``` +The ACLK is enabled by default, with its settings automatically configured and stored in the Agent's memory. If your Agent needs to use a proxy to access the internet, you must [set up a proxy for -connecting to cloud](/src/claim/README.md#connect-through-a-proxy). +connecting to cloud](/src/claim/README.md). You can configure following keys in the `netdata.conf` section `[cloud]`: -``` + +```text [cloud] - statistics = yes - query thread count = 2 + statistics = yes + query thread count = 2 ``` - `statistics` enables/disables ACLK related statistics and their charts. You can disable this to save some space in the database and slightly reduce memory usage of Netdata Agent. - `query thread count` specifies the number of threads to process cloud queries. Increasing this setting is useful for nodes with many children (streaming), which can expect to handle more queries (and/or more complicated queries). - -## Disable the ACLK - -You have two options if you prefer to disable the ACLK and not use Netdata Cloud. - -### Disable at installation - -You can pass the `--disable-cloud` parameter to the Agent installation when using a kickstart script -([kickstart.sh](/packaging/installer/methods/kickstart.md), or a [manual installation from -Git](/packaging/installer/methods/manual.md). - -When you pass this parameter, the installer does not download or compile any extra libraries. Once running, the Agent -kills the thread responsible for the ACLK and connecting behavior, and behaves as though the ACLK, and thus Netdata Cloud, -does not exist. - -### Disable at runtime - -You can change a runtime setting in your `cloud.conf` file to disable the ACLK. This setting only stops the Agent from -attempting any connection via the ACLK, but does not prevent the installer from downloading and compiling the ACLK's -dependencies. - -The file typically exists at `/var/lib/netdata/cloud.d/cloud.conf`, but can change if you set a prefix during -installation. To disable the ACLK, open that file and change the `enabled` setting to `no`: - -```conf -[global] - enabled = no -``` - -If the file at `/var/lib/netdata/cloud.d/cloud.conf` doesn't exist, you need to create it. - -Copy and paste the first two lines from below, which will change your prompt to `cat`. - -```bash -cd /var/lib/netdata/cloud.d -cat > cloud.conf << EOF -``` - -Copy and paste in lines 3-6, and after the final `EOF`, hit **Enter**. The final line must contain only `EOF`. Hit **Enter** again to return to your normal prompt with the newly-created file. - -To get your normal prompt back, the final line -must contain only `EOF`. - -```bash -[global] - enabled = no - cloud base url = https://app.netdata.cloud -EOF -``` - -You also need to change the file's permissions. Use `grep "run as user" /etc/netdata/netdata.conf` to figure out which -user your Agent runs as (typically `netdata`), and replace `netdata:netdata` as shown below if necessary: - -```bash -sudo chmod 0770 cloud.conf -sudo chown netdata:netdata cloud.conf -``` - -Restart your Agent to disable the ACLK. - -### Re-enable the ACLK - -If you first disable the ACLK and any Cloud functionality and then decide you would like to use Cloud, you must either -[reinstall Netdata](/packaging/installer/REINSTALL.md) with Cloud enabled or change the runtime setting in your -`cloud.conf` file. - -If you passed `--disable-cloud` to `netdata-installer.sh` during installation, you must -[reinstall](/packaging/installer/REINSTALL.md) your Agent. Use the same method as before, but pass `--require-cloud` to -the installer. When installation finishes you can [connect your node](/src/claim/README.md#how-to-connect-a-node). - -If you changed the runtime setting in your `var/lib/netdata/cloud.d/cloud.conf` file, edit the file again and change -`enabled` to `yes`: - -```conf -[global] - enabled = yes -``` - -Restart your Agent and [connect your node](/src/claim/README.md#how-to-connect-a-node). - - diff --git a/src/aclk/aclk-schemas/.gitignore b/src/aclk/aclk-schemas/.gitignore new file mode 100644 index 000000000..bd495e9a7 --- /dev/null +++ b/src/aclk/aclk-schemas/.gitignore @@ -0,0 +1,11 @@ +*.pb.go + +#Agent +*.pb.cc +*.pb.h +*.pb.o +*.pb.Po +.dirstamp + +#Jetbrains +.idea diff --git a/src/aclk/aclk-schemas/.travis.yml b/src/aclk/aclk-schemas/.travis.yml new file mode 100644 index 000000000..7c99550fe --- /dev/null +++ b/src/aclk/aclk-schemas/.travis.yml @@ -0,0 +1,4 @@ +--- +language: minimal +install: make deps +script: make CI diff --git a/src/aclk/aclk-schemas/LICENSE b/src/aclk/aclk-schemas/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/src/aclk/aclk-schemas/LICENSE @@ -0,0 +1,674 @@ + 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>. diff --git a/src/aclk/aclk-schemas/Makefile b/src/aclk/aclk-schemas/Makefile new file mode 100644 index 000000000..8f4003070 --- /dev/null +++ b/src/aclk/aclk-schemas/Makefile @@ -0,0 +1,74 @@ +SHELL := /usr/bin/env bash -o pipefail + +# This controls the location of the cache. +PROJECT := cloud-schemas +# This controls the remote HTTPS git location to compare against for breaking changes in CI. +# +# Most CI providers only clone the branch under test and to a certain depth, so when +# running buf check breaking in CI, it is generally preferable to compare against +# the remote repository directly. +# +# Basic authentication is available, see https://buf.build/docs/inputs#https for more details. +HTTPS_GIT := https://github.com/netdata/cloud-schemas.git + +# This controls the version of buf to install and use. +BUF_VERSION := 0.6.0 + +### Everything below this line is meant to be static, i.e. only adjust the above variables. ### + +UNAME_OS := $(shell uname -s) +UNAME_ARCH := $(shell uname -m) +# Buf will be cached to ~/.cache/buf-example. +CACHE_BASE := $(HOME)/.cache/$(PROJECT) +# This allows switching between i.e a Docker container and your local setup without overwriting. +CACHE := $(CACHE_BASE)/$(UNAME_OS)/$(UNAME_ARCH) +# The location where buf will be installed. +CACHE_BIN := $(CACHE)/bin +# Marker files are put into this directory to denote the current version of binaries that are installed. +CACHE_VERSIONS := $(CACHE)/versions + +# Update the $PATH so we can use buf directly +export PATH := $(abspath $(CACHE_BIN)):$(PATH) + +# BUF points to the marker file for the installed version. +# +# If BUF_VERSION is changed, the binary will be re-downloaded. +BUF := $(CACHE_VERSIONS)/buf/$(BUF_VERSION) +$(BUF): + @rm -f $(CACHE_BIN)/buf + @mkdir -p $(CACHE_BIN) + curl -sSL \ + "https://github.com/bufbuild/buf/releases/download/v$(BUF_VERSION)/buf-$(UNAME_OS)-$(UNAME_ARCH)" \ + -o "$(CACHE_BIN)/buf" + chmod +x "$(CACHE_BIN)/buf" + @rm -rf $(dir $(BUF)) + @mkdir -p $(dir $(BUF)) + @touch $(BUF) + +.DEFAULT_GOAL := local + +# deps allows us to install deps without running any checks. + +.PHONY: deps +deps: $(BUF) + +# local is what we run when testing locally. +# This does breaking change detection against our local git repository. + +.PHONY: local +local: $(BUF) + buf check lint ./proto + buf check breaking --against '.git#branch=master' + +# https is what we run when testing in most CI providers. +# This does breaking change detection against our git repository. + +.PHONY: CI +CI: $(BUF) + buf check lint ./proto + buf check breaking --against ".git#branch=master" + +.PHONY: clean +clean: + git clean -xdf + rm -rf $(CACHE_BASE)
\ No newline at end of file diff --git a/src/aclk/aclk-schemas/README.md b/src/aclk/aclk-schemas/README.md new file mode 100644 index 000000000..aa6188977 --- /dev/null +++ b/src/aclk/aclk-schemas/README.md @@ -0,0 +1,2 @@ +# aclk-schemas +Protobuf schemas used in ACLK connection diff --git a/src/aclk/aclk-schemas/buf.yml b/src/aclk/aclk-schemas/buf.yml new file mode 100644 index 000000000..532053d91 --- /dev/null +++ b/src/aclk/aclk-schemas/buf.yml @@ -0,0 +1,9 @@ +build: + roots: + - proto +lint: + use: + - DEFAULT +breaking: + use: + - FILE diff --git a/src/aclk/aclk-schemas/proto/aclk/v1/lib.proto b/src/aclk/aclk-schemas/proto/aclk/v1/lib.proto new file mode 100644 index 000000000..f32c32c6e --- /dev/null +++ b/src/aclk/aclk-schemas/proto/aclk/v1/lib.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package aclk_lib.v1; + +import "google/protobuf/timestamp.proto"; + +option go_package = "aclk_lib/v1;aclklib"; + +// ACLKMessagePosition is used by sequenced messages to define their exact position +message ACLKMessagePosition { + uint64 sequence_id = 1; + // auto generated in Agent's DB upon sequence_id creation + google.protobuf.Timestamp seq_id_created_at = 2; + uint64 previous_sequence_id = 3; +} + +message Capability { + string name = 1; + uint32 version = 2; + // version == 0 is equivalent to not having the capability at all + bool enabled = 3; +} diff --git a/src/aclk/aclk-schemas/proto/agent/v1/cmds.proto b/src/aclk/aclk-schemas/proto/agent/v1/cmds.proto new file mode 100644 index 000000000..c37c00c3a --- /dev/null +++ b/src/aclk/aclk-schemas/proto/agent/v1/cmds.proto @@ -0,0 +1,79 @@ +syntax = "proto3"; +option go_package = "agent/v1;agent"; + +package agent.v1; + +import "google/protobuf/timestamp.proto"; +import "proto/aclk/v1/lib.proto"; + +message CancelPendingRequest { + // must match the ID sent with the request originally made + // other than this agent will not put conditions on it + // and will treat it as opaque string (it simply has to match) + // However this doesn't mean there are no conditions on the id + // made on the request side + string request_id = 1; + + // time when the cancellation request was generated + google.protobuf.Timestamp timestamp = 2; + + // optional might be useful for debugging purposes + string trace_id = 3; +} + +// AgentCommand is sent from the Cloud to the Agent at `/agent/{claim_id}/inbound/v1/cmd/AgentCommand` +// the message includes the resource that the Cloud needs to GET from the Agent HTTP API along with related metadata +message AgentCommand { + // the topic to which the Cloud awaits for the AgentCommandResponse. + // example: `/svc/agent-data-ctrl/2d7b7edd-561e-4aec-8ac1-466a585520f5/resp` + string callback_topic = 1; + // the topic to which the Cloud awaits for the AgentCommandAck. + // example: `/svc/agent-data-ctrl/2d7b7edd-561e-4aec-8ac1-466a585520f5/resp` + string ack_topic = 2; + // unique identifier for the AgentCommand + // example: `617038b3-7c2a-4617-a78f-ab37bd820198` + string message_id = 3; + // defined in milliseconds, the time the Agent has to respond before Cloud + // considering the request as timed-out + // example: `60000` + uint64 timeout = 4; + // defined in milliseconds, the time the Agent has to send back to the Cloud + // an AgentCommandAck message signaling that is still working on the request + // example: `3000` + uint64 ack_timeout = 5; + // the requested Agent resource + // example: `/api/v2/data?query_params_go_here` + string resource = 6; +} + +// AgentCommandAck is sent from the Agent to the Cloud at predefined intervals (`AgentCommand.ack_timeout`) to predefined topic (`AgentCommand.ack_topic`) +// signaling that the Agent is still working to serve an AgentCommand (referenced by the message_id) that the Cloud sent +message AgentCommandAck { + // unique identifier to reference AgentCommand on which the Agent is still working on serving + // example: `617038b3-7c2a-4617-a78f-ab37bd820198` + string message_id = 1; + // the timestamp when the Agent created this AgentCommandAck message + google.protobuf.Timestamp created_at = 2; + // integer revealing the progress of completion to serve the AgentCommand with the given message_id + // example: `25` + uint32 progress_percent = 3; +} + +// AgentCommandResponse is sent from the Agent to the Cloud at `/agent/{claim_id}/inbound/v1/cmd/AgentCommand` +// the message includes the resource that the Cloud needs to GET from the Agent HTTP API along with related metadata +message AgentCommandResponse { + // unique identifier for the AgentCommand + // example: `617038b3-7c2a-4617-a78f-ab37bd820198` + string message_id = 1; + // the (http) status code of the Agent's API response + // example: `200` + uint32 status_code = 2; + // the dumped raw (http) response the Agent's API returned + bytes response = 3; + // the Agent's timestamp (aka legacy `timestamp`) + google.protobuf.Timestamp timestamp = 4; + // the timestamp when the Agent received the AgentCommand for execution (aka legacy `t-rx`) + google.protobuf.Timestamp received_at = 5; + // the amount of microseconds the Agent needed to execute the HTTP request of the AgentCommand (aka legacy`t-exec`) + uint64 exec_time = 6; +} diff --git a/src/aclk/aclk-schemas/proto/agent/v1/connection.proto b/src/aclk/aclk-schemas/proto/agent/v1/connection.proto new file mode 100644 index 000000000..a792214ff --- /dev/null +++ b/src/aclk/aclk-schemas/proto/agent/v1/connection.proto @@ -0,0 +1,71 @@ +syntax = "proto3"; +option go_package = "agent/v1;agent"; + +package agent.v1; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; +import "proto/aclk/v1/lib.proto"; + +message UpdateAgentConnection { + string claim_id = 1; + bool reachable = 2; + + int64 session_id = 3; + + ConnectionUpdateSource update_source = 4; + + // mqtt_broker_addr shard to use for reaching the agent + // cloud injects this information + string mqtt_broker_addr = 5; + + google.protobuf.Timestamp updated_at = 6; + + // vmq_instance_id broker shard to use for reaching the agent + // cloud injects this information + int32 vmq_instance_id = 7; + + // > 15 optional fields: + // How long the system was running until connection (only applicable when reachable=true) + google.protobuf.Duration system_uptime = 15; + + // How long the netdata agent was running until connection (only applicable when reachable=true) + google.protobuf.Duration agent_uptime = 16; + + repeated aclk_lib.v1.Capability capabilities = 17; +} + +message SendNodeInstances { + string claim_id = 1; + Config config = 2; + // The ID of the space where this agent is claimed. + string space_id = 3; +} + +// ConnectionUpdateSource is to determine whether the connection update was issued +enum ConnectionUpdateSource { + // CONNECTION_UPDATE_SOURCE_UNSPECIFIED acts as default value for protobuf and is never specified + CONNECTION_UPDATE_SOURCE_UNSPECIFIED = 0; + // CONNECTION_UPDATE_SOURCE_AGENT A direct message from an agent + CONNECTION_UPDATE_SOURCE_AGENT = 1; + // CONNECTION_UPDATE_SOURCE_LWT message delivered as the Last Will and Testiment from MQTT broker if an agent connection with the broker is lost + CONNECTION_UPDATE_SOURCE_LWT = 2; + // CONNECTION_UPDATE_SOURCE_HEURISTIC A cloud generated message to sanitize incorrect internal state + CONNECTION_UPDATE_SOURCE_HEURISTIC = 3; +} + +message AgentConfig { + string dashboards = 1; + string alert_notifications = 2; +} + +message CloudConfig { + string dashboards = 1; + string alert_notifications = 2; +} + +message Config { + reserved 1 to 3; + AgentConfig agent_config = 4; + CloudConfig cloud_config = 5; +} diff --git a/src/aclk/aclk-schemas/proto/agent/v1/disconnect.proto b/src/aclk/aclk-schemas/proto/agent/v1/disconnect.proto new file mode 100644 index 000000000..852ef702a --- /dev/null +++ b/src/aclk/aclk-schemas/proto/agent/v1/disconnect.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package agent.v1; + +import "google/protobuf/timestamp.proto"; + +option go_package = "agent/v1;agent"; + +// Sent by Cloud to instruct Agent to disconnect ASAP +message DisconnectReq { + uint64 reconnect_after_seconds = 1; + bool permaban = 2; + google.protobuf.Timestamp created_at = 3; + uint32 error_code = 4; + string error_description = 5; +} diff --git a/src/aclk/aclk-schemas/proto/alarm/v1/config.proto b/src/aclk/aclk-schemas/proto/alarm/v1/config.proto new file mode 100644 index 000000000..97050fecd --- /dev/null +++ b/src/aclk/aclk-schemas/proto/alarm/v1/config.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package alarms.v1; + +option go_package = "alarms/v1;alarms"; + +message SendAlarmConfiguration{ + string config_hash = 1; +} + +message ProvideAlarmConfiguration { + string config_hash = 1; + AlarmConfiguration config = 2; +} + +message AlarmConfiguration{ + string alarm = 1; + string template = 2; + string on_chart = 3; + + string classification = 4; + string type = 5; + string component = 6; + + string os = 7; + string hosts = 8; + string plugin = 9; + string module = 10; + string charts = 11; + string families = 12 [deprecated=true]; + string lookup = 13; + string every = 14; + string units = 15; + + string green = 16; + string red = 17; + + string calculation_expr = 18; + string warning_expr = 19; + string critical_expr = 20; + + string recipient = 21; + string exec = 22; + string delay = 23; + string repeat = 24; + string info = 25; + string options = 26; + string host_labels = 27; + + //parsed values from above config values + //indicated by p_ + int32 p_db_lookup_after = 28; + int32 p_db_lookup_before = 29; + string p_db_lookup_dimensions = 30; + string p_db_lookup_method = 31; + string p_db_lookup_options = 32; + int32 p_update_every = 33; + + string chart_labels = 34; + string summary = 35; +} diff --git a/src/aclk/aclk-schemas/proto/alarm/v1/stream.proto b/src/aclk/aclk-schemas/proto/alarm/v1/stream.proto new file mode 100644 index 000000000..f782becdc --- /dev/null +++ b/src/aclk/aclk-schemas/proto/alarm/v1/stream.proto @@ -0,0 +1,151 @@ +syntax = "proto3"; + +package alarms.v1; + +import "google/protobuf/timestamp.proto"; + +option go_package = "alarms/v1;alarms"; + +message SendAlarmLogHealth { + string node_id = 1; +} + +message AlarmLogHealth { + string claim_id = 1; + string node_id = 2; + bool enabled = 3; + AlarmLogStatus status = 4; + LogEntries log_entries = 5; +} + +message LogEntries { + int64 first_sequence_id = 1; + google.protobuf.Timestamp first_when = 2; + + int64 last_sequence_id = 3; + google.protobuf.Timestamp last_when = 4; +} + +enum AlarmLogStatus { + ALARM_LOG_STATUS_UNSPECIFIED = 0; + ALARM_LOG_STATUS_RUNNING = 1; + ALARM_LOG_STATUS_IDLE = 2; +} + +message StartAlarmStreaming { + string node_id = 1; + uint64 batch_id = 2 [deprecated=true]; + uint64 start_sequnce_id = 3 [deprecated=true]; + // Instructs the agent to sync all configured alarms + bool resets = 4; + uint64 version = 5; +} + +message SendAlarmCheckpoint { + string node_id = 1; + string claim_id = 2; + uint64 version = 3; +} + +message AlarmCheckpoint { + string node_id = 1; + string claim_id = 2; + bytes checksum = 3; +} + +message AlarmLogEntry { + string node_id = 1; + string claim_id = 2; + + // The chart's id field + string chart = 3; + string name = 4; + string family = 5 [deprecated=true]; + uint64 batch_id = 6 [deprecated=true]; + uint64 sequence_id = 7 [deprecated=true]; + uint64 when = 8; + + string config_hash = 9; + + int32 utc_offset = 10; + string timezone = 11; + + // Paths that can be custom for the same alarm, but depend on installation path for each user. Should be here or in config ? + string exec_path = 12; + string conf_source = 13; + string command = 14; + + // In seconds, uint32 is safe ? + uint32 duration = 15; + uint32 non_clear_duration = 16; + + AlarmStatus status = 17; + AlarmStatus old_status = 18; + uint64 delay = 19; + uint64 delay_up_to_timestamp = 20; + // Todo: verify that we need these. sequence_id doesn't suffice? + // uint64 updated_by_id = 12; + // uint64 updates_id = 13; + uint64 last_repeat = 21; + bool silenced = 22; + + // Check if string values are needed + string value_string = 23; + string old_value_string = 24; + + double value = 25; + double old_value = 26; + + // Updated alarm entry, when the status of the alarm has been updated by a later entry + bool updated = 27; + + // Rendered_info + string rendered_info = 28; + + // The chart's context field + string chart_context = 29; + + // Counter of alert transitions for this alert chain + uint64 event_id = 30; + + // A unique uuid for this alert event + string transition_id = 31; + + // The chart's name field + string chart_name = 32; + + // The rendered summary + string summary = 33; + uint64 alert_version = 34; +} + +enum AlarmStatus { + ALARM_STATUS_NULL = 0; + ALARM_STATUS_UNKNOWN = 1; + ALARM_STATUS_REMOVED = 2; + ALARM_STATUS_NOT_A_NUMBER = 3; + ALARM_STATUS_CLEAR = 4; + ALARM_STATUS_WARNING = 5; + ALARM_STATUS_CRITICAL = 6; +} + +// SendAlarmSnapshot: send from cloud to the agent, to initiate an AlarmSnapshot image of current alarms back to the cloud +message SendAlarmSnapshot { + string node_id = 1; + string claim_id = 2; + uint64 snapshot_id = 3 [deprecated=true]; + uint64 sequence_id = 4 [deprecated=true]; + string snapshot_uuid = 5; +} + +// Agent responds with AlarmSnapshot to a SendAlarmSnapshot message +message AlarmSnapshot{ + string node_id = 1; + string claim_id = 2; + uint64 snapshot_id = 3 [deprecated=true]; // Same id from SendAlarmSnapshot message + uint32 chunks = 4; // In case full snapshot can not fit in a single message, indicates the total number of messages for this snapshot_id + uint32 chunk_size = 5; // How many alerts this chunk contains + uint32 chunk = 6; // Chunk index of this message + repeated AlarmLogEntry alarms = 7; // a list of AlarmLogEntry's + string snapshot_uuid = 8; +} diff --git a/src/aclk/aclk-schemas/proto/chart/v1/config.proto b/src/aclk/aclk-schemas/proto/chart/v1/config.proto new file mode 100644 index 000000000..f0c5e3a35 --- /dev/null +++ b/src/aclk/aclk-schemas/proto/chart/v1/config.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package chart.v1; + +option go_package = "chart/config/v1;chartconfig"; + +// UpdateChartConfigs command contains the list of missing chart configs from the cloud to agent +message UpdateChartConfigs { + // claim_id, node_id pair is used to identify the Node Instance + string claim_id = 1; + string node_id = 2; + // list of config hashes missing from cloud and requested from the agent + repeated string config_hashes = 3; +} + +message ChartConfigsUpdated { + repeated ChartConfigUpdated configs = 1; +} + +message ChartConfigUpdated { + string type = 1; + string family = 2; + string context = 3; + string title = 4; + uint64 priority = 5; + string plugin = 6; + string module = 7; + ChartType chart_type = 8; + string units = 9; + string config_hash = 10; +} + +enum ChartType { + LINE = 0; + AREA = 1; + STACKED = 2; +} diff --git a/src/aclk/aclk-schemas/proto/chart/v1/dimension.proto b/src/aclk/aclk-schemas/proto/chart/v1/dimension.proto new file mode 100644 index 000000000..8bcb564b8 --- /dev/null +++ b/src/aclk/aclk-schemas/proto/chart/v1/dimension.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package chart.v1; + +import "google/protobuf/timestamp.proto"; + +import "proto/aclk/v1/lib.proto"; + +option go_package = "chart/dimension/v1;chartdimension"; + +// ChartDimensionUpdated is a single event sent from the Agent to the Cloud containing chart dimension data. +// +// ChartDimensionUpdated messages are dispatched in bulk to the Cloud wrapped in ChartsAndDimensionsUpdated messages. +message ChartDimensionUpdated { + string id = 1; + string chart_id = 2; + string node_id = 3; + string claim_id = 4; + string name = 5; + google.protobuf.Timestamp created_at = 6; + // null value means that the dimension is currently collected (live) + google.protobuf.Timestamp last_timestamp = 7; + aclk_lib.v1.ACLKMessagePosition position = 8; +} diff --git a/src/aclk/aclk-schemas/proto/chart/v1/instance.proto b/src/aclk/aclk-schemas/proto/chart/v1/instance.proto new file mode 100644 index 000000000..25c99e7c7 --- /dev/null +++ b/src/aclk/aclk-schemas/proto/chart/v1/instance.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package chart.v1; + +import "proto/aclk/v1/lib.proto"; + +option go_package = "chart/instance/v1;chartinstance"; + +// ChartInstanceUpdated is a single event sent from the Agent to the Cloud containing chart instance data. +// +// ChartInstanceUpdated messages are dispatched in bulk to the Cloud wrapped in ChartsAndDimensionsUpdated messages. +message ChartInstanceUpdated { + string id = 1; + string claim_id = 2; + string node_id = 3; + string name = 4; + map<string, string> chart_labels = 5; + MemoryMode memory_mode = 6; + // in seconds + uint32 update_every_interval = 7; + string config_hash = 8; + aclk_lib.v1.ACLKMessagePosition position = 9; +} + +enum MemoryMode { + NONE = 0; + RAM = 1; + MAP = 2; + SAVE = 3; + ALLOC = 4; + DB_ENGINE = 5; +} diff --git a/src/aclk/aclk-schemas/proto/chart/v1/stream.proto b/src/aclk/aclk-schemas/proto/chart/v1/stream.proto new file mode 100644 index 000000000..9473538f2 --- /dev/null +++ b/src/aclk/aclk-schemas/proto/chart/v1/stream.proto @@ -0,0 +1,86 @@ +syntax = "proto3"; + +package chart.v1; + +import "google/protobuf/timestamp.proto"; + +import "proto/chart/v1/instance.proto"; +import "proto/chart/v1/dimension.proto"; + +option go_package = "chart/stream/v1;chartstream"; + +// StreamChartsAndDimensions is a Command produced by the Cloud, consumed by the Agent. +// +// It instructs the Agent to start sending ChartsAndDimensionsUpdated messages for a NodeInstance +// after the last sequence_id that the Cloud has successfully ingested. +message StreamChartsAndDimensions { + // claim_id, node_id pair is used to identify the Node Instance + string claim_id = 1; + string node_id = 2; + + // sequence_id last verified sequence sent by the Agent + uint64 sequence_id = 3; + // batch_id identifies the stream_id and gets incremented every time the Cloud sends a new StreamChartsAndDimensions command + uint64 batch_id = 4; + // seq_id_created_at autogenerated timestamp in Agent's DB upon sequence_id creation + google.protobuf.Timestamp seq_id_created_at = 5; +} + + +// ChartsAndDimensionsAck is an Event produced by the Cloud, consumed by the Agent. +// +// This Event is an acknowledgment from the Cloud side that Chart messages up to a specific last_sequence_id +// have been successfully ingested, and could be potentially deleted from the Agent's DB. +message ChartsAndDimensionsAck { + string claim_id = 1; + string node_id = 2; + // the last verified stored message's seq_id + uint64 last_sequence_id = 3; +} + +// ResetChartMessages is a Command produced by the Agent, consumed by the Cloud. +// +// This Command instructs the Cloud to clear its Chart state for a specific NodeInstance and re-sync +// because of a ResetReason. +message ResetChartMessages { + // claim_id, node_id pair is used to identify the Node Instance + string claim_id = 1; + string node_id = 2; + + ResetReason reason = 3; +} + +enum ResetReason { + DB_EMPTY = 0; + SEQ_ID_NOT_EXISTS = 1; + TIMESTAMP_MISMATCH = 2; +} + +// ChartsAndDimensionsUpdated is a wrapper Event (`fat` message) produced by the Agent, consumed by the Cloud. +// +// It potentially includes a collection of ChartInstanceUpdated messages and|or a collection of ChartDimensionUpdated messages. +message ChartsAndDimensionsUpdated { + repeated chart.v1.ChartInstanceUpdated charts = 1; + repeated chart.v1.ChartDimensionUpdated dimensions = 2; + uint64 batch_id = 3; +} + +// RetentionUpdated includes the available retentions (in seconds) of the dimensions - of a specific node instance and memory-mode - +// on a per update_every level. +// This message is sent over upon Agent Database rotation events to inform the Cloud in total about the newly updated data retentions +// of a node instance's dimensions. +message RetentionUpdated { + // claim_id, node_id pair is used to identify the Node Instance + string claim_id = 1; + string node_id = 2; + // the memory_mode used by the node instance's chart instances + chart.v1.MemoryMode memory_mode = 3; + // this mapping identifies the newly updated available retention (in seconds) of the node instance's dimensions + // the keys are the update_every categories of various dimensions (1, 2, 4, 10 etc.), + // and the values are the available retention (in seconds) of each dimension belonging to the update_every category + // denoted by the key + map<uint32, uint32> interval_durations = 4; + // the timestamp when the db rotation event took place. Can be used in conjunction with the interval_durations + // to compute the beginning of each `updated_every` group's retention + google.protobuf.Timestamp rotation_timestamp = 5; +} diff --git a/src/aclk/aclk-schemas/proto/context/v1/context.proto b/src/aclk/aclk-schemas/proto/context/v1/context.proto new file mode 100644 index 000000000..eb771f8eb --- /dev/null +++ b/src/aclk/aclk-schemas/proto/context/v1/context.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +package context.v1; + +option go_package = "context/v1;context"; + +// ContextsUpdated is an Event produced by the Agent, consumed by the Cloud. +// +// it contains a collection of ContextUpdated messages for a specific NodeInstance. +message ContextsUpdated { + // contexUpdates contains the collection of context updates + repeated ContextUpdated contextUpdates = 1; + // claim_id, node_id pair identifies the node instance + string claim_id = 2; + string node_id = 3; + // version_hash is the contexts version_hash result the cloud should + // get after applying this message updates. + uint64 version_hash = 4; + // it's and always increasing number to compare + // which version_hash is more recent between multiple + // ContextsUpdated messages. Bigger means more recent. + uint64 created_at = 5; +} + +// ContextUpdated contains context data. +message ContextUpdated { + // context id + string id = 1; + // context version is an epoch in seconds. + uint64 version = 2; + // first_entry, last_entry are epochs in seconds + uint64 first_entry = 3; + uint64 last_entry = 4; + // deleted flag is used to signal a context deletion + bool deleted = 5; + // context configuration fields + string title = 6; + uint64 priority = 7; + string chart_type = 8; + string units = 9; + string family = 10; +} + +// ContextsSnapshot is an Event produced by the Agent, consumed by the Cloud. +// +// it contains a snapshot of the existing contexts on the Agent. +// snapshot version and context versions are epochs in seconds so we can +// identify if a context version was generated after a specific snapshot. +message ContextsSnapshot { + // contexts contains the collection of existing contexts + repeated ContextUpdated contexts = 1; + // claim_id, node_id pair identifies the node instance + string claim_id = 2; + string node_id = 3; + // version is an epoch in seconds + uint64 version = 4; +} diff --git a/src/aclk/aclk-schemas/proto/context/v1/stream.proto b/src/aclk/aclk-schemas/proto/context/v1/stream.proto new file mode 100644 index 000000000..a6e7e3abf --- /dev/null +++ b/src/aclk/aclk-schemas/proto/context/v1/stream.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package context.v1; + +option go_package = "context/v1;context"; + +// ContextsCheckpoint is a Command produced by the Cloud, consumed by the Agent. +// +// It informs the Agent the contexts' version_hash that the cloud has for a specific NodeInstance. +message ContextsCheckpoint { + // claim_id, node_id pair is used to identify the NodeInstance. + string claim_id = 1; + string node_id = 2; + // version_hash tells the Agent the current version hash for the contexts received + // if the version hash calculated by the Agent is different, Agent will request + // to re-sync all contexts. + uint64 version_hash= 3; +} + +// StopStreamingContexts is a Command produced by the Cloud, consumed by the Agent. +// +// It instructs the Agent to stop sending ContextsUpdated messages for a NodeInstance +// due to a reason. +message StopStreamingContexts { + // claim_id, node_id pair is used to identify the node instance + string claim_id = 1; + string node_id = 2; + + StopStreamingContextsReason reason = 3; +} + +enum StopStreamingContextsReason { + RATE_LIMIT_EXCEEDED = 0; +} diff --git a/src/aclk/aclk-schemas/proto/nodeinstance/connection/v1/connection.proto b/src/aclk/aclk-schemas/proto/nodeinstance/connection/v1/connection.proto new file mode 100644 index 000000000..f0c02461e --- /dev/null +++ b/src/aclk/aclk-schemas/proto/nodeinstance/connection/v1/connection.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; +option go_package = "nodeinstance/connection/v1;nodeinstanceconnection"; + +package nodeinstance.v1; + +import "google/protobuf/timestamp.proto"; +import "proto/aclk/v1/lib.proto"; + +message UpdateNodeInstanceConnection { + string claim_id = 1; + string node_id = 2; + + // liveness whether node data are actively streamed to the agent. + bool liveness = 3; + + // queryable whether the agent has data about the node. + bool queryable = 4; + + int64 session_id = 5; + + google.protobuf.Timestamp updated_at = 6; + + // mqtt_broker_addr shard to use for reaching the agent + // cloud injects this information. + string mqtt_broker_addr = 7; + + // vmq_instance_id broker shard to use for reaching the agent + // cloud injects this information. + int32 vmq_instance_id = 8; + + // hops is the number of streaming hops between collection of node data + // and the claimed agent. Zero if no streaming is involved. + int32 hops = 9; + + // capabilities of node instance NOT the NODE or agent!!! + repeated aclk_lib.v1.Capability capabilities = 10; +} diff --git a/src/aclk/aclk-schemas/proto/nodeinstance/create/v1/creation.proto b/src/aclk/aclk-schemas/proto/nodeinstance/create/v1/creation.proto new file mode 100644 index 000000000..922337154 --- /dev/null +++ b/src/aclk/aclk-schemas/proto/nodeinstance/create/v1/creation.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; +option go_package = "node_instance/creation/v1;node_instancecreation"; + +package nodeinstance.create.v1; + +message CreateNodeInstance { + // Claim ID of the Agent the Node Instance belongs to. + // Eventually, the NodeInstance will be identified by the compilation of + // this claim_id and NodeID returned by `CreateNodeInstanceResult` + string claim_id = 1; + // Machine GUID of the Machine the request comes from + // Used to look for an existing NodeID in the space claim_id belongs to + string machine_guid = 2; + string hostname = 3; + + // vmq_instance_id broker shard to use for reaching the agent + // cloud injects this information. + int32 vmq_instance_id = 4; + // mqtt_broker_addr shard to use for reaching the agent + // cloud injects this information. + string mqtt_broker_addr = 5; + + // hops is the number of streaming hops between collection of node data + // and the claimed agent. Zero if no streaming is involved. + int32 hops = 6; +} + +message CreateNodeInstanceResult { + string node_id = 1; + string machine_guid = 2; +} + diff --git a/src/aclk/aclk-schemas/proto/nodeinstance/info/v1/info.proto b/src/aclk/aclk-schemas/proto/nodeinstance/info/v1/info.proto new file mode 100644 index 000000000..7aa9d0448 --- /dev/null +++ b/src/aclk/aclk-schemas/proto/nodeinstance/info/v1/info.proto @@ -0,0 +1,148 @@ +syntax = "proto3"; +option go_package = "node_instance/info/v1;nodeinstanceinfo"; + +package nodeinstance.info.v1; + +import "google/protobuf/timestamp.proto"; +import "proto/aclk/v1/lib.proto"; + +// UpdateNodeInfo (Command) +// +// pulsar topic: `UpdateNodeInfo` (sharded) +// +// key: `claim_id,node_id` +// +// Publishers: `netdata/agent` +// Subscribers: `cloud-node-mqtt-output-service` +// +// When: +// On nodeinstance connect +// +message UpdateNodeInfo { + string node_id = 7; + + string claim_id = 1; + + NodeInfo data = 2; + // to be obsoleted in future + // all new fields should go into node_info + // or node_instance_info respectively + + google.protobuf.Timestamp updated_at = 3; + + int64 session_id = 4; + + string machine_guid = 5; + + bool child = 6; + + MachineLearningInfo ml_info = 8; + // to be obsoleted in far future + + NodeInfo2 node_info = 9; + // node_info shows data about actual node + // for example feature (ml) for this + // node (child) might be available/enabled on the node (child) directly + // but not available trough the parent (node_instance) + + NodeInstanceInfo node_instance_info = 10; + // info specific to the node_instance for this node available trough agent + // who sends this message. + // e.g. machine learning is enabled for this node and processing is done + // by the actual agent (parent). (child itself might or might not be + // ml ml_capable by itself (see node_info)) +} + +message NodeInfo2 { + repeated aclk_lib.v1.Capability capabilities = 1; +} + +message NodeInstanceInfo { + repeated aclk_lib.v1.Capability capabilities = 1; +} + +// NodeInfo describes the metadata of a node +message NodeInfo { + string name = 1; + + string os = 2; + string os_name = 3; + string os_version = 4; + + string kernel_name = 5; + string kernel_version = 6; + + string architecture = 7; + + // number of cpu cores in the node + uint32 cpus = 8; + + // human readable (value + unit) frequency of cpu + string cpu_frequency = 9; + + // human readable (value + unit) size of node's memory + string memory = 10; + + // human readable (value + unit) size of all (sum) node's disks + string disk_space = 11; + + // version of the netdata agent + string version = 12; + + // release channel of netdata agent (example: nightly) + string release_channel = 13; + + string timezone = 14; + + // virtualization_type example: kvm (optional) + string virtualization_type = 15; + + // container_type example: docker (optional) + string container_type = 16; + + string custom_info = 17; + + // [Obsolete] repeated string services = 18; + reserved 18; + + string machine_guid = 19; + + // [Obsolete] repeated MirroredHostStatus mirrored_hosts_status = 20; + reserved 20; + + map<string, string> host_labels = 21; + + MachineLearningInfo ml_info = 22; + + // [Obsolete] repeated string collectors = 23; + reserved 23; +} + +message MachineLearningInfo { + // have ML capability + bool ml_capable = 1; + + // runs ML functionality + bool ml_enabled = 2; +} + +// UpdateNodeCollectors (Command) +// +// key: `claim_id,node_id` +// +// Publishers: `netdata/agent` +// +// When: +// On nodeinstance connect (after agent settles) and on detection of change of collectors +// + +message CollectorInfo { + string module = 1; + string plugin = 2; +} + +message UpdateNodeCollectors { + string claim_id = 1; + string node_id = 2; + repeated CollectorInfo collectors = 3; +} diff --git a/src/aclk/aclk.c b/src/aclk/aclk.c index 389d7455f..7bc620a61 100644 --- a/src/aclk/aclk.c +++ b/src/aclk/aclk.c @@ -2,8 +2,6 @@ #include "aclk.h" -#ifdef ENABLE_ACLK -#include "aclk_stats.h" #include "mqtt_websockets/mqtt_wss_client.h" #include "aclk_otp.h" #include "aclk_tx_msgs.h" @@ -14,7 +12,6 @@ #include "https_client.h" #include "schema-wrappers/schema_wrappers.h" #include "aclk_capas.h" - #include "aclk_proxy.h" #ifdef ACLK_LOG_CONVERSATION_DIR @@ -23,20 +20,38 @@ #include <fcntl.h> #endif -#define ACLK_STABLE_TIMEOUT 3 // Minimum delay to mark AGENT as stable - -#endif /* ENABLE_ACLK */ - int aclk_pubacks_per_conn = 0; // How many PubAcks we got since MQTT conn est. int aclk_rcvd_cloud_msgs = 0; int aclk_connection_counter = 0; -int disconnect_req = 0; -int aclk_connected = 0; +static bool aclk_connected = false; +static inline void aclk_set_connected(void) { + __atomic_store_n(&aclk_connected, true, __ATOMIC_RELAXED); +} +static inline void aclk_set_disconnected(void) { + __atomic_store_n(&aclk_connected, false, __ATOMIC_RELAXED); +} + +inline bool aclk_online(void) { + return __atomic_load_n(&aclk_connected, __ATOMIC_RELAXED); +} + +bool aclk_online_for_contexts(void) { + return aclk_online() && aclk_query_scope_has(HTTP_ACL_METRICS); +} + +bool aclk_online_for_alerts(void) { + return aclk_online() && aclk_query_scope_has(HTTP_ACL_ALERTS); +} + +bool aclk_online_for_nodes(void) { + return aclk_online() && aclk_query_scope_has(HTTP_ACL_NODES); +} + int aclk_ctx_based = 0; int aclk_disable_runtime = 0; -int aclk_stats_enabled; -int aclk_kill_link = 0; + +ACLK_DISCONNECT_ACTION disconnect_req = ACLK_NO_DISCONNECT; usec_t aclk_session_us = 0; time_t aclk_session_sec = 0; @@ -49,13 +64,8 @@ float last_backoff_value = 0; time_t aclk_block_until = 0; -#ifdef ENABLE_ACLK mqtt_wss_client mqttwss_client; -//netdata_mutex_t aclk_shared_state_mutex = NETDATA_MUTEX_INITIALIZER; -//#define ACLK_SHARED_STATE_LOCK netdata_mutex_lock(&aclk_shared_state_mutex) -//#define ACLK_SHARED_STATE_UNLOCK netdata_mutex_unlock(&aclk_shared_state_mutex) - struct aclk_shared_state aclk_shared_state = { .mqtt_shutdown_msg_id = -1, .mqtt_shutdown_msg_rcvd = 0 @@ -152,19 +162,6 @@ biofailed: return 1; } -static int wait_till_cloud_enabled() -{ - nd_log(NDLS_DAEMON, NDLP_INFO, - "Waiting for Cloud to be enabled"); - - while (!netdata_cloud_enabled) { - sleep_usec(USEC_PER_SEC * 1); - if (!service_running(SERVICE_ACLK)) - return 1; - } - return 0; -} - /** * Will block until agent is claimed. Returns only if agent claimed * or if agent needs to shutdown. @@ -174,15 +171,13 @@ static int wait_till_cloud_enabled() */ static int wait_till_agent_claimed(void) { - //TODO prevent malloc and freez - char *agent_id = get_agent_claimid(); - while (likely(!agent_id)) { + ND_UUID uuid = claim_id_get_uuid(); + while (likely(UUIDiszero(uuid))) { sleep_usec(USEC_PER_SEC * 1); if (!service_running(SERVICE_ACLK)) return 1; - agent_id = get_agent_claimid(); + uuid = claim_id_get_uuid(); } - freez(agent_id); return 0; } @@ -204,9 +199,9 @@ static int wait_till_agent_claim_ready() // The NULL return means the value was never initialised, but this value has been initialized in post_conf_load. // We trap the impossible NULL here to keep the linter happy without using a fatal() in the code. - char *cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL); + const char *cloud_base_url = cloud_config_url_get(); if (cloud_base_url == NULL) { - netdata_log_error("Do not move the cloud base url out of post_conf_load!!"); + netdata_log_error("Do not move the \"url\" out of post_conf_load!!"); return 1; } @@ -214,7 +209,7 @@ static int wait_till_agent_claim_ready() // TODO make it without malloc/free memset(&url, 0, sizeof(url_t)); if (url_parse(cloud_base_url, &url)) { - netdata_log_error("Agent is claimed but the URL in configuration key \"cloud base url\" is invalid, please fix"); + netdata_log_error("Agent is claimed but the URL in configuration key \"url\" is invalid, please fix"); url_t_destroy(&url); sleep(5); continue; @@ -230,30 +225,6 @@ static int wait_till_agent_claim_ready() return 1; } -void aclk_mqtt_wss_log_cb(mqtt_wss_log_type_t log_type, const char* str) -{ - switch(log_type) { - case MQTT_WSS_LOG_ERROR: - case MQTT_WSS_LOG_FATAL: - nd_log(NDLS_DAEMON, NDLP_ERR, "%s", str); - return; - - case MQTT_WSS_LOG_WARN: - nd_log(NDLS_DAEMON, NDLP_WARNING, "%s", str); - return; - - case MQTT_WSS_LOG_INFO: - nd_log(NDLS_DAEMON, NDLP_INFO, "%s", str); - return; - - case MQTT_WSS_LOG_DEBUG: - return; - - default: - nd_log(NDLS_DAEMON, NDLP_ERR, "Unknown log type from mqtt_wss"); - } -} - static void msg_callback(const char *topic, const void *msg, size_t msglen, int qos) { UNUSED(qos); @@ -299,9 +270,9 @@ static void puback_callback(uint16_t packet_id) aclk_tbeb_reset(); } -#ifdef NETDATA_INTERNAL_CHECKS - aclk_stats_msg_puback(packet_id); -#endif +//#ifdef NETDATA_INTERNAL_CHECKS +// aclk_stats_msg_puback(packet_id); +//#endif if (aclk_shared_state.mqtt_shutdown_msg_id == (int)packet_id) { nd_log(NDLS_DAEMON, NDLP_DEBUG, @@ -311,21 +282,9 @@ static void puback_callback(uint16_t packet_id) } } -static int read_query_thread_count() -{ - int threads = MIN(get_netdata_cpus()/2, 6); - threads = MAX(threads, 2); - threads = config_get_number(CONFIG_SECTION_CLOUD, "query thread count", threads); - if(threads < 1) { - netdata_log_error("You need at least one query thread. Overriding configured setting of \"%d\"", threads); - threads = 1; - config_set_number(CONFIG_SECTION_CLOUD, "query thread count", threads); - } - return threads; -} - void aclk_graceful_disconnect(mqtt_wss_client client); +bool schedule_node_update = false; /* Keeps connection alive and handles all network communications. * Returns on error or when netdata is shutting down. * @param client instance of mqtt_wss_client @@ -334,7 +293,6 @@ void aclk_graceful_disconnect(mqtt_wss_client client); */ static int handle_connection(mqtt_wss_client client) { - time_t last_periodic_query_wakeup = now_monotonic_sec(); while (service_running(SERVICE_ACLK)) { // timeout 1000 to check at least once a second // for netdata_exit @@ -343,30 +301,32 @@ static int handle_connection(mqtt_wss_client client) return 1; } - if (disconnect_req || aclk_kill_link) { - nd_log(NDLS_DAEMON, NDLP_NOTICE, - "Going to restart connection due to disconnect_req=%s (cloud req), aclk_kill_link=%s (reclaim)", - disconnect_req ? "true" : "false", - aclk_kill_link ? "true" : "false"); + if (disconnect_req != ACLK_NO_DISCONNECT) { + const char *reason; + switch (disconnect_req) { + case ACLK_CLOUD_DISCONNECT: + reason = "cloud request"; + break; + case ACLK_PING_TIMEOUT: + reason = "ping timeout"; + schedule_node_update = true; + break; + case ACLK_RELOAD_CONF: + reason = "reclaim"; + break; + default: + reason = "unknown"; + break; + } + + nd_log(NDLS_DAEMON, NDLP_NOTICE, "Going to restart connection due to \"%s\"", reason); - disconnect_req = 0; - aclk_kill_link = 0; + disconnect_req = ACLK_NO_DISCONNECT; aclk_graceful_disconnect(client); - aclk_queue_unlock(); aclk_shared_state.mqtt_shutdown_msg_id = -1; aclk_shared_state.mqtt_shutdown_msg_rcvd = 0; return 1; } - - // mqtt_wss_service will return faster than in one second - // if there is enough work to do - time_t now = now_monotonic_sec(); - if (last_periodic_query_wakeup < now) { - // wake up at least one Query Thread at least - // once per second - last_periodic_query_wakeup = now; - QUERY_THREAD_WAKEUP; - } } return 0; } @@ -386,13 +346,12 @@ static inline void mqtt_connected_actions(mqtt_wss_client client) else mqtt_wss_subscribe(client, topic, 1); - aclk_stats_upd_online(1); - aclk_connected = 1; + aclk_set_connected(); aclk_pubacks_per_conn = 0; aclk_rcvd_cloud_msgs = 0; aclk_connection_counter++; - aclk_topic_cache_iter_t iter = ACLK_TOPIC_CACHE_ITER_T_INITIALIZER; + size_t iter = 0; while ((topic = (char*)aclk_topic_cache_iterate(&iter)) != NULL) mqtt_wss_set_topic_alias(client, topic); @@ -404,9 +363,6 @@ void aclk_graceful_disconnect(mqtt_wss_client client) nd_log(NDLS_DAEMON, NDLP_DEBUG, "Preparing to gracefully shutdown ACLK connection"); - aclk_queue_lock(); - aclk_queue_flush(); - aclk_shared_state.mqtt_shutdown_msg_id = aclk_send_agent_connection_update(client, 0); time_t t = now_monotonic_sec(); @@ -425,9 +381,8 @@ void aclk_graceful_disconnect(mqtt_wss_client client) nd_log(NDLS_DAEMON, NDLP_WARNING, "ACLK link is down"); nd_log(NDLS_ACCESS, NDLP_WARNING, "ACLK DISCONNECTED"); - aclk_stats_upd_online(0); last_disconnect_time = now_realtime_sec(); - aclk_connected = 0; + aclk_set_disconnected(); nd_log(NDLS_DAEMON, NDLP_DEBUG, "Attempting to gracefully shutdown the MQTT/WSS connection"); @@ -602,9 +557,9 @@ static int aclk_attempt_to_connect(mqtt_wss_client client) bool fallback_ipv4 = false; while (service_running(SERVICE_ACLK)) { - aclk_cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL); + aclk_cloud_base_url = cloud_config_url_get(); if (aclk_cloud_base_url == NULL) { - error_report("Do not move the cloud base url out of post_conf_load!!"); + error_report("Do not move the \"url\" out of post_conf_load!!"); aclk_status = ACLK_STATUS_NO_CLOUD_URL; return -1; } @@ -802,12 +757,7 @@ static int aclk_attempt_to_connect(mqtt_wss_client client) */ void *aclk_main(void *ptr) { - struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr; - - struct aclk_stats_thread *stats_thread = NULL; - - struct aclk_query_threads query_threads; - query_threads.thread_list = NULL; + struct netdata_static_thread *static_thread = ptr; ACLK_PROXY_TYPE proxy_type; aclk_get_proxy(&proxy_type); @@ -817,24 +767,12 @@ void *aclk_main(void *ptr) return NULL; } - unsigned int proto_hdl_cnt = aclk_init_rx_msg_handlers(); - -#if defined( DISABLE_CLOUD ) || !defined( ENABLE_ACLK ) - nd_log(NDLS_DAEMON, NDLP_INFO, - "Killing ACLK thread -> cloud functionality has been disabled"); - - static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; - return NULL; -#endif - query_threads.count = read_query_thread_count(); - - if (wait_till_cloud_enabled()) - goto exit; + aclk_init_rx_msg_handlers(); if (wait_till_agent_claim_ready()) goto exit; - if (!(mqttwss_client = mqtt_wss_new("mqtt_wss", aclk_mqtt_wss_log_cb, msg_callback, puback_callback))) { + if (!((mqttwss_client = mqtt_wss_new(msg_callback, puback_callback)))) { netdata_log_error("Couldn't initialize MQTT_WSS network library"); goto exit; } @@ -856,28 +794,22 @@ void *aclk_main(void *ptr) // that send JSON payloads of 10 MB as single messages mqtt_wss_set_max_buf_size(mqttwss_client, 25*1024*1024); - aclk_stats_enabled = config_get_boolean(CONFIG_SECTION_CLOUD, "statistics", global_statistics_enabled); - if (aclk_stats_enabled) { - stats_thread = callocz(1, sizeof(struct aclk_stats_thread)); - stats_thread->query_thread_count = query_threads.count; - stats_thread->client = mqttwss_client; - aclk_stats_thread_prepare(query_threads.count, proto_hdl_cnt); - stats_thread->thread = nd_thread_create("ACLK_STATS", NETDATA_THREAD_OPTION_JOINABLE, aclk_stats_main_thread, stats_thread); - } - // Keep reconnecting and talking until our time has come // and the Grim Reaper (netdata_exit) calls + netdata_log_info("Starting ACLK query event loop"); + aclk_query_init(mqttwss_client); do { if (aclk_attempt_to_connect(mqttwss_client)) goto exit_full; - if (unlikely(!query_threads.thread_list)) - aclk_query_threads_start(&query_threads, mqttwss_client); + if (schedule_node_update) { + schedule_node_state_update(localhost, 0); + schedule_node_update = false; + } if (handle_connection(mqttwss_client)) { - aclk_stats_upd_online(0); last_disconnect_time = now_realtime_sec(); - aclk_connected = 0; + aclk_set_disconnected(); nd_log(NDLS_ACCESS, NDLP_WARNING, "ACLK DISCONNECTED"); } } while (service_running(SERVICE_ACLK)); @@ -890,16 +822,6 @@ void *aclk_main(void *ptr) #endif exit_full: -// Tear Down - QUERY_THREAD_WAKEUP_ALL; - - aclk_query_threads_cleanup(&query_threads); - - if (aclk_stats_enabled) { - nd_thread_join(stats_thread->thread); - aclk_stats_thread_cleanup(); - freez(stats_thread); - } free_topic_cache(); mqtt_wss_destroy(mqttwss_client); exit: @@ -913,17 +835,16 @@ exit: void aclk_host_state_update(RRDHOST *host, int cmd, int queryable) { - nd_uuid_t node_id; - int ret = 0; + ND_UUID node_id; - if (!aclk_connected) + if (!aclk_online()) return; - if (host->node_id && !uuid_is_null(*host->node_id)) { - uuid_copy(node_id, *host->node_id); + if (!UUIDiszero(host->node_id)) { + node_id = host->node_id; } else { - ret = get_node_id(&host->host_uuid, &node_id); + int ret = get_node_id(&host->host_id.uuid, &node_id.uuid); if (ret > 0) { // this means we were not able to check if node_id already present netdata_log_error("Unable to check for node_id. Ignoring the host state update."); @@ -933,21 +854,23 @@ void aclk_host_state_update(RRDHOST *host, int cmd, int queryable) // node_id not found aclk_query_t create_query; create_query = aclk_query_new(REGISTER_NODE); - rrdhost_aclk_state_lock(localhost); + CLAIM_ID claim_id = claim_id_get(); + node_instance_creation_t node_instance_creation = { - .claim_id = localhost->aclk_state.claimed_id, + .claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL, .hops = host->system_info->hops, .hostname = rrdhost_hostname(host), .machine_guid = host->machine_guid}; + create_query->data.bin_payload.payload = generate_node_instance_creation(&create_query->data.bin_payload.size, &node_instance_creation); - rrdhost_aclk_state_unlock(localhost); + create_query->data.bin_payload.topic = ACLK_TOPICID_CREATE_NODE; create_query->data.bin_payload.msg_name = "CreateNodeInstance"; nd_log(NDLS_DAEMON, NDLP_DEBUG, "Registering host=%s, hops=%u", host->machine_guid, host->system_info->hops); - aclk_queue_query(create_query); + aclk_execute_query(create_query); return; } } @@ -960,14 +883,13 @@ void aclk_host_state_update(RRDHOST *host, int cmd, int queryable) .session_id = aclk_session_newarch }; node_state_update.node_id = mallocz(UUID_STR_LEN); - uuid_unparse_lower(node_id, (char*)node_state_update.node_id); + uuid_unparse_lower(node_id.uuid, (char*)node_state_update.node_id); node_state_update.capabilities = aclk_get_agent_capas(); - rrdhost_aclk_state_lock(localhost); - node_state_update.claim_id = localhost->aclk_state.claimed_id; + CLAIM_ID claim_id = claim_id_get(); + node_state_update.claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL; query->data.bin_payload.payload = generate_node_instance_connection(&query->data.bin_payload.size, &node_state_update); - rrdhost_aclk_state_unlock(localhost); nd_log(NDLS_DAEMON, NDLP_DEBUG, "Queuing status update for node=%s, live=%d, hops=%u, queryable=%d", @@ -975,7 +897,7 @@ void aclk_host_state_update(RRDHOST *host, int cmd, int queryable) freez((void*)node_state_update.node_id); query->data.bin_payload.msg_name = "UpdateNodeInstanceConnection"; query->data.bin_payload.topic = ACLK_TOPICID_NODE_CONN; - aclk_queue_query(query); + aclk_execute_query(query); } void aclk_send_node_instances() @@ -1009,10 +931,9 @@ void aclk_send_node_instances() } node_state_update.capabilities = aclk_get_node_instance_capas(host); - rrdhost_aclk_state_lock(localhost); - node_state_update.claim_id = localhost->aclk_state.claimed_id; + CLAIM_ID claim_id = claim_id_get(); + node_state_update.claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL; query->data.bin_payload.payload = generate_node_instance_connection(&query->data.bin_payload.size, &node_state_update); - rrdhost_aclk_state_unlock(localhost); nd_log(NDLS_DAEMON, NDLP_DEBUG, "Queuing status update for node=%s, live=%d, hops=%d, queryable=1", @@ -1022,7 +943,7 @@ void aclk_send_node_instances() freez((void*)node_state_update.node_id); query->data.bin_payload.msg_name = "UpdateNodeInstanceConnection"; query->data.bin_payload.topic = ACLK_TOPICID_NODE_CONN; - aclk_queue_query(query); + aclk_execute_query(query); } else { aclk_query_t create_query; create_query = aclk_query_new(REGISTER_NODE); @@ -1034,17 +955,17 @@ void aclk_send_node_instances() uuid_unparse_lower(list->host_id, (char*)node_instance_creation.machine_guid); create_query->data.bin_payload.topic = ACLK_TOPICID_CREATE_NODE; create_query->data.bin_payload.msg_name = "CreateNodeInstance"; - rrdhost_aclk_state_lock(localhost); - node_instance_creation.claim_id = localhost->aclk_state.claimed_id, + + CLAIM_ID claim_id = claim_id_get(); + node_instance_creation.claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL, create_query->data.bin_payload.payload = generate_node_instance_creation(&create_query->data.bin_payload.size, &node_instance_creation); - rrdhost_aclk_state_unlock(localhost); nd_log(NDLS_DAEMON, NDLP_DEBUG, "Queuing registration for host=%s, hops=%d", (char*)node_instance_creation.machine_guid, list->hops); freez((void *)node_instance_creation.machine_guid); - aclk_queue_query(create_query); + aclk_execute_query(create_query); } freez(list->hostname); @@ -1089,38 +1010,37 @@ char *aclk_state(void) ); buffer_sprintf(wb, "Protocol Used: Protobuf\nMQTT Version: %d\nClaimed: ", 5); - char *agent_id = get_agent_claimid(); - if (agent_id == NULL) + CLAIM_ID claim_id = claim_id_get(); + if (!claim_id_is_set(claim_id)) buffer_strcat(wb, "No\n"); else { - char *cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL); - buffer_sprintf(wb, "Yes\nClaimed Id: %s\nCloud URL: %s\n", agent_id, cloud_base_url ? cloud_base_url : "null"); - freez(agent_id); + const char *cloud_base_url = cloud_config_url_get(); + buffer_sprintf(wb, "Yes\nClaimed Id: %s\nCloud URL: %s\n", claim_id.str, cloud_base_url ? cloud_base_url : "null"); } - buffer_sprintf(wb, "Online: %s\nReconnect count: %d\nBanned By Cloud: %s\n", aclk_connected ? "Yes" : "No", aclk_connection_counter > 0 ? (aclk_connection_counter - 1) : 0, aclk_disable_runtime ? "Yes" : "No"); - if (last_conn_time_mqtt && (tmptr = localtime_r(&last_conn_time_mqtt, &tmbuf)) ) { + buffer_sprintf(wb, "Online: %s\nReconnect count: %d\nBanned By Cloud: %s\n", aclk_online() ? "Yes" : "No", aclk_connection_counter > 0 ? (aclk_connection_counter - 1) : 0, aclk_disable_runtime ? "Yes" : "No"); + if (last_conn_time_mqtt && ((tmptr = localtime_r(&last_conn_time_mqtt, &tmbuf))) ) { char timebuf[26]; strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr); buffer_sprintf(wb, "Last Connection Time: %s\n", timebuf); } - if (last_conn_time_appl && (tmptr = localtime_r(&last_conn_time_appl, &tmbuf)) ) { + if (last_conn_time_appl && ((tmptr = localtime_r(&last_conn_time_appl, &tmbuf))) ) { char timebuf[26]; strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr); buffer_sprintf(wb, "Last Connection Time + %d PUBACKs received: %s\n", ACLK_PUBACKS_CONN_STABLE, timebuf); } - if (last_disconnect_time && (tmptr = localtime_r(&last_disconnect_time, &tmbuf)) ) { + if (last_disconnect_time && ((tmptr = localtime_r(&last_disconnect_time, &tmbuf))) ) { char timebuf[26]; strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr); buffer_sprintf(wb, "Last Disconnect Time: %s\n", timebuf); } - if (!aclk_connected && next_connection_attempt && (tmptr = localtime_r(&next_connection_attempt, &tmbuf)) ) { + if (!aclk_connected && next_connection_attempt && ((tmptr = localtime_r(&next_connection_attempt, &tmbuf))) ) { char timebuf[26]; strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr); buffer_sprintf(wb, "Next Connection Attempt At: %s\nLast Backoff: %.3f", timebuf, last_backoff_value); } - if (aclk_connected) { + if (aclk_online()) { buffer_sprintf(wb, "Received Cloud MQTT Messages: %d\nMQTT Messages Confirmed by Remote Broker (PUBACKs): %d", aclk_rcvd_cloud_msgs, aclk_pubacks_per_conn); RRDHOST *host; @@ -1129,20 +1049,18 @@ char *aclk_state(void) buffer_sprintf(wb, "\n\n> Node Instance for mGUID: \"%s\" hostname \"%s\"\n", host->machine_guid, rrdhost_hostname(host)); buffer_strcat(wb, "\tClaimed ID: "); - rrdhost_aclk_state_lock(host); - if (host->aclk_state.claimed_id) - buffer_strcat(wb, host->aclk_state.claimed_id); + claim_id = rrdhost_claim_id_get(host); + if(claim_id_is_set(claim_id)) + buffer_strcat(wb, claim_id.str); else buffer_strcat(wb, "null"); - rrdhost_aclk_state_unlock(host); - - if (host->node_id == NULL || uuid_is_null(*host->node_id)) { + if (UUIDiszero(host->node_id)) buffer_strcat(wb, "\n\tNode ID: null\n"); - } else { - char node_id[GUID_LEN + 1]; - uuid_unparse_lower(*host->node_id, node_id); - buffer_sprintf(wb, "\n\tNode ID: %s\n", node_id); + else { + char node_id_str[UUID_STR_LEN]; + uuid_unparse_lower(host->node_id.uuid, node_id_str); + buffer_sprintf(wb, "\n\tNode ID: %s\n", node_id_str); } buffer_sprintf(wb, "\tStreaming Hops: %d\n\tRelationship: %s", host->system_info->hops, host == localhost ? "self" : "child"); @@ -1183,7 +1101,7 @@ static void fill_alert_status_for_host_json(json_object *obj, RRDHOST *host) static json_object *timestamp_to_json(const time_t *t) { struct tm *tmptr, tmbuf; - if (*t && (tmptr = gmtime_r(t, &tmbuf)) ) { + if (*t && ((tmptr = gmtime_r(t, &tmbuf))) ) { char timebuf[26]; strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr); return json_object_new_string(timebuf); @@ -1206,22 +1124,21 @@ char *aclk_state_json(void) json_object_array_add(grp, tmp); json_object_object_add(msg, "protocols-supported", grp); - char *agent_id = get_agent_claimid(); - tmp = json_object_new_boolean(agent_id != NULL); + CLAIM_ID claim_id = claim_id_get(); + tmp = json_object_new_boolean(claim_id_is_set(claim_id)); json_object_object_add(msg, "agent-claimed", tmp); - if (agent_id) { - tmp = json_object_new_string(agent_id); - freez(agent_id); - } else + if (claim_id_is_set(claim_id)) + tmp = json_object_new_string(claim_id.str); + else tmp = NULL; json_object_object_add(msg, "claimed-id", tmp); - char *cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL); + const char *cloud_base_url = cloud_config_url_get(); tmp = cloud_base_url ? json_object_new_string(cloud_base_url) : NULL; json_object_object_add(msg, "cloud-url", tmp); - tmp = json_object_new_boolean(aclk_connected); + tmp = json_object_new_boolean(aclk_online()); json_object_object_add(msg, "online", tmp); tmp = json_object_new_string("Protobuf"); @@ -1242,9 +1159,9 @@ char *aclk_state_json(void) json_object_object_add(msg, "last-connect-time-utc", timestamp_to_json(&last_conn_time_mqtt)); json_object_object_add(msg, "last-connect-time-puback-utc", timestamp_to_json(&last_conn_time_appl)); json_object_object_add(msg, "last-disconnect-time-utc", timestamp_to_json(&last_disconnect_time)); - json_object_object_add(msg, "next-connection-attempt-utc", !aclk_connected ? timestamp_to_json(&next_connection_attempt) : NULL); + json_object_object_add(msg, "next-connection-attempt-utc", !aclk_online() ? timestamp_to_json(&next_connection_attempt) : NULL); tmp = NULL; - if (!aclk_connected && last_backoff_value) + if (!aclk_online() && last_backoff_value) tmp = json_object_new_double(last_backoff_value); json_object_object_add(msg, "last-backoff-value", tmp); @@ -1264,20 +1181,19 @@ char *aclk_state_json(void) tmp = json_object_new_string(host->machine_guid); json_object_object_add(nodeinstance, "mguid", tmp); - rrdhost_aclk_state_lock(host); - if (host->aclk_state.claimed_id) { - tmp = json_object_new_string(host->aclk_state.claimed_id); + claim_id = rrdhost_claim_id_get(host); + if(claim_id_is_set(claim_id)) { + tmp = json_object_new_string(claim_id.str); json_object_object_add(nodeinstance, "claimed_id", tmp); } else json_object_object_add(nodeinstance, "claimed_id", NULL); - rrdhost_aclk_state_unlock(host); - if (host->node_id == NULL || uuid_is_null(*host->node_id)) { + if (UUIDiszero(host->node_id)) { json_object_object_add(nodeinstance, "node-id", NULL); } else { - char node_id[GUID_LEN + 1]; - uuid_unparse_lower(*host->node_id, node_id); - tmp = json_object_new_string(node_id); + char node_id_str[UUID_STR_LEN]; + uuid_unparse_lower(host->node_id.uuid, node_id_str); + tmp = json_object_new_string(node_id_str); json_object_object_add(nodeinstance, "node-id", tmp); } @@ -1303,12 +1219,10 @@ char *aclk_state_json(void) json_object_put(msg); return str; } -#endif /* ENABLE_ACLK */ void add_aclk_host_labels(void) { RRDLABELS *labels = localhost->rrdlabels; -#ifdef ENABLE_ACLK rrdlabels_add(labels, "_aclk_available", "true", RRDLABEL_SRC_AUTO|RRDLABEL_SRC_ACLK); ACLK_PROXY_TYPE aclk_proxy; char *proxy_str; @@ -1329,9 +1243,6 @@ void add_aclk_host_labels(void) { rrdlabels_add(labels, "_mqtt_version", "5", RRDLABEL_SRC_AUTO); rrdlabels_add(labels, "_aclk_proxy", proxy_str, RRDLABEL_SRC_AUTO); rrdlabels_add(labels, "_aclk_ng_new_cloud_protocol", "true", RRDLABEL_SRC_AUTO|RRDLABEL_SRC_ACLK); -#else - rrdlabels_add(labels, "_aclk_available", "false", RRDLABEL_SRC_AUTO|RRDLABEL_SRC_ACLK); -#endif } void aclk_queue_node_info(RRDHOST *host, bool immediate) diff --git a/src/aclk/aclk.h b/src/aclk/aclk.h index 72d1a2e11..45a2eac85 100644 --- a/src/aclk/aclk.h +++ b/src/aclk/aclk.h @@ -4,14 +4,19 @@ #include "daemon/common.h" -#ifdef ENABLE_ACLK #include "aclk_util.h" -#include "aclk_rrdhost_state.h" +//#include "aclk_rrdhost_state.h" // How many MQTT PUBACKs we need to get to consider connection // stable for the purposes of TBEB (truncated binary exponential backoff) #define ACLK_PUBACKS_CONN_STABLE 3 -#endif /* ENABLE_ACLK */ + +typedef enum { + ACLK_NO_DISCONNECT = 0, + ACLK_CLOUD_DISCONNECT = 1, + ACLK_RELOAD_CONF = 2, + ACLK_PING_TIMEOUT = 3 +} ACLK_DISCONNECT_ACTION; typedef enum __attribute__((packed)) { ACLK_STATUS_CONNECTED = 0, @@ -39,12 +44,19 @@ extern ACLK_STATUS aclk_status; extern const char *aclk_cloud_base_url; const char *aclk_status_to_string(void); -extern int aclk_connected; extern int aclk_ctx_based; extern int aclk_disable_runtime; -extern int aclk_stats_enabled; +//extern int aclk_stats_enabled; extern int aclk_kill_link; +bool aclk_online(void); +bool aclk_online_for_contexts(void); +bool aclk_online_for_alerts(void); +bool aclk_online_for_nodes(void); + +void aclk_config_get_query_scope(void); +bool aclk_query_scope_has(HTTP_ACL acl); + extern time_t last_conn_time_mqtt; extern time_t last_conn_time_appl; extern time_t last_disconnect_time; @@ -57,15 +69,10 @@ extern time_t aclk_session_sec; extern time_t aclk_block_until; extern int aclk_connection_counter; -extern int disconnect_req; +extern ACLK_DISCONNECT_ACTION disconnect_req; -#ifdef ENABLE_ACLK void *aclk_main(void *ptr); -extern netdata_mutex_t aclk_shared_state_mutex; -#define ACLK_SHARED_STATE_LOCK netdata_mutex_lock(&aclk_shared_state_mutex) -#define ACLK_SHARED_STATE_UNLOCK netdata_mutex_unlock(&aclk_shared_state_mutex) - extern struct aclk_shared_state { // To wait for `disconnect` message PUBACK // when shutting down @@ -80,8 +87,6 @@ void aclk_send_node_instances(void); void aclk_send_bin_msg(char *msg, size_t msg_len, enum aclk_topics subtopic, const char *msgname); -#endif /* ENABLE_ACLK */ - char *aclk_state(void); char *aclk_state_json(void); void add_aclk_host_labels(void); diff --git a/src/aclk/aclk_alarm_api.c b/src/aclk/aclk_alarm_api.c index 664671f70..a23ad0ff7 100644 --- a/src/aclk/aclk_alarm_api.c +++ b/src/aclk/aclk_alarm_api.c @@ -8,15 +8,6 @@ #include "aclk.h" -void aclk_send_provide_alarm_checkpoint(struct alarm_checkpoint *checkpoint) -{ - aclk_query_t query = aclk_query_new(ALARM_PROVIDE_CHECKPOINT); - query->data.bin_payload.payload = generate_alarm_checkpoint(&query->data.bin_payload.size, checkpoint); - query->data.bin_payload.topic = ACLK_TOPICID_ALARM_CHECKPOINT; - query->data.bin_payload.msg_name = "AlarmCheckpoint"; - QUEUE_IF_PAYLOAD_PRESENT(query); -} - void aclk_send_alarm_log_entry(struct alarm_log_entry *log_entry) { size_t payload_size; diff --git a/src/aclk/aclk_alarm_api.h b/src/aclk/aclk_alarm_api.h index 4d9d9447a..952d55007 100644 --- a/src/aclk/aclk_alarm_api.h +++ b/src/aclk/aclk_alarm_api.h @@ -6,7 +6,6 @@ #include "../daemon/common.h" #include "schema-wrappers/schema_wrappers.h" -void aclk_send_provide_alarm_checkpoint(struct alarm_checkpoint *checkpoint); void aclk_send_alarm_log_entry(struct alarm_log_entry *log_entry); void aclk_send_provide_alarm_cfg(struct provide_alarm_configuration *cfg); void aclk_send_alarm_snapshot(alarm_snapshot_proto_ptr_t snapshot); diff --git a/src/aclk/aclk_capas.c b/src/aclk/aclk_capas.c index 0f7870fdd..dee6bf0c5 100644 --- a/src/aclk/aclk_capas.c +++ b/src/aclk/aclk_capas.c @@ -4,7 +4,11 @@ #include "ml/ml.h" -#define HTTP_API_V2_VERSION 6 +#define HTTP_API_V2_VERSION 7 + +size_t aclk_get_http_api_version(void) { + return HTTP_API_V2_VERSION; +} const struct capability *aclk_get_agent_capas() { @@ -24,8 +28,8 @@ const struct capability *aclk_get_agent_capas() agent_capabilities[2].version = ml_capable() ? 1 : 0; agent_capabilities[2].enabled = ml_enabled(localhost); - agent_capabilities[3].version = enable_metric_correlations ? metric_correlations_version : 0; - agent_capabilities[3].enabled = enable_metric_correlations; + agent_capabilities[3].version = metric_correlations_version; + agent_capabilities[3].enabled = 1; agent_capabilities[7].enabled = localhost->health.health_enabled; @@ -40,9 +44,7 @@ struct capability *aclk_get_node_instance_capas(RRDHOST *host) struct capability ni_caps[] = { { .name = "proto", .version = 1, .enabled = 1 }, { .name = "ml", .version = ml_capable(), .enabled = ml_enabled(host) }, - { .name = "mc", - .version = enable_metric_correlations ? metric_correlations_version : 0, - .enabled = enable_metric_correlations }, + { .name = "mc", .version = metric_correlations_version, .enabled = 1 }, { .name = "ctx", .version = 1, .enabled = 1 }, { .name = "funcs", .version = functions ? 1 : 0, .enabled = functions ? 1 : 0 }, { .name = "http_api_v2", .version = HTTP_API_V2_VERSION, .enabled = 1 }, diff --git a/src/aclk/aclk_capas.h b/src/aclk/aclk_capas.h index c39a197b8..d3808e640 100644 --- a/src/aclk/aclk_capas.h +++ b/src/aclk/aclk_capas.h @@ -8,6 +8,7 @@ #include "schema-wrappers/capability.h" +size_t aclk_get_http_api_version(void); const struct capability *aclk_get_agent_capas(); struct capability *aclk_get_node_instance_capas(RRDHOST *host); diff --git a/src/aclk/aclk_otp.c b/src/aclk/aclk_otp.c index 3b8222931..3e4f7835a 100644 --- a/src/aclk/aclk_otp.c +++ b/src/aclk/aclk_otp.c @@ -4,10 +4,6 @@ #include "aclk_util.h" #include "aclk.h" -#include "daemon/common.h" - -#include "mqtt_websockets/c-rbuf/cringbuffer.h" - static int aclk_https_request(https_req_t *request, https_req_response_t *response, bool *fallback_ipv4) { int rc; // wrapper for ACLK only which loads ACLK specific proxy settings @@ -271,40 +267,8 @@ exit: } #endif -#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110 -static EVP_ENCODE_CTX *EVP_ENCODE_CTX_new(void) -{ - EVP_ENCODE_CTX *ctx = OPENSSL_malloc(sizeof(*ctx)); - - if (ctx != NULL) { - memset(ctx, 0, sizeof(*ctx)); - } - return ctx; -} -static void EVP_ENCODE_CTX_free(EVP_ENCODE_CTX *ctx) -{ - OPENSSL_free(ctx); - return; -} -#endif - #define CHALLENGE_LEN 256 #define CHALLENGE_LEN_BASE64 344 -inline static int base64_decode_helper(unsigned char *out, int *outl, const unsigned char *in, int in_len) -{ - unsigned char remaining_data[CHALLENGE_LEN]; - EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new(); - EVP_DecodeInit(ctx); - EVP_DecodeUpdate(ctx, out, outl, in, in_len); - int remainder = 0; - EVP_DecodeFinal(ctx, remaining_data, &remainder); - EVP_ENCODE_CTX_free(ctx); - if (remainder) { - netdata_log_error("Unexpected data at EVP_DecodeFinal"); - return 1; - } - return 0; -} #define OTP_URL_PREFIX "/api/v1/auth/node/" int aclk_get_otp_challenge(url_t *target, const char *agent_id, unsigned char **challenge, int *challenge_bytes, bool *fallback_ipv4) @@ -351,7 +315,7 @@ int aclk_get_otp_challenge(url_t *target, const char *agent_id, unsigned char ** goto cleanup_json; } const char *challenge_base64; - if (!(challenge_base64 = json_object_get_string(challenge_json))) { + if (!((challenge_base64 = json_object_get_string(challenge_json)))) { netdata_log_error("Failed to extract challenge from JSON object"); goto cleanup_json; } @@ -360,8 +324,9 @@ int aclk_get_otp_challenge(url_t *target, const char *agent_id, unsigned char ** goto cleanup_json; } - *challenge = mallocz((CHALLENGE_LEN_BASE64 / 4) * 3); - base64_decode_helper(*challenge, challenge_bytes, (const unsigned char*)challenge_base64, strlen(challenge_base64)); + *challenge = mallocz((CHALLENGE_LEN_BASE64 / 4) * 3 + 1); + *challenge_bytes = netdata_base64_decode(*challenge, (const unsigned char *) challenge_base64, CHALLENGE_LEN_BASE64); + if (*challenge_bytes != CHALLENGE_LEN) { netdata_log_error("Unexpected challenge length of %d instead of %d", *challenge_bytes, CHALLENGE_LEN); freez(*challenge); @@ -379,7 +344,6 @@ cleanup_resp: int aclk_send_otp_response(const char *agent_id, const unsigned char *response, int response_bytes, url_t *target, struct auth_data *mqtt_auth, bool *fallback_ipv4) { - int len; int rc = 1; https_req_t req = HTTPS_REQ_T_INITIALIZER; https_req_response_t resp = HTTPS_REQ_RESPONSE_T_INITIALIZER; @@ -391,7 +355,7 @@ int aclk_send_otp_response(const char *agent_id, const unsigned char *response, unsigned char base64[CHALLENGE_LEN_BASE64 + 1]; memset(base64, 0, CHALLENGE_LEN_BASE64 + 1); - base64_encode_helper(base64, &len, response, response_bytes); + (void) netdata_base64_encode(base64, response, response_bytes); BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20, &netdata_buffers_statistics.buffers_aclk); BUFFER *resp_json = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20, &netdata_buffers_statistics.buffers_aclk); @@ -487,16 +451,15 @@ int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_p unsigned char *challenge = NULL; int challenge_bytes; - char *agent_id = get_agent_claimid(); - if (agent_id == NULL) { + CLAIM_ID claim_id = claim_id_get(); + if (!claim_id_is_set(claim_id)) { netdata_log_error("Agent was not claimed - cannot perform challenge/response"); return 1; } // Get Challenge - if (aclk_get_otp_challenge(target, agent_id, &challenge, &challenge_bytes, fallback_ipv4)) { + if (aclk_get_otp_challenge(target, claim_id.str, &challenge, &challenge_bytes, fallback_ipv4)) { netdata_log_error("Error getting challenge"); - freez(agent_id); return 1; } @@ -507,17 +470,15 @@ int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_p netdata_log_error("Couldn't decrypt the challenge received"); freez(response_plaintext); freez(challenge); - freez(agent_id); return 1; } freez(challenge); // Encode and Send Challenge struct auth_data data = { .client_id = NULL, .passwd = NULL, .username = NULL }; - if (aclk_send_otp_response(agent_id, response_plaintext, response_plaintext_bytes, target, &data, fallback_ipv4)) { + if (aclk_send_otp_response(claim_id.str, response_plaintext, response_plaintext_bytes, target, &data, fallback_ipv4)) { netdata_log_error("Error getting response"); freez(response_plaintext); - freez(agent_id); return 1; } @@ -526,7 +487,6 @@ int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_p *mqtt_id = data.client_id; freez(response_plaintext); - freez(agent_id); return 0; } @@ -830,17 +790,14 @@ int aclk_get_env(aclk_env_t *env, const char* aclk_hostname, int aclk_port, bool req.request_type = HTTP_REQ_GET; - char *agent_id = get_agent_claimid(); - if (agent_id == NULL) - { + CLAIM_ID claim_id = claim_id_get(); + if (!claim_id_is_set(claim_id)) { netdata_log_error("Agent was not claimed - cannot perform challenge/response"); buffer_free(buf); return 1; } - buffer_sprintf(buf, "/api/v1/env?v=%s&cap=proto,ctx&claim_id=%s", &(NETDATA_VERSION[1]) /* skip 'v' at beginning */, agent_id); - - freez(agent_id); + buffer_sprintf(buf, "/api/v1/env?v=%s&cap=proto,ctx&claim_id=%s", &(NETDATA_VERSION[1]) /* skip 'v' at beginning */, claim_id.str); req.host = (char*)aclk_hostname; req.port = aclk_port; diff --git a/src/aclk/aclk_proxy.c b/src/aclk/aclk_proxy.c index 8d0e2d657..a6185db7c 100644 --- a/src/aclk/aclk_proxy.c +++ b/src/aclk/aclk_proxy.c @@ -79,7 +79,7 @@ static inline int check_socks_enviroment(const char **proxy) { char *tmp = getenv("socks_proxy"); - if (!tmp) + if (!tmp || !*tmp) return 1; if (aclk_verify_proxy(tmp) == PROXY_TYPE_SOCKS5) { @@ -97,7 +97,7 @@ static inline int check_http_enviroment(const char **proxy) { char *tmp = getenv("http_proxy"); - if (!tmp) + if (!tmp || !*tmp) return 1; if (aclk_verify_proxy(tmp) == PROXY_TYPE_HTTP) { @@ -113,15 +113,11 @@ static inline int check_http_enviroment(const char **proxy) const char *aclk_lws_wss_get_proxy_setting(ACLK_PROXY_TYPE *type) { - const char *proxy = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, ACLK_PROXY_CONFIG_VAR, ACLK_PROXY_ENV); - - // backward compatibility: "proxy" was in "netdata.conf" - if (config_exists(CONFIG_SECTION_CLOUD, ACLK_PROXY_CONFIG_VAR)) - proxy = config_get(CONFIG_SECTION_CLOUD, ACLK_PROXY_CONFIG_VAR, ACLK_PROXY_ENV); + const char *proxy = cloud_config_proxy_get(); *type = PROXY_DISABLED; - if (strcmp(proxy, "none") == 0) + if (!proxy || !*proxy || strcmp(proxy, "none") == 0) return proxy; if (strcmp(proxy, ACLK_PROXY_ENV) == 0) { diff --git a/src/aclk/aclk_query.c b/src/aclk/aclk_query.c index 08bc2acf3..1d93a5e2d 100644 --- a/src/aclk/aclk_query.c +++ b/src/aclk/aclk_query.c @@ -1,16 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "aclk_query.h" -#include "aclk_stats.h" #include "aclk_tx_msgs.h" #include "../../web/server/web_client_cache.h" -#define WEB_HDR_ACCEPT_ENC "Accept-Encoding:" - -pthread_cond_t query_cond_wait = PTHREAD_COND_INITIALIZER; -pthread_mutex_t query_lock_wait = PTHREAD_MUTEX_INITIALIZER; -#define QUERY_THREAD_LOCK pthread_mutex_lock(&query_lock_wait) -#define QUERY_THREAD_UNLOCK pthread_mutex_unlock(&query_lock_wait) +static HTTP_ACL default_aclk_http_acl = HTTP_ACL_ALL_FEATURES; struct pending_req_list { const char *msg_id; @@ -22,7 +16,17 @@ struct pending_req_list { }; static struct pending_req_list *pending_req_list_head = NULL; -static pthread_mutex_t pending_req_list_lock = PTHREAD_MUTEX_INITIALIZER; +static SPINLOCK pending_req_list_lock = NETDATA_SPINLOCK_INITIALIZER; + +void aclk_config_get_query_scope(void) { + const char *s = config_get(CONFIG_SECTION_CLOUD, "scope", "full"); + if(strcmp(s, "license manager") == 0) + default_aclk_http_acl = HTTP_ACL_ACLK_LICENSE_MANAGER; +} + +bool aclk_query_scope_has(HTTP_ACL acl) { + return (default_aclk_http_acl & acl) == acl; +} static struct pending_req_list *pending_req_list_add(const char *msg_id) { @@ -30,10 +34,10 @@ static struct pending_req_list *pending_req_list_add(const char *msg_id) new->msg_id = msg_id; new->hash = simple_hash(msg_id); - pthread_mutex_lock(&pending_req_list_lock); + spinlock_lock(&pending_req_list_lock); new->next = pending_req_list_head; pending_req_list_head = new; - pthread_mutex_unlock(&pending_req_list_lock); + spinlock_unlock(&pending_req_list_lock); return new; } @@ -42,7 +46,7 @@ void pending_req_list_rm(const char *msg_id) uint32_t hash = simple_hash(msg_id); struct pending_req_list *prev = NULL; - pthread_mutex_lock(&pending_req_list_lock); + spinlock_lock(&pending_req_list_lock); struct pending_req_list *curr = pending_req_list_head; while (curr) { @@ -59,26 +63,26 @@ void pending_req_list_rm(const char *msg_id) prev = curr; curr = curr->next; } - pthread_mutex_unlock(&pending_req_list_lock); + spinlock_unlock(&pending_req_list_lock); } int mark_pending_req_cancelled(const char *msg_id) { uint32_t hash = simple_hash(msg_id); - pthread_mutex_lock(&pending_req_list_lock); + spinlock_lock(&pending_req_list_lock); struct pending_req_list *curr = pending_req_list_head; while (curr) { if (curr->hash == hash && strcmp(curr->msg_id, msg_id) == 0) { curr->canceled = 1; - pthread_mutex_unlock(&pending_req_list_lock); + spinlock_unlock(&pending_req_list_lock); return 0; } curr = curr->next; } - pthread_mutex_unlock(&pending_req_list_lock); + spinlock_unlock(&pending_req_list_lock); return 1; } @@ -88,7 +92,8 @@ static bool aclk_web_client_interrupt_cb(struct web_client *w __maybe_unused, vo return req->canceled; } -static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) { +int http_api_v2(mqtt_wss_client client, aclk_query_t query) +{ ND_LOG_STACK lgs[] = { ND_LOG_FIELD_TXT(NDF_SRC_TRANSPORT, "aclk"), ND_LOG_FIELD_END(), @@ -97,8 +102,6 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) int retval = 0; BUFFER *local_buffer = NULL; - size_t size = 0; - size_t sent = 0; usec_t dt_ut = 0; int z_ret; @@ -106,7 +109,7 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) struct web_client *w = web_client_get_from_cache(); web_client_set_conn_cloud(w); - w->port_acl = HTTP_ACL_ACLK | HTTP_ACL_ALL_FEATURES; + w->port_acl = HTTP_ACL_ACLK | default_aclk_http_acl; w->acl = w->port_acl; web_client_set_permissions(w, HTTP_ACCESS_MAP_OLD_MEMBER, HTTP_USER_ROLE_MEMBER, WEB_CLIENT_FLAG_AUTH_CLOUD); @@ -124,7 +127,7 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) nd_log(NDLS_ACCESS, NDLP_ERR, "ACLK received request is not valid, code %d", validation); retval = 1; w->response.code = HTTP_RESP_BAD_REQUEST; - w->response.code = (short)aclk_http_msg_v2(query_thr->client, query->callback_topic, query->msg_id, + w->response.code = (short)aclk_http_msg_v2(client, query->callback_topic, query->msg_id, dt_ut, query->created, w->response.code, NULL, 0); goto cleanup; @@ -137,39 +140,18 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) dt_ut / USEC_PER_MS, query->timeout); retval = 1; w->response.code = HTTP_RESP_SERVICE_UNAVAILABLE; - aclk_http_msg_v2_err(query_thr->client, query->callback_topic, query->msg_id, w->response.code, CLOUD_EC_SND_TIMEOUT, CLOUD_EMSG_SND_TIMEOUT, NULL, 0); + aclk_http_msg_v2_err(client, query->callback_topic, query->msg_id, w->response.code, CLOUD_EC_SND_TIMEOUT, CLOUD_EMSG_SND_TIMEOUT, NULL, 0); goto cleanup; } char *path = (char *)buffer_tostring(w->url_path_decoded); - if (aclk_stats_enabled) { - char *url_path_endpoint = strrchr(path, '/'); - ACLK_STATS_LOCK; - int stat_idx = aclk_cloud_req_http_type_to_idx(url_path_endpoint ? url_path_endpoint + 1 : "other"); - aclk_metrics_per_sample.cloud_req_http_by_type[stat_idx]++; - ACLK_STATS_UNLOCK; - } - w->response.code = (short)web_client_api_request_with_node_selection(localhost, w, path); web_client_timeout_checkpoint_response_ready(w, &dt_ut); - if (aclk_stats_enabled) { - ACLK_STATS_LOCK; - aclk_metrics_per_sample.cloud_q_process_total += dt_ut; - aclk_metrics_per_sample.cloud_q_process_count++; - if (aclk_metrics_per_sample.cloud_q_process_max < dt_ut) - aclk_metrics_per_sample.cloud_q_process_max = dt_ut; - ACLK_STATS_UNLOCK; - } - - size = w->response.data->len; - sent = size; - if (w->response.data->len && w->response.zinitialized) { w->response.zstream.next_in = (Bytef *)w->response.data->buffer; w->response.zstream.avail_in = w->response.data->len; - do { w->response.zstream.avail_out = NETDATA_WEB_RESPONSE_ZLIB_CHUNK_SIZE; w->response.zstream.next_out = w->response.zbuffer; @@ -181,7 +163,7 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) netdata_log_error("Unknown error during zlib compression."); retval = 1; w->response.code = 500; - aclk_http_msg_v2_err(query_thr->client, query->callback_topic, query->msg_id, w->response.code, CLOUD_EC_ZLIB_ERROR, CLOUD_EMSG_ZLIB_ERROR, NULL, 0); + aclk_http_msg_v2_err(client, query->callback_topic, query->msg_id, w->response.code, CLOUD_EC_ZLIB_ERROR, CLOUD_EMSG_ZLIB_ERROR, NULL, 0); goto cleanup; } int bytes_to_cpy = NETDATA_WEB_RESPONSE_ZLIB_CHUNK_SIZE - w->response.zstream.avail_out; @@ -208,16 +190,20 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) buffer_need_bytes(local_buffer, w->response.data->len); memcpy(&local_buffer->buffer[local_buffer->len], w->response.data->buffer, w->response.data->len); local_buffer->len += w->response.data->len; - sent = sent - size + w->response.data->len; - } else { + } else buffer_strcat(local_buffer, w->response.data->buffer); - } } // send msg. - w->response.code = (short)aclk_http_msg_v2(query_thr->client, query->callback_topic, query->msg_id, - dt_ut, query->created, w->response.code, - local_buffer->buffer, local_buffer->len); + w->response.code = (short)aclk_http_msg_v2( + client, + query->callback_topic, + query->msg_id, + dt_ut, + query->created, + w->response.code, + local_buffer->buffer, + local_buffer->len); cleanup: web_client_log_completed_request(w, false); @@ -230,144 +216,14 @@ cleanup: return retval; } -static int send_bin_msg(struct aclk_query_thread *query_thr, aclk_query_t query) +int send_bin_msg(mqtt_wss_client client, aclk_query_t query) { // this will be simplified when legacy support is removed - aclk_send_bin_message_subtopic_pid(query_thr->client, query->data.bin_payload.payload, query->data.bin_payload.size, query->data.bin_payload.topic, query->data.bin_payload.msg_name); + aclk_send_bin_message_subtopic_pid( + client, + query->data.bin_payload.payload, + query->data.bin_payload.size, + query->data.bin_payload.topic, + query->data.bin_payload.msg_name); return 0; } - -const char *aclk_query_get_name(aclk_query_type_t qt, int unknown_ok) -{ - switch (qt) { - case HTTP_API_V2: return "http_api_request_v2"; - case REGISTER_NODE: return "register_node"; - case NODE_STATE_UPDATE: return "node_state_update"; - case CHART_DIMS_UPDATE: return "chart_and_dim_update"; - case CHART_CONFIG_UPDATED: return "chart_config_updated"; - case CHART_RESET: return "reset_chart_messages"; - case RETENTION_UPDATED: return "update_retention_info"; - case UPDATE_NODE_INFO: return "update_node_info"; - case ALARM_PROVIDE_CHECKPOINT: return "alarm_checkpoint"; - case ALARM_PROVIDE_CFG: return "provide_alarm_config"; - case ALARM_SNAPSHOT: return "alarm_snapshot"; - case UPDATE_NODE_COLLECTORS: return "update_node_collectors"; - case PROTO_BIN_MESSAGE: return "generic_binary_proto_message"; - default: - if (!unknown_ok) - error_report("Unknown query type used %d", (int) qt); - return "unknown"; - } -} - -static void aclk_query_process_msg(struct aclk_query_thread *query_thr, aclk_query_t query) -{ - if (query->type == UNKNOWN || query->type >= ACLK_QUERY_TYPE_COUNT) { - error_report("Unknown query in query queue. %u", query->type); - aclk_query_free(query); - return; - } - - worker_is_busy(query->type); - if (query->type == HTTP_API_V2) { - netdata_log_debug(D_ACLK, "Processing Queued Message of type: \"http_api_request_v2\""); - http_api_v2(query_thr, query); - } else { - netdata_log_debug(D_ACLK, "Processing Queued Message of type: \"%s\"", query->data.bin_payload.msg_name); - send_bin_msg(query_thr, query); - } - - if (aclk_stats_enabled) { - ACLK_STATS_LOCK; - aclk_metrics_per_sample.queries_dispatched++; - aclk_queries_per_thread[query_thr->idx]++; - aclk_metrics_per_sample.queries_per_type[query->type]++; - ACLK_STATS_UNLOCK; - } - - aclk_query_free(query); - - worker_is_idle(); -} - -/* Processes messages from queue. Compete for work with other threads - */ -int aclk_query_process_msgs(struct aclk_query_thread *query_thr) -{ - aclk_query_t query; - while ((query = aclk_queue_pop())) - aclk_query_process_msg(query_thr, query); - - return 0; -} - -static void worker_aclk_register(void) { - worker_register("ACLKQUERY"); - for (int i = 1; i < ACLK_QUERY_TYPE_COUNT; i++) { - worker_register_job_name(i, aclk_query_get_name(i, 0)); - } -} - -static void aclk_query_request_cancel(void *data) -{ - pthread_cond_broadcast((pthread_cond_t *) data); -} - -/** - * Main query processing thread - */ -void *aclk_query_main_thread(void *ptr) -{ - worker_aclk_register(); - - struct aclk_query_thread *query_thr = ptr; - - service_register(SERVICE_THREAD_TYPE_NETDATA, aclk_query_request_cancel, NULL, &query_cond_wait, false); - - while (service_running(SERVICE_ACLK | ABILITY_DATA_QUERIES)) { - aclk_query_process_msgs(query_thr); - - worker_is_idle(); - QUERY_THREAD_LOCK; - if (unlikely(pthread_cond_wait(&query_cond_wait, &query_lock_wait))) - sleep_usec(USEC_PER_SEC * 1); - QUERY_THREAD_UNLOCK; - } - - worker_unregister(); - return NULL; -} - -#define TASK_LEN_MAX 22 -void aclk_query_threads_start(struct aclk_query_threads *query_threads, mqtt_wss_client client) -{ - netdata_log_info("Starting %d query threads.", query_threads->count); - - char thread_name[TASK_LEN_MAX]; - query_threads->thread_list = callocz(query_threads->count, sizeof(struct aclk_query_thread)); - for (int i = 0; i < query_threads->count; i++) { - query_threads->thread_list[i].idx = i; //thread needs to know its index for statistics - query_threads->thread_list[i].client = client; - - if(unlikely(snprintfz(thread_name, TASK_LEN_MAX, "ACLK_QRY[%d]", i) < 0)) - netdata_log_error("snprintf encoding error"); - - query_threads->thread_list[i].thread = nd_thread_create( - thread_name, - NETDATA_THREAD_OPTION_JOINABLE, - aclk_query_main_thread, - &query_threads->thread_list[i]); - } -} - -void aclk_query_threads_cleanup(struct aclk_query_threads *query_threads) -{ - if (query_threads && query_threads->thread_list) { - for (int i = 0; i < query_threads->count; i++) { - nd_thread_join(query_threads->thread_list[i].thread); - } - freez(query_threads->thread_list); - } - aclk_queue_lock(); - aclk_queue_flush(); -} diff --git a/src/aclk/aclk_query.h b/src/aclk/aclk_query.h index 900583237..04cb460d0 100644 --- a/src/aclk/aclk_query.h +++ b/src/aclk/aclk_query.h @@ -9,30 +9,11 @@ #include "aclk_query_queue.h" -extern pthread_cond_t query_cond_wait; -extern pthread_mutex_t query_lock_wait; -#define QUERY_THREAD_WAKEUP pthread_cond_signal(&query_cond_wait) -#define QUERY_THREAD_WAKEUP_ALL pthread_cond_broadcast(&query_cond_wait) - -// TODO -//extern volatile int aclk_connected; - -struct aclk_query_thread { - ND_THREAD *thread; - int idx; - mqtt_wss_client client; -}; - -struct aclk_query_threads { - struct aclk_query_thread *thread_list; - int count; -}; - -void aclk_query_threads_start(struct aclk_query_threads *query_threads, mqtt_wss_client client); -void aclk_query_threads_cleanup(struct aclk_query_threads *query_threads); - -const char *aclk_query_get_name(aclk_query_type_t qt, int unknown_ok); - int mark_pending_req_cancelled(const char *msg_id); +void aclk_execute_query(aclk_query_t query); +void aclk_query_init(mqtt_wss_client client); +int http_api_v2(mqtt_wss_client client, aclk_query_t query); +int send_bin_msg(mqtt_wss_client client, aclk_query_t query); + #endif //NETDATA_AGENT_CLOUD_LINK_H diff --git a/src/aclk/aclk_query_queue.c b/src/aclk/aclk_query_queue.c index 3edadc002..acaa2d9c6 100644 --- a/src/aclk/aclk_query_queue.c +++ b/src/aclk/aclk_query_queue.c @@ -1,87 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "aclk_query_queue.h" -#include "aclk_query.h" -#include "aclk_stats.h" - -static netdata_mutex_t aclk_query_queue_mutex = NETDATA_MUTEX_INITIALIZER; -#define ACLK_QUEUE_LOCK netdata_mutex_lock(&aclk_query_queue_mutex) -#define ACLK_QUEUE_UNLOCK netdata_mutex_unlock(&aclk_query_queue_mutex) - -static struct aclk_query_queue { - aclk_query_t head; - int block_push; -} aclk_query_queue = { - .head = NULL, - .block_push = 0 -}; - -static inline int _aclk_queue_query(aclk_query_t query) -{ - now_monotonic_high_precision_timeval(&query->created_tv); - query->created = now_realtime_usec(); - - ACLK_QUEUE_LOCK; - if (aclk_query_queue.block_push) { - ACLK_QUEUE_UNLOCK; - if(service_running(SERVICE_ACLK | ABILITY_DATA_QUERIES)) - netdata_log_error("Query Queue is blocked from accepting new requests. This is normally the case when ACLK prepares to shutdown."); - aclk_query_free(query); - return 1; - } - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(aclk_query_queue.head, query, prev, next); - ACLK_QUEUE_UNLOCK; - return 0; - -} - -int aclk_queue_query(aclk_query_t query) -{ - int ret = _aclk_queue_query(query); - if (!ret) { - QUERY_THREAD_WAKEUP; - if (aclk_stats_enabled) { - ACLK_STATS_LOCK; - aclk_metrics_per_sample.queries_queued++; - ACLK_STATS_UNLOCK; - } - } - return ret; -} - -aclk_query_t aclk_queue_pop(void) -{ - aclk_query_t ret; - - ACLK_QUEUE_LOCK; - if (aclk_query_queue.block_push) { - ACLK_QUEUE_UNLOCK; - if(service_running(SERVICE_ACLK | ABILITY_DATA_QUERIES)) - netdata_log_error("POP Query Queue is blocked from accepting new requests. This is normally the case when ACLK prepares to shutdown."); - return NULL; - } - - ret = aclk_query_queue.head; - if (!ret) { - ACLK_QUEUE_UNLOCK; - return ret; - } - - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(aclk_query_queue.head, ret, prev, next); - ACLK_QUEUE_UNLOCK; - - ret->next = NULL; - return ret; -} - -void aclk_queue_flush(void) -{ - aclk_query_t query = aclk_queue_pop(); - while (query) { - aclk_query_free(query); - query = aclk_queue_pop(); - } -} aclk_query_t aclk_query_new(aclk_query_type_t type) { @@ -93,14 +12,14 @@ aclk_query_t aclk_query_new(aclk_query_type_t type) void aclk_query_free(aclk_query_t query) { switch (query->type) { - case HTTP_API_V2: - freez(query->data.http_api_v2.payload); - if (query->data.http_api_v2.query != query->dedup_id) - freez(query->data.http_api_v2.query); - break; - - default: - break; + case HTTP_API_V2: + freez(query->data.http_api_v2.payload); + if (query->data.http_api_v2.query != query->dedup_id) + freez(query->data.http_api_v2.query); + break; + + default: + break; } freez(query->dedup_id); @@ -108,17 +27,3 @@ void aclk_query_free(aclk_query_t query) freez(query->msg_id); freez(query); } - -void aclk_queue_lock(void) -{ - ACLK_QUEUE_LOCK; - aclk_query_queue.block_push = 1; - ACLK_QUEUE_UNLOCK; -} - -void aclk_queue_unlock(void) -{ - ACLK_QUEUE_LOCK; - aclk_query_queue.block_push = 0; - ACLK_QUEUE_UNLOCK; -} diff --git a/src/aclk/aclk_query_queue.h b/src/aclk/aclk_query_queue.h index 4a4a36a3f..8b7e3a10c 100644 --- a/src/aclk/aclk_query_queue.h +++ b/src/aclk/aclk_query_queue.h @@ -14,12 +14,7 @@ typedef enum { HTTP_API_V2, REGISTER_NODE, NODE_STATE_UPDATE, - CHART_DIMS_UPDATE, - CHART_CONFIG_UPDATED, - CHART_RESET, - RETENTION_UPDATED, UPDATE_NODE_INFO, - ALARM_PROVIDE_CHECKPOINT, ALARM_PROVIDE_CFG, ALARM_SNAPSHOT, UPDATE_NODE_COLLECTORS, @@ -32,7 +27,7 @@ struct aclk_query_http_api_v2 { char *query; }; -struct aclk_bin_payload { +struct aclk_bin_payload { char *payload; size_t size; enum aclk_topics topic; @@ -55,7 +50,6 @@ struct aclk_query { struct timeval created_tv; usec_t created; int timeout; - aclk_query_t prev, next; // TODO maybe remove? int version; @@ -68,20 +62,16 @@ struct aclk_query { aclk_query_t aclk_query_new(aclk_query_type_t type); void aclk_query_free(aclk_query_t query); -int aclk_queue_query(aclk_query_t query); -aclk_query_t aclk_queue_pop(void); -void aclk_queue_flush(void); +void aclk_execute_query(aclk_query_t query); -void aclk_queue_lock(void); -void aclk_queue_unlock(void); - -#define QUEUE_IF_PAYLOAD_PRESENT(query) do { \ - if (likely(query->data.bin_payload.payload)) { \ - aclk_queue_query(query); \ - } else { \ - nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to generate payload"); \ - aclk_query_free(query); \ - } \ -} while(0) +#define QUEUE_IF_PAYLOAD_PRESENT(query) \ + do { \ + if (likely((query)->data.bin_payload.payload)) { \ + aclk_execute_query(query); \ + } else { \ + nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to generate payload"); \ + aclk_query_free(query); \ + } \ + } while (0) #endif /* NETDATA_ACLK_QUERY_QUEUE_H */ diff --git a/src/aclk/aclk_rrdhost_state.h b/src/aclk/aclk_rrdhost_state.h deleted file mode 100644 index 5c8a2ddc9..000000000 --- a/src/aclk/aclk_rrdhost_state.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef ACLK_RRDHOST_STATE_H -#define ACLK_RRDHOST_STATE_H - -#include "libnetdata/libnetdata.h" - -typedef struct aclk_rrdhost_state { - char *claimed_id; // Claimed ID if host has one otherwise NULL - char *prev_claimed_id; // Claimed ID if changed (reclaimed) during runtime -} aclk_rrdhost_state; - -#endif /* ACLK_RRDHOST_STATE_H */ diff --git a/src/aclk/aclk_rx_msgs.c b/src/aclk/aclk_rx_msgs.c index 8db8e3f1e..36bd3599d 100644 --- a/src/aclk/aclk_rx_msgs.c +++ b/src/aclk/aclk_rx_msgs.c @@ -2,7 +2,6 @@ #include "aclk_rx_msgs.h" -#include "aclk_stats.h" #include "aclk_query_queue.h" #include "aclk.h" #include "aclk_capas.h" @@ -165,7 +164,7 @@ static int aclk_handle_cloud_http_request_v2(struct aclk_request *cloud_to_agent // it would be strange to get URL from `dedup_id` query->data.http_api_v2.query = query->dedup_id; query->msg_id = cloud_to_agent->msg_id; - aclk_queue_query(query); + aclk_execute_query(query); return 0; error: @@ -268,7 +267,7 @@ int create_node_instance_result(const char *msg, size_t msg_len) freez(res.node_id); return 1; } - update_node_id(&host_id, &node_id); + sql_update_node_id(&host_id, &node_id); aclk_query_t query = aclk_query_new(NODE_STATE_UPDATE); node_instance_connection_t node_state_update = { @@ -292,17 +291,16 @@ int create_node_instance_result(const char *msg, size_t msg_len) node_state_update.capabilities = aclk_get_node_instance_capas(host); } - rrdhost_aclk_state_lock(localhost); - node_state_update.claim_id = localhost->aclk_state.claimed_id; + CLAIM_ID claim_id = claim_id_get(); + node_state_update.claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL; query->data.bin_payload.payload = generate_node_instance_connection(&query->data.bin_payload.size, &node_state_update); - rrdhost_aclk_state_unlock(localhost); freez((void *)node_state_update.capabilities); query->data.bin_payload.msg_name = "UpdateNodeInstanceConnection"; query->data.bin_payload.topic = ACLK_TOPICID_NODE_CONN; - aclk_queue_query(query); + aclk_execute_query(query); freez(res.node_id); freez(res.machine_guid); return 0; @@ -409,7 +407,7 @@ int handle_disconnect_req(const char *msg, size_t msg_len) "Cloud asks not to reconnect for %u seconds. We shall honor that request", (unsigned int)cmd->reconnect_after_s); } - disconnect_req = 1; + disconnect_req = ACLK_CLOUD_DISCONNECT; freez(cmd->error_description); freez(cmd); return 0; @@ -503,12 +501,7 @@ new_cloud_rx_msg_t *find_rx_handler_by_hash(simple_hash_t hash) return NULL; } -const char *rx_handler_get_name(size_t i) -{ - return rx_msgs[i].name; -} - -unsigned int aclk_init_rx_msg_handlers(void) +void aclk_init_rx_msg_handlers(void) { int i; for (i = 0; rx_msgs[i].fnc; i++) { @@ -521,29 +514,17 @@ unsigned int aclk_init_rx_msg_handlers(void) } rx_msgs[i].name_hash = hash; } - return i; } void aclk_handle_new_cloud_msg(const char *message_type, const char *msg, size_t msg_len, const char *topic __maybe_unused) { - if (aclk_stats_enabled) { - ACLK_STATS_LOCK; - aclk_metrics_per_sample.cloud_req_recvd++; - ACLK_STATS_UNLOCK; - } new_cloud_rx_msg_t *msg_descriptor = find_rx_handler_by_hash(simple_hash(message_type)); netdata_log_debug(D_ACLK, "Got message named '%s' from cloud", message_type); if (unlikely(!msg_descriptor)) { netdata_log_error("Do not know how to handle message of type '%s'. Ignoring", message_type); - if (aclk_stats_enabled) { - ACLK_STATS_LOCK; - aclk_metrics_per_sample.cloud_req_err++; - ACLK_STATS_UNLOCK; - } return; } - if (aclklog_enabled) { if (!strncmp(message_type, "cmd", strlen("cmd"))) { log_aclk_message_bin(msg, msg_len, 0, topic, msg_descriptor->name); @@ -554,18 +535,8 @@ void aclk_handle_new_cloud_msg(const char *message_type, const char *msg, size_t } } - if (aclk_stats_enabled) { - ACLK_STATS_LOCK; - aclk_proto_rx_msgs_sample[msg_descriptor-rx_msgs]++; - ACLK_STATS_UNLOCK; - } if (msg_descriptor->fnc(msg, msg_len)) { netdata_log_error("Error processing message of type '%s'", message_type); - if (aclk_stats_enabled) { - ACLK_STATS_LOCK; - aclk_metrics_per_sample.cloud_req_err++; - ACLK_STATS_UNLOCK; - } return; } } diff --git a/src/aclk/aclk_rx_msgs.h b/src/aclk/aclk_rx_msgs.h index 61921faec..ae5dc18b8 100644 --- a/src/aclk/aclk_rx_msgs.h +++ b/src/aclk/aclk_rx_msgs.h @@ -1,17 +1,12 @@ - - // SPDX-License-Identifier: GPL-3.0-or-later #ifndef ACLK_RX_MSGS_H #define ACLK_RX_MSGS_H -#include "daemon/common.h" #include "libnetdata/libnetdata.h" int aclk_handle_cloud_cmd_message(char *payload); - -const char *rx_handler_get_name(size_t i); -unsigned int aclk_init_rx_msg_handlers(void); +void aclk_init_rx_msg_handlers(void); void aclk_handle_new_cloud_msg(const char *message_type, const char *msg, size_t msg_len, const char *topic); #endif /* ACLK_RX_MSGS_H */ diff --git a/src/aclk/aclk_stats.c b/src/aclk/aclk_stats.c deleted file mode 100644 index 47a48c366..000000000 --- a/src/aclk/aclk_stats.c +++ /dev/null @@ -1,483 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef MQTT_WSS_CPUSTATS -#define MQTT_WSS_CPUSTATS -#endif - -#include "aclk_stats.h" - -#include "aclk_query.h" - -netdata_mutex_t aclk_stats_mutex = NETDATA_MUTEX_INITIALIZER; - -struct { - int query_thread_count; - unsigned int proto_hdl_cnt; - uint32_t *aclk_proto_rx_msgs_sample; - RRDDIM **rx_msg_dims; -} aclk_stats_cfg; // there is only 1 stats thread at a time - -// data ACLK stats need per query thread -struct aclk_qt_data { - RRDDIM *dim; -} *aclk_qt_data = NULL; - -uint32_t *aclk_queries_per_thread = NULL; -uint32_t *aclk_queries_per_thread_sample = NULL; -uint32_t *aclk_proto_rx_msgs_sample = NULL; - -struct aclk_metrics aclk_metrics = { - .online = 0, -}; - -struct aclk_metrics_per_sample aclk_metrics_per_sample; - -static void aclk_stats_collect(struct aclk_metrics_per_sample *per_sample, struct aclk_metrics *permanent) -{ - static RRDSET *st_aclkstats = NULL; - static RRDDIM *rd_online_status = NULL; - - if (unlikely(!st_aclkstats)) { - st_aclkstats = rrdset_create_localhost( - "netdata", "aclk_status", NULL, "aclk", NULL, "ACLK/Cloud connection status", - "connected", "netdata", "stats", 200000, localhost->rrd_update_every, RRDSET_TYPE_LINE); - - rd_online_status = rrddim_add(st_aclkstats, "online", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(st_aclkstats, rd_online_status, per_sample->offline_during_sample ? 0 : permanent->online); - - rrdset_done(st_aclkstats); -} - -static void aclk_stats_query_queue(struct aclk_metrics_per_sample *per_sample) -{ - static RRDSET *st_query_thread = NULL; - static RRDDIM *rd_queued = NULL; - static RRDDIM *rd_dispatched = NULL; - - if (unlikely(!st_query_thread)) { - st_query_thread = rrdset_create_localhost( - "netdata", "aclk_query_per_second", NULL, "aclk", NULL, "ACLK Queries per second", "queries/s", - "netdata", "stats", 200001, localhost->rrd_update_every, RRDSET_TYPE_AREA); - - rd_queued = rrddim_add(st_query_thread, "added", NULL, 1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - rd_dispatched = rrddim_add(st_query_thread, "dispatched", NULL, -1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(st_query_thread, rd_queued, per_sample->queries_queued); - rrddim_set_by_pointer(st_query_thread, rd_dispatched, per_sample->queries_dispatched); - - rrdset_done(st_query_thread); -} - -#ifdef NETDATA_INTERNAL_CHECKS -static void aclk_stats_latency(struct aclk_metrics_per_sample *per_sample) -{ - static RRDSET *st = NULL; - static RRDDIM *rd_avg = NULL; - static RRDDIM *rd_max = NULL; - - if (unlikely(!st)) { - st = rrdset_create_localhost( - "netdata", "aclk_latency_mqtt", NULL, "aclk", NULL, "ACLK Message Publish Latency", "ms", - "netdata", "stats", 200002, localhost->rrd_update_every, RRDSET_TYPE_LINE); - - rd_avg = rrddim_add(st, "avg", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_max = rrddim_add(st, "max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - if(per_sample->latency_count) - rrddim_set_by_pointer(st, rd_avg, roundf((float)per_sample->latency_total / per_sample->latency_count)); - else - rrddim_set_by_pointer(st, rd_avg, 0); - - rrddim_set_by_pointer(st, rd_max, per_sample->latency_max); - - rrdset_done(st); -} -#endif - -static void aclk_stats_cloud_req(struct aclk_metrics_per_sample *per_sample) -{ - static RRDSET *st = NULL; - static RRDDIM *rd_rq_rcvd = NULL; - static RRDDIM *rd_rq_err = NULL; - - if (unlikely(!st)) { - st = rrdset_create_localhost( - "netdata", "aclk_cloud_req", NULL, "aclk", NULL, "Requests received from cloud", "req/s", - "netdata", "stats", 200005, localhost->rrd_update_every, RRDSET_TYPE_STACKED); - - rd_rq_rcvd = rrddim_add(st, "received", NULL, 1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - rd_rq_err = rrddim_add(st, "malformed", NULL, 1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(st, rd_rq_rcvd, per_sample->cloud_req_recvd - per_sample->cloud_req_err); - rrddim_set_by_pointer(st, rd_rq_err, per_sample->cloud_req_err); - - rrdset_done(st); -} - -static void aclk_stats_cloud_req_type(struct aclk_metrics_per_sample *per_sample) -{ - static RRDSET *st = NULL; - static RRDDIM *dims[ACLK_QUERY_TYPE_COUNT]; - - if (unlikely(!st)) { - st = rrdset_create_localhost( - "netdata", "aclk_processed_query_type", NULL, "aclk", NULL, "Query thread commands processed by their type", "cmd/s", - "netdata", "stats", 200006, localhost->rrd_update_every, RRDSET_TYPE_STACKED); - - for (int i = 0; i < ACLK_QUERY_TYPE_COUNT; i++) - dims[i] = rrddim_add(st, aclk_query_get_name(i, 1), NULL, 1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - - } - - for (int i = 0; i < ACLK_QUERY_TYPE_COUNT; i++) - rrddim_set_by_pointer(st, dims[i], per_sample->queries_per_type[i]); - - rrdset_done(st); -} - -static char *cloud_req_http_type_names[ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT] = { - "other", - "info", - "data", - "alarms", - "alarm_log", - "chart", - "charts", - "function", - "functions" - // if you change then update `ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT`. -}; - -int aclk_cloud_req_http_type_to_idx(const char *name) -{ - for (int i = 1; i < ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT; i++) - if (!strcmp(cloud_req_http_type_names[i], name)) - return i; - return 0; -} - -static void aclk_stats_cloud_req_http_type(struct aclk_metrics_per_sample *per_sample) -{ - static RRDSET *st = NULL; - static RRDDIM *rd_rq_types[ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT]; - - if (unlikely(!st)) { - st = rrdset_create_localhost( - "netdata", "aclk_cloud_req_http_type", NULL, "aclk", NULL, "Requests received from cloud via HTTP by their type", "req/s", - "netdata", "stats", 200007, localhost->rrd_update_every, RRDSET_TYPE_STACKED); - - for (int i = 0; i < ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT; i++) - rd_rq_types[i] = rrddim_add(st, cloud_req_http_type_names[i], NULL, 1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - } - - for (int i = 0; i < ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT; i++) - rrddim_set_by_pointer(st, rd_rq_types[i], per_sample->cloud_req_http_by_type[i]); - - rrdset_done(st); -} - -#define MAX_DIM_NAME 22 -static void aclk_stats_query_threads(uint32_t *queries_per_thread) -{ - static RRDSET *st = NULL; - - char dim_name[MAX_DIM_NAME]; - - if (unlikely(!st)) { - st = rrdset_create_localhost( - "netdata", "aclk_query_threads", NULL, "aclk", NULL, "Queries Processed Per Thread", "req/s", - "netdata", "stats", 200009, localhost->rrd_update_every, RRDSET_TYPE_STACKED); - - for (int i = 0; i < aclk_stats_cfg.query_thread_count; i++) { - if (snprintfz(dim_name, MAX_DIM_NAME, "Query %d", i) < 0) - netdata_log_error("snprintf encoding error"); - aclk_qt_data[i].dim = rrddim_add(st, dim_name, NULL, 1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - } - } - - for (int i = 0; i < aclk_stats_cfg.query_thread_count; i++) { - rrddim_set_by_pointer(st, aclk_qt_data[i].dim, queries_per_thread[i]); - } - - rrdset_done(st); -} - -static void aclk_stats_query_time(struct aclk_metrics_per_sample *per_sample) -{ - static RRDSET *st = NULL; - static RRDDIM *rd_rq_avg = NULL; - static RRDDIM *rd_rq_max = NULL; - static RRDDIM *rd_rq_total = NULL; - - if (unlikely(!st)) { - st = rrdset_create_localhost( - "netdata", "aclk_query_time", NULL, "aclk", NULL, "Time it took to process cloud requested DB queries", "us", - "netdata", "stats", 200008, localhost->rrd_update_every, RRDSET_TYPE_LINE); - - rd_rq_avg = rrddim_add(st, "avg", NULL, 1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - rd_rq_max = rrddim_add(st, "max", NULL, 1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - rd_rq_total = rrddim_add(st, "total", NULL, 1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - } - - if(per_sample->cloud_q_process_count) - rrddim_set_by_pointer(st, rd_rq_avg, roundf((float)per_sample->cloud_q_process_total / per_sample->cloud_q_process_count)); - else - rrddim_set_by_pointer(st, rd_rq_avg, 0); - rrddim_set_by_pointer(st, rd_rq_max, per_sample->cloud_q_process_max); - rrddim_set_by_pointer(st, rd_rq_total, per_sample->cloud_q_process_total); - - rrdset_done(st); -} - -const char *rx_handler_get_name(size_t i); -static void aclk_stats_newproto_rx(uint32_t *rx_msgs_sample) -{ - static RRDSET *st = NULL; - - if (unlikely(!st)) { - st = rrdset_create_localhost( - "netdata", "aclk_protobuf_rx_types", NULL, "aclk", NULL, "Received new cloud architecture messages by their type.", "msg/s", - "netdata", "stats", 200010, localhost->rrd_update_every, RRDSET_TYPE_STACKED); - - for (unsigned int i = 0; i < aclk_stats_cfg.proto_hdl_cnt; i++) { - aclk_stats_cfg.rx_msg_dims[i] = rrddim_add(st, rx_handler_get_name(i), NULL, 1, localhost->rrd_update_every, RRD_ALGORITHM_ABSOLUTE); - } - } - - for (unsigned int i = 0; i < aclk_stats_cfg.proto_hdl_cnt; i++) - rrddim_set_by_pointer(st, aclk_stats_cfg.rx_msg_dims[i], rx_msgs_sample[i]); - - rrdset_done(st); -} - -static void aclk_stats_mqtt_wss(struct mqtt_wss_stats *stats) -{ - static RRDSET *st = NULL; - static RRDDIM *rd_sent = NULL; - static RRDDIM *rd_recvd = NULL; - static uint64_t sent = 0; - static uint64_t recvd = 0; - - static RRDSET *st_txbuf_perc = NULL; - static RRDDIM *rd_txbuf_perc = NULL; - - static RRDSET *st_txbuf = NULL; - static RRDDIM *rd_tx_buffer_usable = NULL; - static RRDDIM *rd_tx_buffer_reclaimable = NULL; - static RRDDIM *rd_tx_buffer_used = NULL; - static RRDDIM *rd_tx_buffer_free = NULL; - static RRDDIM *rd_tx_buffer_size = NULL; - - static RRDSET *st_timing = NULL; - static RRDDIM *rd_keepalive = NULL; - static RRDDIM *rd_read_socket = NULL; - static RRDDIM *rd_write_socket = NULL; - static RRDDIM *rd_process_websocket = NULL; - static RRDDIM *rd_process_mqtt = NULL; - - sent += stats->bytes_tx; - recvd += stats->bytes_rx; - - if (unlikely(!st)) { - st = rrdset_create_localhost( - "netdata", "aclk_openssl_bytes", NULL, "aclk", NULL, "Received and Sent bytes.", "B/s", - "netdata", "stats", 200011, localhost->rrd_update_every, RRDSET_TYPE_STACKED); - - rd_sent = rrddim_add(st, "sent", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); - rd_recvd = rrddim_add(st, "received", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); - } - - if (unlikely(!st_txbuf_perc)) { - st_txbuf_perc = rrdset_create_localhost( - "netdata", "aclk_mqtt_tx_perc", NULL, "aclk", NULL, "Actively used percentage of MQTT Tx Buffer,", "%", - "netdata", "stats", 200012, localhost->rrd_update_every, RRDSET_TYPE_LINE); - - rd_txbuf_perc = rrddim_add(st_txbuf_perc, "used", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); - } - - if (unlikely(!st_txbuf)) { - st_txbuf = rrdset_create_localhost( - "netdata", "aclk_mqtt_tx_queue", NULL, "aclk", NULL, "State of transmit MQTT queue.", "B", - "netdata", "stats", 200013, localhost->rrd_update_every, RRDSET_TYPE_LINE); - - rd_tx_buffer_usable = rrddim_add(st_txbuf, "usable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_tx_buffer_reclaimable = rrddim_add(st_txbuf, "reclaimable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_tx_buffer_used = rrddim_add(st_txbuf, "used", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_tx_buffer_free = rrddim_add(st_txbuf, "free", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_tx_buffer_size = rrddim_add(st_txbuf, "size", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - if (unlikely(!st_timing)) { - st_timing = rrdset_create_localhost( - "netdata", "aclk_mqtt_wss_time", NULL, "aclk", NULL, "Time spent handling MQTT, WSS, SSL and network communication.", "us", - "netdata", "stats", 200014, localhost->rrd_update_every, RRDSET_TYPE_STACKED); - - rd_keepalive = rrddim_add(st_timing, "keep-alive", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_read_socket = rrddim_add(st_timing, "socket_read_ssl", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_write_socket = rrddim_add(st_timing, "socket_write_ssl", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_process_websocket = rrddim_add(st_timing, "process_websocket", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - rd_process_mqtt = rrddim_add(st_timing, "process_mqtt", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(st, rd_sent, sent); - rrddim_set_by_pointer(st, rd_recvd, recvd); - - float usage = ((float)stats->mqtt.tx_buffer_free + stats->mqtt.tx_buffer_reclaimable) / stats->mqtt.tx_buffer_size; - usage = (1 - usage) * 10000; - rrddim_set_by_pointer(st_txbuf_perc, rd_txbuf_perc, usage); - - rrddim_set_by_pointer(st_txbuf, rd_tx_buffer_usable, stats->mqtt.tx_buffer_reclaimable + stats->mqtt.tx_buffer_free); - rrddim_set_by_pointer(st_txbuf, rd_tx_buffer_reclaimable, stats->mqtt.tx_buffer_reclaimable); - rrddim_set_by_pointer(st_txbuf, rd_tx_buffer_used, stats->mqtt.tx_buffer_used); - rrddim_set_by_pointer(st_txbuf, rd_tx_buffer_free, stats->mqtt.tx_buffer_free); - rrddim_set_by_pointer(st_txbuf, rd_tx_buffer_size, stats->mqtt.tx_buffer_size); - - rrddim_set_by_pointer(st_timing, rd_keepalive, stats->time_keepalive); - rrddim_set_by_pointer(st_timing, rd_read_socket, stats->time_read_socket); - rrddim_set_by_pointer(st_timing, rd_write_socket, stats->time_write_socket); - rrddim_set_by_pointer(st_timing, rd_process_websocket, stats->time_process_websocket); - rrddim_set_by_pointer(st_timing, rd_process_mqtt, stats->time_process_mqtt); - - rrdset_done(st); - rrdset_done(st_txbuf_perc); - rrdset_done(st_txbuf); - rrdset_done(st_timing); -} - -void aclk_stats_thread_prepare(int query_thread_count, unsigned int proto_hdl_cnt) -{ - aclk_qt_data = callocz(query_thread_count, sizeof(struct aclk_qt_data)); - aclk_queries_per_thread = callocz(query_thread_count, sizeof(uint32_t)); - aclk_queries_per_thread_sample = callocz(query_thread_count, sizeof(uint32_t)); - - memset(&aclk_metrics_per_sample, 0, sizeof(struct aclk_metrics_per_sample)); - - aclk_stats_cfg.proto_hdl_cnt = proto_hdl_cnt; - aclk_stats_cfg.aclk_proto_rx_msgs_sample = callocz(proto_hdl_cnt, sizeof(*aclk_proto_rx_msgs_sample)); - aclk_proto_rx_msgs_sample = callocz(proto_hdl_cnt, sizeof(*aclk_proto_rx_msgs_sample)); - aclk_stats_cfg.rx_msg_dims = callocz(proto_hdl_cnt, sizeof(RRDDIM*)); -} - -void aclk_stats_thread_cleanup() -{ - freez(aclk_stats_cfg.rx_msg_dims); - freez(aclk_proto_rx_msgs_sample); - freez(aclk_stats_cfg.aclk_proto_rx_msgs_sample); - freez(aclk_qt_data); - freez(aclk_queries_per_thread); - freez(aclk_queries_per_thread_sample); -} - -void *aclk_stats_main_thread(void *ptr) -{ - struct aclk_stats_thread *args = ptr; - - aclk_stats_cfg.query_thread_count = args->query_thread_count; - - heartbeat_t hb; - heartbeat_init(&hb); - usec_t step_ut = localhost->rrd_update_every * USEC_PER_SEC; - - struct aclk_metrics_per_sample per_sample; - struct aclk_metrics permanent; - - while (service_running(SERVICE_ACLK | SERVICE_COLLECTORS)) { - - // ------------------------------------------------------------------------ - // Wait for the next iteration point. - - heartbeat_next(&hb, step_ut); - - if (!service_running(SERVICE_ACLK | SERVICE_COLLECTORS)) break; - - ACLK_STATS_LOCK; - // to not hold lock longer than necessary, especially not to hold it - // during database rrd* operations - memcpy(&per_sample, &aclk_metrics_per_sample, sizeof(struct aclk_metrics_per_sample)); - - memcpy(aclk_stats_cfg.aclk_proto_rx_msgs_sample, aclk_proto_rx_msgs_sample, sizeof(*aclk_proto_rx_msgs_sample) * aclk_stats_cfg.proto_hdl_cnt); - memset(aclk_proto_rx_msgs_sample, 0, sizeof(*aclk_proto_rx_msgs_sample) * aclk_stats_cfg.proto_hdl_cnt); - - memcpy(&permanent, &aclk_metrics, sizeof(struct aclk_metrics)); - memset(&aclk_metrics_per_sample, 0, sizeof(struct aclk_metrics_per_sample)); - - memcpy(aclk_queries_per_thread_sample, aclk_queries_per_thread, sizeof(uint32_t) * aclk_stats_cfg.query_thread_count); - memset(aclk_queries_per_thread, 0, sizeof(uint32_t) * aclk_stats_cfg.query_thread_count); - ACLK_STATS_UNLOCK; - - aclk_stats_collect(&per_sample, &permanent); - aclk_stats_query_queue(&per_sample); -#ifdef NETDATA_INTERNAL_CHECKS - aclk_stats_latency(&per_sample); -#endif - - aclk_stats_cloud_req(&per_sample); - aclk_stats_cloud_req_type(&per_sample); - aclk_stats_cloud_req_http_type(&per_sample); - - aclk_stats_query_threads(aclk_queries_per_thread_sample); - - aclk_stats_query_time(&per_sample); - - struct mqtt_wss_stats mqtt_wss_stats = mqtt_wss_get_stats(args->client); - aclk_stats_mqtt_wss(&mqtt_wss_stats); - - aclk_stats_newproto_rx(aclk_stats_cfg.aclk_proto_rx_msgs_sample); - } - - return 0; -} - -void aclk_stats_upd_online(int online) { - if(!aclk_stats_enabled) - return; - - ACLK_STATS_LOCK; - aclk_metrics.online = online; - - if(!online) - aclk_metrics_per_sample.offline_during_sample = 1; - ACLK_STATS_UNLOCK; -} - -#ifdef NETDATA_INTERNAL_CHECKS -static usec_t pub_time[UINT16_MAX + 1] = {0}; -void aclk_stats_msg_published(uint16_t id) -{ - ACLK_STATS_LOCK; - pub_time[id] = now_boottime_usec(); - ACLK_STATS_UNLOCK; -} - -void aclk_stats_msg_puback(uint16_t id) -{ - ACLK_STATS_LOCK; - usec_t t; - - if (!aclk_stats_enabled) { - ACLK_STATS_UNLOCK; - return; - } - - if (unlikely(!pub_time[id])) { - ACLK_STATS_UNLOCK; - netdata_log_error("Received PUBACK for unknown message?!"); - return; - } - - t = now_boottime_usec() - pub_time[id]; - t /= USEC_PER_MS; - pub_time[id] = 0; - if (aclk_metrics_per_sample.latency_max < t) - aclk_metrics_per_sample.latency_max = t; - - aclk_metrics_per_sample.latency_total += t; - aclk_metrics_per_sample.latency_count++; - ACLK_STATS_UNLOCK; -} -#endif /* NETDATA_INTERNAL_CHECKS */ diff --git a/src/aclk/aclk_stats.h b/src/aclk/aclk_stats.h deleted file mode 100644 index e13269557..000000000 --- a/src/aclk/aclk_stats.h +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_ACLK_STATS_H -#define NETDATA_ACLK_STATS_H - -#include "daemon/common.h" -#include "libnetdata/libnetdata.h" -#include "aclk_query_queue.h" -#include "mqtt_websockets/mqtt_wss_client.h" - -extern netdata_mutex_t aclk_stats_mutex; - -#define ACLK_STATS_LOCK netdata_mutex_lock(&aclk_stats_mutex) -#define ACLK_STATS_UNLOCK netdata_mutex_unlock(&aclk_stats_mutex) - -// if you change update `cloud_req_http_type_names`. -#define ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT 9 - -int aclk_cloud_req_http_type_to_idx(const char *name); - -struct aclk_stats_thread { - ND_THREAD *thread; - int query_thread_count; - mqtt_wss_client client; -}; - -// preserve between samples -struct aclk_metrics { - volatile uint8_t online; -}; - -// reset to 0 on every sample -extern struct aclk_metrics_per_sample { - /* in the unlikely event of ACLK disconnecting - and reconnecting under 1 sampling rate - we want to make sure we record the disconnection - despite it being then seemingly longer in graph */ - volatile uint8_t offline_during_sample; - - volatile uint32_t queries_queued; - volatile uint32_t queries_dispatched; - -#ifdef NETDATA_INTERNAL_CHECKS - volatile uint32_t latency_max; - volatile uint32_t latency_total; - volatile uint32_t latency_count; -#endif - - volatile uint32_t cloud_req_recvd; - volatile uint32_t cloud_req_err; - - // query types. - volatile uint32_t queries_per_type[ACLK_QUERY_TYPE_COUNT]; - - // HTTP-specific request types. - volatile uint32_t cloud_req_http_by_type[ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT]; - - volatile uint32_t cloud_q_process_total; - volatile uint32_t cloud_q_process_count; - volatile uint32_t cloud_q_process_max; -} aclk_metrics_per_sample; - -extern uint32_t *aclk_proto_rx_msgs_sample; - -extern uint32_t *aclk_queries_per_thread; - -void *aclk_stats_main_thread(void *ptr); -void aclk_stats_thread_prepare(int query_thread_count, unsigned int proto_hdl_cnt); -void aclk_stats_thread_cleanup(); -void aclk_stats_upd_online(int online); - -#ifdef NETDATA_INTERNAL_CHECKS -void aclk_stats_msg_published(uint16_t id); -void aclk_stats_msg_puback(uint16_t id); -#endif /* NETDATA_INTERNAL_CHECKS */ - -#endif /* NETDATA_ACLK_STATS_H */ diff --git a/src/aclk/aclk_tx_msgs.c b/src/aclk/aclk_tx_msgs.c index c1ed68052..2d256279e 100644 --- a/src/aclk/aclk_tx_msgs.c +++ b/src/aclk/aclk_tx_msgs.c @@ -1,9 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "aclk_tx_msgs.h" -#include "daemon/common.h" #include "aclk_util.h" -#include "aclk_stats.h" #include "aclk.h" #include "aclk_capas.h" @@ -13,9 +11,6 @@ #pragma region aclk_tx_msgs helper functions #endif -// version for aclk legacy (old cloud arch) -#define ACLK_VERSION 2 - static void freez_aclk_publish5a(void *ptr) { freez(ptr); } @@ -23,6 +18,8 @@ static void freez_aclk_publish5b(void *ptr) { freez(ptr); } +#define ACLK_HEADER_VERSION (2) + uint16_t aclk_send_bin_message_subtopic_pid(mqtt_wss_client client, char *msg, size_t msg_len, enum aclk_topics subtopic, const char *msgname) { #ifndef ACLK_LOG_CONVERSATION_DIR @@ -38,10 +35,6 @@ uint16_t aclk_send_bin_message_subtopic_pid(mqtt_wss_client client, char *msg, s mqtt_wss_publish5(client, (char*)topic, NULL, msg, &freez_aclk_publish5a, msg_len, MQTT_WSS_PUB_QOS1, &packet_id); -#ifdef NETDATA_INTERNAL_CHECKS - aclk_stats_msg_published(packet_id); -#endif - if (aclklog_enabled) { char *json = protomsg_to_json(msg, msg_len, msgname); log_aclk_message_bin(json, strlen(json), 1, topic, msgname); @@ -51,14 +44,13 @@ uint16_t aclk_send_bin_message_subtopic_pid(mqtt_wss_client client, char *msg, s return packet_id; } -#define TOPIC_MAX_LEN 512 #define V2_BIN_PAYLOAD_SEPARATOR "\x0D\x0A\x0D\x0A" -static int aclk_send_message_with_bin_payload(mqtt_wss_client client, json_object *msg, const char *topic, const void *payload, size_t payload_len) +static short aclk_send_message_with_bin_payload(mqtt_wss_client client, json_object *msg, const char *topic, const void *payload, size_t payload_len) { uint16_t packet_id; const char *str; char *full_msg = NULL; - int len; + size_t len; if (unlikely(!topic || topic[0] != '/')) { netdata_log_error("Full topic required!"); @@ -78,20 +70,16 @@ static int aclk_send_message_with_bin_payload(mqtt_wss_client client, json_objec json_object_put(msg); if (payload_len) { - memcpy(&full_msg[len], V2_BIN_PAYLOAD_SEPARATOR, strlen(V2_BIN_PAYLOAD_SEPARATOR)); + memcpy(&full_msg[len], V2_BIN_PAYLOAD_SEPARATOR, sizeof(V2_BIN_PAYLOAD_SEPARATOR) - 1); len += strlen(V2_BIN_PAYLOAD_SEPARATOR); memcpy(&full_msg[len], payload, payload_len); } int rc = mqtt_wss_publish5(client, (char*)topic, NULL, full_msg, &freez_aclk_publish5b, full_msg_len, MQTT_WSS_PUB_QOS1, &packet_id); - if (rc == MQTT_WSS_ERR_TOO_BIG_FOR_SERVER) + if (rc == MQTT_WSS_ERR_MSG_TOO_BIG) return HTTP_RESP_CONTENT_TOO_LONG; -#ifdef NETDATA_INTERNAL_CHECKS - aclk_stats_msg_published(packet_id); -#endif - return 0; } @@ -99,12 +87,14 @@ static int aclk_send_message_with_bin_payload(mqtt_wss_client client, json_objec * Creates universal header common for all ACLK messages. User gets ownership of json object created. * Usually this is freed by send function after message has been sent. */ -static struct json_object *create_hdr(const char *type, const char *msg_id, time_t ts_secs, usec_t ts_us, int version) +static struct json_object *create_hdr(const char *type, const char *msg_id) { nd_uuid_t uuid; - char uuid_str[36 + 1]; + char uuid_str[UUID_STR_LEN]; json_object *tmp; json_object *obj = json_object_new_object(); + time_t ts_secs; + usec_t ts_us; tmp = json_object_new_string(type); json_object_object_add(obj, "type", tmp); @@ -115,11 +105,9 @@ static struct json_object *create_hdr(const char *type, const char *msg_id, time msg_id = uuid_str; } - if (ts_secs == 0) { - ts_us = now_realtime_usec(); - ts_secs = ts_us / USEC_PER_SEC; - ts_us = ts_us % USEC_PER_SEC; - } + ts_us = now_realtime_usec(); + ts_secs = ts_us / USEC_PER_SEC; + ts_us = ts_us % USEC_PER_SEC; tmp = json_object_new_string(msg_id); json_object_object_add(obj, "msg-id", tmp); @@ -144,7 +132,7 @@ static struct json_object *create_hdr(const char *type, const char *msg_id, time tmp = json_object_new_int64(aclk_session_us); json_object_object_add(obj, "connect-offset-usec", tmp); - tmp = json_object_new_int(version); + tmp = json_object_new_int(ACLK_HEADER_VERSION); json_object_object_add(obj, "version", tmp); return obj; @@ -161,7 +149,7 @@ static struct json_object *create_hdr(const char *type, const char *msg_id, time void aclk_http_msg_v2_err(mqtt_wss_client client, const char *topic, const char *msg_id, int http_code, int ec, const char* emsg, const char *payload, size_t payload_len) { json_object *tmp, *msg; - msg = create_hdr("http", msg_id, 0, 0, 2); + msg = create_hdr("http", msg_id); tmp = json_object_new_int(http_code); json_object_object_add(msg, "http-code", tmp); @@ -176,11 +164,12 @@ void aclk_http_msg_v2_err(mqtt_wss_client client, const char *topic, const char } } -int aclk_http_msg_v2(mqtt_wss_client client, const char *topic, const char *msg_id, usec_t t_exec, usec_t created, int http_code, const char *payload, size_t payload_len) +short aclk_http_msg_v2(mqtt_wss_client client, const char *topic, const char *msg_id, usec_t t_exec, usec_t created, + short http_code, const char *payload, size_t payload_len) { json_object *tmp, *msg; - msg = create_hdr("http", msg_id, 0, 0, 2); + msg = create_hdr("http", msg_id); tmp = json_object_new_int64(t_exec); json_object_object_add(msg, "t-exec", tmp); @@ -191,7 +180,7 @@ int aclk_http_msg_v2(mqtt_wss_client client, const char *topic, const char *msg_ tmp = json_object_new_int(http_code); json_object_object_add(msg, "http-code", tmp); - int rc = aclk_send_message_with_bin_payload(client, msg, topic, payload, payload_len); + short rc = aclk_send_message_with_bin_payload(client, msg, topic, payload, payload_len); switch (rc) { case HTTP_RESP_CONTENT_TOO_LONG: @@ -200,12 +189,11 @@ int aclk_http_msg_v2(mqtt_wss_client client, const char *topic, const char *msg_ case HTTP_RESP_INTERNAL_SERVER_ERROR: aclk_http_msg_v2_err(client, topic, msg_id, rc, CLOUD_EC_FAIL_TOPIC, CLOUD_EMSG_FAIL_TOPIC, payload, payload_len); break; - case HTTP_RESP_GATEWAY_TIMEOUT: - case HTTP_RESP_SERVICE_UNAVAILABLE: - aclk_http_msg_v2_err(client, topic, msg_id, rc, CLOUD_EC_SND_TIMEOUT, CLOUD_EMSG_SND_TIMEOUT, payload, payload_len); + default: + rc = http_code; break; } - return rc ? rc : http_code; + return rc; } uint16_t aclk_send_agent_connection_update(mqtt_wss_client client, int reachable) { @@ -219,19 +207,19 @@ uint16_t aclk_send_agent_connection_update(mqtt_wss_client client, int reachable .capabilities = aclk_get_agent_capas() }; - rrdhost_aclk_state_lock(localhost); - if (unlikely(!localhost->aclk_state.claimed_id)) { + CLAIM_ID claim_id = claim_id_get(); + if (unlikely(!claim_id_is_set(claim_id))) { netdata_log_error("Internal error. Should not come here if not claimed"); - rrdhost_aclk_state_unlock(localhost); return 0; } - if (localhost->aclk_state.prev_claimed_id) - conn.claim_id = localhost->aclk_state.prev_claimed_id; + + CLAIM_ID previous_claim_id = claim_id_get_last_working(); + if (claim_id_is_set(previous_claim_id)) + conn.claim_id = previous_claim_id.str; else - conn.claim_id = localhost->aclk_state.claimed_id; + conn.claim_id = claim_id.str; char *msg = generate_update_agent_connection(&len, &conn); - rrdhost_aclk_state_unlock(localhost); if (!msg) { netdata_log_error("Error generating agent::v1::UpdateAgentConnection payload"); @@ -239,10 +227,9 @@ uint16_t aclk_send_agent_connection_update(mqtt_wss_client client, int reachable } pid = aclk_send_bin_message_subtopic_pid(client, msg, len, ACLK_TOPICID_AGENT_CONN, "UpdateAgentConnection"); - if (localhost->aclk_state.prev_claimed_id) { - freez(localhost->aclk_state.prev_claimed_id); - localhost->aclk_state.prev_claimed_id = NULL; - } + if (claim_id_is_set(previous_claim_id)) + claim_id_clear_previous_working(); + return pid; } @@ -254,16 +241,14 @@ char *aclk_generate_lwt(size_t *size) { .capabilities = NULL }; - rrdhost_aclk_state_lock(localhost); - if (unlikely(!localhost->aclk_state.claimed_id)) { + CLAIM_ID claim_id = claim_id_get(); + if(!claim_id_is_set(claim_id)) { netdata_log_error("Internal error. Should not come here if not claimed"); - rrdhost_aclk_state_unlock(localhost); return NULL; } - conn.claim_id = localhost->aclk_state.claimed_id; + conn.claim_id = claim_id.str; char *msg = generate_update_agent_connection(size, &conn); - rrdhost_aclk_state_unlock(localhost); if (!msg) netdata_log_error("Error generating agent::v1::UpdateAgentConnection payload for LWT"); diff --git a/src/aclk/aclk_tx_msgs.h b/src/aclk/aclk_tx_msgs.h index 86ed20c38..6b11996d1 100644 --- a/src/aclk/aclk_tx_msgs.h +++ b/src/aclk/aclk_tx_msgs.h @@ -4,7 +4,6 @@ #include <json-c/json.h> #include "libnetdata/libnetdata.h" -#include "daemon/common.h" #include "mqtt_websockets/mqtt_wss_client.h" #include "schema-wrappers/schema_wrappers.h" #include "aclk_util.h" @@ -12,7 +11,8 @@ uint16_t aclk_send_bin_message_subtopic_pid(mqtt_wss_client client, char *msg, size_t msg_len, enum aclk_topics subtopic, const char *msgname); void aclk_http_msg_v2_err(mqtt_wss_client client, const char *topic, const char *msg_id, int http_code, int ec, const char* emsg, const char *payload, size_t payload_len); -int aclk_http_msg_v2(mqtt_wss_client client, const char *topic, const char *msg_id, usec_t t_exec, usec_t created, int http_code, const char *payload, size_t payload_len); +short aclk_http_msg_v2(mqtt_wss_client client, const char *topic, const char *msg_id, usec_t t_exec, usec_t created, + short http_code, const char *payload, size_t payload_len); uint16_t aclk_send_agent_connection_update(mqtt_wss_client client, int reachable); char *aclk_generate_lwt(size_t *size); diff --git a/src/aclk/aclk_util.c b/src/aclk/aclk_util.c index 3bf2e3f18..d01fa8f2c 100644 --- a/src/aclk/aclk_util.c +++ b/src/aclk/aclk_util.c @@ -2,8 +2,6 @@ #include "aclk_util.h" -#ifdef ENABLE_ACLK - #include "aclk_proxy.h" #include "daemon/common.h" @@ -12,8 +10,6 @@ usec_t aclk_session_newarch = 0; aclk_env_t *aclk_env = NULL; -int chart_batch_id; - aclk_encoding_type_t aclk_encoding_type_t_from_str(const char *str) { if (!strcmp(str, "json")) { return ACLK_ENC_JSON; @@ -186,20 +182,18 @@ static void topic_generate_final(struct aclk_topic *t) { if (!replace_tag) return; - rrdhost_aclk_state_lock(localhost); - if (unlikely(!localhost->aclk_state.claimed_id)) { + CLAIM_ID claim_id = claim_id_get(); + if (unlikely(!claim_id_is_set(claim_id))) { netdata_log_error("This should never be called if agent not claimed"); - rrdhost_aclk_state_unlock(localhost); return; } - t->topic = mallocz(strlen(t->topic_recvd) + 1 - strlen(CLAIM_ID_REPLACE_TAG) + strlen(localhost->aclk_state.claimed_id)); + t->topic = mallocz(strlen(t->topic_recvd) + 1 - strlen(CLAIM_ID_REPLACE_TAG) + strlen(claim_id.str)); memcpy(t->topic, t->topic_recvd, replace_tag - t->topic_recvd); dest = t->topic + (replace_tag - t->topic_recvd); - memcpy(dest, localhost->aclk_state.claimed_id, strlen(localhost->aclk_state.claimed_id)); - dest += strlen(localhost->aclk_state.claimed_id); - rrdhost_aclk_state_unlock(localhost); + memcpy(dest, claim_id.str, strlen(claim_id.str)); + dest += strlen(claim_id.str); replace_tag += strlen(CLAIM_ID_REPLACE_TAG); strcpy(dest, replace_tag); dest += strlen(replace_tag); @@ -315,7 +309,7 @@ const char *aclk_get_topic(enum aclk_topics topic) * having to resort to callbacks. */ -const char *aclk_topic_cache_iterate(aclk_topic_cache_iter_t *iter) +const char *aclk_topic_cache_iterate(size_t *iter) { if (!aclk_topic_cache) { netdata_log_error("Topic cache not initialized when %s was called.", __FUNCTION__); @@ -348,15 +342,13 @@ unsigned long int aclk_tbeb_delay(int reset, int base, unsigned long int min, un attempt++; - if (attempt == 0) { - srandom(time(NULL)); + if (attempt == 0) return 0; - } unsigned long int delay = pow(base, attempt - 1); delay *= MSEC_PER_SEC; - delay += (random() % (MAX(1000, delay/2))); + delay += (os_random32() % (MAX(1000, delay/2))); if (delay <= min * MSEC_PER_SEC) return min; @@ -440,45 +432,3 @@ void aclk_set_proxy(char **ohost, int *port, char **uname, char **pwd, enum mqtt freez(proxy); } -#endif /* ENABLE_ACLK */ - -#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110 -static EVP_ENCODE_CTX *EVP_ENCODE_CTX_new(void) -{ - EVP_ENCODE_CTX *ctx = OPENSSL_malloc(sizeof(*ctx)); - - if (ctx != NULL) { - memset(ctx, 0, sizeof(*ctx)); - } - return ctx; -} -static void EVP_ENCODE_CTX_free(EVP_ENCODE_CTX *ctx) -{ - OPENSSL_free(ctx); - return; -} -#endif - -int base64_encode_helper(unsigned char *out, int *outl, const unsigned char *in, int in_len) -{ - int len; - unsigned char *str = out; - EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new(); - EVP_EncodeInit(ctx); - EVP_EncodeUpdate(ctx, str, outl, in, in_len); - str += *outl; - EVP_EncodeFinal(ctx, str, &len); - *outl += len; - - str = out; - while(*str) { - if (*str != 0x0D && *str != 0x0A) - *out++ = *str++; - else - str++; - } - *out = 0; - - EVP_ENCODE_CTX_free(ctx); - return 0; -} diff --git a/src/aclk/aclk_util.h b/src/aclk/aclk_util.h index 6c0239cc3..24e179964 100644 --- a/src/aclk/aclk_util.h +++ b/src/aclk/aclk_util.h @@ -3,8 +3,6 @@ #define ACLK_UTIL_H #include "libnetdata/libnetdata.h" - -#ifdef ENABLE_ACLK #include "mqtt_websockets/mqtt_wss_client.h" #define CLOUD_EC_MALFORMED_NODE_ID 1 @@ -95,15 +93,10 @@ enum aclk_topics { ACLK_TOPICID_CTXS_UPDATED = 20 }; -typedef size_t aclk_topic_cache_iter_t; -#define ACLK_TOPIC_CACHE_ITER_T_INITIALIZER (0) - const char *aclk_get_topic(enum aclk_topics topic); -int aclk_generate_topic_cache(struct json_object *json); +int aclk_generate_topic_cache(json_object *json); void free_topic_cache(void); -const char *aclk_topic_cache_iterate(aclk_topic_cache_iter_t *iter); -// TODO -// aclk_topics_reload //when claim id changes +const char *aclk_topic_cache_iterate(size_t *iter); #ifdef ACLK_LOG_CONVERSATION_DIR extern volatile int aclk_conversation_log_counter; @@ -114,8 +107,5 @@ unsigned long int aclk_tbeb_delay(int reset, int base, unsigned long int min, un #define aclk_tbeb_reset(x) aclk_tbeb_delay(1, 0, 0, 0) void aclk_set_proxy(char **ohost, int *port, char **uname, char **pwd, enum mqtt_wss_proxy_type *type); -#endif /* ENABLE_ACLK */ - -int base64_encode_helper(unsigned char *out, int *outl, const unsigned char *in, int in_len); #endif /* ACLK_UTIL_H */ diff --git a/src/aclk/helpers/mqtt_wss_pal.h b/src/aclk/helpers/mqtt_wss_pal.h deleted file mode 100644 index fe1aacf49..000000000 --- a/src/aclk/helpers/mqtt_wss_pal.h +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef MQTT_WSS_PAL_H -#define MQTT_WSS_PAL_H - -#include "libnetdata/libnetdata.h" - -#undef OPENSSL_VERSION_095 -#undef OPENSSL_VERSION_097 -#undef OPENSSL_VERSION_110 -#undef OPENSSL_VERSION_111 - -#endif /* MQTT_WSS_PAL_H */ diff --git a/src/aclk/helpers/ringbuffer_pal.h b/src/aclk/helpers/ringbuffer_pal.h deleted file mode 100644 index 2f7e1cb93..000000000 --- a/src/aclk/helpers/ringbuffer_pal.h +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef RINGBUFFER_PAL_H -#define RINGBUFFER_PAL_H - -#include "libnetdata/libnetdata.h" - -#define crbuf_malloc(...) mallocz(__VA_ARGS__) -#define crbuf_free(...) freez(__VA_ARGS__) - -#endif /* RINGBUFFER_PAL_H */ diff --git a/src/aclk/https_client.c b/src/aclk/https_client.c index 4a0362992..f144eaf15 100644 --- a/src/aclk/https_client.c +++ b/src/aclk/https_client.c @@ -105,7 +105,8 @@ static int parse_http_hdr(rbuf_t buf, http_parse_ctx *parse_ctx) int idx, idx_end; char buf_key[HTTP_HDR_BUFFER_SIZE]; char buf_val[HTTP_HDR_BUFFER_SIZE]; - char *ptr = buf_key; + char *ptr; + if (!rbuf_find_bytes(buf, HTTP_LINE_TERM, strlen(HTTP_LINE_TERM), &idx_end)) { netdata_log_error("CRLF expected"); return 1; @@ -555,7 +556,7 @@ static int handle_http_request(https_req_ctx_t *ctx) { // we remove those but during encoding we need that space in the buffer creds_base64_len += (1+(creds_base64_len/64)) * strlen("\n"); char *creds_base64 = callocz(1, creds_base64_len + 1); - base64_encode_helper((unsigned char*)creds_base64, &creds_base64_len, (unsigned char*)creds_plain, creds_plain_len); + (void) netdata_base64_encode((unsigned char *)creds_base64, (unsigned char *)creds_plain, creds_plain_len); buffer_sprintf(hdr, "Proxy-Authorization: Basic %s\x0D\x0A", creds_base64); freez(creds_plain); } @@ -583,7 +584,6 @@ static int handle_http_request(https_req_ctx_t *ctx) { if (ctx->parse_ctx.chunked_response) freez(ctx->parse_ctx.chunked_response); rc = 4; - goto err_exit; } err_exit: diff --git a/src/aclk/https_client.h b/src/aclk/https_client.h index cf14ffd87..b1445a5b7 100644 --- a/src/aclk/https_client.h +++ b/src/aclk/https_client.h @@ -5,9 +5,6 @@ #include "libnetdata/libnetdata.h" -#include "mqtt_websockets/c-rbuf/cringbuffer.h" -#include "mqtt_websockets/c_rhash/c_rhash.h" - typedef enum http_req_type { HTTP_REQ_GET = 0, HTTP_REQ_POST, diff --git a/src/aclk/mqtt_websockets/.github/workflows/run-tests.yaml b/src/aclk/mqtt_websockets/.github/workflows/run-tests.yaml deleted file mode 100644 index da5dde821..000000000 --- a/src/aclk/mqtt_websockets/.github/workflows/run-tests.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: run-tests -on: - push: - schedule: - - cron: '5 3 * * 0' - pull_request: -jobs: - run-tests: - runs-on: ubuntu-latest - steps: - - name: Install ruby and deps - run: sudo apt-get install ruby ruby-dev mosquitto - - name: Checkout - uses: actions/checkout@v2 diff --git a/src/aclk/mqtt_websockets/.gitignore b/src/aclk/mqtt_websockets/.gitignore deleted file mode 100644 index 9f1a0d89a..000000000 --- a/src/aclk/mqtt_websockets/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -build/* -!build/.keep -test -.vscode -mqtt/mqtt.c -mqtt/include/mqtt.h -libmqttwebsockets.* -*.o -.dirstamp -.deps diff --git a/src/aclk/mqtt_websockets/README.md b/src/aclk/mqtt_websockets/README.md index b159686df..9507fedb5 100644 --- a/src/aclk/mqtt_websockets/README.md +++ b/src/aclk/mqtt_websockets/README.md @@ -4,4 +4,4 @@ Library to connect MQTT client over Websockets Secure (WSS). ## License -The Project is released under GPL v3 license. See [License](LICENSE) +The Project is released under GPL v3 license. See [License](/LICENSE) diff --git a/src/aclk/mqtt_websockets/c-rbuf/cringbuffer.c b/src/aclk/mqtt_websockets/c-rbuf/cringbuffer.c deleted file mode 100644 index 8950c6906..000000000 --- a/src/aclk/mqtt_websockets/c-rbuf/cringbuffer.c +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only - -#include "cringbuffer.h" -#include "cringbuffer_internal.h" - -#include <stdlib.h> -#include <assert.h> -#include <string.h> - -#define MIN(a,b) (((a)<(b))?(a):(b)) -#define MAX(a,b) (((a)>(b))?(a):(b)) - -// this allows user to use their own -// custom memory allocation functions -#ifdef RBUF_CUSTOM_MALLOC -#include "../../helpers/ringbuffer_pal.h" -#else -#define crbuf_malloc(...) malloc(__VA_ARGS__) -#define crbuf_free(...) free(__VA_ARGS__) -#endif - -rbuf_t rbuf_create(size_t size) -{ - rbuf_t buffer = crbuf_malloc(sizeof(struct rbuf_t) + size); - if (!buffer) - return NULL; - - memset(buffer, 0, sizeof(struct rbuf_t)); - - buffer->data = ((char*)buffer) + sizeof(struct rbuf_t); - - buffer->head = buffer->data; - buffer->tail = buffer->data; - buffer->size = size; - buffer->end = buffer->data + size; - - return buffer; -} - -void rbuf_free(rbuf_t buffer) -{ - crbuf_free(buffer); -} - -void rbuf_flush(rbuf_t buffer) -{ - buffer->head = buffer->data; - buffer->tail = buffer->data; - buffer->size_data = 0; -} - -char *rbuf_get_linear_insert_range(rbuf_t buffer, size_t *bytes) -{ - *bytes = 0; - if (buffer->head == buffer->tail && buffer->size_data) - return NULL; - - *bytes = ((buffer->head >= buffer->tail) ? buffer->end : buffer->tail) - buffer->head; - return buffer->head; -} - -char *rbuf_get_linear_read_range(rbuf_t buffer, size_t *bytes) -{ - *bytes = 0; - if(buffer->head == buffer->tail && !buffer->size_data) - return NULL; - - *bytes = ((buffer->tail >= buffer->head) ? buffer->end : buffer->head) - buffer->tail; - - return buffer->tail; -} - -int rbuf_bump_head(rbuf_t buffer, size_t bytes) -{ - size_t free_bytes = rbuf_bytes_free(buffer); - if (bytes > free_bytes) - return 0; - int i = buffer->head - buffer->data; - buffer->head = &buffer->data[(i + bytes) % buffer->size]; - buffer->size_data += bytes; - return 1; -} - -int rbuf_bump_tail(rbuf_t buffer, size_t bytes) -{ - if(!rbuf_bump_tail_noopt(buffer, bytes)) - return 0; - - // if tail catched up with head - // start writing buffer from beggining - // this is not necessary (rbuf must work well without it) - // but helps to optimize big writes as rbuf_get_linear_insert_range - // will return bigger continuous region - if(buffer->tail == buffer->head) { - assert(buffer->size_data == 0); - rbuf_flush(buffer); - } - - return 1; -} - -size_t rbuf_get_capacity(rbuf_t buffer) -{ - return buffer->size; -} - -size_t rbuf_bytes_available(rbuf_t buffer) -{ - return buffer->size_data; -} - -size_t rbuf_bytes_free(rbuf_t buffer) -{ - return buffer->size - buffer->size_data; -} - -size_t rbuf_push(rbuf_t buffer, const char *data, size_t len) -{ - size_t to_cpy; - char *w_ptr = rbuf_get_linear_insert_range(buffer, &to_cpy); - if(!to_cpy) - return to_cpy; - - to_cpy = MIN(to_cpy, len); - memcpy(w_ptr, data, to_cpy); - rbuf_bump_head(buffer, to_cpy); - if(to_cpy < len) - to_cpy += rbuf_push(buffer, &data[to_cpy], len - to_cpy); - return to_cpy; -} - -size_t rbuf_pop(rbuf_t buffer, char *data, size_t len) -{ - size_t to_cpy; - const char *r_ptr = rbuf_get_linear_read_range(buffer, &to_cpy); - if(!to_cpy) - return to_cpy; - - to_cpy = MIN(to_cpy, len); - memcpy(data, r_ptr, to_cpy); - rbuf_bump_tail(buffer, to_cpy); - if(to_cpy < len) - to_cpy += rbuf_pop(buffer, &data[to_cpy], len - to_cpy); - return to_cpy; -} - -static inline void rbuf_ptr_inc(rbuf_t buffer, const char **ptr) -{ - (*ptr)++; - if(*ptr >= buffer->end) - *ptr = buffer->data; -} - -int rbuf_memcmp(rbuf_t buffer, const char *haystack, const char *needle, size_t needle_bytes) -{ - const char *end = needle + needle_bytes; - - // as head==tail can mean 2 things here - if (haystack == buffer->head && buffer->size_data) { - if (*haystack != *needle) - return (*haystack - *needle); - rbuf_ptr_inc(buffer, &haystack); - needle++; - } - - while (haystack != buffer->head && needle != end) { - if (*haystack != *needle) - return (*haystack - *needle); - rbuf_ptr_inc(buffer, &haystack); - needle++; - } - return 0; -} - -int rbuf_memcmp_n(rbuf_t buffer, const char *to_cmp, size_t to_cmp_bytes) -{ - return rbuf_memcmp(buffer, buffer->tail, to_cmp, to_cmp_bytes); -} - -char *rbuf_find_bytes(rbuf_t buffer, const char *needle, size_t needle_bytes, int *found_idx) -{ - const char *ptr = buffer->tail; - *found_idx = 0; - - if (!rbuf_bytes_available(buffer)) - return NULL; - - if (buffer->head == buffer->tail && buffer->size_data) { - if(!rbuf_memcmp(buffer, ptr, needle, needle_bytes)) - return (char *)ptr; - rbuf_ptr_inc(buffer, &ptr); - (*found_idx)++; - } - - while (ptr != buffer->head) - { - if(!rbuf_memcmp(buffer, ptr, needle, needle_bytes)) - return (char *)ptr; - rbuf_ptr_inc(buffer, &ptr); - (*found_idx)++; - } - return NULL; -} diff --git a/src/aclk/mqtt_websockets/c-rbuf/cringbuffer.h b/src/aclk/mqtt_websockets/c-rbuf/cringbuffer.h deleted file mode 100644 index eb98035a9..000000000 --- a/src/aclk/mqtt_websockets/c-rbuf/cringbuffer.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only - -#ifndef CRINGBUFFER_H -#define CRINGBUFFER_H - -#include <stddef.h> - -typedef struct rbuf_t *rbuf_t; - -rbuf_t rbuf_create(size_t size); -void rbuf_free(rbuf_t buffer); -void rbuf_flush(rbuf_t buffer); - -/* /param bytes how much bytes can be copied into pointer returned - * /return pointer where data can be copied to or NULL if buffer full - */ -char *rbuf_get_linear_insert_range(rbuf_t buffer, size_t *bytes); -char *rbuf_get_linear_read_range(rbuf_t buffer, size_t *bytes); - -int rbuf_bump_head(rbuf_t buffer, size_t bytes); -int rbuf_bump_tail(rbuf_t buffer, size_t bytes); - -/* @param buffer related buffer instance - * @returns total capacity of buffer in bytes (not free/used) - */ -size_t rbuf_get_capacity(rbuf_t buffer); - -/* @param buffer related buffer instance - * @returns count of bytes stored in the buffer - */ -size_t rbuf_bytes_available(rbuf_t buffer); - -/* @param buffer related buffer instance - * @returns count of bytes available/free in the buffer (how many more bytes you can store in this buffer) - */ -size_t rbuf_bytes_free(rbuf_t buffer); - -/* writes as many bytes from `data` into the `buffer` as possible - * but maximum `len` bytes - */ -size_t rbuf_push(rbuf_t buffer, const char *data, size_t len); -size_t rbuf_pop(rbuf_t buffer, char *data, size_t len); - -char *rbuf_find_bytes(rbuf_t buffer, const char *needle, size_t needle_bytes, int *found_idx); -int rbuf_memcmp_n(rbuf_t buffer, const char *to_cmp, size_t to_cmp_bytes); - -#endif diff --git a/src/aclk/mqtt_websockets/c-rbuf/cringbuffer_internal.h b/src/aclk/mqtt_websockets/c-rbuf/cringbuffer_internal.h deleted file mode 100644 index d32de187c..000000000 --- a/src/aclk/mqtt_websockets/c-rbuf/cringbuffer_internal.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only - -#ifndef CRINGBUFFER_INTERNAL_H -#define CRINGBUFFER_INTERNAL_H - -struct rbuf_t { - char *data; - - // points to next byte where we can write - char *head; - // points to oldest (next to be poped) readable byte - char *tail; - - // to avoid calculating data + size - // all the time - char *end; - - size_t size; - size_t size_data; -}; - -/* this exists so that it can be tested by unit tests - * without optimization that resets head and tail to - * beginning if buffer empty - */ -inline static int rbuf_bump_tail_noopt(rbuf_t buffer, size_t bytes) -{ - if (bytes > buffer->size_data) - return 0; - int i = buffer->tail - buffer->data; - buffer->tail = &buffer->data[(i + bytes) % buffer->size]; - buffer->size_data -= bytes; - - return 1; -} - -#endif diff --git a/src/aclk/mqtt_websockets/c-rbuf/ringbuffer_test.c b/src/aclk/mqtt_websockets/c-rbuf/ringbuffer_test.c deleted file mode 100644 index 6a17c9956..000000000 --- a/src/aclk/mqtt_websockets/c-rbuf/ringbuffer_test.c +++ /dev/null @@ -1,485 +0,0 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only - -#include "ringbuffer.h" - -// to be able to access internals -// never do this from app -#include "../src/ringbuffer_internal.h" - -#include <stdio.h> -#include <string.h> - -#define KNRM "\x1B[0m" -#define KRED "\x1B[31m" -#define KGRN "\x1B[32m" -#define KYEL "\x1B[33m" -#define KBLU "\x1B[34m" -#define KMAG "\x1B[35m" -#define KCYN "\x1B[36m" -#define KWHT "\x1B[37m" - -#define UNUSED(x) (void)(x) - -int total_fails = 0; -int total_tests = 0; -int total_checks = 0; - -#define CHECK_EQ_RESULT(x, y) \ - while (s_len--) \ - putchar('.'); \ - printf("%s%s " KNRM "\n", (((x) == (y)) ? KGRN : KRED), (((x) == (y)) ? " PASS " : " FAIL ")); \ - if ((x) != (y)) \ - total_fails++; \ - total_checks++; - -#define CHECK_EQ_PREFIX(x, y, prefix, subtest_name, ...) \ - { \ - int s_len = \ - 100 - \ - printf(("Checking: " KWHT "%s %s%2d " subtest_name " " KNRM), __func__, prefix, subtest_no, ##__VA_ARGS__); \ - CHECK_EQ_RESULT(x, y) \ - } - -#define CHECK_EQ(x, y, subtest_name, ...) \ - { \ - int s_len = \ - 100 - printf(("Checking: " KWHT "%s %2d " subtest_name " " KNRM), __func__, subtest_no, ##__VA_ARGS__); \ - CHECK_EQ_RESULT(x, y) \ - } - -#define TEST_DECL() \ - int subtest_no = 0; \ - printf(KYEL "TEST SUITE: %s\n" KNRM, __func__); \ - total_tests++; - -static void test_rbuf_get_linear_insert_range() -{ - TEST_DECL(); - - // check empty buffer behaviour - rbuf_t buff = rbuf_create(5); - char *to_write; - size_t ret; - to_write = rbuf_get_linear_insert_range(buff, &ret); - CHECK_EQ(ret, 5, "empty size"); - CHECK_EQ(to_write, buff->head, "empty write ptr"); - rbuf_free(buff); - - // check full buffer behaviour - subtest_no++; - buff = rbuf_create(5); - ret = rbuf_bump_head(buff, 5); - CHECK_EQ(ret, 1, "ret"); - to_write = rbuf_get_linear_insert_range(buff, &ret); - CHECK_EQ(to_write, NULL, "writable NULL"); - CHECK_EQ(ret, 0, "writable count = 0"); - - // check buffer flush - subtest_no++; - rbuf_flush(buff); - CHECK_EQ(rbuf_bytes_free(buff), 5, "size_free"); - CHECK_EQ(rbuf_bytes_available(buff), 0, "size_avail"); - CHECK_EQ(buff->head, buff->data, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - // check behaviour head > tail - subtest_no++; - rbuf_flush(buff); - rbuf_bump_head(buff, 3); - to_write = rbuf_get_linear_insert_range(buff, &ret); - CHECK_EQ(to_write, buff->head, "write location"); - CHECK_EQ(ret, 2, "availible to linear write"); - - // check behaviour tail > head - subtest_no++; - rbuf_flush(buff); - rbuf_bump_head(buff, 5); - rbuf_bump_tail(buff, 3); - CHECK_EQ(buff->head, buff->data, "head_ptr"); - CHECK_EQ(buff->tail, buff->data + 3, "tail_ptr"); - to_write = rbuf_get_linear_insert_range(buff, &ret); - CHECK_EQ(to_write, buff->head, "write location"); - CHECK_EQ(ret, 3, "availible to linear write"); - -/* // check behaviour tail and head at last element - subtest_no++; - rbuf_flush(buff); - rbuf_bump_head(buff, 4); - rbuf_bump_tail(buff, 4); - CHECK_EQ(buff->head, buff->end - 1, "head_ptr"); - CHECK_EQ(buff->tail, buff->end - 1, "tail_ptr"); - to_write = rbuf_get_linear_insert_range(buff, &ret); - CHECK_EQ(to_write, buff->head, "write location"); - CHECK_EQ(ret, 1, "availible to linear write");*/ - - // check behaviour tail and head at last element - // after rbuf_bump_tail optimisation that restarts buffer - // in case tail catches up with head - subtest_no++; - rbuf_flush(buff); - rbuf_bump_head(buff, 4); - rbuf_bump_tail(buff, 4); - CHECK_EQ(buff->head, buff->data, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - to_write = rbuf_get_linear_insert_range(buff, &ret); - CHECK_EQ(to_write, buff->head, "write location"); - CHECK_EQ(ret, 5, "availible to linear write"); -} - -#define _CHECK_EQ(x, y, subtest_name, ...) CHECK_EQ_PREFIX(x, y, prefix, subtest_name, ##__VA_ARGS__) -#define _PREFX "(size = %5zu) " -static void test_rbuf_bump_head_bsize(size_t size) -{ - char prefix[16]; - snprintf(prefix, 16, _PREFX, size); - int subtest_no = 0; - rbuf_t buff = rbuf_create(size); - _CHECK_EQ(rbuf_bytes_free(buff), size, "size_free"); - - subtest_no++; - int ret = rbuf_bump_head(buff, size); - _CHECK_EQ(buff->data, buff->head, "loc"); - _CHECK_EQ(ret, 1, "ret"); - _CHECK_EQ(buff->size_data, buff->size, "size"); - _CHECK_EQ(rbuf_bytes_free(buff), 0, "size_free"); - - subtest_no++; - ret = rbuf_bump_head(buff, 1); - _CHECK_EQ(buff->data, buff->head, "loc no move"); - _CHECK_EQ(ret, 0, "ret error"); - _CHECK_EQ(buff->size_data, buff->size, "size"); - _CHECK_EQ(rbuf_bytes_free(buff), 0, "size_free"); - rbuf_free(buff); - - subtest_no++; - buff = rbuf_create(size); - ret = rbuf_bump_head(buff, size - 1); - _CHECK_EQ(buff->head, buff->end-1, "loc end"); - rbuf_free(buff); -} -#undef _CHECK_EQ - -static void test_rbuf_bump_head() -{ - TEST_DECL(); - UNUSED(subtest_no); - - size_t test_sizes[] = { 1, 2, 3, 5, 6, 7, 8, 100, 99999, 0 }; - for (int i = 0; test_sizes[i]; i++) - test_rbuf_bump_head_bsize(test_sizes[i]); -} - -static void test_rbuf_bump_tail_noopt(int subtest_no) -{ - rbuf_t buff = rbuf_create(10); - CHECK_EQ(rbuf_bytes_free(buff), 10, "size_free"); - CHECK_EQ(rbuf_bytes_available(buff), 0, "size_avail"); - - subtest_no++; - int ret = rbuf_bump_head(buff, 5); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_free(buff), 5, "size_free"); - CHECK_EQ(rbuf_bytes_available(buff), 5, "size_avail"); - CHECK_EQ(buff->head, buff->data + 5, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - subtest_no++; - ret = rbuf_bump_tail_noopt(buff, 2); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 3, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 7, "size_free"); - CHECK_EQ(buff->head, buff->data + 5, "head_ptr"); - CHECK_EQ(buff->tail, buff->data + 2, "tail_ptr"); - - subtest_no++; - ret = rbuf_bump_tail_noopt(buff, 3); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 0, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 10, "size_free"); - CHECK_EQ(buff->head, buff->data + 5, "head_ptr"); - CHECK_EQ(buff->tail, buff->data + 5, "tail_ptr"); - - subtest_no++; - ret = rbuf_bump_tail_noopt(buff, 1); - CHECK_EQ(ret, 0, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 0, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 10, "size_free"); - CHECK_EQ(buff->head, buff->data + 5, "head_ptr"); - CHECK_EQ(buff->tail, buff->data + 5, "tail_ptr"); - - subtest_no++; - ret = rbuf_bump_head(buff, 7); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 7, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 3, "size_free"); - CHECK_EQ(buff->head, buff->data + 2, "head_ptr"); - CHECK_EQ(buff->tail, buff->data + 5, "tail_ptr"); - - subtest_no++; - ret = rbuf_bump_tail_noopt(buff, 5); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 2, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 8, "size_free"); - CHECK_EQ(buff->head, buff->data + 2, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - // check tail can't overrun head - subtest_no++; - ret = rbuf_bump_tail_noopt(buff, 3); - CHECK_EQ(ret, 0, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 2, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 8, "size_free"); - CHECK_EQ(buff->head, buff->data + 2, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - // check head can't overrun tail - subtest_no++; - ret = rbuf_bump_head(buff, 9); - CHECK_EQ(ret, 0, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 2, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 8, "size_free"); - CHECK_EQ(buff->head, buff->data + 2, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - // check head can fill the buffer - subtest_no++; - ret = rbuf_bump_head(buff, 8); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 10, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 0, "size_free"); - CHECK_EQ(buff->head, buff->data, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - // check can empty the buffer - subtest_no++; - ret = rbuf_bump_tail_noopt(buff, 10); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 0, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 10, "size_free"); - CHECK_EQ(buff->head, buff->data, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); -} - -static void test_rbuf_bump_tail_opt(int subtest_no) -{ - subtest_no++; - rbuf_t buff = rbuf_create(10); - CHECK_EQ(rbuf_bytes_free(buff), 10, "size_free"); - CHECK_EQ(rbuf_bytes_available(buff), 0, "size_avail"); - - subtest_no++; - int ret = rbuf_bump_head(buff, 5); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_free(buff), 5, "size_free"); - CHECK_EQ(rbuf_bytes_available(buff), 5, "size_avail"); - CHECK_EQ(buff->head, buff->data + 5, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - subtest_no++; - ret = rbuf_bump_tail(buff, 2); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 3, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 7, "size_free"); - CHECK_EQ(buff->head, buff->data + 5, "head_ptr"); - CHECK_EQ(buff->tail, buff->data + 2, "tail_ptr"); - - subtest_no++; - ret = rbuf_bump_tail(buff, 3); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 0, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 10, "size_free"); - CHECK_EQ(buff->head, buff->data, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - subtest_no++; - ret = rbuf_bump_tail_noopt(buff, 1); - CHECK_EQ(ret, 0, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 0, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 10, "size_free"); - CHECK_EQ(buff->head, buff->data, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - subtest_no++; - ret = rbuf_bump_head(buff, 6); - ret = rbuf_bump_tail(buff, 5); - ret = rbuf_bump_head(buff, 6); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 7, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 3, "size_free"); - CHECK_EQ(buff->head, buff->data + 2, "head_ptr"); - CHECK_EQ(buff->tail, buff->data + 5, "tail_ptr"); - - subtest_no++; - ret = rbuf_bump_tail(buff, 5); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 2, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 8, "size_free"); - CHECK_EQ(buff->head, buff->data + 2, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - // check tail can't overrun head - subtest_no++; - ret = rbuf_bump_tail(buff, 3); - CHECK_EQ(ret, 0, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 2, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 8, "size_free"); - CHECK_EQ(buff->head, buff->data + 2, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - // check head can't overrun tail - subtest_no++; - ret = rbuf_bump_head(buff, 9); - CHECK_EQ(ret, 0, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 2, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 8, "size_free"); - CHECK_EQ(buff->head, buff->data + 2, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - // check head can fill the buffer - subtest_no++; - ret = rbuf_bump_head(buff, 8); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 10, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 0, "size_free"); - CHECK_EQ(buff->head, buff->data, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - - // check can empty the buffer - subtest_no++; - ret = rbuf_bump_tail(buff, 10); - CHECK_EQ(ret, 1, "ret"); - CHECK_EQ(rbuf_bytes_available(buff), 0, "size_avail"); - CHECK_EQ(rbuf_bytes_free(buff), 10, "size_free"); - CHECK_EQ(buff->head, buff->data, "head_ptr"); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); -} - -static void test_rbuf_bump_tail() -{ - TEST_DECL(); - test_rbuf_bump_tail_noopt(subtest_no); - test_rbuf_bump_tail_opt(subtest_no); -} - -#define ASCII_A 0x61 -#define ASCII_Z 0x7A -#define TEST_DATA_SIZE ASCII_Z-ASCII_A+1 -static void test_rbuf_push() -{ - TEST_DECL(); - rbuf_t buff = rbuf_create(10); - int i; - char test_data[TEST_DATA_SIZE]; - - for (int i = 0; i <= TEST_DATA_SIZE; i++) - test_data[i] = i + ASCII_A; - - int ret = rbuf_push(buff, test_data, 10); - CHECK_EQ(ret, 10, "written 10 bytes"); - CHECK_EQ(rbuf_bytes_free(buff), 0, "empty size == 0"); - for (i = 0; i < 10; i++) - CHECK_EQ(buff->data[i], i + ASCII_A, "Check data"); - - subtest_no++; - rbuf_flush(buff); - rbuf_bump_head(buff, 5); - rbuf_bump_tail_noopt(buff, 5); //to not reset both pointers to beginning - ret = rbuf_push(buff, test_data, 10); - CHECK_EQ(ret, 10, "written 10 bytes"); - for (i = 0; i < 10; i++) - CHECK_EQ(buff->data[i], ((i+5)%10) + ASCII_A, "Check Data"); - - subtest_no++; - rbuf_flush(buff); - rbuf_bump_head(buff, 9); - rbuf_bump_tail_noopt(buff, 9); - ret = rbuf_push(buff, test_data, 10); - CHECK_EQ(ret, 10, "written 10 bytes"); - for (i = 0; i < 10; i++) - CHECK_EQ(buff->data[i], ((i + 1) % 10) + ASCII_A, "Check data"); - - // let tail > head - subtest_no++; - rbuf_flush(buff); - rbuf_bump_head(buff, 9); - rbuf_bump_tail_noopt(buff, 9); - rbuf_bump_head(buff, 1); - ret = rbuf_push(buff, test_data, 9); - CHECK_EQ(ret, 9, "written 9 bytes"); - CHECK_EQ(buff->head, buff->end - 1, "head_ptr"); - CHECK_EQ(buff->tail, buff->head, "tail_ptr"); - rbuf_bump_tail(buff, 1); - //TODO push byte can be usefull optimisation - ret = rbuf_push(buff, &test_data[9], 1); - CHECK_EQ(ret, 1, "written 1 byte"); - CHECK_EQ(rbuf_bytes_free(buff), 0, "empty size == 0"); - for (i = 0; i < 10; i++) - CHECK_EQ(buff->data[i], i + ASCII_A, "Check data"); - - subtest_no++; - rbuf_flush(buff); - rbuf_bump_head(buff, 9); - rbuf_bump_tail_noopt(buff, 7); - rbuf_bump_head(buff, 1); - ret = rbuf_push(buff, test_data, 7); - CHECK_EQ(ret, 7, "written 7 bytes"); - CHECK_EQ(buff->head, buff->data + 7, "head_ptr"); - CHECK_EQ(buff->tail, buff->head, "tail_ptr"); - rbuf_bump_tail(buff, 3); - CHECK_EQ(buff->tail, buff->data, "tail_ptr"); - //TODO push byte can be usefull optimisation - ret = rbuf_push(buff, &test_data[7], 3); - CHECK_EQ(ret, 3, "written 3 bytes"); - CHECK_EQ(rbuf_bytes_free(buff), 0, "empty size == 0"); - for (i = 0; i < 10; i++) - CHECK_EQ(buff->data[i], i + ASCII_A, "Check data"); - - // test can't overfill the buffer - subtest_no++; - rbuf_flush(buff); - rbuf_push(buff, test_data, TEST_DATA_SIZE); - CHECK_EQ(ret, 3, "written 10 bytes"); - for (i = 0; i < 10; i++) - CHECK_EQ(buff->data[i], i + ASCII_A, "Check data"); -} - -#define TEST_RBUF_FIND_BYTES_SIZE 10 -void test_rbuf_find_bytes() -{ - TEST_DECL(); - rbuf_t buff = rbuf_create(TEST_RBUF_FIND_BYTES_SIZE); - char *filler_3 = " "; - char *needle = "needle"; - int idx; - char *ptr; - - // make sure needle is wrapped aroung in the buffer - // to test we still can find it - // target "edle ne" - rbuf_bump_head(buff, TEST_RBUF_FIND_BYTES_SIZE / 2); - rbuf_push(buff, filler_3, strlen(filler_3)); - rbuf_bump_tail(buff, TEST_RBUF_FIND_BYTES_SIZE / 2); - rbuf_push(buff, needle, strlen(needle)); - ptr = rbuf_find_bytes(buff, needle, strlen(needle), &idx); - CHECK_EQ(ptr, buff->data + (TEST_RBUF_FIND_BYTES_SIZE / 2) + strlen(filler_3), "Pointer to needle correct"); - CHECK_EQ(idx, ptr - buff->tail, "Check needle index"); -} - -int main() -{ - test_rbuf_bump_head(); - test_rbuf_bump_tail(); - test_rbuf_get_linear_insert_range(); - test_rbuf_push(); - test_rbuf_find_bytes(); - - printf( - KNRM "Total Tests %d, Total Checks %d, Successful Checks %d, Failed Checks %d\n", - total_tests, total_checks, total_checks - total_fails, total_fails); - if (total_fails) - printf(KRED "!!!Some test(s) Failed!!!\n"); - else - printf(KGRN "ALL TESTS PASSED\n"); - - return total_fails; -} diff --git a/src/aclk/mqtt_websockets/c_rhash/c_rhash.c b/src/aclk/mqtt_websockets/c_rhash/c_rhash.c deleted file mode 100644 index a71b500e2..000000000 --- a/src/aclk/mqtt_websockets/c_rhash/c_rhash.c +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only - -#include "c_rhash_internal.h" - -#include <stdlib.h> -#include <string.h> - -#ifdef DEBUG_VERBOSE -#include <stdio.h> -#endif - -#define c_rmalloc(...) malloc(__VA_ARGS__) -#define c_rcalloc(...) calloc(__VA_ARGS__) -#define c_rfree(...) free(__VA_ARGS__) - -static inline uint32_t simple_hash(const char *name) { - unsigned char *s = (unsigned char *) name; - uint32_t hval = 0x811c9dc5; - while (*s) { - hval *= 16777619; - hval ^= (uint32_t) *s++; - } - return hval; -} - -c_rhash c_rhash_new(size_t bin_count) { - if (!bin_count) - bin_count = 1000; - - c_rhash hash = c_rcalloc(1, sizeof(struct c_rhash_s) + (bin_count * sizeof(struct bin_ll*)) ); - if (hash == NULL) - return NULL; - - hash->bin_count = bin_count; - hash->bins = (c_rhash_bin *)((char*)hash + sizeof(struct c_rhash_s)); - - return hash; -} - -static size_t get_itemtype_len(uint8_t item_type, const void* item_data) { - switch (item_type) { - case ITEMTYPE_STRING: - return strlen(item_data) + 1; - case ITEMTYPE_UINT64: - return sizeof(uint64_t); - case ITEMTYPE_UINT8: - return 1; - case ITEMTYPE_OPAQUE_PTR: - return sizeof(void*); - default: - return 0; - } -} - -static int compare_bin_item(struct bin_item *item, uint8_t key_type, const void *key) { - if (item->key_type != key_type) - return 1; - - size_t key_value_len = get_itemtype_len(key_type, key); - - if(key_type == ITEMTYPE_STRING) { - size_t new_key_value_len = get_itemtype_len(item->key_type, item->key); - if (new_key_value_len != key_value_len) - return 1; - } - - if(memcmp(item->key, key, key_value_len) == 0) { - return 0; - } - - return 1; -} - -static int insert_into_bin(c_rhash_bin *bin, uint8_t key_type, const void *key, uint8_t value_type, const void *value) { - struct bin_item *prev = NULL; - while (*bin != NULL) { - if (!compare_bin_item(*bin, key_type, key)) { -#ifdef DEBUG_VERBOSE - printf("Key already present! Updating value!\n"); -#endif -// TODO: optimize here if the new value is of different kind compared to the old one -// in case it is not crazily bigger we can reuse the memory and avoid malloc and free - c_rfree((*bin)->value); - (*bin)->value_type = value_type; - (*bin)->value = c_rmalloc(get_itemtype_len(value_type, value)); - if ((*bin)->value == NULL) - return 1; - memcpy((*bin)->value, value, get_itemtype_len(value_type, value)); - return 0; - } - prev = *bin; - bin = &(*bin)->next; - } - - if (*bin == NULL) - *bin = c_rcalloc(1, sizeof(struct bin_item)); - if (prev != NULL) - prev->next = *bin; - - (*bin)->key_type = key_type; - size_t len = get_itemtype_len(key_type, key); - (*bin)->key = c_rmalloc(len); - memcpy((*bin)->key, key, len); - - (*bin)->value_type = value_type; - len = get_itemtype_len(value_type, value); - (*bin)->value = c_rmalloc(len); - memcpy((*bin)->value, value, len); - return 0; -} - -static inline uint32_t get_bin_idx_str(c_rhash hash, const char *key) { - uint32_t nhash = simple_hash(key); - return nhash % hash->bin_count; -} - -static inline c_rhash_bin *get_binptr_by_str(c_rhash hash, const char *key) { - return &hash->bins[get_bin_idx_str(hash, key)]; -} - -int c_rhash_insert_str_ptr(c_rhash hash, const char *key, void *value) { - c_rhash_bin *bin = get_binptr_by_str(hash, key); - -#ifdef DEBUG_VERBOSE - if (bin != NULL) - printf("COLLISION. There will be more than one item in bin idx=%d\n", nhash); -#endif - - return insert_into_bin(bin, ITEMTYPE_STRING, key, ITEMTYPE_OPAQUE_PTR, &value); -} - -int c_rhash_insert_str_uint8(c_rhash hash, const char *key, uint8_t value) { - c_rhash_bin *bin = get_binptr_by_str(hash, key); - -#ifdef DEBUG_VERBOSE - if (bin != NULL) - printf("COLLISION. There will be more than one item in bin idx=%d\n", nhash); -#endif - - return insert_into_bin(bin, ITEMTYPE_STRING, key, ITEMTYPE_UINT8, &value); -} - -int c_rhash_insert_uint64_ptr(c_rhash hash, uint64_t key, void *value) { - c_rhash_bin *bin = &hash->bins[key % hash->bin_count]; - -#ifdef DEBUG_VERBOSE - if (bin != NULL) - printf("COLLISION. There will be more than one item in bin idx=%d\n", nhash); -#endif - - return insert_into_bin(bin, ITEMTYPE_UINT64, &key, ITEMTYPE_OPAQUE_PTR, &value); -} - -int c_rhash_get_uint8_by_str(c_rhash hash, const char *key, uint8_t *ret_val) { - uint32_t nhash = get_bin_idx_str(hash, key); - - struct bin_item *bin = hash->bins[nhash]; - - while (bin) { - if (bin->key_type == ITEMTYPE_STRING) { - if (!strcmp(bin->key, key)) { - *ret_val = *(uint8_t*)bin->value; - return 0; - } - } - bin = bin->next; - } - return 1; -} - -int c_rhash_get_ptr_by_str(c_rhash hash, const char *key, void **ret_val) { - uint32_t nhash = get_bin_idx_str(hash, key); - - struct bin_item *bin = hash->bins[nhash]; - - while (bin) { - if (bin->key_type == ITEMTYPE_STRING) { - if (!strcmp(bin->key, key)) { - *ret_val = *((void**)bin->value); - return 0; - } - } - bin = bin->next; - } - *ret_val = NULL; - return 1; -} - -int c_rhash_get_ptr_by_uint64(c_rhash hash, uint64_t key, void **ret_val) { - uint32_t nhash = key % hash->bin_count; - - struct bin_item *bin = hash->bins[nhash]; - - while (bin) { - if (bin->key_type == ITEMTYPE_UINT64) { - if (*((uint64_t *)bin->key) == key) { - *ret_val = *((void**)bin->value); - return 0; - } - } - bin = bin->next; - } - *ret_val = NULL; - return 1; -} - -static void c_rhash_destroy_bin(c_rhash_bin bin) { - struct bin_item *next; - do { - next = bin->next; - c_rfree(bin->key); - c_rfree(bin->value); - c_rfree(bin); - bin = next; - } while (bin != NULL); -} - -int c_rhash_iter_uint64_keys(c_rhash hash, c_rhash_iter_t *iter, uint64_t *key) { - while (iter->bin < hash->bin_count) { - if (iter->item != NULL) - iter->item = iter->item->next; - if (iter->item == NULL) { - if (iter->initialized) - iter->bin++; - else - iter->initialized = 1; - if (iter->bin < hash->bin_count) - iter->item = hash->bins[iter->bin]; - } - if (iter->item != NULL && iter->item->key_type == ITEMTYPE_UINT64) { - *key = *(uint64_t*)iter->item->key; - return 0; - } - } - return 1; -} - -int c_rhash_iter_str_keys(c_rhash hash, c_rhash_iter_t *iter, const char **key) { - while (iter->bin < hash->bin_count) { - if (iter->item != NULL) - iter->item = iter->item->next; - if (iter->item == NULL) { - if (iter->initialized) - iter->bin++; - else - iter->initialized = 1; - if (iter->bin < hash->bin_count) - iter->item = hash->bins[iter->bin]; - } - if (iter->item != NULL && iter->item->key_type == ITEMTYPE_STRING) { - *key = (const char*)iter->item->key; - return 0; - } - } - return 1; -} - -void c_rhash_destroy(c_rhash hash) { - for (size_t i = 0; i < hash->bin_count; i++) { - if (hash->bins[i] != NULL) - c_rhash_destroy_bin(hash->bins[i]); - } - c_rfree(hash); -} diff --git a/src/aclk/mqtt_websockets/c_rhash/c_rhash.h b/src/aclk/mqtt_websockets/c_rhash/c_rhash.h deleted file mode 100644 index 37addd161..000000000 --- a/src/aclk/mqtt_websockets/c_rhash/c_rhash.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only - -#include <sys/types.h> -#include <stdint.h> -#include <stddef.h> - -#ifndef DEFAULT_BIN_COUNT - #define DEFAULT_BIN_COUNT 1000 -#endif - -#define ITEMTYPE_UNSET (0x0) -#define ITEMTYPE_STRING (0x1) -#define ITEMTYPE_UINT8 (0x2) -#define ITEMTYPE_UINT64 (0x3) -#define ITEMTYPE_OPAQUE_PTR (0x4) - -typedef struct c_rhash_s *c_rhash; - -c_rhash c_rhash_new(size_t bin_count); - -void c_rhash_destroy(c_rhash hash); - -// # Insert -// ## Insert where key is string -int c_rhash_insert_str_ptr(c_rhash hash, const char *key, void *value); -int c_rhash_insert_str_uint8(c_rhash hash, const char *key, uint8_t value); -// ## Insert where key is uint64 -int c_rhash_insert_uint64_ptr(c_rhash hash, uint64_t key, void *value); - -// # Get -// ## Get where key is string -int c_rhash_get_ptr_by_str(c_rhash hash, const char *key, void **ret_val); -int c_rhash_get_uint8_by_str(c_rhash hash, const char *key, uint8_t *ret_val); -// ## Get where key is uint64 -int c_rhash_get_ptr_by_uint64(c_rhash hash, uint64_t key, void **ret_val); - -typedef struct { - size_t bin; - struct bin_item *item; - int initialized; -} c_rhash_iter_t; - -#define C_RHASH_ITER_T_INITIALIZER { .bin = 0, .item = NULL, .initialized = 0 } - -#define c_rhash_iter_t_initialize(p_iter) memset(p_iter, 0, sizeof(c_rhash_iter_t)) - -/* - * goes trough whole hash map and returns every - * type uint64 key present/stored - * - * it is not necessary to finish iterating and iterator can be reinitialized - * there are no guarantees on the order in which the keys will come - * behavior here is implementation dependent and can change any time - * - * returns: - * 0 for every key and stores the key in *key - * 1 on error or when all keys of this type has been already iterated over - */ -int c_rhash_iter_uint64_keys(c_rhash hash, c_rhash_iter_t *iter, uint64_t *key); - -int c_rhash_iter_str_keys(c_rhash hash, c_rhash_iter_t *iter, const char **key); diff --git a/src/aclk/mqtt_websockets/c_rhash/c_rhash_internal.h b/src/aclk/mqtt_websockets/c_rhash/c_rhash_internal.h deleted file mode 100644 index 20f741076..000000000 --- a/src/aclk/mqtt_websockets/c_rhash/c_rhash_internal.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only - -#include "c_rhash.h" - -struct bin_item { - uint8_t key_type:4; - void *key; - uint8_t value_type:4; - void *value; - - struct bin_item *next; -}; - -typedef struct bin_item *c_rhash_bin; - -struct c_rhash_s { - size_t bin_count; - c_rhash_bin *bins; -}; diff --git a/src/aclk/mqtt_websockets/c_rhash/tests.c b/src/aclk/mqtt_websockets/c_rhash/tests.c deleted file mode 100644 index 909c5562d..000000000 --- a/src/aclk/mqtt_websockets/c_rhash/tests.c +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only - -#include <stdio.h> -#include <string.h> - -#include "c_rhash.h" - -// terminal color codes -#define KNRM "\x1B[0m" -#define KRED "\x1B[31m" -#define KGRN "\x1B[32m" -#define KYEL "\x1B[33m" -#define KBLU "\x1B[34m" -#define KMAG "\x1B[35m" -#define KCYN "\x1B[36m" -#define KWHT "\x1B[37m" - -#define KEY_1 "key1" -#define KEY_2 "keya" - -#define PRINT_ERR(str, ...) fprintf(stderr, "└─╼ ❌ " KRED str KNRM "\n" __VA_OPT__(,) __VA_ARGS__) - -#define ASSERT_RETVAL(fnc, comparator, expected_retval, ...) \ -{ int rval; \ -if(!((rval = fnc(__VA_ARGS__)) comparator expected_retval)) { \ - PRINT_ERR("Failed test. Value returned by \"%s\" in fnc:\"%s\",line:%d is not equal to expected value. Expected:%d, Got:%d", #fnc, __FUNCTION__, __LINE__, expected_retval, rval); \ - rc = 1; \ - goto test_cleanup; \ -} passed_subtest_count++;}; - -#define ASSERT_VAL_UINT8(returned, expected) \ -if(returned != expected) { \ - PRINT_ERR("Failed test. Value returned (%d) doesn't match expected (%d)! fnc:\"%s\",line:%d", returned, expected, __FUNCTION__, __LINE__); \ - rc = 1; \ - goto test_cleanup; \ -} passed_subtest_count++; - -#define ASSERT_VAL_PTR(returned, expected) \ -if((void*)returned != (void*)expected) { \ - PRINT_ERR("Failed test. Value returned(%p) doesn't match expected(%p)! fnc:\"%s\",line:%d", (void*)returned, (void*)expected, __FUNCTION__, __LINE__); \ - rc = 1; \ - goto test_cleanup; \ -} passed_subtest_count++; - -#define ALL_SUBTESTS_PASS() printf("└─╼ ✅" KGRN " Test \"%s\" DONE. All of %zu subtests PASS. (line:%d)\n" KNRM, __FUNCTION__, passed_subtest_count, __LINE__); - -#define TEST_START() size_t passed_subtest_count = 0; int rc = 0; printf("╒═ Starting test \"%s\"\n", __FUNCTION__); - -int test_str_uint8() { - c_rhash hash = c_rhash_new(100); - uint8_t val; - - TEST_START(); - // function should fail on empty hash - ASSERT_RETVAL(c_rhash_get_uint8_by_str, !=, 0, hash, KEY_1, &val); - - ASSERT_RETVAL(c_rhash_insert_str_uint8, ==, 0, hash, KEY_1, 5); - ASSERT_RETVAL(c_rhash_get_uint8_by_str, ==, 0, hash, KEY_1, &val); - ASSERT_VAL_UINT8(5, val); - - ASSERT_RETVAL(c_rhash_insert_str_uint8, ==, 0, hash, KEY_2, 8); - ASSERT_RETVAL(c_rhash_get_uint8_by_str, ==, 0, hash, KEY_1, &val); - ASSERT_VAL_UINT8(5, val); - ASSERT_RETVAL(c_rhash_get_uint8_by_str, ==, 0, hash, KEY_2, &val); - ASSERT_VAL_UINT8(8, val); - ASSERT_RETVAL(c_rhash_get_uint8_by_str, !=, 0, hash, "sndnskjdf", &val); - - // test update of key - ASSERT_RETVAL(c_rhash_insert_str_uint8, ==, 0, hash, KEY_1, 100); - ASSERT_RETVAL(c_rhash_get_uint8_by_str, ==, 0, hash, KEY_1, &val); - ASSERT_VAL_UINT8(100, val); - - ALL_SUBTESTS_PASS(); -test_cleanup: - c_rhash_destroy(hash); - return rc; -} - -int test_uint64_ptr() { - c_rhash hash = c_rhash_new(100); - void *val; - - TEST_START(); - - // function should fail on empty hash - ASSERT_RETVAL(c_rhash_get_ptr_by_uint64, !=, 0, hash, 0, &val); - - ASSERT_RETVAL(c_rhash_insert_uint64_ptr, ==, 0, hash, 0, &hash); - ASSERT_RETVAL(c_rhash_get_ptr_by_uint64, ==, 0, hash, 0, &val); - ASSERT_VAL_PTR(&hash, val); - - ASSERT_RETVAL(c_rhash_insert_uint64_ptr, ==, 0, hash, 1, &val); - ASSERT_RETVAL(c_rhash_get_ptr_by_uint64, ==, 0, hash, 0, &val); - ASSERT_VAL_PTR(&hash, val); - ASSERT_RETVAL(c_rhash_get_ptr_by_uint64, ==, 0, hash, 1, &val); - ASSERT_VAL_PTR(&val, val); - ASSERT_RETVAL(c_rhash_get_ptr_by_uint64, !=, 0, hash, 2, &val); - - ALL_SUBTESTS_PASS(); -test_cleanup: - c_rhash_destroy(hash); - return rc; -} - -#define UINT64_PTR_INC_ITERATION_COUNT 5000 -int test_uint64_ptr_incremental() { - c_rhash hash = c_rhash_new(100); - void *val; - - TEST_START(); - - char a = 0x20; - char *ptr = &a; - while(ptr < &a + UINT64_PTR_INC_ITERATION_COUNT) { - ASSERT_RETVAL(c_rhash_insert_uint64_ptr, ==, 0, hash, (ptr-&a), ptr); - ptr++; - } - - ptr = &a; - char *retptr; - for(int i = 0; i < UINT64_PTR_INC_ITERATION_COUNT; i++) { - ASSERT_RETVAL(c_rhash_get_ptr_by_uint64, ==, 0, hash, i, (void**)&retptr); - ASSERT_VAL_PTR(retptr, (&a+i)); - } - - ALL_SUBTESTS_PASS(); -test_cleanup: - c_rhash_destroy(hash); - return rc; -} - -struct test_string { - const char *str; - int counter; -}; - -struct test_string test_strings[] = { - { .str = "Cillum reprehenderit eiusmod elit nisi aliquip esse exercitation commodo Lorem voluptate esse.", .counter = 0 }, - { .str = "Ullamco eiusmod tempor occaecat ad.", .counter = 0 }, - { .str = "Esse aliquip tempor sint tempor ullamco duis aute incididunt ad.", .counter = 0 }, - { .str = "Cillum Lorem labore cupidatat commodo proident adipisicing.", .counter = 0 }, - { .str = "Quis ad cillum officia exercitation.", .counter = 0 }, - { .str = "Ipsum enim dolor ullamco amet sint nisi ut occaecat sint non.", .counter = 0 }, - { .str = "Id duis officia ipsum cupidatat velit fugiat.", .counter = 0 }, - { .str = "Aliqua non occaecat voluptate reprehenderit reprehenderit veniam minim exercitation ea aliquip enim aliqua deserunt qui.", .counter = 0 }, - { .str = "Ullamco elit tempor laboris reprehenderit quis deserunt duis quis tempor reprehenderit magna dolore reprehenderit exercitation.", .counter = 0 }, - { .str = "Culpa do dolor quis incididunt et labore in ex.", .counter = 0 }, - { .str = "Aliquip velit cupidatat qui incididunt ipsum nostrud eiusmod ut proident nisi magna fugiat excepteur.", .counter = 0 }, - { .str = "Aliqua qui dolore tempor id proident ullamco sunt magna.", .counter = 0 }, - { .str = "Labore eiusmod ut fugiat dolore reprehenderit mollit magna.", .counter = 0 }, - { .str = "Veniam aliquip dolor excepteur minim nulla esse cupidatat esse.", .counter = 0 }, - { .str = "Do quis dolor irure nostrud occaecat aute proident anim.", .counter = 0 }, - { .str = "Enim veniam non nulla ad quis sit amet.", .counter = 0 }, - { .str = "Cillum reprehenderit do enim esse do ullamco consectetur ea.", .counter = 0 }, - { .str = "Sit et duis sint anim qui ad anim labore exercitation sunt cupidatat.", .counter = 0 }, - { .str = "Dolor officia adipisicing sint pariatur in dolor occaecat officia reprehenderit magna.", .counter = 0 }, - { .str = "Aliquip dolore qui occaecat eiusmod sunt incididunt reprehenderit minim et.", .counter = 0 }, - { .str = "Aute fugiat laboris cillum tempor consequat tempor do non laboris culpa officia nisi.", .counter = 0 }, - { .str = "Et excepteur do aliquip fugiat nisi velit tempor officia enim quis elit incididunt.", .counter = 0 }, - { .str = "Eu officia adipisicing incididunt occaecat officia cupidatat enim sit sit officia.", .counter = 0 }, - { .str = "Do amet cillum duis pariatur commodo nulla cillum magna nulla Lorem veniam cupidatat.", .counter = 0 }, - { .str = "Dolor adipisicing voluptate laboris occaecat culpa aliquip ipsum ut consequat aliqua aliquip commodo sunt velit.", .counter = 0 }, - { .str = "Nulla proident ipsum quis nulla.", .counter = 0 }, - { .str = "Laborum adipisicing nulla do aute aliqua est quis sint culpa pariatur laborum voluptate qui.", .counter = 0 }, - { .str = "Proident eiusmod sunt et nulla elit pariatur dolore irure ex voluptate excepteur adipisicing consectetur.", .counter = 0 }, - { .str = "Consequat ex voluptate officia excepteur aute deserunt proident commodo et.", .counter = 0 }, - { .str = "Velit sit cupidatat dolor dolore.", .counter = 0 }, - { .str = "Sunt enim do non anim nostrud exercitation ullamco ex proident commodo.", .counter = 0 }, - { .str = "Id ex officia cillum ad.", .counter = 0 }, - { .str = "Laboris in sunt eiusmod veniam laboris nostrud.", .counter = 0 }, - { .str = "Ex magna occaecat ea ea incididunt aliquip.", .counter = 0 }, - { .str = "Sunt eiusmod ex nostrud eu pariatur sit cupidatat ea adipisicing cillum culpa esse consequat aliquip.", .counter = 0 }, - { .str = "Excepteur commodo qui incididunt enim culpa sunt non excepteur Lorem adipisicing.", .counter = 0 }, - { .str = "Quis officia est ullamco reprehenderit incididunt occaecat pariatur ex reprehenderit nisi.", .counter = 0 }, - { .str = "Culpa irure proident proident et eiusmod irure aliqua ipsum cupidatat minim sit.", .counter = 0 }, - { .str = "Qui cupidatat aliquip est velit magna veniam.", .counter = 0 }, - { .str = "Pariatur ad ad mollit nostrud non irure minim veniam anim aliquip quis eu.", .counter = 0 }, - { .str = "Nisi ex minim eu adipisicing tempor Lorem nisi do ad exercitation est non eu.", .counter = 0 }, - { .str = "Cupidatat do mollit ad commodo cupidatat ut.", .counter = 0 }, - { .str = "Est non excepteur eiusmod nostrud et eu.", .counter = 0 }, - { .str = "Cupidatat mollit nisi magna officia ut elit eiusmod.", .counter = 0 }, - { .str = "Est aliqua consectetur laboris ex consequat est ut dolor.", .counter = 0 }, - { .str = "Duis eu laboris laborum ut id Lorem nostrud qui ad velit proident fugiat minim ullamco.", .counter = 0 }, - { .str = "Pariatur esse excepteur anim amet excepteur irure sint quis esse ex cupidatat ut.", .counter = 0 }, - { .str = "Esse reprehenderit amet qui excepteur aliquip amet.", .counter = 0 }, - { .str = "Ullamco laboris elit labore adipisicing aute nulla qui laborum tempor officia ut dolor aute.", .counter = 0 }, - { .str = "Commodo sunt cillum velit minim laborum Lorem aliqua tempor ad id eu.", .counter = 0 }, - { .str = NULL, .counter = 0 } -}; - -uint32_t test_strings_contain_element(const char *str) { - struct test_string *str_desc = test_strings; - while(str_desc->str) { - if (!strcmp(str, str_desc->str)) - return str_desc - test_strings; - str_desc++; - } - return -1; -} - -#define TEST_INCREMENT_STR_KEYS_HASH_SIZE 20 -int test_increment_str_keys() { - c_rhash hash; - const char *key; - - TEST_START(); - - hash = c_rhash_new(TEST_INCREMENT_STR_KEYS_HASH_SIZE); // less than element count of test_strings - - c_rhash_iter_t iter = C_RHASH_ITER_T_INITIALIZER; - - // check iter on empty hash - ASSERT_RETVAL(c_rhash_iter_str_keys, !=, 0, hash, &iter, &key); - - int32_t element_count = 0; - while (test_strings[element_count].str) { - ASSERT_RETVAL(c_rhash_insert_str_ptr, ==, 0, hash, test_strings[element_count].str, NULL); - test_strings[element_count].counter++; // we want to test we got each key exactly once - element_count++; - } - - if (element_count <= TEST_INCREMENT_STR_KEYS_HASH_SIZE * 2) { - // verify we are actually test also iteration trough single bin (when 2 keys have same hash pointing them to same bin) - PRINT_ERR("For this test to properly test all the hash size needs to be much smaller than all test key count."); - rc = 1; - goto test_cleanup; - } - - // we insert another type of key as iterator should skip it - // in case is another type - ASSERT_RETVAL(c_rhash_insert_uint64_ptr, ==, 0, hash, 5, NULL); - - c_rhash_iter_t_initialize(&iter); - while(!c_rhash_iter_str_keys(hash, &iter, &key)) { - element_count--; - int i; - if ( (i = test_strings_contain_element(key)) < 0) { - PRINT_ERR("Key \"%s\" is not present in test_strings array! (Fnc: %s, Line: %d)", key, __FUNCTION__, __LINE__); - rc = 1; - goto test_cleanup; - } - passed_subtest_count++; - - test_strings[i].counter--; - } - ASSERT_VAL_UINT8(element_count, 0); // we added also same non string keys - - // check each key was present exactly once - struct test_string *str_desc = test_strings; - while (str_desc->str) { - ASSERT_VAL_UINT8(str_desc->counter, 0); - str_desc++; - } - - ALL_SUBTESTS_PASS(); -test_cleanup: - c_rhash_destroy(hash); - return rc; -} - -#define RUN_TEST(fnc) \ -if(fnc()) \ - return 1; - -int main(int argc, char *argv[]) { - RUN_TEST(test_str_uint8); - RUN_TEST(test_uint64_ptr); - RUN_TEST(test_uint64_ptr_incremental); - RUN_TEST(test_increment_str_keys); - // TODO hash with mixed key tests - // TODO iterator test - return 0; -} diff --git a/src/aclk/mqtt_websockets/common_internal.h b/src/aclk/mqtt_websockets/common_internal.h index 2be1c45b8..d79dbb3f3 100644 --- a/src/aclk/mqtt_websockets/common_internal.h +++ b/src/aclk/mqtt_websockets/common_internal.h @@ -1,27 +1,12 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-License-Identifier: GPL-3.0-or-later #ifndef COMMON_INTERNAL_H #define COMMON_INTERNAL_H #include "endian_compat.h" -#ifdef MQTT_WSS_CUSTOM_ALLOC -#include "../helpers/mqtt_wss_pal.h" -#else -#define mw_malloc(...) malloc(__VA_ARGS__) -#define mw_calloc(...) calloc(__VA_ARGS__) -#define mw_free(...) free(__VA_ARGS__) -#define mw_strdup(...) strdup(__VA_ARGS__) -#define mw_realloc(...) realloc(__VA_ARGS__) -#endif - #ifndef MQTT_WSS_FRAG_MEMALIGN #define MQTT_WSS_FRAG_MEMALIGN (8) #endif -#define OPENSSL_VERSION_095 0x00905100L -#define OPENSSL_VERSION_097 0x00907000L -#define OPENSSL_VERSION_110 0x10100000L -#define OPENSSL_VERSION_111 0x10101000L - #endif /* COMMON_INTERNAL_H */ diff --git a/src/aclk/mqtt_websockets/common_public.h b/src/aclk/mqtt_websockets/common_public.h index a855737f9..8f3b4f7d1 100644 --- a/src/aclk/mqtt_websockets/common_public.h +++ b/src/aclk/mqtt_websockets/common_public.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + #ifndef MQTT_WEBSOCKETS_COMMON_PUBLIC_H #define MQTT_WEBSOCKETS_COMMON_PUBLIC_H diff --git a/src/aclk/mqtt_websockets/mqtt_ng.c b/src/aclk/mqtt_websockets/mqtt_ng.c index 8ad6bd5c9..9abe77b5f 100644 --- a/src/aclk/mqtt_websockets/mqtt_ng.c +++ b/src/aclk/mqtt_websockets/mqtt_ng.c @@ -1,35 +1,19 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only +// SPDX-License-Identifier: GPL-3.0-or-later #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include <stdint.h> -#include <stdlib.h> -#include <string.h> -#include <pthread.h> -#include <inttypes.h> - -#include "c_rhash/c_rhash.h" +#include "libnetdata/libnetdata.h" #include "common_internal.h" #include "mqtt_constants.h" -#include "mqtt_wss_log.h" #include "mqtt_ng.h" -#define UNIT_LOG_PREFIX "mqtt_client: " -#define FATAL(fmt, ...) mws_fatal(client->log, UNIT_LOG_PREFIX fmt, ##__VA_ARGS__) -#define ERROR(fmt, ...) mws_error(client->log, UNIT_LOG_PREFIX fmt, ##__VA_ARGS__) -#define WARN(fmt, ...) mws_warn (client->log, UNIT_LOG_PREFIX fmt, ##__VA_ARGS__) -#define INFO(fmt, ...) mws_info (client->log, UNIT_LOG_PREFIX fmt, ##__VA_ARGS__) -#define DEBUG(fmt, ...) mws_debug(client->log, UNIT_LOG_PREFIX fmt, ##__VA_ARGS__) - #define SMALL_STRING_DONT_FRAGMENT_LIMIT 128 -#define MIN(a,b) (((a)<(b))?(a):(b)) - -#define LOCK_HDR_BUFFER(buffer) pthread_mutex_lock(&((buffer)->mutex)) -#define UNLOCK_HDR_BUFFER(buffer) pthread_mutex_unlock(&((buffer)->mutex)) +#define LOCK_HDR_BUFFER(buffer) spinlock_lock(&((buffer)->spinlock)) +#define UNLOCK_HDR_BUFFER(buffer) spinlock_unlock(&((buffer)->spinlock)) #define BUFFER_FRAG_GARBAGE_COLLECT 0x01 // some packets can be marked for garbage collection @@ -75,17 +59,17 @@ struct transaction_buffer { // to be able to revert state easily // in case of error mid processing struct header_buffer state_backup; - pthread_mutex_t mutex; + SPINLOCK spinlock; struct buffer_fragment *sending_frag; }; enum mqtt_client_state { - RAW = 0, - CONNECT_PENDING, - CONNECTING, - CONNECTED, - ERROR, - DISCONNECTED + MQTT_STATE_RAW = 0, + MQTT_STATE_CONNECT_PENDING, + MQTT_STATE_CONNECTING, + MQTT_STATE_CONNECTED, + MQTT_STATE_ERROR, + MQTT_STATE_DISCONNECTED }; enum parser_state { @@ -224,7 +208,7 @@ struct topic_aliases_data { c_rhash stoi_dict; uint32_t idx_max; uint32_t idx_assigned; - pthread_rwlock_t rwlock; + SPINLOCK spinlock; }; struct mqtt_ng_client { @@ -234,8 +218,6 @@ struct mqtt_ng_client { mqtt_msg_data connect_msg; - mqtt_wss_log_ctx_t log; - mqtt_ng_send_fnc_t send_fnc_ptr; void *user_ctx; @@ -253,7 +235,7 @@ struct mqtt_ng_client { unsigned int ping_pending:1; struct mqtt_ng_stats stats; - pthread_mutex_t stats_mutex; + SPINLOCK stats_spinlock; struct topic_aliases_data tx_topic_aliases; c_rhash rx_aliases; @@ -407,7 +389,7 @@ enum memory_mode { CALLER_RESPONSIBLE }; -static inline enum memory_mode ptr2memory_mode(void * ptr) { +static enum memory_mode ptr2memory_mode(void * ptr) { if (ptr == NULL) return MEMCPY; if (ptr == CALLER_RESPONSIBILITY) @@ -492,15 +474,8 @@ static void buffer_rebuild(struct header_buffer *buf) } while(frag); } -static void buffer_garbage_collect(struct header_buffer *buf, mqtt_wss_log_ctx_t log_ctx) +static void buffer_garbage_collect(struct header_buffer *buf) { -#if !defined(MQTT_DEBUG_VERBOSE) && !defined(ADDITIONAL_CHECKS) - (void) log_ctx; -#endif -#ifdef MQTT_DEBUG_VERBOSE - mws_debug(log_ctx, "Buffer Garbage Collection!"); -#endif - struct buffer_fragment *frag = BUFFER_FIRST_FRAG(buf); while (frag) { if (!frag_is_marked_for_gc(frag)) @@ -511,12 +486,8 @@ static void buffer_garbage_collect(struct header_buffer *buf, mqtt_wss_log_ctx_t frag = frag->next; } - if (frag == BUFFER_FIRST_FRAG(buf)) { -#ifdef MQTT_DEBUG_VERBOSE - mws_debug(log_ctx, "Buffer Garbage Collection! No Space Reclaimed!"); -#endif + if (frag == BUFFER_FIRST_FRAG(buf)) return; - } if (!frag) { buf->tail_frag = NULL; @@ -535,21 +506,17 @@ static void buffer_garbage_collect(struct header_buffer *buf, mqtt_wss_log_ctx_t buffer_rebuild(buf); } -static void transaction_buffer_garbage_collect(struct transaction_buffer *buf, mqtt_wss_log_ctx_t log_ctx) +static void transaction_buffer_garbage_collect(struct transaction_buffer *buf) { -#ifdef MQTT_DEBUG_VERBOSE - mws_debug(log_ctx, "Transaction Buffer Garbage Collection! %s", buf->sending_frag == NULL ? "NULL" : "in flight message"); -#endif - // Invalidate the cached sending fragment // as we will move data around if (buf->sending_frag != &ping_frag) buf->sending_frag = NULL; - buffer_garbage_collect(&buf->hdr_buffer, log_ctx); + buffer_garbage_collect(&buf->hdr_buffer); } -static int transaction_buffer_grow(struct transaction_buffer *buf, mqtt_wss_log_ctx_t log_ctx, float rate, size_t max) +static int transaction_buffer_grow(struct transaction_buffer *buf, float rate, size_t max) { if (buf->hdr_buffer.size >= max) return 0; @@ -565,35 +532,30 @@ static int transaction_buffer_grow(struct transaction_buffer *buf, mqtt_wss_log_ void *ret = reallocz(buf->hdr_buffer.data, buf->hdr_buffer.size); if (ret == NULL) { - mws_warn(log_ctx, "Buffer growth failed (realloc)"); + nd_log(NDLS_DAEMON, NDLP_WARNING, "Buffer growth failed (realloc)"); return 1; } - mws_debug(log_ctx, "Message metadata buffer was grown"); + nd_log(NDLS_DAEMON, NDLP_DEBUG, "Message metadata buffer was grown"); buf->hdr_buffer.data = ret; buffer_rebuild(&buf->hdr_buffer); return 0; } -inline static int transaction_buffer_init(struct transaction_buffer *to_init, size_t size) +inline static void transaction_buffer_init(struct transaction_buffer *to_init, size_t size) { - pthread_mutex_init(&to_init->mutex, NULL); + spinlock_init(&to_init->spinlock); to_init->hdr_buffer.size = size; to_init->hdr_buffer.data = mallocz(size); - if (to_init->hdr_buffer.data == NULL) - return 1; - to_init->hdr_buffer.tail = to_init->hdr_buffer.data; to_init->hdr_buffer.tail_frag = NULL; - return 0; } static void transaction_buffer_destroy(struct transaction_buffer *to_init) { buffer_purge(&to_init->hdr_buffer); - pthread_mutex_destroy(&to_init->mutex); freez(to_init->hdr_buffer.data); } @@ -629,54 +591,30 @@ void transaction_buffer_transaction_rollback(struct transaction_buffer *buf, str struct mqtt_ng_client *mqtt_ng_init(struct mqtt_ng_init *settings) { struct mqtt_ng_client *client = callocz(1, sizeof(struct mqtt_ng_client)); - if (client == NULL) - return NULL; - if (transaction_buffer_init(&client->main_buffer, HEADER_BUFFER_SIZE)) - goto err_free_client; + transaction_buffer_init(&client->main_buffer, HEADER_BUFFER_SIZE); client->rx_aliases = RX_ALIASES_INITIALIZE(); - if (client->rx_aliases == NULL) - goto err_free_trx_buf; - if (pthread_mutex_init(&client->stats_mutex, NULL)) - goto err_free_rx_alias; + spinlock_init(&client->stats_spinlock); + spinlock_init(&client->tx_topic_aliases.spinlock); client->tx_topic_aliases.stoi_dict = TX_ALIASES_INITIALIZE(); - if (client->tx_topic_aliases.stoi_dict == NULL) - goto err_free_stats_mutex; client->tx_topic_aliases.idx_max = UINT16_MAX; - if (pthread_rwlock_init(&client->tx_topic_aliases.rwlock, NULL)) - goto err_free_tx_alias; - // TODO just embed the struct into mqtt_ng_client client->parser.received_data = settings->data_in; client->send_fnc_ptr = settings->data_out_fnc; client->user_ctx = settings->user_ctx; - client->log = settings->log; - client->puback_callback = settings->puback_callback; client->connack_callback = settings->connack_callback; client->msg_callback = settings->msg_callback; return client; - -err_free_tx_alias: - c_rhash_destroy(client->tx_topic_aliases.stoi_dict); -err_free_stats_mutex: - pthread_mutex_destroy(&client->stats_mutex); -err_free_rx_alias: - c_rhash_destroy(client->rx_aliases); -err_free_trx_buf: - transaction_buffer_destroy(&client->main_buffer); -err_free_client: - freez(client); - return NULL; } -static inline uint8_t get_control_packet_type(uint8_t first_hdr_byte) +static uint8_t get_control_packet_type(uint8_t first_hdr_byte) { return first_hdr_byte >> 4; } @@ -708,33 +646,27 @@ static void mqtt_ng_destroy_tx_alias_hash(c_rhash hash) void mqtt_ng_destroy(struct mqtt_ng_client *client) { transaction_buffer_destroy(&client->main_buffer); - pthread_mutex_destroy(&client->stats_mutex); mqtt_ng_destroy_tx_alias_hash(client->tx_topic_aliases.stoi_dict); - pthread_rwlock_destroy(&client->tx_topic_aliases.rwlock); mqtt_ng_destroy_rx_alias_hash(client->rx_aliases); freez(client); } -int frag_set_external_data(mqtt_wss_log_ctx_t log, struct buffer_fragment *frag, void *data, size_t data_len, free_fnc_t data_free_fnc) +int frag_set_external_data(struct buffer_fragment *frag, void *data, size_t data_len, free_fnc_t data_free_fnc) { if (frag->len) { // TODO?: This could potentially be done in future if we set rule // external data always follows in buffer data // could help reduce fragmentation in some messages but // currently not worth it considering time is tight - mws_fatal(log, UNIT_LOG_PREFIX "INTERNAL ERROR: Cannot set external data to fragment already containing in buffer data!"); + nd_log(NDLS_DAEMON, NDLP_ERR, "INTERNAL ERROR: Cannot set external data to fragment already containing in buffer data!"); return 1; } switch (ptr2memory_mode(data_free_fnc)) { case MEMCPY: frag->data = mallocz(data_len); - if (frag->data == NULL) { - mws_error(log, UNIT_LOG_PREFIX "OOM while malloc @_optimized_add"); - return 1; - } memcpy(frag->data, data, data_len); break; case EXTERNAL_FREE_AFTER_USE: @@ -816,18 +748,18 @@ static size_t mqtt_ng_connect_size(struct mqtt_auth_properties *auth, #define PACK_2B_INT(buffer, integer, frag) { *(uint16_t *)WRITE_POS(frag) = htobe16((integer)); \ DATA_ADVANCE(buffer, sizeof(uint16_t), frag); } -static int _optimized_add(struct header_buffer *buf, mqtt_wss_log_ctx_t log_ctx, void *data, size_t data_len, free_fnc_t data_free_fnc, struct buffer_fragment **frag) +static int _optimized_add(struct header_buffer *buf, void *data, size_t data_len, free_fnc_t data_free_fnc, struct buffer_fragment **frag) { if (data_len > SMALL_STRING_DONT_FRAGMENT_LIMIT) { buffer_frag_flag_t flags = BUFFER_FRAG_DATA_EXTERNAL; if ((*frag)->flags & BUFFER_FRAG_GARBAGE_COLLECT_ON_SEND) flags |= BUFFER_FRAG_GARBAGE_COLLECT_ON_SEND; if( (*frag = buffer_new_frag(buf, flags)) == NULL ) { - mws_error(log_ctx, "Out of buffer space while generating the message"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Out of buffer space while generating the message"); return 1; } - if (frag_set_external_data(log_ctx, *frag, data, data_len, data_free_fnc)) { - mws_error(log_ctx, "Error adding external data to newly created fragment"); + if (frag_set_external_data(*frag, data, data_len, data_free_fnc)) { + nd_log(NDLS_DAEMON, NDLP_ERR, "Error adding external data to newly created fragment"); return 1; } // we dont want to write to this fragment anymore @@ -842,31 +774,30 @@ static int _optimized_add(struct header_buffer *buf, mqtt_wss_log_ctx_t log_ctx, return 0; } -#define TRY_GENERATE_MESSAGE(generator_function, client, ...) \ - int rc = generator_function(&client->main_buffer, client->log, ##__VA_ARGS__); \ +#define TRY_GENERATE_MESSAGE(generator_function, ...) \ + int rc = generator_function(&client->main_buffer, ##__VA_ARGS__); \ if (rc == MQTT_NG_MSGGEN_BUFFER_OOM) { \ LOCK_HDR_BUFFER(&client->main_buffer); \ - transaction_buffer_garbage_collect((&client->main_buffer), client->log); \ + transaction_buffer_garbage_collect((&client->main_buffer)); \ UNLOCK_HDR_BUFFER(&client->main_buffer); \ - rc = generator_function(&client->main_buffer, client->log, ##__VA_ARGS__); \ + rc = generator_function(&client->main_buffer, ##__VA_ARGS__); \ if (rc == MQTT_NG_MSGGEN_BUFFER_OOM && client->max_mem_bytes) { \ LOCK_HDR_BUFFER(&client->main_buffer); \ - transaction_buffer_grow((&client->main_buffer), client->log, GROWTH_FACTOR, client->max_mem_bytes); \ + transaction_buffer_grow((&client->main_buffer),GROWTH_FACTOR, client->max_mem_bytes); \ UNLOCK_HDR_BUFFER(&client->main_buffer); \ - rc = generator_function(&client->main_buffer, client->log, ##__VA_ARGS__); \ + rc = generator_function(&client->main_buffer, ##__VA_ARGS__); \ } \ if (rc == MQTT_NG_MSGGEN_BUFFER_OOM) \ - mws_error(client->log, "%s failed to generate message due to insufficient buffer space (line %d)", __FUNCTION__, __LINE__); \ + nd_log(NDLS_DAEMON, NDLP_ERR, "%s failed to generate message due to insufficient buffer space (line %d)", __FUNCTION__, __LINE__); \ } \ if (rc == MQTT_NG_MSGGEN_OK) { \ - pthread_mutex_lock(&client->stats_mutex); \ + spinlock_lock(&client->stats_spinlock); \ client->stats.tx_messages_queued++; \ - pthread_mutex_unlock(&client->stats_mutex); \ + spinlock_unlock(&client->stats_spinlock); \ } \ return rc; mqtt_msg_data mqtt_ng_generate_connect(struct transaction_buffer *trx_buf, - mqtt_wss_log_ctx_t log_ctx, struct mqtt_auth_properties *auth, struct mqtt_lwt_properties *lwt, uint8_t clean_start, @@ -874,7 +805,7 @@ mqtt_msg_data mqtt_ng_generate_connect(struct transaction_buffer *trx_buf, { // Sanity Checks First (are given parameters correct and up to MQTT spec) if (!auth->client_id) { - mws_error(log_ctx, "ClientID must be set. [MQTT-3.1.3-3]"); + nd_log(NDLS_DAEMON, NDLP_ERR, "ClientID must be set. [MQTT-3.1.3-3]"); return NULL; } @@ -885,29 +816,29 @@ mqtt_msg_data mqtt_ng_generate_connect(struct transaction_buffer *trx_buf, // however server MUST allow ClientIDs between 1-23 bytes [MQTT-3.1.3-5] // so we will warn client server might not like this and he is using it // at his own risk! - mws_warn(log_ctx, "client_id provided is empty string. This might not be allowed by server [MQTT-3.1.3-6]"); + nd_log(NDLS_DAEMON, NDLP_WARNING, "client_id provided is empty string. This might not be allowed by server [MQTT-3.1.3-6]"); } if(len > MQTT_MAX_CLIENT_ID) { // [MQTT-3.1.3-5] server MUST allow client_id length 1-32 // server MAY allow longer client_id, if user provides longer client_id // warn them he is doing so at his own risk! - mws_warn(log_ctx, "client_id provided is longer than 23 bytes, server might not allow that [MQTT-3.1.3-5]"); + nd_log(NDLS_DAEMON, NDLP_WARNING, "client_id provided is longer than 23 bytes, server might not allow that [MQTT-3.1.3-5]"); } if (lwt) { if (lwt->will_message && lwt->will_message_size > 65535) { - mws_error(log_ctx, "Will message cannot be longer than 65535 bytes due to MQTT protocol limitations [MQTT-3.1.3-4] and [MQTT-1.5.6]"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Will message cannot be longer than 65535 bytes due to MQTT protocol limitations [MQTT-3.1.3-4] and [MQTT-1.5.6]"); return NULL; } if (!lwt->will_topic) { //TODO topic given with strlen==0 ? check specs - mws_error(log_ctx, "If will message is given will topic must also be given [MQTT-3.1.3.3]"); + nd_log(NDLS_DAEMON, NDLP_ERR, "If will message is given will topic must also be given [MQTT-3.1.3.3]"); return NULL; } if (lwt->will_qos > MQTT_MAX_QOS) { // refer to [MQTT-3-1.2-12] - mws_error(log_ctx, "QOS for LWT message is bigger than max"); + nd_log(NDLS_DAEMON, NDLP_ERR, "QOS for LWT message is bigger than max"); return NULL; } } @@ -941,8 +872,10 @@ mqtt_msg_data mqtt_ng_generate_connect(struct transaction_buffer *trx_buf, *connect_flags = 0; if (auth->username) *connect_flags |= MQTT_CONNECT_FLAG_USERNAME; + if (auth->password) *connect_flags |= MQTT_CONNECT_FLAG_PASSWORD; + if (lwt) { *connect_flags |= MQTT_CONNECT_FLAG_LWT; *connect_flags |= lwt->will_qos << MQTT_CONNECT_FLAG_QOS_BITSHIFT; @@ -966,7 +899,7 @@ mqtt_msg_data mqtt_ng_generate_connect(struct transaction_buffer *trx_buf, // [MQTT-3.1.3.1] Client identifier CHECK_BYTES_AVAILABLE(&trx_buf->hdr_buffer, 2, goto fail_rollback); PACK_2B_INT(&trx_buf->hdr_buffer, strlen(auth->client_id), frag); - if (_optimized_add(&trx_buf->hdr_buffer, log_ctx, auth->client_id, strlen(auth->client_id), auth->client_id_free, &frag)) + if (_optimized_add(&trx_buf->hdr_buffer, auth->client_id, strlen(auth->client_id), auth->client_id_free, &frag)) goto fail_rollback; if (lwt != NULL) { @@ -980,7 +913,7 @@ mqtt_msg_data mqtt_ng_generate_connect(struct transaction_buffer *trx_buf, // Will Topic [MQTT-3.1.3.3] CHECK_BYTES_AVAILABLE(&trx_buf->hdr_buffer, 2, goto fail_rollback); PACK_2B_INT(&trx_buf->hdr_buffer, strlen(lwt->will_topic), frag); - if (_optimized_add(&trx_buf->hdr_buffer, log_ctx, lwt->will_topic, strlen(lwt->will_topic), lwt->will_topic_free, &frag)) + if (_optimized_add(&trx_buf->hdr_buffer, lwt->will_topic, strlen(lwt->will_topic), lwt->will_topic_free, &frag)) goto fail_rollback; // Will Payload [MQTT-3.1.3.4] @@ -988,7 +921,7 @@ mqtt_msg_data mqtt_ng_generate_connect(struct transaction_buffer *trx_buf, BUFFER_TRANSACTION_NEW_FRAG(&trx_buf->hdr_buffer, 0, frag, goto fail_rollback); CHECK_BYTES_AVAILABLE(&trx_buf->hdr_buffer, 2, goto fail_rollback); PACK_2B_INT(&trx_buf->hdr_buffer, lwt->will_message_size, frag); - if (_optimized_add(&trx_buf->hdr_buffer, log_ctx, lwt->will_message, lwt->will_message_size, lwt->will_topic_free, &frag)) + if (_optimized_add(&trx_buf->hdr_buffer, lwt->will_message, lwt->will_message_size, lwt->will_topic_free, &frag)) goto fail_rollback; } } @@ -998,7 +931,7 @@ mqtt_msg_data mqtt_ng_generate_connect(struct transaction_buffer *trx_buf, BUFFER_TRANSACTION_NEW_FRAG(&trx_buf->hdr_buffer, 0, frag, goto fail_rollback); CHECK_BYTES_AVAILABLE(&trx_buf->hdr_buffer, 2, goto fail_rollback); PACK_2B_INT(&trx_buf->hdr_buffer, strlen(auth->username), frag); - if (_optimized_add(&trx_buf->hdr_buffer, log_ctx, auth->username, strlen(auth->username), auth->username_free, &frag)) + if (_optimized_add(&trx_buf->hdr_buffer, auth->username, strlen(auth->username), auth->username_free, &frag)) goto fail_rollback; } @@ -1007,7 +940,7 @@ mqtt_msg_data mqtt_ng_generate_connect(struct transaction_buffer *trx_buf, BUFFER_TRANSACTION_NEW_FRAG(&trx_buf->hdr_buffer, 0, frag, goto fail_rollback); CHECK_BYTES_AVAILABLE(&trx_buf->hdr_buffer, 2, goto fail_rollback); PACK_2B_INT(&trx_buf->hdr_buffer, strlen(auth->password), frag); - if (_optimized_add(&trx_buf->hdr_buffer, log_ctx, auth->password, strlen(auth->password), auth->password_free, &frag)) + if (_optimized_add(&trx_buf->hdr_buffer, auth->password, strlen(auth->password), auth->password_free, &frag)) goto fail_rollback; } trx_buf->hdr_buffer.tail_frag->flags |= BUFFER_FRAG_MQTT_PACKET_TAIL; @@ -1024,7 +957,7 @@ int mqtt_ng_connect(struct mqtt_ng_client *client, uint8_t clean_start, uint16_t keep_alive) { - client->client_state = RAW; + client->client_state = MQTT_STATE_RAW; client->parser.state = MQTT_PARSE_FIXED_HEADER_PACKET_TYPE; LOCK_HDR_BUFFER(&client->main_buffer); @@ -1033,28 +966,23 @@ int mqtt_ng_connect(struct mqtt_ng_client *client, buffer_purge(&client->main_buffer.hdr_buffer); UNLOCK_HDR_BUFFER(&client->main_buffer); - pthread_rwlock_wrlock(&client->tx_topic_aliases.rwlock); + spinlock_lock(&client->tx_topic_aliases.spinlock); // according to MQTT spec topic aliases should not be persisted // even if clean session is true mqtt_ng_destroy_tx_alias_hash(client->tx_topic_aliases.stoi_dict); + client->tx_topic_aliases.stoi_dict = TX_ALIASES_INITIALIZE(); - if (client->tx_topic_aliases.stoi_dict == NULL) { - pthread_rwlock_unlock(&client->tx_topic_aliases.rwlock); - return 1; - } client->tx_topic_aliases.idx_assigned = 0; - pthread_rwlock_unlock(&client->tx_topic_aliases.rwlock); + spinlock_unlock(&client->tx_topic_aliases.spinlock); mqtt_ng_destroy_rx_alias_hash(client->rx_aliases); client->rx_aliases = RX_ALIASES_INITIALIZE(); - if (client->rx_aliases == NULL) - return 1; - client->connect_msg = mqtt_ng_generate_connect(&client->main_buffer, client->log, auth, lwt, clean_start, keep_alive); + client->connect_msg = mqtt_ng_generate_connect(&client->main_buffer, auth, lwt, clean_start, keep_alive); if (client->connect_msg == NULL) return 1; - pthread_mutex_lock(&client->stats_mutex); + spinlock_lock(&client->stats_spinlock); if (clean_start) client->stats.tx_messages_queued = 1; else @@ -1062,9 +990,9 @@ int mqtt_ng_connect(struct mqtt_ng_client *client, client->stats.tx_messages_sent = 0; client->stats.rx_messages_rcvd = 0; - pthread_mutex_unlock(&client->stats_mutex); + spinlock_unlock(&client->stats_spinlock); - client->client_state = CONNECT_PENDING; + client->client_state = MQTT_STATE_CONNECT_PENDING; return 0; } @@ -1074,15 +1002,16 @@ uint16_t get_unused_packet_id() { return packet_id ? packet_id : ++packet_id; } -static inline size_t mqtt_ng_publish_size(const char *topic, - size_t msg_len, - uint16_t topic_id) +static size_t mqtt_ng_publish_size( + const char *topic, + size_t msg_len, + uint16_t topic_id) { - size_t retval = 2 /* Topic Name Length */ - + (topic == NULL ? 0 : strlen(topic)) - + 2 /* Packet identifier */ - + 1 /* Properties Length TODO for now fixed to 1 property */ - + msg_len; + size_t retval = 2 + + (topic == NULL ? 0 : strlen(topic)) /* Topic Name Length */ + + 2 /* Packet identifier */ + + 1 /* Properties Length for now fixed to 1 property */ + + msg_len; if (topic_id) retval += 3; @@ -1091,7 +1020,6 @@ static inline size_t mqtt_ng_publish_size(const char *topic, } int mqtt_ng_generate_publish(struct transaction_buffer *trx_buf, - mqtt_wss_log_ctx_t log_ctx, char *topic, free_fnc_t topic_free, void *msg, @@ -1130,7 +1058,7 @@ int mqtt_ng_generate_publish(struct transaction_buffer *trx_buf, // [MQTT-3.3.2.1] PACK_2B_INT(&trx_buf->hdr_buffer, topic == NULL ? 0 : strlen(topic), frag); if (topic != NULL) { - if (_optimized_add(&trx_buf->hdr_buffer, log_ctx, topic, strlen(topic), topic_free, &frag)) + if (_optimized_add(&trx_buf->hdr_buffer, topic, strlen(topic), topic_free, &frag)) goto fail_rollback; BUFFER_TRANSACTION_NEW_FRAG(&trx_buf->hdr_buffer, 0, frag, goto fail_rollback); } @@ -1154,7 +1082,7 @@ int mqtt_ng_generate_publish(struct transaction_buffer *trx_buf, if( (frag = buffer_new_frag(&trx_buf->hdr_buffer, BUFFER_FRAG_DATA_EXTERNAL)) == NULL ) goto fail_rollback; - if (frag_set_external_data(log_ctx, frag, msg, msg_len, msg_free)) + if (frag_set_external_data(frag, msg, msg_len, msg_free)) goto fail_rollback; trx_buf->hdr_buffer.tail_frag->flags |= BUFFER_FRAG_MQTT_PACKET_TAIL; @@ -1178,9 +1106,9 @@ int mqtt_ng_publish(struct mqtt_ng_client *client, uint16_t *packet_id) { struct topic_alias_data *alias = NULL; - pthread_rwlock_rdlock(&client->tx_topic_aliases.rwlock); + spinlock_lock(&client->tx_topic_aliases.spinlock); c_rhash_get_ptr_by_str(client->tx_topic_aliases.stoi_dict, topic, (void**)&alias); - pthread_rwlock_unlock(&client->tx_topic_aliases.rwlock); + spinlock_unlock(&client->tx_topic_aliases.spinlock); uint16_t topic_id = 0; @@ -1194,14 +1122,14 @@ int mqtt_ng_publish(struct mqtt_ng_client *client, } if (client->max_msg_size && PUBLISH_SP_SIZE + mqtt_ng_publish_size(topic, msg_len, topic_id) > client->max_msg_size) { - mws_error(client->log, "Message too big for server: %zu", msg_len); + nd_log(NDLS_DAEMON, NDLP_ERR, "Message too big for server: %zu", msg_len); return MQTT_NG_MSGGEN_MSG_TOO_BIG; } - TRY_GENERATE_MESSAGE(mqtt_ng_generate_publish, client, topic, topic_free, msg, msg_free, msg_len, publish_flags, packet_id, topic_id); + TRY_GENERATE_MESSAGE(mqtt_ng_generate_publish, topic, topic_free, msg, msg_free, msg_len, publish_flags, packet_id, topic_id); } -static inline size_t mqtt_ng_subscribe_size(struct mqtt_sub *subs, size_t sub_count) +static size_t mqtt_ng_subscribe_size(struct mqtt_sub *subs, size_t sub_count) { size_t len = 2 /* Packet Identifier */ + 1 /* Properties Length TODO for now fixed 0 */; len += sub_count * (2 /* topic filter string length */ + 1 /* [MQTT-3.8.3.1] Subscription Options Byte */); @@ -1212,7 +1140,7 @@ static inline size_t mqtt_ng_subscribe_size(struct mqtt_sub *subs, size_t sub_co return len; } -int mqtt_ng_generate_subscribe(struct transaction_buffer *trx_buf, mqtt_wss_log_ctx_t log_ctx, struct mqtt_sub *subs, size_t sub_count) +int mqtt_ng_generate_subscribe(struct transaction_buffer *trx_buf, struct mqtt_sub *subs, size_t sub_count) { // >> START THE RODEO << transaction_buffer_transaction_start(trx_buf); @@ -1247,7 +1175,7 @@ int mqtt_ng_generate_subscribe(struct transaction_buffer *trx_buf, mqtt_wss_log_ for (size_t i = 0; i < sub_count; i++) { BUFFER_TRANSACTION_NEW_FRAG(&trx_buf->hdr_buffer, 0, frag, goto fail_rollback); PACK_2B_INT(&trx_buf->hdr_buffer, strlen(subs[i].topic), frag); - if (_optimized_add(&trx_buf->hdr_buffer, log_ctx, subs[i].topic, strlen(subs[i].topic), subs[i].topic_free, &frag)) + if (_optimized_add(&trx_buf->hdr_buffer, subs[i].topic, strlen(subs[i].topic), subs[i].topic_free, &frag)) goto fail_rollback; BUFFER_TRANSACTION_NEW_FRAG(&trx_buf->hdr_buffer, 0, frag, goto fail_rollback); *WRITE_POS(frag) = subs[i].options; @@ -1264,12 +1192,11 @@ fail_rollback: int mqtt_ng_subscribe(struct mqtt_ng_client *client, struct mqtt_sub *subs, size_t sub_count) { - TRY_GENERATE_MESSAGE(mqtt_ng_generate_subscribe, client, subs, sub_count); + TRY_GENERATE_MESSAGE(mqtt_ng_generate_subscribe, subs, sub_count); } -int mqtt_ng_generate_disconnect(struct transaction_buffer *trx_buf, mqtt_wss_log_ctx_t log_ctx, uint8_t reason_code) +int mqtt_ng_generate_disconnect(struct transaction_buffer *trx_buf, uint8_t reason_code) { - (void) log_ctx; // >> START THE RODEO << transaction_buffer_transaction_start(trx_buf); @@ -1308,12 +1235,11 @@ fail_rollback: int mqtt_ng_disconnect(struct mqtt_ng_client *client, uint8_t reason_code) { - TRY_GENERATE_MESSAGE(mqtt_ng_generate_disconnect, client, reason_code); + TRY_GENERATE_MESSAGE(mqtt_ng_generate_disconnect, reason_code); } -static int mqtt_generate_puback(struct transaction_buffer *trx_buf, mqtt_wss_log_ctx_t log_ctx, uint16_t packet_id, uint8_t reason_code) +static int mqtt_generate_puback(struct transaction_buffer *trx_buf, uint16_t packet_id, uint8_t reason_code) { - (void) log_ctx; // >> START THE RODEO << transaction_buffer_transaction_start(trx_buf); @@ -1353,7 +1279,7 @@ fail_rollback: static int mqtt_ng_puback(struct mqtt_ng_client *client, uint16_t packet_id, uint8_t reason_code) { - TRY_GENERATE_MESSAGE(mqtt_generate_puback, client, packet_id, reason_code); + TRY_GENERATE_MESSAGE(mqtt_generate_puback, packet_id, reason_code); } int mqtt_ng_ping(struct mqtt_ng_client *client) @@ -1370,7 +1296,6 @@ int mqtt_ng_ping(struct mqtt_ng_client *client) #define MQTT_NG_CLIENT_PROTOCOL_ERROR -1 #define MQTT_NG_CLIENT_SERVER_RETURNED_ERROR -2 #define MQTT_NG_CLIENT_NOT_IMPL_YET -3 -#define MQTT_NG_CLIENT_OOM -4 #define MQTT_NG_CLIENT_INTERNAL_ERROR -5 #define BUF_READ_CHECK_AT_LEAST(buf, x) \ @@ -1379,10 +1304,10 @@ int mqtt_ng_ping(struct mqtt_ng_client *client) #define vbi_parser_reset_ctx(ctx) memset(ctx, 0, sizeof(struct mqtt_vbi_parser_ctx)) -static int vbi_parser_parse(struct mqtt_vbi_parser_ctx *ctx, rbuf_t data, mqtt_wss_log_ctx_t log) +static int vbi_parser_parse(struct mqtt_vbi_parser_ctx *ctx, rbuf_t data) { if (ctx->bytes > MQTT_VBI_MAXBYTES - 1) { - mws_error(log, "MQTT Variable Byte Integer can't be longer than %d bytes", MQTT_VBI_MAXBYTES); + nd_log(NDLS_DAEMON, NDLP_ERR, "MQTT Variable Byte Integer can't be longer than %d bytes", MQTT_VBI_MAXBYTES); return MQTT_NG_CLIENT_PROTOCOL_ERROR; } if (!ctx->bytes || ctx->data[ctx->bytes-1] & MQTT_VBI_CONTINUATION_FLAG) { @@ -1394,7 +1319,7 @@ static int vbi_parser_parse(struct mqtt_vbi_parser_ctx *ctx, rbuf_t data, mqtt_w } if (mqtt_vbi_to_uint32(ctx->data, &ctx->result)) { - mws_error(log, "MQTT Variable Byte Integer failed to be parsed."); + nd_log(NDLS_DAEMON, NDLP_ERR, "MQTT Variable Byte Integer failed to be parsed."); return MQTT_NG_CLIENT_PROTOCOL_ERROR; } @@ -1480,12 +1405,12 @@ struct mqtt_property *get_property_by_id(struct mqtt_property *props, uint8_t pr } // Parses [MQTT-2.2.2] -static int parse_properties_array(struct mqtt_properties_parser_ctx *ctx, rbuf_t data, mqtt_wss_log_ctx_t log) +static int parse_properties_array(struct mqtt_properties_parser_ctx *ctx, rbuf_t data) { int rc; switch (ctx->state) { case PROPERTIES_LENGTH: - rc = vbi_parser_parse(&ctx->vbi_parser_ctx, data, log); + rc = vbi_parser_parse(&ctx->vbi_parser_ctx, data); if (rc == MQTT_NG_CLIENT_PARSE_DONE) { ctx->properties_length = ctx->vbi_parser_ctx.result; ctx->bytes_consumed += ctx->vbi_parser_ctx.bytes; @@ -1534,7 +1459,7 @@ static int parse_properties_array(struct mqtt_properties_parser_ctx *ctx, rbuf_t ctx->state = PROPERTY_TYPE_STR_BIN_LEN; break; default: - mws_error(log, "Unsupported property type %d for property id %d.", (int)ctx->tail->type, (int)ctx->tail->id); + nd_log(NDLS_DAEMON, NDLP_ERR, "Unsupported property type %d for property id %d.", (int)ctx->tail->type, (int)ctx->tail->id); return MQTT_NG_CLIENT_PROTOCOL_ERROR; } break; @@ -1552,7 +1477,7 @@ static int parse_properties_array(struct mqtt_properties_parser_ctx *ctx, rbuf_t ctx->state = PROPERTY_TYPE_STR; break; default: - mws_error(log, "Unexpected datatype in PROPERTY_TYPE_STR_BIN_LEN %d", (int)ctx->tail->type); + nd_log(NDLS_DAEMON, NDLP_ERR, "Unexpected datatype in PROPERTY_TYPE_STR_BIN_LEN %d", (int)ctx->tail->type); return MQTT_NG_CLIENT_INTERNAL_ERROR; } break; @@ -1577,7 +1502,7 @@ static int parse_properties_array(struct mqtt_properties_parser_ctx *ctx, rbuf_t ctx->state = PROPERTY_NEXT; break; case PROPERTY_TYPE_VBI: - rc = vbi_parser_parse(&ctx->vbi_parser_ctx, data, log); + rc = vbi_parser_parse(&ctx->vbi_parser_ctx, data); if (rc == MQTT_NG_CLIENT_PARSE_DONE) { ctx->tail->data.uint32 = ctx->vbi_parser_ctx.result; ctx->bytes_consumed += ctx->vbi_parser_ctx.bytes; @@ -1627,9 +1552,9 @@ static int parse_connack_varhdr(struct mqtt_ng_client *client) mqtt_properties_parser_ctx_reset(&parser->properties_parser); break; case MQTT_PARSE_VARHDR_PROPS: - return parse_properties_array(&parser->properties_parser, parser->received_data, client->log); + return parse_properties_array(&parser->properties_parser, parser->received_data); default: - ERROR("invalid state for connack varhdr parser"); + nd_log(NDLS_DAEMON, NDLP_ERR, "invalid state for connack varhdr parser"); return MQTT_NG_CLIENT_INTERNAL_ERROR; } return MQTT_NG_CLIENT_OK_CALL_AGAIN; @@ -1653,9 +1578,9 @@ static int parse_disconnect_varhdr(struct mqtt_ng_client *client) mqtt_properties_parser_ctx_reset(&parser->properties_parser); break; case MQTT_PARSE_VARHDR_PROPS: - return parse_properties_array(&parser->properties_parser, parser->received_data, client->log); + return parse_properties_array(&parser->properties_parser, parser->received_data); default: - ERROR("invalid state for connack varhdr parser"); + nd_log(NDLS_DAEMON, NDLP_ERR, "invalid state for connack varhdr parser"); return MQTT_NG_CLIENT_INTERNAL_ERROR; } return MQTT_NG_CLIENT_OK_CALL_AGAIN; @@ -1691,9 +1616,9 @@ static int parse_puback_varhdr(struct mqtt_ng_client *client) mqtt_properties_parser_ctx_reset(&parser->properties_parser); /* FALLTHROUGH */ case MQTT_PARSE_VARHDR_PROPS: - return parse_properties_array(&parser->properties_parser, parser->received_data, client->log); + return parse_properties_array(&parser->properties_parser, parser->received_data); default: - ERROR("invalid state for puback varhdr parser"); + nd_log(NDLS_DAEMON, NDLP_ERR, "invalid state for puback varhdr parser"); return MQTT_NG_CLIENT_INTERNAL_ERROR; } return MQTT_NG_CLIENT_OK_CALL_AGAIN; @@ -1716,7 +1641,7 @@ static int parse_suback_varhdr(struct mqtt_ng_client *client) mqtt_properties_parser_ctx_reset(&parser->properties_parser); /* FALLTHROUGH */ case MQTT_PARSE_VARHDR_PROPS: - rc = parse_properties_array(&parser->properties_parser, parser->received_data, client->log); + rc = parse_properties_array(&parser->properties_parser, parser->received_data); if (rc != MQTT_NG_CLIENT_PARSE_DONE) return rc; parser->mqtt_parsed_len += parser->properties_parser.bytes_consumed; @@ -1737,7 +1662,7 @@ static int parse_suback_varhdr(struct mqtt_ng_client *client) return MQTT_NG_CLIENT_NEED_MORE_BYTES; default: - ERROR("invalid state for suback varhdr parser"); + nd_log(NDLS_DAEMON, NDLP_ERR, "invalid state for suback varhdr parser"); return MQTT_NG_CLIENT_INTERNAL_ERROR; } return MQTT_NG_CLIENT_OK_CALL_AGAIN; @@ -1761,8 +1686,6 @@ static int parse_publish_varhdr(struct mqtt_ng_client *client) break; } publish->topic = callocz(1, publish->topic_len + 1 /* add 0x00 */); - if (publish->topic == NULL) - return MQTT_NG_CLIENT_OOM; parser->varhdr_state = MQTT_PARSE_VARHDR_TOPICNAME; /* FALLTHROUGH */ case MQTT_PARSE_VARHDR_TOPICNAME: @@ -1788,7 +1711,7 @@ static int parse_publish_varhdr(struct mqtt_ng_client *client) parser->mqtt_parsed_len += 2; /* FALLTHROUGH */ case MQTT_PARSE_VARHDR_PROPS: - rc = parse_properties_array(&parser->properties_parser, parser->received_data, client->log); + rc = parse_properties_array(&parser->properties_parser, parser->received_data); if (rc != MQTT_NG_CLIENT_PARSE_DONE) return rc; parser->mqtt_parsed_len += parser->properties_parser.bytes_consumed; @@ -1798,7 +1721,7 @@ static int parse_publish_varhdr(struct mqtt_ng_client *client) if (parser->mqtt_fixed_hdr_remaining_length < parser->mqtt_parsed_len) { freez(publish->topic); publish->topic = NULL; - ERROR("Error parsing PUBLISH message"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Error parsing PUBLISH message"); return MQTT_NG_CLIENT_PROTOCOL_ERROR; } publish->data_len = parser->mqtt_fixed_hdr_remaining_length - parser->mqtt_parsed_len; @@ -1809,18 +1732,12 @@ static int parse_publish_varhdr(struct mqtt_ng_client *client) BUF_READ_CHECK_AT_LEAST(parser->received_data, publish->data_len); publish->data = mallocz(publish->data_len); - if (publish->data == NULL) { - freez(publish->topic); - publish->topic = NULL; - return MQTT_NG_CLIENT_OOM; - } - rbuf_pop(parser->received_data, publish->data, publish->data_len); parser->mqtt_parsed_len += publish->data_len; return MQTT_NG_CLIENT_PARSE_DONE; default: - ERROR("invalid state for publish varhdr parser"); + nd_log(NDLS_DAEMON, NDLP_ERR, "invalid state for publish varhdr parser"); return MQTT_NG_CLIENT_INTERNAL_ERROR; } return MQTT_NG_CLIENT_OK_CALL_AGAIN; @@ -1840,7 +1757,7 @@ static int parse_data(struct mqtt_ng_client *client) parser->state = MQTT_PARSE_FIXED_HEADER_LEN; break; case MQTT_PARSE_FIXED_HEADER_LEN: - rc = vbi_parser_parse(&parser->vbi_parser, parser->received_data, client->log); + rc = vbi_parser_parse(&parser->vbi_parser, parser->received_data); if (rc == MQTT_NG_CLIENT_PARSE_DONE) { parser->mqtt_fixed_hdr_remaining_length = parser->vbi_parser.result; parser->state = MQTT_PARSE_VARIABLE_HEADER; @@ -1883,10 +1800,11 @@ static int parse_data(struct mqtt_ng_client *client) return rc; case MQTT_CPT_PINGRESP: if (parser->mqtt_fixed_hdr_remaining_length) { - ERROR ("PINGRESP has to be 0 Remaining Length."); // [MQTT-3.13.1] + nd_log(NDLS_DAEMON, NDLP_ERR, "PINGRESP has to be 0 Remaining Length."); // [MQTT-3.13.1] return MQTT_NG_CLIENT_PROTOCOL_ERROR; } parser->state = MQTT_PARSE_MQTT_PACKET_DONE; + ping_timeout = 0; break; case MQTT_CPT_DISCONNECT: rc = parse_disconnect_varhdr(client); @@ -1896,7 +1814,7 @@ static int parse_data(struct mqtt_ng_client *client) } return rc; default: - ERROR("Parsing Control Packet Type %" PRIu8 " not implemented yet.", get_control_packet_type(parser->mqtt_control_packet_type)); + nd_log(NDLS_DAEMON, NDLP_ERR, "Parsing Control Packet Type %" PRIu8 " not implemented yet.", get_control_packet_type(parser->mqtt_control_packet_type)); rbuf_bump_tail(parser->received_data, parser->mqtt_fixed_hdr_remaining_length); parser->state = MQTT_PARSE_MQTT_PACKET_DONE; return MQTT_NG_CLIENT_NOT_IMPL_YET; @@ -1916,12 +1834,12 @@ static int parse_data(struct mqtt_ng_client *client) // return -1 on error // return 0 if there is fragment set static int mqtt_ng_next_to_send(struct mqtt_ng_client *client) { - if (client->client_state == CONNECT_PENDING) { + if (client->client_state == MQTT_STATE_CONNECT_PENDING) { client->main_buffer.sending_frag = client->connect_msg; - client->client_state = CONNECTING; + client->client_state = MQTT_STATE_CONNECTING; return 0; } - if (client->client_state != CONNECTED) + if (client->client_state != MQTT_STATE_CONNECTED) return -1; struct buffer_fragment *frag = BUFFER_FIRST_FRAG(&client->main_buffer.hdr_buffer); @@ -1959,7 +1877,7 @@ static int send_fragment(struct mqtt_ng_client *client) { if (bytes) processed = client->send_fnc_ptr(client->user_ctx, ptr, bytes); else - WARN("This fragment was fully sent already. This should not happen!"); + nd_log(NDLS_DAEMON, NDLP_WARNING, "This fragment was fully sent already. This should not happen!"); frag->sent += processed; if (frag->sent != frag->len) @@ -1967,11 +1885,11 @@ static int send_fragment(struct mqtt_ng_client *client) { if (frag->flags & BUFFER_FRAG_MQTT_PACKET_TAIL) { client->time_of_last_send = time(NULL); - pthread_mutex_lock(&client->stats_mutex); + spinlock_lock(&client->stats_spinlock); if (client->main_buffer.sending_frag != &ping_frag) client->stats.tx_messages_queued--; client->stats.tx_messages_sent++; - pthread_mutex_unlock(&client->stats_mutex); + spinlock_unlock(&client->stats_spinlock); client->main_buffer.sending_frag = NULL; return 1; } @@ -1995,7 +1913,7 @@ static void try_send_all(struct mqtt_ng_client *client) { } while(send_all_message_fragments(client) >= 0); } -static inline void mark_message_for_gc(struct buffer_fragment *frag) +static void mark_message_for_gc(struct buffer_fragment *frag) { while (frag) { frag->flags |= BUFFER_FRAG_GARBAGE_COLLECT; @@ -2013,7 +1931,7 @@ static int mark_packet_acked(struct mqtt_ng_client *client, uint16_t packet_id) while (frag) { if ( (frag->flags & BUFFER_FRAG_MQTT_PACKET_HEAD) && frag->packet_id == packet_id) { if (!frag->sent) { - ERROR("Received packet_id (%" PRIu16 ") belongs to MQTT packet which was not yet sent!", packet_id); + nd_log(NDLS_DAEMON, NDLP_ERR, "Received packet_id (%" PRIu16 ") belongs to MQTT packet which was not yet sent!", packet_id); UNLOCK_HDR_BUFFER(&client->main_buffer); return 1; } @@ -2023,7 +1941,7 @@ static int mark_packet_acked(struct mqtt_ng_client *client, uint16_t packet_id) } frag = frag->next; } - ERROR("Received packet_id (%" PRIu16 ") is unknown!", packet_id); + nd_log(NDLS_DAEMON, NDLP_ERR, "Received packet_id (%" PRIu16 ") is unknown!", packet_id); UNLOCK_HDR_BUFFER(&client->main_buffer); return 1; } @@ -2031,110 +1949,113 @@ static int mark_packet_acked(struct mqtt_ng_client *client, uint16_t packet_id) int handle_incoming_traffic(struct mqtt_ng_client *client) { int rc; + while ((rc = parse_data(client)) == MQTT_NG_CLIENT_OK_CALL_AGAIN) { + ; + } + if (rc != MQTT_NG_CLIENT_MQTT_PACKET_DONE) + return rc; + struct mqtt_publish *pub; - while( (rc = parse_data(client)) == MQTT_NG_CLIENT_OK_CALL_AGAIN ); - if ( rc == MQTT_NG_CLIENT_MQTT_PACKET_DONE ) { - struct mqtt_property *prop; -#ifdef MQTT_DEBUG_VERBOSE - DEBUG("MQTT Packet Parsed Successfully!"); -#endif - pthread_mutex_lock(&client->stats_mutex); - client->stats.rx_messages_rcvd++; - pthread_mutex_unlock(&client->stats_mutex); - - switch (get_control_packet_type(client->parser.mqtt_control_packet_type)) { - case MQTT_CPT_CONNACK: -#ifdef MQTT_DEBUG_VERBOSE - DEBUG("Received CONNACK"); -#endif - LOCK_HDR_BUFFER(&client->main_buffer); - mark_message_for_gc(client->connect_msg); - UNLOCK_HDR_BUFFER(&client->main_buffer); - client->connect_msg = NULL; - if (client->client_state != CONNECTING) { - ERROR("Received unexpected CONNACK"); - client->client_state = ERROR; - return MQTT_NG_CLIENT_PROTOCOL_ERROR; - } - if ((prop = get_property_by_id(client->parser.properties_parser.head, MQTT_PROP_MAX_PKT_SIZE)) != NULL) { - INFO("MQTT server limits message size to %" PRIu32, prop->data.uint32); - client->max_msg_size = prop->data.uint32; - } - if (client->connack_callback) - client->connack_callback(client->user_ctx, client->parser.mqtt_packet.connack.reason_code); - if (!client->parser.mqtt_packet.connack.reason_code) { - INFO("MQTT Connection Accepted By Server"); - client->client_state = CONNECTED; - break; - } - client->client_state = ERROR; - return MQTT_NG_CLIENT_SERVER_RETURNED_ERROR; - case MQTT_CPT_PUBACK: -#ifdef MQTT_DEBUG_VERBOSE - DEBUG("Received PUBACK %" PRIu16, client->parser.mqtt_packet.puback.packet_id); -#endif - if (mark_packet_acked(client, client->parser.mqtt_packet.puback.packet_id)) - return MQTT_NG_CLIENT_PROTOCOL_ERROR; - if (client->puback_callback) - client->puback_callback(client->parser.mqtt_packet.puback.packet_id); - break; - case MQTT_CPT_PINGRESP: -#ifdef MQTT_DEBUG_VERBOSE - DEBUG("Received PINGRESP"); -#endif - break; - case MQTT_CPT_SUBACK: -#ifdef MQTT_DEBUG_VERBOSE - DEBUG("Received SUBACK %" PRIu16, client->parser.mqtt_packet.suback.packet_id); -#endif - if (mark_packet_acked(client, client->parser.mqtt_packet.suback.packet_id)) - return MQTT_NG_CLIENT_PROTOCOL_ERROR; + struct mqtt_property *prop; + spinlock_lock(&client->stats_spinlock); + client->stats.rx_messages_rcvd++; + spinlock_unlock(&client->stats_spinlock); + + uint8_t ctrl_packet_type = get_control_packet_type(client->parser.mqtt_control_packet_type); + switch (ctrl_packet_type) { + case MQTT_CPT_CONNACK: + LOCK_HDR_BUFFER(&client->main_buffer); + mark_message_for_gc(client->connect_msg); + UNLOCK_HDR_BUFFER(&client->main_buffer); + + client->connect_msg = NULL; + + if (client->client_state != MQTT_STATE_CONNECTING) { + nd_log(NDLS_DAEMON, NDLP_ERR, "Received unexpected CONNACK"); + client->client_state = MQTT_STATE_ERROR; + return MQTT_NG_CLIENT_PROTOCOL_ERROR; + } + + if ((prop = get_property_by_id(client->parser.properties_parser.head, MQTT_PROP_MAX_PKT_SIZE)) != NULL) { + nd_log(NDLS_DAEMON, NDLP_INFO, "MQTT server limits message size to %" PRIu32, prop->data.uint32); + client->max_msg_size = prop->data.uint32; + } + + if (client->connack_callback) + client->connack_callback(client->user_ctx, client->parser.mqtt_packet.connack.reason_code); + if (!client->parser.mqtt_packet.connack.reason_code) { + nd_log(NDLS_DAEMON, NDLP_INFO, "MQTT Connection Accepted By Server"); + client->client_state = MQTT_STATE_CONNECTED; break; - case MQTT_CPT_PUBLISH: -#ifdef MQTT_DEBUG_VERBOSE - DEBUG("Recevied PUBLISH"); -#endif - pub = &client->parser.mqtt_packet.publish; - if (pub->qos > 1) { - freez(pub->topic); - freez(pub->data); - return MQTT_NG_CLIENT_NOT_IMPL_YET; - } - if ( pub->qos == 1 && (rc = mqtt_ng_puback(client, pub->packet_id, 0)) ) { - client->client_state = ERROR; - ERROR("Error generating PUBACK reply for PUBLISH"); - return rc; - } - if ( (prop = get_property_by_id(client->parser.properties_parser.head, MQTT_PROP_TOPIC_ALIAS)) != NULL ) { - // Topic Alias property was sent from server - void *topic_ptr; - if (!c_rhash_get_ptr_by_uint64(client->rx_aliases, prop->data.uint8, &topic_ptr)) { - if (pub->topic != NULL) { - ERROR("We do not yet support topic alias reassignment"); - return MQTT_NG_CLIENT_NOT_IMPL_YET; - } - pub->topic = topic_ptr; - } else { - if (pub->topic == NULL) { - ERROR("Topic alias with id %d unknown and topic not set by server!", prop->data.uint8); - return MQTT_NG_CLIENT_PROTOCOL_ERROR; - } - c_rhash_insert_uint64_ptr(client->rx_aliases, prop->data.uint8, pub->topic); + } + client->client_state = MQTT_STATE_ERROR; + return MQTT_NG_CLIENT_SERVER_RETURNED_ERROR; + + case MQTT_CPT_PUBACK: + if (mark_packet_acked(client, client->parser.mqtt_packet.puback.packet_id)) + return MQTT_NG_CLIENT_PROTOCOL_ERROR; + if (client->puback_callback) + client->puback_callback(client->parser.mqtt_packet.puback.packet_id); + break; + + case MQTT_CPT_PINGRESP: + break; + + case MQTT_CPT_SUBACK: + if (mark_packet_acked(client, client->parser.mqtt_packet.suback.packet_id)) + return MQTT_NG_CLIENT_PROTOCOL_ERROR; + break; + + case MQTT_CPT_PUBLISH: + pub = &client->parser.mqtt_packet.publish; + + if (pub->qos > 1) { + freez(pub->topic); + freez(pub->data); + return MQTT_NG_CLIENT_NOT_IMPL_YET; + } + + if ( pub->qos == 1 && ((rc = mqtt_ng_puback(client, pub->packet_id, 0))) ) { + client->client_state = MQTT_STATE_ERROR; + nd_log(NDLS_DAEMON, NDLP_ERR, "Error generating PUBACK reply for PUBLISH"); + return rc; + } + + if ( (prop = get_property_by_id(client->parser.properties_parser.head, MQTT_PROP_TOPIC_ALIAS)) != NULL ) { + // Topic Alias property was sent from server + void *topic_ptr; + if (!c_rhash_get_ptr_by_uint64(client->rx_aliases, prop->data.uint8, &topic_ptr)) { + if (pub->topic != NULL) { + nd_log(NDLS_DAEMON, NDLP_ERR, "We do not yet support topic alias reassignment"); + return MQTT_NG_CLIENT_NOT_IMPL_YET; } + pub->topic = topic_ptr; + } else { + if (pub->topic == NULL) { + nd_log(NDLS_DAEMON, NDLP_ERR, "Topic alias with id %d unknown and topic not set by server!", prop->data.uint8); + return MQTT_NG_CLIENT_PROTOCOL_ERROR; + } + c_rhash_insert_uint64_ptr(client->rx_aliases, prop->data.uint8, pub->topic); } - if (client->msg_callback) - client->msg_callback(pub->topic, pub->data, pub->data_len, pub->qos); - // in case we have property topic alias and we have topic we take over the string - // and add pointer to it into topic alias list - if (prop == NULL) - freez(pub->topic); - freez(pub->data); - return MQTT_NG_CLIENT_WANT_WRITE; - case MQTT_CPT_DISCONNECT: - INFO ("Got MQTT DISCONNECT control packet from server. Reason code: %d", (int)client->parser.mqtt_packet.disconnect.reason_code); - client->client_state = DISCONNECTED; - break; - } + } + + if (client->msg_callback) + client->msg_callback(pub->topic, pub->data, pub->data_len, pub->qos); + // in case we have property topic alias and we have topic we take over the string + // and add pointer to it into topic alias list + if (prop == NULL) + freez(pub->topic); + freez(pub->data); + return MQTT_NG_CLIENT_WANT_WRITE; + + case MQTT_CPT_DISCONNECT: + nd_log(NDLS_DAEMON, NDLP_INFO, "Got MQTT DISCONNECT control packet from server. Reason code: %d", (int)client->parser.mqtt_packet.disconnect.reason_code); + client->client_state = MQTT_STATE_DISCONNECTED; + break; + + default: + nd_log(NDLS_DAEMON, NDLP_INFO, "Got unknown control packet %u from server", ctrl_packet_type); + break; } return rc; @@ -2142,10 +2063,10 @@ int handle_incoming_traffic(struct mqtt_ng_client *client) int mqtt_ng_sync(struct mqtt_ng_client *client) { - if (client->client_state == RAW || client->client_state == DISCONNECTED) + if (client->client_state == MQTT_STATE_RAW || client->client_state == MQTT_STATE_DISCONNECTED) return 0; - if (client->client_state == ERROR) + if (client->client_state == MQTT_STATE_ERROR) return 1; LOCK_HDR_BUFFER(&client->main_buffer); @@ -2182,9 +2103,9 @@ void mqtt_ng_set_max_mem(struct mqtt_ng_client *client, size_t bytes) void mqtt_ng_get_stats(struct mqtt_ng_client *client, struct mqtt_ng_stats *stats) { - pthread_mutex_lock(&client->stats_mutex); + spinlock_lock(&client->stats_spinlock); memcpy(stats, &client->stats, sizeof(struct mqtt_ng_stats)); - pthread_mutex_unlock(&client->stats_mutex); + spinlock_unlock(&client->stats_spinlock); stats->tx_bytes_queued = 0; stats->tx_buffer_reclaimable = 0; @@ -2207,11 +2128,11 @@ void mqtt_ng_get_stats(struct mqtt_ng_client *client, struct mqtt_ng_stats *stat int mqtt_ng_set_topic_alias(struct mqtt_ng_client *client, const char *topic) { uint16_t idx; - pthread_rwlock_wrlock(&client->tx_topic_aliases.rwlock); + spinlock_lock(&client->tx_topic_aliases.spinlock); if (client->tx_topic_aliases.idx_assigned >= client->tx_topic_aliases.idx_max) { - pthread_rwlock_unlock(&client->tx_topic_aliases.rwlock); - mws_error(client->log, "Tx topic alias indexes were exhausted (current version of the library doesn't support reassigning yet. Feel free to contribute."); + spinlock_unlock(&client->tx_topic_aliases.spinlock); + nd_log(NDLS_DAEMON, NDLP_ERR, "Tx topic alias indexes were exhausted (current version of the library doesn't support reassigning yet. Feel free to contribute."); return 0; //0 is not a valid topic alias } @@ -2220,8 +2141,8 @@ int mqtt_ng_set_topic_alias(struct mqtt_ng_client *client, const char *topic) // this is not a problem for library but might be helpful to warn user // as it might indicate bug in their program (but also might be expected) idx = alias->idx; - pthread_rwlock_unlock(&client->tx_topic_aliases.rwlock); - mws_debug(client->log, "%s topic \"%s\" already has alias set. Ignoring.", __FUNCTION__, topic); + spinlock_unlock(&client->tx_topic_aliases.spinlock); + nd_log(NDLS_DAEMON, NDLP_DEBUG, "%s topic \"%s\" already has alias set. Ignoring.", __FUNCTION__, topic); return idx; } @@ -2232,6 +2153,6 @@ int mqtt_ng_set_topic_alias(struct mqtt_ng_client *client, const char *topic) c_rhash_insert_str_ptr(client->tx_topic_aliases.stoi_dict, topic, (void*)alias); - pthread_rwlock_unlock(&client->tx_topic_aliases.rwlock); + spinlock_unlock(&client->tx_topic_aliases.spinlock); return idx; } diff --git a/src/aclk/mqtt_websockets/mqtt_ng.h b/src/aclk/mqtt_websockets/mqtt_ng.h index 4b0584d58..c5f6d94cc 100644 --- a/src/aclk/mqtt_websockets/mqtt_ng.h +++ b/src/aclk/mqtt_websockets/mqtt_ng.h @@ -1,10 +1,5 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only +// SPDX-License-Identifier: GPL-3.0-or-later -#include <stdint.h> -#include <sys/types.h> -#include <time.h> - -#include "c-rbuf/cringbuffer.h" #include "common_public.h" #define MQTT_NG_MSGGEN_OK 0 @@ -15,7 +10,7 @@ #define MQTT_NG_MSGGEN_MSG_TOO_BIG 3 struct mqtt_ng_client; - +extern time_t ping_timeout; /* Converts integer to MQTT Variable Byte Integer as per 1.5.5 of MQTT 5 specs * @param input value to be converted * @param output pointer to memory where output will be written to. Must allow up to 4 bytes to be written. @@ -72,7 +67,6 @@ int mqtt_ng_ping(struct mqtt_ng_client *client); typedef ssize_t (*mqtt_ng_send_fnc_t)(void *user_ctx, const void* buf, size_t len); struct mqtt_ng_init { - mqtt_wss_log_ctx_t log; rbuf_t data_in; mqtt_ng_send_fnc_t data_out_fnc; void *user_ctx; diff --git a/src/aclk/mqtt_websockets/mqtt_wss_client.c b/src/aclk/mqtt_websockets/mqtt_wss_client.c index bb0e17262..5c576ced5 100644 --- a/src/aclk/mqtt_websockets/mqtt_wss_client.c +++ b/src/aclk/mqtt_websockets/mqtt_wss_client.c @@ -1,32 +1,24 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-License-Identifier: GPL-3.0-or-later #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif +#include "libnetdata/libnetdata.h" #include "mqtt_wss_client.h" #include "mqtt_ng.h" #include "ws_client.h" #include "common_internal.h" - -#include <stdlib.h> -#include <fcntl.h> -#include <unistd.h> -#include <poll.h> -#include <string.h> -#include <time.h> - -#include <sys/socket.h> -#include <netinet/in.h> - -#include <openssl/err.h> -#include <openssl/ssl.h> +#include "../aclk.h" #define PIPE_READ_END 0 #define PIPE_WRITE_END 1 #define POLLFD_SOCKET 0 #define POLLFD_PIPE 1 +#define PING_TIMEOUT (60) //Expect a ping response within this time (seconds) +time_t ping_timeout = 0; + #if (OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110) && (SSLEAY_VERSION_NUMBER >= OPENSSL_VERSION_097) #include <openssl/conf.h> #endif @@ -69,6 +61,8 @@ char *util_openssl_ret_err(int err) return "SSL_ERROR_SYSCALL"; case SSL_ERROR_SSL: return "SSL_ERROR_SSL"; + default: + break; } return "UNKNOWN"; } @@ -76,8 +70,6 @@ char *util_openssl_ret_err(int err) struct mqtt_wss_client_struct { ws_client *ws_client; - mqtt_wss_log_ctx_t log; - // immediate connection (e.g. proxy server) char *host; int port; @@ -129,69 +121,49 @@ static void mws_connack_callback_ng(void *user_ctx, int code) switch(code) { case 0: client->mqtt_connected = 1; - return; + break; //TODO manual labor: all the CONNACK error codes with some nice error message default: - mws_error(client->log, "MQTT CONNACK returned error %d", code); - return; + nd_log(NDLS_DAEMON, NDLP_ERR, "MQTT CONNACK returned error %d", code); + break; } } static ssize_t mqtt_send_cb(void *user_ctx, const void* buf, size_t len) { mqtt_wss_client client = user_ctx; -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "mqtt_pal_sendall(len=%d)", len); -#endif int ret = ws_client_send(client->ws_client, WS_OP_BINARY_FRAME, buf, len); - if (ret >= 0 && (size_t)ret != len) { -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "Not complete message sent (Msg=%d,Sent=%d). Need to arm POLLOUT!", len, ret); -#endif + if (ret >= 0 && (size_t)ret != len) client->mqtt_didnt_finish_write = 1; - } return ret; } -mqtt_wss_client mqtt_wss_new(const char *log_prefix, - mqtt_wss_log_callback_t log_callback, - msg_callback_fnc_t msg_callback, - void (*puback_callback)(uint16_t packet_id)) +mqtt_wss_client mqtt_wss_new( + msg_callback_fnc_t msg_callback, + void (*puback_callback)(uint16_t packet_id)) { - mqtt_wss_log_ctx_t log; - - log = mqtt_wss_log_ctx_create(log_prefix, log_callback); - if(!log) - return NULL; - SSL_library_init(); SSL_load_error_strings(); mqtt_wss_client client = callocz(1, sizeof(struct mqtt_wss_client_struct)); - if (!client) { - mws_error(log, "OOM alocating mqtt_wss_client"); - goto fail; - } spinlock_init(&client->stat_lock); client->msg_callback = msg_callback; client->puback_callback = puback_callback; - client->ws_client = ws_client_new(0, &client->target_host, log); + client->ws_client = ws_client_new(0, &client->target_host); if (!client->ws_client) { - mws_error(log, "Error creating ws_client"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Error creating ws_client"); goto fail_1; } - client->log = log; - #ifdef __APPLE__ if (pipe(client->write_notif_pipe)) { #else if (pipe2(client->write_notif_pipe, O_CLOEXEC /*| O_DIRECT*/)) { #endif - mws_error(log, "Couldn't create pipe"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Couldn't create pipe"); goto fail_2; } @@ -201,7 +173,6 @@ mqtt_wss_client mqtt_wss_new(const char *log_prefix, client->poll_fds[POLLFD_SOCKET].events = POLLIN; struct mqtt_ng_init settings = { - .log = log, .data_in = client->ws_client->buf_to_mqtt, .data_out_fnc = &mqtt_send_cb, .user_ctx = client, @@ -209,22 +180,14 @@ mqtt_wss_client mqtt_wss_new(const char *log_prefix, .puback_callback = puback_callback, .msg_callback = msg_callback }; - if ( (client->mqtt = mqtt_ng_init(&settings)) == NULL ) { - mws_error(log, "Error initializing internal MQTT client"); - goto fail_3; - } + client->mqtt = mqtt_ng_init(&settings); return client; -fail_3: - close(client->write_notif_pipe[PIPE_WRITE_END]); - close(client->write_notif_pipe[PIPE_READ_END]); fail_2: ws_client_destroy(client->ws_client); fail_1: freez(client); -fail: - mqtt_wss_log_ctx_destroy(log); return NULL; } @@ -265,30 +228,25 @@ void mqtt_wss_destroy(mqtt_wss_client client) if (client->sockfd > 0) close(client->sockfd); - mqtt_wss_log_ctx_destroy(client->log); freez(client); } static int cert_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { - SSL *ssl; - X509 *err_cert; - mqtt_wss_client client; - int err = 0, depth; - char *err_str; + int err = 0; - ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); - client = SSL_get_ex_data(ssl, 0); + SSL* ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + mqtt_wss_client client = SSL_get_ex_data(ssl, 0); // TODO handle depth as per https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html if (!preverify_ok) { err = X509_STORE_CTX_get_error(ctx); - depth = X509_STORE_CTX_get_error_depth(ctx); - err_cert = X509_STORE_CTX_get_current_cert(ctx); - err_str = X509_NAME_oneline(X509_get_subject_name(err_cert), NULL, 0); + int depth = X509_STORE_CTX_get_error_depth(ctx); + X509* err_cert = X509_STORE_CTX_get_current_cert(ctx); + char* err_str = X509_NAME_oneline(X509_get_subject_name(err_cert), NULL, 0); - mws_error(client->log, "verify error:num=%d:%s:depth=%d:%s", err, + nd_log(NDLS_DAEMON, NDLP_ERR, "verify error:num=%d:%s:depth=%d:%s", err, X509_verify_cert_error_string(err), depth, err_str); freez(err_str); @@ -298,7 +256,7 @@ static int cert_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) client->ssl_flags & MQTT_WSS_SSL_ALLOW_SELF_SIGNED) { preverify_ok = 1; - mws_error(client->log, "Self Signed Certificate Accepted as the connection was " + nd_log(NDLS_DAEMON, NDLP_ERR, "Self Signed Certificate Accepted as the connection was " "requested with MQTT_WSS_SSL_ALLOW_SELF_SIGNED"); } @@ -312,16 +270,14 @@ static int cert_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) #define HTTP_HDR_TERMINATOR "\x0D\x0A\x0D\x0A" #define HTTP_CODE_LEN 4 #define HTTP_REASON_MAX_LEN 512 -static int http_parse_reply(mqtt_wss_client client, rbuf_t buf) +static int http_parse_reply(rbuf_t buf) { - char *ptr; char http_code_s[4]; - int http_code; int idx; if (rbuf_memcmp_n(buf, PROXY_HTTP, strlen(PROXY_HTTP))) { if (rbuf_memcmp_n(buf, PROXY_HTTP10, strlen(PROXY_HTTP10))) { - mws_error(client->log, "http_proxy expected reply with \"" PROXY_HTTP "\" or \"" PROXY_HTTP10 "\""); + nd_log(NDLS_DAEMON, NDLP_ERR, "http_proxy expected reply with \"" PROXY_HTTP "\" or \"" PROXY_HTTP10 "\""); return 1; } } @@ -329,39 +285,37 @@ static int http_parse_reply(mqtt_wss_client client, rbuf_t buf) rbuf_bump_tail(buf, strlen(PROXY_HTTP)); if (!rbuf_pop(buf, http_code_s, 1) || http_code_s[0] != 0x20) { - mws_error(client->log, "http_proxy missing space after \"" PROXY_HTTP "\" or \"" PROXY_HTTP10 "\""); + nd_log(NDLS_DAEMON, NDLP_ERR, "http_proxy missing space after \"" PROXY_HTTP "\" or \"" PROXY_HTTP10 "\""); return 2; } if (!rbuf_pop(buf, http_code_s, HTTP_CODE_LEN)) { - mws_error(client->log, "http_proxy missing HTTP code"); + nd_log(NDLS_DAEMON, NDLP_ERR, "http_proxy missing HTTP code"); return 3; } for (int i = 0; i < HTTP_CODE_LEN - 1; i++) if (http_code_s[i] > 0x39 || http_code_s[i] < 0x30) { - mws_error(client->log, "http_proxy HTTP code non numeric"); + nd_log(NDLS_DAEMON, NDLP_ERR, "http_proxy HTTP code non numeric"); return 4; } http_code_s[HTTP_CODE_LEN - 1] = 0; - http_code = atoi(http_code_s); + int http_code = str2i(http_code_s); // TODO check if we ever have more headers here rbuf_find_bytes(buf, HTTP_ENDLINE, strlen(HTTP_ENDLINE), &idx); if (idx >= HTTP_REASON_MAX_LEN) { - mws_error(client->log, "http_proxy returned reason that is too long"); + nd_log(NDLS_DAEMON, NDLP_ERR, "http_proxy returned reason that is too long"); return 5; } if (http_code != 200) { - ptr = mallocz(idx + 1); - if (!ptr) - return 6; + char *ptr = mallocz(idx + 1); rbuf_pop(buf, ptr, idx); ptr[idx] = 0; - mws_error(client->log, "http_proxy returned error code %d \"%s\"", http_code, ptr); + nd_log(NDLS_DAEMON, NDLP_ERR, "http_proxy returned error code %d \"%s\"", http_code, ptr); freez(ptr); return 7; }/* else @@ -374,52 +328,11 @@ static int http_parse_reply(mqtt_wss_client client, rbuf_t buf) rbuf_bump_tail(buf, strlen(HTTP_HDR_TERMINATOR)); if (rbuf_bytes_available(buf)) { - mws_error(client->log, "http_proxy unexpected trailing bytes after end of HTTP hdr"); + nd_log(NDLS_DAEMON, NDLP_ERR, "http_proxy unexpected trailing bytes after end of HTTP hdr"); return 8; } - mws_debug(client->log, "http_proxy CONNECT succeeded"); - return 0; -} - -#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110 -static EVP_ENCODE_CTX *EVP_ENCODE_CTX_new(void) -{ - EVP_ENCODE_CTX *ctx = OPENSSL_malloc(sizeof(*ctx)); - - if (ctx != NULL) { - memset(ctx, 0, sizeof(*ctx)); - } - return ctx; -} -static void EVP_ENCODE_CTX_free(EVP_ENCODE_CTX *ctx) -{ - OPENSSL_free(ctx); - return; -} -#endif - -inline static int base64_encode_helper(unsigned char *out, int *outl, const unsigned char *in, int in_len) -{ - int len; - unsigned char *str = out; - EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new(); - EVP_EncodeInit(ctx); - EVP_EncodeUpdate(ctx, str, outl, in, in_len); - str += *outl; - EVP_EncodeFinal(ctx, str, &len); - *outl += len; - - str = out; - while(*str) { - if (*str != 0x0D && *str != 0x0A) - *out++ = *str++; - else - str++; - } - *out = 0; - - EVP_ENCODE_CTX_free(ctx); + nd_log(NDLS_DAEMON, NDLP_DEBUG, "http_proxy CONNECT succeeded"); return 0; } @@ -430,13 +343,12 @@ static int http_proxy_connect(mqtt_wss_client client) rbuf_t r_buf = rbuf_create(4096); if (!r_buf) return 1; - char *r_buf_ptr; size_t r_buf_linear_insert_capacity; poll_fd.fd = client->sockfd; poll_fd.events = POLLIN; - r_buf_ptr = rbuf_get_linear_insert_range(r_buf, &r_buf_linear_insert_capacity); + char *r_buf_ptr = rbuf_get_linear_insert_range(r_buf, &r_buf_linear_insert_capacity); snprintf(r_buf_ptr, r_buf_linear_insert_capacity,"%s %s:%d %s" HTTP_ENDLINE "Host: %s" HTTP_ENDLINE, PROXY_CONNECT, client->target_host, client->target_port, PROXY_HTTP, client->target_host); write(client->sockfd, r_buf_ptr, strlen(r_buf_ptr)); @@ -445,7 +357,7 @@ static int http_proxy_connect(mqtt_wss_client client) size_t creds_plain_len = strlen(client->proxy_uname) + strlen(client->proxy_passwd) + 2; char *creds_plain = mallocz(creds_plain_len); if (!creds_plain) { - mws_error(client->log, "OOM creds_plain"); + nd_log(NDLS_DAEMON, NDLP_ERR, "OOM creds_plain"); rc = 6; goto cleanup; } @@ -456,7 +368,7 @@ static int http_proxy_connect(mqtt_wss_client client) char *creds_base64 = mallocz(creds_base64_len + 1); if (!creds_base64) { freez(creds_plain); - mws_error(client->log, "OOM creds_base64"); + nd_log(NDLS_DAEMON, NDLP_ERR, "OOM creds_base64"); rc = 6; goto cleanup; } @@ -466,8 +378,7 @@ static int http_proxy_connect(mqtt_wss_client client) *ptr++ = ':'; strcpy(ptr, client->proxy_passwd); - int b64_len; - base64_encode_helper((unsigned char*)creds_base64, &b64_len, (unsigned char*)creds_plain, strlen(creds_plain)); + (void) netdata_base64_encode((unsigned char*)creds_base64, (unsigned char*)creds_plain, strlen(creds_plain)); freez(creds_plain); r_buf_ptr = rbuf_get_linear_insert_range(r_buf, &r_buf_linear_insert_capacity); @@ -482,13 +393,13 @@ static int http_proxy_connect(mqtt_wss_client client) // or timeout while ((rc = poll(&poll_fd, 1, 1000)) >= 0) { if (!rc) { - mws_error(client->log, "http_proxy timeout waiting reply from proxy server"); + nd_log(NDLS_DAEMON, NDLP_ERR, "http_proxy timeout waiting reply from proxy server"); rc = 2; goto cleanup; } r_buf_ptr = rbuf_get_linear_insert_range(r_buf, &r_buf_linear_insert_capacity); if (!r_buf_ptr) { - mws_error(client->log, "http_proxy read ring buffer full"); + nd_log(NDLS_DAEMON, NDLP_ERR, "http_proxy read ring buffer full"); rc = 3; goto cleanup; } @@ -496,20 +407,20 @@ static int http_proxy_connect(mqtt_wss_client client) if (errno == EWOULDBLOCK || errno == EAGAIN) { continue; } - mws_error(client->log, "http_proxy error reading from socket \"%s\"", strerror(errno)); + nd_log(NDLS_DAEMON, NDLP_ERR, "http_proxy error reading from socket \"%s\"", strerror(errno)); rc = 4; goto cleanup; } rbuf_bump_head(r_buf, rc); if (rbuf_find_bytes(r_buf, HTTP_HDR_TERMINATOR, strlen(HTTP_HDR_TERMINATOR), &rc)) { rc = 0; - if (http_parse_reply(client, r_buf)) + if (http_parse_reply(r_buf)) rc = 5; goto cleanup; } } - mws_error(client->log, "proxy negotiation poll error \"%s\"", strerror(errno)); + nd_log(NDLS_DAEMON, NDLP_ERR, "proxy negotiation poll error \"%s\"", strerror(errno)); rc = 5; cleanup: rbuf_free(r_buf); @@ -522,11 +433,11 @@ int mqtt_wss_connect( int port, struct mqtt_connect_params *mqtt_params, int ssl_flags, - struct mqtt_wss_proxy *proxy, + const struct mqtt_wss_proxy *proxy, bool *fallback_ipv4) { if (!mqtt_params) { - mws_error(client->log, "mqtt_params can't be null!"); + nd_log(NDLS_DAEMON, NDLP_ERR, "mqtt_params can't be null!"); return -1; } @@ -583,7 +494,7 @@ int mqtt_wss_connect( struct timeval timeout = { .tv_sec = 10, .tv_usec = 0 }; int fd = connect_to_this_ip46(IPPROTO_TCP, SOCK_STREAM, client->host, 0, port_str, &timeout, fallback_ipv4); if (fd < 0) { - mws_error(client->log, "Could not connect to remote endpoint \"%s\", port %d.\n", client->host, port); + nd_log(NDLS_DAEMON, NDLP_ERR, "Could not connect to remote endpoint \"%s\", port %d.\n", client->host, port); return -3; } @@ -598,12 +509,12 @@ int mqtt_wss_connect( int flag = 1; int result = setsockopt(client->sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)); if (result < 0) - mws_error(client->log, "Could not dissable NAGLE"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Could not dissable NAGLE"); client->poll_fds[POLLFD_SOCKET].fd = client->sockfd; if (fcntl(client->sockfd, F_SETFL, fcntl(client->sockfd, F_GETFL, 0) | O_NONBLOCK) == -1) { - mws_error(client->log, "Error setting O_NONBLOCK to TCP socket. \"%s\"", strerror(errno)); + nd_log(NDLS_DAEMON, NDLP_ERR, "Error setting O_NONBLOCK to TCP socket. \"%s\"", strerror(errno)); return -8; } @@ -619,7 +530,7 @@ int mqtt_wss_connect( SSL_library_init(); #else if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL) != 1) { - mws_error(client->log, "Failed to initialize SSL"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to initialize SSL"); return -1; }; #endif @@ -636,7 +547,7 @@ int mqtt_wss_connect( SSL_CTX_set_default_verify_paths(client->ssl_ctx); SSL_CTX_set_verify(client->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, cert_verify_callback); } else - mws_error(client->log, "SSL Certificate checking completely disabled!!!"); + nd_log(NDLS_DAEMON, NDLP_ERR, "SSL Certificate checking completely disabled!!!"); #ifdef MQTT_WSS_DEBUG if(client->ssl_ctx_keylog_cb) @@ -646,7 +557,7 @@ int mqtt_wss_connect( client->ssl = SSL_new(client->ssl_ctx); if (!(client->ssl_flags & MQTT_WSS_SSL_DONT_CHECK_CERTS)) { if (!SSL_set_ex_data(client->ssl, 0, client)) { - mws_error(client->log, "Could not SSL_set_ex_data"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Could not SSL_set_ex_data"); return -4; } } @@ -654,27 +565,27 @@ int mqtt_wss_connect( SSL_set_connect_state(client->ssl); if (!SSL_set_tlsext_host_name(client->ssl, client->target_host)) { - mws_error(client->log, "Error setting TLS SNI host"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Error setting TLS SNI host"); return -7; } result = SSL_connect(client->ssl); if (result != -1 && result != 1) { - mws_error(client->log, "SSL could not connect"); + nd_log(NDLS_DAEMON, NDLP_ERR, "SSL could not connect"); return -5; } if (result == -1) { int ec = SSL_get_error(client->ssl, result); if (ec != SSL_ERROR_WANT_READ && ec != SSL_ERROR_WANT_WRITE) { - mws_error(client->log, "Failed to start SSL connection"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to start SSL connection"); return -6; } } client->mqtt_keepalive = (mqtt_params->keep_alive ? mqtt_params->keep_alive : 400); - mws_info(client->log, "Going to connect using internal MQTT 5 implementation"); + nd_log(NDLS_DAEMON, NDLP_INFO, "Going to connect using internal MQTT 5 implementation"); struct mqtt_auth_properties auth; auth.client_id = (char*)mqtt_params->clientid; auth.client_id_free = NULL; @@ -694,7 +605,7 @@ int mqtt_wss_connect( int ret = mqtt_ng_connect(client->mqtt, &auth, mqtt_params->will_msg ? &lwt : NULL, 1, client->mqtt_keepalive); if (ret) { - mws_error(client->log, "Error generating MQTT connect"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Error generating MQTT connect"); return 1; } @@ -703,7 +614,7 @@ int mqtt_wss_connect( // wait till MQTT connection is established while (!client->mqtt_connected) { if(mqtt_wss_service(client, -1)) { - mws_error(client->log, "Error connecting to MQTT WSS server \"%s\", port %d.", host, port); + nd_log(NDLS_DAEMON, NDLP_ERR, "Error connecting to MQTT WSS server \"%s\", port %d.", host, port); return 2; } } @@ -716,14 +627,14 @@ int mqtt_wss_connect( #define NSEC_PER_MSEC 1000000ULL #define NSEC_PER_SEC 1000000000ULL -static inline uint64_t boottime_usec(mqtt_wss_client client) { +static uint64_t boottime_usec(void) { struct timespec ts; #if defined(__APPLE__) || defined(__FreeBSD__) if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) { #else if (clock_gettime(CLOCK_BOOTTIME, &ts) == -1) { #endif - mws_error(client->log, "clock_gettimte failed"); + nd_log(NDLS_DAEMON, NDLP_ERR, "clock_gettimte failed"); return 0; } return (uint64_t)ts.tv_sec * USEC_PER_SEC + (ts.tv_nsec % NSEC_PER_SEC) / NSEC_PER_USEC; @@ -732,7 +643,7 @@ static inline uint64_t boottime_usec(mqtt_wss_client client) { #define MWS_TIMED_OUT 1 #define MWS_ERROR 2 #define MWS_OK 0 -static inline const char *mqtt_wss_error_tos(int ec) +static const char *mqtt_wss_error_tos(int ec) { switch(ec) { case MWS_TIMED_OUT: @@ -745,13 +656,12 @@ static inline const char *mqtt_wss_error_tos(int ec) } -static inline int mqtt_wss_service_all(mqtt_wss_client client, int timeout_ms) +static int mqtt_wss_service_all(mqtt_wss_client client, int timeout_ms) { - uint64_t exit_by = boottime_usec(client) + (timeout_ms * NSEC_PER_MSEC); - uint64_t now; + uint64_t exit_by = boottime_usec() + (timeout_ms * NSEC_PER_MSEC); client->poll_fds[POLLFD_SOCKET].events |= POLLOUT; // TODO when entering mwtt_wss_service use out buffer size to arm POLLOUT while (rbuf_bytes_available(client->ws_client->buf_write)) { - now = boottime_usec(client); + const uint64_t now = boottime_usec(); if (now >= exit_by) return MWS_TIMED_OUT; if (mqtt_wss_service(client, exit_by - now)) @@ -762,15 +672,13 @@ static inline int mqtt_wss_service_all(mqtt_wss_client client, int timeout_ms) void mqtt_wss_disconnect(mqtt_wss_client client, int timeout_ms) { - int ret; - // block application from sending more MQTT messages client->mqtt_disconnecting = 1; // send whatever was left at the time of calling this function - ret = mqtt_wss_service_all(client, timeout_ms / 4); + int ret = mqtt_wss_service_all(client, timeout_ms / 4); if(ret) - mws_error(client->log, + nd_log(NDLS_DAEMON, NDLP_ERR, "Error while trying to send all remaining data in an attempt " "to gracefully disconnect! EC=%d Desc:\"%s\"", ret, @@ -782,7 +690,7 @@ void mqtt_wss_disconnect(mqtt_wss_client client, int timeout_ms) ret = mqtt_wss_service_all(client, timeout_ms / 4); if(ret) - mws_error(client->log, + nd_log(NDLS_DAEMON, NDLP_ERR, "Error while trying to send MQTT disconnect message in an attempt " "to gracefully disconnect! EC=%d Desc:\"%s\"", ret, @@ -795,7 +703,7 @@ void mqtt_wss_disconnect(mqtt_wss_client client, int timeout_ms) if(ret) { // Some MQTT/WSS servers will close socket on receipt of MQTT disconnect and // do not wait for WebSocket to be closed properly - mws_warn(client->log, + nd_log(NDLS_DAEMON, NDLP_WARNING, "Error while trying to send WebSocket disconnect message in an attempt " "to gracefully disconnect! EC=%d Desc:\"%s\".", ret, @@ -810,22 +718,19 @@ void mqtt_wss_disconnect(mqtt_wss_client client, int timeout_ms) client->sockfd = -1; } -static inline void mqtt_wss_wakeup(mqtt_wss_client client) +static void mqtt_wss_wakeup(mqtt_wss_client client) { -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "mqtt_wss_wakup - forcing wake up of main loop"); -#endif write(client->write_notif_pipe[PIPE_WRITE_END], " ", 1); } #define THROWAWAY_BUF_SIZE 32 char throwaway[THROWAWAY_BUF_SIZE]; -static inline void util_clear_pipe(int fd) +static void util_clear_pipe(int fd) { (void)read(fd, throwaway, THROWAWAY_BUF_SIZE); } -static inline void set_socket_pollfds(mqtt_wss_client client, int ssl_ret) { +static void set_socket_pollfds(mqtt_wss_client client, int ssl_ret) { if (ssl_ret == SSL_ERROR_WANT_WRITE) client->poll_fds[POLLFD_SOCKET].events |= POLLOUT; if (ssl_ret == SSL_ERROR_WANT_READ) @@ -836,27 +741,25 @@ static int handle_mqtt_internal(mqtt_wss_client client) { int rc = mqtt_ng_sync(client->mqtt); if (rc) { - mws_error(client->log, "mqtt_ng_sync returned %d != 0", rc); + nd_log(NDLS_DAEMON, NDLP_ERR, "mqtt_ng_sync returned %d != 0", rc); client->mqtt_connected = 0; return 1; } return 0; } -#define SEC_TO_MSEC 1000 -static inline long long int t_till_next_keepalive_ms(mqtt_wss_client client) +static int t_till_next_keepalive_ms(mqtt_wss_client client) { time_t last_send = mqtt_ng_last_send_time(client->mqtt); - long long int next_mqtt_keep_alive = (last_send * SEC_TO_MSEC) - + (client->mqtt_keepalive * (SEC_TO_MSEC * 0.75 /* SEND IN ADVANCE */)); - return(next_mqtt_keep_alive - (time(NULL) * SEC_TO_MSEC)); + time_t next_mqtt_keep_alive = last_send + client->mqtt_keepalive * 0.75; + return ((next_mqtt_keep_alive - now_realtime_sec()) * MSEC_PER_SEC); } #ifdef MQTT_WSS_CPUSTATS -static inline uint64_t mqtt_wss_now_usec(mqtt_wss_client client) { +static uint64_t mqtt_wss_now_usec(void) { struct timespec ts; if(clock_gettime(CLOCK_MONOTONIC, &ts) == -1) { - mws_error(client->log, "clock_gettime(CLOCK_MONOTONIC, ×pec) failed."); + nd_log(NDLS_DAEMON, NDLP_ERR, "clock_gettime(CLOCK_MONOTONIC, ×pec) failed."); return 0; } return (uint64_t)ts.tv_sec * USEC_PER_SEC + (ts.tv_nsec % NSEC_PER_SEC) / NSEC_PER_USEC; @@ -871,63 +774,51 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms) int send_keepalive = 0; #ifdef MQTT_WSS_CPUSTATS - uint64_t t1,t2; - t1 = mqtt_wss_now_usec(client); -#endif - -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, ">>>>> mqtt_wss_service <<<<<"); - mws_debug(client->log, "Waiting for events: %s%s%s", - (client->poll_fds[POLLFD_SOCKET].events & POLLIN) ? "SOCKET_POLLIN " : "", - (client->poll_fds[POLLFD_SOCKET].events & POLLOUT) ? "SOCKET_POLLOUT " : "", - (client->poll_fds[POLLFD_PIPE].events & POLLIN) ? "PIPE_POLLIN" : "" ); + uint64_t t2; + uint64_t t1 = mqtt_wss_now_usec(); #endif // Check user requested TO doesn't interfere with MQTT keep alives - long long int till_next_keep_alive = t_till_next_keepalive_ms(client); - if (client->mqtt_connected && (timeout_ms < 0 || timeout_ms >= till_next_keep_alive)) { - #ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "Shortening Timeout requested %d to %lld to ensure keep-alive can be sent", timeout_ms, till_next_keep_alive); - #endif - timeout_ms = till_next_keep_alive; - send_keepalive = 1; + if (!ping_timeout) { + int till_next_keep_alive = t_till_next_keepalive_ms(client); + if (till_next_keep_alive < 0) + till_next_keep_alive = 0; + if (client->mqtt_connected && (timeout_ms < 0 || timeout_ms >= till_next_keep_alive)) { + timeout_ms = till_next_keep_alive; + send_keepalive = 1; + } } #ifdef MQTT_WSS_CPUSTATS - t2 = mqtt_wss_now_usec(client); + t2 = mqtt_wss_now_usec(); client->stats.time_keepalive += t2 - t1; #endif if ((ret = poll(client->poll_fds, 2, timeout_ms >= 0 ? timeout_ms : -1)) < 0) { if (errno == EINTR) { - mws_warn(client->log, "poll interrupted by EINTR"); + nd_log(NDLS_DAEMON, NDLP_WARNING, "poll interrupted by EINTR"); return 0; } - mws_error(client->log, "poll error \"%s\"", strerror(errno)); + nd_log(NDLS_DAEMON, NDLP_ERR, "poll error \"%s\"", strerror(errno)); return -2; } -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "Poll events happened: %s%s%s%s", - (client->poll_fds[POLLFD_SOCKET].revents & POLLIN) ? "SOCKET_POLLIN " : "", - (client->poll_fds[POLLFD_SOCKET].revents & POLLOUT) ? "SOCKET_POLLOUT " : "", - (client->poll_fds[POLLFD_PIPE].revents & POLLIN) ? "PIPE_POLLIN " : "", - (!ret) ? "POLL_TIMEOUT" : ""); -#endif - #ifdef MQTT_WSS_CPUSTATS - t1 = mqtt_wss_now_usec(client); + t1 = mqtt_wss_now_usec(); #endif if (ret == 0) { + time_t now = now_realtime_sec(); if (send_keepalive) { // otherwise we shortened the timeout ourselves to take care of // MQTT keep alives -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "Forcing MQTT Ping/keep-alive"); -#endif mqtt_ng_ping(client->mqtt); + ping_timeout = now + PING_TIMEOUT; } else { + if (ping_timeout && ping_timeout < now) { + disconnect_req = ACLK_PING_TIMEOUT; + ping_timeout = 0; + } // if poll timed out and user requested timeout was being used // return here let user do his work and he will call us back soon return 0; @@ -935,7 +826,7 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms) } #ifdef MQTT_WSS_CPUSTATS - t2 = mqtt_wss_now_usec(client); + t2 = mqtt_wss_now_usec(); client->stats.time_keepalive += t2 - t1; #endif @@ -943,9 +834,6 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms) if ((ptr = rbuf_get_linear_insert_range(client->ws_client->buf_read, &size))) { if((ret = SSL_read(client->ssl, ptr, size)) > 0) { -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "SSL_Read: Read %d.", ret); -#endif spinlock_lock(&client->stat_lock); client->stats.bytes_rx += ret; spinlock_unlock(&client->stat_lock); @@ -953,22 +841,19 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms) } else { int errnobkp = errno; ret = SSL_get_error(client->ssl, ret); -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "Read Err: %s", util_openssl_ret_err(ret)); -#endif set_socket_pollfds(client, ret); if (ret != SSL_ERROR_WANT_READ && ret != SSL_ERROR_WANT_WRITE) { - mws_error(client->log, "SSL_read error: %d %s", ret, util_openssl_ret_err(ret)); + nd_log(NDLS_DAEMON, NDLP_ERR, "SSL_read error: %d %s", ret, util_openssl_ret_err(ret)); if (ret == SSL_ERROR_SYSCALL) - mws_error(client->log, "SSL_read SYSCALL errno: %d %s", errnobkp, strerror(errnobkp)); + nd_log(NDLS_DAEMON, NDLP_ERR, "SSL_read SYSCALL errno: %d %s", errnobkp, strerror(errnobkp)); return MQTT_WSS_ERR_CONN_DROP; } } } #ifdef MQTT_WSS_CPUSTATS - t1 = mqtt_wss_now_usec(client); + t1 = mqtt_wss_now_usec(); client->stats.time_read_socket += t1 - t2; #endif @@ -976,18 +861,20 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms) switch(ret) { case WS_CLIENT_PROTOCOL_ERROR: return MQTT_WSS_ERR_PROTO_WS; + case WS_CLIENT_NEED_MORE_BYTES: -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "WSCLIENT WANT READ"); -#endif client->poll_fds[POLLFD_SOCKET].events |= POLLIN; break; + case WS_CLIENT_CONNECTION_CLOSED: return MQTT_WSS_ERR_CONN_DROP; + + default: + return MQTT_WSS_ERR_PROTO_WS; } #ifdef MQTT_WSS_CPUSTATS - t2 = mqtt_wss_now_usec(client); + t2 = mqtt_wss_now_usec(); client->stats.time_process_websocket += t2 - t1; #endif @@ -1002,18 +889,12 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms) } #ifdef MQTT_WSS_CPUSTATS - t1 = mqtt_wss_now_usec(client); + t1 = mqtt_wss_now_usec(); client->stats.time_process_mqtt += t1 - t2; #endif if ((ptr = rbuf_get_linear_read_range(client->ws_client->buf_write, &size))) { -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "Have data to write to SSL"); -#endif if ((ret = SSL_write(client->ssl, ptr, size)) > 0) { -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "SSL_Write: Written %d of avail %d.", ret, size); -#endif spinlock_lock(&client->stat_lock); client->stats.bytes_tx += ret; spinlock_unlock(&client->stat_lock); @@ -1021,15 +902,12 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms) } else { int errnobkp = errno; ret = SSL_get_error(client->ssl, ret); -#ifdef DEBUG_ULTRA_VERBOSE - mws_debug(client->log, "Write Err: %s", util_openssl_ret_err(ret)); -#endif set_socket_pollfds(client, ret); if (ret != SSL_ERROR_WANT_READ && ret != SSL_ERROR_WANT_WRITE) { - mws_error(client->log, "SSL_write error: %d %s", ret, util_openssl_ret_err(ret)); + nd_log(NDLS_DAEMON, NDLP_ERR, "SSL_write error: %d %s", ret, util_openssl_ret_err(ret)); if (ret == SSL_ERROR_SYSCALL) - mws_error(client->log, "SSL_write SYSCALL errno: %d %s", errnobkp, strerror(errnobkp)); + nd_log(NDLS_DAEMON, NDLP_ERR, "SSL_write SYSCALL errno: %d %s", errnobkp, strerror(errnobkp)); return MQTT_WSS_ERR_CONN_DROP; } } @@ -1039,7 +917,7 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms) util_clear_pipe(client->write_notif_pipe[PIPE_READ_END]); #ifdef MQTT_WSS_CPUSTATS - t2 = mqtt_wss_now_usec(client); + t2 = mqtt_wss_now_usec(); client->stats.time_write_socket += t2 - t1; #endif @@ -1056,12 +934,12 @@ int mqtt_wss_publish5(mqtt_wss_client client, uint16_t *packet_id) { if (client->mqtt_disconnecting) { - mws_error(client->log, "mqtt_wss is disconnecting can't publish"); + nd_log(NDLS_DAEMON, NDLP_ERR, "mqtt_wss is disconnecting can't publish"); return 1; } if (!client->mqtt_connected) { - mws_error(client->log, "MQTT is offline. Can't send message."); + nd_log(NDLS_DAEMON, NDLP_ERR, "MQTT is offline. Can't send message."); return 1; } uint8_t mqtt_flags = 0; @@ -1072,7 +950,7 @@ int mqtt_wss_publish5(mqtt_wss_client client, int rc = mqtt_ng_publish(client->mqtt, topic, topic_free, msg, msg_free, msg_len, mqtt_flags, packet_id); if (rc == MQTT_NG_MSGGEN_MSG_TOO_BIG) - return MQTT_WSS_ERR_TOO_BIG_FOR_SERVER; + return MQTT_WSS_ERR_MSG_TOO_BIG; mqtt_wss_wakeup(client); @@ -1083,12 +961,12 @@ int mqtt_wss_subscribe(mqtt_wss_client client, char *topic, int max_qos_level) { (void)max_qos_level; //TODO now hardcoded if (!client->mqtt_connected) { - mws_error(client->log, "MQTT is offline. Can't subscribe."); + nd_log(NDLS_DAEMON, NDLP_ERR, "MQTT is offline. Can't subscribe."); return 1; } if (client->mqtt_disconnecting) { - mws_error(client->log, "mqtt_wss is disconnecting can't subscribe"); + nd_log(NDLS_DAEMON, NDLP_ERR, "mqtt_wss is disconnecting can't subscribe"); return 1; } diff --git a/src/aclk/mqtt_websockets/mqtt_wss_client.h b/src/aclk/mqtt_websockets/mqtt_wss_client.h index f0bdce98b..2fd94075d 100644 --- a/src/aclk/mqtt_websockets/mqtt_wss_client.h +++ b/src/aclk/mqtt_websockets/mqtt_wss_client.h @@ -1,56 +1,36 @@ -// SPDX-License-Identifier: GPL-3.0-only -// Copyright (C) 2020 Timotej Šiškovič +// SPDX-License-Identifier: GPL-3.0-or-later #ifndef MQTT_WSS_CLIENT_H #define MQTT_WSS_CLIENT_H -#include <stdbool.h> -#include <stdint.h> -#include <stddef.h> //size_t - -#include "mqtt_wss_log.h" #include "common_public.h" -// All OK call me at your earliest convinience -#define MQTT_WSS_OK 0 -/* All OK, poll timeout you requested when calling mqtt_wss_service expired - you might want to know if timeout - * happened or we got some data or handle same as MQTT_WSS_OK - */ -#define MQTT_WSS_OK_TO 1 -// Connection was closed by remote -#define MQTT_WSS_ERR_CONN_DROP -1 -// Error in MQTT protocol (e.g. malformed packet) -#define MQTT_WSS_ERR_PROTO_MQTT -2 -// Error in WebSocket protocol (e.g. malformed packet) -#define MQTT_WSS_ERR_PROTO_WS -3 - -#define MQTT_WSS_ERR_TX_BUF_TOO_SMALL -4 -#define MQTT_WSS_ERR_RX_BUF_TOO_SMALL -5 - -#define MQTT_WSS_ERR_TOO_BIG_FOR_SERVER -6 -// if client was initialized with MQTT 3 but MQTT 5 feature -// was requested by user of library -#define MQTT_WSS_ERR_CANT_DO -8 + +#define MQTT_WSS_OK 0 // All OK call me at your earliest convinience +#define MQTT_WSS_OK_TO 1 // All OK, poll timeout you requested when calling mqtt_wss_service expired + //you might want to know if timeout + //happened or we got some data or handle same as MQTT_WSS_OK +#define MQTT_WSS_ERR_CONN_DROP -1 // Connection was closed by remote +#define MQTT_WSS_ERR_PROTO_MQTT -2 // Error in MQTT protocol (e.g. malformed packet) +#define MQTT_WSS_ERR_PROTO_WS -3 // Error in WebSocket protocol (e.g. malformed packet) +#define MQTT_WSS_ERR_MSG_TOO_BIG -6 // Message size too big for server +#define MQTT_WSS_ERR_CANT_DO -8 // if client was initialized with MQTT 3 but MQTT 5 feature + // was requested by user of library typedef struct mqtt_wss_client_struct *mqtt_wss_client; typedef void (*msg_callback_fnc_t)(const char *topic, const void *msg, size_t msglen, int qos); + /* Creates new instance of MQTT over WSS. Doesn't start connection. - * @param log_prefix this is prefix to be used when logging to discern between multiple - * mqtt_wss instances. Can be NULL. - * @param log_callback is function pointer to fnc to be called when mqtt_wss wants - * to log. This allows plugging this library into your own logging system/solution. - * If NULL STDOUT/STDERR will be used. * @param msg_callback is function pointer to function which will be called * when application level message arrives from broker (for subscribed topics). * Can be NULL if you are not interested about incoming messages. * @param puback_callback is function pointer to function to be called when QOS1 Publish * is acknowledged by server */ -mqtt_wss_client mqtt_wss_new(const char *log_prefix, - mqtt_wss_log_callback_t log_callback, - msg_callback_fnc_t msg_callback, - void (*puback_callback)(uint16_t packet_id)); +mqtt_wss_client mqtt_wss_new( + msg_callback_fnc_t msg_callback, + void (*puback_callback)(uint16_t packet_id)); void mqtt_wss_set_max_buf_size(mqtt_wss_client client, size_t size); @@ -76,7 +56,7 @@ int mqtt_wss_connect( int port, struct mqtt_connect_params *mqtt_params, int ssl_flags, - struct mqtt_wss_proxy *proxy, + const struct mqtt_wss_proxy *proxy, bool *fallback_ipv4); int mqtt_wss_service(mqtt_wss_client client, int timeout_ms); void mqtt_wss_disconnect(mqtt_wss_client client, int timeout_ms); diff --git a/src/aclk/mqtt_websockets/mqtt_wss_log.c b/src/aclk/mqtt_websockets/mqtt_wss_log.c deleted file mode 100644 index e5da76fcf..000000000 --- a/src/aclk/mqtt_websockets/mqtt_wss_log.c +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only - -#include <stdlib.h> -#include <stdarg.h> -#include <string.h> -#include <stdio.h> - -#include "mqtt_wss_log.h" -#include "common_internal.h" - -struct mqtt_wss_log_ctx { - mqtt_wss_log_callback_t extern_log_fnc; - char *ctx_prefix; - char *buffer; - char *buffer_w_ptr; - size_t buffer_bytes_avail; -}; - -#define LOG_BUFFER_SIZE 1024 * 4 -#define LOG_CTX_PREFIX_SEV_STR " : " -#define LOG_CTX_PREFIX_LIMIT 15 -#define LOG_CTX_PREFIX_LIMIT_STR (LOG_CTX_PREFIX_LIMIT - (2 + strlen(LOG_CTX_PREFIX_SEV_STR))) // with [] characters and affixed ' ' it is total 15 chars -#if (LOG_CTX_PREFIX_LIMIT * 10) > LOG_BUFFER_SIZE -#error "LOG_BUFFER_SIZE too small" -#endif -mqtt_wss_log_ctx_t mqtt_wss_log_ctx_create(const char *ctx_prefix, mqtt_wss_log_callback_t log_callback) -{ - mqtt_wss_log_ctx_t ctx = callocz(1, sizeof(struct mqtt_wss_log_ctx)); - if(!ctx) - return NULL; - - if(log_callback) { - ctx->extern_log_fnc = log_callback; - ctx->buffer = callocz(1, LOG_BUFFER_SIZE); - if(!ctx->buffer) - goto cleanup; - - ctx->buffer_w_ptr = ctx->buffer; - if(ctx_prefix) { - *(ctx->buffer_w_ptr++) = '['; - strncpy(ctx->buffer_w_ptr, ctx_prefix, LOG_CTX_PREFIX_LIMIT_STR); - ctx->buffer_w_ptr += strnlen(ctx_prefix, LOG_CTX_PREFIX_LIMIT_STR); - *(ctx->buffer_w_ptr++) = ']'; - } - strcpy(ctx->buffer_w_ptr, LOG_CTX_PREFIX_SEV_STR); - ctx->buffer_w_ptr += strlen(LOG_CTX_PREFIX_SEV_STR); - // no term '\0' -> calloc is used - - ctx->buffer_bytes_avail = LOG_BUFFER_SIZE - strlen(ctx->buffer); - - return ctx; - } - - if(ctx_prefix) { - ctx->ctx_prefix = strndup(ctx_prefix, LOG_CTX_PREFIX_LIMIT_STR); - if(!ctx->ctx_prefix) - goto cleanup; - } - - return ctx; - -cleanup: - freez(ctx); - return NULL; -} - -void mqtt_wss_log_ctx_destroy(mqtt_wss_log_ctx_t ctx) -{ - freez(ctx->ctx_prefix); - freez(ctx->buffer); - freez(ctx); -} - -static inline char severity_to_c(int severity) -{ - switch (severity) { - case MQTT_WSS_LOG_FATAL: - return 'F'; - case MQTT_WSS_LOG_ERROR: - return 'E'; - case MQTT_WSS_LOG_WARN: - return 'W'; - case MQTT_WSS_LOG_INFO: - return 'I'; - case MQTT_WSS_LOG_DEBUG: - return 'D'; - default: - return '?'; - } -} - -void mws_log(int severity, mqtt_wss_log_ctx_t ctx, const char *fmt, va_list args) -{ - size_t size; - - if(ctx->extern_log_fnc) { - size = vsnprintf(ctx->buffer_w_ptr, ctx->buffer_bytes_avail, fmt, args); - *(ctx->buffer_w_ptr - 3) = severity_to_c(severity); - - ctx->extern_log_fnc(severity, ctx->buffer); - - if(size >= ctx->buffer_bytes_avail) - mws_error(ctx, "Last message of this type was truncated! Consider what you log or increase LOG_BUFFER_SIZE if really needed."); - - return; - } - - if(ctx->ctx_prefix) - printf("[%s] ", ctx->ctx_prefix); - - printf("%c: ", severity_to_c(severity)); - - vprintf(fmt, args); - putchar('\n'); -} - -#define DEFINE_MWS_SEV_FNC(severity_fncname, severity) \ -void mws_ ## severity_fncname(mqtt_wss_log_ctx_t ctx, const char *fmt, ...) \ -{ \ - va_list args; \ - va_start(args, fmt); \ - mws_log(severity, ctx, fmt, args); \ - va_end(args); \ -} - -DEFINE_MWS_SEV_FNC(fatal, MQTT_WSS_LOG_FATAL) -DEFINE_MWS_SEV_FNC(error, MQTT_WSS_LOG_ERROR) -DEFINE_MWS_SEV_FNC(warn, MQTT_WSS_LOG_WARN ) -DEFINE_MWS_SEV_FNC(info, MQTT_WSS_LOG_INFO ) -DEFINE_MWS_SEV_FNC(debug, MQTT_WSS_LOG_DEBUG) diff --git a/src/aclk/mqtt_websockets/mqtt_wss_log.h b/src/aclk/mqtt_websockets/mqtt_wss_log.h deleted file mode 100644 index 6ae60d870..000000000 --- a/src/aclk/mqtt_websockets/mqtt_wss_log.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright: SPDX-License-Identifier: GPL-3.0-only - -#ifndef MQTT_WSS_LOG_H -#define MQTT_WSS_LOG_H - -typedef enum mqtt_wss_log_type { - MQTT_WSS_LOG_DEBUG = 0x01, - MQTT_WSS_LOG_INFO = 0x02, - MQTT_WSS_LOG_WARN = 0x03, - MQTT_WSS_LOG_ERROR = 0x81, - MQTT_WSS_LOG_FATAL = 0x88 -} mqtt_wss_log_type_t; - -typedef void (*mqtt_wss_log_callback_t)(mqtt_wss_log_type_t, const char*); - -typedef struct mqtt_wss_log_ctx *mqtt_wss_log_ctx_t; - -/** Creates logging context with optional prefix and optional callback - * @param ctx_prefix String to be prefixed to every log message. - * This is useful if multiple clients are instantiated to be able to - * know which one this message belongs to. Can be `NULL` for no prefix. - * @param log_callback Callback to be called instead of logging to - * `STDOUT` or `STDERR` (if debug enabled otherwise silent). Callback has to be - * pointer to function of `void function(mqtt_wss_log_type_t, const char*)` type. - * If `NULL` default will be used (silent or STDERR/STDOUT). - * @return mqtt_wss_log_ctx_t or `NULL` on error */ -mqtt_wss_log_ctx_t mqtt_wss_log_ctx_create(const char *ctx_prefix, mqtt_wss_log_callback_t log_callback); - -/** Destroys logging context and cleans up the memory - * @param ctx Context to destroy */ -void mqtt_wss_log_ctx_destroy(mqtt_wss_log_ctx_t ctx); - -void mws_fatal(mqtt_wss_log_ctx_t ctx, const char *fmt, ...); -void mws_error(mqtt_wss_log_ctx_t ctx, const char *fmt, ...); -void mws_warn (mqtt_wss_log_ctx_t ctx, const char *fmt, ...); -void mws_info (mqtt_wss_log_ctx_t ctx, const char *fmt, ...); -void mws_debug(mqtt_wss_log_ctx_t ctx, const char *fmt, ...); - -#endif /* MQTT_WSS_LOG_H */ diff --git a/src/aclk/mqtt_websockets/ws_client.c b/src/aclk/mqtt_websockets/ws_client.c index a6b9b23f3..99ea266c8 100644 --- a/src/aclk/mqtt_websockets/ws_client.c +++ b/src/aclk/mqtt_websockets/ws_client.c @@ -1,103 +1,43 @@ -// Copyright (C) 2020 Timotej Šiškovič -// SPDX-License-Identifier: GPL-3.0-only -// -// 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, version 3. -// -// 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/>. - -#include <fcntl.h> -#include <unistd.h> -#include <string.h> -#include <errno.h> -#include <ctype.h> - -#include <openssl/evp.h> +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" #include "ws_client.h" #include "common_internal.h" -#ifdef MQTT_WEBSOCKETS_DEBUG -#include "../c-rbuf/src/ringbuffer_internal.h" -#endif - -#define UNIT_LOG_PREFIX "ws_client: " -#define FATAL(fmt, ...) mws_fatal(client->log, UNIT_LOG_PREFIX fmt, ##__VA_ARGS__) -#define ERROR(fmt, ...) mws_error(client->log, UNIT_LOG_PREFIX fmt, ##__VA_ARGS__) -#define WARN(fmt, ...) mws_warn (client->log, UNIT_LOG_PREFIX fmt, ##__VA_ARGS__) -#define INFO(fmt, ...) mws_info (client->log, UNIT_LOG_PREFIX fmt, ##__VA_ARGS__) -#define DEBUG(fmt, ...) mws_debug(client->log, UNIT_LOG_PREFIX fmt, ##__VA_ARGS__) - const char *websocket_upgrage_hdr = "GET /mqtt HTTP/1.1\x0D\x0A" "Host: %s\x0D\x0A" "Upgrade: websocket\x0D\x0A" "Connection: Upgrade\x0D\x0A" "Sec-WebSocket-Key: %s\x0D\x0A" - "Origin: http://example.com\x0D\x0A" + "Origin: \x0D\x0A" "Sec-WebSocket-Protocol: mqtt\x0D\x0A" "Sec-WebSocket-Version: 13\x0D\x0A\x0D\x0A"; const char *mqtt_protoid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; #define DEFAULT_RINGBUFFER_SIZE (1024*128) -#define ENTROPY_SOURCE "/dev/urandom" -ws_client *ws_client_new(size_t buf_size, char **host, mqtt_wss_log_ctx_t log) -{ - ws_client *client; +ws_client *ws_client_new(size_t buf_size, char **host) +{ if(!host) return NULL; - client = callocz(1, sizeof(ws_client)); - if (!client) - return NULL; - + ws_client *client = callocz(1, sizeof(ws_client)); client->host = host; - client->log = log; - client->buf_read = rbuf_create(buf_size ? buf_size : DEFAULT_RINGBUFFER_SIZE); - if (!client->buf_read) - goto cleanup; - client->buf_write = rbuf_create(buf_size ? buf_size : DEFAULT_RINGBUFFER_SIZE); - if (!client->buf_write) - goto cleanup_1; - client->buf_to_mqtt = rbuf_create(buf_size ? buf_size : DEFAULT_RINGBUFFER_SIZE); - if (!client->buf_to_mqtt) - goto cleanup_2; - - client->entropy_fd = open(ENTROPY_SOURCE, O_RDONLY | O_CLOEXEC); - if (client->entropy_fd < 1) { - ERROR("Error opening entropy source \"" ENTROPY_SOURCE "\". Reason: \"%s\"", strerror(errno)); - goto cleanup_3; - } return client; - -cleanup_3: - rbuf_free(client->buf_to_mqtt); -cleanup_2: - rbuf_free(client->buf_write); -cleanup_1: - rbuf_free(client->buf_read); -cleanup: - freez(client); - return NULL; } void ws_client_free_headers(ws_client *client) { struct http_header *ptr = client->hs.headers; - struct http_header *tmp; while (ptr) { - tmp = ptr; + struct http_header *tmp = ptr; ptr = ptr->next; freez(tmp); } @@ -112,7 +52,6 @@ void ws_client_destroy(ws_client *client) ws_client_free_headers(client); freez(client->hs.nonce_reply); freez(client->hs.http_reply_msg); - close(client->entropy_fd); rbuf_free(client->buf_read); rbuf_free(client->buf_write); rbuf_free(client->buf_to_mqtt); @@ -141,7 +80,7 @@ void ws_client_reset(ws_client *client) int ws_client_add_http_header(ws_client *client, struct http_header *hdr) { if (client->hs.hdr_count > MAX_HTTP_HDR_COUNT) { - ERROR("Too many HTTP response header fields"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Too many HTTP response header fields"); return -1; } @@ -156,7 +95,7 @@ int ws_client_add_http_header(ws_client *client, struct http_header *hdr) return 0; } -int ws_client_want_write(ws_client *client) +int ws_client_want_write(const ws_client *client) { return rbuf_bytes_available(client->buf_write); } @@ -165,78 +104,89 @@ int ws_client_want_write(ws_client *client) #define TEMP_BUF_SIZE 4096 int ws_client_start_handshake(ws_client *client) { - nd_uuid_t nonce; + unsigned char nonce[WEBSOCKET_NONCE_SIZE]; char nonce_b64[256]; char second[TEMP_BUF_SIZE]; unsigned int md_len; - unsigned char *digest; + unsigned char digest[EVP_MAX_MD_SIZE]; // EVP_MAX_MD_SIZE ensures enough space EVP_MD_CTX *md_ctx; const EVP_MD *md; + int rc = 1; if(!client->host || !*client->host) { - ERROR("Hostname has not been set. We should not be able to come here!"); - return 1; - } - - uuid_generate_random(nonce); - EVP_EncodeBlock((unsigned char *)nonce_b64, (const unsigned char *)nonce, WEBSOCKET_NONCE_SIZE); - snprintf(second, TEMP_BUF_SIZE, websocket_upgrage_hdr, *client->host, nonce_b64); - - if(rbuf_bytes_free(client->buf_write) < strlen(second)) { - ERROR("Write buffer capacity too low."); + nd_log(NDLS_DAEMON, NDLP_ERR, "Hostname has not been set. We should not be able to come here!"); return 1; } - rbuf_push(client->buf_write, second, strlen(second)); - client->state = WS_HANDSHAKE; - - //Calculating expected Sec-WebSocket-Accept reply - snprintf(second, TEMP_BUF_SIZE, "%s%s", nonce_b64, mqtt_protoid); + // Generate a random 16-byte nonce + os_random_bytes(nonce, sizeof(nonce)); + // Initialize the digest context #if (OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110) md_ctx = EVP_MD_CTX_create(); #else md_ctx = EVP_MD_CTX_new(); #endif if (md_ctx == NULL) { - ERROR("Cant create EVP_MD Context"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Can't create EVP_MD context"); return 1; } - md = EVP_get_digestbyname("sha1"); + md = EVP_sha1(); // Use SHA-1 for WebSocket handshake if (!md) { - ERROR("Unknown message digest"); - return 1; + nd_log(NDLS_DAEMON, NDLP_ERR, "Unknown message digest SHA-1"); + goto exit_with_error; } - if ((digest = (unsigned char *)OPENSSL_malloc(EVP_MD_size(EVP_sha256()))) == NULL) { - ERROR("Cant alloc digest"); - return 1; + (void) netdata_base64_encode((unsigned char *) nonce_b64, nonce, WEBSOCKET_NONCE_SIZE); + + // Format and push the upgrade header to the write buffer + size_t bytes = snprintf(second, TEMP_BUF_SIZE, websocket_upgrage_hdr, *client->host, nonce_b64); + if(rbuf_bytes_free(client->buf_write) < bytes) { + nd_log(NDLS_DAEMON, NDLP_ERR, "Write buffer capacity too low."); + goto exit_with_error; } + rbuf_push(client->buf_write, second, bytes); + + client->state = WS_HANDSHAKE; - EVP_DigestInit_ex(md_ctx, md, NULL); - EVP_DigestUpdate(md_ctx, second, strlen(second)); - EVP_DigestFinal_ex(md_ctx, digest, &md_len); + // Create the expected Sec-WebSocket-Accept value + bytes = snprintf(second, TEMP_BUF_SIZE, "%s%s", nonce_b64, mqtt_protoid); - EVP_EncodeBlock((unsigned char *)nonce_b64, digest, (int) md_len); + if (!EVP_DigestInit_ex(md_ctx, md, NULL)) { + nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to initialize digest context"); + goto exit_with_error; + } + + if (!EVP_DigestUpdate(md_ctx, second, bytes)) { + nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to update digest"); + goto exit_with_error; + } + + if (!EVP_DigestFinal_ex(md_ctx, digest, &md_len)) { + nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to finalize digest"); + goto exit_with_error; + } + + (void) netdata_base64_encode((unsigned char *) nonce_b64, digest, md_len); freez(client->hs.nonce_reply); client->hs.nonce_reply = strdupz(nonce_b64); + rc = 0; - OPENSSL_free(digest); - +exit_with_error: #if (OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110) EVP_MD_CTX_destroy(md_ctx); #else EVP_MD_CTX_free(md_ctx); #endif - return 0; + return rc; } #define BUF_READ_MEMCMP_CONST(const, err) \ if (rbuf_memcmp_n(client->buf_read, const, strlen(const))) { \ - ERROR(err); \ + nd_log(NDLS_DAEMON, NDLP_ERR, err); \ rbuf_flush(client->buf_read); \ return WS_CLIENT_PROTOCOL_ERROR; \ } @@ -262,7 +212,7 @@ int ws_client_start_handshake(ws_client *client) #define HTTP_HDR_LINE_CHECK_LIMIT(x) \ if ((x) >= MAX_HTTP_LINE_LENGTH) { \ - ERROR("HTTP line received is too long. Maximum is %d", MAX_HTTP_LINE_LENGTH); \ + nd_log(NDLS_DAEMON, NDLP_ERR, "HTTP line received is too long. Maximum is %d", MAX_HTTP_LINE_LENGTH); \ return WS_CLIENT_PROTOCOL_ERROR; \ } @@ -285,13 +235,13 @@ int ws_client_parse_handshake_resp(ws_client *client) BUF_READ_CHECK_AT_LEAST(HTTP_SC_LENGTH); // "XXX " http return code rbuf_pop(client->buf_read, buf, HTTP_SC_LENGTH); if (buf[HTTP_SC_LENGTH - 1] != 0x20) { - ERROR("HTTP status code received is not terminated by space (0x20)"); + nd_log(NDLS_DAEMON, NDLP_ERR, "HTTP status code received is not terminated by space (0x20)"); return WS_CLIENT_PROTOCOL_ERROR; } buf[HTTP_SC_LENGTH - 1] = 0; client->hs.http_code = atoi(buf); if (client->hs.http_code < 100 || client->hs.http_code >= 600) { - ERROR("HTTP status code received not in valid range 100-600"); + nd_log(NDLS_DAEMON, NDLP_ERR, "HTTP status code received not in valid range 100-600"); return WS_CLIENT_PROTOCOL_ERROR; } client->hs.hdr_state = WS_HDR_ENDLINE; @@ -330,16 +280,16 @@ int ws_client_parse_handshake_resp(ws_client *client) ptr = rbuf_find_bytes(client->buf_read, HTTP_HDR_SEPARATOR, strlen(HTTP_HDR_SEPARATOR), &idx_sep); if (!ptr || idx_sep > idx_crlf) { - ERROR("Expected HTTP hdr field key/value separator \": \" before endline in non empty HTTP header line"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Expected HTTP hdr field key/value separator \": \" before endline in non empty HTTP header line"); return WS_CLIENT_PROTOCOL_ERROR; } if (idx_crlf == idx_sep + (int)strlen(HTTP_HDR_SEPARATOR)) { - ERROR("HTTP Header value cannot be empty"); + nd_log(NDLS_DAEMON, NDLP_ERR, "HTTP Header value cannot be empty"); return WS_CLIENT_PROTOCOL_ERROR; } if (idx_sep > HTTP_HEADER_NAME_MAX_LEN) { - ERROR("HTTP header too long (%d)", idx_sep); + nd_log(NDLS_DAEMON, NDLP_ERR, "HTTP header too long (%d)", idx_sep); return WS_CLIENT_PROTOCOL_ERROR; } @@ -347,23 +297,21 @@ int ws_client_parse_handshake_resp(ws_client *client) hdr->key = ((char*)hdr) + sizeof(struct http_header); hdr->value = hdr->key + idx_sep + 1; - bytes = rbuf_pop(client->buf_read, hdr->key, idx_sep); + rbuf_pop(client->buf_read, hdr->key, idx_sep); rbuf_bump_tail(client->buf_read, strlen(HTTP_HDR_SEPARATOR)); - bytes = rbuf_pop(client->buf_read, hdr->value, idx_crlf - idx_sep - strlen(HTTP_HDR_SEPARATOR)); + rbuf_pop(client->buf_read, hdr->value, idx_crlf - idx_sep - strlen(HTTP_HDR_SEPARATOR)); rbuf_bump_tail(client->buf_read, strlen(WS_HTTP_NEWLINE)); for (int i = 0; hdr->key[i]; i++) hdr->key[i] = tolower(hdr->key[i]); -// DEBUG("HTTP header \"%s\" received. Value \"%s\"", hdr->key, hdr->value); - if (ws_client_add_http_header(client, hdr)) return WS_CLIENT_PROTOCOL_ERROR; if (!strcmp(hdr->key, WS_CONN_ACCEPT)) { if (strcmp(client->hs.nonce_reply, hdr->value)) { - ERROR("Received NONCE \"%s\" does not match expected nonce of \"%s\"", hdr->value, client->hs.nonce_reply); + nd_log(NDLS_DAEMON, NDLP_ERR, "Received NONCE \"%s\" does not match expected nonce of \"%s\"", hdr->value, client->hs.nonce_reply); return WS_CLIENT_PROTOCOL_ERROR; } client->hs.nonce_matched = 1; @@ -373,21 +321,21 @@ int ws_client_parse_handshake_resp(ws_client *client) case WS_HDR_PARSE_DONE: if (!client->hs.nonce_matched) { - ERROR("Missing " WS_CONN_ACCEPT " header"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Missing " WS_CONN_ACCEPT " header"); return WS_CLIENT_PROTOCOL_ERROR; } if (client->hs.http_code != 101) { - ERROR("HTTP return code not 101. Received %d with msg \"%s\".", client->hs.http_code, client->hs.http_reply_msg); + nd_log(NDLS_DAEMON, NDLP_ERR, "HTTP return code not 101. Received %d with msg \"%s\".", client->hs.http_code, client->hs.http_reply_msg); return WS_CLIENT_PROTOCOL_ERROR; } client->state = WS_ESTABLISHED; client->hs.hdr_state = WS_HDR_ALL_DONE; - INFO("Websocket Connection Accepted By Server"); + nd_log(NDLS_DAEMON, NDLP_INFO, "Websocket Connection Accepted By Server"); return WS_CLIENT_PARSING_DONE; case WS_HDR_ALL_DONE: - FATAL("This is error we should never come here!"); + nd_log(NDLS_DAEMON, NDLP_CRIT, "This is error we should never come here!"); return WS_CLIENT_PROTOCOL_ERROR; } return 0; @@ -397,7 +345,7 @@ int ws_client_parse_handshake_resp(ws_client *client) #define WS_FINAL_FRAG BYTE_MSB #define WS_PAYLOAD_MASKED BYTE_MSB -static inline size_t get_ws_hdr_size(size_t payload_size) +static size_t get_ws_hdr_size(size_t payload_size) { size_t hdr_len = 2 + 4 /*mask*/; if(payload_size > 125) @@ -408,7 +356,7 @@ static inline size_t get_ws_hdr_size(size_t payload_size) } #define MAX_POSSIBLE_HDR_LEN 14 -int ws_client_send(ws_client *client, enum websocket_opcode frame_type, const char *data, size_t size) +int ws_client_send(const ws_client *client, enum websocket_opcode frame_type, const char *data, size_t size) { // TODO maybe? implement fragmenting, it is not necessary though // as both tested MQTT brokers have no reuirement of one MQTT envelope @@ -416,24 +364,16 @@ int ws_client_send(ws_client *client, enum websocket_opcode frame_type, const ch // one big MQTT message as single fragmented WebSocket envelope char hdr[MAX_POSSIBLE_HDR_LEN]; char *ptr = hdr; - char *mask; int size_written = 0; size_t j = 0; size_t w_buff_free = rbuf_bytes_free(client->buf_write); size_t hdr_len = get_ws_hdr_size(size); - if (w_buff_free < hdr_len * 2) { -#ifdef DEBUG_ULTRA_VERBOSE - DEBUG("Write buffer full. Can't write requested %d size.", size); -#endif + if (w_buff_free < hdr_len * 2) return 0; - } if (w_buff_free < (hdr_len + size)) { -#ifdef DEBUG_ULTRA_VERBOSE - DEBUG("Can't write whole MQTT packet of %d bytes into the buffer. Will do partial send of %d.", size, w_buff_free - hdr_len); -#endif size = w_buff_free - hdr_len; hdr_len = get_ws_hdr_size(size); // the actual needed header size might decrease if we cut number of bytes @@ -459,12 +399,10 @@ int ws_client_send(ws_client *client, enum websocket_opcode frame_type, const ch ptr += sizeof(be); } else *ptr++ |= size; - - mask = ptr; - if (read(client->entropy_fd, mask, sizeof(uint32_t)) < (ssize_t)sizeof(uint32_t)) { - ERROR("Unable to get mask from \"" ENTROPY_SOURCE "\""); - return -2; - } + + char *mask = ptr; + uint32_t mask32 = os_random32() + 1; + memcpy(mask, &mask32, sizeof(mask32)); rbuf_push(client->buf_write, hdr, hdr_len); @@ -490,7 +428,7 @@ int ws_client_send(ws_client *client, enum websocket_opcode frame_type, const ch return size_written; } -static int check_opcode(ws_client *client,enum websocket_opcode oc) +static int check_opcode(enum websocket_opcode oc) { switch(oc) { case WS_OP_BINARY_FRAME: @@ -498,34 +436,34 @@ static int check_opcode(ws_client *client,enum websocket_opcode oc) case WS_OP_PING: return 0; case WS_OP_CONTINUATION_FRAME: - FATAL("WS_OP_CONTINUATION_FRAME NOT IMPLEMENTED YET!!!!"); + nd_log(NDLS_DAEMON, NDLP_ERR, "WS_OP_CONTINUATION_FRAME NOT IMPLEMENTED YET!!!!"); return 0; case WS_OP_TEXT_FRAME: - FATAL("WS_OP_TEXT_FRAME NOT IMPLEMENTED YET!!!!"); + nd_log(NDLS_DAEMON, NDLP_ERR, "WS_OP_TEXT_FRAME NOT IMPLEMENTED YET!!!!"); return 0; case WS_OP_PONG: - FATAL("WS_OP_PONG NOT IMPLEMENTED YET!!!!"); + nd_log(NDLS_DAEMON, NDLP_ERR, "WS_OP_PONG NOT IMPLEMENTED YET!!!!"); return 0; default: return WS_CLIENT_PROTOCOL_ERROR; } } -static inline void ws_client_rx_post_hdr_state(ws_client *client) +static void ws_client_rx_post_hdr_state(ws_client *client) { switch(client->rx.opcode) { case WS_OP_BINARY_FRAME: client->rx.parse_state = WS_PAYLOAD_DATA; - return; + break; case WS_OP_CONNECTION_CLOSE: client->rx.parse_state = WS_PAYLOAD_CONNECTION_CLOSE; - return; + break; case WS_OP_PING: client->rx.parse_state = WS_PAYLOAD_PING_REQ_PAYLOAD; - return; + break; default: client->rx.parse_state = WS_PAYLOAD_SKIP_UNKNOWN_PAYLOAD; - return; + break; } } @@ -541,15 +479,15 @@ int ws_client_process_rx_ws(ws_client *client) client->rx.opcode = buf[0] & (char)~BYTE_MSB; if (!(buf[0] & (char)~WS_FINAL_FRAG)) { - ERROR("Not supporting fragmented messages yet!"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Not supporting fragmented messages yet!"); return WS_CLIENT_PROTOCOL_ERROR; } - if (check_opcode(client, client->rx.opcode) == WS_CLIENT_PROTOCOL_ERROR) + if (check_opcode(client->rx.opcode) == WS_CLIENT_PROTOCOL_ERROR) return WS_CLIENT_PROTOCOL_ERROR; if (buf[1] & (char)WS_PAYLOAD_MASKED) { - ERROR("Mask is not allowed in Server->Client Websocket direction."); + nd_log(NDLS_DAEMON, NDLP_ERR, "Mask is not allowed in Server->Client Websocket direction."); return WS_CLIENT_PROTOCOL_ERROR; } @@ -584,12 +522,8 @@ int ws_client_process_rx_ws(ws_client *client) if (!rbuf_bytes_available(client->buf_read)) return WS_CLIENT_NEED_MORE_BYTES; char *insert = rbuf_get_linear_insert_range(client->buf_to_mqtt, &size); - if (!insert) { -#ifdef DEBUG_ULTRA_VERBOSE - DEBUG("BUFFER TOO FULL. Avail %d req %d", (int)size, (int)remaining); -#endif + if (!insert) return WS_CLIENT_BUFFER_FULL; - } size = (size > remaining) ? remaining : size; size = rbuf_pop(client->buf_read, insert, size); rbuf_bump_head(client->buf_to_mqtt, size); @@ -603,11 +537,11 @@ int ws_client_process_rx_ws(ws_client *client) // b) 2byte reason code // c) 2byte reason code followed by message if (client->rx.payload_length == 1) { - ERROR("WebScoket CONNECTION_CLOSE can't have payload of size 1"); + nd_log(NDLS_DAEMON, NDLP_ERR, "WebScoket CONNECTION_CLOSE can't have payload of size 1"); return WS_CLIENT_PROTOCOL_ERROR; } if (!client->rx.payload_length) { - INFO("WebSocket server closed the connection without giving reason."); + nd_log(NDLS_DAEMON, NDLP_INFO, "WebSocket server closed the connection without giving reason."); client->rx.parse_state = WS_PACKET_DONE; break; } @@ -621,7 +555,7 @@ int ws_client_process_rx_ws(ws_client *client) client->rx.payload_processed += sizeof(uint16_t); if(client->rx.payload_processed == client->rx.payload_length) { - INFO("WebSocket server closed the connection with EC=%d. Without message.", + nd_log(NDLS_DAEMON, NDLP_INFO, "WebSocket server closed the connection with EC=%d. Without message.", client->rx.specific_data.op_close.ec); client->rx.parse_state = WS_PACKET_DONE; break; @@ -640,7 +574,7 @@ int ws_client_process_rx_ws(ws_client *client) client->rx.payload_length - client->rx.payload_processed); } client->rx.specific_data.op_close.reason[client->rx.payload_length] = 0; - INFO("WebSocket server closed the connection with EC=%d and reason \"%s\"", + nd_log(NDLS_DAEMON, NDLP_INFO, "WebSocket server closed the connection with EC=%d and reason \"%s\"", client->rx.specific_data.op_close.ec, client->rx.specific_data.op_close.reason); freez(client->rx.specific_data.op_close.reason); @@ -649,14 +583,14 @@ int ws_client_process_rx_ws(ws_client *client) break; case WS_PAYLOAD_SKIP_UNKNOWN_PAYLOAD: BUF_READ_CHECK_AT_LEAST(client->rx.payload_length); - WARN("Skipping Websocket Packet of unsupported/unknown type"); + nd_log(NDLS_DAEMON, NDLP_WARNING, "Skipping Websocket Packet of unsupported/unknown type"); if (client->rx.payload_length) rbuf_bump_tail(client->buf_read, client->rx.payload_length); client->rx.parse_state = WS_PACKET_DONE; return WS_CLIENT_PARSING_DONE; case WS_PAYLOAD_PING_REQ_PAYLOAD: if (client->rx.payload_length > rbuf_get_capacity(client->buf_read) / 2) { - ERROR("Ping arrived with payload which is too big!"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Ping arrived with payload which is too big!"); return WS_CLIENT_INTERNAL_ERROR; } BUF_READ_CHECK_AT_LEAST(client->rx.payload_length); @@ -666,7 +600,7 @@ int ws_client_process_rx_ws(ws_client *client) // then attempt to send as soon as buffer space clears up size = ws_client_send(client, WS_OP_PONG, client->rx.specific_data.ping_msg, client->rx.payload_length); if (size != client->rx.payload_length) { - ERROR("Unable to send the PONG as one packet back. Closing connection."); + nd_log(NDLS_DAEMON, NDLP_ERR, "Unable to send the PONG as one packet back. Closing connection."); return WS_CLIENT_PROTOCOL_ERROR; } client->rx.parse_state = WS_PACKET_DONE; @@ -678,7 +612,7 @@ int ws_client_process_rx_ws(ws_client *client) return WS_CLIENT_CONNECTION_CLOSED; return WS_CLIENT_PARSING_DONE; default: - FATAL("Unknown parse state"); + nd_log(NDLS_DAEMON, NDLP_ERR, "Unknown parse state"); return WS_CLIENT_INTERNAL_ERROR; } return 0; @@ -711,6 +645,8 @@ int ws_client_process(ws_client *client) case WS_CLIENT_CONNECTION_CLOSED: client->state = WS_CONN_CLOSED_GRACEFUL; break; + default: + break; } // if ret == 0 we can continue parsing // if ret == WS_CLIENT_PARSING_DONE we processed @@ -719,13 +655,13 @@ int ws_client_process(ws_client *client) } while (!ret || ret == WS_CLIENT_PARSING_DONE); break; case WS_ERROR: - ERROR("ws_client is in error state. Restart the connection!"); + nd_log(NDLS_DAEMON, NDLP_ERR, "ws_client is in error state. Restart the connection!"); return WS_CLIENT_PROTOCOL_ERROR; case WS_CONN_CLOSED_GRACEFUL: - ERROR("Connection has been gracefully closed. Calling this is useless (and probably bug) until you reconnect again."); + nd_log(NDLS_DAEMON, NDLP_ERR, "Connection has been gracefully closed. Calling this is useless (and probably bug) until you reconnect again."); return WS_CLIENT_CONNECTION_CLOSED; default: - FATAL("Unknown connection state! Probably memory corruption."); + nd_log(NDLS_DAEMON, NDLP_CRIT, "Unknown connection state! Probably memory corruption."); return WS_CLIENT_INTERNAL_ERROR; } return ret; diff --git a/src/aclk/mqtt_websockets/ws_client.h b/src/aclk/mqtt_websockets/ws_client.h index 0ccbd29a8..67e5835a2 100644 --- a/src/aclk/mqtt_websockets/ws_client.h +++ b/src/aclk/mqtt_websockets/ws_client.h @@ -1,14 +1,8 @@ -// SPDX-License-Identifier: GPL-3.0-only -// Copyright (C) 2020 Timotej Šiškovič +// SPDX-License-Identifier: GPL-3.0-or-later #ifndef WS_CLIENT_H #define WS_CLIENT_H -#include "c-rbuf/cringbuffer.h" -#include "mqtt_wss_log.h" - -#include <stdint.h> - #define WS_CLIENT_NEED_MORE_BYTES 0x10 #define WS_CLIENT_PARSING_DONE 0x11 #define WS_CLIENT_CONNECTION_CLOSED 0x12 @@ -98,23 +92,20 @@ typedef struct websocket_client { // memory usage and remove one more memcpy buf_read->buf_to_mqtt rbuf_t buf_to_mqtt; // RAW data for MQTT lib - int entropy_fd; - // careful host is borrowed, don't free char **host; - mqtt_wss_log_ctx_t log; } ws_client; -ws_client *ws_client_new(size_t buf_size, char **host, mqtt_wss_log_ctx_t log); +ws_client *ws_client_new(size_t buf_size, char **host); void ws_client_destroy(ws_client *client); void ws_client_reset(ws_client *client); int ws_client_start_handshake(ws_client *client); -int ws_client_want_write(ws_client *client); +int ws_client_want_write(const ws_client *client); int ws_client_process(ws_client *client); -int ws_client_send(ws_client *client, enum websocket_opcode frame_type, const char *data, size_t size); +int ws_client_send(const ws_client *client, enum websocket_opcode frame_type, const char *data, size_t size); #endif /* WS_CLIENT_H */ |