pax_global_header00006660000000000000000000000064150505730760014522gustar00rootroot0000000000000052 comment=6927c35799b1edc6801344aca31b1b6d2a3532f4 readsb-3.16/000077500000000000000000000000001505057307600127135ustar00rootroot00000000000000readsb-3.16/.gitattributes000066400000000000000000000000141505057307600156010ustar00rootroot00000000000000*.xz binary readsb-3.16/.github/000077500000000000000000000000001505057307600142535ustar00rootroot00000000000000readsb-3.16/.github/workflows/000077500000000000000000000000001505057307600163105ustar00rootroot00000000000000readsb-3.16/.github/workflows/docker.yaml000066400000000000000000000037771505057307600204610ustar00rootroot00000000000000name: docker build on: schedule: - cron: "0 5 * * 1" push: branches: - "dev" - "sid" tags: - "v*.*.*" pull_request: branches: - "dev" jobs: docker: runs-on: ubuntu-latest permissions: packages: write steps: - name: Set variables useful for later id: useful_vars run: |- echo "name=timestamp::$(date +%s)" >> $GITHUB_OUTPUT echo "name=short_sha::${GITHUB_SHA::8}" >> $GITHUB_OUTPUT - name: Checkout uses: actions/checkout@v3 - name: Docker meta id: docker_meta uses: docker/metadata-action@v4 with: images: ghcr.io/${{ github.repository }} tags: | type=schedule type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha,prefix=,format=long,event=tag type=sha type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} type=raw,value=${{ github.ref_name }}-${{ steps.useful_vars.outputs.short_sha }}-${{ steps.useful_vars.outputs.timestamp }},enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Login to GHCR if: github.event_name != 'pull_request' uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v4 with: context: . push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} platforms: linux/amd64,linux/arm64 build-args: | BUILDKIT_CONTEXT_KEEP_GIT_DIR=true readsb-3.16/.gitignore000066400000000000000000000001561505057307600147050ustar00rootroot00000000000000*.o .*.swp *~ *.rej *.orig nbproject/* oneoff/convert_benchmark oneoff/decode_comm_b readsb viewadsb .version readsb-3.16/COPYING000066400000000000000000001045151505057307600137540ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . readsb-3.16/Dockerfile000066400000000000000000000035061505057307600147110ustar00rootroot00000000000000FROM ghcr.io/wiedehopf/readsb-builder:latest AS builder SHELL ["/bin/bash", "-x", "-o", "pipefail", "-c"] RUN --mount=type=bind,source=.,target=/app/git \ cd /app/git && \ READSB_BUILD_DIR=$(mktemp -d) && \ cp -aT /app/git $READSB_BUILD_DIR && \ cd $READSB_BUILD_DIR && \ [[ $(uname -m) == x86_64 ]] && MARCH=" -march=nehalem" || MARCH="" && \ make -j$(nproc) RTLSDR=yes OPTIMIZE="-O2 $MARCH" && \ mv readsb /usr/local/bin && \ mv viewadsb /usr/local/bin && \ chmod +x /usr/local/bin/viewadsb /usr/local/bin/readsb && \ make clean && \ make -j$(nproc) PRINT_UUIDS=yes TRACKS_UUID=yes OPTIMIZE="-O2 $MARCH" && \ mv readsb /usr/local/bin/readsb-uuid && \ mv viewadsb /usr/local/bin/viewadsb-uuid && \ chmod +x /usr/local/bin/viewadsb-uuid && \ chmod +x /usr/local/bin/readsb-uuid && \ rm -rf $READSB_BUILD_DIR && \ mkdir -p /usr/local/share/tar1090 && \ wget -O /usr/local/share/tar1090/aircraft.csv.gz https://github.com/wiedehopf/tar1090-db/raw/csv/aircraft.csv.gz && \ true FROM debian:bookworm-slim RUN \ --mount=type=bind,from=builder,source=/,target=/builder/ \ apt-get update && \ apt-get -y install --no-install-recommends \ librtlsdr0 libncurses6 zlib1g libzstd1 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ mkdir -p /run/readsb && \ cp /builder/usr/local/bin/readsb* /usr/local/bin/ && \ cp /builder/usr/local/bin/viewadsb* /usr/local/bin/ && \ mkdir -p /usr/local/share/tar1090 && \ cp /builder/usr/local/share/tar1090/aircraft.csv.gz /usr/local/share/tar1090/aircraft.csv.gz && \ cp /builder/usr/local/lib/libjemalloc.so.2 /usr/local/lib/libjemalloc.so.2 && \ true ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 ENV MALLOC_CONF=narenas:1,tcache:false ENTRYPOINT ["/usr/local/bin/readsb"] readsb-3.16/LICENSE000066400000000000000000000041531505057307600137230ustar00rootroot00000000000000This version of readsb is licensed under the GPL, v3 or any later version. Please see the individual source files and the file COPYING for full copyright and license details. Copyright (c) 2020 Matthias Wirth Copyright (c) 2019 Michael Wolf This source code incorporates work that was released under GPL, v2 or later license. For unmodified versions of the original work that may be used under the terms of that license, please see https://github.com/flightaware/dump1090 The source code also incorporates work that was released under a BSD-style license, reproduced below. For unmodified versions of the original work that may be used under the terms of that license, please see https://github.com/antirez/dump1090 and https://github.com/MalcolmRobb/dump1090. Copyright (C) 2012 by Salvatore Sanfilippo All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. readsb-3.16/Makefile000066400000000000000000000133251505057307600143570ustar00rootroot00000000000000PROGNAME=readsb READSB_VERSION := "$(shell echo -n `cat version`; { git show -s --format=format: && echo -n ' wiedehopf git: ' && git describe --abbrev --dirty --always && git show -s --format=format:"(committed: %cd)" | tr -cd '[a-z],[A-Z],[0-9],:, ,\-,_,(,)';} || echo -n ' compiled on '`date +%y%m%d` )" RTLSDR ?= no BLADERF ?= no HACKRF ?= no PLUTOSDR ?= no SOAPYSDR ?= no AGGRESSIVE ?= no HAVE_BIASTEE ?= no TRACKS_UUID ?= no PRINT_UUIDS ?= no DIALECT = -std=c11 CFLAGS = $(DIALECT) -W -Wall -Werror -fno-common -O2 CFLAGS += -DMODES_READSB_VERSION=\"$(READSB_VERSION)\" CFLAGS += -DREADSB_SHORT_VERSION=\"$(shell cat version)\" CFLAGS += -DREADSB_SHORT_COMMIT=\"$(shell git describe --abbrev --dirty --always || echo nogit)\" CFLAGS += -Wdate-time -fstack-protector-strong -Wformat -Werror=format-security # Platform-specific settings UNAME := $(shell uname) ifeq ($(UNAME), Linux) CFLAGS += -D_GNU_SOURCE -D_DEFAULT_SOURCE CFLAGS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 LIBS = -pthread -lpthread -lm -lrt -lzstd else ifeq ($(UNAME), Darwin) CFLAGS += -D_DARWIN_C_SOURCE CFLAGS += -I./compat/apple # For epoll_shim.h CFLAGS += -I/opt/homebrew/include # For zstd.h LDFLAGS += -L/opt/homebrew/lib LIBS = -pthread -lpthread -lm -lzstd COMPAT += compat/apple/epoll_shim.o else LIBS = -pthread -lpthread -lm -lrt -lzstd endif ifeq ($(ZLIB_STATIC), yes) LIBS += -l:libz.a else LIBS += -lz endif ifeq ($(shell $(CC) -c feature_test.c -o feature_test.o -Wno-format-truncation -Werror >/dev/null 2>&1 && echo 1 || echo 0), 1) CFLAGS += -Wno-format-truncation endif ifeq ($(shell uname -m | grep -qs -e arm -e aarch64 >/dev/null 2>&1 && echo 1 || echo 0), 1) CFLAGS += -DSC16Q11_TABLE_BITS=8 endif ifeq ($(DISABLE_INTERACTIVE), yes) CFLAGS += -DDISABLE_INTERACTIVE else ifeq ($(UNAME), Darwin) LIBS += $(shell pkg-config --libs ncurses 2>/dev/null || echo -lncurses) else LIBS += $(shell pkg-config --libs ncurses) endif endif # only disable workaround if zerocopy is disabled in librtlsdr, otherwise expect significantly increased CPU use ifeq ($(DISABLE_RTLSDR_ZEROCOPY_WORKAROUND), yes) CFLAGS += -DDISABLE_RTLSDR_ZEROCOPY_WORKAROUND endif ifeq ($(LTO), yes) CFLAGS += -flto LDFLAGS += -flto endif ifeq ($(HISTORY), yes) CFLAGS += -DALL_JSON=1 endif ifneq ($(PREAMBLE_THRESHOLD_DEFAULT),) CFLAGS += -DPREAMBLE_THRESHOLD_DEFAULT=$(PREAMBLE_THRESHOLD_DEFAULT) endif ifneq ($(TRACE_THREADS),) CFLAGS += -DTRACE_THREADS=$(TRACE_THREADS) endif ifneq ($(TRACE_RECENT_POINTS),) CFLAGS += -DTRACE_RECENT_POINTS=$(TRACE_RECENT_POINTS) endif ifneq ($(AIRCRAFT_HASH_BITS),) CFLAGS += -DAIRCRAFT_HASH_BITS=$(AIRCRAFT_HASH_BITS) endif ifeq ($(STATS_PHASE),yes) CFLAGS += -DSTATS_PHASE endif ifeq ($(TRACKS_UUID), yes) CFLAGS += -DTRACKS_UUID endif ifeq ($(PRINT_UUIDS), yes) CFLAGS += -DPRINT_UUIDS endif ifneq ($(RECENT_RECEIVER_IDS),) CFLAGS += -DRECENT_RECEIVER_IDS=$(RECENT_RECEIVER_IDS) endif ifeq ($(RTLSDR), yes) SDR_OBJ += sdr_rtlsdr.o CFLAGS += -DENABLE_RTLSDR ifeq ($(HAVE_BIASTEE), yes) CFLAGS += -DENABLE_RTLSDR_BIASTEE endif ifdef RTLSDR_PREFIX CFLAGS += -I$(RTLSDR_PREFIX)/include LDFLAGS += -L$(RTLSDR_PREFIX)/lib else CFLAGS += $(shell pkg-config --cflags librtlsdr) LDFLAGS += $(shell pkg-config --libs-only-L librtlsdr) endif # static linking not well supported, use at own risk ifeq ($(STATIC), yes) LIBS_SDR += -Wl,-Bstatic -lrtlsdr -Wl,-Bdynamic -lusb-1.0 else LIBS_SDR += -lrtlsdr -lusb-1.0 endif endif ifeq ($(BLADERF), yes) SDR_OBJ += sdr_bladerf.o sdr_ubladerf.o CFLAGS += $(shell pkg-config --cflags libbladeRF) -DENABLE_BLADERF LIBS_SDR += $(shell pkg-config --libs libbladeRF) endif ifeq ($(HACKRF), yes) SDR_OBJ += sdr_hackrf.o CFLAGS += $(shell pkg-config --cflags libhackrf) -DENABLE_HACKRF LIBS_SDR += $(shell pkg-config --libs libhackrf) endif ifeq ($(PLUTOSDR), yes) SDR_OBJ += sdr_plutosdr.o CFLAGS += $(shell pkg-config --cflags libiio libad9361) -DENABLE_PLUTOSDR LIBS_SDR += $(shell pkg-config --libs libiio libad9361) endif ifeq ($(SOAPYSDR), yes) SDR_OBJ += sdr_soapy.o CFLAGS += $(shell pkg-config --cflags SoapySDR) -DENABLE_SOAPYSDR LIBS_SDR += $(shell pkg-config --libs SoapySDR) endif # add custom overrides if user defines them CFLAGS += -g $(OPTIMIZE) all: readsb viewadsb ifneq ($(shell cat .version 2>/dev/null),prefix $(READSB_VERSION)) .PHONY: .version .version: @(echo 'prefix $(READSB_VERSION)' >.version &) endif readsb.o: readsb.c *.h .version $(CC) $(CFLAGS) -c $< -o $@ %.o: %.c *.h $(CC) $(CFLAGS) -c $< -o $@ readsb: readsb.o argp.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o json_out.o net_io.o crc.o demod_2400.o \ uat2esnt/uat2esnt.o uat2esnt/uat_decode.o \ stats.o cpr.o icao_filter.o track.o util.o fasthash.o convert.o sdr_ifile.o sdr_beast.o sdr.o ais_charset.o \ globe_index.o geomag.o receiver.o aircraft.o api.o threadpool.o \ $(SDR_OBJ) $(COMPAT) $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_SDR) $(OPTIMIZE) viewadsb: readsb rm -f viewadsb cp readsb viewadsb clean: rm -f *.o uat2esnt/*.o compat/clock_gettime/*.o compat/clock_nanosleep/*.o compat/apple/*.o readsb viewadsb cprtests crctests convert_benchmark test: cprtest crctest cprtest: cprtests ./cprtests crctest: cprtests ./cprtests cprtests: cpr.o cprtests.o $(CC) $(CFLAGS) -o $@ $^ -lm crctests: crc.c crc.h $(CC) $(CFLAGS) -DCRCDEBUG -o $@ $< benchmarks: oneoff/convert_benchmark ./convert_benchmark oneoff/convert_benchmark: oneoff/convert_benchmark.o convert.o util.o $(CC) $(CFLAGS) -o $@ $^ -lm oneoff/decode_comm_b: oneoff/decode_comm_b.o comm_b.o ais_charset.o $(CC) $(CFLAGS) -o $@ $^ -lm readsb-3.16/README-api.md000066400000000000000000000010621505057307600147400ustar00rootroot00000000000000The api-port is best used with nginx and a unix socket: ``` readsb [...] --net-api-port unix:/run/readsb/api.sock ``` nginx location: ``` location /re-api/ { gzip on; proxy_http_version 1.1; proxy_max_temp_file_size 0; proxy_set_header Connection $http_connection; proxy_set_header Host $http_host; proxy_pass http://unix:/run/readsb/api.sock:/$is_args$args; } ``` Which can then be queried like this: ``` curl --compressed -sS 'http://localhost/re-api/?box=-90,90,0,20' | jq ``` See json readme for more details on the query syntax readsb-3.16/README-json.md000066400000000000000000000525301505057307600151460ustar00rootroot00000000000000# JSON output formats readsb generates several json files with informaton about the receiver itself, currently known aircraft, and general statistics. These are used by the webmap, but could also be used by other things feeds stats about readsb operation to collectd for later graphing. ## Reading the json files There are two ways to obtain the json files: * By HTTP from the external webserver that readsb is feeding. The json is served from the data/ path, e.g. http://somehost/tar1090/data/aircraft.json when installed with tar1090 * As a file in the directory specified by --write-json on readsb command line (by default /run/readsb) The HTTP versions are always up to date. The file versions are written periodically; for aircraft, typically once a second, for stats, once a minute. The file versions are updated to a temporary file, then atomically renamed to the right path, so you should never see partial copies. Each file contains a single JSON object. The file formats are: ## receiver.json This file has general metadata about readsb. It does not change often and you probably just want to read it once at startup. The keys are: * version: the version of readsb in use * refresh: how often aircraft.json is updated (for the file version), in milliseconds. the webmap uses this to control its refresh interval. * lat: the latitude of the receiver in decimal degrees. Optional, may not be present. * lon: the longitude of the receiver in decimal degrees. Optional, may not be present. ## aircraft.json and --json-port - --json-port will supply one aircraft object per line, each aircraft object has it's own now as a timestamp - aircraft.json contains recently seen aircraft. The keys are: * now: the time this file was generated, in seconds since Jan 1 1970 00:00:00 GMT (the Unix epoch). * messages: the total number of Mode S messages processed since readsb started. * aircraft: an array of JSON objects, one per known aircraft. Each aircraft has the following keys. Keys will be omitted if data is not available. * hex: the 24-bit ICAO identifier of the aircraft, as 6 hex digits. The identifier may start with '~', this means that the address is a non-ICAO address (e.g. from TIS-B). * type: type of underlying messages / best source of current data for this position / aircraft: (the following list is in order of which data is preferentially used) * adsb_icao: messages from a Mode S or ADS-B transponder, using a 24-bit ICAO address * adsb_icao_nt: messages from an ADS-B equipped "non-transponder" emitter e.g. a ground vehicle, using a 24-bit ICAO address * adsr_icao: rebroadcast of ADS-B messages originally sent via another data link e.g. UAT, using a 24-bit ICAO address * tisb_icao: traffic information about a non-ADS-B target identified by a 24-bit ICAO address, e.g. a Mode S target tracked by secondary radar * adsc: ADS-C (received by monitoring satellite downlinks) * mlat: MLAT, position calculated arrival time differences using multiple receivers, outliers and varying accuracy is expected. * other: miscellaneous data received via Basestation / SBS format, quality / source is unknown. * mode_s: ModeS data from the planes transponder (no position transmitted) * adsb_other: messages from an ADS-B transponder using a non-ICAO address, e.g. anonymized address * adsr_other: rebroadcast of ADS-B messages originally sent via another data link e.g. UAT, using a non-ICAO address * tisb_other: traffic information about a non-ADS-B target using a non-ICAO address * tisb_trackfile: traffic information about a non-ADS-B target using a track/file identifier, typically from primary or Mode A/C radar * flight: callsign, the flight name or aircraft registration as 8 chars (2.2.8.2.6) * alt_baro: the aircraft barometric altitude in feet as a number OR "ground" as a string * alt_geom: geometric (GNSS / INS) altitude in feet referenced to the WGS84 ellipsoid * gs: ground speed in knots * ias: indicated air speed in knots * tas: true air speed in knots * mach: Mach number * track: true track over ground in degrees (0-359) * track_rate: Rate of change of track, degrees/second * roll: Roll, degrees, negative is left roll * mag_heading: Heading, degrees clockwise from magnetic north * true_heading: Heading, degrees clockwise from true north (usually only transmitted on ground, in the air usually derived from the magnetic heading using magnetic model WMM2020) * baro_rate: Rate of change of barometric altitude, feet/minute * geom_rate: Rate of change of geometric (GNSS / INS) altitude, feet/minute * squawk: Mode A code (Squawk), encoded as 4 octal digits * emergency: ADS-B emergency/priority status, a superset of the 7x00 squawks (2.2.3.2.7.8.1.1) (none, general, lifeguard, minfuel, nordo, unlawful, downed, reserved) * category: emitter category to identify particular aircraft or vehicle classes (values A0 - D7) (2.2.3.2.5.2) * nav_qnh: altimeter setting (QFE or QNH/QNE), hPa * nav_altitude_mcp: selected altitude from the Mode Control Panel / Flight Control Unit (MCP/FCU) or equivalent equipment * nav_altitude_fms: selected altitude from the Flight Manaagement System (FMS) (2.2.3.2.7.1.3.3) * nav_heading: selected heading (True or Magnetic is not defined in DO-260B, mostly Magnetic as that is the de facto standard) (2.2.3.2.7.1.3.7) * nav_modes: set of engaged automation modes: 'autopilot', 'vnav', 'althold', 'approach', 'lnav', 'tcas' * lat, lon: the aircraft position in decimal degrees * nic: Navigation Integrity Category (2.2.3.2.7.2.6) * rc: Radius of Containment, meters; a measure of position integrity derived from NIC & supplementary bits. (2.2.3.2.7.2.6, Table 2-69) * seen_pos: how long ago (in seconds before "now") the position was last updated * track: true track over ground in degrees (0-359) * version: ADS-B Version Number 0, 1, 2 (3-7 are reserved) (2.2.3.2.7.5) * nic_baro: Navigation Integrity Category for Barometric Altitude (2.2.5.1.35) * nac_p: Navigation Accuracy for Position (2.2.5.1.35) * nac_v: Navigation Accuracy for Velocity (2.2.5.1.19) * sil: Source Integity Level (2.2.5.1.40) * sil_type: interpretation of SIL: unknown, perhour, persample * gva: Geometric Vertical Accuracy (2.2.3.2.7.2.8) * sda: System Design Assurance (2.2.3.2.7.2.4.6) * mlat: list of fields derived from MLAT data * tisb: list of fields derived from TIS-B data * messages: total number of Mode S messages received from this aircraft * seen: how long ago (in seconds before "now") a message was last received from this aircraft * rssi: recent average RSSI (signal power), in dbFS; this will always be negative. * alert: Flight status alert bit (2.2.3.2.3.2) * spi: Flight status special position identification bit (2.2.3.2.3.2) * wd, ws: wind direction and wind speed are calculated from ground track, true heading, true airspeed and ground speed * oat, tat: outer/static air temperature (C) and total air temperature (C) are calculated from mach number and true airspeed (typically somewhat inaccurate at lower altitudes / mach numbers below 0.5, calculation is inhibited for mach < 0.395) * acas_ra: experimental, subject to change, see format here: https://github.com/wiedehopf/readsb/blob/ca5b8257bb6176854eb18ecd96761e107fbb12fa/json_out.c#L249 * gpsOkBefore: experimental, subject to change: aircraft lost GPS / GPS heavily degraded, it was working well before this timestamp, only displayed for 15 min after GPS is lost / degraded (Section references (2.2.xyz) refer to DO-260B.) If used with --db-file using a aircraft.csv.gz from the tar1090-db repository (csv branch), these additional fields will be available if the aircraft is in the database: * r: aircraft registration pulled from database * t: aircraft type pulled from database * (optional with --db-file-lt: desc: long type name) * dbFlags: bitfield for certain database flags, below & must be a bitwise and ... check the documentation for your programming language: ``` military = dbFlags & 1; interesting = dbFlags & 2; PIA = dbFlags & 4; LADD = dbFlags & 8; ``` * lastPosition: {lat, lon, nic, rc, seen_pos} when the regular lat and lon are older than 60 seconds they are no longer considered valid, this will provide the last position and show the age for the last position. aircraft will only be in the aircraft json if a messages has been received in the last 60 seconds, longer for the jaero input, see readsb --help. * rr_lat, rr_lon: If no ADS-B or MLAT position available, a rough estimated position for the aircraft based on the receiver’s estimated coordinates. (If used with multiple receivers / as an aggregation server with --net-ingest --net-receiver-id) ## --net-api-port query formats * opens a builtin webserver that can handle a couple query formats * for more info on nginx proxy_pass and technical details, see README-api.md * now: the time the api data was cached, in seconds since Jan 1 1970 00:00:00 GMT (the Unix epoch). * api caching frequency is controlled by --write-json-every * resultCount: number of aircraft in the aircraft array * ptime: time in milliseconds it took to parse the request and create the json output ``` --net-api-port 8042 curl -sS 'http://localhost:8042/?hexlist=3CD6E3' | jq /?circle=,, /?closest=,, /?box=,,, /?all_with_pos /?all /?find_hex=,,.... /?find_callsign=,,..... /?find_reg=,,..... /?find_type=,,..... ``` * circle returns all aircraft within radius nautical miles of lat, lon * closest is the same as circle but only returning the closest aircraft * box is will give you all aircraft within a rectangle delimited by 2 latitudes and longitudes * closest and circle will supply an extra field named "dst" which will have the distance in nautical miles from the supplied location * all_with_pos will return all aircraft for which we have received a position in the last minute or last 40 minutes for ADS-C * all will return all aircraft returned by all_with_pos and all aircraft with ModeS messages received in the last 30 seconds * find_hex (alias: hexList) will return all aircraft with an exact match on one of the given hex / ICAO ids (limited to 1000) * find_callsign will return all aircraft with an exact match on one of the given callsigns (limited to 1000 or 8000 characters for the request) * find_reg will return all aircraft with an exact match on one of the given registrations (limited to 1000 or 8000 characters for the request) * find_type will return all aircraft that have one of the specified icao type codes (A321, B738, .....) For circle and closest the following two fields are added to each aircraft object: * dst: distance from supplied center point in nmi * dir: true direction of the aircraft from the supplied center point (degrees) To the above base queries you can add these filteroptions ``` &filter_callsign_exact= &filter_callsign_prefix= &filter_squawk= &filter_with_pos &filter_type=,,..... &below_alt_baro= &above_alt_baro= ``` filter any of the base queries for: * an exact callsign match (multiple exact matches possible) * all callsigns that start with * a specific squawk code * only return aircraft that have a valid position * only return aircraft that match one of the icao type codes * only above and / or below a certain altitude in ft (uncorrected barometric altitude, ground treated as 0 ft for simplicity / versatility) ``` &filter_mil &filter_pia &filter_ladd ``` filter any of the base queries for these database flags: * filter_mil will return military aircraft * filter_pia using a PIA hex code * filter_ladd will return aircraft on the LADD list these three filter options can be combined in any combination and will be connected by an OR in contrast, when combining other filters they restrict an already filtered result ``` &jv2 ``` * Change json syntax to be compatible with adsbexchange v2 API output * now: same as normal BUT in milliseconds * total: number of aircraft in the aircraft array * ptime: time in milliseconds it took to parse the request and create the json output ``` ?status ``` * Status code 200 during normal operation ## trace jsons * overall structure ``` { icao: "0123ac", // hex id of the aircraft timestamp: 1609275898.495, // unix timestamp in seconds since epoch (1970) trace: [ [ seconds after timestamp, lat, lon, altitude in ft or "ground" or null, ground speed in knots or null, track in degrees or null, (if altitude == "ground", this will be true heading instead of track) flags as a bitfield: (use bitwise and to extract data) (flags & 1 > 0): position is stale (no position received for 20 seconds before this one) (flags & 2 > 0): start of a new leg (tries to detect a separation point between landing and takeoff that separates fligths) (flags & 4 > 0): vertical rate is geometric and not barometric (flags & 8 > 0): altitude is geometric and not barometric , vertical rate in fpm or null, aircraft object with extra details or null (see aircraft.json documentation, note that not all fields are present as lat and lon for example arlready in the values above), // the following fields only in files generated 2022 and later: type / source of this position or null, geometric altitude or null, geometric vertical rate or null, indicated airspeed or null, roll angle or null ], [next entry like the one before], [next entry like the one before], ] } ``` Example : ```json { "icao":"3c66b0", "r":"D-AIUP", "t":"A320", "dbFlags":0, "desc":"AIRBUS A-320", "timestamp": 1663259853.016, "trace":[ [7016.59,49.263300,10.614239,25125,446.5,309.0,0,-2176, {"type":"adsb_icao","flight":"DLH7YA ","alt_geom":25875,"ias":335,"tas":484,"mach":0.796,"wd":297,"ws":40,"oat":-30,"tat":1,"track":309.00,"track_rate":-0.53,"roll":-10.72,"mag_heading":304.28,"true_heading":308.02,"baro_rate":-2176,"geom_rate":-2208,"squawk":"1000","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":14016,"nic":8,"rc":186,"version":2,"nic_baro":1,"nac_p":8,"nac_v":0,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0}, "adsb_icao",25875,-2208,335,-10.7], [7024.85,49.273589,10.593278,24825,446.0,306.6,0,-2176,null,"adsb_icao",25550,-2144,337,-1.6], [7035.67,49.286865,10.565890,24425,446.8,306.5,0,-2176,null,"adsb_icao",25150,-2144,339,0.3], [7046.71,49.300403,10.537985,24025,446.8,306.5,0,-2176,null,"adsb_icao",24775,-2176,341,0.3], [7057.80,49.314042,10.509941,23625,445.2,306.7,0,-2176, {"type":"adsb_icao","flight":"DLH7YA ","alt_geom":24325,"ias":339,"tas":482,"mach":0.784,"wd":296,"ws":37,"oat":-24,"tat":6,"track":306.69,"track_rate":0.00,"roll":0.18,"mag_heading":302.17,"true_heading":305.89,"baro_rate":-2176,"geom_rate":-2176,"squawk":"1000","category":"A3","nav_qnh":1012.8,"nav_altitude_mcp":14016,"nic":8,"rc":186,"version":2,"nic_baro":1,"nac_p":8,"nac_v":0,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"alert":0,"spi":0}, "adsb_icao",24325,-2176,339,0.2], [7068.82,49.327469,10.482225,23250,443.2,306.6,0,-2112,null,"adsb_icao",23925,-2144,340,0.2], [7080.53,49.341694,10.452841,22875,441.2,306.4,0,-1728,null,"adsb_icao",23550,-1728,341,-0.2] } ``` ## stats.json This file contains statistics about readsb operations. There are 5 top level keys: "latest", "last1min", "last5min", "last15min", "total". Each key has statistics for a different period, defined by the "start" and "end" subkeys: * "total" covers the entire period from when readsb was started up to the current time * "last1min" covers a recent 1-minute period. This may be up to 1 minute out of date (i.e. "end" may be up to 1 minute old). * "last5min" covers a recent 5-minute period. As above, this may be up to 1 minute out of date. * "last15min" covers a recent 15-minute period. As above, this may be up to 1 minute out of date. * "latest" covers the time between the end of the "last1min" period and the current time. Internally, live stats are collected into "latest". Once a minute, "latest" is copied to "last1min" and "latest" is reset. Then "last5min" and "last15min" are recalculated from a history of the last 5 or 15 1-minute periods. Each period has the following subkeys: * start: the start time (in seconds-since-1-Jan-1970) of this statistics collection period. * end: the end time (in seconds-since-1-Jan-1970) of this statistics collection period. * local: statistics about messages received from a local SDR dongle. Not present in --net-only mode. Has subkeys: * blocks_processed: number of sample blocks processed * blocks_dropped: number of sample blocks dropped before processing. A nonzero value means CPU overload. * modeac: number of Mode A / C messages decoded * modes: number of Mode S preambles received. This is *not* the number of valid messages! * bad: number of Mode S preambles that didn't result in a valid message * unknown_icao: number of Mode S preambles which looked like they might be valid but we didn't recognize the ICAO address and it was one of the message types where we can't be sure it's valid in this case. * accepted: array. Index N has the number of valid Mode S messages accepted with N-bit errors corrected. * signal: mean signal power of successfully received messages, in dbFS; always negative. * peak_signal: peak signal power of a successfully received message, in dbFS; always negative. * strong_signals: number of messages received that had a signal power above -3dBFS. * remote: statistics about messages received from remote clients. Only present in --net or --net-only mode. Has subkeys: * modeac: number of Mode A / C messages received. * modes: number of Mode S messages received. * bad: number of Mode S messages that had bad CRC or were otherwise invalid. * unknown_icao: number of Mode S messages which looked like they might be valid but we didn't recognize the ICAO address and it was one of the message types where we can't be sure it's valid in this case. * accepted: array. Index N has the number of valid Mode S messages accepted with N-bit errors corrected. * http_requests: number of HTTP requests handled. * cpu: statistics about CPU use. Has subkeys: * demod: milliseconds spent doing demodulation and decoding in response to data from a SDR dongle * reader: milliseconds spent reading sample data over USB from a SDR dongle * background: milliseconds spent doing network I/O, processing received network messages, and periodic tasks. * cpr: statistics about Compact Position Report message decoding. Has subkeys: * surface: total number of surface CPR messages received * airborne: total number of airborne CPR messages received * global_ok: global positions successfuly derived * global_bad: global positions that were rejected because they were inconsistent * global_range: global positions that were rejected because they exceeded the receiver max range * global_speed: global positions that were rejected because they failed the inter-position speed check * global_skipped: global position attempts skipped because we did not have the right data (e.g. even/odd messages crossed a zone boundary) * local_ok: local (relative) positions successfully found * local_aircraft_relative: local positions found relative to a previous aircraft position * local_receiver_relative: local positions found relative to the receiver position * local_skipped: local (relative) positions not used because we did not have the right data * local_range: local positions not used because they exceeded the receiver max range or fell into the ambiguous part of the receiver range * local_speed: local positions not used because they failed the inter-position speed check * filtered: number of CPR messages ignored because they matched one of the heuristics for faulty transponder output * tracks: statistics on aircraft tracks. Each track represents a unique aircraft and persists for up to 5 minutes after the last message from the aircraft is heard. If messages from the same aircraft are subsequently heard after the 5 minute period, this will be counted as a new track. * all: total tracks created * single_message: tracks consisting of only a single message. These are usually due to message decoding errors that produce a bad aircraft address. * messages: total number of messages accepted by readsb from any source ## minimal example on how to use python to process aircraft.json: ``` from contextlib import closing from urllib.request import urlopen, URLError import json url="http://192.168.2.14/tar1090/data/aircraft.json" with closing(urlopen(url, None, 5.0)) as aircraft_file: aircraft_data = json.load(aircraft_file) for a in aircraft_data['aircraft']: hex = a.get('hex') lat = a.get('lat') lon = a.get('lon') if lat and lon: print("Icao 24 bit id: {hex} Latitude: {lat:.4f} Longitude: {lon:.4f}".format(hex=hex, lat=lat, lon=lon)) ``` readsb-3.16/README.md000066400000000000000000000715721505057307600142060ustar00rootroot00000000000000# Readsb This is a detached fork of https://github.com/Mictronics/readsb It's continually under development, expect bugs, segfaults and all the good stuff :) ## NO WARRANTY THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" see the LICENSE file for details ## how to install / build I'd recommend this script to automatically install it: - https://github.com/wiedehopf/adsb-scripts/wiki/Automatic-installation-for-readsb See the [Debian Package](##debian-package) section if you want to build the package yourself. Or check here on how to further install a webinterface and other useful stuff: - https://github.com/wiedehopf/adsb-wiki/wiki/Building-readsb-from-source - https://github.com/wiedehopf/adsb-wiki/wiki/Raspbian-Lite:-ADS-B-receiver For macOS build and info, check the [macOS](##macos) section ## Credits / history antirez (original dump1090) Malcom Robb (work on his dump1090 fork) mutability (forked to dump1090-mutability and further to dump1090-fa) Mictronics (readsb as a fork of dump1090-fa) wiedehopf (this fork of Mictronics readsb) ### aircraft.json format: [json file Readme](README-json.md) ### Push server support readsb connects to a listening server. Sending beast data (beast_out): ``` --net-connector 192.168.2.22,30004,beast_out ``` Receiving beast data (beast_in); ``` --net-connector 192.168.2.28,30005,beast_in ``` ### BeastReduce output Selectively forwards beast messages if the received data hasn't been forwarded in the last 125 ms (or `--net-beast-reduce-interval`). Data not related to the physical aircraft state are only forwarded every 500 ms (4 * `--net-beast-reduce-interval`).The messages of this output are normal beast messages and compatible with every program able to receive beast messages. This is used by some aggregators to aggregate ADS-B data, an example net connector would be: ``` --net-connector=feed.airplanes.live,30004,beast_reduce_plus_out,uuid=0033062d-e17e-4389-91a9-79ebb967fb4c ``` The uuid is optional, if none is given, the uuid from --uuid-file is used, if that isn't present no uuid is sent. The beast_reduce_out net-connector will never send an uuid. The aggregator enables --net-receiver-id and --net-ingest on their readsb server, it's made to work with beast_reduce_plus_out. ## Debian package - Build and install with rtlsdr support: ``` sudo apt update sudo apt install --no-install-recommends --no-install-suggests -y \ git build-essential debhelper libusb-1.0-0-dev pkg-config fakeroot \ libncurses-dev zlib1g-dev zlib1g libzstd-dev libzstd1 \ librtlsdr-dev git clone --depth 20 https://github.com/wiedehopf/readsb.git cd readsb export DEB_BUILD_OPTIONS=noddebs rm -f ../readsb_*.deb dpkg-buildpackage -b -ui -uc -us --build-profiles=rtlsdr sudo dpkg -i ../readsb_*.deb ``` - Build package with no additional receiver library dependencies: `dpkg-buildpackage -b -ui -uc -us`. - Build with RTLSDR support: `dpkg-buildpackage -b -ui -uc -us --build-profiles=rtlsdr` - Build with all the support: `dpkg-buildpackage -b -ui -uc -us --build-profiles=with_sdrs` required build deps (omit last line if you're not building with the various SDR support) ``` git build-essential debhelper libusb-1.0-0-dev pkg-config fakeroot \ libncurses-dev zlib1g-dev zlib1g libzstd-dev libzstd1 \ librtlsdr-dev libsoapysdr-dev libhackrf-dev libbladerf-dev libad9361-dev libiio-dev ``` ## Building manually You can probably just run "make". By default "make" builds with no specific library support. See below. Binaries are built in the source directory; you will need to arrange to install them (and a method for starting them) yourself. "make RTLSDR=yes" will enable rtl-sdr support and add the dependency on librtlsdr. On Raspbian 32 bit, mostly rpi2 and older you might want to use this to compile if you're running into CPU issues: ``` make RTLSDR=yes OPTIMIZE="-Ofast -mcpu=arm1176jzf-s -mfpu=vfp" ``` In general if you want to save on CPU cycles, you can try building with these options: ``` make AIRCRAFT_HASH_BITS=11 RTLSDR=yes OPTIMIZE="-O3 -march=native" ``` The difference of using -Ofast or -O3 over the default of -O2 is likely very minimal. -march=native also usually makes little difference but it might, so it's worth a try. ## Configuration If required, edit `/etc/default/readsb` to set the service options, device type, network ports etc. ## Autogain For rtl-sdr devices a software gain algorithm is the default, it's optimized for ADS-B. On the command line it's activated using `--gain=auto` an is silent by default. `--gain=auto-verbose` can be used to enable log messages for gain changes. To tweak the internals, more parameters can be passed: ``` --gain=auto-verbose,,,, ``` The defaults are: ``` --gain=auto-verbose,0,27,31,243 ``` The thresholds are numbers 0 to 256, tweaking them requires some understanding of how it works. One option would be to change the noise thresholds up or down and then observe the log. There should be no need to tweak these parameters. ## rtl-sdr bias tee Use this utility independent of readsb: https://github.com/wiedehopf/adsb-wiki/wiki/RTL-Bias-Tee ## Global map of aircraft One of this forks main uses is to be the backend of a global map. For that purpose it's used in conjunction with tar1090 with some extra options to cope with the number of aircraft and also record a history of flight paths: https://github.com/wiedehopf/tar1090#0800-destroy-sd-card Websites using this software: - https://adsb.lol/ - https://globe.airplanes.live/ - https://globe.adsb.fi/ - https://globe.adsbexchange.com/ Projects using this softare: - https://sdr-enthusiasts.gitbook.io/ads-b/ - https://github.com/sdr-enthusiasts/docker-adsb-ultrafeeder - https://adsb.im/ (indirectly via an SDR-E container) Projects that use or have used data generated by this software: - https://gpsjam.org/ - https://adsb.exposed/ - https://tech.marksblogg.com/global-flight-tracking-adsb.html ## Saving traces History function as used by several online aggregators using tar1090. Warning: the following will generate several thousand files a day and can use significant amounts of disk space depending on your data source. The following command line options need to be added to for example the decoder options in `/etc/default/readsb` ``` --write-globe-history /var/globe_history --heatmap 30 ``` To increase time resolution to maximum, you can add `--json-trace-interval=0.1` which will add every position received to traces. The heatmap interval can also be reduced from the default of 30 seconds, i wouldn't recommend less than 5 seconds for that though. Aggregators will generally use `--write-json-globe-index` as well but that's not necessary if you don't have more than 500 concurrent planes. /var/globe_history needs to be a directory writeable by the user readsb. `sudo mkdir /var/globe_history` and `sudo chown readsb /var/globe_history` are useful for that. You should also download ``` wget -O /usr/local/share/tar1090/aircraft.csv.gz https://github.com/wiedehopf/tar1090-db/raw/csv/aircraft.csv.gz ``` and add this command line option (for exaple via /etc/default/readsb): ``` --db-file /usr/local/share/tar1090/aircraft.csv.gz ``` This will obviously write data to the hard drive, be aware of that. The data format is subject to change, don't expect this to be stable. Be aware of that when upgrading either tar1090 or readsb to a new commit. If you're using --write-json-globe-index, it's also recommended to use --tar1090-use-api It will use the readsb API to get data, it's less requests and usually more efficient, for details see the file nginx-readsb-api.conf (this needs adding to your existing nginx tar1090 configuration, this is only for people who really know their stuff anyway) If configuring this stuff seems complicated, consider just using the sdr-enthusiasts ultrafeeder container. Just don't configure feeds for aggregated data or if you don't want to feed data from there. ## non-SDR data source If you don't want readsb to read data from the SDR, you'll also need to change the receiver options line to something like this: ``` RECEIVER_OPTIONS="--net-only --net-connector 192.168.2.7,30005,beast_in" ``` If you have another dump1090/readsb running on the same machine, you'll also need to change all the ports to avoid conflicts. ## --debug=S: speed check debugging output For current reference please see the speed_check function. hex SQ means same quality (ADS-B vs MLAT and stuff) LQ means lower quality fail / ok ok means speed check passed (displayed only with cpr-focus) A means airborne and S means surface. reliable is my reliability counter every good position increases each aircrafts position reliability, if it gets to zero, speed check is no longer applied and it's allowed to "JUMP", "JUMP" is also allowed if we haven't had a position for 2 minutes. tD is the trackDifference 170 or 180 means the new position goes in the opposite direction of the ground track broadcast by the aircraft. then we have actual distance / allowed distance. the allowed distance i tweak depending on the trackDifference high trackDifference makes the allowed distance go slightly negative as i don't want aircraft to jump backwards. elapsed time actual / allowed speed (allowed speed based on allowed distance) old --> new lat, lon -> lat, lon oh if you want that display: --debug=S you'll have to update, just disabled the MLAT speed check from displayign stuff ... because usually it's not interesting ## macOS Thank you to https://github.com/ind006/readsb_macos/ for all the shims needed to make this work. readsb should be (largely) compatible with macOS, on both Intel and ARM architectures. There is a recipe for homebrew avalailable: https://formulae.brew.sh/formula/readsb Run `brew install readsb` to install readsb using homebrew. Or compile manually roughly like this: You may need to modify the Makefile to point at where on your mac you have the standard libs and includes installed. This in turn depends on whether you're using macports or homebrew for those libs and includes. The Makefile has paths pre-set for homebrew. These packages are needed, possibly more: ``` git librtlsdr libusb ncurses ``` Build using: ``` make -j4 RTLSDR=yes ``` You can run it from the command line (try `screen -S readsb` and run it in there, press ctrl-A to detach the terminal) Example command line: ```sh ./readsb --quiet --net --device-type rtlsdr --gain auto # add console table of planes for a quick test: --interactive # optionally add coordinates: --lat -33.874 --lon 151.206 # add --interactive for testing, it will show a list of planes in the terminal # optional output of json and other regularly updated files: --write-json-every 0.5 --write-json=~/tar1090/html/data/ # optional database info --db-file ~/tar1090/aircraft.csv.gz # optional listen ports: --net-ri-port 30001 --net-ro-port 30002 --net-sbs-port 30003 --net-bi-port 30004,30104 --net-bo-port 30005 # optional sending of data to an aggregator: --net-connector feed.flyrealtraffic.com,30004,beast_reduce_plus_out,7817bd08-f226-11ef-ba9e-072eee452592 ``` For a graphical interface, the tar1090 webinterface is recommended: https://github.com/wiedehopf/tar1090 The install script won't work so i'd recommend the following basic webserver configuration: - serve the html directory as /tar1090 - serve the write-json directory as /tar1090/data ``` git clone --depth 1 https://github.com/wiedehopf/tar1090 ~/tar1090 wget -O ~/tar1090/aircraft.csv.gz https://github.com/wiedehopf/tar1090-db/raw/csv/aircraft.csv.gz ``` Simple http server using python reachable using http://localhost:8081 ``` cd ~/tar1090/html python3 -m http.server 8081 ``` Using nginx this would look something like this (replace USER appropriately): ``` location /tar1090/data/ { alias /home/USER/tar1090/data/; add_header Cache-Control "no-cache"; location /tar1090/data/traces/ { gzip off; add_header Content-Encoding "gzip"; add_header Cache-Control "no-cache"; } } location /tar1090/globe_history/ { alias /home/USER/readsb_history/; gzip off; add_header Content-Encoding "gzip"; add_header Cache-Control "no-cache"; } location /tar1090 { alias /home/USER/tar1090/html/; add_header Cache-Control "no-cache"; } ``` An easy way to add some traces when selecting a plane (with nginx): Add `--write-globe-history=/home/USER/readsb_history` to the readsb command line. You can also serve this folder as /tar1090/globe_history but that's only required for the history going back further than 24h. The classical tar1090 uses traces created via a shell script and served at /tar1090/chunks but running that shell script is probably a hassle, so just use the above. ## readsb --help might be out of date, check the command on a freshly compiled version ``` Usage: readsb [OPTIONS...] readsb Mode-S/ADSB/TIS Receiver Build options: ENABLE_RTLSDR General options: --lat= Reference/receiver surface latitude --lon= Reference/receiver surface longitude --no-interactive Disable interactive mode, print to stdout --interactive-ttl= Remove from list if idle for (default: 60) --modeac Enable decoding of SSR Modes 3/A & 3/C --modeac-auto Enable Mode A/C if requested by a Beast connection --max-range= Absolute maximum range for position decoding (in nm, default: 300) --fix Enable CRC single-bit error correction (default) --no-fix Disable CRC single-bit error correction --no-fix-df Disable CRC single-bit error correction on the DF type to produce more DF17 messages (disabling reduces CPU usage) --metric Use metric units --show-only= Show only messages by given ICAO on stdout --process-only= Process only messages by given ICAO --filter-DF= When displaying decoded ModeS messages on stdout only show this DF type --device-type= Select SDR type (this needs to be placed on the command line before any SDR type specific options) --gain= Set gain (default: auto gain, possible values for rtl-sdr devices: auto auto-verbose 0.0 0.9 1.4 2.7 3.7 7.7 8.7 12.5 14.4 15.7 16.6 19.7 20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4 37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6 58) --freq= Set frequency (default: 1090 MHz) --interactive Interactive mode refreshing data on screen. Implies --throttle --raw Show only messages hex values --preamble-threshold=<40-400> lower threshold --> more CPU usage (default: 58, pi zero / pi 1: 75, hot CPU 42) --forward-mlat Forward received beast mlat results to beast output ports --forward-mlat-sbs Forward received mlat results to sbs output ports --stats Print stats at exit. No other output --stats-range Collect/show range histogram --stats-every= Show and reset stats every seconds (rounded to the nearest 10 seconds due to implementation, first inteval can be up to 5 seconds shorter) --auto-exit= Run for X seconds, then exit (default: run indefinitely) --range-outline-hours= Make the range outline retain data for the last X hours (float, default: 24.0) --onlyaddr Show only ICAO addresses --gnss Show altitudes as GNSS when available --snip= Strip IQ file removing samples < level --debug= Debug mode (verbose), n: network, P: CPR, S: speed check --devel= Development debugging mode, see source for options, can be specified more than once --receiver-focus= only process messages from receiverId --cpr-focus= show CPR details for this hex --leg-focus= show leg marking details for this hex --trace-focus= show traceAdd details for this hex --quiet Disable output (default) --write-json= Periodically write json output to --write-prom= Periodically write prometheus output to --write-globe-history= Write traces to this directory, 1 gz compressed json per day and airframe --write-state= Write state to disk to have traces after a restart --write-state-every= Continuously write state to disk every X seconds (default: 3600) --write-state-only-on-exit Don't continously update state. --heatmap-dir= Change the directory where heatmaps are saved (default is in globe history dir) --heatmap= Make Heatmap, each aircraft at most every interval seconds (creates historydir/heatmap.bin and exit after that) --dump-beast=,, Dump compressed beast files to this directory, start a new file evey interval seconds --write-json-every= Write json output and update API json every sec seconds (default 1) --json-location-accuracy= Accuracy of receiver location in json metadata: 0=no location, 1=approximate, 2=exact --ac-hash-bits= Main hash map size: 2^n entries (default: AIRCRAFT_HASH_BITS) --write-json-globe-index Write specially indexed globe_xxxx.json files (for tar1090) --write-receiver-id-json Write receivers.json --json-trace-interval= Interval after which a new position will guaranteed to be written to the trace and the json position output (default: 30) --json-trace-hist-only=1,2,3,8 Don't write recent(1), full(2), either(3) traces to /run, only archive via write-globe-history (8: irregularly write limited traces to run, subject to change) --write-json-gzip Write aircraft.json also as aircraft.json.gz --write-json-binCraft-only= Use only binary binCraft format for globe files (1), for aircraft.json as well (2) --write-binCraft-old write old gzipped binCraft files --json-reliable= Minimum position reliability to put it into json (default: 1, globe options will default set this to 2, disable speed filter: -1, max: 4) --position-persistence= Position persistence against outliers (default: 4), incremented by json-reliable minus 1 --jaero-timeout= How long in minutes JAERO positions remain valid and on the map in tar1090 (default:33) --db-file= Default: "none" (as of writing a compatible file is available here: https://github.com/wiedehopf/tar1090-db/tree/csv) --db-file-lt aircraft.json: add long type as field desc, add field ownOp for the owner, add field year Network options: --net-connector= Establish connection, can be specified multiple times (e.g. 127.0.0.1,23004,beast_out) Protocols: beast_out, beast_in, raw_out, raw_in, sbs_in, sbs_in_jaero, sbs_out, sbs_out_jaero, vrs_out, json_out, gpsd_in, uat_in, uat_replay_out, planefinder_in, asterix_in, asterix_out (one failover ip/address,port can be specified: primary-address,primary-port,protocol,failover-address,failover-port) (any position in the comma separated list can also be either silent_fail or uuid=) --net Enable networking --net-only Legacy Option, Enable networking, use --net instead --net-bind-address= IP address to bind to (default: Any; Use 127.0.0.1 for private) --net-bo-port= TCP Beast output listen ports (default: 0) --net-bi-port= TCP Beast input listen ports (default: 0) --net-ro-port= TCP raw output listen ports (default: 0) --net-ri-port= TCP raw input listen ports (default: 0) --net-uat-replay-port= UAT replay output listen ports (default: 0) --net-uat-in-port= UAT input listen ports (default: 0) --net-sbs-port= TCP BaseStation output listen ports (default: 0) --net-sbs-in-port= TCP BaseStation input listen ports (default: 0) --net-sbs-jaero-port= TCP SBS Jaero output listen ports (default: 0) --net-sbs-jaero-in-port= TCP SBS Jaero input listen ports (default: 0) --net-asterix-out-port= TCP Asterix output listen ports (default: 0) --net-asterix-in-port= TCP Asterix input listen ports (default: 0) --net-asterix-reduce Apply beast reduce logic and interval to ASTERIX outputs --net-vrs-port= TCP VRS json output listen ports (default: 0) --net-vrs-interval= TCP VRS json output interval (default: 5.0) --net-json-port= TCP json position output listen ports, sends one line with a json object containing aircraft details for every position received (default: 0) (consider raising --net-ro-size to 8192 for less fragmentation if this is a concern) --net-json-port-interval= Set minimum interval between outputs per aircraft for TCP json output, default: 0.0 (every position) --net-json-port-include-noposition TCP json position output: include aircraft without position (state is sent for aircraft for every DF11 with CRC if the aircraft hasn't sent a position in the last 10 seconds and interval allowing) --net-api-port= TCP API listen port (in contrast to other listeners, only a single port is allowed) (update frequency controlled by write-json-every parameter) (default: 0) --api-shutdown-delay= Shutdown delay to server remaining API queries, new queries get a 503 response (default: 0) --tar1090-use-api when running with globe-index, signal tar1090 use the readsb API to get data, requires webserver mapping of /tar1090/re-api to proxy_pass the requests to the --net-api-port, see nginx-readsb-api.conf in the tar1090 repository for details --net-beast-reduce-out-port= TCP BeastReduce output listen ports (default: 0) --net-beast-reduce-interval= BeastReduce data update interval, longer means less data (default: 0.250, valid range: 0.000 - 14.999) --net-beast-reduce-optimize-for-mlat BeastReduce output: keep all messages relevant to mlat-client --net-beast-reduce-filter-dist= beast-reduce: remove aircraft which are further than distance from the receiver --net-beast-reduce-filter-alt= beast-reduce: remove aircraft which are above altitude --net-sbs-reduce Apply beast reduce logic and interval to SBS outputs --net-receiver-id forward receiver ID --net-ingest primary ingest node --net-garbage= timeout receivers, output messages from timed out receivers as beast on --decode-threads= Number of decode threads, either 1 or 2 (default: 1). Only use 2 when you have beast traffic > 200 MBit/s, expect 1.4x speedup for 2x CPU --uuid-file= path to UUID file --net-ro-size= TCP output flush size (maximum amount of internally buffered data before writing to network) (default: 1280) --net-ro-interval= TCP output flush interval in seconds (maximum delay between placing data in the output buffer and sending)(default: 0.05, valid values 0.0 - 1.0) --net-ro-interval-beast-reduce= TCP output flush interval in seconds for beast-reduce outputs (default: value from --net-ro-interval, valid values 0.0 - 1.0) --net-connector-delay= Outbound re-connection delay (default: 15) --net-heartbeat= TCP heartbeat rate in seconds (default: 60 sec; 0 to disable) --net-buffer= control some buffer sizes: 8KB * (2^n) (default: n=1, 16KB) --net-verbatim Forward messages unchanged --sdr-buffer-size= SDR buffer / USB transfer size in kibibytes (default: 256 which is equivalent to around 54 ms using rtl-sdr, option might be ignored in future versions) RTL-SDR options: use with --device-type rtlsdr --device= Select device by index or serial number --enable-agc Enable digital AGC (not tuner AGC!) --ppm= Set oscillator frequency correction in PPM Modes-S Beast options, use with --device-type modesbeast: --beast-serial= Path to Beast serial device (default /dev/ttyUSB0) --beast-df1117-on Turn ON DF11/17-only filter --beast-mlat-off Turn OFF MLAT time stamps --beast-crc-off Turn OFF CRC checking --beast-df045-on Turn ON DF0/4/5 filter --beast-fec-off Turn OFF forward error correction --beast-modeac Turn ON mode A/C --beast-baudrate= Override Baudrate (default rate 3000000 baud) GNS HULC options, use with --device-type gnshulc: Beast binary and HULC protocol input with hardware handshake enabled. --beast-serial= Path to GNS HULC serial device (default /dev/ttyUSB0) ifile-specific options, use with --device-type ifile: --ifile= Read samples from given file ('-' for stdin) --iformat= Set sample format (UC8, SC16, SC16Q11) --throttle Process samples at the original capture speed Help options: --help Give this help list --usage Give a short usage message ``` readsb-3.16/aircraft.c000066400000000000000000000676251505057307600146720ustar00rootroot00000000000000#include "readsb.h" void freeAircraftBack() { while (Modes.aircraftBack) { struct aircraftBack *prev = Modes.aircraftBack->prev; if (Modes.thp) { cmMunmap(Modes.aircraftBack, aircraftBackAlloc); } else { sfree(Modes.aircraftBack); } Modes.aircraft_data_size -= aircraftBackAlloc; Modes.aircraftBack = prev; } } static inline void lockBack() { pthread_mutex_lock(&Modes.aircraftBackMutex); //spinLock(&Modes.aircraftBackSpinlock); } static inline void unlockBack() { pthread_mutex_unlock(&Modes.aircraftBackMutex); //spinRelease(&Modes.aircraftBackSpinlock); } static struct aircraft *allocAircraft() { struct aircraft *a = NULL; lockBack(); if (Modes.aircraftBackFree) { //fprintf(stderr, "alloc from freelist\n"); a = Modes.aircraftBackFree; Modes.aircraftBackFree = a->next; unlockBack(); return a; } if (!Modes.aircraftBack) { Modes.aircraft_data_size += aircraftBackAlloc; if (Modes.thp) { Modes.aircraftBack = cmMmap(aircraftBackAlloc); } else { Modes.aircraftBack = cmalloc(aircraftBackAlloc); } Modes.aircraftBack->used = 0; Modes.aircraftBack->prev = NULL; Modes.aircraftBack->next = NULL; } if (Modes.aircraftBack->used >= aircraftBackCap) { struct aircraftBack *prev = Modes.aircraftBack; Modes.aircraft_data_size += aircraftBackAlloc; if (Modes.thp) { Modes.aircraftBack = cmMmap(aircraftBackAlloc); } else { Modes.aircraftBack = cmalloc(aircraftBackAlloc); } Modes.aircraftBack->used = 0; Modes.aircraftBack->prev = prev; prev->next = Modes.aircraftBack; Modes.aircraftBack->next = NULL; } if (!Modes.aircraftBack || Modes.aircraftBack->used >= aircraftBackCap) { fprintf(stderr, "FATAL allocAircraft\n"); abort(); } //fprintf(stderr, "alloc fresh\n"); a = &(Modes.aircraftBack->store[Modes.aircraftBack->used]); Modes.aircraftBack->used++; unlockBack(); return a; } static void deallocAircraft(struct aircraft *a) { if (Modes.quickFree) { return; } lockBack(); a->next = Modes.aircraftBackFree; Modes.aircraftBackFree = a; unlockBack(); } static int isMilRange(uint32_t i); static void updateDetails(struct aircraft *curr, struct char_buffer cb, uint32_t offset); static inline uint32_t dbHash(uint32_t addr) { return addrHash(addr, DB_HASH_BITS); } static inline uint32_t aircraftHash(uint32_t addr) { return addrHash(addr, Modes.acHashBits); } #define EMPTY 0xFFFFFFFF #define quickMinBits 8 #define quickMaxBits 16 #define quickStride 8 static int quickBits; static int quickBuckets; static int quickSize; struct ap { uint32_t addr; struct aircraft *ptr; }; static struct ap *quick; static void quickResize(int bits) { quickBits = bits; quickBuckets = (1LL << bits) + quickStride; quickSize = sizeof(struct ap) * quickBuckets; if (quickBuckets > 64000) fprintf(stderr, "quickLookup: changing size to %d!\n", (int) quickBuckets); sfree(quick); quick = cmalloc(quickSize); memset(quick, 0xFF, quickSize); } static struct ap *quickGet(uint32_t addr) { uint32_t hash = addrHash(addr, quickBits); for (unsigned i = 0; i < quickStride; i++) { struct ap *q = &(quick[hash + i]); if (q->addr == addr) { return q; } } return NULL; } static void quickMaintenance() { for (int i = 0; i < quickBuckets; i++) { struct ap *q = &(quick[i]); if (q->addr != EMPTY) { if (!q->ptr->onActiveList) { q->addr = EMPTY; q->ptr = NULL; } } } } void quickRemove(struct aircraft *a) { struct ap *q = quickGet(a->addr); if (q) { q->addr = EMPTY; q->ptr = NULL; } //fprintf(stderr, "r: %06x\n", a->addr); } void quickAdd(struct aircraft *a) { struct ap *q = quickGet(a->addr); if (q) return; uint32_t hash = addrHash(a->addr, quickBits); for (unsigned i = 0; i < quickStride; i++) { q = &quick[hash + i]; if (q->addr == EMPTY) { q->addr = a->addr; q->ptr = a; return; } } } void quickInit() { if (quickBits > quickMinBits && Modes.aircraftActive.len < quickBuckets * 2 / 8) { quickResize(quickBits - 1); } else if (quickBits < quickMinBits) { quickResize(quickMinBits); } else if (quickBits < quickMaxBits && Modes.aircraftActive.len > quickBuckets * 5 / 8) { quickResize(quickBits + 1); } static int64_t nextMaintenance; int64_t mono = mono_milli_seconds(); if (mono > nextMaintenance) { quickMaintenance(); nextMaintenance = mono + 1 * MINUTES; } /* for (int i = 0; i < quickBuckets; i++) { if (quick[i].addr == EMPTY) fprintf(stderr, " "); else fprintf(stderr, "."); } fprintf(stderr, "\n"); */ } void quickDestroy() { sfree(quick); } struct aircraft *aircraftGet(uint32_t addr) { struct ap *q = quickGet(addr); if (q) { return q->ptr; } struct aircraft *a = Modes.aircraft[aircraftHash(addr)]; while (a && a->addr != addr) { a = a->next; } if (a) { quickAdd(a); } return a; } void freeAircraft(struct aircraft *a) { if (Modes.quickFree) { traceCleanupNoUnlink(a); deallocAircraft(a); return; } quickRemove(a); // remove from the globeList set_globe_index(a, -5); if (a->onActiveList) { ca_remove(&Modes.aircraftActive, a); } traceCleanup(a); memset(a, 0x0, sizeof (struct aircraft)); deallocAircraft(a); } void aircraftZeroTail(struct aircraft *a) { memset(&a->zeroStart, 0x0, &a->zeroEnd - &a->zeroStart); } struct aircraft *aircraftCreate(uint32_t addr) { struct aircraft *a = aircraftGet(addr); if (a) { return a; } a = allocAircraft(); // Default everything to zero/NULL memset(a, 0, sizeof (struct aircraft)); // Now initialise things that should not be 0/NULL to their defaults a->addr = addr; a->addrtype = ADDR_UNKNOWN; // defaults until we see a message otherwise a->adsb_version = -1; a->adsb_hrd = HEADING_MAGNETIC; a->adsb_tah = HEADING_GROUND_TRACK; if (Modes.json_globe_index) { a->globe_index = -5; } // initialize data validity ages //adjustExpire(a, 58); updateTypeReg(a); uint32_t hash = aircraftHash(addr); // this isn't needed as this happens on separate areas of the hashtable when it's called in // parallel //pthread_mutex_lock(&Modes.aircraftCreateMutex); a->next = Modes.aircraft[hash]; Modes.aircraft[hash] = a; //pthread_mutex_unlock(&Modes.aircraftCreateMutex); return a; } void toBinCraft(struct aircraft *a, struct binCraft *new, int64_t now) { memset(new, 0, sizeof(struct binCraft)); new->hex = a->addr; new->seen = (int32_t) nearbyint((now - a->seen) / 100.0); new->callsign_valid = trackDataValid(&a->callsign_valid); for (unsigned i = 0; i < sizeof(new->callsign); i++) new->callsign[i] = a->callsign[i] * new->callsign_valid; if (Modes.db) { memcpy(new->registration, a->registration, sizeof(new->registration)); memcpy(new->typeCode, a->typeCode, sizeof(new->typeCode)); new->dbFlags = a->dbFlags; } new->extraFlags |= ((nogps(now, a)) << 0); if (Modes.json_globe_index || Modes.apiShutdownDelay) { new->messages = (uint16_t) nearbyint(10 * a->messageRate); } else { new->messages = (uint16_t) a->messages; } new->position_valid = trackDataValid(&a->pos_reliable_valid); if (new->position_valid || now < a->seenPosReliable + 14 * 24 * HOURS) { new->seen_pos = (int32_t) nearbyint((now - a->seenPosReliable) / 100.0); new->lat = (int32_t) nearbyint(a->latReliable * 1E6); new->lon = (int32_t) nearbyint(a->lonReliable * 1E6); new->pos_nic = a->pos_nic_reliable; new->pos_rc = a->pos_rc_reliable; } new->baro_alt_valid = altBaroReliable(a); new->baro_alt = (int16_t) nearbyint(a->baro_alt * BINCRAFT_ALT_FACTOR); new->geom_alt = (int16_t) nearbyint(a->geom_alt * BINCRAFT_ALT_FACTOR); new->baro_rate = (int16_t) nearbyint(a->baro_rate / 8.0); new->geom_rate = (int16_t) nearbyint(a->geom_rate / 8.0); new->ias = a->ias; new->tas = a->tas; new->squawk = a->squawk; new->category = a->category * (now < a->category_updated + Modes.trackExpireJaero); // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset new->nav_altitude_mcp = (uint16_t) nearbyint(a->nav_altitude_mcp / 4.0); new->nav_altitude_fms = (uint16_t) nearbyint(a->nav_altitude_fms / 4.0); new->nav_qnh = (int16_t) nearbyint(a->nav_qnh * 10.0); new->gs = (int16_t) nearbyint(a->gs * 10.0); new->mach = (int16_t) nearbyint(a->mach * 1000.0); new->track_rate = (int16_t) nearbyint(a->track_rate * 100.0); new->roll = (int16_t) nearbyint(a->roll * 100.0); if (trackDataValid(&a->track_valid)) new->track = (int16_t) nearbyint(a->track * 90.0); else new->track = (int16_t) nearbyint(a->calc_track * 90.0); new->mag_heading = (int16_t) nearbyint(a->mag_heading * 90.0); new->true_heading = (int16_t) nearbyint(a->true_heading * 90.0); new->nav_heading = (int16_t) nearbyint(a->nav_heading * 90.0); new->emergency = a->emergency; new->airground = a->airground * trackDataValid(&a->airground_valid); new->addrtype = a->addrtype; new->nav_modes = a->nav_modes; new->nav_altitude_src = a->nav_altitude_src; new->sil_type = a->sil_type; new->wind_valid = (now < a->wind_updated + TRACK_EXPIRE && abs(a->wind_altitude - a->baro_alt) < 500); new->wind_direction = (int) nearbyint(a->wind_direction) * new->wind_valid; new->wind_speed = (int) nearbyint(a->wind_speed) * new->wind_valid; new->temp_valid = (now < a->oat_updated + TRACK_EXPIRE); new->oat = (int) nearbyint(a->oat) * new->temp_valid; new->tat = (int) nearbyint(a->tat) * new->temp_valid; if (a->adsb_version < 0) new->adsb_version = 15; else new->adsb_version = a->adsb_version; if (a->adsr_version < 0) new->adsr_version = 15; else new->adsr_version = a->adsr_version; if (a->tisb_version < 0) new->tisb_version = 15; else new->tisb_version = a->tisb_version; new->nic_a = a->nic_a; new->nic_c = a->nic_c; new->nic_baro = a->nic_baro; new->nac_p = a->nac_p; new->nac_v = a->nac_v; new->sil = a->sil; new->gva = a->gva; new->sda = a->sda; new->alert = a->alert; new->spi = a->spi; //new->signal = get8bitSignal(a); old version new->signal = nearbyint((getSignal(a) + 50.0f) * (255.0f / 50.0f)); //fprintf(stderr, "%0.1f %u\n", getSignal(a), new->signal); #if defined(TRACKS_UUID) new->receiverId = (uint32_t) (a->receiverId >> 32); #endif if (Modes.netReceiverId) { new->receiverCount = a->receiverCount; } #define F(f) do { new->f##_valid = trackDataValid(&a->f##_valid); new->f *= new->f##_valid; } while (0) F(geom_alt); F(gs); F(ias); F(tas); F(mach); F(track); F(track_rate); F(roll); F(mag_heading); F(true_heading); F(baro_rate); F(geom_rate); F(nic_a); F(nic_c); F(nic_baro); F(nac_p); F(nac_v); F(sil); F(gva); F(sda); F(squawk); F(emergency); F(nav_qnh); F(nav_altitude_mcp); F(nav_altitude_fms); F(nav_altitude_src); F(nav_heading); F(nav_modes); F(alert); F(spi); #undef F } // rudimentary sanitization so the json output hopefully won't be invalid static inline void sanitize(char *str, int len) { unsigned char b2 = (1<<7) + (1<<6); // 2 byte code or more unsigned char b3 = (1<<7) + (1<<6) + (1<<5); // 3 byte code or more unsigned char b4 = (1<<7) + (1<<6) + (1<<5) + (1<<4); // 4 byte code int debug = 0; // UTF that goes beyond our string is cut off by terminating the string if (len >= 3 && (str[len - 3] & b4) == b4) { if (debug) { fprintf(stderr, "b4%d\n", str[len - 3]); } str[len - 3] = '\0'; } if (len >= 2 && (str[len - 2] & b3) == b3) { if (debug) { fprintf(stderr, "b3%d\n", str[len - 2]); } str[len - 2] = '\0'; } if (len >= 1 && (str[len - 1] & b2) == b2) { if (debug) { fprintf(stderr, "b2%d\n", str[len - 1]); } str[len - 1] = '\0'; } char *p = str; // careful str might be unterminated, use len while(p - str < len) { if (*p == '"') { //if (debug) { fprintf(stderr, "quotation marks: %s\n", str); } // replace with single quote *p = '\''; } if (*p > 0 && *p < 0x1f) { if (debug) { fprintf(stderr, "non-printable: %s\n", str); } // replace with space *p = ' '; } p++; } if (p - 1 >= str && *(p - 1) == '\\') { *(p - 1) = '\0'; } } static char *sprintDB2(char *p, char *end, dbEntry *d) { struct aircraft aBack; struct aircraft *a = &aBack; updateDetails(a, Modes.db2Raw, d->rawOffset); a->addr = d->addr; p = safe_snprintf(p, end, "\n\"%s%06x\":{", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); char *regInfo = p; if (a->registration[0]) p = safe_snprintf(p, end, "\"r\":\"%.*s\",", (int) sizeof(a->registration), a->registration); if (a->typeCode[0]) p = safe_snprintf(p, end, "\"t\":\"%.*s\",", (int) sizeof(a->typeCode), a->typeCode); if (a->typeLong[0]) p = safe_snprintf(p, end, "\"desc\":\"%.*s\",", (int) sizeof(a->typeLong), a->typeLong); if (a->dbFlags) p = safe_snprintf(p, end, "\"dbFlags\":%u,", a->dbFlags); if (a->ownOp[0]) p = safe_snprintf(p, end, "\"ownOp\":\"%.*s\",", (int) sizeof(a->ownOp), a->ownOp); if (a->year[0]) p = safe_snprintf(p, end, "\"year\":\"%.*s\",", (int) sizeof(a->year), a->year); if (p == regInfo) p = safe_snprintf(p, end, "\"noRegData\":true,"); if (*(p-1) == ',') p--; p = safe_snprintf(p, end, "},"); return p; } static void db2ToJson() { size_t buflen = 32 * 1024 * 1024; char *buf = (char *) cmalloc(buflen), *p = buf, *end = buf + buflen; p = safe_snprintf(p, end, "{"); for (int j = 0; j < DB_BUCKETS; j++) { if (0 && j % 1000 == 0) { fprintf(stderr, "db print %d\n", j); } for (dbEntry *d = Modes.db2Index[j]; d; d = d->next) { p = sprintDB2(p, end, d); if ((p + 1000) >= end) { int used = p - buf; buflen *= 2; buf = (char *) realloc(buf, buflen); p = buf + used; end = buf + buflen; } } } if (*(p-1) == ',') p--; p = safe_snprintf(p, end, "\n}"); struct char_buffer cb2; cb2.len = p - buf; cb2.buffer = buf; writeJsonToFile(Modes.json_dir, "db.json", cb2); // location changed free(buf); } // get next CSV token based on the assumption eot points to the previous delimiter static inline int nextToken(char delim, char **sot, char **eot, char **eol) { *sot = *eot + 1; if (*sot >= *eol) return 0; *eot = memchr(*sot, delim, *eol - *sot); if (!*eot) return 0; return 1; } static int is_df18_exception(uint32_t addr) { switch (addr) { case 0xa08508: case 0xab33a0: case 0xa7d24c: case 0xa6e2cd: case 0xaa8fca: case 0xac808b: case 0x48f6f7: case 0x7cbc3d: case 0x7c453a: case 0x401cf9: case 0x40206a: case 0xa3227d: case 0x478676: case 0x40389d: case 0x405acf: case 0xc82452: case 0x40334a: return 1; default: return 0; } } // meant to be used with this DB: https://raw.githubusercontent.com/wiedehopf/tar1090-db/csv/aircraft.csv.gz int dbUpdate(int64_t now) { static int64_t next_db_check; if (now < next_db_check) { return 0; } // db update check every 30 seconds next_db_check = now + 30 * SECONDS; // this update only takes effect in dbFinishUpdate with all other threads locked char *filename = Modes.db_file; if (!filename) { return 0; } struct timespec watch; startWatch(&watch); gzFile gzfp = NULL; struct char_buffer cb = {0}; int fd = open(filename, O_RDONLY); if (fd == -1) { fprintf(stderr, "dbUpdate: open db-file failed:"); perror(filename); return 0; } //fprintf(stderr, "checking for db Update\n"); struct stat fileinfo = {0}; if (fstat(fd, &fileinfo)) { fprintf(stderr, "%s: dbUpdate: fstat failed, wat?!\n", filename); goto DBU0; } int64_t modTime = fileinfo.st_mtim.tv_sec; if (Modes.dbModificationTime == modTime) goto DBU0; gzfp = gzdopen(fd, "r"); if (!gzfp) { fprintf(stderr, "db update error: gzdopen failed.\n"); goto DBU0; } cb = readWholeGz(gzfp, filename); if (!cb.buffer) { fprintf(stderr, "database read failed due to readWholeGz.\n"); goto DBU0; } if (cb.len < 1000) { fprintf(stderr, "database file very small, bailing out of dbUpdate.\n"); goto DBU0; } if (1) { // reallocate so we don't waste memory char *oldBuffer = cb.buffer; cb.len++; // for adding zero termination cb.buffer = realloc(cb.buffer, cb.len); if (!cb.buffer) { fprintf(stderr, "database read failed due to realloc\n"); sfree(oldBuffer); goto DBU0; } cb.buffer[cb.len - 1] = '\0'; // zero terminate for good measure } int alloc = 0; // memchr is not faster, seems the compiler is smart enough to optimize a simple loop that counts the newlines for (uint32_t i = 0; i < cb.len; i++) { if (cb.buffer[i] == '\n') alloc++; } int indexSize = DB_BUCKETS * sizeof(void*); int entriesSize = alloc * sizeof(dbEntry); Modes.db2 = cmalloc(entriesSize); Modes.db2Raw = cb; Modes.db2Index = cmalloc(indexSize); memset(Modes.db2Index, 0, indexSize); if (0) { fprintf(stderr, "db mem usage: total %d index %d entries %d text %d kB\n", indexSize / 1024 + entriesSize / 1024 + (int) (cb.len / 1024), indexSize / 1024, entriesSize / 1024, (int) (cb.len / 1024)); } if (!Modes.db2 || !Modes.db2Index) { fprintf(stderr, "db update error: malloc failure!\n"); goto DBU0; } fprintf(stderr, "Database update in progress!\n"); char *eob = cb.buffer + cb.len; char *sol = cb.buffer; char *eol; int i; for (i = 0; eob > sol && (eol = memchr(sol, '\n', eob - sol)); sol = eol + 1) { char *sot; char *eot = sol - 1; // this pointer must not be dereferenced, nextToken will increment it. dbEntry *curr = &Modes.db2[i]; memset(curr, 0, sizeof(dbEntry)); if (!nextToken(';', &sot, &eot, &eol)) continue; curr->addr = strtol(sot, NULL, 16); if (curr->addr == 0) continue; if (!nextToken(';', &sot, &eot, &eol)) continue; curr->rawOffset = sot - cb.buffer; i++; // increment db array index // add to hashtable dbPut(curr->addr, Modes.db2Index, curr); } if (i < 1) { fprintf(stderr, "db update error: DB has no entries, maybe old / incorrect format?!\n"); goto DBU0; } //fflush(stdout); //fprintf(stderr, "dbUpdate() done\n"); gzclose(gzfp); Modes.dbModificationTime = modTime; if (Modes.json_dir) { free(writeJsonToFile(Modes.json_dir, "receiver.json", generateReceiverJson()).buffer); } double elapsed = stopWatch(&watch) / 1000.0; fprintf(stderr, "Database update first part took %.3f seconds!\n", elapsed); // write database to json dir for testing if (Modes.json_dir && Modes.debug_dbJson) { db2ToJson(); } return 1; DBU0: if (gzfp) gzclose(gzfp); free(cb.buffer); free(Modes.db2); free(Modes.db2Index); Modes.db2 = NULL; Modes.db2Index = NULL; Modes.db2Raw.buffer = NULL; Modes.db2Raw.len = 0; close(fd); return 1; } static void updateTypeRegRange(void *arg, threadpool_threadbuffers_t *threadbuffers) { MODES_NOTUSED(threadbuffers); readsb_task_t *info = (readsb_task_t *) arg; //fprintf(stderr, "%d %d\n", info->from, info->to); for (int j = info->from; j < info->to; j++) { for (struct aircraft *a = Modes.aircraft[j]; a; a = a->next) { updateTypeReg(a); } } } int dbFinishUpdate() { if (!Modes.db2) { return 0; } // finish db update struct timespec watch; startWatch(&watch); sfree(Modes.dbIndex); sfree(Modes.dbRaw.buffer); sfree(Modes.db); Modes.dbIndex = Modes.db2Index; Modes.dbRaw = Modes.db2Raw; Modes.db = Modes.db2; Modes.db2Index = NULL; Modes.db2 = NULL; Modes.db2Raw.buffer = NULL; Modes.db2Raw.len = 0; /* for (int j = 0; j < Modes.acBuckets; j++) { for (struct aircraft *a = Modes.aircraft[j]; a; a = a->next) { updateTypeReg(a); } } */ int64_t now = mstime(); threadpool_distribute_and_run(Modes.allPool, Modes.allTasks, updateTypeRegRange, Modes.acBuckets, 0, now); double elapsed = stopWatch(&watch) / 1000.0; fprintf(stderr, "Database update done! (critical part took %.3f seconds)\n", elapsed); return 1; } dbEntry *dbGet(uint32_t addr, dbEntry **index) { if (!index) return NULL; dbEntry *d = index[dbHash(addr)]; while (d && d->addr != addr) { d = d->next; } return d; } void dbPut(uint32_t addr, dbEntry **index, dbEntry *d) { uint32_t hash = dbHash(addr); d->next = index[hash]; index[hash] = d; } static void copyDetailFunc(char *sot, char *eot, char *target, int alloc) { int len = imin(alloc, eot - sot); memcpy(target, sot, len); // terminate if string doesn't fill up alloc // this mildly insane program can hopefully handle unterminated database strings if (len < alloc) { target[len] = '\0'; } sanitize(target, len); } static void updateDetails(struct aircraft *curr, struct char_buffer cb, uint32_t offset) { //fprintf(stdout, "%u %u\n", offset, (uint32_t) cb.len); char *eob = cb.buffer + cb.len; char *sol = cb.buffer + offset; char *eol = memchr(sol, '\n', eob - sol); char *sot; char *eot = sol - 1; // this pointer must not be dereferenced, nextToken will increment it. if (!eol) goto BAD_ENTRY; #define copyDetail(d) do { copyDetailFunc(sot, eot, curr->d, sizeof(curr->d)); } while (0) if (!nextToken(';', &sot, &eot, &eol)) goto BAD_ENTRY; copyDetail(registration); if (!nextToken(';', &sot, &eot, &eol)) goto BAD_ENTRY; copyDetail(typeCode); if (!nextToken(';', &sot, &eot, &eol)) goto BAD_ENTRY; curr->dbFlags = 0; for (int j = 0; j < 8 * (int) sizeof(curr->dbFlags) && sot < eot; j++, sot++) curr->dbFlags |= ((*sot == '1') << j); if (!nextToken(';', &sot, &eot, &eol)) goto BAD_ENTRY; copyDetail(typeLong); if (!nextToken(';', &sot, &eot, &eol)) goto BAD_ENTRY; copyDetail(year); if (!nextToken(';', &sot, &eot, &eol)) goto BAD_ENTRY; copyDetail(ownOp); #undef copyDetail if (false) { // debugging output fprintf(stdout, "%06X;%.12s;%.4s;%c%c;%.54s\n", curr->addr, curr->registration, curr->typeCode, curr->dbFlags & 1 ? '1' : '0', curr->dbFlags & 2 ? '1' : '0', curr->typeLong); } return; BAD_ENTRY: curr->registration[0] = '\0'; curr->typeCode[0] = '\0'; curr->typeLong[0] = '\0'; curr->ownOp[0] = '\0'; curr->year[0] = '\0'; curr->dbFlags = 0; } void updateTypeReg(struct aircraft *a) { dbEntry *d = dbGet(a->addr, Modes.dbIndex); if (d) { updateDetails(a, Modes.dbRaw, d->rawOffset); } else { a->registration[0] = '\0'; a->typeCode[0] = '\0'; a->typeLong[0] = '\0'; a->ownOp[0] = '\0'; a->year[0] = '\0'; a->dbFlags = 0; } if (is_df18_exception(a->addr)) { a->is_df18_exception = 1; } if (isMilRange(a->addr)) { a->dbFlags |= 1; } } static int isMilRange(uint32_t i) { return false // us military //adf7c8-adf7cf = united states mil_5(uf) //adf7d0-adf7df = united states mil_4(uf) //adf7e0-adf7ff = united states mil_3(uf) //adf800-adffff = united states mil_2(uf) //ae0000-afffff = united states mil_1(uf) || (i >= 0xadf7c8 && i <= 0xafffff) //010070-01008f = egypt_mil || (i >= 0x010070 && i <= 0x01008f) //0a4000-0a4fff = algeria mil(ap) || (i >= 0x0a4000 && i <= 0x0a4fff) //33ff00-33ffff = italy mil(iy) || (i >= 0x33ff00 && i <= 0x33ffff) //350000-37ffff = spain mil(sp) || (i >= 0x350000 && i <= 0x37ffff) //3aa000-3affff = france mil_1(fs) || (i >= 0x3aa000 && i <= 0x3affff) //3b7000-3bffff = france mil_2(fs) || (i >= 0x3b7000 && i <= 0x3bffff) //3ea000-3ebfff = germany mil_1(df) || (i >= 0x3ea000 && i <= 0x3ebfff) //3f4000-3f7fff = germany mil_2(df) //3f8000-3fbfff = germany mil_3(df) || (i >= 0x3f4000 && i <= 0x3fbfff) //400000-40003f = united kingdom mil_1(ra) || (i >= 0x400000 && i <= 0x40003f) //43c000-43cfff = united kingdom mil(ra) || (i >= 0x43c000 && i <= 0x43cfff) //444000-446fff = austria mil(aq) || (i >= 0x444000 && i <= 0x446fff) //44f000-44ffff = belgium mil(bc) || (i >= 0x44f000 && i <= 0x44ffff) //457000-457fff = bulgaria mil(bu) || (i >= 0x457000 && i <= 0x457fff) //45f400-45f4ff = denmark mil(dg) || (i >= 0x45f400 && i <= 0x45f4ff) //468000-4683ff = greece mil(gc) || (i >= 0x468000 && i <= 0x4683ff) //473c00-473c0f = hungary mil(hm) || (i >= 0x473c00 && i <= 0x473c0f) //478100-4781ff = norway mil(nn) || (i >= 0x478100 && i <= 0x4781ff) //480000-480fff = netherlands mil(nm) || (i >= 0x480000 && i <= 0x480fff) //48d800-48d87f = poland mil(po) || (i >= 0x48d800 && i <= 0x48d87f) //497c00-497cff = portugal mil(pu) || (i >= 0x497c00 && i <= 0x497cff) //498420-49842f = czech republic mil(ct) || (i >= 0x498420 && i <= 0x49842f) //4b7000-4b7fff = switzerland mil(su) || (i >= 0x4b7000 && i <= 0x4b7fff) //4b8200-4b82ff = turkey mil(tq) || (i >= 0x4b8200 && i <= 0x4b82ff) //506f32-506fff = slovenia mil(sj) //|| (i >= 0x506f32 && i <= 0x506fff) //70c070-70c07f = oman mil(on) || (i >= 0x70c070 && i <= 0x70c07f) //710258-71025f = saudi arabia mil_1(sx) //710260-71027f = saudi arabia mil_2(sx) //710280-71028f = saudi arabia mil_3(sx) || (i >= 0x710258 && i <= 0x71028f) //710380-71039f = saudi arabia mil_4(sx) || (i >= 0x710380 && i <= 0x71039f) //738a00-738aff = israel mil(iz) || (i >= 0x738a00 && i <= 0x738aff) //7cf800-7cfaff australia mil || (i >= 0x7cf800 && i <= 0x7cfaff) //800200-8002ff = india mil(im) || (i >= 0x800200 && i <= 0x8002ff) //c20000-c3ffff = canada mil(cb) || (i >= 0xc20000 && i <= 0xc3ffff) //e40000-e41fff = brazil mil(bq) || (i >= 0xe40000 && i <= 0xe41fff) //e80600-e806ff = chile mil(cq) //|| (i >= 0xe80600 && i <= 0xe806ff) // disabled due to civilian aircraft in hex range ; } readsb-3.16/aircraft.h000066400000000000000000000120571505057307600146640ustar00rootroot00000000000000#ifndef AIRCRAFT_H #define AIRCRAFT_H void freeAircraftBack(); static inline uint32_t addrHash(uint32_t addr, uint32_t bits) { const uint64_t m = 0x880355f21e6d1965ULL; const uint64_t seed = 0x30732349f7810465ULL; uint64_t h = seed ^ (4 * m); uint64_t v = addr; h ^= mix_fasthash(v); h *= m; h = mix_fasthash(h); // collapse to required bit width while retaining as much info as possible uint64_t res = h ^ (h >> 32); if (bits < 16) res ^= (res >> 16); res ^= (res >> bits); // mask to fit the requested bit width res &= (((uint64_t) 1) << bits) - 1; return (uint32_t) res; } void quickInit(); void quickDestroy(); void quickAdd(struct aircraft *a); void quickRemove(struct aircraft *a); void aircraftZeroTail(struct aircraft *a); struct aircraft *aircraftGet(uint32_t addr); struct aircraft *aircraftCreate(uint32_t addr); void freeAircraft(struct aircraft *a); typedef struct dbEntry { struct dbEntry *next; uint32_t addr; uint32_t rawOffset; } dbEntry; dbEntry *dbGet(uint32_t addr, dbEntry **index); void dbPut(uint32_t addr, dbEntry **index, dbEntry *d); #define BINCRAFT_ALT_FACTOR (1.0f/25.0f) struct binCraft { uint32_t hex; int32_t seen; // 8 int32_t lon; int32_t lat; // 16 int16_t baro_rate; int16_t geom_rate; int16_t baro_alt; int16_t geom_alt; // 24 uint16_t nav_altitude_mcp; // FCU/MCP selected altitude uint16_t nav_altitude_fms; // FMS selected altitude int16_t nav_qnh; // Altimeter setting (QNH/QFE), millibars int16_t nav_heading; // target heading, degrees (0-359) // 32 uint16_t squawk; // Squawk int16_t gs; int16_t mach; int16_t roll; // Roll angle, degrees right // 40 int16_t track; // Ground track int16_t track_rate; // Rate of change of ground track, degrees/second int16_t mag_heading; // Magnetic heading int16_t true_heading; // True heading // 48 int16_t wind_direction; int16_t wind_speed; int16_t oat; int16_t tat; // 56 uint16_t tas; uint16_t ias; uint16_t pos_rc; // Rc of last computed position uint16_t messages; // 64 unsigned category:8; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset unsigned pos_nic:8; // NIC of last computed position // 66 nav_modes_t nav_modes:8; // enabled modes (autopilot, vnav, etc) emergency_t emergency:4; // Emergency/priority status addrtype_t addrtype:4; // highest priority address type seen for this aircraft // 68 airground_t airground:4; // air/ground status nav_altitude_source_t nav_altitude_src:4; // source of altitude used by automation sil_type_t sil_type:4; // SIL supplement from TSS or opstatus unsigned adsb_version:4; // ADS-B version (from ADS-B operational status); -1 means no ADS-B messages seen // 70 unsigned adsr_version:4; // As above, for ADS-R messages unsigned tisb_version:4; // As above, for TIS-B messages unsigned nac_p : 4; // NACp from TSS or opstatus unsigned nac_v : 4; // NACv from airborne velocity or opstatus // 72 unsigned sil : 2; // SIL from TSS or opstatus unsigned gva : 2; // GVA from opstatus unsigned sda : 2; // SDA from opstatus unsigned nic_a : 1; // NIC supplement A from opstatus unsigned nic_c : 1; // NIC supplement C from opstatus unsigned nic_baro : 1; // NIC baro supplement from TSS or opstatus unsigned alert : 1; // FS Flight status alert bit unsigned spi : 1; // FS Flight status SPI (Special Position Identification) bit unsigned callsign_valid:1; unsigned baro_alt_valid:1; unsigned geom_alt_valid:1; unsigned position_valid:1; unsigned gs_valid:1; // 74 unsigned ias_valid:1; unsigned tas_valid:1; unsigned mach_valid:1; unsigned track_valid:1; unsigned track_rate_valid:1; unsigned roll_valid:1; unsigned mag_heading_valid:1; unsigned true_heading_valid:1; unsigned baro_rate_valid:1; unsigned geom_rate_valid:1; unsigned nic_a_valid:1; unsigned nic_c_valid:1; unsigned nic_baro_valid:1; unsigned nac_p_valid:1; unsigned nac_v_valid:1; unsigned sil_valid:1; // 76 unsigned gva_valid:1; unsigned sda_valid:1; unsigned squawk_valid:1; unsigned emergency_valid:1; unsigned spi_valid:1; unsigned nav_qnh_valid:1; unsigned nav_altitude_mcp_valid:1; unsigned nav_altitude_fms_valid:1; unsigned nav_altitude_src_valid:1; unsigned nav_heading_valid:1; unsigned nav_modes_valid:1; unsigned alert_valid:1; unsigned wind_valid:1; unsigned temp_valid:1; unsigned unused_1:1; unsigned unused_2:1; // 78 char callsign[8]; // Flight number // 86 uint16_t dbFlags; // 88 char typeCode[4]; // 92 char registration[12]; // 104 uint8_t receiverCount; uint8_t signal; uint8_t extraFlags; uint8_t reserved; // 108 // javascript sucks, this must be a multiple of 4 bytes for Int32Array to work correctly int32_t seen_pos; // 112 #if defined(TRACKS_UUID) uint32_t receiverId; #endif // 116 } __attribute__ ((__packed__)); void toBinCraft(struct aircraft *a, struct binCraft *new, int64_t now); int dbUpdate(int64_t now); int dbFinishUpdate(); void updateTypeReg(struct aircraft *a); #endif readsb-3.16/ais_charset.c000066400000000000000000000017561505057307600153550ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // ais_charset.c: Valid character set. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2017 FlightAware, LLC // // This file 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 // any later version. // // This file 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 . #include "ais_charset.h" char ais_charset[65] = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"; readsb-3.16/ais_charset.h000066400000000000000000000017221505057307600153530ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // ais_charset.h: Valid character set. (header) // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2017 FlightAware, LLC // // This file 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 // any later version. // // This file 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 . #ifndef AIS_CHARSET_H #define AIS_CHARSET_H extern char ais_charset[65]; #endif readsb-3.16/anet.c000066400000000000000000000340201505057307600140050ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // anet.c: Basic TCP socket stuff made a bit less boring // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2016 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (c) 2006-2012, Salvatore Sanfilippo // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Redis nor the names of its contributors may be used // to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include "compat/apple/net_compat.h" #endif #include "anet.h" #define _UNUSED(V) ((void) V) static void anetSetError(char *err, const char *fmt, ...) { va_list ap; if (!err) return; va_start(ap, fmt); vsnprintf(err, ANET_ERR_LEN, fmt, ap); va_end(ap); } int anetNonBlock(char *err, int fd) { int flags; /* Set the socket nonblocking. * Note that fcntl(2) for F_GETFL and F_SETFL can't be * interrupted by a signal. */ if ((flags = fcntl(fd, F_GETFL)) == -1) { anetSetError(err, "fcntl(F_GETFL): %s", strerror(errno)); return ANET_ERR; } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno)); return ANET_ERR; } return ANET_OK; } int anetTcpNoDelay(char *err, int fd) { int yes = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&yes, sizeof(yes)) == -1) { anetSetError(err, "setsockopt TCP_NODELAY: %s", strerror(errno)); return ANET_ERR; } return ANET_OK; } int anetSetSendBuffer(char *err, int fd, int buffsize) { if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&buffsize, sizeof(buffsize)) == -1) { anetSetError(err, "setsockopt SO_SNDBUF: %s", strerror(errno)); return ANET_ERR; } return ANET_OK; } int anetTcpKeepAlive(char *err, int fd) { int yes = 1; int idle = 20; int interval = 2; int count = 3; #ifdef __APPLE__ _UNUSED(yes); if (set_tcp_keepalive(fd, idle, interval, count) == -1) { anetSetError(err, "setsockopt tcp keepalive: %s", strerror(errno)); return ANET_ERR; } #else if ( setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*) &yes, sizeof(yes)) || setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, (void*) &idle, sizeof(idle)) || setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, (void*) &interval, sizeof(interval)) || setsockopt(fd, SOL_TCP, TCP_KEEPCNT, (void*) &count, sizeof(count)) ) { anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno)); return ANET_ERR; } #endif return ANET_OK; } static inline void emfileError() { struct rlimit limits; getrlimit(RLIMIT_NOFILE, &limits); fprintf(stderr, "<3>EMFILE: Out of file descriptors (either there is a leak that needs fixing or you need to increase ulimit if you need more than %d connections)!\n", (int) limits.rlim_cur); } int anetCreateSocket(char *err, int domain, int typeFlags) { int s, on = 1; if ((s = socket(domain, SOCK_STREAM | typeFlags, 0)) == -1) { if (errno == EMFILE) { emfileError(); } anetSetError(err, "creating socket: %s", strerror(errno)); return ANET_ERR; } /* Make sure connection-intensive things like the redis benckmark * will be able to close/open sockets a zillion of times */ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) == -1) { anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno)); anetCloseSocket(s); return ANET_ERR; } return s; } #define ANET_CONNECT_NONE 0 #define ANET_CONNECT_NONBLOCK 1 static int anetTcpGenericConnect(char *err, char *addr, char *service, int flags, struct sockaddr_storage *ss) { int s; struct addrinfo gai_hints; struct addrinfo *gai_result, *p; int gai_error; gai_hints.ai_family = AF_UNSPEC; gai_hints.ai_socktype = SOCK_STREAM; gai_hints.ai_protocol = 0; gai_hints.ai_flags = 0; gai_hints.ai_addrlen = 0; gai_hints.ai_addr = NULL; gai_hints.ai_canonname = NULL; gai_hints.ai_next = NULL; gai_error = getaddrinfo(addr, service, &gai_hints, &gai_result); if (gai_error != 0) { anetSetError(err, "can't resolve %s: %s", addr, gai_strerror(gai_error)); return ANET_ERR; } for (p = gai_result; p != NULL; p = p->ai_next) { int nonBlock = (flags & ANET_CONNECT_NONBLOCK) ? SOCK_NONBLOCK : 0; if ((s = anetCreateSocket(err, p->ai_family, nonBlock)) == ANET_ERR) continue; if (connect(s, p->ai_addr, p->ai_addrlen) >= 0 || (errno == EINPROGRESS && (flags & ANET_CONNECT_NONBLOCK)) ) { // If we were passed a place to toss the sockaddr info, save it if (ss) { memcpy(ss, p->ai_addr, sizeof(*ss)); } if (gai_result) { freeaddrinfo(gai_result); gai_result = NULL; } return s; } anetSetError(err, "connect: %s", strerror(errno)); anetCloseSocket(s); } if (gai_result) { freeaddrinfo(gai_result); gai_result = NULL; } return ANET_ERR; } int anetTcpConnect(char *err, char *addr, char *service, struct sockaddr_storage *ss) { return anetTcpGenericConnect(err,addr,service,ANET_CONNECT_NONE, ss); } int anetTcpNonBlockConnect(char *err, char *addr, char *service, struct sockaddr_storage *ss) { return anetTcpGenericConnect(err,addr,service,ANET_CONNECT_NONBLOCK, ss); } int anetGetaddrinfo(char *err, char *addr, char *service, struct addrinfo **gai_result) { struct addrinfo gai_hints; int gai_error; gai_hints.ai_family = AF_UNSPEC; gai_hints.ai_socktype = SOCK_STREAM; gai_hints.ai_protocol = 0; gai_hints.ai_flags = 0; gai_hints.ai_addrlen = 0; gai_hints.ai_addr = NULL; gai_hints.ai_canonname = NULL; gai_hints.ai_next = NULL; gai_error = getaddrinfo(addr, service, &gai_hints, gai_result); if (gai_error != 0) { anetSetError(err, "can't resolve %s: %s", addr, gai_strerror(gai_error)); return ANET_ERR; } return 0; } int anetTcpNonBlockConnectAddr(char *err, struct addrinfo *p) { int s; if ((s = anetCreateSocket(err, p->ai_family, SOCK_NONBLOCK)) == ANET_ERR) return ANET_ERR; if (connect(s, p->ai_addr, p->ai_addrlen) >= 0) { return s; } if (errno == EINPROGRESS) { return s; } anetSetError(err, "connect: %s", strerror(errno)); anetCloseSocket(s); return ANET_ERR; } /* Like read(2) but make sure 'count' is read before to return * (unless error or EOF condition is encountered) */ int anetRead(int fd, char *buf, int count) { int nread, totlen = 0; while(totlen < count) { nread = read(fd, buf, (size_t) (count - totlen)); if (nread == 0) return totlen; if (nread == -1) return -1; totlen += nread; buf += nread; } return totlen; } /* Like write(2) but make sure 'count' is read before to return * (unless error is encountered) */ int anetWrite(int fd, char *buf, int count) { int nwritten, totlen = 0; while(totlen != count) { nwritten = write(fd, buf, (size_t) (count - totlen)); if (nwritten == 0) return totlen; if (nwritten == -1) return -1; totlen += nwritten; buf += nwritten; } return totlen; } static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) { if (sa->sa_family == AF_INET6) { int on = 1; setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); } if (bind(s,sa,len) == -1) { anetSetError(err, "bind: %s", strerror(errno)); anetCloseSocket(s); return ANET_ERR; } // no real drawback to using a large backlog, will usually be capped to 4096 by the kernel if (listen(s, 65535) == -1) { anetSetError(err, "listen: %s", strerror(errno)); anetCloseSocket(s); return ANET_ERR; } return ANET_OK; } static void anetSetBuffers(int fd, int sndsize, int rcvsize) { if (sndsize > 0 && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&sndsize, sizeof(sndsize)) == -1) { fprintf(stderr, "setsockopt SO_SNDBUF: %s", strerror(errno)); } if (rcvsize > 0 && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void*)&rcvsize, sizeof(rcvsize)) == -1) { fprintf(stderr, "setsockopt SO_RCVBUF: %s", strerror(errno)); } } int anetTcpServer(char *err, char *service, char *bindaddr, int *fds, int nfds, int flags, int sndsize, int rcvsize) { int s; int i = 0; struct addrinfo gai_hints; struct addrinfo *gai_result, *p; int gai_error; gai_hints.ai_family = AF_UNSPEC; gai_hints.ai_socktype = SOCK_STREAM; gai_hints.ai_protocol = 0; gai_hints.ai_flags = AI_PASSIVE; gai_hints.ai_addrlen = 0; gai_hints.ai_addr = NULL; gai_hints.ai_canonname = NULL; gai_hints.ai_next = NULL; gai_error = getaddrinfo(bindaddr, service, &gai_hints, &gai_result); if (gai_error != 0) { anetSetError(err, "can't resolve %s: %s", bindaddr, gai_strerror(gai_error)); return ANET_ERR; } for (p = gai_result; p != NULL && i < nfds; p = p->ai_next) { if ((s = anetCreateSocket(err, p->ai_family, flags)) == ANET_ERR) continue; anetSetBuffers(s, sndsize, rcvsize); if (anetListen(err, s, p->ai_addr, p->ai_addrlen) == ANET_ERR) { continue; } fds[i++] = s; } if (gai_result) { freeaddrinfo(gai_result); gai_result = NULL; } return (i > 0 ? i : ANET_ERR); } int anetUnixSocket(char *err, char *path, int flags) { int s; if ((s = anetCreateSocket(err, AF_UNIX, flags)) == ANET_ERR) { return ANET_ERR; } struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, path); if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) == -1) { anetSetError(err, "bind: %s", strerror(errno)); anetCloseSocket(s); return ANET_ERR; } // explicitely setting tcp buffers causes failure of linux tcp window auto tuning ... it just doesn't work well without the auto tuning // anetSetBuffers(s, 512 * 1024, 64 * 1024); // no real drawback to using a large backlog, will usually be capped to 4096 by the kernel if (listen(s, 65535) == -1) { anetSetError(err, "listen: %s", strerror(errno)); anetCloseSocket(s); return ANET_ERR; } return s; } int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len, int flags) { int fd; fd = accept4(s, sa, len, flags); if (fd == -1) { if (errno == EMFILE) { emfileError(); } if (errno != EINTR) { anetSetError(err, "Generic accept error: %s %d", strerror(errno), fd); } return ANET_ERR; } return fd; } static inline int get_socket_error(int fd) { int err = 1; socklen_t len = sizeof err; if (-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &err, &len)) { fprintf(stderr, "Get client socket error failed.\n"); } if (err) { errno = err; // Set errno to the socket SO_ERROR } return err; } void anetCloseSocket(int fd) { if (fd < 0) { return; } get_socket_error(fd); // First clear any errors, which can cause close to fail if (shutdown(fd, SHUT_RDWR) < 0) { // Secondly, terminate the reliable delivery if (errno != ENOTCONN && errno != EINVAL) { // SGI causes EINVAL fprintf(stderr, "Shutdown client socket failed.\n"); } } close(fd); } readsb-3.16/anet.h000066400000000000000000000073251505057307600140220ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // anet.h: Basic TCP socket stuff made a bit less boring (header) // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2016 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (c) 2006-2012, Salvatore Sanfilippo // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Redis nor the names of its contributors may be used // to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #ifndef ANET_H #define ANET_H #define ANET_OK 0 #define ANET_ERR -1 #define ANET_ERR_LEN 256 #if defined(__sun) #define AF_LOCAL AF_UNIX #endif #include #include #include int anetTcpConnect(char *err, char *addr, char *service, struct sockaddr_storage *ss); int anetTcpNonBlockConnect(char *err, char *addr, char *service, struct sockaddr_storage *ss); int anetTcpNonBlockConnectAddr(char *err, struct addrinfo *p); int anetGetaddrinfo(char *err, char *addr, char *service, struct addrinfo **gai_result); int anetRead(int fd, char *buf, int count); int anetTcpServer(char *err, char *service, char *bindaddr, int *fds, int nfds, int flags, int sndsize, int rcvsize); int anetUnixSocket(char *err, char *path, int flags); int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len, int flags); int anetWrite(int fd, char *buf, int count); int anetNonBlock(char *err, int fd); int anetTcpNoDelay(char *err, int fd); int anetTcpKeepAlive(char *err, int fd); int anetSetSendBuffer(char *err, int fd, int buffsize); int anetCreateSocket(char *err, int domain, int typeFlags); void anetCloseSocket(int fd); #endif readsb-3.16/api.c000066400000000000000000002207011505057307600136320ustar00rootroot00000000000000#include "readsb.h" static int apiUpdate(); static inline uint32_t hexHash(uint32_t addr, struct apiBuffer *buffer) { uint32_t res = addrHash(addr, buffer->hashBits); //fprintf(stderr, "%06x -> %06u\n", addr, res); return res; } static inline uint32_t regHash(char *reg, struct apiBuffer *buffer) { const uint64_t seed = 0x30732349f7810465ULL; uint64_t h = fasthash64(reg, memberSize(struct binCraft, registration), seed); uint32_t bits = buffer->hashBits; uint64_t res = h ^ (h >> 32); if (bits < 16) res ^= (res >> 16); res ^= (res >> bits); // mask to fit the requested bit width res &= (((uint64_t) 1) << bits) - 1; //fprintf(stderr, "%s -> %06u\n", reg, (uint32_t) res); return (uint32_t) res; } static inline uint32_t callsignHash(char *callsign, struct apiBuffer *buffer) { const uint64_t seed = 0x30732349f7810465ULL; uint64_t h = fasthash64(callsign, 8, seed); uint32_t bits = buffer->hashBits; uint64_t res = h ^ (h >> 32); if (bits < 16) res ^= (res >> 16); res ^= (res >> bits); // mask to fit the requested bit width res &= (((uint64_t) 1) << bits) - 1; return (uint32_t) res; } static int antiSpam(int64_t *nextPrint, int64_t interval) { int64_t now = mstime(); if (now > *nextPrint) { *nextPrint = now + interval; return 1; } else { return 0; } } static int compareLon(const void *p1, const void *p2) { struct apiEntry *a1 = (struct apiEntry*) p1; struct apiEntry *a2 = (struct apiEntry*) p2; return (a1->bin.lon > a2->bin.lon) - (a1->bin.lon < a2->bin.lon); } static struct range findLonRange(int32_t ref_from, int32_t ref_to, struct apiEntry *list, int len) { struct range res; memset(&res, 0, sizeof(res)); if (len == 0 || ref_from > ref_to) return res; // get lower bound int i = 0; int j = len - 1; while (j > i + 1) { int pivot = (i + j) / 2; if (list[pivot].bin.lon < ref_from) i = pivot; else j = pivot; } if (list[j].bin.lon < ref_from) { res.from = j + 1; } else if (list[i].bin.lon < ref_from) { res.from = i + 1; } else { res.from = i; } // get upper bound (exclusive) i = imin(res.from, len - 1); j = len - 1; while (j > i + 1) { int pivot = (i + j) / 2; if (list[pivot].bin.lon <= ref_to) i = pivot; else j = pivot; } if (list[j].bin.lon <= ref_to) { res.to = j + 1; } else if (list[i].bin.lon <= ref_to) { res.to = i + 1; } else { res.to = i; } return res; } static int filter_alt_baro(struct apiEntry *haystack, int haylen, struct apiEntry *matches, size_t *alloc, struct apiOptions *options) { int count = 0; float reverse_alt_factor = 1.0f / BINCRAFT_ALT_FACTOR; for (int i = 0; i < haylen; i++) { struct apiEntry *e = &haystack[i]; int32_t alt = INT32_MIN; if (e->bin.baro_alt_valid) { alt = e->bin.baro_alt * reverse_alt_factor; } else if (e->bin.airground == AG_GROUND) { alt = 0; } if (alt >= options->above_alt_baro && alt <= options->below_alt_baro && alt != INT32_MIN) { matches[count++] = *e; *alloc += e->jsonOffset.len; } } return count; } static int filter_dbFlags(struct apiEntry *haystack, int haylen, struct apiEntry *matches, size_t *alloc, struct apiOptions *options) { int count = 0; for (int i = 0; i < haylen; i++) { struct apiEntry *e = &haystack[i]; if ( (options->filter_mil && (e->bin.dbFlags & 1)) || (options->filter_interesting && (e->bin.dbFlags & 2)) || (options->filter_pia && (e->bin.dbFlags & 4)) || (options->filter_ladd && (e->bin.dbFlags & 8)) ) { matches[count++] = *e; *alloc += e->jsonOffset.len; } } return count; } static int filterWithPos(struct apiEntry *haystack, int haylen, struct apiEntry *matches, size_t *alloc) { int count = 0; for (int i = 0; i < haylen; i++) { struct apiEntry *e = &haystack[i]; if (e->bin.position_valid) { matches[count++] = *e; *alloc += e->jsonOffset.len; } } return count; } static int filterSquawk(struct apiEntry *haystack, int haylen, struct apiEntry *matches, size_t *alloc, unsigned squawk) { int count = 0; for (int i = 0; i < haylen; i++) { struct apiEntry *e = &haystack[i]; //fprintf(stderr, "%04x %04x\n", options->squawk, e->bin.squawk); if (e->bin.squawk == squawk && e->bin.squawk_valid) { matches[count++] = *e; *alloc += e->jsonOffset.len; } } return count; } static int filterCallsignPrefix(struct apiEntry *haystack, int haylen, struct apiEntry *matches, size_t *alloc, char *callsign_prefix) { int count = 0; int prefix_len = strlen(callsign_prefix); for (int i = 0; i < prefix_len; i++) { // upper case callsign prefix callsign_prefix[i] = toupper(callsign_prefix[i]); } for (int j = 0; j < haylen; j++) { struct apiEntry *e = &haystack[j]; if (e->bin.callsign_valid && strncmp(e->bin.callsign, callsign_prefix, prefix_len) == 0) { matches[count++] = *e; *alloc += e->jsonOffset.len; } } return count; } static int filterCallsignExact(struct apiEntry *haystack, int haylen, struct apiEntry *matches, size_t *alloc, char *callsign) { int callLen = memberSize(struct binCraft, callsign); for (int i = 0; i < callLen; i++) { // replace null padding with space padding if (callsign[i] == '\0') { callsign[i] = ' '; } // upper case callsign callsign[i] = toupper(callsign[i]); } int count = 0; for (int j = 0; j < haylen; j++) { struct apiEntry *e = &haystack[j]; if (e->bin.callsign_valid) { //fprintf(stderr, "%s %s\n", callsign, e->bin.callsign); if (strncmp(e->bin.callsign, callsign, callLen) == 0) { matches[count++] = *e; *alloc += e->jsonOffset.len; } } } return count; } static int filterTypeList(struct apiEntry *haystack, int haylen, char *typeList, int typeCount, struct apiEntry *matches, size_t *alloc) { int count = 0; int typeLen = memberSize(struct binCraft, typeCode); for (int k = 0; k < typeCount; k++) { char *typeCode = typeList + typeLen * k; // upper case typeCode for (int i = 0; i < typeLen; i++) { typeCode[i] = toupper(typeCode[i]); } } for (int j = 0; j < haylen; j++) { struct apiEntry *e = &haystack[j]; for (int k = 0; k < typeCount; k++) { char *typeCode = typeList + typeLen * k; if (strncmp(e->bin.typeCode, typeCode, typeLen) == 0) { //fprintf(stderr, "typeCode: %.4s %.4s alloc increase by %d\n", e->bin.typeCode, typeCode, e->jsonOffset.len); matches[count++] = *e; *alloc += e->jsonOffset.len; // break inner loop break; } } } return count; } static int inLatRange(struct apiEntry *e, int32_t lat1, int32_t lat2, struct apiOptions *options) { return (e->bin.lat >= lat1 && e->bin.lat <= lat2 && (e->bin.position_valid || options->binCraft)); } static int findInBox(struct apiEntry *haystack, int haylen, struct apiOptions *options, struct apiEntry *matches, size_t *alloc) { double *box = options->box; struct range r[2]; memset(r, 0, sizeof(r)); int count = 0; int32_t lat1 = (int32_t) (box[0] * 1E6); int32_t lat2 = (int32_t) (box[1] * 1E6); int32_t lon1 = (int32_t) (box[2] * 1E6); int32_t lon2 = (int32_t) (box[3] * 1E6); if (lon1 <= lon2) { r[0] = findLonRange(lon1, lon2, haystack, haylen); } else if (lon1 > lon2) { r[0] = findLonRange(lon1, 180E6, haystack, haylen); r[1] = findLonRange(-180E6, lon2, haystack, haylen); //fprintf(stderr, "%.1f to 180 and -180 to %1.f\n", lon1 / 1E6, lon2 / 1E6); } for (int k = 0; k < 2; k++) { for (int j = r[k].from; j < r[k].to; j++) { struct apiEntry *e = &haystack[j]; if (inLatRange(e, lat1, lat2, options)) { matches[count++] = *e; *alloc += e->jsonOffset.len; } } } //fprintf(stderr, "box: lat %.1f to %.1f, lon %.1f to %.1f, count: %d\n", box[0], box[1], box[2], box[3], count); return count; } static int findRegList(struct apiBuffer *buffer, char *regList, int regCount, struct apiEntry *matches, size_t *alloc) { struct apiEntry **hashList = buffer->regHash; int count = 0; int regLen = memberSize(struct binCraft, registration); for (int k = 0; k < regCount; k++) { char *reg = ®List[k * regLen]; // upper case reg for (int i = 0; i < regLen; i++) { reg[i] = toupper(reg[i]); } //fprintf(stderr, "reg: %s\n", reg); uint32_t hash = regHash(reg, buffer); struct apiEntry *e = hashList[hash]; while (e) { if (strncmp(e->bin.registration, reg, regLen) == 0) { matches[count++] = *e; *alloc += e->jsonOffset.len; break; } e = e->nextReg; } } return count; } static int findCallsignList(struct apiBuffer *buffer, char *callsignList, int callsignCount, struct apiEntry *matches, size_t *alloc) { struct apiEntry **hashList = buffer->callsignHash; int count = 0; int callLen = memberSize(struct binCraft, callsign); for (int k = 0; k < callsignCount; k++) { char *callsign = &callsignList[k * callLen]; // replace null padding with space padding, upper case input for (int i = 0; i < callLen; i++) { callsign[i] = toupper(callsign[i]); if (callsign[i] == '\0') { callsign[i] = ' '; } } uint32_t hash = callsignHash(callsign, buffer); //fprintf(stderr, "callsign: %8s hash: %u\n", callsign, hash); struct apiEntry *e = hashList[hash]; while (e) { //fprintf(stderr, "callsign: %8s\n", e->bin.callsign); if (strncmp(e->bin.callsign, callsign, callLen) == 0) { matches[count++] = *e; *alloc += e->jsonOffset.len; break; } e = e->nextCallsign; } } return count; } static int findHexList(struct apiBuffer *buffer, uint32_t *hexList, int hexCount, struct apiEntry *matches, size_t *alloc) { struct apiEntry **hashList = buffer->hexHash; int count = 0; for (int k = 0; k < hexCount; k++) { uint32_t addr = hexList[k]; uint32_t hash = hexHash(addr, buffer); //fprintf(stderr, "----> %06x -> %06u\n", addr, hash); struct apiEntry *e = hashList[hash]; while (e) { if (e->bin.hex == addr) { matches[count++] = *e; *alloc += e->jsonOffset.len; break; } e = e->nextHex; } } return count; } static int findInCircle(struct apiEntry *haystack, int haylen, struct apiOptions *options, struct apiEntry *matches, size_t *alloc) { struct apiCircle *circle = &options->circle; struct range r[2]; memset(r, 0, sizeof(r)); int count = 0; double lat = circle->lat; double lon = circle->lon; double radius = circle->radius; // in meters bool onlyClosest = circle->onlyClosest; double circum = 40075e3; // earth circumference is 40075km double fudge = 1.002; // make the box we check a little bigger double londiff = fudge * radius / (cos(lat * M_PI / 180.0) * circum + 1) * 360; double o1 = lon - londiff; double o2 = lon + londiff; o1 = o1 < -180 ? o1 + 360: o1; o2 = o2 > 180 ? o2 - 360 : o2; if (londiff >= 180) { // just check all lon o1 = -180; o2 = 180; } double latdiff = fudge * radius / (circum / 2) * 180.0; double a1 = lat - latdiff; double a2 = lat + latdiff; if (a1 < -90 || a2 > 90) { // going over a pole, just check all lon o1 = -180; o2 = 180; } int32_t lat1 = (int32_t) (a1 * 1E6); int32_t lat2 = (int32_t) (a2 * 1E6); int32_t lon1 = (int32_t) (o1 * 1E6); int32_t lon2 = (int32_t) (o2 * 1E6); //fprintf(stderr, "radius:%8.0f latdiff: %8.0f londiff: %8.0f\n", radius, greatcircle(a1, lon, lat, lon), greatcircle(lat, o1, lat, lon, 0)); if (lon1 <= lon2) { r[0] = findLonRange(lon1, lon2, haystack, haylen); } else if (lon1 > lon2) { r[0] = findLonRange(lon1, 180E6, haystack, haylen); r[1] = findLonRange(-180E6, lon2, haystack, haylen); //fprintf(stderr, "%.1f to 180 and -180 to %1.f\n", lon1 / 1E6, lon2 / 1E6); } if (onlyClosest) { bool found = false; double minDistance = 300E6; // larger than any distances we encounter, also how far light travels in a second for (int k = 0; k < 2; k++) { for (int j = r[k].from; j < r[k].to; j++) { struct apiEntry *e = &haystack[j]; if (inLatRange(e, lat1, lat2, options)) { double dist = greatcircle(lat, lon, e->bin.lat / 1E6, e->bin.lon / 1E6, 0); if (dist < radius && dist < minDistance) { // first match is overwritten repeatedly matches[0] = *e; matches[0].distance = (float) dist; minDistance = dist; found = true; } } } } if (found) { // calculate bearing for (the only) match struct apiEntry *e = &matches[0]; *alloc += e->jsonOffset.len; e->direction = (float) bearing(lat, lon, e->bin.lat / 1E6, e->bin.lon / 1E6); count = 1; } } if (!onlyClosest) { for (int k = 0; k < 2; k++) { for (int j = r[k].from; j < r[k].to; j++) { struct apiEntry *e = &haystack[j]; if (inLatRange(e, lat1, lat2, options)) { double dist = greatcircle(lat, lon, e->bin.lat / 1E6, e->bin.lon / 1E6, 0); if (dist < radius) { matches[count] = *e; matches[count].distance = (float) dist; matches[count].direction = (float) bearing(lat, lon, e->bin.lat / 1E6, e->bin.lon / 1E6); *alloc += e->jsonOffset.len; count++; } } } } } //fprintf(stderr, "circle count: %d\n", count); return count; } static struct apiEntry *apiAlloc(int count) { struct apiEntry *buf = cmalloc(count * sizeof(struct apiEntry)); if (!buf) { fprintf(stderr, "FATAL: apiAlloc malloc fail\n"); setExit(2); } return buf; } static struct char_buffer apiReq(struct apiThread *thread, struct apiOptions *options) { int flip = atomic_load(&Modes.apiFlip[thread->index]); struct apiBuffer *buffer = &Modes.apiBuffer[flip]; struct apiEntry *haystack; int haylen; struct range pos_range; struct range all_range; if (options->filter_dbFlag) { haystack = buffer->list_flag; haylen = buffer->len_flag; pos_range = buffer->list_flag_pos_range; all_range.from = 0; all_range.to = haylen; } else { haystack = buffer->list; haylen = buffer->len; pos_range = buffer->list_pos_range; all_range.from = 0; all_range.to = haylen; } struct char_buffer cb = { 0 }; struct apiEntry *matches = NULL; size_t alloc_base = API_REQ_PADSTART + 1024; size_t alloc = alloc_base; int count = 0; int doFree = 0; if (options->is_box) { int combined_len = haylen; if (options->is_hexList) { // this is a special case, in addition to the box, also return results for the hexList // we don't bother deduplicating, so this can return results more than once // thus allocate haylen and then also the number of hexes queried in addition combined_len += options->hexCount; } doFree = 1; matches = apiAlloc(combined_len); if (!matches) { return cb; }; // first get matches for the box count = findInBox(haystack, haylen, options, matches, &alloc); if (options->is_hexList) { // optionally add matches for &find_hex count += findHexList(buffer, options->hexList, options->hexCount, matches + count, &alloc); } } else if (options->is_circle) { doFree = 1; matches = apiAlloc(haylen); if (!matches) { return cb; }; count = findInCircle(haystack, haylen, options, matches, &alloc); alloc += count * 30; // adding 27 characters per entry: ,"dst":1000.000, "dir":357 } else if (options->is_hexList) { doFree = 1; matches = apiAlloc(options->hexCount); if (!matches) { return cb; }; count = findHexList(buffer, options->hexList, options->hexCount, matches, &alloc); } else if (options->is_regList) { doFree = 1; matches = apiAlloc(options->regCount); if (!matches) { return cb; }; count = findRegList(buffer, options->regList, options->regCount, matches, &alloc); } else if (options->is_callsignList) { doFree = 1; matches = apiAlloc(options->callsignCount); if (!matches) { return cb; }; count = findCallsignList(buffer, options->callsignList, options->callsignCount, matches, &alloc); } else if (options->is_typeList) { doFree = 1; matches = apiAlloc(haylen); if (!matches) { return cb; }; count = filterTypeList(haystack, haylen, options->typeList, options->typeCount, matches, &alloc); } else if (options->all || options->all_with_pos) { struct range range; if (options->all) { range = all_range; } else if ( options->all_with_pos) { range = pos_range; } else { fprintf(stderr, "FATAL: unreachablei ahchoh8R\n"); setExit(2); return cb; } count = range.to - range.from; if (count > 0) { struct apiEntry *first = &haystack[range.from]; struct apiEntry *last = &haystack[range.to - 1]; // assume continuous allocation from generation of api buffer alloc += last->jsonOffset.offset + last->jsonOffset.len - first->jsonOffset.offset; doFree = 0; matches = first; } else { doFree = 0; matches = NULL; } } if (options->filter_squawk) { struct apiEntry *filtered = apiAlloc(count); if (!filtered) { return cb; } size_t alloc = alloc_base; count = filterSquawk(matches, count, filtered, &alloc, options->squawk); if (doFree) { sfree(matches); }; doFree = 1; matches = filtered; } // filter all_with_pos as pos_range unreliable due do gpsOkBefore f***ery if (options->filter_with_pos || options->all_with_pos) { struct apiEntry *filtered = apiAlloc(count); if (!filtered) { return cb; } size_t alloc = alloc_base; count = filterWithPos(matches, count, filtered, &alloc); if (doFree) { sfree(matches); }; doFree = 1; matches = filtered; } if (options->filter_dbFlag) { struct apiEntry *filtered = apiAlloc(count); if (!filtered) { return cb; } size_t alloc = alloc_base; count = filter_dbFlags(matches, count, filtered, &alloc, options); if (doFree) { sfree(matches); }; doFree = 1; matches = filtered; } if (options->filter_alt_baro) { struct apiEntry *filtered = apiAlloc(count); if (!filtered) { return cb; } size_t alloc = alloc_base; count = filter_alt_baro(matches, count, filtered, &alloc, options); if (doFree) { sfree(matches); }; doFree = 1; matches = filtered; } if (options->filter_callsign_prefix) { struct apiEntry *filtered = apiAlloc(count); if (!filtered) { return cb; } size_t alloc = alloc_base; count = filterCallsignPrefix(matches, count, filtered, &alloc, options->callsign_prefix); if (doFree) { sfree(matches); }; doFree = 1; matches = filtered; } if (options->filter_callsign_exact) { struct apiEntry *filtered = apiAlloc(count); if (!filtered) { return cb; } size_t alloc = alloc_base; count = filterCallsignExact(matches, count, filtered, &alloc, options->callsign_exact); if (doFree) { sfree(matches); }; doFree = 1; matches = filtered; } if (options->filter_typeList) { struct apiEntry *filtered = apiAlloc(count); if (!filtered) { return cb; } size_t alloc = alloc_base; count = filterTypeList(matches, count, options->typeList, options->typeCount, filtered, &alloc); if (doFree) { sfree(matches); }; doFree = 1; matches = filtered; } // elementSize only applies to binCraft output uint32_t elementSize = sizeof(struct binCraft); if (options->binCraft) { alloc = API_REQ_PADSTART + 2 * elementSize + count * elementSize; } cb.buffer = cmalloc(alloc); if (!cb.buffer) return cb; char *payload = cb.buffer + API_REQ_PADSTART; char *p = payload; char *end = cb.buffer + alloc; if (options->binCraft) { memset(p, 0, elementSize); #define memWrite(p, var) do { if (p + sizeof(var) > end) { break; }; memcpy(p, &var, sizeof(var)); p += sizeof(var); } while(0) int64_t now = buffer->timestamp; memWrite(p, now); memWrite(p, elementSize); uint32_t ac_count_pos = Modes.globalStatsCount.readsb_aircraft_with_position; memWrite(p, ac_count_pos); uint32_t index = 0; memWrite(p, index); int16_t south = -90; int16_t west = -180; int16_t north = 90; int16_t east = 180; if (options->is_box) { south = nearbyint(options->box[0]); north = nearbyint(options->box[1]); west = nearbyint(options->box[2]); east = nearbyint(options->box[3]); } memWrite(p, south); memWrite(p, west); memWrite(p, north); memWrite(p, east); uint32_t messageCount = Modes.stats_current.messages_total + Modes.stats_alltime.messages_total; memWrite(p, messageCount); uint32_t resultCount = count; memWrite(p, resultCount); int32_t dummy = 0; memWrite(p, dummy); memWrite(p, Modes.binCraftVersion); uint32_t messageRate = nearbyint(Modes.messageRate * 10); memWrite(p, messageRate); uint32_t flags = 0; if (Modes.json_globe_index || Modes.apiShutdownDelay) { flags |= (1 << 0); } memWrite(p, flags); #undef memWrite if (p - payload > (int) elementSize) { fprintf(stderr, "apiBin: too many details in first element\n"); } p = payload + elementSize; for (int i = 0; i < count; i++) { if (unlikely(p + elementSize > end)) { fprintf(stderr, "search code deeK9OoR: count: %d need: %ld alloc: %ld\n", count, (long) ((count + 1) * elementSize), (long) alloc); break; } struct apiEntry *e = &matches[i]; memcpy(p, &e->bin, elementSize); p += elementSize; } } else { if (options->jamesv2) { p = safe_snprintf(p, end, "{\"ac\":["); } else { p = safe_snprintf(p, end, "{\"now\": %.3f", buffer->timestamp / 1000.0); p = safe_snprintf(p, end, "\n,\"aircraft\":["); } char *json = buffer->json; for (int i = 0; i < count; i++) { struct apiEntry *e = &matches[i]; struct offset off = e->jsonOffset; // READ-ONLY here if (unlikely(p + off.len + 100 >= end)) { fprintf(stderr, "search code ieva2aeV: count: %d need: %ld alloc: %ld\n", count, (long) ((p + off.len + 100) - payload), (long) alloc); break; } memcpy(p, json + off.offset, off.len); p += off.len; if (options->is_circle) { // json objects in cache are terminated by a comma: \n{ .... }, p -= 2; // remove \} and , and make sure printf puts those back p = safe_snprintf(p, end, ",\"dst\":%.3f,\"dir\":%.1f},", e->distance / 1852.0, e->direction); } } // json objects in cache are terminated by a comma: \n{ .... }, if (*(p - 1) == ',') p--; // remove trailing comma if necessary options->request_processed = microtime(); p = safe_snprintf(p, end, "\n]"); if (options->jamesv2) { p = safe_snprintf(p, end, "\n,\"msg\": \"No error\""); p = safe_snprintf(p, end, "\n,\"now\": %lld", (long long) buffer->timestamp); p = safe_snprintf(p, end, "\n,\"total\": %d", count); p = safe_snprintf(p, end, "\n,\"ctime\": %lld", (long long) buffer->timestamp); p = safe_snprintf(p, end, "\n,\"ptime\": %lld", (long long) nearbyint((options->request_processed - options->request_received) / 1000.0)); } else { p = safe_snprintf(p, end, "\n,\"resultCount\": %d", count); p = safe_snprintf(p, end, "\n,\"ptime\": %.3f", (options->request_processed - options->request_received) / 1000.0); } p = safe_snprintf(p, end, "\n}\n"); } cb.len = p - cb.buffer; size_t payload_len = p - payload; if (cb.len > alloc) { fprintf(stderr, "apiReq buffer insufficient\n"); } if (doFree) { sfree(matches); } if (options->zstd || options->zstd_encode) { struct char_buffer new = { 0 }; size_t new_alloc = API_REQ_PADSTART + ZSTD_compressBound(alloc); new.buffer = cmalloc(new_alloc); memset(new.buffer, 0x0, new_alloc); struct char_buffer dst; dst.buffer = new.buffer + API_REQ_PADSTART; dst.len = new_alloc - API_REQ_PADSTART; //fprintf(stderr, "payload_len %ld\n", (long) payload_len); size_t compressedSize = ZSTD_compressCCtx(thread->cctx, dst.buffer, dst.len, payload, payload_len, API_ZSTD_LVL); dst.len = compressedSize; new.len = API_REQ_PADSTART + compressedSize; ident(dst); //free uncompressed buffer sfree(cb.buffer); cb = new; if (ZSTD_isError(compressedSize)) { fprintf(stderr, "API zstd error: %s\n", ZSTD_getErrorName(compressedSize)); sfree(cb.buffer); cb.buffer = NULL; cb.len = 0; return cb; } //fprintf(stderr, "first 4 bytes: %08x len: %ld\n", *((uint32_t *) cb.buffer), (long) cb.len); } return cb; } static inline int apiAdd(struct apiBuffer *buffer, struct aircraft *a, int64_t now) { if (!(includeAircraftJson(now, a))) return 0; if (buffer->len >= buffer->alloc) { return -1; } struct apiEntry *entry = &(buffer->list[buffer->len]); memset(entry, 0, sizeof(struct apiEntry)); toBinCraft(a, &entry->bin, now); if (trackDataValid(&a->pos_reliable_valid)) { // position valid // else if (trackDataAge(now, &a->pos_reliable_valid) < 30 * MINUTES) } else if (a->nogpsCounter >= NOGPS_SHOW && now - a->seenAdsbReliable < NOGPS_DWELL) { // keep in box } else { // change lat / lon for sorting purposes entry->bin.lat = INT32_MAX; entry->bin.lon = INT32_MAX; } buffer->aircraftJsonCount++; entry->globe_index = a->globe_index; buffer->len++; return 1; } static inline void apiGenerateJson(struct apiBuffer *buffer, int64_t now) { sfree(buffer->json); buffer->json = NULL; size_t alloc = buffer->len * 1024 + 4096; // The initial buffer is resized as needed buffer->json = (char *) cmalloc(alloc); char *p = buffer->json; char *end = buffer->json + alloc; for (int i = 0; i < buffer->len; i++) { if ((p + 16 * 1024) >= end) { int used = p - buffer->json; alloc *= 2; buffer->json = (char *) realloc(buffer->json, alloc); p = buffer->json + used; end = buffer->json + alloc; } struct apiEntry *entry = &buffer->list[i]; struct aircraft *a = aircraftGet(entry->bin.hex); if (!a) { fprintf(stderr, "FATAL: apiGenerateJson: aircraft missing, this shouldn't happen."); setExit(2); entry->jsonOffset.offset = 0; entry->jsonOffset.len = 0; continue; } uint32_t hash; hash = hexHash(entry->bin.hex, buffer); entry->nextHex = buffer->hexHash[hash]; buffer->hexHash[hash] = entry; if (strlen(entry->bin.registration) > 0) { hash = regHash(entry->bin.registration, buffer); entry->nextReg = buffer->regHash[hash]; buffer->regHash[hash] = entry; } if (strlen(entry->bin.callsign) > 0) { hash = callsignHash(entry->bin.callsign, buffer); entry->nextCallsign = buffer->callsignHash[hash]; buffer->callsignHash[hash] = entry; } //fprintf(stderr, "callsign: %8s hash: %u\n", entry->bin.callsign, hash); char *start = p; *p++ = '\n'; p = sprintAircraftObject(p, end, a, now, 0, NULL); *p++ = ','; entry->jsonOffset.offset = start - buffer->json; entry->jsonOffset.len = p - start; } buffer->jsonLen = p - buffer->json; if (p >= end) { fprintf(stderr, "FATAL: buffer full apiAdd\n"); setExit(2); } } static int apiUpdate() { struct craftArray *ca = &Modes.aircraftActive; // always clear and update the inactive apiBuffer int flip = (atomic_load(&Modes.apiFlip[0]) + 1) % 2; struct apiBuffer *buffer = &Modes.apiBuffer[flip]; // reset buffer lengths buffer->len = 0; buffer->len_flag = 0; int acCount = ca->len; if (buffer->alloc < acCount + 32) { if (acCount > 100000) { fprintf(stderr, "<3> this is strange, too many aircraft!\n"); } buffer->alloc = acCount + 64; sfree(buffer->list); sfree(buffer->list_flag); buffer->list = cmalloc(buffer->alloc * sizeof(struct apiEntry)); buffer->list_flag = cmalloc(buffer->alloc * sizeof(struct apiEntry)); if (!buffer->list || !buffer->list_flag) { fprintf(stderr, "apiList alloc: out of memory!\n"); exit(1); } } int reallocHash = 0; while (buffer->hashBuckets < buffer->alloc) { buffer->hashBits += 1; buffer->hashBuckets = 1 << buffer->hashBits; reallocHash = 1; } if (reallocHash) { //fprintf(stderr, "-----> hashBuckets: %d\n", buffer->hashBuckets); sfree(buffer->hexHash); sfree(buffer->regHash); sfree(buffer->callsignHash); buffer->hexHash = cmalloc(buffer->hashBuckets * sizeof(struct apiEntry*)); buffer->regHash = cmalloc(buffer->hashBuckets * sizeof(struct apiEntry*)); buffer->callsignHash = cmalloc(buffer->hashBuckets * sizeof(struct apiEntry*)); } // reset hashList to NULL memset(buffer->hexHash, 0x0, buffer->hashBuckets * sizeof(struct apiEntry*)); memset(buffer->regHash, 0x0, buffer->hashBuckets * sizeof(struct apiEntry*)); memset(buffer->callsignHash, 0x0, buffer->hashBuckets * sizeof(struct apiEntry*)); // reset api list, just in case we don't set the entries completely due to oversight memset(buffer->list, 0x0, buffer->alloc * sizeof(struct apiEntry)); memset(buffer->list_flag, 0x0, buffer->alloc * sizeof(struct apiEntry)); buffer->aircraftJsonCount = 0; int64_t now = mstime(); ca_lock_read(ca); for (int i = 0; i < ca->len; i++) { struct aircraft *a = ca->list[i]; if (a == NULL) continue; int res = apiAdd(buffer, a, now);; if (res == -1) { fprintf(stderr, "transitory: skipping a couple of aircraft for api / json due to insufficient buffer\n"); break; } } ca_unlock_read(ca); // sort api lists qsort(buffer->list, buffer->len, sizeof(struct apiEntry), compareLon); apiGenerateJson(buffer, now); for (int i = 0; i < buffer->len; i++) { struct apiEntry entry = buffer->list[i]; if (entry.bin.dbFlags) { // copy entry into flags list (only contains aircraft with at least one dbFlag set buffer->list_flag[buffer->len_flag++] = entry; } } // sort not needed as order is maintained copying from main list buffer->list_pos_range = findLonRange(-180 * 1E6, 180 * 1E6, buffer->list, buffer->len); buffer->list_flag_pos_range = findLonRange(-180 * 1E6, 180 * 1E6, buffer->list_flag, buffer->len_flag); buffer->timestamp = now; // doesn't matter which of the 2 buffers the api req will use they are both pretty current for (int i = 0; i < Modes.apiThreadCount; i++) { atomic_store(&Modes.apiFlip[i], flip); } pthread_cond_signal(&Threads.json.cond); pthread_cond_signal(&Threads.globeJson.cond); return buffer->len; } static int shutClose(int fd) { if (shutdown(fd, SHUT_RDWR) < 0) { // Secondly, terminate the reliable delivery if (errno != ENOTCONN && errno != EINVAL) { // SGI causes EINVAL fprintf(stderr, "API: Shutdown client socket failed.\n"); } } return close(fd); } static void apiCloseCon(struct apiCon *con, struct apiThread *thread) { if (!con->open) { fprintf(stderr, "apiCloseCon double close!\n"); return; } int fd = con->fd; if (con->events && epoll_ctl(thread->epfd, EPOLL_CTL_DEL, fd, NULL)) { fprintf(stderr, "apiCloseCon: EPOLL_CTL_DEL %d: %s\n", fd, strerror(errno)); } con->events = 0; if (shutClose(fd) != 0) { perror("apiCloseCon: close:"); } if (Modes.debug_api) { fprintf(stderr, "%d %d apiCloseCon()\n", thread->index, fd); } sfree(con->request.buffer); con->request.len = 0; con->request.alloc = 0; struct char_buffer *reply = &con->reply; thread->responseBytesBuffered -= reply->len; sfree(reply->buffer); reply->len = 0; reply->alloc = 0; con->open = 0; thread->conCount--; // put it back on the stack of free connection structs thread->stack[thread->stackCount++] = con; //fprintf(stderr, "%2d %5d\n", thread->index, thread->conCount); } static void apiResetCon(struct apiCon *con, struct apiThread *thread) { if (!con->open) { fprintf(stderr, "apiResetCon called on closed connection!\n"); return; } if (!con->keepalive) { apiCloseCon(con, thread); return; } if (Modes.debug_api) { fprintf(stderr, "%d %d apiResetCon\n", thread->index, con->fd); } // not freeing request buffer, rather reusing it con->request.len = 0; con->bytesSent = 0; struct char_buffer *reply = &con->reply; thread->responseBytesBuffered -= reply->len; // free reply buffer sfree(reply->buffer); reply->len = 0; reply->alloc = 0; con->lastReset = mstime(); } static void sendStatus(int fd, int keepalive, const char *http_status) { char buf[256]; char *p = buf; char *end = buf + sizeof(buf); p = safe_snprintf(p, end, "HTTP/1.1 %s\r\n" "Server: readsb/wiedehopf\r\n" "Connection: %s\r\n" "Cache-control: no-store\r\n" "Content-length: 0\r\n\r\n", http_status, keepalive ? "keep-alive" : "close"); int res = send(fd, buf, strlen(buf), 0); MODES_NOTUSED(res); } static void send200(int fd, int keepalive) { sendStatus(fd, keepalive, "200 OK"); } static void send400(int fd, int keepalive) { sendStatus(fd, keepalive, "400 Bad Request"); } static void send405(int fd, int keepalive) { sendStatus(fd, keepalive, "405 Method Not Allowed"); } static void send505(int fd, int keepalive) { sendStatus(fd, keepalive, "505 HTTP Version Not Supported"); } static void send503(int fd, int keepalive) { sendStatus(fd, keepalive, "503 Service Unavailable"); } static void send500(int fd, int keepalive) { sendStatus(fd, keepalive, "500 Internal Server Error"); } static int parseDoubles(char *start, char *end, double *results, int max) { int count = 0; char *sot; char *eot = start - 1; char *endptr = NULL; //fprintf(stderr, "%s\n", start); while ((sot = eot + 1) < end) { eot = memchr(sot, ',', end - sot); if (!eot) { eot = end; // last token memchr returns NULL and eot is set to end } *eot = '\0'; results[count++] = strtod(sot, &endptr); if (eot != endptr) { return -1; } if (count > max) { return -1; } } return count; } // expects lower cased input static struct char_buffer parseFetch(struct apiCon *con, struct char_buffer *request, struct apiOptions *options, struct apiThread *thread) { struct char_buffer invalid = { 0 }; char *req = request->buffer; // GET URL HTTPVERSION char *query = memchr(req, '?', request->len); if (!query) { return invalid; } // skip URL to after ? which signifies start of query options query++; // find end of query char *eoq = memchr(query, ' ', request->len); if (!eoq) { return invalid; } // we only want the URL *eoq = '\0'; // set some option defaults: options->above_alt_baro = INT32_MIN; options->below_alt_baro = INT32_MAX; options->request_received = microtime(); char *sot; char *eot = query - 1; while ((sot = eot + 1) < eoq) { eot = memchr(sot, '&', eoq - sot); if (!eot) { eot = eoq; // last token memchr returns NULL and eot is set to eoq } *eot = '\0'; char *p = sot; char *option = strsep(&p, "="); char *value = p; if (value) { //fprintf(stderr, "%s=%s\n", option, value); // handle parameters WITH associated value if (byteMatchStrict(option, "box")) { options->is_box = 1; double *box = options->box; int count = parseDoubles(value, eot, box, 4); if (count < 4) return invalid; for (int i = 0; i < 4; i++) { if (box[i] > 180 || box[i] < -180) return invalid; } if (box[0] > box[1]) return invalid; } else if (byteMatchStrict(option, "closest") || byteMatchStrict(option, "circle")) { options->is_circle = 1; if (byteMatchStrict(option, "closest")) { options->closest = 1; } struct apiCircle *circle = &options->circle; double numbers[3]; int count = parseDoubles(value, eot, numbers, 3); if (count < 3) return invalid; circle->onlyClosest = options->closest; circle->lat = numbers[0]; circle->lon = numbers[1]; // user input in nmi, internally we use meters circle->radius = numbers[2] * 1852; //fprintf(stderr, "%.1f, %.1f, %.1f\n", circle->lat, circle->lon, circle->radius); if (circle->lat > 90 || circle->lat < -90) return invalid; if (circle->lon > 180 || circle->lon < -180) return invalid; } else if (byteMatchStrict(option, "find_hex") || byteMatchStrict(option, "hexlist")) { options->is_hexList = 1; int hexCount = 0; int maxCount = API_REQ_LIST_MAX; uint32_t *hexList = options->hexList; char *saveptr = NULL; char *endptr = NULL; char *tok = strtok_r(value, ",", &saveptr); while (tok && hexCount < maxCount) { int other = 0; if (tok[0] == '~') { other = 1; tok++; // skip over ~ } uint32_t hex = (uint32_t) strtol(tok, &endptr, 16); if (tok != endptr) { hex |= (other ? MODES_NON_ICAO_ADDRESS : 0); hexList[hexCount] = hex; hexCount++; //fprintf(stderr, "%06x\n", hex); } tok = strtok_r(NULL, ",", &saveptr); } options->hexCount = hexCount; } else if (byteMatchStrict(option, "find_callsign")) { options->is_callsignList = 1; int callsignCount = 0; int maxCount = API_REQ_LIST_MAX; char *callsignList = options->callsignList; char *saveptr = NULL; char *endptr = NULL; char *tok = strtok_r(value, ",", &saveptr); while (tok && callsignCount < maxCount) { strncpy(callsignList + callsignCount * 8, tok, 8); if (tok != endptr) callsignCount++; tok = strtok_r(NULL, ",", &saveptr); } if (callsignCount == 0) return invalid; options->callsignCount = callsignCount; } else if (byteMatchStrict(option, "find_reg")) { options->is_regList = 1; int regCount = 0; int maxCount = API_REQ_LIST_MAX; char *regList = options->regList; char *saveptr = NULL; char *endptr = NULL; char *tok = strtok_r(value, ",", &saveptr); int regLen = memberSize(struct binCraft, registration); while (tok && regCount < maxCount) { strncpy(regList + regCount * regLen, tok, regLen); if (tok != endptr) regCount++; tok = strtok_r(NULL, ",", &saveptr); } if (regCount == 0) return invalid; options->regCount = regCount; } else if (byteMatchStrict(option, "find_type") || byteMatchStrict(option, "filter_type")) { if (byteMatchStrict(option, "find_type")) { options->is_typeList = 1; } else { options->filter_typeList = 1; } int typeCount = 0; int typeLen = memberSize(struct binCraft, typeCode); int maxCount = API_REQ_LIST_MAX; char *typeList = options->typeList; char *saveptr = NULL; char *endptr = NULL; char *tok = strtok_r(value, ",", &saveptr); while (tok && typeCount < maxCount) { strncpy(typeList + typeCount * typeLen, tok, typeLen); if (tok != endptr) typeCount++; tok = strtok_r(NULL, ",", &saveptr); } if (typeCount == 0) return invalid; options->typeCount = typeCount; } else if (byteMatchStrict(option, "filter_callsign_exact")) { options->filter_callsign_exact = 1; memset(options->callsign_exact, 0x0, sizeof(options->callsign_exact)); strncpy(options->callsign_exact, value, memberSize(struct binCraft, callsign)); } else if (byteMatchStrict(option, "filter_callsign_prefix")) { options->filter_callsign_prefix = 1; memset(options->callsign_prefix, 0x0, sizeof(options->callsign_prefix)); strncpy(options->callsign_prefix, value, memberSize(struct binCraft, callsign)); } else if (byteMatchStrict(option, "above_alt_baro")) { options->filter_alt_baro = 1; options->above_alt_baro = strtol(value, NULL, 10); } else if (byteMatchStrict(option, "below_alt_baro")) { options->filter_alt_baro = 1; options->below_alt_baro = strtol(value, NULL, 10); } else if (byteMatchStrict(option, "filter_squawk")) { options->filter_squawk = 1; //int dec = strtol(value, NULL, 10); //options->squawk = (dec / 1000) * 16*16*16 + (dec / 100 % 10) * 16*16 + (dec / 10 % 10) * 16 + (dec % 10); int hex = strtol(value, NULL, 16); //fprintf(stderr, "%04d %04x\n", dec, hex); options->squawk = hex; } else { return invalid; } } else { // handle parameters WITHOUT associated value if (byteMatchStrict(option, "json")) { // this is the default } else if (byteMatchStrict(option, "jv2")) { options->jamesv2 = 1; } else if (byteMatchStrict(option, "zstd")) { options->zstd = 1; } else if (byteMatchStrict(option, "bincraft")) { options->binCraft = 1; } else if (byteMatchStrict(option, "all")) { options->all = 1; } else if (byteMatchStrict(option, "all_with_pos")) { options->all_with_pos = 1; } else if (byteMatchStrict(option, "filter_with_pos")) { options->filter_with_pos = 1; } else if (byteMatchStrict(option, "filter_mil")) { options->filter_dbFlag = 1; options->filter_mil = 1; } else if (byteMatchStrict(option, "filter_interesting")) { options->filter_dbFlag = 1; options->filter_interesting = 1; } else if (byteMatchStrict(option, "filter_pia")) { options->filter_dbFlag = 1; options->filter_pia = 1; } else if (byteMatchStrict(option, "filter_ladd")) { options->filter_dbFlag = 1; options->filter_ladd = 1; } else if (byteMatchStrict(option, "include_version")) { con->include_version = 1; } else { return invalid; } } } int mainOptionCount = options->is_box + options->is_circle + options->is_hexList + options->is_callsignList + options->is_regList + options->is_typeList + options->all + options->all_with_pos; if (mainOptionCount != 1) { if (mainOptionCount == 2 && options->is_hexList && options->is_box) { // this is ok } else { return invalid; } } if (options->is_typeList && options->filter_typeList) { return invalid; } //fprintf(stderr, "parseFetch calling apiReq\n"); if (options->zstd) { // don't double zstd compress options->zstd_encode = 0; con->content_type = "application/zstd"; } else if (options->binCraft) { con->content_type = "application/octet-stream"; } else { con->content_type = "application/json"; } return apiReq(thread, options); } static void apiSendData(struct apiCon *con, struct apiThread *thread) { struct char_buffer *reply = &con->reply; int toSend = reply->len - con->bytesSent; if (toSend <= 0) { if ((con->events & EPOLLOUT)) { con->events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; struct epoll_event epollEvent = { .events = con->events }; epollEvent.data.ptr = con; if (epoll_ctl(thread->epfd, EPOLL_CTL_MOD, con->fd, &epollEvent)) { perror("apiResetCon() epoll_ctl fail:"); } } if (toSend < 0) { fprintf(stderr, "wat?! toSend < 0\n"); } return; } char *dataStart = reply->buffer + con->bytesSent; int nwritten = send(con->fd, dataStart, toSend, 0); if (nwritten > 0) { con->bytesSent += nwritten; } // all data has been sent, reset the connection if (nwritten == toSend) { apiResetCon(con, thread); return; } if (nwritten < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { // no progress, make sure EPOLLOUT is set. } else { // non recoverable error, close connection if (antiSpam(&thread->antiSpam[0], 5 * SECONDS)) { fprintf(stderr, "apiSendData fail: %s (was trying to send %d bytes)\n", strerror(errno), toSend); } apiCloseCon(con, thread); return; } } //fprintf(stderr, "wrote only %d of %d\n", nwritten, toSend); // couldn't write everything, set EPOLLOUT if (!(con->events & EPOLLOUT)) { con->events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP | EPOLLOUT; struct epoll_event epollEvent = { .events = con->events }; epollEvent.data.ptr = con; if (epoll_ctl(thread->epfd, EPOLL_CTL_MOD, con->fd, &epollEvent)) { perror("apiSendData() epoll_ctl fail:"); } } return; } static void apiShutdown(struct apiCon *con, struct apiThread *thread, int line, int err) { if (con->bytesSent != con->reply.len) { if (antiSpam(&thread->antiSpam[1], 5 * SECONDS)) { fprintf(stderr, "Connection shutdown with incomplete or no reply sent." " (reply.len: %d, bytesSent: %d, request.len: %d open: %d line: %d errno: %s)\n", (int) con->reply.len, (int) con->bytesSent, (int) con->request.len, con->open, line, err ? strerror(err) : "-"); } } apiCloseCon(con, thread); } static void apiReadRequest(struct apiCon *con, struct apiThread *thread) { // delay processing requests until we have more memory if (thread->responseBytesBuffered > 512 * 1024 * 1024) { if (antiSpam(&thread->antiSpam[2], 5 * SECONDS)) { fprintf(stderr, "Delaying request processing due to per thread memory limit: 512 MB\n"); } return; } int nread, toRead; int fd = con->fd; struct char_buffer *request = &con->request; int end_pad = 32; size_t requestMax = 1024 + 13 * API_REQ_LIST_MAX + end_pad; if (request->len > requestMax) { send400(con->fd, con->keepalive); apiResetCon(con, thread); return; } if (!request->alloc) { request->alloc = 2048; request->buffer = realloc(request->buffer, request->alloc); } else if (request->len + end_pad + 512 > request->alloc) { request->alloc = requestMax; request->buffer = realloc(request->buffer, request->alloc); } if (!request->buffer) { fprintf(stderr, "FATAL: apiReadRequest request->buffer malloc fail\n"); setExit(2); send503(con->fd, con->keepalive); apiCloseCon(con, thread); return; } toRead = (request->alloc - end_pad) - request->len; nread = recv(fd, request->buffer + request->len, toRead, 0); if (Modes.debug_api) { fprintf(stderr, "%d %d nread %d\n", thread->index, con->fd, nread); } if (nread < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { return; } apiShutdown(con, thread, __LINE__, errno); return; } if (nread == 0) { apiShutdown(con, thread, __LINE__, 0); return; } if (con->reply.buffer) { int toSend = con->reply.len - con->bytesSent; fprintf(stderr, "wat?! reply buffer but got new request data. toSend: %d\n", toSend); apiCloseCon(con, thread); return; } int oldlen = request->len; if (nread > 0) { request->len += nread; // terminate string request->buffer[request->len] = '\0'; } int req_len = request->len; char *req_start = request->buffer; char *newline = memchr(req_start + oldlen, '\n', req_len); if (!newline || !strstr(req_start + imax(0, oldlen - 4), "\r\n\r\n")) { // request not complete return; } thread->requestCount++; char *eol = memchr(req_start, '\n', req_len) + 1; // we already know we have at least one newline char *req_end = req_start + req_len; char *protocol = eol - litLen("HTTP/1.x\r\n"); // points to H if (protocol < req_start) { send505(con->fd, con->keepalive); apiResetCon(con, thread); return; } struct apiOptions optionsBack = { 0 }; struct apiOptions *options = &optionsBack; // set end padding to zeros for byteMatchStart and byteMatch (memcmp) use without regrets memset(req_end, 0, end_pad); int isGET = byteMatchStart(req_start, "GET"); char *http_minor_version = protocol + litLen("HTTP/1."); if (!byteMatchStart(protocol, "HTTP/1.") || !((*http_minor_version == '0') || (*http_minor_version == '1'))) { send505(con->fd, con->keepalive); apiResetCon(con, thread); return; } con->http_minor_version = (*http_minor_version == '1') ? 1 : 0; // parseFetch expects lower cased input // lower case entire request // HTTP / GET checks are done above as they are case sensitive _unroll_32 for (int k = 0; k < req_len; k++) { req_start[k] = tolower(req_start[k]); } // header parsing char *hl = eol; con->keepalive = con->http_minor_version == 1 ? 1 : 0; while (hl < req_end && (eol = memchr(hl, '\n', req_end - hl))) { *eol = '\0'; if (byteMatchStart(hl, "accept-encoding")) { if (strstr(hl, "zstd")) { options->zstd_encode = 1; } } if (byteMatchStart(hl, "connection")) { if (strstr(hl, "close")) { con->keepalive = 0; } else if (con->keepalive || strstr(hl, "keep-alive")) { con->keepalive = 1; } } hl = eol + 1; } if (!isGET) { send405(con->fd, con->keepalive); apiResetCon(con, thread); return; } //fprintf(stderr, "%s\n", request->buffer); thread->request_len_sum += req_len; thread->request_count++; if (thread->request_count % (1000 * 1000) == 0) { int64_t avg = thread->request_len_sum / thread->request_count; thread->request_len_sum = 0; thread->request_count = 0; fprintf(stderr, "API average req_len: %d\n", (int) avg); } char *status = protocol - litLen("?status "); if (status > req_start && byteMatchStart(status, "?status ")) { if (Modes.exitSoon) { send503(con->fd, con->keepalive); } else { send200(con->fd, con->keepalive); } apiResetCon(con, thread); return; } con->content_type = "multipart/mixed"; struct char_buffer reply = parseFetch(con, request, options, thread); if (reply.len == 0) { //fprintf(stderr, "parseFetch returned invalid\n"); send400(con->fd, con->keepalive); apiResetCon(con, thread); return; } thread->responseBytesBuffered += reply.len; // at header before payload char header[API_REQ_PADSTART]; char *p = header; char *end = header + API_REQ_PADSTART; int content_len = reply.len - API_REQ_PADSTART; p = safe_snprintf(p, end, "HTTP/1.1 200 OK\r\n" "Server: readsb/wiedehopf\r\n" "%s" "Content-Type: %s\r\n" "Connection: %s\r\n" "Cache-Control: no-store\r\n" "%s" "Content-Length: %d\r\n\r\n", con->include_version ? "readsb_version: "MODES_READSB_VERSION"\r\n" : "", con->content_type, con->keepalive ? "keep-alive" : "close", options->zstd_encode ? "Content-Encoding: zstd\r\n" : "", content_len); int hlen = p - header; //fprintf(stderr, "hlen %d\n", hlen); if (hlen >= API_REQ_PADSTART) { fprintf(stderr, "API error: API_REQ_PADSTART insufficient\n"); send500(con->fd, con->keepalive); apiResetCon(con, thread); return; } // increase bytesSent counter so we don't transmit the empty buffer before the header con->bytesSent = API_REQ_PADSTART - hlen; // copy the header into the correct position immediately before the payload (which we already have) memcpy(reply.buffer + con->bytesSent, header, hlen); con->reply = reply; apiSendData(con, thread); } static void acceptCon(struct apiCon *con, struct apiThread *thread) { int listen_fd = con->fd; struct sockaddr_storage storage; struct sockaddr *saddr = (struct sockaddr *) &storage; socklen_t slen = sizeof(storage); // accept at most 16 connections per epoll_wait wakeup and thread for (int j = 0; j < 16; j++) { int fd = accept4(listen_fd, saddr, &slen, SOCK_NONBLOCK); if (fd < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { break; } else if (errno == EMFILE) { if (antiSpam(&thread->antiSpam[5], 5 * SECONDS)) { fprintf(stderr, "<3>Out of file descriptors accepting api clients, " "exiting to make sure we don't remain in a broken state!\n"); } setExit(2); break; } else { if (antiSpam(&thread->antiSpam[6], 5 * SECONDS)) { fprintf(stderr, "api acceptCon(): Error accepting new connection: errno: %d %s\n", errno, strerror(errno)); } break; } } // when starving for connections, close old connections if (!thread->stackCount) { int64_t now = mstime(); int64_t bounce_delay = 5 * SECONDS; if (now > thread->next_bounce) { thread->next_bounce = now + bounce_delay / 20; if (antiSpam(&thread->antiSpam[3], 5 * SECONDS)) { fprintf(stderr, "starving for connections, closing all connections idle for 5 or more seconds\n"); } for (int j = 0; j < Modes.api_fds_per_thread; j++) { struct apiCon *con = &thread->cons[j]; if (now - con->lastReset > bounce_delay) { apiCloseCon(con, thread); } } } } // reject new connection if we still don't have a free connection if (!thread->stackCount) { if (antiSpam(&thread->antiSpam[4], 5 * SECONDS)) { fprintf(stderr, "too many concurrent connections, rejecting new connections, sendng 503s :/\n"); } send503(fd, 0); if (shutClose(fd) != 0) { if (antiSpam(&thread->antiSpam[4], 5 * SECONDS)) { perror("accept: shutClose failed when rejecting a new connection:"); } } return; } // take a free connection from the stack struct apiCon *con = thread->stack[--thread->stackCount]; memset(con, 0, sizeof(struct apiCon)); thread->conCount++; con->open = 1; con->fd = fd; con->lastReset = mstime(); if (Modes.debug_api) { fprintf(stderr, "%d %d acceptCon()\n", thread->index, fd); } int op = EPOLL_CTL_ADD; con->events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; struct epoll_event epollEvent = { .events = con->events }; epollEvent.data.ptr = con; if (epoll_ctl(thread->epfd, op, fd, &epollEvent)) { perror("acceptCon() epoll_ctl fail:"); } } } static void *apiThreadEntryPoint(void *arg) { struct apiThread *thread = (struct apiThread *) arg; srandom(get_seed()); int core = imax(0, Modes.num_procs - Modes.apiThreadCount + thread->index); //fprintf(stderr, "%d\n", core); threadAffinity(core); // set this thread low priority setLowestPriorityPthread(); thread->cons = cmalloc(Modes.api_fds_per_thread * sizeof(struct apiCon)); memset(thread->cons, 0x0, Modes.api_fds_per_thread * sizeof(struct apiCon)); thread->stack = cmalloc(Modes.api_fds_per_thread * sizeof(struct apiCon *)); for (int k = 0; k < Modes.api_fds_per_thread; k++) { thread->stack[k] = &thread->cons[k]; thread->stackCount++; } thread->cctx = ZSTD_createCCtx(); thread->epfd = my_epoll_create(&Modes.exitNowEventfd); for (int i = 0; i < Modes.apiService.listener_count; ++i) { struct apiCon *con = Modes.apiListeners[i]; struct epoll_event epollEvent = { .events = con->events }; epollEvent.data.ptr = con; if (epoll_ctl(thread->epfd, EPOLL_CTL_ADD, con->fd, &epollEvent)) { perror("apiThreadEntryPoint() epoll_ctl fail:"); } } int count = 0; struct epoll_event *events = NULL; int maxEvents = 0; struct timespec cpu_timer; start_cpu_timing(&cpu_timer); int64_t next_stats_sync = 0; while (!Modes.exit) { if (count == maxEvents) { epollAllocEvents(&events, &maxEvents); } int64_t wait_ms = 5 * SECONDS; #ifdef NO_EVENT_FD wait_ms = imin(wait_ms, 100); // no event_fd, limit sleep to 100 ms #endif count = epoll_wait(thread->epfd, events, maxEvents, wait_ms); int64_t now = mstime(); if (now > next_stats_sync) { next_stats_sync = now + 1 * SECONDS; struct timespec used = { 0 }; end_cpu_timing(&cpu_timer, &used); int micro = (int) (used.tv_sec * 1000LL * 1000LL + used.tv_nsec / 1000LL); atomic_fetch_add(&Modes.apiWorkerCpuMicro, micro); start_cpu_timing(&cpu_timer); //fprintf(stderr, "%2d %5d\n", thread->index, thread->conCount); unsigned int requestCount = thread->requestCount; atomic_fetch_add(&Modes.apiRequestCounter, requestCount); thread->requestCount = 0; } for (int i = 0; i < count; i++) { struct epoll_event event = events[i]; if (event.data.ptr == &Modes.exitNowEventfd) continue; struct apiCon *con = event.data.ptr; if (con->accept && (event.events & EPOLLIN)) { acceptCon(con, thread); continue; } if (event.events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) { if (con->open) { apiReadRequest(con, thread); } else { apiShutdown(con, thread, __LINE__, 0); } continue; } if (con->open) { if (event.events & EPOLLIN) { apiReadRequest(con, thread); } if (event.events & EPOLLOUT) { apiSendData(con, thread); } } if (con->wakeups++ > 512 * 1024) { if (antiSpam(&thread->antiSpam[7], 5 * SECONDS)) { fprintf(stderr, "connection triggered too many events (bad webserver logic), send 500 :/ (EPOLLIN: %d, EPOLLOUT: %d) " "(reply.len: %d, bytesSent: %d, request.len: %d open: %d)\n", (event.events & EPOLLIN), (event.events & EPOLLOUT), (int) con->reply.len, (int) con->bytesSent, (int) con->request.len, con->open); } send500(con->fd, con->keepalive); apiCloseCon(con, thread); continue; } } } for (int j = 0; j < Modes.api_fds_per_thread; j++) { struct apiCon *con = &thread->cons[j]; if (con->open) { apiCloseCon(con, thread); } } sfree(events); ZSTD_freeCCtx(thread->cctx); close(thread->epfd); sfree(thread->stack); sfree(thread->cons); return NULL; } static void *apiUpdateEntryPoint(void *arg) { MODES_NOTUSED(arg); srandom(get_seed()); pthread_mutex_lock(&Threads.apiUpdate.mutex); struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); struct timespec cpu_timer; while (!Modes.exit) { struct timespec watch; int debug = 0; if (debug) { fprintTimePrecise(stderr, mstime()); fprintf(stderr, " apiUpdate\n"); startWatch(&watch); } start_cpu_timing(&cpu_timer); int ac_count = apiUpdate(); end_cpu_timing(&cpu_timer, &Modes.stats_current.api_update_cpu); if (0 && debug) { int64_t elapsed = stopWatch(&watch); fprintTimePrecise(stderr, mstime()); fprintf(stderr, " apiUpdate took: %.3f s for %d aircraft!\n", elapsed / 1000.0, ac_count); } int64_t now = mstime(); int64_t ival = Modes.json_interval; int64_t remaining = ival - (now % ival); if (remaining < 5) { remaining += ival; } int64_t waitStarted = now; int64_t elapsed = 0; do { threadTimedWait(&Threads.apiUpdate, &ts, remaining - elapsed); //fprintf(stderr, "."); elapsed = mstime() - waitStarted; } while (!Modes.exit && elapsed < remaining); } pthread_mutex_unlock(&Threads.apiUpdate.mutex); return NULL; } void apiBufferInit() { if (Modes.apiThreadCount <= 0) { // if --devel=apiThreads, isn't given, use nproc - 1 api threads // don't use more than 8 threads unless explicit count is given Modes.apiThreadCount = imin(8, imax(1, Modes.num_procs - 1)); } size_t size = sizeof(struct apiThread) * Modes.apiThreadCount; Modes.apiThread = cmalloc(size); memset(Modes.apiThread, 0x0, size); size = sizeof(atomic_int) * Modes.apiThreadCount; Modes.apiFlip = cmalloc(size); memset(Modes.apiFlip, 0x0, size); apiUpdate(); // run an initial apiUpdate Modes.apiBufferInitDone++; if (Modes.apiBufferInitDone != 1) { fprintf(stderr, "FATAL: buffer init call fail %d\n", Modes.apiBufferInitDone); setExit(2); return; } threadCreate(&Threads.apiUpdate, NULL, apiUpdateEntryPoint, NULL); } void apiBufferCleanup() { threadSignalJoin(&Threads.apiUpdate); for (int i = 0; i < 2; i++) { sfree(Modes.apiBuffer[i].list); sfree(Modes.apiBuffer[i].list_flag); sfree(Modes.apiBuffer[i].json); sfree(Modes.apiBuffer[i].hexHash); sfree(Modes.apiBuffer[i].regHash); sfree(Modes.apiBuffer[i].callsignHash); } sfree(Modes.apiThread); sfree(Modes.apiFlip); } void apiInit() { if (Modes.apiBufferInitDone != 1) { fprintf(stderr, "FATAL: buffer init call fail %d\n", Modes.apiBufferInitDone); setExit(2); return; } Modes.apiService.descr = "API output"; serviceListen(&Modes.apiService, Modes.net_bind_address, Modes.net_output_api_ports, -1); fprintf(stderr, "\n"); if (Modes.apiService.listener_count <= 0) { Modes.api = 0; return; } Modes.apiListeners = cmalloc(sizeof(struct apiCon*) * Modes.apiService.listener_count); memset(Modes.apiListeners, 0, sizeof(struct apiCon*) * Modes.apiService.listener_count); for (int i = 0; i < Modes.apiService.listener_count; ++i) { struct apiCon *con = cmalloc(sizeof(struct apiCon)); memset(con, 0, sizeof(struct apiCon)); if (!con) fprintf(stderr, "EMEM, how much is the fish?\n"), exit(1); Modes.apiListeners[i] = con; con->fd = Modes.apiService.listener_fds[i]; con->accept = 1; #ifndef EPOLLEXCLUSIVE #define EPOLLEXCLUSIVE (0) #endif con->events = EPOLLIN | EPOLLEXCLUSIVE; } Modes.api_fds_per_thread = imin(Modes.max_fds_api / Modes.apiThreadCount, Modes.apiThreadCount * 4096); if (Modes.api_fds_per_thread < 1) { Modes.api_fds_per_thread = 1; fprintf(stderr, "WARNING: Setting Modes.api_fds_per_thread = 1 because it was %d\n", Modes.api_fds_per_thread); } //fprintf(stderr, "Modes.api_fds_per_thread: %d\n", Modes.api_fds_per_thread); for (int i = 0; i < Modes.apiThreadCount; i++) { Modes.apiThread[i].index = i; pthread_create(&Modes.apiThread[i].thread, NULL, apiThreadEntryPoint, &Modes.apiThread[i]); } } void apiCleanup() { for (int i = 0; i < Modes.apiThreadCount; i++) { pthread_join(Modes.apiThread[i].thread, NULL); } struct net_service *service = &Modes.apiService; for (int i = 0; i < service->listener_count; ++i) { sfree(Modes.apiListeners[i]); } sfree(Modes.apiListeners); sfree(service->listener_fds); if (service->unixSocket) { unlink(service->unixSocket); sfree(service->unixSocket); } } struct char_buffer apiGenerateAircraftJson(threadpool_buffer_t *pbuffer) { struct char_buffer cb = { 0 }; int flip = atomic_load(&Modes.apiFlip[0]); struct apiBuffer *buffer = &Modes.apiBuffer[flip]; ssize_t alloc = buffer->jsonLen + 8 * 1024; char *buf = check_grow_threadpool_buffer_t(pbuffer, alloc); char *p = buf; char *end = buf + alloc; if (!buf) { return cb; } p = safe_snprintf(p, end, "{ \"now\" : %.3f,\n" " \"messages\" : %u,\n", buffer->timestamp / 1000.0, Modes.stats_current.messages_total + Modes.stats_alltime.messages_total); //fprintf(stderr, "%.3f\n", ((double) mstime() - (double) buffer->timestamp) / 1000.0); p = safe_snprintf(p, end, " \"aircraft\" : ["); memcpy(p, buffer->json, buffer->jsonLen); p += buffer->jsonLen; // json objects in cache are terminated by a comma: \n{ .... }, if (*(p-1) == ',') p--; p = safe_snprintf(p, end, "\n ]\n}\n"); cb.len = p - buf; cb.buffer = buf; return cb; } struct char_buffer apiGenerateGlobeJson(int globe_index, threadpool_buffer_t *pbuffer) { assert (globe_index <= GLOBE_MAX_INDEX); struct char_buffer cb = { 0 }; int flip = atomic_load(&Modes.apiFlip[0]); struct apiBuffer *buffer = &Modes.apiBuffer[flip]; ssize_t alloc = 16 * 1024 + buffer->jsonLen; char *buf = check_grow_threadpool_buffer_t(pbuffer, alloc); char *p = buf; char *end = buf + alloc; p = safe_snprintf(p, end, "{ \"now\" : %.3f,\n" " \"messages\" : %u,\n", buffer->timestamp / 1000.0, Modes.stats_current.messages_total + Modes.stats_alltime.messages_total); p = safe_snprintf(p, end, " \"global_ac_count_withpos\" : %d,\n", Modes.globalStatsCount.readsb_aircraft_with_position ); p = safe_snprintf(p, end, " \"globeIndex\" : %d, ", globe_index); if (globe_index >= GLOBE_MIN_INDEX) { int grid = GLOBE_INDEX_GRID; int lat = ((globe_index - GLOBE_MIN_INDEX) / GLOBE_LAT_MULT) * grid - 90; int lon = ((globe_index - GLOBE_MIN_INDEX) % GLOBE_LAT_MULT) * grid - 180; p = safe_snprintf(p, end, "\"south\" : %d, " "\"west\" : %d, " "\"north\" : %d, " "\"east\" : %d,\n", lat, lon, lat + grid, lon + grid); } else { struct tile *tiles = Modes.json_globe_special_tiles; struct tile tile = tiles[globe_index]; p = safe_snprintf(p, end, "\"south\" : %d, " "\"west\" : %d, " "\"north\" : %d, " "\"east\" : %d,\n", tile.south, tile.west, tile.north, tile.east); } p = safe_snprintf(p, end, " \"aircraft\" : ["); for (int j = 0; j < buffer->len; j++) { struct apiEntry *entry = &buffer->list[j]; if (entry->globe_index != globe_index) continue; // check if we have enough space if (p + entry->jsonOffset.len >= end) { fprintf(stderr, "apiGenerateGlobeJson buffer overrun\n"); break; } memcpy(p, buffer->json + entry->jsonOffset.offset, entry->jsonOffset.len); p += entry->jsonOffset.len; } // json objects in cache are terminated by a comma: \n{ .... }, if (*(p - 1) == ',') p--; p = safe_snprintf(p, end, "\n ]\n}\n"); cb.len = p - buf; cb.buffer = buf; return cb; } readsb-3.16/api.h000066400000000000000000000062521505057307600136420ustar00rootroot00000000000000#ifndef API_H #define API_H #define API_REQ_PADSTART (2048) #define API_REQ_LIST_MAX 1024 #define API_ZSTD_LVL (2) struct apiCon { int fd; int accept; struct char_buffer reply; size_t bytesSent; uint32_t events; int open; int wakeups; int keepalive; int http_minor_version; int include_version; struct char_buffer request; int64_t lastReset; // milliseconds char *content_type; }; struct apiCircle { double lat; double lon; double radius; // in meters bool onlyClosest; }; struct apiOptions { int64_t request_received; // microseconds int64_t request_processed; // microseconds double box[4]; struct apiCircle circle; int is_box; int is_circle; int is_hexList; int is_callsignList; int is_regList; int is_typeList; int include_no_position; int filter_typeList; int closest; int all; int all_with_pos; int jamesv2; int filter_squawk; int binCraft; int zstd; int zstd_encode; unsigned squawk; int filter_dbFlag; int filter_mil; int filter_interesting; int filter_pia; int filter_ladd; int filter_with_pos; int filter_callsign_exact; char callsign_exact[9]; int filter_callsign_prefix; char callsign_prefix[9]; int32_t filter_alt_baro; int32_t above_alt_baro; int32_t below_alt_baro; int hexCount; uint32_t hexList[API_REQ_LIST_MAX]; int callsignCount; char callsignList[API_REQ_LIST_MAX * memberSize(struct binCraft, callsign) + 1]; int regCount; char regList[API_REQ_LIST_MAX * memberSize(struct binCraft, registration) + 1]; int typeCount; char typeList[API_REQ_LIST_MAX * memberSize(struct binCraft, typeCode) + 1]; }; struct offset { int32_t offset; int32_t len; }; struct apiEntry { struct offset jsonOffset; struct binCraft bin; struct apiEntry *nextHex; struct apiEntry *nextReg; struct apiEntry *nextCallsign; float distance; float direction; int32_t globe_index; }; struct range { int from; // inclusive int to; // exclusive }; struct apiBuffer { int len; int len_flag; int alloc; int jsonLen; char *json; struct apiEntry *list; struct apiEntry *list_flag; struct range list_pos_range; struct range list_flag_pos_range; int64_t timestamp; int hashBuckets; int hashBits; struct apiEntry **hexHash; struct apiEntry **regHash; struct apiEntry **callsignHash; uint32_t focus; int aircraftJsonCount; }; struct apiThread { pthread_t thread; int index; int epfd; int eventfd; int responseBytesBuffered; uint32_t requestCount; int conCount; int stackCount; struct apiCon *cons; struct apiCon **stack; ZSTD_CCtx* cctx; // for producing average request len numbers int64_t request_len_sum; int64_t request_count; int64_t next_bounce; int64_t antiSpam[16]; }; void apiBufferInit(); void apiBufferCleanup(); void apiInit(); void apiCleanup(); struct char_buffer apiGenerateAircraftJson(threadpool_buffer_t *pbuffer); struct char_buffer apiGenerateGlobeJson(int globe_index, threadpool_buffer_t *pbuffer); #endif readsb-3.16/argp.c000066400000000000000000000131271505057307600140140ustar00rootroot00000000000000/* * file: argp.c * description: minimal replacement for GNU Argp library * Copyright 2011 Peter Desnoyers, Northeastern University * Copyright 2022 Matthias Wirth * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. */ #include #include #include #include #include "argp.h" char *cleanarg(char *s) { char *v = strrchr(s, '/'); return v ? v+1 : s; } void argp_help(struct argp_state *state) { printf("Usage: %s [OPTIONS...] %s\n%s\n\n", state->name, state->argp->arg_doc, state->argp->prog_doc); struct argp_option *opt; for (opt = state->argp->options; opt->name != NULL || opt->doc != NULL; opt++) { if (opt->flags == OPTION_HIDDEN) continue; if (opt->name == NULL) { printf("\n%s\n", opt->doc); // help category names } else { char tmp[80], *p = tmp; p += sprintf(tmp, "--%s", opt->name); if (opt->arg) sprintf(p, "=%s", opt->arg); printf(" %-*s%s\n", state->maxlen + 8, tmp, opt->doc); } } printf(" --%-*s%s\n", state->maxlen+6, "help", "Give this help list"); printf(" --%-*s%s\n", state->maxlen+6, "usage", "Give a short usage message"); printf("\nCredits:\n%s\n", argp_program_credits); printf("\nReport bugs to %s\n", argp_program_bug_address); } void argp_usage(struct argp_state *state) { char buf[64 * 1024], *p = buf, *col0 = buf; p += sprintf(p, "Usage: %s", state->name); int indent = p-buf; struct argp_option *opt; for (opt = state->argp->options; opt->name != NULL || opt->doc != NULL; opt++) { if (opt->flags == OPTION_HIDDEN) continue; if (opt->name != NULL) { p += sprintf(p, " [--%s%s%s]", opt->name, opt->arg ? "=":"", opt->arg ? opt->arg : ""); if (p-col0 > (78-state->maxlen)) { p += sprintf(p, "\n"); col0 = p; p += sprintf(p, "%*s", indent, ""); } } } sprintf(p, " %s\n", state->argp->arg_doc); printf("%s", buf); } int argp_parse(struct argp *argp, int argc, char **argv, int flags, int tmp, void *input) { __VARNOTUSED(flags); __VARNOTUSED(tmp); int n_opts = 0; struct argp_state state = {.name = cleanarg(argv[0]), .input = input, .argp = argp}; state.arg_num = 0; /* calculate max "--opt=var" length */ int max = 0; struct argp_option *opt; for (opt = argp->options; opt->name != NULL || opt->doc != NULL; opt++) { if (opt->name != NULL) { int m = strlen(opt->name) + (opt->arg ? 1+strlen(opt->arg) : 0); max = (max < m) ? m : max; } n_opts++; } state.maxlen = max+2; struct option *long_opts = calloc((n_opts+3) * sizeof(*long_opts), 1); int argp_opt_index; int long_count = 0; for (opt = argp->options; opt->name != NULL || opt->doc != NULL; opt++) { if (opt->name != NULL) { //fprintf(stderr, "%d %s\n", opt->key, opt->name); int has_arg = opt->arg != NULL; long_opts[long_count].name = opt->name; long_opts[long_count].has_arg = has_arg ? required_argument : no_argument; long_opts[long_count].flag = &argp_opt_index; long_opts[long_count].val = (opt - argp->options); // opt index long_count++; } } // deal with version / usage / help stuff if (argc >= 2) { if (strcmp(argv[1], "-V") == 0 || strcmp(argv[1], "--version") == 0) { fprintf(stderr, "%s\n", argp_program_version); exit(argc == 2 ? 0 : 1); } if (strcmp(argv[1], "-?") == 0 || strcmp(argv[1], "--help") == 0) { argp_help(&state); exit(argc == 2 ? 0 : 1); } if (strcmp(argv[1], "--usage") == 0) { argp_usage(&state); exit(argc == 2 ? 0 : 1); } } /* we only accept long arguments - return value is zero, and 'long_index' * gives us the index into 'long_opts[]' */ int long_index; int c; optind = 1; while (argp_opt_index = -1, (c = getopt_long(argc, argv, "", long_opts, &long_index)) != -1) { if (c == '?') { fprintf(stderr, "Try `%s --help' or `%s --usage' for more information.\n", argv[0], argv[0]); return 1; } if (c != 0) { fprintf(stderr, "argp.c: unexpected getopt_long return value: %d %c\n", c, c); fprintf(stderr, "Try `%s --help' or `%s --usage' for more information.\n", argv[0], argv[0]); return 1; } if (argp_opt_index == -1) { fprintf(stderr, "argp.c: unexpected code path re3Oovei\n"); fprintf(stderr, "Try `%s --help' or `%s --usage' for more information.\n", argv[0], argv[0]); return 1; } //int index = optind - 1; //fprintf(stderr, "option %d: %s %s\n", index, argv[index], optarg); int res = argp->parser(argp->options[argp_opt_index].key, optarg, &state); if (res == ARGP_ERR_UNKNOWN) { return 1; } } while (optind < argc) { int res = argp->parser(ARGP_KEY_ARG, argv[optind++], &state); if (res == ARGP_ERR_UNKNOWN) { return 1; } } argp->parser(ARGP_KEY_END, NULL, &state); free(long_opts); return 0; } readsb-3.16/argp.h000066400000000000000000000033351505057307600140210ustar00rootroot00000000000000/* * file: argp.h * description: minimal replacement for GNU Argp library * Copyright 2011 Peter Desnoyers, Northeastern University * Copyright 2022 Matthias Wirth * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. */ #ifndef __ARGP_H__ /* This only includes the features I've used in my programs to date; * in particular, it totally ignores any sort of flag. */ #define __VARNOTUSED(V) ((void) V) #ifndef __error_t_defined typedef int error_t; # define __error_t_defined #endif // just for compat, this flag is default for this version #define ARGP_NO_EXIT (0) struct argp_option { char *name; int key; char *arg; int flags; char *doc; int group; /* ignored */ }; struct argp_state { void *input; char *name; struct argp *argp; int maxlen; int arg_num; }; struct argp { struct argp_option *options; error_t (*parser)(int key, char *arg, struct argp_state *state); const char *arg_doc; const char *prog_doc; void *unused1; void *unused2; void *unused3; }; void argp_help(struct argp_state *state); void argp_usage(struct argp_state *state); char *cleanarg(char *s); enum {ARGP_KEY_ARG, ARGP_KEY_END, ARGP_ERR_UNKNOWN, ARGP_IN_ORDER}; enum {OPTION_NONE, OPTION_DOC, OPTION_HIDDEN}; int argp_parse(struct argp *argp, int argc, char **argv, int flags, int tmp, void *input); extern const char *argp_program_version; extern const char *argp_program_credits; extern const char *argp_program_bug_address; #endif /* __ARGP_H__ */ readsb-3.16/attach_gdb.sh000077500000000000000000000006221505057307600153320ustar00rootroot00000000000000#!/bin/bash if [[ -d /opt/rh/devtoolset-9 ]]; then source scl_source enable devtoolset-9 fi while true; do date -u date sleep 5 & gdb --pid "$(pgrep -f readsb)" \ -ex 'handle SIGTERM nostop print pass' \ -ex 'handle SIGINT nostop print pass' \ -ex 'handle SIGCONT nostop print pass' \ -batch -ex continue -ex 'bt full' wait done >> gdb.log 2>&1 readsb-3.16/changelog000066400000000000000000000003441505057307600145660ustar00rootroot00000000000000readsb (3.16) stable; urgency=medium * debian: cleanup package build / service file * debian: autogenerate manpage during build using help2man -- Matthias Wirth Mon, 18 Aug 2025 09:00:00 +0000 readsb-3.16/checkapi.sh000077500000000000000000000236131505057307600150260ustar00rootroot00000000000000#!/bin/bash hexlist1000="4bb18f,8832b5,4d0106,704015,75010a,7582f5,8003b3,8a060e,885970,88530c,3c4581,7503fd,880841,8815b4,881057,880c44,8840f5,770588,a9733c,880453,880849,7503db,884202,880845,4bb188,750042,884347,885175,76d1c4,88434e,76cdb6,7503f0,4242ff,76cef7,76bcc8,750324,7502de,06a10e,750480,75046e,76cdac,750220,8a04a8,4bb154,4ba956,710063,750466,7504c4,7bd008,884207,702092,885961,750244,899000,76b869,7503ec,8a04f7,8a07c9,76cc6a,7504f7,ad9e66,880450,7504aa,06a102,78084a,86e7a2,8a08a1,76e4c6,76e4cc,781153,76e4c4,76e4cb,76e4ce,76e4cd,76b444,7817e1,86ef06,68220d,06a03d,7813ba,8a0227,8a04f5,7bd014,8a03b1,7c531c,7805d0,7802d4,780d13,780b87,780399,8a0551,8a07b7,888191,8a02d5,75020f,8a08a0,8a02e9,8a0813,88815a,886289,750107,8a0261,88811c,7503f3,76cdae,76cda4,888169,8a03c1,781418,8881c6,888185,8881a5,76cc64,88816b,7503b1,78137e,8a0661,8881ac,888096,888171,8a0605,88805e,888129,8880ac,750500,8a04ce,8880b4,88817c,88808a,8881be,8a0603,8a02a3,70e048,8880df,7bd023,76cd09,8881c3,8013d6,88817a,8880a5,8a0451,8880ed,888137,8a05d5,88816e,8881ba,8a0893,8a088a,88819a,750502,888179,76cc65,888173,888167,888095,8a0643,88815f,888133,88816c,888174,88813e,8880f3,76bd47,42486a,8a044e,899013,75020a,888051,8990da,7812bd,780a28,7504e8,8a0827,8a040a,8991b5,7504b5,8a0831,8991bd,8a0823,899020,780a5b,8a0515,78010e,750475,750485,8a08a7,781c50,78027d,78018f,7805fa,89647d,7819de,780e7f,8a03f3,750266,780ad5,4ba9f6,8a0455,781145,7805ec,780769,76b862,7818f2,76cc62,780cce,78058e,780d8e,7815f8,7504b1,75010b,781929,79a066,06a0e7,899039,a137df,780a2b,76d248,78088e,780df3,78124e,780625,abba93,789209,780294,4d2307,7808a1,76cc63,7c44b8,78100d,7807d3,7812c1,750065,71c275,3fe81e,8a08c7,7c2fe7,780a6c,75021c,7c3119,75025f,7c1758,7c1b0f,7c4c67,7c19a0,7c6ce8,7c49c5,7504e3,76cd0a,7c42ce,7c0742,7cf65a,7c7796,7c4f13,750264,8a08b1,76e729,750215,7c4530,780fd1,7c80fb,7c1c70,7c2bf5,780b60,8a0751,7c6d38,750069,7c2be9,888139,7c2bf6,7c3f19,780b5c,780c45,7c683d,7816f5,780671,7c009f,7c49fb,7c5343,7c1bee,780d75,780901,7c7a36,7c6f94,7c6c33,7c4d44,8a06ec,7c42d6,7c4cac,7c42e1,76cd14,780d99,7c2539,7c2bcf,780ede,7c6ca2,7813dc,7801ca,781015,abb6b5,780e87,780eec,7c6843,7c4708,7c4f0a,7c77fe,7c77fa,780595,7c6c25,89505b,7c42dc,7c44ae,7c42cc,781939,88809e,a7df59,78160e,780d03,78086e,780f6b,7c1ac1,a01f37,7c7799,7c7534,7c1761,7802f1,7805b1,7816c9,89913a,89900d,8991b7,7c7aee,781601,780ba1,7c66cd,781871,781c11,7808c0,7c2bea,7817a2,7814d3,a4cb6f,781097,a7f7fc,781649,861b47,780476,78155a,76d1c3,780b99,780a4a,78088d,79a077,780c69,75839f,781949,71bd54,7801bd,899081,7810fc,899144,79a067,7c4a01,780465,780742,04008d,76cd13,780d56,780a43,899093,7811d1,780b4e,78088c,78070b,89902b,7812d5,789253,7813b5,899040,75840b,7813ab,8990d0,79a071,781676,7583d1,7819d2,4bb18a,781464,06a1cc,758598,7805fe,4d214a,780b75,758325,758532,7583fa,780f9a,7585ab,8990cf,780f35,7811cf,8990ce,7c2e2e,7c1c6f,758475,78185c,78091e,781437,780f0a,7cf9e3,86226a,899053,780376,76cee9,89901d,89902a,758404,75841b,899149,7804af,888188,758303,7c1b2d,861b20,899130,75869f,75824e,7803ae,71bf41,78060c,781d6d,7814b5,780a87,7c1473,7580b3,781363,a34291,7816a1,7585cc,7585e3,8990e3,a57e87,896473,7801e3,75860e,758306,7803b1,781685,758616,780af4,7802ac,7802fe,758610,71c285,86eaf2,06a0af,780928,758304,7582e7,758322,71c364,7803cc,a967c9,8991bc,789241,7582b4,abed10,758531,7583a7,7583e7,7583ae,86d9ae,718a07,71be16,76cc6b,86796c,885177,71c255,7586ca,71bd85,71c042,71bd07,71c365,71c345,71bf35,7c6bbf,71c397,71c015,718a93,a874bf,71bd51,71c273,780a5e,71d707,71c311,71c384,71c234,71c339,71ba01,71bc21,8990b9,71c356,89907e,71c039,71c396,ae1209,71c018,780ac8,71c334,ae10e2,71c277,71bf16,780da0,ae6212,8414a9,86ef28,ae620c,ae6b9c,888153,86960c,71c095,ae6b9b,71bf22,86d906,780dfc,71c245,851bba,71c501,89805e,76cd72,841115,868038,71ce49,71c256,71bf89,71c043,861f22,841ebc,7806a2,872f5c,a290f3,8681b9,8462c4,868088,86e488,71c304,88819b,76cda8,8465c0,71c326,7c01e4,84b83e,86d629,c04599,845bde,86cf5a,84715c,7c01e2,86d215,ae116c,84c43e,86d2cc,8681ac,86e518,8467a0,a7e1c2,789252,86cf6f,780a6d,c8196a,78922f,868041,aa7d2b,850e36,86d68f,86e86e,780abd,71c250,ae4a79,846748,84bb0e,71c359,86cec5,76cd08,846844,86e7c4,7808a8,861ae1,8511ca,71c081,861e78,84b42f,851914,3c70ca,86cee7,84bb74,851826,86dd88,899100,84b79c,aba7d9,abe1eb,7c806c,850df2,851936,841538,780963,8681b0,84bac2,84b982,a07bb3,86d61c,899137,86d602,841894,71c274,845c3b,85c5dc,84cb66,86d330,86ced2,861fa4,8417d6,8681ab,863766,841ae0,7812a1,841500,85d098,8681b6,86da16,84c214,846d80,846822,841bd1,846bfa,8463b4,424407,abc96f,86ddaa,87cc08,8515c4,7c668c,872968,841e74,84b772,868042,845d1c,84b7d8,8964c3,8518ae,86e02e,a7006f,7c5b4a,84c29c,85c55a,75827c,86eee4,87cd05,84659e,846954,851cca,7c810d,845f30,7c6d2a,84b860,7c6d90,86d943,86d607,7cf9cc,861ede,86e13e,8991c0,84657c,7c81de,86cf4d,847c18,872863,ae29cf,842250,ae5dfe,846adc,ae5dfd,ae1531,84183c,86e8ac,86cf91,861eb4,8674f0,8514f8,8510f6,86cea3,84cefa,850e7a,850d8c,84612d,8681b1,867d00,84bd16,862dd8,781ae9,850dae,634323,851c86,899125,868094,84c27a,c82337,86d98e,a65092,86d594,84c5ec,862d50,861f3c,846978,868ee4,86eea0,86e79a,86d668,85e27c,06a1bc,71c382,780a1d,7c8069,861e4e,86808a,84116c,84b940,a18cbb,a954ca,c0173f,845f5a,781941,86dd6e,846910,7c77f8,85cd04,86ce8e,7c0c9a,8470a0,84b5ac,8463b5,c82258,899124,846d0c,7c8068,aac2e2,1506c1,8991ac,7c7bce,8462ee,8694fa,7c627b,7c6bb8,86951c,4d233a,4b1a02,7c4e6b,4d2089,841e00,8682fc,7c6695,ae638d,750255,867fac,7c531f,867fce,781094,7c1c23,7c2baf,7c7c77,a4c34c,06a1ea,7c6dbb,7cf359,7c7a3f,7c77f9,7cf6cb,7cf353,7c7ccc,7c7ab5,800740,7c4ef5,7c6dea,7c6c92,7c07ad,7c2fe1,7c6685,7c7c16,77058c,7c6ca0,7c4878,8695c6,7c6d7f,7c6de5,7c77fc,7c16f1,7c1662,7c5342,7c5348,7c39f1,7c451d,7c6b36,7c335c,7c6d2f,7c6c52,7c6273,7c423e,7c7ab6,86d682,7c4515,7c4512,7c7caf,7c809a,86d930,7c4513,7c4518,7c7bcc,7c7621,7c29d4,7c6deb,7c7a38,7c6b0b,780da8,7c6d9e,7c7ab8,a31a57,750256,7c6d39,a21633,7c5e46,7c6319,76cd0c,7c7aa1,7c7ab2,7c530b,7c801e,7c6ded,4d0104,7c7a45,7c4487,7c5b4c,7c68a5,7c6ddd,7c498f,7c3316,7c29d8,7c5d71,7c5300,7c585f,7c7c9c,7c77fd,7c6d2e,7c3b3e,7cf8ed,a81f7b,7c6d9b,7c7bd6,7c1fbd,7c2590,7c0bda,7c5310,7c7f01,aa7468,7c6d30,7cf7cc,7cfa0d,abd6c6,7c3559,aa7f8d,7c47ce,780b7d,7c16de,7c6691,7c4d14,76bd48,7c43d9,7c6de4,867f68,7c6d24,7c6276,7c6d2b,7c1469,896209,899097,7c6d8c,ac0d21,7c5323,7c52fa,7c81e0,7c6b40,7c7a3e,7c6b18,7cf481,7cf4fd,7c5304,7c0012,7c7a47,7c7bc7,7cf43a,7cf4cf,7c617c,7cf4e7,7c495b,7cf7ca,7cf7cb,7c6c9b,71bd78,7c530e,7c6b3b,89916b,7c6ba1,7c6de8,7c7c98,7c01c2,7c77f6,7c6aec,7c5309,7c6182,7c6c55,7c6d32,7c39fc,7c7ab3,7c67b5,7c7aa9,7c6b30,7c7ab9,7c7800,7c6b12,7c1c4c,7c6b2f,7c8021,7c39fb,7c4877,7c52f8,7c6ca6,7c6699,76cd05,7c595c,7c4519,7c5307,7c0002,7c6d27,7c7599,7c6782,7c6ad3,7c083c,7c24be,7c6acc,7c1475,7c2e34,7c6d94,7c2ba8,7c7a48,7c6d8f,7c6a8d,7c4dcc,899134,7c6d7b,7c6c99,7c6db6,899121,7c7701,7cf864,a4c02d,71c076,780561,7cf831,7c1505,868db5,76cd42,7c1795,ac4d43,71ba08,86d624,76cd45,ac10d8,c87ed7,868078,71ba03,7580d5,76cee7,c87ed5,86e474,781346,c820cf,c822e9,c8222f,c87f31,86e778,7c6c95,c87ee1,c87e0c,c827e3,c87e61,c87ee2,8990ec,c81861,c81ce7,c81ce9,781009" hexlist200="4BB566,8832b5,4d0106,704015,75010a,7582f5,8003b3,8a060e,885970,88530c,3c4581,7503fd,880841,8815b4,881057,880c44,8840f5,770588,a9733c,880453,880849,7503db,884202,880845,4bb188,750042,884347,885175,76d1c4,88434e,76cdb6,7503f0,4242ff,76cef7,76bcc8,750324,7502de,06a10e,750480,75046e,76cdac,750220,8a04a8,4bb154,4ba956,710063,750466,7504c4,7bd008,884207,702092,885961,750244,899000,76b869,7503ec,8a04f7,8a07c9,76cc6a,7504f7,ad9e66,880450,7504aa,06a102,78084a,86e7a2,8a08a1,76e4c6,76e4cc,781153,76e4c4,76e4cb,76e4ce,76e4cd,76b444,7817e1,86ef06,68220d,06a03d,7813ba,8a0227,8a04f5,7bd014,8a03b1,7c531c,7805d0,7802d4,780d13,780b87,780399,8a0551,8a07b7,888191,8a02d5,75020f,8a08a0,8a02e9,8a0813,88815a,886289,750107,8a0261,88811c,7503f3,76cdae,76cda4,888169,8a03c1,781418,8881c6,888185,8881a5,76cc64,88816b,7503b1,78137e,8a0661,8881ac,888096,888171,8a0605,88805e,888129,8880ac,750500,8a04ce,8880b4,88817c,88808a,8881be,8a0603,8a02a3,70e048,8880df,7bd023,76cd09,8881c3,8013d6,88817a,8880a5,8a0451,8880ed,888137,8a05d5,88816e,8881ba,8a0893,8a088a,88819a,750502,888179,76cc65,888173,888167,888095,8a0643,88815f,888133,88816c,888174,88813e,8880f3,76bd47,42486a,8a044e,899013,75020a,888051,8990da,7812bd,780a28,7504e8,8a0827,8a040a,8991b5,7504b5,8a0831,8991bd,8a0823,899020,780a5b,8a0515,78010e,750475,750485,8a08a7,781c50,78027d,78018f,7805fa,89647d,7819de,780e7f,8a03f3,750266,780ad5,4ba9f6,8a0455,781145,7805ec" function curltime() { request="$1" curl -sS --compressed "$request" \ -w 'code: %{response_code}\tresolve: %{time_namelookup}\tfirst_byte: %{time_starttransfer}\ttotal: %{time_total}\n' \ -o /dev/null } function get() { request="$1" curl -sS --compressed "$request" } set -e queries=() queries+=("?hexlist=${hexlist200}&jv2") queries+=("?circle=33,-118,300&jv2") queries+=("?closest=33,-118,300&jv2") queries+=("?box=50,60,-5,5&jv2") queries+=("?all_with_pos&jv2") queries+=("?all&jv2") queries+=("?all&filter_callsign_exact=DLH7Le&jv2") queries+=("?all&filter_callsign_prefix=DLH&jv2") queries+=("?all_with_pos&filter_mil&jv2") queries+=("?all_with_pos&filter_ladd&jv2") queries+=("?all_with_pos&filter_squawk=1000&jv2") queries+=("/re-api/") queries+=("?box=50,60,-5,5") #queries=() queries+=("?all") queries+=("?all_with_pos") queries+=("?all&filter_with_pos") queries+=("?hexlist=4853D4,498421") queries+=("?find_callsign=N126CM,CEF05N") queries+=("?find_reg=TC-RBJ,PH-HXK") queries+=("?find_type=a320,b788") queries+=("?box=10,80,-5,20&filter_type=a320,b788") queries+=("?box=10,80,-5,20") queries+=("?box=10,80,-5,20&find_hex=4BB267") queries+=("?box=10,80,-5,20&above_alt_baro=30000&below_alt_baro=35000") testhost() { host=$1 for query in "${queries[@]}"; do request="${host}${query}" echo -e -n "checking: ${query:0:30}\t" get "$request" | jq | tail -n4 | grep -e resultCount -e total -e ptime | tr '\n' '\t' curltime "$request" done } hosts=(\ "http://127.0.0.1:30152/" \ ) for host in "${hosts[@]}"; do echo "testing host: $host" testhost "$host" | column -t echo done readsb-3.16/comm_b.c000066400000000000000000000607401505057307600143220ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // comm_b.c: Comm-B message decoding // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2017 FlightAware, LLC // Copyright (c) 2017 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #include "readsb.h" #include "ais_charset.h" typedef int (*CommBDecoderFn)(struct modesMessage *, bool); static int decodeEmptyResponse(struct modesMessage *mm, bool store); static int decodeBDS10(struct modesMessage *mm, bool store); static int decodeBDS17(struct modesMessage *mm, bool store); static int decodeBDS20(struct modesMessage *mm, bool store); static int decodeBDS30(struct modesMessage *mm, bool store); static int decodeBDS40(struct modesMessage *mm, bool store); static int decodeBDS50(struct modesMessage *mm, bool store); static int decodeBDS60(struct modesMessage *mm, bool store); static int decodeBDS44(struct modesMessage *mm, bool store); static CommBDecoderFn comm_b_decoders[] = { &decodeEmptyResponse, &decodeBDS10, &decodeBDS20, &decodeBDS30, &decodeBDS17, &decodeBDS40, &decodeBDS50, &decodeBDS60, &decodeBDS44 }; void decodeCommB(struct modesMessage *mm) { mm->commb_format = COMMB_UNKNOWN; // If DR or UM are set, this message is _probably_ noise // as nothing really seems to use the multisite broadcast stuff? // Also skip anything that had errors corrected if (mm->DR != 0 || mm->UM != 0 || mm->correctedbits > 0) { return; } // This is a bit hairy as we don't know what the requested register was int bestScore = 0; CommBDecoderFn bestDecoder = NULL; int ambiguous = 0; for (unsigned i = 0; i < (sizeof (comm_b_decoders) / sizeof (comm_b_decoders[0])); ++i) { int score = comm_b_decoders[i](mm, false); if (score > bestScore) { bestScore = score; bestDecoder = comm_b_decoders[i]; ambiguous = 0; } else if (score == bestScore) { ambiguous = 1; } } if (bestDecoder) { if (ambiguous) { mm->commb_format = COMMB_AMBIGUOUS; } else { // decode it bestDecoder(mm, true); } } } static int decodeEmptyResponse(struct modesMessage *mm, bool store) { for (unsigned i = 0; i < 7; ++i) { if (mm->MB[i] != 0) { return 0; } } if (store) { mm->commb_format = COMMB_EMPTY_RESPONSE; } return 56; } // BDS1,0 Datalink capabilities static int decodeBDS10(struct modesMessage *mm, bool store) { unsigned char *msg = mm->MB; // BDS identifier if (msg[0] != 0x10) { return 0; } // Reserved bits if (getbits(msg, 10, 14) != 0) { return 0; } // Looks plausible. if (store) { mm->commb_format = COMMB_DATALINK_CAPS; } return 56; } // BDS1,7 Common usage GICB capability report static int decodeBDS17(struct modesMessage *mm, bool store) { unsigned char *msg = mm->MB; // reserved bits if (getbits(msg, 25, 56) != 0) { return 0; } int score = 0; if (getbit(msg, 7)) { score += 1; // 2,0 aircraft identification } else { // BDS2,0 is on almost everything score -= 2; } // unlikely bits if (getbit(msg, 10)) { // 4,1 next waypoint identifier score -= 2; } if (getbit(msg, 11)) { // 4,2 next waypoint position score -= 2; } if (getbit(msg, 12)) { // 4,3 next waypoint information score -= 2; } if (getbit(msg, 13)) { // 4,4 meterological routine report score -= 2; } if (getbit(msg, 14)) { // 4,4 meterological hazard report score -= 2; } if (getbit(msg, 20)) { // 5,4 waypoint 1 score -= 2; } if (getbit(msg, 21)) { // 5,5 waypoint 2 score -= 2; } if (getbit(msg, 22)) { // 5,6 waypoint 3 score -= 2; } if (getbit(msg, 1) && getbit(msg, 2) && getbit(msg, 3) && getbit(msg, 4) && getbit(msg, 5)) { // looks like ES capable score += 5; if (getbit(msg, 6)) { // ES EDI score += 1; } } else if (!getbit(msg, 1) && !getbit(msg, 2) && !getbit(msg, 3) && !getbit(msg, 4) && !getbit(msg, 5) && !getbit(msg, 6)) { // not ES capable score += 1; } else { // partial ES support, unlikely score -= 12; } if (getbit(msg, 16) && getbit(msg, 24)) { // track/turn, heading/speed score += 2; if (getbit(msg, 9)) { // vertical intent score += 1; } } else if (!getbit(msg, 16) && !getbit(msg, 24) && !getbit(msg, 9)) { // neither score += 1; } else { // unlikely score -= 6; } if (store) { mm->commb_format = COMMB_GICB_CAPS; } return score; } // BDS2,0 Aircraft identification static int decodeBDS20(struct modesMessage *mm, bool store) { char callsign[sizeof(mm->callsign)]; unsigned char *msg = mm->MB; // BDS identifier if (msg[0] != 0x20) { return 0; } callsign[0] = ais_charset[getbits(msg, 9, 14)]; callsign[1] = ais_charset[getbits(msg, 15, 20)]; callsign[2] = ais_charset[getbits(msg, 21, 26)]; callsign[3] = ais_charset[getbits(msg, 27, 32)]; callsign[4] = ais_charset[getbits(msg, 33, 38)]; callsign[5] = ais_charset[getbits(msg, 39, 44)]; callsign[6] = ais_charset[getbits(msg, 45, 50)]; callsign[7] = ais_charset[getbits(msg, 51, 56)]; callsign[8] = 0; // score based on number of valid characters int score = 8; int valid = 1; for (int i = 0; i < 8; ++i) { if ( (callsign[i] >= 'A' && callsign[i] <= 'Z') // -./0123456789 || (callsign[i] >= '-' && callsign[i] <= '9') || callsign[i] == ' ' || callsign[i] == '@' ) { // valid chars score += 6; } else { // Invalid if (Modes.debug_callsign) { fprintf(stderr, "%06x %8s (len: %d)\n", mm->addr, callsign, (int) strlen(callsign)); } return 0; } } if (store) { mm->commb_format = COMMB_AIRCRAFT_IDENT; if (valid) { memcpy(mm->callsign, callsign, sizeof(mm->callsign)); mm->callsign_valid = 1; } } return score; } // check if the payload is a valid ACAS payload // https://mode-s.org/decode/book-the_1090mhz_riddle-junzi_sun.pdf int checkAcasRaValid(unsigned char *msg, struct modesMessage *mm, int debug) { bool ara = getbit(msg, 9); bool rat = getbit(msg, 27); bool mte = getbit(msg, 28); // not a valid RA if none of the bits are set if (!ara && !rat && !mte) return 0; if (getbits(msg, 9, 28) == 0) return 0; // these are the bits that contain the info, all zero it's not an RA if (getbit(msg, 23) && getbit(msg, 24)) return 0; // complementary bits, both set is invalid (above / below) if (getbit(msg, 25) && getbit(msg, 26)) return 0; // complementary bits, both set is invalid (left / right) if (mm->msgtype == 16) { if (getbits(msg, 29, 56) != 0) return 0; // in DF16 messages msg bits 29 to 56 are reserved return 1; } // some extra restrictions for DF != 16 below if (getbit(msg, 25) || getbit(msg, 26)) return 0; // left / right isn't used, require zero // for COMMB messages let's check if the thread indicator makes sense int tti = getbits(msg, 29, 30); // thread type indicator // 00 No identity data in threat identity data if (tti == 0) { if (getbits(msg, 31, 56) != 0) return 0; return 1; } // When the threat type indicator is 01 , MB bits 31-54 contain the 24-bit Mode S transponder address and the last two bits are set to zero. if (tti == 1) { if (getbits(msg, 55, 56) != 0) return 0; uint32_t addr = getbits(msg, 31, 54); if (icaoFilterTest(addr)) return 1; return debug; // zero unless debug } // 10 Threat identity data contains altitude, range, and bearing if (tti == 2) { if (mm->metype == 28)// allow for DF17 return 1; // hard to tell if used and separate from garbage, don't mark valid for the moment return debug; // zero unless debug } // 11 Not assigned if (tti == 3) { return 0; } return 0; } // BDS3,0 ACAS RA static int decodeBDS30(struct modesMessage *mm, bool store) { unsigned char *msg = mm->MB; // BDS identifier if (msg[0] != 0x30) { return 0; } if (store) { mm->commb_format = COMMB_ACAS_RA; mm->acas_ra_valid = 1; } // just accept it. return 56; } // BDS4,0 Selected vertical intention static int decodeBDS40(struct modesMessage *mm, bool store) { unsigned char *msg = mm->MB; unsigned mcp_valid = getbit(msg, 1); unsigned mcp_raw = getbits(msg, 2, 13); unsigned fms_valid = getbit(msg, 14); unsigned fms_raw = getbits(msg, 15, 26); unsigned baro_valid = getbit(msg, 27); unsigned baro_raw = getbits(msg, 28, 39); unsigned reserved_1 = getbits(msg, 40, 47); unsigned mode_valid = getbit(msg, 48); unsigned mode_raw = getbits(msg, 49, 51); unsigned reserved_2 = getbits(msg, 52, 53); unsigned source_valid = getbit(msg, 54); unsigned source_raw = getbits(msg, 55, 56); if (!mcp_valid && !fms_valid && !baro_valid && !mode_valid && !source_valid) { return 0; } int score = 0; unsigned mcp_alt = 0; if (mcp_valid && mcp_raw != 0) { mcp_alt = mcp_raw * 16; if (mcp_alt >= 1000 && mcp_alt <= 50000) { score += 13; } else { // unlikely altitude return 0; } } else if (!mcp_valid && mcp_raw == 0) { score += 1; } else { return 0; } unsigned fms_alt = 0; if (fms_valid && fms_raw != 0) { fms_alt = fms_raw * 16; if (fms_alt >= 1000 && fms_alt <= 50000) { score += 13; } else { // unlikely altitude return 0; } } else if (!fms_valid && fms_raw == 0) { score += 1; } else { return 0; } float baro_setting = 0; if (baro_valid && baro_raw != 0) { baro_setting = 800 + baro_raw * 0.1; if (baro_setting >= 900 && baro_setting <= 1100) { score += 13; } else { // unlikely pressure setting return 0; } } else if (!baro_valid && baro_raw == 0) { score += 1; } else { return 0; } if (reserved_1 != 0) { return 0; } if (mode_valid) { score += 4; } else if (!mode_valid && mode_raw == 0) { score += 1; } else { return 0; } if (reserved_2 != 0) { return 0; } if (source_valid) { score += 3; } else if (!source_valid && source_raw == 0) { score += 1; } else { return 0; } // small penalty for inconsistent data if (mcp_valid && fms_valid && mcp_alt != fms_alt) { score -= 4; } if (mcp_valid) { unsigned remainder = mcp_alt % 500; if (!(remainder < 16 || remainder > 484)) { // mcp altitude is not a multiple of 500 score -= 4; } } if (fms_valid) { unsigned remainder = fms_alt % 500; if (!(remainder < 16 || remainder > 484)) { // fms altitude is not a multiple of 500 score -= 4; } } if (store) { mm->commb_format = COMMB_VERTICAL_INTENT; if (mcp_valid) { mm->nav.mcp_altitude_valid = 1; mm->nav.mcp_altitude = mcp_alt; } if (fms_valid) { mm->nav.fms_altitude_valid = 1; mm->nav.fms_altitude = fms_alt; } if (baro_valid) { mm->nav.qnh_valid = 1; mm->nav.qnh = baro_setting; } if (mode_valid) { mm->nav.modes_valid = 1; mm->nav.modes = ((mode_raw & 4) ? NAV_MODE_VNAV : 0) | ((mode_raw & 2) ? NAV_MODE_ALT_HOLD : 0) | ((mode_raw & 1) ? NAV_MODE_APPROACH : 0); } if (source_valid) { switch (source_raw) { case 0: mm->nav.altitude_source = NAV_ALT_UNKNOWN; break; case 1: mm->nav.altitude_source = NAV_ALT_AIRCRAFT; break; case 2: mm->nav.altitude_source = NAV_ALT_MCP; break; case 3: mm->nav.altitude_source = NAV_ALT_FMS; break; default: mm->nav.altitude_source = NAV_ALT_INVALID; break; } } else { mm->nav.altitude_source = NAV_ALT_INVALID; } } return score; } // BDS5,0 Track and turn report static int decodeBDS50(struct modesMessage *mm, bool store) { unsigned char *msg = mm->MB; unsigned roll_valid = getbit(msg, 1); unsigned roll_sign = getbit(msg, 2); unsigned roll_raw = getbits(msg, 3, 11); unsigned track_valid = getbit(msg, 12); unsigned track_sign = getbit(msg, 13); unsigned track_raw = getbits(msg, 14, 23); unsigned gs_valid = getbit(msg, 24); unsigned gs_raw = getbits(msg, 25, 34); unsigned track_rate_valid = getbit(msg, 35); unsigned track_rate_sign = getbit(msg, 36); unsigned track_rate_raw = getbits(msg, 37, 45); unsigned tas_valid = getbit(msg, 46); unsigned tas_raw = getbits(msg, 47, 56); if (!roll_valid || !track_valid || !gs_valid || !tas_valid) { return 0; } int score = 0; float roll = 0; if (roll_valid) { roll = roll_raw * 45.0 / 256.0; if (roll_sign) { roll -= 90.0; } if (roll >= -40 && roll < 40) { score += 11; } else { return 0; } } else if (!roll_valid && roll_raw == 0 && !roll_sign) { score += 1; } else { return 0; } float track = 0; if (track_valid) { score += 12; track = track_raw * 90.0 / 512.0; if (track_sign) { track += 180.0; } } else if (!track_valid && track_raw == 0 && !track_sign) { score += 1; } else { return 0; } unsigned gs = 0; if (gs_valid && gs_raw != 0) { gs = gs_raw * 2; if (gs >= 50 && gs <= 700) { score += 11; } else { return 0; } } else if (!gs_valid && gs_raw == 0) { score += 1; } else { return 0; } float track_rate = 0; if (track_rate_valid) { track_rate = track_rate_raw * 8.0 / 256.0; if (track_rate_sign) { track_rate -= 16; } if (track_rate >= -10.0 && track_rate <= 10.0) { score += 11; } else { return 0; } } else if (!track_rate_valid && track_rate_raw == 0 && !track_rate_sign) { score += 1; } else { return 0; } unsigned tas = 0; if (tas_valid && tas_raw != 0) { tas = tas_raw * 2; if (tas >= 50 && tas <= 700) { score += 11; } else { return 0; } } else if (!tas_valid && tas_raw == 0) { score += 1; } else { return 0; } // small penalty for inconsistent data if (gs_valid && tas_valid) { int delta = abs((int)gs_valid - (int)tas_valid); if (delta > 150) { score -= 6; } } // compute the theoretical turn rate and compare to track angle rate if (roll_valid && tas_valid && tas > 0 && track_rate_valid) { double turn_rate = 68625 * tan(roll * M_PI / 180.0) / (tas * 20 * M_PI); double delta = fabs(turn_rate - track_rate); if (delta > 2.0) { score -= 6; } } if (store) { mm->commb_format = COMMB_TRACK_TURN; if (roll_valid) { mm->roll_valid = 1; mm->roll = roll; } if (track_valid) { mm->heading_valid = 1; mm->heading = track; mm->heading_type = HEADING_GROUND_TRACK; } if (gs_valid) { mm->gs_valid = 1; mm->gs.v0 = mm->gs.v2 = mm->gs.selected = gs; } if (track_rate_valid) { mm->track_rate_valid = 1; mm->track_rate = track_rate; } if (tas_valid) { mm->tas_valid = 1; mm->tas = tas; } } return score; } // BDS6,0 Heading and speed report static int decodeBDS60(struct modesMessage *mm, bool store) { unsigned char *msg = mm->MB; unsigned heading_valid = getbit(msg, 1); unsigned heading_sign = getbit(msg, 2); unsigned heading_raw = getbits(msg, 3, 12); unsigned ias_valid = getbit(msg, 13); unsigned ias_raw = getbits(msg, 14, 23); unsigned mach_valid = getbit(msg, 24); unsigned mach_raw = getbits(msg, 25, 34); unsigned baro_rate_valid = getbit(msg, 35); unsigned baro_rate_sign = getbit(msg, 36); unsigned baro_rate_raw = getbits(msg, 37, 45); unsigned inertial_rate_valid = getbit(msg, 46); unsigned inertial_rate_sign = getbit(msg, 47); unsigned inertial_rate_raw = getbits(msg, 48, 56); if (!heading_valid || !ias_valid || !mach_valid || (!baro_rate_valid && !inertial_rate_valid)) { return 0; } int score = 0; float heading = 0; if (heading_valid) { heading = heading_raw * 90.0 / 512.0; if (heading_sign) { heading += 180.0; } score += 12; } else if (!heading_valid && heading_raw == 0 && !heading_sign) { score += 1; } else { return 0; } unsigned ias = 0; if (ias_valid && ias_raw != 0) { ias = ias_raw; if (ias >= 50 && ias <= 700) { score += 11; } else { return 0; } } else if (!ias_valid && ias_raw == 0) { score += 1; } else { return 0; } float mach = 0; if (mach_valid && mach_raw != 0) { mach = mach_raw * 2.048 / 512; if (mach >= 0.1 && mach <= 0.9) { score += 11; } else { return 0; } } else if (!mach_valid && mach_raw == 0) { score += 1; } else { return 0; } int baro_rate = 0; if (baro_rate_valid) { baro_rate = baro_rate_raw * 32; if (baro_rate_sign) { baro_rate -= 16384; } if (baro_rate >= -6000 && baro_rate <= 6000) { score += 11; } else { return 0; } } else if (!baro_rate_valid && baro_rate_raw == 0) { score += 1; } else { return 0; } int inertial_rate = 0; if (inertial_rate_valid) { inertial_rate = inertial_rate_raw * 32; if (inertial_rate_sign) { inertial_rate -= 16384; } if (inertial_rate >= -6000 && inertial_rate <= 6000) { score += 11; } else { return 0; } } else if (!inertial_rate_valid && inertial_rate_raw == 0) { score += 1; } else { return 0; } // small penalty for inconsistent data // Should check IAS vs Mach at given altitude, but the maths is a little involved if (baro_rate_valid && inertial_rate_valid) { int delta = abs(baro_rate - inertial_rate); if (delta > 2000) { score -= 12; } } if (store) { mm->commb_format = COMMB_HEADING_SPEED; if (heading_valid) { mm->heading_valid = 1; mm->heading = heading; mm->heading_type = HEADING_MAGNETIC; } if (ias_valid) { mm->ias_valid = 1; mm->ias = ias; } if (mach_valid) { mm->mach_valid = 1; mm->mach = mach; } if (baro_rate_valid) { mm->baro_rate_valid = 1; mm->baro_rate = baro_rate; } if (inertial_rate_valid) { // INS-derived data is treated as a "geometric rate" / "geometric altitude" // elsewhere, so do the same here. mm->geom_rate_valid = 1; mm->geom_rate = inertial_rate; } } return score; } // BDS 4,4 Meteorological routine air report static int decodeBDS44(struct modesMessage *mm, bool store) { unsigned char *msg = mm->MB; unsigned source = getbits(msg, 1, 4); unsigned wind_valid = getbit(msg, 5); unsigned wind_speed_raw = getbits(msg, 6, 14); unsigned wind_direction_raw = getbits(msg, 15, 23); unsigned temperature_sign = getbit(msg, 24); unsigned static_air_temperature_raw = getbits(msg, 25, 34); unsigned pressure_valid = getbit(msg, 35); unsigned static_pressure_raw = getbits(msg, 36, 46); unsigned turbulence_valid = getbit(msg, 47); unsigned turbulence_raw = getbits(msg, 48, 49); unsigned humidity_valid = getbit(msg, 50); unsigned humidity_raw = getbits(msg, 51, 56); /* if (!wind_valid || !temperature_valid || !pressure_valid || !turbulence_valid && !humidity_valid){ return 0; } */ int met_source = source; int score = 0; int wind_speed = 0; float wind_direction = 0; float temperature = 0; int static_pressure = 0; int turbulence = 0; float humidity = 0; if (met_source >= 0 && met_source <= 6) { score += 4; } else { return 0; } if (wind_valid){ wind_speed = (int)wind_speed_raw; if (wind_speed <= 511 && wind_speed >= 0){ score += 9; } else { return 0; } wind_direction = wind_direction_raw * (180 / 256); if (wind_direction >= 0 && wind_direction <= 360){ score += 9; } else { return 0; } } else if (wind_speed == 0) { score += 2; } if (temperature_sign){ temperature = (static_air_temperature_raw - pow(2, 10)) * 0.25; } else { temperature = static_air_temperature_raw * 0.25; } if (temperature >= -128 && temperature <= 128){ score += 10; } else { return 0; } if (pressure_valid){ static_pressure = (int)static_pressure_raw; if (static_pressure >= 0 && static_pressure <= 2048){ score += 11; return 0; } else { } } else if (static_pressure == 0) { score += 1; } if (turbulence_valid){ turbulence = (int)turbulence_raw; if (turbulence >= 0 && turbulence <= 3) { score += 2; } else { return 0; } } else if (turbulence == 0) { score += 1; } if (humidity_valid) { humidity = humidity_raw * (100.0f / 64); if (humidity >= 0 && humidity <= 100){ score += 6; } else { return 0; } } else if (humidity == 0) { score += 1; } if (store) { mm->commb_format = COMMB_METEOROLOGICAL_ROUTINE; mm->met_source_valid = 1; mm->met_source = met_source; if (wind_valid) { mm->wind_valid = 1; mm->wind_speed = wind_speed; mm->wind_direction = wind_direction; } mm->oat_valid = 1; mm->oat = temperature; if (pressure_valid) { mm->static_pressure_valid = 1; mm->static_pressure = static_pressure; } if (turbulence_valid) { mm->turbulence_valid = 1; mm->turbulence = turbulence; } if (humidity_valid) { mm->humidity_valid = 1; mm->humidity = humidity; } } return score; } readsb-3.16/comm_b.h000066400000000000000000000021401505057307600143150ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // comm_b.h: Comm-B message decoding (header) // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2017 FlightAware, LLC // Copyright (c) 2017 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #ifndef COMM_B_H #define COMM_B_H void decodeCommB (struct modesMessage *mm); int checkAcasRaValid(unsigned char *MV, struct modesMessage *mm, int debug); #endif readsb-3.16/compat/000077500000000000000000000000001505057307600141765ustar00rootroot00000000000000readsb-3.16/compat/apple/000077500000000000000000000000001505057307600152775ustar00rootroot00000000000000readsb-3.16/compat/apple/clock_compat.h000066400000000000000000000043101505057307600201040ustar00rootroot00000000000000#ifndef CLOCK_COMPAT_H #define CLOCK_COMPAT_H #ifdef __APPLE__ #include #include #include #include #include /* Clock definitions */ #ifndef CLOCK_MONOTONIC #define CLOCK_MONOTONIC 1 #endif #ifndef TIMER_ABSTIME #define TIMER_ABSTIME 1 #endif /* Mach timebase info for conversion */ static mach_timebase_info_data_t __clock_timebase_info; static pthread_once_t __clock_timebase_once = PTHREAD_ONCE_INIT; static void __clock_timebase_init(void) { mach_timebase_info(&__clock_timebase_info); } /* Clock compatibility functions */ static inline uint64_t clock_gettime_nsec(void) { pthread_once(&__clock_timebase_once, __clock_timebase_init); return (mach_absolute_time() * __clock_timebase_info.numer) / __clock_timebase_info.denom; } static inline int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *request, struct timespec *remain) { (void)clock_id; // Only CLOCK_MONOTONIC supported (void)remain; // remain not supported on macOS pthread_once(&__clock_timebase_once, __clock_timebase_init); if (flags & TIMER_ABSTIME) { // For absolute time, calculate the relative delay struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); uint64_t now_ns = (uint64_t)now.tv_sec * 1000000000ULL + now.tv_nsec; uint64_t target_ns = (uint64_t)request->tv_sec * 1000000000ULL + request->tv_nsec; if (now_ns >= target_ns) { return 0; // Target time already passed } uint64_t delay_ns = target_ns - now_ns; struct timespec ts = { .tv_sec = delay_ns / 1000000000ULL, .tv_nsec = delay_ns % 1000000000ULL }; while (nanosleep(&ts, &ts) == -1) { if (errno != EINTR) { return errno; } } } else { // For relative time, just use nanosleep directly struct timespec ts = *request; while (nanosleep(&ts, &ts) == -1) { if (errno != EINTR) { return errno; } } } return 0; } #endif /* __APPLE__ */ #endif /* CLOCK_COMPAT_H */ readsb-3.16/compat/apple/compat.h000066400000000000000000000011451505057307600167340ustar00rootroot00000000000000#ifndef COMPAT_UTIL_H #define COMPAT_UTIL_H /* * Platform-specific bits */ #if defined(__APPLE__) /* * Mach endian conversion */ # include # define bswap_16 OSSwapInt16 # define bswap_32 OSSwapInt32 # define bswap_64 OSSwapInt64 # include # define le16toh(x) OSSwapLittleToHostInt16(x) # define le32toh(x) OSSwapLittleToHostInt32(x) #else // other platforms # include #endif #ifdef MISSING_NANOSLEEP #include "clock_nanosleep/clock_nanosleep.h" #endif #ifdef MISSING_GETTIME #include "clock_gettime/clock_gettime.h" #endif #endif //COMPAT_UTIL_H readsb-3.16/compat/apple/cpu_compat.h000066400000000000000000000024161505057307600176050ustar00rootroot00000000000000#ifndef CPU_COMPAT_H #define CPU_COMPAT_H #ifdef __APPLE__ #include #include #include #include /* CPU set definitions */ typedef uint64_t cpu_set_t; #define CPU_SETSIZE 1024 #define CPU_COUNT(set) __builtin_popcountll(*(set)) #define CPU_ZERO(set) (*(set) = 0) #define CPU_SET(cpu, set) (*(set) |= (1ULL << (cpu))) #define CPU_CLR(cpu, set) (*(set) &= ~(1ULL << (cpu))) #define CPU_ISSET(cpu, set) ((*(set) & (1ULL << (cpu))) != 0) /* CPU affinity functions */ static inline int sched_getaffinity(pid_t pid, size_t cpu_size, cpu_set_t *mask) { (void)pid; // Suppress unused parameter warning (void)cpu_size; // Suppress unused parameter warning int32_t core_count = 0; size_t len = sizeof(core_count); if (sysctlbyname("hw.logicalcpu", &core_count, &len, NULL, 0) == 0) { CPU_ZERO(mask); for (int i = 0; i < core_count; i++) { CPU_SET(i, mask); } return 0; } return -1; } static inline int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask) { /* On macOS, we can't actually set CPU affinity, so we just pretend it worked */ (void)pid; (void)cpusetsize; (void)mask; return 0; } #endif /* __APPLE__ */ #endif /* CPU_COMPAT_H */ readsb-3.16/compat/apple/epoll_shim.c000066400000000000000000000036151505057307600176030ustar00rootroot00000000000000#ifdef __APPLE__ #include "epoll_shim.h" #include #include int epoll_create1(int flags) { (void)flags; // Suppress unused parameter warning return kqueue(); } int epoll_create(int size) { (void)size; // Suppress unused parameter warning return epoll_create1(0); } int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) { struct kevent ke; if (op == EPOLL_CTL_ADD || op == EPOLL_CTL_MOD) { uint16_t flags = EV_ADD; if (event->events & EPOLLIN) EV_SET(&ke, fd, EVFILT_READ, flags, 0, 0, event->data.ptr); if (event->events & EPOLLOUT) EV_SET(&ke, fd, EVFILT_WRITE, flags, 0, 0, event->data.ptr); return kevent(epfd, &ke, 1, NULL, 0, NULL); } else if (op == EPOLL_CTL_DEL) { EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); kevent(epfd, &ke, 1, NULL, 0, NULL); EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); return kevent(epfd, &ke, 1, NULL, 0, NULL); } return -1; } int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) { struct kevent kevents[maxevents]; struct timespec ts; if (timeout >= 0) { ts.tv_sec = timeout / 1000; ts.tv_nsec = (timeout % 1000) * 1000000; } int nev = kevent(epfd, NULL, 0, kevents, maxevents, timeout >= 0 ? &ts : NULL); for (int i = 0; i < nev; i++) { events[i].events = 0; if (kevents[i].filter == EVFILT_READ) events[i].events |= EPOLLIN; if (kevents[i].filter == EVFILT_WRITE) events[i].events |= EPOLLOUT; if (kevents[i].flags & EV_ERROR) events[i].events |= EPOLLERR; if (kevents[i].flags & EV_EOF) events[i].events |= EPOLLHUP; events[i].data.ptr = kevents[i].udata; } return nev; } #endif /* __APPLE__ */ readsb-3.16/compat/apple/epoll_shim.h000066400000000000000000000017041505057307600176050ustar00rootroot00000000000000#ifndef EPOLL_SHIM_H #define EPOLL_SHIM_H #ifdef __APPLE__ #include #include #include #include #include "cpu_compat.h" /* epoll definitions */ #define EPOLLIN 0x001 #define EPOLLOUT 0x004 #define EPOLLERR 0x008 #define EPOLLHUP 0x010 #define EPOLLPRI 0x002 #define EPOLLRDHUP 0x020 #define EPOLL_CTL_ADD 1 #define EPOLL_CTL_DEL 2 #define EPOLL_CTL_MOD 3 typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; epoll_data_t data; }; #ifdef __cplusplus extern "C" { #endif /* Function declarations */ int epoll_create(int size); int epoll_create1(int flags); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); #ifdef __cplusplus } #endif #endif /* __APPLE__ */ #endif /* EPOLL_SHIM_H */ readsb-3.16/compat/apple/net_compat.h000066400000000000000000000050211505057307600175770ustar00rootroot00000000000000#ifndef NET_COMPAT_H #define NET_COMPAT_H #ifdef __APPLE__ #include #include #include #include #include #include /* Socket flags */ #ifndef SOCK_NONBLOCK #define SOCK_NONBLOCK 0x00004000 /* Match Linux's value */ #endif #ifndef SOCK_CLOEXEC #define SOCK_CLOEXEC FD_CLOEXEC #endif /* TCP socket options */ #ifndef SOL_TCP #define SOL_TCP IPPROTO_TCP #endif /* TCP keepalive options */ #ifndef TCP_KEEPIDLE #define TCP_KEEPIDLE TCP_KEEPALIVE #endif #ifndef TCP_KEEPINTVL #define TCP_KEEPINTVL 0x101 /* Time between keepalive probes */ #endif #ifndef TCP_KEEPCNT #define TCP_KEEPCNT 0x102 /* Number of keepalive probes before disconnect */ #endif /* Basic socket operations */ static inline int set_socket_nonblock(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) return -1; return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } static inline int create_socket_nonblock(int domain, int type, int protocol) { int fd = socket(domain, type & ~SOCK_NONBLOCK, protocol); if (fd < 0) return fd; if (type & SOCK_NONBLOCK) { if (set_socket_nonblock(fd) < 0) { close(fd); return -1; } } return fd; } /* accept4 compatibility */ static inline int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) { int fd = accept(sockfd, addr, addrlen); if (fd < 0) return fd; if (flags & SOCK_NONBLOCK) { if (set_socket_nonblock(fd) < 0) { close(fd); return -1; } } if (flags & SOCK_CLOEXEC) { int current = fcntl(fd, F_GETFD); if (current < 0 || fcntl(fd, F_SETFD, current | FD_CLOEXEC) < 0) { close(fd); return -1; } } return fd; } /* Socket creation override */ #ifdef socket #undef socket #endif #define socket(domain, type, protocol) create_socket_nonblock(domain, type, protocol) /* TCP keepalive compatibility */ static inline int set_tcp_keepalive(int fd, int idle, int interval, int count) { int ret = 0; /* Enable keepalive */ int yes = 1; ret |= setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)); /* On macOS, TCP_KEEPALIVE is in seconds */ ret |= setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &idle, sizeof(idle)); /* macOS doesn't support changing the interval and count directly */ (void)interval; (void)count; return ret; } #endif /* __APPLE__ */ #endif /* NET_COMPAT_H */ readsb-3.16/compat/apple/sendfile_compat.h000066400000000000000000000037361505057307600206150ustar00rootroot00000000000000#ifndef SENDFILE_COMPAT_H #define SENDFILE_COMPAT_H #ifdef __APPLE__ #include #include #include #include #include /* sendfile compatibility for macOS */ static inline ssize_t linux_sendfile(int out_fd, int in_fd, off_t *offset, size_t count) { off_t len = count; /* Use native macOS sendfile if possible */ if (sendfile(in_fd, out_fd, *offset, &len, NULL, 0) == 0) { *offset += len; return len; } /* Fall back to read/write if sendfile fails */ char buffer[8192]; size_t remaining = count; ssize_t total = 0; if (offset && lseek(in_fd, *offset, SEEK_SET) == -1) { return -1; } while (remaining > 0) { size_t to_read = (remaining < sizeof(buffer)) ? remaining : sizeof(buffer); ssize_t bytes_read = read(in_fd, buffer, to_read); if (bytes_read <= 0) { if (bytes_read == 0) break; /* EOF */ if (errno == EINTR) continue; if (total > 0) break; /* Return partial success */ return -1; } size_t to_write = bytes_read; size_t written = 0; while (written < to_write) { ssize_t ret = write(out_fd, buffer + written, to_write - written); if (ret <= 0) { if (ret == -1 && errno == EINTR) continue; if (total > 0 || written > 0) { /* Return partial success */ total += written; if (offset) *offset += total; return total; } return -1; } written += ret; } total += written; remaining -= written; } if (offset) *offset += total; return total; } #define sendfile(out_fd, in_fd, offset, count) linux_sendfile(out_fd, in_fd, offset, count) #endif /* __APPLE__ */ #endif /* SENDFILE_COMPAT_H */ readsb-3.16/compat/apple/serial_compat.h000066400000000000000000000015761505057307600203030ustar00rootroot00000000000000#ifndef SERIAL_COMPAT_H #define SERIAL_COMPAT_H #ifdef __APPLE__ #include #include #include /* Define missing baud rates for macOS */ #ifndef B460800 #define B460800 460800 #endif #ifndef B500000 #define B500000 500000 #endif #ifndef B576000 #define B576000 576000 #endif #ifndef B921600 #define B921600 921600 #endif #ifndef B1000000 #define B1000000 1000000 #endif #ifndef B1152000 #define B1152000 1152000 #endif #ifndef B1500000 #define B1500000 1500000 #endif #ifndef B2000000 #define B2000000 2000000 #endif #ifndef B2500000 #define B2500000 2500000 #endif #ifndef B3000000 #define B3000000 3000000 #endif #ifndef B3500000 #define B3500000 3500000 #endif #ifndef B4000000 #define B4000000 4000000 #endif #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif /* __APPLE__ */ #endif /* SERIAL_COMPAT_H */ readsb-3.16/compat/apple/stat_compat.h000066400000000000000000000003261505057307600177670ustar00rootroot00000000000000#ifndef STAT_COMPAT_H #define STAT_COMPAT_H #ifdef __APPLE__ #include /* macOS uses st_mtimespec instead of st_mtim */ #define st_mtim st_mtimespec #endif /* __APPLE__ */ #endif /* STAT_COMPAT_H */ readsb-3.16/compat/apple/thread_compat.h000066400000000000000000000032331505057307600202630ustar00rootroot00000000000000#ifndef THREAD_COMPAT_H #define THREAD_COMPAT_H #ifdef __APPLE__ #include #include #include #include #include "cpu_compat.h" /* Forward declarations */ static inline int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime); /* pthread_tryjoin_np implementation for macOS */ static inline int pthread_tryjoin_np(pthread_t thread, void **retval) { struct timespec ts = { 0, 0 }; // Zero timeout return pthread_timedjoin_np(thread, retval, &ts); } /* pthread_timedjoin_np implementation for macOS */ static inline int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime) { int res; struct timespec ts_start; /* Try joining immediately first */ res = pthread_kill(thread, 0); if (res != 0) { return res; } if (clock_gettime(CLOCK_REALTIME, &ts_start) != 0) { return EINVAL; } while ((res = pthread_join(thread, retval)) == EBUSY) { struct timespec ts_now; if (clock_gettime(CLOCK_REALTIME, &ts_now) != 0) { return EINVAL; } /* Check if we've exceeded the timeout */ if (ts_now.tv_sec > ts_start.tv_sec + abstime->tv_sec || (ts_now.tv_sec == ts_start.tv_sec + abstime->tv_sec && ts_now.tv_nsec >= ts_start.tv_nsec + abstime->tv_nsec)) { return ETIMEDOUT; } /* Sleep for a short time before trying again */ struct timespec ts_sleep = { 0, 1000000 }; /* 1ms */ nanosleep(&ts_sleep, NULL); } return res; } #endif /* __APPLE__ */ #endif /* THREAD_COMPAT_H */ readsb-3.16/compat/clock_gettime/000077500000000000000000000000001505057307600170075ustar00rootroot00000000000000readsb-3.16/compat/clock_gettime/LICENSE000066400000000000000000000030501505057307600200120ustar00rootroot00000000000000/* * Copyright (c), MM Weiss * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the MM Weiss nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */readsb-3.16/compat/clock_gettime/clock_gettime.c000066400000000000000000000112771505057307600217740ustar00rootroot00000000000000/* * Copyright (c), MM Weiss * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the MM Weiss nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * clock_gettime_stub.c * gcc -Wall -c clock_gettime_stub.c * posix realtime functions; MacOS user space glue */ /* @comment * other possible implementation using intel builtin rdtsc * rdtsc-workaround: http://www.mcs.anl.gov/~kazutomo/rdtsc.html * * we could get the ticks by doing this * * __asm __volatile("mov %%ebx, %%esi\n\t" * "cpuid\n\t" * "xchg %%esi, %%ebx\n\t" * "rdtsc" * : "=a" (a), * "=d" (d) * ); * we could even replace our tricky sched_yield call by assembly code to get a better accurency, * anyway the following C stub will satisfy 99% of apps using posix clock_gettime call, * moreover, the setter version (clock_settime) could be easly written using mach primitives: * http://www.opensource.apple.com/source/xnu/xnu-${VERSION}/osfmk/man/ (clock_[set|get]_time) * * hackers don't be crackers, don't you use a flush toilet? * * * @see draft: ./posix-realtime-stub/posix-realtime-stub.c * */ #include "clock_gettime.h" #include // for clock_get_time #include // for mach_timespec_t, CALENDAR_CLOCK, etc #include // for KERN_SUCCESS, kern_return_t #include // for host_get_clock_service #include // for mach_host_self #include // for clock_serv_t #include // for sched_yield #include // for EINVAL, errno #include // for getpid int clock_gettime(clockid_t clk_id, struct timespec *tp) { kern_return_t ret; clock_serv_t clk; clock_id_t clk_serv_id; mach_timespec_t tm; uint64_t start, end, delta, nano; /* task_basic_info_data_t tinfo; task_thread_times_info_data_t ttinfo; mach_msg_type_number_t tflag; */ int retval = -1; switch (clk_id) { case CLOCK_REALTIME: case CLOCK_MONOTONIC: clk_serv_id = clk_id == CLOCK_REALTIME ? CALENDAR_CLOCK : SYSTEM_CLOCK; if (KERN_SUCCESS == (ret = host_get_clock_service(mach_host_self(), clk_serv_id, &clk))) { if (KERN_SUCCESS == (ret = clock_get_time(clk, &tm))) { tp->tv_sec = tm.tv_sec; tp->tv_nsec = tm.tv_nsec; retval = 0; } } if (KERN_SUCCESS != ret) { errno = EINVAL; retval = -1; } break; case CLOCK_PROCESS_CPUTIME_ID: case CLOCK_THREAD_CPUTIME_ID: start = mach_absolute_time(); if (clk_id == CLOCK_PROCESS_CPUTIME_ID) { getpid(); } else { sched_yield(); } end = mach_absolute_time(); delta = end - start; if (0 == __clock_gettime_inf.denom) { mach_timebase_info(&__clock_gettime_inf); } nano = delta * __clock_gettime_inf.numer / __clock_gettime_inf.denom; tp->tv_sec = nano * 1e-9; tp->tv_nsec = nano - (tp->tv_sec * 1e9); retval = 0; break; default: errno = EINVAL; retval = -1; } return retval; } readsb-3.16/compat/clock_gettime/clock_gettime.h000066400000000000000000000010241505057307600217660ustar00rootroot00000000000000#ifndef CLOCK_GETTIME_H #define CLOCK_GETTIME_H #include // Apple-only, but this isn't inclued on other BSDs #ifdef _CLOCKID_T_DEFINED_ #define CLOCKID_T #endif #ifndef CLOCKID_T #define CLOCKID_T typedef enum { CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID } clockid_t; #endif // ifndef CLOCKID_T struct timespec; static mach_timebase_info_data_t __clock_gettime_inf; int clock_gettime(clockid_t clk_id, struct timespec *tp); #endif // CLOCK_GETTIME_H readsb-3.16/compat/clock_nanosleep/000077500000000000000000000000001505057307600173355ustar00rootroot00000000000000readsb-3.16/compat/clock_nanosleep/LICENSE000066400000000000000000000022221505057307600203400ustar00rootroot00000000000000/*********************************************************************** * Copyright © 2006 Rémi Denis-Courmont. * * This program is free software; you can redistribute and/or modify * * it under the terms of the GNU General Public License as published * * by the Free Software Foundation; version 2 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, you can get it from: * * http://www.gnu.org/copyleft/gpl.html * ***********************************************************************/readsb-3.16/compat/clock_nanosleep/clock_nanosleep.c000066400000000000000000000045731505057307600226510ustar00rootroot00000000000000/* * clock_nanosleep.c - clock_nanosleep() replacement */ /*********************************************************************** * Copyright © 2006 Rémi Denis-Courmont. * * This program is free software; you can redistribute and/or modify * * it under the terms of the GNU General Public License as published * * by the Free Software Foundation; version 2 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, you can get it from: * * http://www.gnu.org/copyleft/gpl.html * ***********************************************************************/ #include // for errno, EINVAL #include // for nanosleep, NULL #include "clock_nanosleep.h" #ifdef MISSING_GETTIME #include "../clock_gettime/clock_gettime.h" // for clock_gettime #endif int clock_nanosleep(clockid_t id, int flags, const struct timespec *ts, struct timespec *ots) { int ret; if (id != CLOCK_REALTIME) return EINVAL; if (flags & TIMER_ABSTIME) { struct timespec mine; if (clock_gettime(id, &mine)) return errno; if (mine.tv_sec > ts->tv_sec) return 0; // behind schedule if (mine.tv_nsec > ts->tv_nsec) { if (mine.tv_sec == ts->tv_sec) return 0; // behind schedule too mine.tv_nsec = 1000000000 + ts->tv_nsec - mine.tv_nsec; mine.tv_sec++; } else mine.tv_nsec = ts->tv_nsec - mine.tv_nsec; mine.tv_sec = ts->tv_sec - mine.tv_sec; /* With TIMER_ABSTIME, clock_nanosleep ignores */ ret = nanosleep(&mine, NULL); } else ret = nanosleep(ts, ots); return ret ? errno : 0; } readsb-3.16/compat/clock_nanosleep/clock_nanosleep.h000066400000000000000000000010241505057307600226420ustar00rootroot00000000000000#ifndef CLOCK_NANOSLEEP_H #define CLOCK_NANOSLEEP_H #ifdef _CLOCKID_T_DEFINED_ #define CLOCKID_T #endif #ifndef CLOCKID_T #define CLOCKID_T typedef enum { CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID } clockid_t; #endif // ifndef CLOCKID_T #ifndef TIMER_ABSTIME #define TIMER_ABSTIME 1 #endif // TIMER_ABSTIME struct timespec; int clock_nanosleep (clockid_t id, int flags, const struct timespec *ts, struct timespec *ots); #endif //CLOCK_NANOSLEEP_H readsb-3.16/compat/compat.h000066400000000000000000000016231505057307600156340ustar00rootroot00000000000000#ifndef COMPAT_UTIL_H #define COMPAT_UTIL_H /* * Platform-specific bits */ #if defined(__APPLE__) #define NO_EVENT_FD /* * Mach endian conversion */ # include # define bswap_16 OSSwapInt16 # define bswap_32 OSSwapInt32 # define bswap_64 OSSwapInt64 # include # define le16toh(x) OSSwapLittleToHostInt16(x) # define le32toh(x) OSSwapLittleToHostInt32(x) #include "apple/clock_compat.h" #include "apple/compat.h" #include "apple/cpu_compat.h" #include "apple/epoll_shim.h" #include "apple/net_compat.h" #include "apple/sendfile_compat.h" #include "apple/serial_compat.h" #include "apple/stat_compat.h" #include "apple/thread_compat.h" #else // other platforms # include #endif #ifdef MISSING_NANOSLEEP #include "clock_nanosleep/clock_nanosleep.h" #endif #ifdef MISSING_GETTIME #include "clock_gettime/clock_gettime.h" #endif #endif //COMPAT_UTIL_H readsb-3.16/convert.c000066400000000000000000000317051505057307600145450ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // convert.c: support for various IQ -> magnitude conversions // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #include "readsb.h" struct converter_state { float dc_a; float dc_b; float z1_I; float z1_Q; }; static uint16_t *uc8_lookup; static bool init_uc8_lookup() { if (uc8_lookup) return true; uc8_lookup = cmalloc(sizeof (uint16_t) * 256 * 256); if (!uc8_lookup) { fprintf(stderr, "can't allocate UC8 conversion lookup table\n"); return false; } for (int i = 0; i <= 255; i++) { for (int q = 0; q <= 255; q++) { float fI, fQ, magsq; fI = (i - 127.5) / 127.5; fQ = (q - 127.5) / 127.5; magsq = fI * fI + fQ * fQ; //fprintf(stderr, "%d %d %.2f\n", i, q, magsq); if (magsq > 1) magsq = 1; float mag = sqrtf(magsq); uc8_lookup[le16toh((i * 256) + q)] = (uint16_t) (mag * 65535.0f + 0.5f); } } return true; } static void convert_uc8_nodc(void *iq_data, uint16_t *mag_data, unsigned nsamples, struct converter_state *state, double *out_mean_level, double *out_mean_power) { uint16_t *in = iq_data; unsigned i; uint64_t sum_level = 0; uint64_t sum_power = 0; uint16_t mag; MODES_NOTUSED(state); // Increases readability but no optimization #define DO_ONE_SAMPLE \ do { \ mag = uc8_lookup[*in++]; \ *mag_data++ = mag; \ sum_level += mag; \ sum_power += (uint32_t)mag * (uint32_t)mag; \ } while(0) // unroll this a bit for (i = 0; i < (nsamples / 4); ++i) { DO_ONE_SAMPLE; DO_ONE_SAMPLE; DO_ONE_SAMPLE; DO_ONE_SAMPLE; } for (i = 0; i < (nsamples % 4); ++i) { DO_ONE_SAMPLE; } #undef DO_ONE_SAMPLE if (out_mean_level) { *out_mean_level = sum_level / 65536.0 / nsamples; } if (out_mean_power) { *out_mean_power = sum_power / 65535.0 / 65535.0 / nsamples; } } static void convert_uc8_generic(void *iq_data, uint16_t *mag_data, unsigned nsamples, struct converter_state *state, double *out_mean_level, double *out_mean_power) { uint8_t *in = iq_data; float z1_I = state->z1_I; float z1_Q = state->z1_Q; const float dc_a = state->dc_a; const float dc_b = state->dc_b; unsigned i; uint8_t I, Q; float fI, fQ, magsq; float sum_level = 0, sum_power = 0; for (i = 0; i < nsamples; ++i) { I = *in++; Q = *in++; fI = (I - 127.5f) / 127.5f; fQ = (Q - 127.5f) / 127.5f; // DC block z1_I = fI * dc_a + z1_I * dc_b; z1_Q = fQ * dc_a + z1_Q * dc_b; fI -= z1_I; fQ -= z1_Q; magsq = fI * fI + fQ * fQ; if (magsq > 1) magsq = 1; float mag = sqrtf(magsq); sum_power += magsq; sum_level += mag; *mag_data++ = (uint16_t) (mag * 65535.0f + 0.5f); } state->z1_I = z1_I; state->z1_Q = z1_Q; if (out_mean_level) { *out_mean_level = sum_level / nsamples; } if (out_mean_power) { *out_mean_power = sum_power / nsamples; } } static void convert_sc16_generic(void *iq_data, uint16_t *mag_data, unsigned nsamples, struct converter_state *state, double *out_mean_level, double *out_mean_power) { uint16_t *in = iq_data; float z1_I = state->z1_I; float z1_Q = state->z1_Q; const float dc_a = state->dc_a; const float dc_b = state->dc_b; unsigned i; int16_t I, Q; float fI, fQ, magsq; float sum_level = 0, sum_power = 0; for (i = 0; i < nsamples; ++i) { I = (int16_t) le16toh(*in++); Q = (int16_t) le16toh(*in++); fI = I / 32768.0f; fQ = Q / 32768.0f; // DC block z1_I = fI * dc_a + z1_I * dc_b; z1_Q = fQ * dc_a + z1_Q * dc_b; fI -= z1_I; fQ -= z1_Q; magsq = fI * fI + fQ * fQ; if (magsq > 1) magsq = 1; float mag = sqrtf(magsq); sum_power += magsq; sum_level += mag; *mag_data++ = (uint16_t) (mag * 65535.0f + 0.5f); } state->z1_I = z1_I; state->z1_Q = z1_Q; if (out_mean_level) { *out_mean_level = sum_level / nsamples; } if (out_mean_power) { *out_mean_power = sum_power / nsamples; } } static void convert_sc16_nodc(void *iq_data, uint16_t *mag_data, unsigned nsamples, struct converter_state *state, double *out_mean_level, double *out_mean_power) { MODES_NOTUSED(state); uint16_t *in = iq_data; unsigned i; int16_t I, Q; float fI, fQ, magsq; float sum_level = 0, sum_power = 0; for (i = 0; i < nsamples; ++i) { I = (int16_t) le16toh(*in++); Q = (int16_t) le16toh(*in++); fI = I / 32768.0f; fQ = Q / 32768.0f; magsq = fI * fI + fQ * fQ; if (magsq > 1) magsq = 1; float mag = sqrtf(magsq); sum_power += magsq; sum_level += mag; *mag_data++ = (uint16_t) (mag * 65535.0f + 0.5f); } if (out_mean_level) { *out_mean_level = sum_level / nsamples; } if (out_mean_power) { *out_mean_power = sum_power / nsamples; } } // SC16Q11_TABLE_BITS controls the size of the lookup table // for SC16Q11 data. The size of the table is 2 * (1 << (2*BITS)) // bytes. Reducing the number of bits reduces precision but // can run substantially faster by staying in cache. // See convert_benchmark.c for some numbers. // Leaving SC16QQ_TABLE_BITS undefined will disable the table lookup and always use // the floating-point path, which may be faster on some systems #if defined(SC16Q11_TABLE_BITS) #define USE_BITS SC16Q11_TABLE_BITS #define LOSE_BITS (11 - SC16Q11_TABLE_BITS) static uint16_t *sc16q11_lookup; static bool init_sc16q11_lookup() { if (sc16q11_lookup) return true; sc16q11_lookup = cmalloc(sizeof (uint16_t) * (1 << (USE_BITS * 2))); if (!sc16q11_lookup) { fprintf(stderr, "can't allocate SC16Q11 conversion lookup table\n"); return false; } for (int i = 0; i < 2048; i += (1 << LOSE_BITS)) { for (int q = 0; q < 2048; q += (1 << LOSE_BITS)) { float fI = i / 2048.0, fQ = q / 2048.0; float magsq = fI * fI + fQ * fQ; if (magsq > 1) magsq = 1; float mag = sqrtf(magsq); unsigned index = ((i >> LOSE_BITS) << USE_BITS) | (q >> LOSE_BITS); sc16q11_lookup[index] = (uint16_t) (mag * 65535.0f + 0.5f); } } return true; } static void convert_sc16q11_table(void *iq_data, uint16_t *mag_data, unsigned nsamples, struct converter_state *state, double *out_mean_level, double *out_mean_power) { uint16_t *in = iq_data; unsigned i; uint16_t I, Q; uint64_t sum_level = 0; uint64_t sum_power = 0; uint16_t mag; MODES_NOTUSED(state); for (i = 0; i < nsamples; ++i) { I = abs((int16_t) le16toh(*in++)) & 2047; Q = abs((int16_t) le16toh(*in++)) & 2047; mag = sc16q11_lookup[((I >> LOSE_BITS) << USE_BITS) | (Q >> LOSE_BITS)]; *mag_data++ = mag; sum_level += mag; sum_power += (uint32_t) mag * (uint32_t) mag; } if (out_mean_level) { *out_mean_level = sum_level / 65536.0 / nsamples; } if (out_mean_power) { *out_mean_power = sum_power / 65535.0 / 65535.0 / nsamples; } } #else /* ! defined(SC16Q11_TABLE_BITS) */ static void convert_sc16q11_nodc(void *iq_data, uint16_t *mag_data, unsigned nsamples, struct converter_state *state, double *out_mean_level, double *out_mean_power) { MODES_NOTUSED(state); uint16_t *in = iq_data; unsigned i; int16_t I, Q; float fI, fQ, magsq; float sum_level = 0, sum_power = 0; for (i = 0; i < nsamples; ++i) { I = (int16_t) le16toh(*in++); Q = (int16_t) le16toh(*in++); fI = I / 2048.0f; fQ = Q / 2048.0f; magsq = fI * fI + fQ * fQ; if (magsq > 1) magsq = 1; float mag = sqrtf(magsq); sum_power += magsq; sum_level += mag; *mag_data++ = (uint16_t) (mag * 65535.0f + 0.5f); } if (out_mean_level) { *out_mean_level = sum_level / nsamples; } if (out_mean_power) { *out_mean_power = sum_power / nsamples; } } #endif /* defined(SC16Q11_TABLE_BITS) */ static void convert_sc16q11_generic(void *iq_data, uint16_t *mag_data, unsigned nsamples, struct converter_state *state, double *out_mean_level, double *out_mean_power) { uint16_t *in = iq_data; float z1_I = state->z1_I; float z1_Q = state->z1_Q; const float dc_a = state->dc_a; const float dc_b = state->dc_b; unsigned i; int16_t I, Q; float fI, fQ, magsq; float sum_level = 0, sum_power = 0; for (i = 0; i < nsamples; ++i) { I = (int16_t) le16toh(*in++); Q = (int16_t) le16toh(*in++); fI = I / 2048.0f; fQ = Q / 2048.0f; // DC block z1_I = fI * dc_a + z1_I * dc_b; z1_Q = fQ * dc_a + z1_Q * dc_b; fI -= z1_I; fQ -= z1_Q; magsq = fI * fI + fQ * fQ; if (magsq > 1) magsq = 1; float mag = sqrtf(magsq); sum_power += magsq; sum_level += mag; *mag_data++ = (uint16_t) (mag * 65535.0f + 0.5f); } state->z1_I = z1_I; state->z1_Q = z1_Q; if (out_mean_level) { *out_mean_level = sum_level / nsamples; } if (out_mean_power) { *out_mean_power = sum_power / nsamples; } } static struct { input_format_t format; int can_filter_dc; iq_convert_fn fn; const char *description; bool(*init)(); } converters_table[] = { // In order of preference { INPUT_UC8, 0, convert_uc8_nodc, "UC8, integer/table path", init_uc8_lookup}, { INPUT_UC8, 1, convert_uc8_generic, "UC8, float path", NULL}, { INPUT_SC16, 0, convert_sc16_nodc, "SC16, float path, no DC", NULL}, { INPUT_SC16, 1, convert_sc16_generic, "SC16, float path", NULL}, #if defined(SC16Q11_TABLE_BITS) { INPUT_SC16Q11, 0, convert_sc16q11_table, "SC16Q11, integer/table path", init_sc16q11_lookup}, #else { INPUT_SC16Q11, 0, convert_sc16q11_nodc, "SC16Q11, float path, no DC", NULL}, #endif { INPUT_SC16Q11, 1, convert_sc16q11_generic, "SC16Q11, float path", NULL}, { 0, 0, NULL, NULL, NULL} }; iq_convert_fn init_converter(input_format_t format, double sample_rate, int filter_dc, struct converter_state **out_state) { int i; for (i = 0; converters_table[i].fn; ++i) { if (converters_table[i].format != format) continue; if (filter_dc && !converters_table[i].can_filter_dc) continue; break; } if (!converters_table[i].fn) { fprintf(stderr, "no suitable converter for format=%d dc=%d\n", format, filter_dc); return NULL; } if (converters_table[i].init) { if (!converters_table[i].init()) return NULL; } *out_state = cmalloc(sizeof (struct converter_state)); if (! *out_state) { fprintf(stderr, "can't allocate converter state\n"); return NULL; } (*out_state)->z1_I = 0; (*out_state)->z1_Q = 0; if (filter_dc) { // init DC block @ 1Hz (*out_state)->dc_b = exp(-2.0 * M_PI * 1.0 / sample_rate); (*out_state)->dc_a = 1.0 - (*out_state)->dc_b; } else { // if the converter does filtering, make sure it has no effect (*out_state)->dc_b = 1.0; (*out_state)->dc_a = 0.0; } if (Modes.sdr_type == SDR_IFILE) { fprintf(stderr, "init_converter: using %s\n", converters_table[i].description); } return converters_table[i].fn; } void cleanup_converter(struct converter_state **state) { sfree(uc8_lookup); #if defined(SC16Q11_TABLE_BITS) sfree(sc16q11_lookup); #endif sfree(*state); } readsb-3.16/convert.h000066400000000000000000000032301505057307600145420ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // convert.h: support for various IQ -> magnitude conversions // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #ifndef DUMP1090_CONVERT_H #define DUMP1090_CONVERT_H struct converter_state; typedef enum { INPUT_UC8 = 0, INPUT_SC16, INPUT_SC16Q11 } input_format_t; typedef void (*iq_convert_fn)(void *iq_data, uint16_t *mag_data, unsigned nsamples, struct converter_state *state, double *out_mean_level, double *out_mean_power); iq_convert_fn init_converter (input_format_t format, double sample_rate, int filter_dc, struct converter_state **out_state); void cleanup_converter (struct converter_state **state); #endif readsb-3.16/cpr.c000066400000000000000000000323431505057307600136500ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // cpr.c - Compact Position Reporting decoder and tests // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include // //========================================================================= // // Always positive MOD operation, used for CPR decoding. // static int cprModInt(int a, int b) { int res = a % b; if (res < 0) res += b; return res; } static double cprModDouble(double a, double b) { double res = fmod(a, b); if (res < 0) res += b; return res; } // //========================================================================= // // The NL function uses the precomputed table from 1090-WP-9-14 // static int cprNLFunction(double lat) { if (lat < 0) lat = -lat; // Table is simmetric about the equator if (lat > 60) goto L60; if (lat > 44.2) goto L442; if (lat > 30) goto L30; if (lat < 10.47047130) return 59; if (lat < 14.82817437) return 58; if (lat < 18.18626357) return 57; if (lat < 21.02939493) return 56; if (lat < 23.54504487) return 55; if (lat < 25.82924707) return 54; if (lat < 27.93898710) return 53; if (lat < 29.91135686) return 52; L30: if (lat < 31.77209708) return 51; if (lat < 33.53993436) return 50; if (lat < 35.22899598) return 49; if (lat < 36.85025108) return 48; if (lat < 38.41241892) return 47; if (lat < 39.92256684) return 46; if (lat < 41.38651832) return 45; if (lat < 42.80914012) return 44; if (lat < 44.19454951) return 43; L442: if (lat < 45.54626723) return 42; if (lat < 46.86733252) return 41; if (lat < 48.16039128) return 40; if (lat < 49.42776439) return 39; if (lat < 50.67150166) return 38; if (lat < 51.89342469) return 37; if (lat < 53.09516153) return 36; if (lat < 54.27817472) return 35; if (lat < 55.44378444) return 34; if (lat < 56.59318756) return 33; if (lat < 57.72747354) return 32; if (lat < 58.84763776) return 31; if (lat < 59.95459277) return 30; L60: if (lat < 61.04917774) return 29; if (lat < 62.13216659) return 28; if (lat < 63.20427479) return 27; if (lat < 64.26616523) return 26; if (lat < 65.31845310) return 25; if (lat < 66.36171008) return 24; if (lat < 67.39646774) return 23; if (lat < 68.42322022) return 22; if (lat < 69.44242631) return 21; if (lat < 70.45451075) return 20; if (lat < 71.45986473) return 19; if (lat < 72.45884545) return 18; if (lat < 73.45177442) return 17; if (lat < 74.43893416) return 16; if (lat < 75.42056257) return 15; if (lat < 76.39684391) return 14; if (lat < 77.36789461) return 13; if (lat < 78.33374083) return 12; if (lat < 79.29428225) return 11; if (lat < 80.24923213) return 10; if (lat < 81.19801349) return 9; if (lat < 82.13956981) return 8; if (lat < 83.07199445) return 7; if (lat < 83.99173563) return 6; if (lat < 84.89166191) return 5; if (lat < 85.75541621) return 4; if (lat < 86.53536998) return 3; if (lat < 87.00000000) return 2; else return 1; } // //========================================================================= // static int cprNFunction(double lat, int fflag) { int nl = cprNLFunction(lat) - (fflag ? 1 : 0); if (nl < 1) nl = 1; return nl; } // //========================================================================= // static double cprDlonFunction(double lat, int fflag, int surface) { return (surface ? 90.0 : 360.0) / cprNFunction(lat, fflag); } // //========================================================================= // // This algorithm comes from: // http://www.lll.lu/~edward/edward/adsb/DecodingADSBposition.html. // // A few remarks: // 1) 131072 is 2^17 since CPR latitude and longitude are encoded in 17 bits. // int decodeCPRairborne(int even_cprlat, int even_cprlon, int odd_cprlat, int odd_cprlon, int fflag, double *out_lat, double *out_lon) { double AirDlat0 = 360.0 / 60.0; double AirDlat1 = 360.0 / 59.0; double lat0 = even_cprlat; double lat1 = odd_cprlat; double lon0 = even_cprlon; double lon1 = odd_cprlon; double rlat, rlon; // Compute the Latitude Index "j" int j = (int) floor(((59 * lat0 - 60 * lat1) / 131072) + 0.5); double rlat0 = AirDlat0 * (cprModInt(j, 60) + lat0 / 131072); double rlat1 = AirDlat1 * (cprModInt(j, 59) + lat1 / 131072); if (rlat0 >= 270) rlat0 -= 360; if (rlat1 >= 270) rlat1 -= 360; // Check to see that the latitude is in range: -90 .. +90 if (rlat0 < -90 || rlat0 > 90 || rlat1 < -90 || rlat1 > 90) return (-2); // bad data // Check that both are in the same latitude zone, or abort. if (cprNLFunction(rlat0) != cprNLFunction(rlat1)) return (-1); // positions crossed a latitude zone, try again later // Compute ni and the Longitude Index "m" if (fflag) { // Use odd packet. int ni = cprNFunction(rlat1, 1); int m = (int) floor((((lon0 * (cprNLFunction(rlat1) - 1)) - (lon1 * cprNLFunction(rlat1))) / 131072.0) + 0.5); rlon = cprDlonFunction(rlat1, 1, 0) * (cprModInt(m, ni) + lon1 / 131072); rlat = rlat1; } else { // Use even packet. int ni = cprNFunction(rlat0, 0); int m = (int) floor((((lon0 * (cprNLFunction(rlat0) - 1)) - (lon1 * cprNLFunction(rlat0))) / 131072) + 0.5); rlon = cprDlonFunction(rlat0, 0, 0) * (cprModInt(m, ni) + lon0 / 131072); rlat = rlat0; } // Renormalize to -180 .. +180 rlon -= floor((rlon + 180) / 360) * 360; *out_lat = rlat; *out_lon = rlon; return 0; } int decodeCPRsurface(double reflat, double reflon, int even_cprlat, int even_cprlon, int odd_cprlat, int odd_cprlon, int fflag, double *out_lat, double *out_lon) { double AirDlat0 = 90.0 / 60.0; double AirDlat1 = 90.0 / 59.0; double lat0 = even_cprlat; double lat1 = odd_cprlat; double lon0 = even_cprlon; double lon1 = odd_cprlon; double rlon, rlat; // Compute the Latitude Index "j" int j = (int) floor(((59 * lat0 - 60 * lat1) / 131072) + 0.5); double rlat0 = AirDlat0 * (cprModInt(j, 60) + lat0 / 131072); double rlat1 = AirDlat1 * (cprModInt(j, 59) + lat1 / 131072); // Pick the quadrant that's closest to the reference location - // this is not necessarily the same quadrant that contains the // reference location. // // There are also only two valid quadrants: -90..0 and 0..90; // no correct message would try to encoding a latitude in the // ranges -180..-90 and 90..180. // // If the computed latitude is more than 45 degrees north of // the reference latitude (using the northern hemisphere // solution), then the southern hemisphere solution will be // closer to the refernce latitude. // // e.g. reflat=0, rlat=44, use rlat=44 // reflat=0, rlat=46, use rlat=46-90 = -44 // reflat=40, rlat=84, use rlat=84 // reflat=40, rlat=86, use rlat=86-90 = -4 // reflat=-40, rlat=4, use rlat=4 // reflat=-40, rlat=6, use rlat=6-90 = -84 // As a special case, -90, 0 and +90 all encode to zero, so // there's a little extra work to do there. if (rlat0 == 0) { if (reflat < -45) rlat0 = -90; else if (reflat > 45) rlat0 = 90; } else if ((rlat0 - reflat) > 45) { rlat0 -= 90; } if (rlat1 == 0) { if (reflat < -45) rlat1 = -90; else if (reflat > 45) rlat1 = 90; } else if ((rlat1 - reflat) > 45) { rlat1 -= 90; } // Check to see that the latitude is in range: -90 .. +90 if (rlat0 < -90 || rlat0 > 90 || rlat1 < -90 || rlat1 > 90) return (-2); // bad data // Check that both are in the same latitude zone, or abort. if (cprNLFunction(rlat0) != cprNLFunction(rlat1)) return (-1); // positions crossed a latitude zone, try again later // Compute ni and the Longitude Index "m" if (fflag) { // Use odd packet. int ni = cprNFunction(rlat1, 1); int m = (int) floor((((lon0 * (cprNLFunction(rlat1) - 1)) - (lon1 * cprNLFunction(rlat1))) / 131072.0) + 0.5); rlon = cprDlonFunction(rlat1, 1, 1) * (cprModInt(m, ni) + lon1 / 131072); rlat = rlat1; } else { // Use even packet. int ni = cprNFunction(rlat0, 0); int m = (int) floor((((lon0 * (cprNLFunction(rlat0) - 1)) - (lon1 * cprNLFunction(rlat0))) / 131072) + 0.5); rlon = cprDlonFunction(rlat0, 0, 1) * (cprModInt(m, ni) + lon0 / 131072); rlat = rlat0; } // Pick the quadrant that's closest to the reference location - // this is not necessarily the same quadrant that contains the // reference location. Unlike the latitude case, all four // quadrants are valid. // if reflon is more than 45 degrees away, move some multiple of 90 degrees towards it rlon += floor((reflon - rlon + 45) / 90) * 90; // this might move us outside (-180..+180), we fix this below // Renormalize to -180 .. +180 rlon -= floor((rlon + 180) / 360) * 360; *out_lat = rlat; *out_lon = rlon; return 0; } // //========================================================================= // // This algorithm comes from: // 1090-WP29-07-Draft_CPR101 (which also defines decodeCPR() ) // // Despite what the earlier comment here said, we should *not* be using trunc(). // See Figure 5-5 / 5-6 and note that floor is applied to (0.5 + fRP - fEP), not // directly to (fRP - fEP). Eq 38 is correct. // int decodeCPRrelative(double reflat, double reflon, int cprlat, int cprlon, int fflag, int surface, double *out_lat, double *out_lon) { double AirDlat; double AirDlon; double fractional_lat = cprlat / 131072.0; double fractional_lon = cprlon / 131072.0; double rlon, rlat; int j, m; AirDlat = (surface ? 90.0 : 360.0) / (fflag ? 59.0 : 60.0); // Compute the Latitude Index "j" j = (int) (floor(reflat / AirDlat) + floor(0.5 + cprModDouble(reflat, AirDlat) / AirDlat - fractional_lat)); rlat = AirDlat * (j + fractional_lat); if (rlat >= 270) rlat -= 360; // Check to see that the latitude is in range: -90 .. +90 if (rlat < -90 || rlat > 90) { return (-1); // Time to give up - Latitude error } // Check to see that answer is reasonable - ie no more than 1/2 cell away if (fabs(rlat - reflat) > (AirDlat / 2)) { return (-1); // Time to give up - Latitude error } // Compute the Longitude Index "m" AirDlon = cprDlonFunction(rlat, fflag, surface); m = (int) (floor(reflon / AirDlon) + floor(0.5 + cprModDouble(reflon, AirDlon) / AirDlon - fractional_lon)); rlon = AirDlon * (m + fractional_lon); if (rlon > 180) rlon -= 360; // Check to see that answer is reasonable - ie no more than 1/2 cell away if (fabs(rlon - reflon) > (AirDlon / 2)) return (-1); // Time to give up - Longitude error *out_lat = rlat; *out_lon = rlon; return (0); } readsb-3.16/cpr.h000066400000000000000000000031531505057307600136520ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // cpr.h - Compact Position Reporting prototypes // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #ifndef DUMP1090_CPR_H #define DUMP1090_CPR_H int decodeCPRairborne (int even_cprlat, int even_cprlon, int odd_cprlat, int odd_cprlon, int fflag, double *out_lat, double *out_lon); int decodeCPRsurface (double reflat, double reflon, int even_cprlat, int even_cprlon, int odd_cprlat, int odd_cprlon, int fflag, double *out_lat, double *out_lon); int decodeCPRrelative (double reflat, double reflon, int cprlat, int cprlon, int fflag, int surface, double *out_lat, double *out_lon); #endif readsb-3.16/cpreadsb.sh000077500000000000000000000001631505057307600150350ustar00rootroot00000000000000#!/bin/bash set -e rm -f /usr/bin/readsb /usr/bin/viewadsb cp readsb /usr/bin/readsb cp viewadsb /usr/bin/viewadsb readsb-3.16/cprtests.c000066400000000000000000000400231505057307600147250ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // cprtests.c - tests for CPR decoder // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #include #include #include "cpr.h" // Global, airborne CPR test data: static const struct { int even_cprlat, even_cprlon; // input: raw CPR values, even message int odd_cprlat, odd_cprlon; // input: raw CPR values, odd message int even_result; // verify: expected result from decoding with fflag=0 (even message is latest) double even_rlat, even_rlon; // verify: expected position from decoding with fflag=0 (even message is latest) int odd_result; // verify: expected result from decoding with fflag=1 (odd message is latest) double odd_rlat, odd_rlon; // verify: expected position from decoding with fflag=1 (odd message is latest) } cprGlobalAirborneTests[] = { { 80536, 9432, 61720, 9192, 0, 51.686646, 0.700156, 0, 51.686763, 0.701294}, { 80534, 9413, 61714, 9144, 0, 51.686554, 0.698745, 0, 51.686484, 0.697632}, // todo: more positions, bad data }; // Global, surface CPR test data: static const struct { double reflat, reflon; // input: reference location for decoding int even_cprlat, even_cprlon; // input: raw CPR values, even message int odd_cprlat, odd_cprlon; // input: raw CPR values, odd message int even_result; // verify: expected result from decoding with fflag=0 (even message is latest) double even_rlat, even_rlon; // verify: expected position from decoding with fflag=0 (even message is latest) int odd_result; // verify: expected result from decoding with fflag=1 (odd message is latest) double odd_rlat, odd_rlon; // verify: expected position from decoding with fflag=1 (odd message is latest) } cprGlobalSurfaceTests[] = { // The real position received here was on the Cambridge (UK) airport apron at 52.21N 0.177E // We mess with the reference location to check that the right quadrant is used. // longitude quadrants: { 52.00, -180.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601 - 180.0, 0, 52.209976, 0.176507 - 180.0}, { 52.00, -140.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601 - 180.0, 0, 52.209976, 0.176507 - 180.0}, { 52.00, -130.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601 - 90.0, 0, 52.209976, 0.176507 - 90.0}, { 52.00, -50.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601 - 90.0, 0, 52.209976, 0.176507 - 90.0}, { 52.00, -40.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601, 0, 52.209976, 0.176507}, { 52.00, -10.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601, 0, 52.209976, 0.176507}, { 52.00, 0.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601, 0, 52.209976, 0.176507}, { 52.00, 10.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601, 0, 52.209976, 0.176507}, { 52.00, 40.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601, 0, 52.209976, 0.176507}, { 52.00, 50.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601 + 90.0, 0, 52.209976, 0.176507 + 90.0}, { 52.00, 130.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601 + 90.0, 0, 52.209976, 0.176507 + 90.0}, { 52.00, 140.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601 - 180.0, 0, 52.209976, 0.176507 - 180.0}, { 52.00, 180.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601 - 180.0, 0, 52.209976, 0.176507 - 180.0}, // latitude quadrants (but only 2). The decoded longitude also changes because the cell size changes with latitude { 90.00, 0.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601, 0, 52.209976, 0.176507}, { 52.00, 0.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601, 0, 52.209976, 0.176507}, { 8.00, 0.00, 105730, 9259, 29693, 8997, 0, 52.209984, 0.176601, 0, 52.209976, 0.176507}, { 7.00, 0.00, 105730, 9259, 29693, 8997, 0, 52.209984 - 90.0, 0.135269, 0, 52.209976 - 90.0, 0.134299}, { -52.00, 0.00, 105730, 9259, 29693, 8997, 0, 52.209984 - 90.0, 0.135269, 0, 52.209976 - 90.0, 0.134299}, { -90.00, 0.00, 105730, 9259, 29693, 8997, 0, 52.209984 - 90.0, 0.135269, 0, 52.209976 - 90.0, 0.134299}, // poles/equator cases { -46.00, -180.00, 0, 0, 0, 0, 0, -90.0, -180.000000, 0, -90.0, -180.0}, // south pole { -44.00, -180.00, 0, 0, 0, 0, 0, 0.0, -180.000000, 0, 0.0, -180.0}, // equator { 44.00, -180.00, 0, 0, 0, 0, 0, 0.0, -180.000000, 0, 0.0, -180.0}, // equator { 46.00, -180.00, 0, 0, 0, 0, 0, 90.0, -180.000000, 0, 90.0, -180.0}, // north pole }; // Relative CPR test data: static const struct { double reflat, reflon; // input: reference location for decoding int cprlat, cprlon; // input: raw CPR values, even or odd message int fflag; // input: fflag in raw message int surface; // input: decode as air (0) or surface (1) position int result; // verify: expected result double rlat, rlon; // verify: expected position } cprRelativeTests[] = { // // AIRBORNE // { 52.00, 0.00, 80536, 9432, 0, 0, 0, 51.686646, 0.700156}, // even, airborne { 52.00, 0.00, 61720, 9192, 1, 0, 0, 51.686763, 0.701294}, // odd, airborne { 52.00, 0.00, 80534, 9413, 0, 0, 0, 51.686554, 0.698745}, // even, airborne { 52.00, 0.00, 61714, 9144, 1, 0, 0, 51.686484, 0.697632}, // odd, airborne // test moving the receiver around a bit // We cannot move it more than 1/2 cell away before ambiguity happens. // latitude must be within about 3 degrees (cell size is 360/60 = 6 degrees) { 48.70, 0.00, 80536, 9432, 0, 0, 0, 51.686646, 0.700156}, // even, airborne { 48.70, 0.00, 61720, 9192, 1, 0, 0, 51.686763, 0.701294}, // odd, airborne { 48.70, 0.00, 80534, 9413, 0, 0, 0, 51.686554, 0.698745}, // even, airborne { 48.70, 0.00, 61714, 9144, 1, 0, 0, 51.686484, 0.697632}, // odd, airborne { 54.60, 0.00, 80536, 9432, 0, 0, 0, 51.686646, 0.700156}, // even, airborne { 54.60, 0.00, 61720, 9192, 1, 0, 0, 51.686763, 0.701294}, // odd, airborne { 54.60, 0.00, 80534, 9413, 0, 0, 0, 51.686554, 0.698745}, // even, airborne { 54.60, 0.00, 61714, 9144, 1, 0, 0, 51.686484, 0.697632}, // odd, airborne // longitude must be within about 4.8 degrees at this latitude { 52.00, 5.40, 80536, 9432, 0, 0, 0, 51.686646, 0.700156}, // even, airborne { 52.00, 5.40, 61720, 9192, 1, 0, 0, 51.686763, 0.701294}, // odd, airborne { 52.00, 5.40, 80534, 9413, 0, 0, 0, 51.686554, 0.698745}, // even, airborne { 52.00, 5.40, 61714, 9144, 1, 0, 0, 51.686484, 0.697632}, // odd, airborne { 52.00, -4.10, 80536, 9432, 0, 0, 0, 51.686646, 0.700156}, // even, airborne { 52.00, -4.10, 61720, 9192, 1, 0, 0, 51.686763, 0.701294}, // odd, airborne { 52.00, -4.10, 80534, 9413, 0, 0, 0, 51.686554, 0.698745}, // even, airborne { 52.00, -4.10, 61714, 9144, 1, 0, 0, 51.686484, 0.697632}, // odd, airborne // // SURFACE // // Surface position on the Cambridge (UK) airport apron at 52.21N 0.18E { 52.00, 0.00, 105730, 9259, 0, 1, 0, 52.209984, 0.176601}, // even, surface { 52.00, 0.00, 29693, 8997, 1, 1, 0, 52.209976, 0.176507}, // odd, surface // test moving the receiver around a bit // We cannot move it more than 1/2 cell away before ambiguity happens. // latitude must be within about 0.75 degrees (cell size is 90/60 = 1.5 degrees) { 51.46, 0.00, 105730, 9259, 0, 1, 0, 52.209984, 0.176601}, // even, surface { 51.46, 0.00, 29693, 8997, 1, 1, 0, 52.209976, 0.176507}, // odd, surface { 52.95, 0.00, 105730, 9259, 0, 1, 0, 52.209984, 0.176601}, // even, surface { 52.95, 0.00, 29693, 8997, 1, 1, 0, 52.209976, 0.176507}, // odd, surface // longitude must be within about 1.25 degrees at this latitude { 52.00, 1.40, 105730, 9259, 0, 1, 0, 52.209984, 0.176601}, // even, surface { 52.00, 1.40, 29693, 8997, 1, 1, 0, 52.209976, 0.176507}, // odd, surface { 52.00, -1.05, 105730, 9259, 0, 1, 0, 52.209984, 0.176601}, // even, surface { 52.00, -1.05, 29693, 8997, 1, 1, 0, 52.209976, 0.176507}, // odd, surface }; static int testCPRGlobalAirborne() { int ok = 1; unsigned i; for (i = 0; i < sizeof (cprGlobalAirborneTests) / sizeof (cprGlobalAirborneTests[0]); ++i) { double rlat = 0, rlon = 0; int res; res = decodeCPRairborne(cprGlobalAirborneTests[i].even_cprlat, cprGlobalAirborneTests[i].even_cprlon, cprGlobalAirborneTests[i].odd_cprlat, cprGlobalAirborneTests[i].odd_cprlon, 0, &rlat, &rlon); if (res != cprGlobalAirborneTests[i].even_result || fabs(rlat - cprGlobalAirborneTests[i].even_rlat) > 1e-6 || fabs(rlon - cprGlobalAirborneTests[i].even_rlon) > 1e-6) { ok = 0; fprintf(stderr, "testCPRGlobalAirborne[%u,EVEN]: FAIL: decodeCPRairborne(%d,%d,%d,%d,EVEN) failed:\n" " result %d (expected %d)\n" " lat %.6f (expected %.6f)\n" " lon %.6f (expected %.6f)\n", i, cprGlobalAirborneTests[i].even_cprlat, cprGlobalAirborneTests[i].even_cprlon, cprGlobalAirborneTests[i].odd_cprlat, cprGlobalAirborneTests[i].odd_cprlon, res, cprGlobalAirborneTests[i].even_result, rlat, cprGlobalAirborneTests[i].even_rlat, rlon, cprGlobalAirborneTests[i].even_rlon); } else { fprintf(stderr, "testCPRGlobalAirborne[%u,EVEN]: PASS\n", i); } res = decodeCPRairborne(cprGlobalAirborneTests[i].even_cprlat, cprGlobalAirborneTests[i].even_cprlon, cprGlobalAirborneTests[i].odd_cprlat, cprGlobalAirborneTests[i].odd_cprlon, 1, &rlat, &rlon); if (res != cprGlobalAirborneTests[i].odd_result || fabs(rlat - cprGlobalAirborneTests[i].odd_rlat) > 1e-6 || fabs(rlon - cprGlobalAirborneTests[i].odd_rlon) > 1e-6) { ok = 0; fprintf(stderr, "testCPRGlobalAirborne[%u,ODD]: FAIL: decodeCPRairborne(%d,%d,%d,%d,ODD) failed:\n" " result %d (expected %d)\n" " lat %.6f (expected %.6f)\n" " lon %.6f (expected %.6f)\n", i, cprGlobalAirborneTests[i].even_cprlat, cprGlobalAirborneTests[i].even_cprlon, cprGlobalAirborneTests[i].odd_cprlat, cprGlobalAirborneTests[i].odd_cprlon, res, cprGlobalAirborneTests[i].odd_result, rlat, cprGlobalAirborneTests[i].odd_rlat, rlon, cprGlobalAirborneTests[i].odd_rlon); } else { fprintf(stderr, "testCPRGlobalAirborne[%u,ODD]: PASS\n", i); } } return ok; } static int testCPRGlobalSurface() { int ok = 1; unsigned i; for (i = 0; i < sizeof (cprGlobalSurfaceTests) / sizeof (cprGlobalSurfaceTests[0]); ++i) { double rlat = 0, rlon = 0; int res; res = decodeCPRsurface(cprGlobalSurfaceTests[i].reflat, cprGlobalSurfaceTests[i].reflon, cprGlobalSurfaceTests[i].even_cprlat, cprGlobalSurfaceTests[i].even_cprlon, cprGlobalSurfaceTests[i].odd_cprlat, cprGlobalSurfaceTests[i].odd_cprlon, 0, &rlat, &rlon); if (res != cprGlobalSurfaceTests[i].even_result || fabs(rlat - cprGlobalSurfaceTests[i].even_rlat) > 1e-6 || fabs(rlon - cprGlobalSurfaceTests[i].even_rlon) > 1e-6) { ok = 0; fprintf(stderr, "testCPRGlobalSurface[%u,EVEN]: FAIL: decodeCPRsurface(%.6f,%.6f,%d,%d,%d,%d,EVEN) failed:\n" " result %d (expected %d)\n" " lat %.6f (expected %.6f)\n" " lon %.6f (expected %.6f)\n", i, cprGlobalSurfaceTests[i].reflat, cprGlobalSurfaceTests[i].reflon, cprGlobalSurfaceTests[i].even_cprlat, cprGlobalSurfaceTests[i].even_cprlon, cprGlobalSurfaceTests[i].odd_cprlat, cprGlobalSurfaceTests[i].odd_cprlon, res, cprGlobalSurfaceTests[i].even_result, rlat, cprGlobalSurfaceTests[i].even_rlat, rlon, cprGlobalSurfaceTests[i].even_rlon); } else { fprintf(stderr, "testCPRGlobalSurface[%u,EVEN]: PASS\n", i); } res = decodeCPRsurface(cprGlobalSurfaceTests[i].reflat, cprGlobalSurfaceTests[i].reflon, cprGlobalSurfaceTests[i].even_cprlat, cprGlobalSurfaceTests[i].even_cprlon, cprGlobalSurfaceTests[i].odd_cprlat, cprGlobalSurfaceTests[i].odd_cprlon, 1, &rlat, &rlon); if (res != cprGlobalSurfaceTests[i].odd_result || fabs(rlat - cprGlobalSurfaceTests[i].odd_rlat) > 1e-6 || fabs(rlon - cprGlobalSurfaceTests[i].odd_rlon) > 1e-6) { ok = 0; fprintf(stderr, "testCPRGlobalSurface[%u,ODD]: FAIL: decodeCPRsurface(%.6f,%.6f,%d,%d,%d,%d,ODD) failed:\n" " result %d (expected %d)\n" " lat %.6f (expected %.6f)\n" " lon %.6f (expected %.6f)\n", i, cprGlobalSurfaceTests[i].reflat, cprGlobalSurfaceTests[i].reflon, cprGlobalSurfaceTests[i].even_cprlat, cprGlobalSurfaceTests[i].even_cprlon, cprGlobalSurfaceTests[i].odd_cprlat, cprGlobalSurfaceTests[i].odd_cprlon, res, cprGlobalSurfaceTests[i].odd_result, rlat, cprGlobalSurfaceTests[i].odd_rlat, rlon, cprGlobalSurfaceTests[i].odd_rlon); } else { fprintf(stderr, "testCPRGlobalSurface[%u,ODD]: PASS\n", i); } } return ok; } static int testCPRRelative() { int ok = 1; unsigned i; for (i = 0; i < sizeof (cprRelativeTests) / sizeof (cprRelativeTests[0]); ++i) { double rlat = 0, rlon = 0; int res; res = decodeCPRrelative(cprRelativeTests[i].reflat, cprRelativeTests[i].reflon, cprRelativeTests[i].cprlat, cprRelativeTests[i].cprlon, cprRelativeTests[i].fflag, cprRelativeTests[i].surface, &rlat, &rlon); if (res != cprRelativeTests[i].result || fabs(rlat - cprRelativeTests[i].rlat) > 1e-6 || fabs(rlon - cprRelativeTests[i].rlon) > 1e-6) { ok = 0; fprintf(stderr, "testCPRRelative[%u]: FAIL: decodeCPRrelative(%.6f,%.6f,%d,%d,%d,%d) failed:\n" " result %d (expected %d)\n" " lat %.6f (expected %.6f)\n" " lon %.6f (expected %.6f)\n", i, cprRelativeTests[i].reflat, cprRelativeTests[i].reflon, cprRelativeTests[i].cprlat, cprRelativeTests[i].cprlon, cprRelativeTests[i].fflag, cprRelativeTests[i].surface, res, cprRelativeTests[i].result, rlat, cprRelativeTests[i].rlat, rlon, cprRelativeTests[i].rlon); } else { fprintf(stderr, "testCPRRelative[%u]: PASS\n", i); } } return ok; } int main(int __attribute__ ((unused)) argc, char __attribute__ ((unused)) **argv) { int ok = 1; ok = testCPRGlobalAirborne() && ok; ok = testCPRGlobalSurface() && ok; ok = testCPRRelative() && ok; return ok ? 0 : 1; } readsb-3.16/crc.c000066400000000000000000000426011505057307600136310ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // crc.h: Mode S CRC calculation and error correction. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #include "readsb.h" #include // Errorinfo for "no errors" static struct errorinfo NO_ERRORS; // Generator polynomial for the Mode S CRC: #define MODES_GENERATOR_POLY 0xfff409U // CRC values for all single-byte messages; // used to speed up CRC calculation. ALIGNED static uint32_t crc_table[256]; // Syndrome values for all single-bit errors; // used to speed up construction of error- // correction tables. ALIGNED static uint32_t single_bit_syndrome[112]; static void initLookupTables() { int i; uint8_t msg[112 / 8]; for (i = 0; i < 256; ++i) { uint32_t c = i << 16; int j; for (j = 0; j < 8; ++j) { if (c & 0x800000) c = (c << 1) ^ MODES_GENERATOR_POLY; else c = (c << 1); } crc_table[i] = c & 0x00ffffff; } memset(msg, 0, sizeof (msg)); for (i = 0; i < 112; ++i) { msg[i / 8] ^= 1 << (7 - (i & 7)); single_bit_syndrome[i] = modesChecksum(msg, 112); msg[i / 8] ^= 1 << (7 - (i & 7)); } } uint32_t modesChecksum(uint8_t *message, int bits) { uint32_t rem = 0; int i; int n = bits / 8; assert(bits % 8 == 0); assert(n >= 3); for (i = 0; i < n - 3; ++i) { rem = (rem << 8) ^ crc_table[message[i] ^ ((rem & 0xff0000) >> 16)]; rem = rem & 0xffffff; } rem = rem ^ (message[n - 3] << 16) ^ (message[n - 2] << 8) ^ (message[n - 1]); return rem; } static struct errorinfo *bitErrorTable_short; static int bitErrorTableSize_short; static struct errorinfo *bitErrorTable_long; static int bitErrorTableSize_long; // compare two errorinfo structures static int syndrome_compare(const void *x, const void *y) { struct errorinfo *ex = (struct errorinfo*) x; struct errorinfo *ey = (struct errorinfo*) y; return (int) ex->syndrome - (int) ey->syndrome; } // (n k), the number of ways of selecting k distinct items from a set of n items static int combinations(int n, int k) { int result = 1, i; if (k == 0 || k == n) return 1; if (k > n) return 0; for (i = 1; i <= k; ++i) { result = result * n / i; n = n - 1; } return result; } // Recursively populates an errorinfo table with error syndromes // // in: // table: the table to fill // n: first entry to fill // maxSize: max size of table // offset: start bit offset for checksum calculation // startbit: first bit to introduce errors into // endbit: (one past) last bit to introduce errors info // base_entry: template entry to start from // error_bit: how many error bits have already been set // max_errors: maximum total error bits to set // out: // returns: the next free entry in the table // table: has been populated between [n, return value) static int prepareSubtable(struct errorinfo *table, int n, int maxsize, int offset, int startbit, int endbit, struct errorinfo *base_entry, int error_bit, int max_errors) { int i = 0; if (error_bit >= max_errors) return n; for (i = startbit; i < endbit; ++i) { assert(n < maxsize); table[n] = *base_entry; table[n].syndrome ^= single_bit_syndrome[i + offset]; table[n].errors = error_bit + 1; table[n].bit[error_bit] = i; ++n; n = prepareSubtable(table, n, maxsize, offset, i + 1, endbit, &table[n - 1], error_bit + 1, max_errors); } return n; } static int flagCollisions(struct errorinfo *table, int tablesize, int offset, int startbit, int endbit, uint32_t base_syndrome, int error_bit, int first_error, int last_error) { int i = 0; int count = 0; if (error_bit > last_error) return 0; for (i = startbit; i < endbit; ++i) { struct errorinfo ei; ei.syndrome = base_syndrome ^ single_bit_syndrome[i + offset]; if (error_bit >= first_error) { struct errorinfo *collision = bsearch(&ei, table, tablesize, sizeof (struct errorinfo), syndrome_compare); if (collision != NULL && collision->errors != -1) { ++count; collision->errors = -1; } } count += flagCollisions(table, tablesize, offset, i + 1, endbit, ei.syndrome, error_bit + 1, first_error, last_error); } return count; } // Allocate and build an error table for messages of length "bits" (max 112) // returns a pointer to the new table and sets *size_out to the table length static struct errorinfo *prepareErrorTable(int bits, int max_correct, int max_detect, int *size_out) { int maxsize, usedsize; struct errorinfo *table; struct errorinfo base_entry; int i, j; assert(bits >= 0 && bits <= 112); assert(max_correct >= 0 && max_correct <= MODES_MAX_BITERRORS); assert(max_detect >= max_correct); if (!max_correct) { *size_out = 0; return NULL; } maxsize = 0; for (i = 1; i <= max_correct; ++i) { maxsize += combinations(bits - 5, i); // space needed for all i-bit errors } #ifdef CRCDEBUG fprintf(stderr, "Preparing syndrome table to correct up to %d-bit errors (detecting %d-bit errors) in a %d-bit message (max %d entries)\n", max_correct, max_detect, bits, maxsize); #endif table = cmalloc(maxsize * sizeof (struct errorinfo)); base_entry.syndrome = 0; base_entry.errors = 0; for (i = 0; i < MODES_MAX_BITERRORS; ++i) base_entry.bit[i] = -1; // ignore the first 5 bits (DF type) usedsize = prepareSubtable(table, 0, maxsize, 112 - bits, 5, bits, &base_entry, 0, max_correct); #ifdef CRCDEBUG fprintf(stderr, "%d syndromes (expected %d).\n", usedsize, maxsize); fprintf(stderr, "Sorting syndromes..\n"); #endif qsort(table, usedsize, sizeof (struct errorinfo), syndrome_compare); #ifdef CRCDEBUG { // Show the table stats fprintf(stderr, "Undetectable errors:\n"); for (i = 1; i <= max_correct; ++i) { int j, count; count = 0; for (j = 0; j < usedsize; ++j) if (table[j].errors == i && table[j].syndrome == 0) ++count; fprintf(stderr, " %d undetectable %d-bit errors\n", count, i); } } #endif // Handle ambiguous cases, where there is more than one possible error pattern // that produces a given syndrome (this happens with >2 bit errors). #ifdef CRCDEBUG fprintf(stderr, "Finding collisions..\n"); #endif for (i = 0, j = 0; i < usedsize; ++i) { if (i < usedsize - 1 && table[i + 1].syndrome == table[i].syndrome) { // skip over this entry and all collisions while (i < usedsize && table[i + 1].syndrome == table[i].syndrome) ++i; // now table[i] is the last duplicate continue; } if (i != j) table[j] = table[i]; ++j; } if (j < usedsize) { #ifdef CRCDEBUG fprintf(stderr, "Discarded %d collisions.\n", usedsize - j); #endif usedsize = j; } // Flag collisions we want to detect but not correct if (max_detect > max_correct) { int flagged; #ifdef CRCDEBUG fprintf(stderr, "Flagging collisions between %d - %d bits..\n", max_correct + 1, max_detect); #endif flagged = flagCollisions(table, usedsize, 112 - bits, 5, bits, 0, 1, max_correct + 1, max_detect); #ifdef CRCDEBUG fprintf(stderr, "Flagged %d collisions for removal.\n", flagged); #else #endif if (flagged > 0) { for (i = 0, j = 0; i < usedsize; ++i) { if (table[i].errors != -1) { if (i != j) table[j] = table[i]; ++j; } } #ifdef CRCDEBUG fprintf(stderr, "Discarded %d flagged collisions.\n", usedsize - j); #endif usedsize = j; } } if (usedsize < maxsize) { #ifdef CRCDEBUG fprintf(stderr, "Shrinking table from %d to %d..\n", maxsize, usedsize); table = realloc(table, usedsize * sizeof (struct errorinfo)); #endif } *size_out = usedsize; #ifdef CRCDEBUG { // Check the table. unsigned char *msg = cmalloc(bits / 8); for (i = 0; i < usedsize; ++i) { int j; struct errorinfo *ei; uint32_t result; memset(msg, 0, bits / 8); ei = &table[i]; for (j = 0; j < ei->errors; ++j) { msg[ei->bit[j] >> 3] ^= 1 << (7 - (ei->bit[j]&7)); } result = modesChecksum(msg, bits); if (result != ei->syndrome) { fprintf(stderr, "PROBLEM: entry %6d/%6d syndrome %06x errors %d bits ", i, usedsize, ei->syndrome, ei->errors); for (j = 0; j < ei->errors; ++j) fprintf(stderr, "%3d ", ei->bit[j]); fprintf(stderr, " checksum %06x\n", result); } } free(msg); // Show the table stats fprintf(stderr, "Syndrome table summary:\n"); for (i = 1; i <= max_correct; ++i) { int j, count, possible; count = 0; for (j = 0; j < usedsize; ++j) if (table[j].errors == i) ++count; possible = combinations(bits - 5, i); fprintf(stderr, " %d entries for %d-bit errors (%d possible, %d%% coverage)\n", count, i, possible, 100 * count / possible); } fprintf(stderr, " %d entries total\n", usedsize); } #endif return table; } // Precompute syndrome tables for 56- and 112-bit messages. void modesChecksumInit(int fixBits) { initLookupTables(); switch (fixBits) { case 0: bitErrorTable_short = bitErrorTable_long = NULL; bitErrorTableSize_short = bitErrorTableSize_long = 0; break; case 1: // For 1 bit correction, we have 100% coverage up to 4 bit detection, so don't bother // with flagging collisions there. bitErrorTable_short = prepareErrorTable(MODES_SHORT_MSG_BITS, 1, 1, &bitErrorTableSize_short); bitErrorTable_long = prepareErrorTable(MODES_LONG_MSG_BITS, 1, 1, &bitErrorTableSize_long); break; default: // Detect out to 4 bit errors; this reduces our 2-bit coverage to about 65%. // This can take a little while - tell the user. fprintf(stderr, "Preparing error correction tables.. "); bitErrorTable_short = prepareErrorTable(MODES_SHORT_MSG_BITS, 2, 4, &bitErrorTableSize_short); bitErrorTable_long = prepareErrorTable(MODES_LONG_MSG_BITS, 2, 4, &bitErrorTableSize_long); fprintf(stderr, "done.\n"); break; } } // Given an error syndrome and message length, return // an error-correction descriptor, or NULL if the // syndrome is uncorrectable struct errorinfo *modesChecksumDiagnose(uint32_t syndrome, int bitlen) { struct errorinfo *table; int tablesize; struct errorinfo ei; if (syndrome == 0) return &NO_ERRORS; assert(bitlen == 56 || bitlen == 112); if (bitlen == 56) { table = bitErrorTable_short; tablesize = bitErrorTableSize_short; } else { table = bitErrorTable_long; tablesize = bitErrorTableSize_long; } if (!table) return NULL; ei.syndrome = syndrome; return bsearch(&ei, table, tablesize, sizeof (struct errorinfo), syndrome_compare); } // Given a message and an error-correction descriptor, // apply the error correction to the given message. void modesChecksumFix(uint8_t *msg, struct errorinfo *info) { int i; if (!info) return; for (i = 0; i < info->errors; ++i) msg[info->bit[i] >> 3] ^= 1 << (7 - (info->bit[i] & 7)); } /* * Clean CRC LUTs on exit. * */ void crcCleanupTables(void) { if (bitErrorTable_short != NULL) free(bitErrorTable_short); if (bitErrorTable_long != NULL) free(bitErrorTable_long); } #ifdef CRCDEBUG int main(int argc, char **argv) { int shortlen, longlen; int i; struct errorinfo *shorttable, *longtable; if (argc < 3) { fprintf(stderr, "syntax: crctests \n"); return 1; } initLookupTables(); shorttable = prepareErrorTable(MODES_SHORT_MSG_BITS, atoi(argv[1]), atoi(argv[2]), &shortlen); longtable = prepareErrorTable(MODES_LONG_MSG_BITS, atoi(argv[1]), atoi(argv[2]), &longlen); // check for DF11 correction syndromes where there is a syndrome with lower 7 bits all zero // (which would be used for DF11 error correction), but there's also a syndrome which has // the same upper 17 bits but nonzero lower 7 bits. // empirically, with ncorrect=1 ndetect=2 we get no ambiguous syndromes; // for ncorrect=2 ndetect=4 we get 11 ambiguous syndromes: /* syndrome 1 = 000C00 bits=[ 44 45 ] syndrome 2 = 000C1B bits=[ 30 43 ] syndrome 1 = 001400 bits=[ 43 45 ] syndrome 2 = 00141B bits=[ 30 44 ] syndrome 1 = 001800 bits=[ 43 44 ] syndrome 2 = 00181B bits=[ 30 45 ] syndrome 1 = 001800 bits=[ 43 44 ] syndrome 2 = 001836 bits=[ 29 42 ] syndrome 1 = 002400 bits=[ 42 45 ] syndrome 2 = 00242D bits=[ 29 30 ] syndrome 1 = 002800 bits=[ 42 44 ] syndrome 2 = 002836 bits=[ 29 43 ] syndrome 1 = 003000 bits=[ 42 43 ] syndrome 2 = 003036 bits=[ 29 44 ] syndrome 1 = 003000 bits=[ 42 43 ] syndrome 2 = 00306C bits=[ 28 41 ] syndrome 1 = 004800 bits=[ 41 44 ] syndrome 2 = 00485A bits=[ 28 29 ] syndrome 1 = 005000 bits=[ 41 43 ] syndrome 2 = 00506C bits=[ 28 42 ] syndrome 1 = 006000 bits=[ 41 42 ] syndrome 2 = 00606C bits=[ 28 43 ] */ // So in the DF11 correction logic, we just discard messages that require more than a 1 bit fix. fprintf(stderr, "checking %d syndromes for DF11 collisions..\n", shortlen); for (i = 0; i < shortlen; ++i) { if ((shorttable[i].syndrome & 0xFF) == 0) { int j; // all syndromes with the same first 17 bits should sort immediately after entry i, // so this is fairly easy for (j = i + 1; j < shortlen; ++j) { if ((shorttable[i].syndrome & 0xFFFF80) == (shorttable[j].syndrome & 0xFFFF80)) { int k; int mismatch = 0; // we don't care if the only differences are in bits that lie in the checksum for (k = 0; k < shorttable[i].errors; ++k) { int l, matched = 0; if (shorttable[i].bit[k] >= 49) continue; // bit is in the final 7 bits, we don't care for (l = 0; l < shorttable[j].errors; ++l) { if (shorttable[i].bit[k] == shorttable[j].bit[l]) { matched = 1; break; } } if (!matched) mismatch = 1; } for (k = 0; k < shorttable[j].errors; ++k) { int l, matched = 0; if (shorttable[j].bit[k] >= 49) continue; // bit is in the final 7 bits, we don't care for (l = 0; l < shorttable[i].errors; ++l) { if (shorttable[j].bit[k] == shorttable[i].bit[l]) { matched = 1; break; } } if (!matched) mismatch = 1; } if (mismatch) { fprintf(stderr, "DF11 correction collision: \n" " syndrome 1 = %06X bits=[", shorttable[i].syndrome); for (k = 0; k < shorttable[i].errors; ++k) fprintf(stderr, " %d", shorttable[i].bit[k]); fprintf(stderr, " ]\n"); fprintf(stderr, " syndrome 2 = %06X bits=[", shorttable[j].syndrome); for (k = 0; k < shorttable[j].errors; ++k) fprintf(stderr, " %d", shorttable[j].bit[k]); fprintf(stderr, " ]\n"); } } else { break; } } } } free(shorttable); free(longtable); return 0; } #endif readsb-3.16/crc.h000066400000000000000000000027421505057307600136400ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // crc.h: Mode S checksum prototypes. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #ifndef DUMP1090_CRC_H #define DUMP1090_CRC_H #include // Global max for fixable bit erros #define MODES_MAX_BITERRORS 2 struct errorinfo { uint32_t syndrome; // CRC syndrome int errors; // number of errors int8_t bit[MODES_MAX_BITERRORS]; // bit positions to fix (-1 = no bit) uint16_t padding; }; void modesChecksumInit (int fixBits); uint32_t modesChecksum (uint8_t *msg, int bitlen); struct errorinfo *modesChecksumDiagnose (uint32_t syndrome, int bitlen); void modesChecksumFix (uint8_t *msg, struct errorinfo *info); void crcCleanupTables (void); #endif readsb-3.16/debian/000077500000000000000000000000001505057307600141355ustar00rootroot00000000000000readsb-3.16/debian/README.librtlsdr000066400000000000000000000016161505057307600170210ustar00rootroot00000000000000This package includes binaries that are statically linked against librtlsdr, which is licensed under the GPL: 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. The upstream source is available at https://github.com/steve-m/librtlsdr readsb-3.16/debian/changelog000066400000000000000000000003441505057307600160100ustar00rootroot00000000000000readsb (3.16) stable; urgency=medium * debian: cleanup package build / service file * debian: autogenerate manpage during build using help2man -- Matthias Wirth Mon, 18 Aug 2025 09:00:00 +0000 readsb-3.16/debian/compat000066400000000000000000000000021505057307600153330ustar00rootroot0000000000000010readsb-3.16/debian/control000066400000000000000000000023051505057307600155400ustar00rootroot00000000000000Source: readsb Section: net Priority: optional Maintainer: Matthias Wirth Build-Depends: debhelper(>=9), libncurses-dev, zlib1g-dev, pkg-config, libzstd-dev, help2man, libusb-1.0-0-dev , librtlsdr-dev , libsoapysdr-dev , libhackrf-dev , libbladerf-dev , libad9361-dev , libiio-dev Standards-Version: 4.4.0.1 Homepage: https://github.com/wiedehopf/readsb Vcs-Git: https://github.com/wiedehopf/readsb.git Package: readsb Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, adduser Description: Networked ADS-B / ModeS Decoder for SDRs (1090 MHz) Supports RTL-SDR, BladeRF, HackRF, PlutoSDR and others via SoapySDR Can output and input these protocols via TCP listen and connect: beast, AVR, SBS, some Asterix, UAT from dump978-fa (input only) Provides various live update json files Can save aircraft track data persistently as gzipped json files Fast builtin webserver for area and other queries, returns json format Webinterface available using the live update json files readsb-3.16/debian/copyright000066400000000000000000001333271505057307600161010ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: readsb Upstream-Contact: Matthias Wirth Source: https://github.com/wiedehopf/readsb Files: * Copyright: Copyright 2012 Salvatore Sanfilippo Copyright 2013,2014 Malcolm Robb Copyright 2014,2015 Oliver Jowett Copyright 2019 Michael Wolf Copyright 2020 Matthias Wirth License: BSD-3-Clause, GPL-2+ and GPL-3+ Files: compat/clock_gettime/* Copyright: Copyright (c), MM Weiss License: BSD-3-Clause Files: compat/clock_nanosleep/* Copyright: Copyright © 2006 Rémi Denis-Courmont. License: GPL-2+ Files: public_html/flags-tiny/* Copyright: Gang of the Coconuts License: CC-BY-SA-3.0 Files: public_html/markers.js Copyright: Kaboldy Icons made by Freepik License: CC-BY-SA-3.0 and CC-BY-3.0 Comment: _generic_plane_svg was added with https://github.com/mutability/dump1090/commit/5f0e295580c34da34ecef3a37f03e9a9d57485ff Source: https://github.com/DE8MSH Files: public_html/jquery/* Copyright: 2015 jQuery Foundation and other contributors License: MIT or GPL-2+ Source: http://www.jquery.com/ Files: public_html/jquery/jquery.ui.touch-punch.js Copyright: 2011–2014 Dave Furfero License: MIT Files: public_html/ol3/ol-3.17.1.css public_html/ol3/ol-3.17.1.js Copyright: 2005-2016 OpenLayers Contributors License: BSD-2-Clause-OpenLayers Source: http://openlayers.org Files: public_html/ol3/ol3-layerswitcher.js public_html/ol3/ol3-layerswitcher.css Copyright: Matt Walker License: MIT Source: https://github.com/walkermatt/ol3-layerswitcher License: GPL-2+ 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 2 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 package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA . On Debian systems, the full text of the GNU General Public License version 2 can be found in the file `/usr/share/common-licenses/GPL-2'. License: GPL-3+ 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 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 . . On Debian systems, the full text of the GNU General Public License version 3 can be found in the file `/usr/share/common-licenses/GPL-3'. License: BSD-2-Clause-OpenLayers Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. . 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . THIS SOFTWARE IS PROVIDED BY OPENLAYERS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. . The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of OpenLayers Contributors. License: BSD-3-Clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. License: Apache-2.0 Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. . On Debian systems, the full text of the Apache Software License version 2 can be found in the file `/usr/share/common-licenses/Apache-2.0'. License: CC-BY-SA-3.0 CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. . License . THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. . BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. . 1. Definitions "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. . 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. . 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: . to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; to Distribute and Publicly Perform the Work including as incorporated in Collections; and, to Distribute and Publicly Perform Adaptations. . For the avoidance of doubt: Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. . The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. . 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: . You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. . 5. Representations, Warranties and Disclaimer . UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. . 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. . 7. Termination . This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. . 8. Miscellaneous . Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for t Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. License: CC-BY-3.0 CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. . License . THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. . BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. . 1. Definitions . a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. . b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. . c. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. . d. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. . e. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. . f. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. . g. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. . h. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. . i. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. . 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. . 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: . a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; . b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; . c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, . d. to Distribute and Publicly Perform Adaptations. . e. For the avoidance of doubt: . i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; . ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, . iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. . The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. . 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: . a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. . b. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. . c. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. . 5. Representations, Warranties and Disclaimer . UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. . 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. . 7. Termination . a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. . b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. . 8. Miscellaneous . a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. . b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. . c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. . d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representatio Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. . e. This License may not be modified without the mutual written agreement of the Licensor and You. . f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. readsb-3.16/debian/readsb.default000066400000000000000000000007101505057307600167410ustar00rootroot00000000000000# readsb configuration # This is sourced by /etc/systemd/system/default.target.wants/readsb.service as # daemon startup configuration. RECEIVER_OPTIONS="--device 0 --device-type rtlsdr --gain auto --ppm 0" DECODER_OPTIONS="--max-range 450 --write-json-every 1" NET_OPTIONS="--net --net-ri-port 30001 --net-ro-port 30002 --net-sbs-port 30003 --net-bi-port 30004,30104 --net-bo-port 30005" JSON_OPTIONS="--json-location-accuracy 2 --range-outline-hours 24" readsb-3.16/debian/readsb.docs000066400000000000000000000000301505057307600162400ustar00rootroot00000000000000debian/README.librtlsdr readsb-3.16/debian/readsb.install000066400000000000000000000000001505057307600167530ustar00rootroot00000000000000readsb-3.16/debian/readsb.manpages000066400000000000000000000000121505057307600171030ustar00rootroot00000000000000debian/*.1readsb-3.16/debian/readsb.postinst000066400000000000000000000027321505057307600172060ustar00rootroot00000000000000#!/bin/bash # postinst script for readsb # # see: dh_installdeb(1) set -e # summary of how this script can be called: # * `configure' # * `abort-upgrade' # * `abort-remove' `in-favour' # # * `abort-remove' # * `abort-deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package NAME=readsb RUNAS=readsb CRONFILE=/etc/cron.d/$NAME TEMPLATECRON=/usr/share/$NAME/cron-template case "$1" in configure) . /usr/share/debconf/confmodule if ! getent passwd "$RUNAS" >/dev/null then adduser --system --home /usr/share/$NAME --no-create-home --quiet "$RUNAS" fi # plugdev required for bladeRF USB access adduser "$RUNAS" plugdev # dialout required for Mode-S Beast and GNS5894 ttyAMA0 access adduser "$RUNAS" dialout ;; abort-upgrade|abort-remove|abort-deconfigure) ;; *) echo "postinst called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# if [ "$1" = "configure" ]; then db_stop; fi exit 0 readsb-3.16/debian/readsb.postrm000066400000000000000000000021011505057307600166350ustar00rootroot00000000000000#!/bin/sh # postrm script for #PACKAGE# # # see: dh_installdeb(1) set -e # summary of how this script can be called: # * `remove' # * `purge' # * `upgrade' # * `failed-upgrade' # * `abort-install' # * `abort-install' # * `abort-upgrade' # * `disappear' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package case "$1" in purge) rm -f /etc/default/readsb deluser readsb plugdev deluser readsb dialout deluser readsb ;; remove) ;; upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) ;; *) echo "postrm called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 readsb-3.16/debian/readsb.service000066400000000000000000000010061505057307600167540ustar00rootroot00000000000000# readsb service for systemd [Unit] Description=readsb ADS-B receiver Documentation=https://github.com/wiedehopf/readsb Wants=network.target After=network.target [Service] EnvironmentFile=/etc/default/readsb User=readsb RuntimeDirectory=readsb RuntimeDirectoryMode=0755 ExecStart=/usr/bin/readsb --write-json /run/readsb --quiet $RECEIVER_OPTIONS $DECODER_OPTIONS $NET_OPTIONS $JSON_OPTIONS Type=simple Restart=always RestartSec=15 StartLimitInterval=1 StartLimitBurst=100 Nice=-5 [Install] WantedBy=default.target readsb-3.16/debian/rules000077500000000000000000000042161505057307600152200ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 export DEB_BUILD_MAINT_OPTIONS = hardening=+all DPKG_EXPORT_BUILDFLAGS = 1 include /usr/share/dpkg/default.mk ifeq ($(DEB_HOST_ARCH),armhf) # Assume a Pi-like target, where using an 8-bit table is a fairly big win over the float path CPPFLAGS += -DSC16Q11_TABLE_BITS=8 endif CONFIG_SWITCH = ifneq ($(filter with_sdrs,$(DEB_BUILD_PROFILES)),) CONFIG_SWITCH += 'RTLSDR=yes' CONFIG_SWITCH += 'SOAPYSDR=yes' CONFIG_SWITCH += 'HACKRF=yes' CONFIG_SWITCH += 'BLADERF=yes' CONFIG_SWITCH += 'PLUTOSDR=yes' endif ifneq ($(filter rtlsdr,$(DEB_BUILD_PROFILES)),) CONFIG_SWITCH += 'RTLSDR=yes' endif ifneq ($(filter soapysdr,$(DEB_BUILD_PROFILES)),) CONFIG_SWITCH += 'SOAPYSDR=yes' endif ifneq ($(filter hackrf,$(DEB_BUILD_PROFILES)),) CONFIG_SWITCH += 'HACKRF=yes' endif ifneq ($(filter bladerf,$(DEB_BUILD_PROFILES)),) CONFIG_SWITCH += 'BLADERF=yes' endif ifneq ($(filter plutosdr,$(DEB_BUILD_PROFILES)),) CONFIG_SWITCH += 'PLUTOSDR=yes' endif ifneq ($(filter history,$(DEB_BUILD_PROFILES)),) CONFIG_SWITCH += 'HISTORY=yes' endif ifneq ($(filter native,$(DEB_BUILD_PROFILES)),) CONFIG_SWITCH += 'OPTIMIZE=-march=native' endif ifneq ($(filter biastee,$(DEB_BUILD_PROFILES)),) CONFIG_SWITCH += 'HAVE_BIASTEE=yes' endif override_dh_auto_build: make -j$(shell nproc) $(CONFIG_SWITCH) help2man -N --no-discard-stderr ./viewadsb > debian/viewadsb.1 help2man -N --no-discard-stderr ./readsb > debian/readsb.1 override_dh_install: dh_install install -d debian/readsb/usr/bin cp -a readsb debian/readsb/usr/bin/readsb ln -s /usr/bin/readsb debian/readsb/usr/bin/viewadsb override_dh_installinit: dh_installinit --noscripts override_dh_shlibdeps: dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info %: dh $@ --with=systemd readsb-3.16/debian/source/000077500000000000000000000000001505057307600154355ustar00rootroot00000000000000readsb-3.16/debian/source/format000066400000000000000000000000151505057307600166440ustar00rootroot000000000000003.0 (native) readsb-3.16/demod_2400.c000066400000000000000000000720321505057307600146200ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // demod_2400.c: 2.4MHz Mode S demodulator. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #include "readsb.h" #include #ifdef MODEAC_DEBUG #include #endif // 2.4MHz sampling rate version // // When sampling at 2.4MHz we have exactly 6 samples per 5 symbols. // Each symbol is 500ns wide, each sample is 416.7ns wide // // We maintain a phase offset that is expressed in units of 1/5 of a sample i.e. 1/6 of a symbol, 83.333ns // Each symbol we process advances the phase offset by 6 i.e. 6/5 of a sample, 500ns // // The correlation functions below correlate a 1-0 pair of symbols (i.e. manchester encoded 1 bit) // starting at the given sample, and assuming that the symbol starts at a fixed 0-5 phase offset within // m[0]. They return a correlation value, generally interpreted as >0 = 1 bit, <0 = 0 bit // TODO check if there are better (or more balanced) correlation functions to use here // nb: the correlation functions sum to zero, so we do not need to adjust for the DC offset in the input signal // (adding any constant value to all of m[0..3] does not change the result) // Changes 2020 by wiedehopf: // 20 units per sample, 24 units per symbol that are distributed according to phase // 1 bit has 2 symbols, in a bit representing a one the first symbol is high and the second is low // The previous assumption was that symbols beyond our control are zero. // Let's make the assumption that the symbols beyond our control are a statistical mean of 0 and 1. // Such a mean is represented by 12 units per symbol. // As an example for the above let's discuss the first slice function: // Samples 0 and 1 are completely occupied by the bit we are trying to judge thus no outside symbols. // The 3rd sample is 8 units of our bit and 12 units of the following symbol. // Our bit contributes part of a low symbol represented by -8 units // but we also get 12 units of 0.5 resulting in +6 units from the following symbol. // // The above comment is how these changes started out, i'll leave them here as food for thought. // Using --ifile the coefficients from the above thought process were iteratively tweaked by hand. // Note one of the correlation functions is no longer DC balanced (but just slightly) // Further testing on your own samples using --ifile --quiet --stats is welcome // Note you might need to use --throttle unless your using wiedehopf's readsb fork, // otherwise position stats won't work as they rely on realtime differences between // reception of CPRs. // Creating a 5 minute sample with a gain of 43.9: // timeout 300 rtl_sdr -f 1090000000 -s 2400000 -g 43.9 sample.dat // Checking a set of correlation functions using the above sample: // make && ./readsb --device-type ifile --ifile sample.dat --quiet --stats static inline __attribute__((always_inline)) int slice_phase0(uint16_t *m) { return 18 * m[0] - 15 * m[1] - 3 * m[2]; } static inline __attribute__((always_inline)) int slice_phase1(uint16_t *m) { return 14 * m[0] - 5 * m[1] - 9 * m[2]; } // slightly DC unbalanced but better results static inline __attribute__((always_inline)) int slice_phase2(uint16_t *m) { return 16 * m[0] + 5 * m[1] - 20 * m[2]; } static inline __attribute__((always_inline)) int slice_phase3(uint16_t *m) { return 7 * m[0] + 11 * m[1] - 18 * m[2]; } static inline __attribute__((always_inline)) int slice_phase4(uint16_t *m) { return 4 * m[0] + 15 * m[1] - 20 * m[2] + 1 * m[3]; } static uint32_t valid_df_short_bitset; // set of acceptable DF values for short messages static uint32_t valid_df_long_bitset; // set of acceptable DF values for long messages static uint32_t generate_damage_set(uint8_t df, unsigned damage_bits) { uint32_t result = (1 << df); if (!damage_bits) return result; for (unsigned bit = 0; bit < 5; ++bit) { unsigned damaged_df = df ^ (1 << bit); result |= generate_damage_set(damaged_df, damage_bits - 1); } return result; } static void init_bitsets() { // DFs that we directly understand without correction valid_df_short_bitset = (1 << 0) | (1 << 4) | (1 << 5) | (1 << 11); valid_df_long_bitset = (1 << 16) | (1 << 17) | (1 << 18) | (1 << 20) | (1 << 21); #ifdef ENABLE_DF24 if (1) valid_df_long_bitset |= (1 << 24) | (1 << 25) | (1 << 26) | (1 << 27) | (1 << 28) | (1 << 29) | (1 << 30) | (1 << 31); #endif // if we can also repair DF damage, include those corrections if (Modes.fixDF && Modes.nfix_crc) { // only correct for possible DF17, other types are less useful usually (DF11/18 would also be possible) valid_df_long_bitset |= generate_damage_set(17, 1); } } // extract one byte from the mag buffers using slice_phase functions // advance pPtr and phase static inline __attribute__((always_inline)) uint8_t slice_byte(uint16_t **pPtr, int *phase) { uint8_t theByte = 0; switch (*phase) { case 0: theByte = (slice_phase0(*pPtr) > 0 ? 0x80 : 0) | (slice_phase2(*pPtr+2) > 0 ? 0x40 : 0) | (slice_phase4(*pPtr+4) > 0 ? 0x20 : 0) | (slice_phase1(*pPtr+7) > 0 ? 0x10 : 0) | (slice_phase3(*pPtr+9) > 0 ? 0x08 : 0) | (slice_phase0(*pPtr+12) > 0 ? 0x04 : 0) | (slice_phase2(*pPtr+14) > 0 ? 0x02 : 0) | (slice_phase4(*pPtr+16) > 0 ? 0x01 : 0); *phase = 1; *pPtr += 19; break; case 1: theByte = (slice_phase1(*pPtr) > 0 ? 0x80 : 0) | (slice_phase3(*pPtr+2) > 0 ? 0x40 : 0) | (slice_phase0(*pPtr+5) > 0 ? 0x20 : 0) | (slice_phase2(*pPtr+7) > 0 ? 0x10 : 0) | (slice_phase4(*pPtr+9) > 0 ? 0x08 : 0) | (slice_phase1(*pPtr+12) > 0 ? 0x04 : 0) | (slice_phase3(*pPtr+14) > 0 ? 0x02 : 0) | (slice_phase0(*pPtr+17) > 0 ? 0x01 : 0); *phase = 2; *pPtr += 19; break; case 2: theByte = (slice_phase2(*pPtr) > 0 ? 0x80 : 0) | (slice_phase4(*pPtr+2) > 0 ? 0x40 : 0) | (slice_phase1(*pPtr+5) > 0 ? 0x20 : 0) | (slice_phase3(*pPtr+7) > 0 ? 0x10 : 0) | (slice_phase0(*pPtr+10) > 0 ? 0x08 : 0) | (slice_phase2(*pPtr+12) > 0 ? 0x04 : 0) | (slice_phase4(*pPtr+14) > 0 ? 0x02 : 0) | (slice_phase1(*pPtr+17) > 0 ? 0x01 : 0); *phase = 3; *pPtr += 19; break; case 3: theByte = (slice_phase3(*pPtr) > 0 ? 0x80 : 0) | (slice_phase0(*pPtr+3) > 0 ? 0x40 : 0) | (slice_phase2(*pPtr+5) > 0 ? 0x20 : 0) | (slice_phase4(*pPtr+7) > 0 ? 0x10 : 0) | (slice_phase1(*pPtr+10) > 0 ? 0x08 : 0) | (slice_phase3(*pPtr+12) > 0 ? 0x04 : 0) | (slice_phase0(*pPtr+15) > 0 ? 0x02 : 0) | (slice_phase2(*pPtr+17) > 0 ? 0x01 : 0); *phase = 4; *pPtr += 19; break; case 4: theByte = (slice_phase4(*pPtr) > 0 ? 0x80 : 0) | (slice_phase1(*pPtr+3) > 0 ? 0x40 : 0) | (slice_phase3(*pPtr+5) > 0 ? 0x20 : 0) | (slice_phase0(*pPtr+8) > 0 ? 0x10 : 0) | (slice_phase2(*pPtr+10) > 0 ? 0x08 : 0) | (slice_phase4(*pPtr+12) > 0 ? 0x04 : 0) | (slice_phase1(*pPtr+15) > 0 ? 0x02 : 0) | (slice_phase3(*pPtr+17) > 0 ? 0x01 : 0); *phase = 0; *pPtr += 20; break; } return theByte; } static void score_phase(int try_phase, uint16_t *pa, unsigned char **bestmsg, int *bestscore, int *bestphase, unsigned char **msg, unsigned char *msg1, unsigned char *msg2) { Modes.stats_current.demod_preamblePhase[try_phase - 4]++; uint16_t *pPtr; int phase, score, bytelen; pPtr = pa + 19 + (try_phase / 5); phase = try_phase % 5; (*msg)[0] = slice_byte(&pPtr, &phase); // inspect DF field early, only continue processing // messages where the DF appears valid uint32_t df = ((uint8_t) (*msg)[0]) >> 3; if (valid_df_long_bitset & (1 << df)) { bytelen = MODES_LONG_MSG_BYTES; } else if (valid_df_short_bitset & (1 << df)) { bytelen = MODES_SHORT_MSG_BYTES; } else { score = -2; if (score > *bestscore) { // this is only for preamble stats *bestscore = score; } return; } for (int i = 1; i < bytelen; ++i) { (*msg)[i] = slice_byte(&pPtr, &phase); } // Score the mode S message and see if it's any good. score = scoreModesMessage(*msg, bytelen * 8); if (score > *bestscore) { // new high score! *bestmsg = *msg; *bestscore = score; *bestphase = try_phase; // swap to using the other buffer so we don't clobber our demodulated data // (if we find a better result then we'll swap back, but that's OK because // we no longer need this copy if we found a better one) *msg = (*msg == msg1) ? msg2 : msg1; } } // // Given 'mlen' magnitude samples in 'm', sampled at 2.4MHz, // try to demodulate some Mode S messages. // void demodulate2400(struct mag_buf *mag) { unsigned char msg1[MODES_LONG_MSG_BYTES], msg2[MODES_LONG_MSG_BYTES], *msg; unsigned char *bestmsg = NULL; int bestscore; int bestphase = 0; uint16_t *m = mag->data; uint32_t mlen = mag->length; uint64_t sum_scaled_signal_power = 0; // initialize bitsets on first call if (!valid_df_short_bitset) init_bitsets(); msg = msg1; // advance ifile artificial clock even if we don't receive anything if (Modes.sdr_type == SDR_IFILE && Modes.synthetic_now) { Modes.synthetic_now = mag->sysTimestamp; } uint16_t *pa = m; uint16_t *stop = m + mlen; uint16_t *statsProgress = m; const uint32_t statsWindow = MODES_SHORT_MSG_SAMPLES / 2; // half a short message uint32_t loudEvents = 0; uint32_t noiseLowSamples = 0; uint32_t noiseHighSamples = 0; const uint32_t loudThreshold = Modes.loudThreshold * Modes.loudThreshold * statsWindow; const uint32_t noiseLowThreshold = Modes.noiseLowThreshold * Modes.noiseLowThreshold * statsWindow; const uint32_t noiseHighThreshold = Modes.noiseHighThreshold * Modes.noiseHighThreshold * statsWindow; for (; pa < stop; pa++) { int32_t pa_mag, base_noise, ref_level; int msglen; // Look for a message starting at around sample 0 with phase offset 3..7 // Ideal sample values for preambles with different phase // Xn is the first data symbol with phase offset N // // sample#: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 // phase 3: 2/4\0/5\1 0 0 0 0/5\1/3 3\0 0 0 0 0 0 X4 // phase 4: 1/5\0/4\2 0 0 0 0/4\2 2/4\0 0 0 0 0 0 0 X0 // phase 5: 0/5\1/3 3\0 0 0 0/3 3\1/5\0 0 0 0 0 0 0 X1 // phase 6: 0/4\2 2/4\0 0 0 0 2/4\0/5\1 0 0 0 0 0 0 X2 // phase 7: 0/3 3\1/5\0 0 0 0 1/5\0/4\2 0 0 0 0 0 0 X3 // do a pre-check to reduce CPU usage // some silly unrolling that cuts CPU cycles // due to plenty room in the message buffer for decoding // we can with pa go beyond stop without a buffer overrun ... if (Modes.autoGain && pa >= statsProgress) { uint32_t magSum = 0; for (uint32_t i = 0; i < statsWindow; i++) { magSum += pa[i]; } loudEvents += (magSum > loudThreshold); noiseLowSamples += statsWindow * (magSum < noiseLowThreshold); noiseHighSamples += statsWindow * (magSum < noiseHighThreshold); statsProgress = pa + statsWindow; } if (pa[1] > pa[7] && pa[12] > pa[14] && pa[12] > pa[15]) { goto after_pre; } pa++; if (pa[1] > pa[7] && pa[12] > pa[14] && pa[12] > pa[15]) { goto after_pre; } pa++; if (pa[1] > pa[7] && pa[12] > pa[14] && pa[12] > pa[15]) { goto after_pre; } pa++; if (pa[1] > pa[7] && pa[12] > pa[14] && pa[12] > pa[15]) { goto after_pre; } pa++; if (pa[1] > pa[7] && pa[12] > pa[14] && pa[12] > pa[15]) { goto after_pre; } pa++; if (pa[1] > pa[7] && pa[12] > pa[14] && pa[12] > pa[15]) { goto after_pre; } pa++; if (pa[1] > pa[7] && pa[12] > pa[14] && pa[12] > pa[15]) { goto after_pre; } pa++; if (pa[1] > pa[7] && pa[12] > pa[14] && pa[12] > pa[15]) { goto after_pre; } pa++; if (pa[1] > pa[7] && pa[12] > pa[14] && pa[12] > pa[15]) { goto after_pre; } pa++; if (pa[1] > pa[7] && pa[12] > pa[14] && pa[12] > pa[15]) { goto after_pre; } continue; after_pre: // ... but we must NOT decode if have ran past stop if (!(pa < stop)) continue; // 5 noise samples base_noise = pa[5] + pa[8] + pa[16] + pa[17] + pa[18]; // pa_mag is the sum of the 4 preamble high bits // minus 2 low bits between each of high bit pairs // reduce number of preamble detections if we recently dropped samples if (Modes.stats_15min.samples_dropped) ref_level = base_noise * imax(PREAMBLE_THRESHOLD_PIZERO, Modes.preambleThreshold); else ref_level = base_noise * Modes.preambleThreshold; ref_level >>= 5; // divide by 32 bestscore = -42; int32_t diff_2_3 = pa[2] - pa[3]; int32_t sum_1_4 = pa[1] + pa[4]; int32_t diff_10_11 = pa[10] - pa[11]; int32_t common3456 = sum_1_4 - diff_2_3 + pa[9] + pa[12]; // sample#: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 // phase 3: 2/4\0/5\1 0 0 0 0/5\1/3 3\0 0 0 0 0 0 X4 // phase 4: 1/5\0/4\2 0 0 0 0/4\2 2/4\0 0 0 0 0 0 0 X0 pa_mag = common3456 - diff_10_11; if (pa_mag >= ref_level) { // peaks at 1,3,9,11-12: phase 3 score_phase(4, pa, &bestmsg, &bestscore, &bestphase, &msg, msg1, msg2); // peaks at 1,3,9,12: phase 4 score_phase(5, pa, &bestmsg, &bestscore, &bestphase, &msg, msg1, msg2); } // sample#: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 // phase 5: 0/5\1/3 3\0 0 0 0/3 3\1/5\0 0 0 0 0 0 0 X1 // phase 6: 0/4\2 2/4\0 0 0 0 2/4\0/5\1 0 0 0 0 0 0 X2 pa_mag = common3456 + diff_10_11; if (pa_mag >= ref_level) { // peaks at 1,3-4,9-10,12: phase 5 score_phase(6, pa, &bestmsg, &bestscore, &bestphase, &msg, msg1, msg2); // peaks at 1,4,10,12: phase 6 score_phase(7, pa, &bestmsg, &bestscore, &bestphase, &msg, msg1, msg2); } // peaks at 1-2,4,10,12: phase 7 // sample#: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 // phase 7: 0/3 3\1/5\0 0 0 0 1/5\0/4\2 0 0 0 0 0 0 X3 pa_mag = sum_1_4 + 2 * diff_2_3 + diff_10_11 + pa[12]; if (pa_mag >= ref_level) score_phase(8, pa, &bestmsg, &bestscore, &bestphase, &msg, msg1, msg2); // no preamble detected if (bestscore == -42) continue; // we had at least one phase greater than the preamble threshold // and used scoremodesmessage on those bytes Modes.stats_current.demod_preambles++; // Do we have a candidate? if (bestscore < 0) { if (bestscore == -1) Modes.stats_current.demod_rejected_unknown_icao++; else Modes.stats_current.demod_rejected_bad++; continue; // nope. } msglen = modesMessageLenByType(getbits(bestmsg, 1, 5)); struct modesMessage *mm = netGetMM(&Modes.netMessageBuffer[0]); // For consistency with how the Beast / Radarcape does it, // we report the timestamp at the end of bit 56 (even if // the frame is a 112-bit frame) mm->timestamp = mag->sampleTimestamp + (pa -m) * 5 + (8 + 56) * 12 + bestphase; // compute message receive time as block-start-time + difference in the 12MHz clock mm->sysTimestamp = mag->sysTimestamp + receiveclock_ms_elapsed(mag->sampleTimestamp, mm->timestamp); // advance ifile artifical clock for every message received if (Modes.sdr_type == SDR_IFILE && Modes.synthetic_now) { Modes.synthetic_now = mm->sysTimestamp; } mm->score = bestscore; // Decode the received message { memcpy(mm->msg, bestmsg, MODES_LONG_MSG_BYTES); int result = decodeModesMessage(mm); if (result < 0) { if (result == -1) Modes.stats_current.demod_rejected_unknown_icao++; else Modes.stats_current.demod_rejected_bad++; continue; } else { Modes.stats_current.demod_accepted[mm->correctedbits]++; } } Modes.stats_current.demod_bestPhase[bestphase - 4]++; // measure signal power { double signal_power; uint64_t scaled_signal_power = 0; int signal_len = msglen * 12 / 5; int k; for (k = 0; k < signal_len; ++k) { uint32_t mag = pa[19 + k]; scaled_signal_power += mag * mag; } signal_power = scaled_signal_power / 65535.0 / 65535.0; mm->signalLevel = signal_power / signal_len; Modes.stats_current.signal_power_sum += signal_power; Modes.stats_current.signal_power_count += signal_len; sum_scaled_signal_power += scaled_signal_power; if (mm->signalLevel > Modes.stats_current.peak_signal_power) Modes.stats_current.peak_signal_power = mm->signalLevel; if (mm->signalLevel > 0.50119) Modes.stats_current.strong_signal_count++; // signal power above -3dBFS } // Skip over the message: // (we actually skip to 8 bits before the end of the message, // because we can often decode two messages that *almost* collide, // where the preamble of the second message clobbered the last // few bits of the first message, but the message bits didn't // overlap) //pa += msglen * 12 / 5; // // let's test something, only jump part of the message and let the preamble detection handle the rest. pa += msglen * 8 / 4; // Pass data to the next layer netUseMessage(mm); } mag->loudEvents = loudEvents; mag->noiseLowSamples = noiseLowSamples; mag->noiseHighSamples = noiseHighSamples; /* update noise power */ { double sum_signal_power = sum_scaled_signal_power / 65535.0 / 65535.0; Modes.stats_current.noise_power_sum += (mag->mean_power * mag->length - sum_signal_power); Modes.stats_current.noise_power_count += mag->length; } netDrainMessageBuffers(); } #ifdef MODEAC_DEBUG static int yscale(unsigned signal) { return (int) (299 - 299.0 * signal / 65536.0); } static void draw_modeac(uint16_t *m, unsigned modeac, unsigned f1_clock, unsigned noise_threshold, unsigned signal_threshold, unsigned bits, unsigned noisy_bits, unsigned uncertain_bits) { // 25 bits at 87*60MHz // use 1 pixel = 30MHz = 1087 pixels gdImagePtr im = gdImageCreate(1088, 300); int red = gdImageColorAllocate(im, 255, 0, 0); int brightgreen = gdImageColorAllocate(im, 0, 255, 0); int darkgreen = gdImageColorAllocate(im, 0, 180, 0); int blue = gdImageColorAllocate(im, 0, 0, 255); int grey = gdImageColorAllocate(im, 200, 200, 200); int white = gdImageColorAllocate(im, 255, 255, 255); int black = gdImageColorAllocate(im, 0, 0, 0); gdImageFilledRectangle(im, 0, 0, 1087, 299, white); // draw samples for (unsigned pixel = 0; pixel < 1088; ++pixel) { int clock_offset = (pixel - 150) * 2; int bit = clock_offset / 87; int sample = (f1_clock + clock_offset) / 25; int bitoffset = clock_offset % 87; int color; if (sample < 0) continue; if (clock_offset < 0 || bit >= 20) { color = grey; } else if (bitoffset < 27 && (uncertain_bits & (1 << (19 - bit)))) { color = red; } else if (bitoffset >= 27 && (noisy_bits & (1 << (19 - bit)))) { color = red; } else if (bitoffset >= 27) { color = grey; } else if (bits & (1 << (19 - bit))) { color = brightgreen; } else { color = darkgreen; } gdImageLine(im, pixel, 299, pixel, yscale(m[sample]), color); } // draw bit boundaries for (unsigned bit = 0; bit < 20; ++bit) { unsigned clock = 87 * bit; unsigned pixel0 = clock / 2 + 150; unsigned pixel1 = (clock + 27) / 2 + 150; gdImageLine(im, pixel0, 0, pixel0, 299, (bit == 0 || bit == 14) ? black : grey); gdImageLine(im, pixel1, 0, pixel1, 299, (bit == 0 || bit == 14) ? black : grey); } // draw thresholds gdImageLine(im, 0, yscale(noise_threshold), 1087, yscale(noise_threshold), blue); gdImageLine(im, 0, yscale(signal_threshold), 1087, yscale(signal_threshold), blue); // save it static int file_counter; char filename[PATH_MAX]; sprintf(filename, "modeac_%04X_%04d.png", modeac, ++file_counter); fprintf(stderr, "writing %s\n", filename); FILE *pngout = fopen(filename, "wb"); gdImagePng(im, pngout); fclose(pngout); gdImageDestroy(im); } #endif ////////// ////////// MODE A/C ////////// // Mode A/C bits are 1.45us wide, consisting of 0.45us on and 1.0us off // We track this in terms of a (virtual) 60MHz clock, which is the lowest common multiple // of the bit frequency and the 2.4MHz sampling frequency // // 0.45us = 27 cycles } // 1.00us = 60 cycles } one bit period = 1.45us = 87 cycles // // one 2.4MHz sample = 25 cycles void demodulate2400AC(struct mag_buf *mag) { uint16_t *m = mag->data; uint32_t mlen = mag->length; unsigned f1_sample; double noise_stddev = sqrt(mag->mean_power - mag->mean_level * mag->mean_level); // Var(X) = E[(X-E[X])^2] = E[X^2] - (E[X])^2 unsigned noise_level = (unsigned) ((mag->mean_power + noise_stddev) * 65535 + 0.5); for (f1_sample = 1; f1_sample < mlen; ++f1_sample) { // Mode A/C messages should match this bit sequence: // bit # value // -1 0 quiet zone // 0 1 framing pulse (F1) // 1 C1 // 2 A1 // 3 C2 // 4 A2 // 5 C4 // 6 A4 // 7 0 quiet zone (X1) // 8 B1 // 9 D1 // 10 B2 // 11 D2 // 12 B4 // 13 D4 // 14 1 framing pulse (F2) // 15 0 quiet zone (X2) // 16 0 quiet zone (X3) // 17 SPI // 18 0 quiet zone (X4) // 19 0 quiet zone (X5) // Look for a F1 and F2 pair, // with F1 starting at offset f1_sample. // the first framing pulse covers 3.5 samples: // // |----| |----| // | F1 |________| C1 |_ // // | 0 | 1 | 2 | 3 | 4 | // // and there is some unknown phase offset of the // leading edge e.g.: // // |----| |----| // __| F1 |________| C1 |_ // // | 0 | 1 | 2 | 3 | 4 | // // in theory the "on" period can straddle 3 samples // but it's not a big deal as at most 4% of the power // is in the third sample. if (!(m[f1_sample - 1] < m[f1_sample + 0])) continue; // not a rising edge if (m[f1_sample + 2] > m[f1_sample + 0] || m[f1_sample + 2] > m[f1_sample + 1]) continue; // quiet part of bit wasn't sufficiently quiet unsigned f1_level = (m[f1_sample + 0] + m[f1_sample + 1]) / 2; if (noise_level * 2 > f1_level) { // require 6dB above noise continue; } // estimate initial clock phase based on the amount of power // that ended up in the second sample float f1a_power = (float) m[f1_sample] * m[f1_sample]; float f1b_power = (float) m[f1_sample + 1] * m[f1_sample + 1]; float fraction = f1b_power / (f1a_power + f1b_power); unsigned f1_clock = (unsigned) (25 * (f1_sample + fraction * fraction) + 0.5); // same again for F2 // F2 is 20.3us / 14 bit periods after F1 unsigned f2_clock = f1_clock + (87 * 14); unsigned f2_sample = f2_clock / 25; assert(f2_sample < mlen + Modes.trailing_samples); if (!(m[f2_sample - 1] < m[f2_sample + 0])) continue; if (m[f2_sample + 2] > m[f2_sample + 0] || m[f2_sample + 2] > m[f2_sample + 1]) continue; // quiet part of bit wasn't sufficiently quiet unsigned f2_level = (m[f2_sample + 0] + m[f2_sample + 1]) / 2; if (noise_level * 2 > f2_level) { // require 6dB above noise continue; } unsigned f1f2_level = (f1_level > f2_level ? f1_level : f2_level); float midpoint = sqrtf(noise_level * f1f2_level); // geometric mean of the two levels unsigned signal_threshold = (unsigned) (midpoint * M_SQRT2 + 0.5); // +3dB unsigned noise_threshold = (unsigned) (midpoint / M_SQRT2 + 0.5); // -3dB // Looks like a real signal. Demodulate all the bits. unsigned uncertain_bits = 0; unsigned noisy_bits = 0; unsigned bits = 0; unsigned bit; unsigned clock; for (bit = 0, clock = f1_clock; bit < 20; ++bit, clock += 87) { unsigned sample = clock / 25; bits <<= 1; noisy_bits <<= 1; uncertain_bits <<= 1; // check for excessive noise in the quiet period if (m[sample + 2] >= signal_threshold) { noisy_bits |= 1; } // decide if this bit is on or off if (m[sample + 0] >= signal_threshold || m[sample + 1] >= signal_threshold) { bits |= 1; } else if (m[sample + 0] > noise_threshold && m[sample + 1] > noise_threshold) { /* not certain about this bit */ uncertain_bits |= 1; } else { /* this bit is off */ } } // framing bits must be on if ((bits & 0x80020) != 0x80020) { continue; } // quiet bits must be off if ((bits & 0x0101B) != 0) { continue; } if (noisy_bits || uncertain_bits) { continue; } // Convert to the form that we use elsewhere: // 00 A4 A2 A1 00 B4 B2 B1 SPI C4 C2 C1 00 D4 D2 D1 unsigned modeac = ((bits & 0x40000) ? 0x0010 : 0) | // C1 ((bits & 0x20000) ? 0x1000 : 0) | // A1 ((bits & 0x10000) ? 0x0020 : 0) | // C2 ((bits & 0x08000) ? 0x2000 : 0) | // A2 ((bits & 0x04000) ? 0x0040 : 0) | // C4 ((bits & 0x02000) ? 0x4000 : 0) | // A4 ((bits & 0x00800) ? 0x0100 : 0) | // B1 ((bits & 0x00400) ? 0x0001 : 0) | // D1 ((bits & 0x00200) ? 0x0200 : 0) | // B2 ((bits & 0x00100) ? 0x0002 : 0) | // D2 ((bits & 0x00080) ? 0x0400 : 0) | // B4 ((bits & 0x00040) ? 0x0004 : 0) | // D4 ((bits & 0x00004) ? 0x0080 : 0); // SPI #ifdef MODEAC_DEBUG draw_modeac(m, modeac, f1_clock, noise_threshold, signal_threshold, bits, noisy_bits, uncertain_bits); #endif // This message looks good, submit it struct modesMessage *mm = netGetMM(&Modes.netMessageBuffer[0]); // For consistency with how the Beast / Radarcape does it, // we report the timestamp at the second framing pulse (F2) mm->timestamp = mag->sampleTimestamp + f2_clock / 5; // 60MHz -> 12MHz // compute message receive time as block-start-time + difference in the 12MHz clock mm->sysTimestamp = mag->sysTimestamp + receiveclock_ms_elapsed(mag->sampleTimestamp, mm->timestamp); decodeModeAMessage(mm, modeac); // Pass data to the next layer netUseMessage(mm); f1_sample += (20 * 87 / 25); Modes.stats_current.demod_modeac++; } netDrainMessageBuffers(); } readsb-3.16/demod_2400.h000066400000000000000000000025021505057307600146200ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // demod_2400.h: 2.4MHz Mode S demodulator prototypes. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #ifndef DUMP1090_DEMOD_2400_H #define DUMP1090_DEMOD_2400_H #include #define PREAMBLE_THRESHOLD_MIN 40 #define PREAMBLE_THRESHOLD_HOT 42 #ifndef PREAMBLE_THRESHOLD_DEFAULT #define PREAMBLE_THRESHOLD_DEFAULT 58 #endif #define PREAMBLE_THRESHOLD_PIZERO 75 #define PREAMBLE_THRESHOLD_MAX 400 struct mag_buf; void demodulate2400 (struct mag_buf *mag); void demodulate2400AC (struct mag_buf *mag); #endif readsb-3.16/fasthash.c000066400000000000000000000054511505057307600146650ustar00rootroot00000000000000/* The MIT License Copyright (C) 2012 Zilong Tan (eric.zltan@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "fasthash.h" #undef FT #define FT #ifdef __has_attribute #if __has_attribute (fallthrough) /* not portable */ #undef FT #define FT __attribute__ ((fallthrough)); #endif #endif // Compression function for Merkle-Damgard construction. // This function is generated using the framework provided. #define mix(h) ({ \ (h) ^= (h) >> 23; \ (h) *= 0x2127599bf4325c37ULL; \ (h) ^= (h) >> 47; }) uint64_t fasthash64(const void *buf, size_t len, uint64_t seed) { const uint64_t m = 0x880355f21e6d1965ULL; const uint64_t *pos = (const uint64_t *)buf; const uint64_t *end = pos + (len / 8); const unsigned char *pos2; uint64_t h = seed ^ (len * m); uint64_t v; while (pos != end) { v = *pos++; h ^= mix(v); h *= m; } pos2 = (const unsigned char*)pos; v = 0; switch (len & 7) { case 7: v ^= (uint64_t)pos2[6] << 48; FT case 6: v ^= (uint64_t)pos2[5] << 40; FT case 5: v ^= (uint64_t)pos2[4] << 32; FT case 4: v ^= (uint64_t)pos2[3] << 24; FT case 3: v ^= (uint64_t)pos2[2] << 16; FT case 2: v ^= (uint64_t)pos2[1] << 8; FT case 1: v ^= (uint64_t)pos2[0]; h ^= mix(v); h *= m; } return mix(h); } uint32_t fasthash32(const void *buf, size_t len, uint32_t seed) { // the following trick converts the 64-bit hashcode to Fermat // residue, which shall retain information from both the higher // and lower parts of hashcode. uint64_t h = fasthash64(buf, len, seed); return h - (h >> 32); } readsb-3.16/fasthash.h000066400000000000000000000035471505057307600146760ustar00rootroot00000000000000/* The MIT License Copyright (C) 2012 Zilong Tan (eric.zltan@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef _FASTHASH_H #define _FASTHASH_H #include #include #define mix_fasthash(h) ({ \ (h) ^= (h) >> 23; \ (h) *= 0x2127599bf4325c37ULL; \ (h) ^= (h) >> 47; }) #ifdef __cplusplus extern "C" { #endif /** * fasthash32 - 32-bit implementation of fasthash * @buf: data buffer * @len: data size * @seed: the seed */ uint32_t fasthash32(const void *buf, size_t len, uint32_t seed); /** * fasthash64 - 64-bit implementation of fasthash * @buf: data buffer * @len: data size * @seed: the seed */ uint64_t fasthash64(const void *buf, size_t len, uint64_t seed); #ifdef __cplusplus } #endif #endif readsb-3.16/feature_test.c000066400000000000000000000000001505057307600155370ustar00rootroot00000000000000readsb-3.16/feature_test.h000066400000000000000000000000571505057307600155600ustar00rootroot00000000000000struct test { int test1; int test2; }; readsb-3.16/flamegraph.md000066400000000000000000000006071505057307600153460ustar00rootroot00000000000000 cat > perf.sh << "EOF" #!/bin/bash perf record -F 99 --call-graph dwarf --pid $(pgrep -w -d ',' 'readsb') EOF { timeout 120 ./perf.sh; }; sleep 2; perf script | ./stackcollapse-perf.pl --kernel | ./flamegraph.pl --width 1600 --bgcolors grey --cp > /opt/html/readsb.svg; echo; echo done https://talawah.io/blog/extreme-http-performance-tuning-one-point-two-million/#flame-graph-generation readsb-3.16/gdb.sh000077500000000000000000000011211505057307600140010ustar00rootroot00000000000000#!/bin/bash instance=readsb systemctl stop ${instance} #rm -rf /run/${instance} mkdir -p /run/${instance} chown readsb /run/${instance} source /etc/default/${instance} runuser -u readsb -- gdb -batch -ex 'set confirm off' -ex 'handle SIGTERM nostop print pass' -ex 'handle SIGINT nostop print pass' -ex run -ex 'bt full' --args /usr/bin/readsb --quiet $RECEIVER_OPTIONS $DECODER_OPTIONS $NET_OPTIONS $JSON_OPTIONS --write-json /run/${instance} $@ #/usr/bin/sudo -u readsb /usr/bin/readsb --quiet $RECEIVER_OPTIONS $DECODER_OPTIONS $NET_OPTIONS $JSON_OPTIONS --write-json /run/${instance} $@ readsb-3.16/geomag.c000066400000000000000000000660701505057307600143270ustar00rootroot00000000000000 // March 2020: Made to work as a small and easy to use library for GNU/Linux C programs by M. Wirth // Original notes provided below /* PROGRAM MAGPOINT (GEOMAG DRIVER) */ /************************************************************************ Contact Information Software and Model Support National Geophysical Data Center NOAA EGC/2 325 Broadway Boulder, CO 80303 USA Attn: Manoj Nair or Stefan Maus Phone: (303) 497-4642 or -6522 Email: Manoj.C.Nair@Noaa.gov or Stefan.Maus@noaa.gov Web: http://www.ngdc.noaa.gov/geomag/WMM/ Sponsoring Government Agency National Geospatial-Intelligence Agency PRG / CSAT, M.S. L-41 3838 Vogel Road Arnold, MO 63010 Attn: Craig Rollins Phone: (314) 263-4186 Email: Craig.M.Rollins@Nga.Mil Original Program By: Dr. John Quinn FLEET PRODUCTS DIVISION, CODE N342 NAVAL OCEANOGRAPHIC OFFICE (NAVOCEANO) STENNIS SPACE CENTER (SSC), MS 39522-5001 3/25/05 Version 2.0 Stefan Maus corrected 2 bugs: - use %c instead of %s for character read - help text: positive inclination is downward 1/29/2010 Version 3.0 Manoj Nair Converted floating variables from single precision to double Changed : height above AMSL (WGS84) to Height above WGS84 Ellipsoid Removed the NaN forcing at the geographic poles A new function "my_isnan" for improved portablility */ // Copyright Notice // // As required by 17 U.S.C. 403, third parties producing copyrighted works // consisting predominantly of the material produced by U.S. government agencies // must provide notice with such work(s) identifying the U.S. Government material // incorporated and stating that such material is not subject to copyright // protection within the United States. The information on government web pages // is in the public domain and not subject to copyright protection within the // United States unless specifically annotated otherwise (copyright may be held // elsewhere). Foreign copyrights may apply. #include #include #include #include #define _UNUSED(V) ((void) V) #define NaN log(-1.0) static char **wmm_lines; static char *wmm_string; static int wmm_index; static int maxdeg; static double epochlowlim,epochuplim,epoch; char decd[7], dipd[7],modl[20]; static char* goodbye = "\n -- End of WMM Point Calculation Program -- \n\n"; static int my_isnan(double d) { return (d != d); /* IEEE: only NaN is not equal to itself */ } static int geomag_E0_init(int *maxdeg); static char geomag_introduction(double epochlowlim); int geomag_destroy() { return 0; } int geomag_init() { // hard code WMM2020, this can easily enough be replaced // i don't like having to read a file, better to have the data in the source code. // when replacing this, make sure to add \n\ at the end of each line to the text from the WMM.COF // also make sure the indentation stays as it is ... some code depends on that. wmm_string = strdup("\ 2025.0 WMM-2025 11/13/2024\n\ 1 0 -29351.8 0.0 12.0 0.0\n\ 1 1 -1410.8 4545.4 9.7 -21.5\n\ 2 0 -2556.6 0.0 -11.6 0.0\n\ 2 1 2951.1 -3133.6 -5.2 -27.7\n\ 2 2 1649.3 -815.1 -8.0 -12.1\n\ 3 0 1361.0 0.0 -1.3 0.0\n\ 3 1 -2404.1 -56.6 -4.2 4.0\n\ 3 2 1243.8 237.5 0.4 -0.3\n\ 3 3 453.6 -549.5 -15.6 -4.1\n\ 4 0 895.0 0.0 -1.6 0.0\n\ 4 1 799.5 278.6 -2.4 -1.1\n\ 4 2 55.7 -133.9 -6.0 4.1\n\ 4 3 -281.1 212.0 5.6 1.6\n\ 4 4 12.1 -375.6 -7.0 -4.4\n\ 5 0 -233.2 0.0 0.6 0.0\n\ 5 1 368.9 45.4 1.4 -0.5\n\ 5 2 187.2 220.2 0.0 2.2\n\ 5 3 -138.7 -122.9 0.6 0.4\n\ 5 4 -142.0 43.0 2.2 1.7\n\ 5 5 20.9 106.1 0.9 1.9\n\ 6 0 64.4 0.0 -0.2 0.0\n\ 6 1 63.8 -18.4 -0.4 0.3\n\ 6 2 76.9 16.8 0.9 -1.6\n\ 6 3 -115.7 48.8 1.2 -0.4\n\ 6 4 -40.9 -59.8 -0.9 0.9\n\ 6 5 14.9 10.9 0.3 0.7\n\ 6 6 -60.7 72.7 0.9 0.9\n\ 7 0 79.5 0.0 -0.0 0.0\n\ 7 1 -77.0 -48.9 -0.1 0.6\n\ 7 2 -8.8 -14.4 -0.1 0.5\n\ 7 3 59.3 -1.0 0.5 -0.8\n\ 7 4 15.8 23.4 -0.1 0.0\n\ 7 5 2.5 -7.4 -0.8 -1.0\n\ 7 6 -11.1 -25.1 -0.8 0.6\n\ 7 7 14.2 -2.3 0.8 -0.2\n\ 8 0 23.2 0.0 -0.1 0.0\n\ 8 1 10.8 7.1 0.2 -0.2\n\ 8 2 -17.5 -12.6 0.0 0.5\n\ 8 3 2.0 11.4 0.5 -0.4\n\ 8 4 -21.7 -9.7 -0.1 0.4\n\ 8 5 16.9 12.7 0.3 -0.5\n\ 8 6 15.0 0.7 0.2 -0.6\n\ 8 7 -16.8 -5.2 -0.0 0.3\n\ 8 8 0.9 3.9 0.2 0.2\n\ 9 0 4.6 0.0 -0.0 0.0\n\ 9 1 7.8 -24.8 -0.1 -0.3\n\ 9 2 3.0 12.2 0.1 0.3\n\ 9 3 -0.2 8.3 0.3 -0.3\n\ 9 4 -2.5 -3.3 -0.3 0.3\n\ 9 5 -13.1 -5.2 0.0 0.2\n\ 9 6 2.4 7.2 0.3 -0.1\n\ 9 7 8.6 -0.6 -0.1 -0.2\n\ 9 8 -8.7 0.8 0.1 0.4\n\ 9 9 -12.9 10.0 -0.1 0.1\n\ 10 0 -1.3 0.0 0.1 0.0\n\ 10 1 -6.4 3.3 0.0 0.0\n\ 10 2 0.2 0.0 0.1 -0.0\n\ 10 3 2.0 2.4 0.1 -0.2\n\ 10 4 -1.0 5.3 -0.0 0.1\n\ 10 5 -0.6 -9.1 -0.3 -0.1\n\ 10 6 -0.9 0.4 0.0 0.1\n\ 10 7 1.5 -4.2 -0.1 0.0\n\ 10 8 0.9 -3.8 -0.1 -0.1\n\ 10 9 -2.7 0.9 -0.0 0.2\n\ 10 10 -3.9 -9.1 -0.0 -0.0\n\ 11 0 2.9 0.0 0.0 0.0\n\ 11 1 -1.5 0.0 -0.0 -0.0\n\ 11 2 -2.5 2.9 0.0 0.1\n\ 11 3 2.4 -0.6 0.0 -0.0\n\ 11 4 -0.6 0.2 0.0 0.1\n\ 11 5 -0.1 0.5 -0.1 -0.0\n\ 11 6 -0.6 -0.3 0.0 -0.0\n\ 11 7 -0.1 -1.2 -0.0 0.1\n\ 11 8 1.1 -1.7 -0.1 -0.0\n\ 11 9 -1.0 -2.9 -0.1 0.0\n\ 11 10 -0.2 -1.8 -0.1 0.0\n\ 11 11 2.6 -2.3 -0.1 0.0\n\ 12 0 -2.0 0.0 0.0 0.0\n\ 12 1 -0.2 -1.3 0.0 -0.0\n\ 12 2 0.3 0.7 -0.0 0.0\n\ 12 3 1.2 1.0 -0.0 -0.1\n\ 12 4 -1.3 -1.4 -0.0 0.1\n\ 12 5 0.6 -0.0 -0.0 -0.0\n\ 12 6 0.6 0.6 0.1 -0.0\n\ 12 7 0.5 -0.1 -0.0 -0.0\n\ 12 8 -0.1 0.8 0.0 0.0\n\ 12 9 -0.4 0.1 0.0 -0.0\n\ 12 10 -0.2 -1.0 -0.1 -0.0\n\ 12 11 -1.3 0.1 -0.0 0.0\n\ 12 12 -0.7 0.2 -0.1 -0.1\n\ 999999999999999999999999999999999999999999999999\n\ 999999999999999999999999999999999999999999999999\n\ "); wmm_lines = malloc(sizeof(char*) * 256); if (!wmm_lines) { fprintf(stderr, "malloc fail in geomag.c\n"); return -1; } wmm_index = 0; char *saveptr = NULL; wmm_lines[wmm_index] = strtok_r(wmm_string, "\n", &saveptr); while (wmm_lines[wmm_index]) { wmm_index++; wmm_lines[wmm_index] = strtok_r(NULL, "\n", &saveptr); } if (wmm_lines[0] == NULL || sscanf(wmm_lines[0],"%lf%s",&epochlowlim,modl) < 2) { fprintf(stderr, "Invalid header in model wmm_string in geomag.c\n"); return -1; } /* INITIALIZE GEOMAG ROUTINE */ maxdeg = 12; int result = geomag_E0_init(&maxdeg); free(wmm_lines); wmm_lines = NULL; free(wmm_string); wmm_string = NULL; return result; } /*************************************************************************/ static int E0000(int IENTRY, int *maxdeg, double alt, double glat, double glon, double time, double *dec, double *dip, double *ti, double *gv) { static int maxord,i,icomp,n,m,j,D1,D2,D3,D4; static double c[13][13],cd[13][13],tc[13][13],dp[13][13],snorm[169], sp[13],cp[13],fn[13],fm[13],pp[13],k[13][13],pi,dtr,a,b,re, a2,b2,c2,a4,b4,c4,gnm,hnm,dgnm,dhnm,flnmj, dt,rlon,rlat,srlon,srlat,crlon,crlat,srlat2, crlat2,q,q1,q2,ct,st,r2,r,d,ca,sa,aor,ar,br,bt,bp,bpp, par,temp1,temp2,parp,bx,by,bz,bh; static char model[20], c_new[5]; static double *p = snorm; switch(IENTRY){case 0: goto INIT; case 1: goto CALC;} INIT: /* INITIALIZE CONSTANTS */ maxord = *maxdeg; sp[0] = 0.0; cp[0] = *p = pp[0] = 1.0; dp[0][0] = 0.0; a = 6378.137; b = 6356.7523142; re = 6371.2; a2 = a*a; b2 = b*b; c2 = a2-b2; a4 = a2*a2; b4 = b2*b2; c4 = a4 - b4; /* READ WORLD MAGNETIC MODEL SPHERICAL HARMONIC COEFFICIENTS */ c[0][0] = 0.0; cd[0][0] = 0.0; wmm_index = 0; if (wmm_lines[wmm_index] == NULL || sscanf(wmm_lines[wmm_index],"%lf%s",&epoch,model) < 2) { fprintf(stderr, "Invalid header in model wmm_string in geomag.c\n"); return -1; } S3: wmm_index++; if (wmm_lines[wmm_index] == NULL) goto S4; /* CHECK FOR LAST LINE IN FILE */ for (i=0; i<4 && (wmm_lines[wmm_index][i] != '\0'); i++) { c_new[i] = wmm_lines[wmm_index][i]; c_new[i+1] = '\0'; } icomp = strcmp("9999", c_new); if (icomp == 0) goto S4; /* END OF FILE NOT ENCOUNTERED, GET VALUES */ sscanf(wmm_lines[wmm_index], "%d%d%lf%lf%lf%lf",&n,&m,&gnm,&hnm,&dgnm,&dhnm); if (n > maxord) goto S4; if (m > n || m < 0.0) { fprintf(stderr, "%d\n", wmm_index); fprintf(stderr, "Corrupt record in model wmm_string in geomag.c\n"); return -1; } if (m <= n) { c[m][n] = gnm; cd[m][n] = dgnm; if (m != 0) { c[n][m-1] = hnm; cd[n][m-1] = dhnm; } } goto S3; /* CONVERT SCHMIDT NORMALIZED GAUSS COEFFICIENTS TO UNNORMALIZED */ S4: *snorm = 1.0; fm[0] = 0.0; for (n=1; n<=maxord; n++) { *(snorm+n) = *(snorm+n-1)*(double)(2*n-1)/(double)n; j = 2; for (m=0,D1=1,D2=(n-m+D1)/D1; D2>0; D2--,m+=D1) { k[m][n] = (double)(((n-1)*(n-1))-(m*m))/(double)((2*n-1)*(2*n-3)); if (m > 0) { flnmj = (double)((n-m+1)*j)/(double)(n+m); *(snorm+n+m*13) = *(snorm+n+(m-1)*13)*sqrt(flnmj); j = 1; c[n][m-1] = *(snorm+n+m*13)*c[n][m-1]; cd[n][m-1] = *(snorm+n+m*13)*cd[n][m-1]; } c[m][n] = *(snorm+n+m*13)*c[m][n]; cd[m][n] = *(snorm+n+m*13)*cd[m][n]; } fn[n] = (double)(n+1); fm[n] = (double)n; } k[1][1] = 0.0; return 0; /*************************************************************************/ CALC: dt = time - epoch; pi = 3.14159265359; dtr = pi/180.0; rlon = glon*dtr; rlat = glat*dtr; srlon = sin(rlon); srlat = sin(rlat); crlon = cos(rlon); crlat = cos(rlat); srlat2 = srlat*srlat; crlat2 = crlat*crlat; sp[1] = srlon; cp[1] = crlon; /* CONVERT FROM GEODETIC COORDS. TO SPHERICAL COORDS. */ q = sqrt(a2-c2*srlat2); q1 = alt*q; q2 = ((q1+a2)/(q1+b2))*((q1+a2)/(q1+b2)); ct = srlat/sqrt(q2*crlat2+srlat2); st = sqrt(1.0-(ct*ct)); r2 = (alt*alt)+2.0*q1+(a4-c4*srlat2)/(q*q); r = sqrt(r2); d = sqrt(a2*crlat2+b2*srlat2); ca = (alt+d)/r; sa = c2*crlat*srlat/(r*d); for (m=2; m<=maxord; m++) { sp[m] = sp[1]*cp[m-1]+cp[1]*sp[m-1]; cp[m] = cp[1]*cp[m-1]-sp[1]*sp[m-1]; } aor = re/r; ar = aor*aor; br = bt = bp = bpp = 0.0; for (n=1; n<=maxord; n++) { ar = ar*aor; for (m=0,D3=1,D4=(n+m+D3)/D3; D4>0; D4--,m+=D3) { /* COMPUTE UNNORMALIZED ASSOCIATED LEGENDRE POLYNOMIALS AND DERIVATIVES VIA RECURSION RELATIONS */ if (n == m) { *(p+n+m*13) = st**(p+n-1+(m-1)*13); dp[m][n] = st*dp[m-1][n-1]+ct**(p+n-1+(m-1)*13); goto S50; } if (n == 1 && m == 0) { *(p+n+m*13) = ct**(p+n-1+m*13); dp[m][n] = ct*dp[m][n-1]-st**(p+n-1+m*13); goto S50; } if (n > 1 && n != m) { if (m > n-2) *(p+n-2+m*13) = 0.0; if (m > n-2) dp[m][n-2] = 0.0; *(p+n+m*13) = ct**(p+n-1+m*13)-k[m][n]**(p+n-2+m*13); dp[m][n] = ct*dp[m][n-1] - st**(p+n-1+m*13)-k[m][n]*dp[m][n-2]; } S50: /* TIME ADJUST THE GAUSS COEFFICIENTS */ tc[m][n] = c[m][n]+dt*cd[m][n]; if (m != 0) tc[n][m-1] = c[n][m-1]+dt*cd[n][m-1]; /* ACCUMULATE TERMS OF THE SPHERICAL HARMONIC EXPANSIONS */ par = ar**(p+n+m*13); if (m == 0) { temp1 = tc[m][n]*cp[m]; temp2 = tc[m][n]*sp[m]; } else { temp1 = tc[m][n]*cp[m]+tc[n][m-1]*sp[m]; temp2 = tc[m][n]*sp[m]-tc[n][m-1]*cp[m]; } bt = bt-ar*temp1*dp[m][n]; bp += (fm[m]*temp2*par); br += (fn[n]*temp1*par); /* SPECIAL CASE: NORTH/SOUTH GEOGRAPHIC POLES */ if (st == 0.0 && m == 1) { if (n == 1) pp[n] = pp[n-1]; else pp[n] = ct*pp[n-1]-k[m][n]*pp[n-2]; parp = ar*pp[n]; bpp += (fm[m]*temp2*parp); } } } if (st == 0.0) bp = bpp; else bp /= st; /* ROTATE MAGNETIC VECTOR COMPONENTS FROM SPHERICAL TO GEODETIC COORDINATES */ bx = -bt*ca-br*sa; by = bp; bz = bt*sa-br*ca; /* COMPUTE DECLINATION (DEC), INCLINATION (DIP) AND TOTAL INTENSITY (TI) */ bh = sqrt((bx*bx)+(by*by)); *ti = sqrt((bh*bh)+(bz*bz)); *dec = atan2(by,bx)/dtr; *dip = atan2(bz,bh)/dtr; /* COMPUTE MAGNETIC GRID VARIATION IF THE CURRENT GEODETIC POSITION IS IN THE ARCTIC OR ANTARCTIC (I.E. GLAT > +55 DEGREES OR GLAT < -55 DEGREES) OTHERWISE, SET MAGNETIC GRID VARIATION TO -999.0 */ *gv = -999.0; if (fabs(glat) >= 55.) { if (glat > 0.0 && glon >= 0.0) *gv = *dec-glon; if (glat > 0.0 && glon < 0.0) *gv = *dec+fabs(glon); if (glat < 0.0 && glon >= 0.0) *gv = *dec+glon; if (glat < 0.0 && glon < 0.0) *gv = *dec-fabs(glon); if (*gv > +180.0) *gv -= 360.0; if (*gv < -180.0) *gv += 360.0; } return 0; } /*************************************************************************/ static int geomag_E0_init(int *maxdeg) { return E0000(0,maxdeg,0.0,0.0,0.0,0.0,NULL,NULL,NULL,NULL); } /*************************************************************************/ int geomag_calc(double alt, double glat, double glon, double time, double *dec, double *dip, double *ti, double *gv) { return E0000(1,NULL,alt,glat,glon,time,dec,dip,ti,gv); } /*************************************************************************/ static char geomag_introduction(double epochlowlim) { char help; static char ans; int res = 0; _UNUSED(res); printf("\n\n Welcome to the World Magnetic Model (WMM) %4.0lf C-Program\n\n", epochlowlim); printf(" --- Version 3.0, January 2010 ---\n\n"); printf("\n This program estimates the strength and direction of "); printf("\n Earth's main magnetic field for a given point/area."); printf("\n Enter h for help and contact information or c to continue."); printf ("\n >"); res = scanf("%c%*[^\n]",&help); getchar(); if ((help == 'h') || (help == 'H')) { printf("\n Help information "); printf("\n The World Magnetic Model (WMM) for %7.2lf", epochlowlim); printf("\n is a model of Earth's main magnetic field. The WMM"); printf("\n is recomputed every five (5) years, in years divisible by "); printf("\n five (i.e. 2010, 2015). See the contact information below"); printf("\n to obtain more information on the WMM and associated software."); printf("\n "); printf("\n Input required is the location in geodetic latitude and"); printf("\n longitude (positive for northern latitudes and eastern "); printf("\n longitudes), geodetic altitude in meters, and the date of "); printf("\n interest in years."); printf("\n\n\n The program computes the estimated magnetic Declination"); printf("\n (D) which is sometimes called MAGVAR, Inclination (I), Total"); printf("\n Intensity (F or TI), Horizontal Intensity (H or HI), Vertical"); printf("\n Intensity (Z), and Grid Variation (GV). Declination and Grid"); printf("\n Variation are measured in units of degrees and are considered"); printf("\n positive when east or north. Inclination is measured in units"); printf("\n of degrees and is considered positive when pointing down (into"); printf("\n the Earth). The WMM is reference to the WGS-84 ellipsoid and"); printf("\n is valid for 5 years after the base epoch."); printf("\n\n\n It is very important to note that a degree and order 12 model,"); printf("\n such as WMM, describes only the long wavelength spatial magnetic "); printf("\n fluctuations due to Earth's core. Not included in the WMM series"); printf("\n models are intermediate and short wavelength spatial fluctuations "); printf("\n that originate in Earth's mantle and crust. Consequently, isolated"); printf("\n angular errors at various positions on the surface (primarily over"); printf("\n land, incontinental margins and over oceanic seamounts, ridges and"); printf("\n trenches) of several degrees may be expected. Also not included in"); printf("\n the model are temporal fluctuations of magnetospheric and ionospheric"); printf("\n origin. On the days during and immediately following magnetic storms,"); printf("\n temporal fluctuations can cause substantial deviations of the geomagnetic"); printf("\n field from model values. If the required declination accuracy is"); printf("\n more stringent than the WMM series of models provide, the user is"); printf("\n advised to request special (regional or local) surveys be performed"); printf("\n and models prepared. Please make requests of this nature to the"); printf("\n National Geospatial-Intelligence Agency (NGA) at the address below."); printf("\n\n\n Contact Information"); printf("\n Software and Model Support"); printf("\n National Geophysical Data Center"); printf("\n NOAA EGC/2"); printf("\n 325 Broadway"); printf("\n Boulder, CO 80303 USA"); printf("\n Attn: Susan McLean or Stefan Maus"); printf("\n Phone: (303) 497-6478 or -6522"); printf("\n Email: Susan.McLean@noaa.gov or Stefan.Maus@noaa.gov "); printf("\n\n\n Continue with program? (y or n) "); res = scanf("%c%*[^\n]", &ans); getchar(); } else { ans = 'y'; } return(ans); } void geomag_interactive() { int warn_H, warn_H_strong, warn_P; double warn_H_val, warn_H_strong_val; char answer; double x1,x2,y1,y2,z1,z2,h1,h2; double altm, dlat, dlon; double ati, adec, adip; double alt, time, dec, dip, ti, gv; double time1, dec1, dip1, ti1; double dec2, dip2, ti2; double ax,ay,az,ah; double rTd=0.017453292; double epochrange = 5.0; double dmin, imin, ddeg, ideg; int res = 0; _UNUSED(res); char ans = geomag_introduction(epochlowlim); if ((ans == 'y') || (ans == 'Y')) S1: maxdeg = 12; warn_H = 0; warn_H_val = 99999.0; warn_H_strong = 0; warn_H_strong_val = 99999.0; warn_P = 0; printf("\n\n\nENTER LATITUDE IN DECIMAL DEGREES "); printf("\n(North latitude positive, South latitude negative \n"); printf("i.e. 25.5 for 25 degrees 30 minutes north.) \n"); res = scanf("%lf%*[^\n]", &dlat); getchar(); printf("ENTER LONGITUDE IN DECIMAL DEGREES"); printf("(East longitude positive, West negative \n"); printf("i.e.- 100.0 for 100.0 degrees west.)\n"); res = scanf("%lf%*[^\n]", &dlon); getchar(); printf("ENTER ALTITUDE IN KILOMETERS ABOVE WGS84 ELLIPSOID\n"); res = scanf("%lf%*[^\n]", &altm); getchar(); alt = altm; epochuplim = epochlowlim + epochrange; printf("ENTER TIME IN DECIMAL YEAR (%-7.2lf - %-7.2lf)\n",epochlowlim,epochuplim); res = scanf("%lf%*[^\n]",&time); getchar(); double dt = time - epoch; printf("%.1f %.1f\n", time, epoch); if (time < 0.0 || (dt < 0.0 || dt > 5.0)) { printf("\n\n WARNING - TIME EXTENDS BEYOND MODEL 5-YEAR LIFE SPAN"); printf("\n CONTACT NGDC FOR PRODUCT UPDATES:"); printf("\n National Geophysical Data Center"); printf("\n NOAA EGC/2"); printf("\n 325 Broadway"); printf("\n Boulder, CO 80303 USA"); printf("\n Attn: Manoj Nair or Stefan Maus"); printf("\n Phone: (303) 497-4642 or -6522"); printf("\n Email: Manoj.C.Nair@Noaa.Gov"); printf("\n or"); printf("\n Stefan.Maus@noaa.gov"); printf("\n Web: http://www.ngdc.noaa.gov/geomag/WMM/"); printf("\n\n EPOCH = %.3lf",epoch); printf("\n TIME = %.3lf",time); printf("\n Do you wish to continue? (y or n) "); res = scanf("%c%*[^\n]",&answer); getchar(); if ((answer == 'n') || (answer == 'N')) goto MORE; } geomag_calc(alt,dlat,dlon,time,&dec,&dip,&ti,&gv); time1 = time; dec1 = dec; dip1 = dip; ti1 = ti; time = time1 + 1.0; geomag_calc(alt,dlat,dlon,time,&dec,&dip,&ti,&gv); dec2 = dec; dip2 = dip; ti2 = ti; /*COMPUTE X, Y, Z, AND H COMPONENTS OF THE MAGNETIC FIELD*/ x1=ti1*(cos((dec1*rTd))*cos((dip1*rTd))); x2=ti2*(cos((dec2*rTd))*cos((dip2*rTd))); y1=ti1*(cos((dip1*rTd))*sin((dec1*rTd))); y2=ti2*(cos((dip2*rTd))*sin((dec2*rTd))); z1=ti1*(sin((dip1*rTd))); z2=ti2*(sin((dip2*rTd))); h1=ti1*(cos((dip1*rTd))); h2=ti2*(cos((dip2*rTd))); /* COMPUTE ANNUAL CHANGE FOR TOTAL INTENSITY */ ati = ti2 - ti1; /* COMPUTE ANNUAL CHANGE FOR DIP & DEC */ adip = (dip2 - dip1) * 60.; adec = (dec2 - dec1) * 60.; /* COMPUTE ANNUAL CHANGE FOR X, Y, Z, AND H */ ax = x2-x1; ay = y2-y1; az = z2-z1; ah = h2-h1; if (dec1 < 0.0) { strcpy (decd,"(WEST)"); } else { strcpy(decd,"(EAST)"); } if (dip1 < 0.0) { strcpy(dipd,"(UP) "); } else { strcpy(dipd,"(DOWN)"); } /* deal with geographic and magnetic poles */ if (h1 < 100.0) /* at magnetic poles */ { dec1 = NaN; adec = NaN; strcpy(decd,"(VOID)"); /* while rest is ok */ } if (h1 < 1000.0) { warn_H = 0; warn_H_strong = 1; warn_H_strong_val = h1; } else if (h1 < 5000.0 && !warn_H_strong) { warn_H = 1; warn_H_val = h1; } /* convert D and I to deg and min */ if (my_isnan(dec1)) ddeg = dec1; else ddeg=(int)dec1; dmin=(dec1-(double)ddeg)*60; if (dec1 > 0 && dmin >= 59.5) { dmin -= 60.0; ddeg++; } if (dec1 < 0 && dmin <= -59.5) { dmin += 60.0; ddeg--; } if(ddeg!=0) dmin=fabs(dmin); if (my_isnan(dip1)) ideg = dip1; else ideg=(int)dip1; imin=(dip1-(double)ideg)*60; if (dip1 > 0 && imin >= 59.5) { imin -= 60.0; ideg++; } if (dip1 < 0 && imin <= -59.5) { imin += 60.0; ideg--; } if(ideg!=0) imin=fabs(imin); printf("\n Results For \n"); if (dlat < 0) printf("\n LATITUDE: %7.2lfS",-dlat); else printf("\n LATITUDE: %7.2lfN",dlat); if (dlon < 0) printf("\n LONGITUDE: %7.2lfW",-dlon); else printf("\n LONGITUDE: %7.2lfE",dlon); printf("\n ALTITUDE: %8.2lf KM ABOVE WGS84 ELLIPSOID",altm); printf("\n DATE: %6.1lf\n",time1); printf("\n Main Field \t\t\t Secular Change"); printf("\n F = %-9.1lf nT\t\t dF = %-8.1lf nT/yr",ti1,ati); if (my_isnan(h1)) printf("\n H = NaN \t\t dH = NaN"); else printf("\n H = %-9.1lf nT\t\t dH = %-8.1lf nT/yr",h1,ah); if (my_isnan(x1)) printf("\n X = NaN \t\t dX = NaN"); else printf("\n X = %-9.1lf nT\t\t dX = %-8.1lf nT/yr ",x1,ax); if (my_isnan(y1)) printf("\n Y = NaN \t\t dY = NaN"); else printf("\n Y = %-9.1lf nT\t\t dY = %-8.1lf nT/yr ",y1,ay); printf("\n Z = %-9.1lf nT\t\t dZ = %-8.1lf nT/yr ",z1,az); if (my_isnan(dec1)) printf("\n D = NaN \t\t dD = NaN"); else printf("\n D = %4.0lf Deg %3.0lf Min %s\t dD = %-8.1lf Min/yr",ddeg,dmin,decd,adec); printf("\n I = %4.0lf Deg %3.0lf Min %s\t dI = %-8.1lf Min/yr",ideg,imin,dipd,adip); if (warn_H) { printf("\n\nWarning: The horizontal field strength at this location is only %6.1lf nT\n",warn_H_val); printf(" Compass readings have large uncertainties in areas where H is\n"); printf(" smaller than 5000 nT\n"); } if (warn_H_strong) { printf("\n\nWarning: The horizontal field strength at this location is only %6.1lf nT\n",warn_H_strong_val); printf(" Compass readings have VERY LARGE uncertainties in areas where H is\n"); printf(" smaller than 1000 nT\n"); } if (warn_P) { printf("\n\nWarning: Location is at geographic pole where X, Y, and Decl are undefined\n"); } MORE: printf("\n\nDO YOU NEED MORE POINT DATA? (y or n) "); res = scanf("%c%*[^\n]", &answer); getchar(); if ((answer =='y')||(answer == 'Y')) goto S1; else { printf("%s",goodbye); } } readsb-3.16/geomag.h000066400000000000000000000025651505057307600143330ustar00rootroot00000000000000#ifndef GEOMAG_H #define GEOMAG_H // this library is NOT thread safe. // UNSAFE with threading // // // functions with type int: // return value 0: SUCCESS // return value -1: ERROR // on ERROR an error message is printed to stderr // call once to initialize the library int geomag_init(); // call on program exit if you care about freeing malloced memory int geomag_destroy(); // alt: altitude above WGS84 ellipsoid in km // glat: latitude in degrees // glon: longitude in degrees // time: decimal year // // pass variables by reference, results will be written to the variables // (see example.c) // dec: https://en.wikipedia.org/wiki/Magnetic_declination // dip: https://en.wikipedia.org/wiki/Magnetic_dip // ti: Total intensity in nano Tesla nT // from geomag.c: // COMPUTE DECLINATION (DEC), INCLINATION (DIP) AND // TOTAL INTENSITY (TI) // from geomag.c // gv: Grid variation // COMPUTE MAGNETIC GRID VARIATION IF THE CURRENT // GEODETIC POSITION IS IN THE ARCTIC OR ANTARCTIC // (I.E. GLAT > +55 DEGREES OR GLAT < -55 DEGREES) // // OTHERWISE, SET MAGNETIC GRID VARIATION TO -999.0 // // int geomag_calc(double alt, double glat, double glon, double time, double *dec, double *dip, double *ti, double *gv); // more or less the original program this library was adapted from, interactive console input/output of data void geomag_interactive(); #endif readsb-3.16/globe_index.c000066400000000000000000004016261505057307600153470ustar00rootroot00000000000000#include "readsb.h" #define STATE_SAVE_MAGIC (0x7ba09e63757314ceULL) #define STATE_SAVE_MAGIC_END (STATE_SAVE_MAGIC + 1) static const char zstd_magic[] = { 0x28, 0xb5, 0x2f, 0xfd }; static void mark_legs(traceBuffer tb, struct aircraft *a, int start, int recent); static traceBuffer reassembleTrace(struct aircraft *a, int numPoints, int64_t after_timestamp, threadpool_buffer_t *buffer); static void resizeTraceCurrent(struct aircraft *a, int64_t now, int extra, int force); void init_globe_index() { struct tile *s_tiles = Modes.json_globe_special_tiles = cmalloc(GLOBE_SPECIAL_INDEX * sizeof(struct tile)); memset(s_tiles, 0, GLOBE_SPECIAL_INDEX * sizeof(struct tile)); int count = 0; // Arctic s_tiles[count++] = (struct tile) { 60, -126, 90, 0 }; s_tiles[count++] = (struct tile) { 60, 0, 90, 150 }; // Alaska and Chukotka s_tiles[count++] = (struct tile) { 51, 150, 90, -126 }; // North Pacific s_tiles[count++] = (struct tile) { 9, 150, 51, -126 }; // Northern Canada s_tiles[count++] = (struct tile) { 51, -126, 60, -69 }; // Northwest USA s_tiles[count++] = (struct tile) { 45, -120, 51, -114 }; s_tiles[count++] = (struct tile) { 45, -114, 51, -102 }; s_tiles[count++] = (struct tile) { 45, -102, 51, -90 }; // Eastern Canada s_tiles[count++] = (struct tile) { 45, -90, 51, -75 }; s_tiles[count++] = (struct tile) { 45, -75, 51, -69 }; // Balkan s_tiles[count++] = (struct tile) { 42, 12, 48, 18 }; s_tiles[count++] = (struct tile) { 42, 18, 48, 24 }; // Poland s_tiles[count++] = (struct tile) { 48, 18, 54, 24 }; // Sweden s_tiles[count++] = (struct tile) { 54, 12, 60, 24 }; // Denmark s_tiles[count++] = (struct tile) { 54, 3, 60, 12 }; // Northern UK s_tiles[count++] = (struct tile) { 54, -9, 60, 3 }; // Golfo de Vizcaya / Bay of Biscay s_tiles[count++] = (struct tile) { 42, -9, 48, 0 }; // West Russia s_tiles[count++] = (struct tile) { 42, 24, 51, 51 }; s_tiles[count++] = (struct tile) { 51, 24, 60, 51 }; // Central Russia s_tiles[count++] = (struct tile) { 30, 51, 60, 90 }; // East Russia s_tiles[count++] = (struct tile) { 30, 90, 60, 120 }; // Koreas and Japan and some Russia s_tiles[count++] = (struct tile) { 30, 120, 39, 129 }; s_tiles[count++] = (struct tile) { 30, 129, 39, 138 }; s_tiles[count++] = (struct tile) { 30, 138, 39, 150 }; s_tiles[count++] = (struct tile) { 39, 120, 60, 150 }; // Vietnam s_tiles[count++] = (struct tile) { 9, 90, 21, 111 }; // South China s_tiles[count++] = (struct tile) { 21, 90, 30, 111 }; // South China and ICAO special use s_tiles[count++] = (struct tile) { 9, 111, 24, 129 }; s_tiles[count++] = (struct tile) { 24, 111, 30, 120 }; s_tiles[count++] = (struct tile) { 24, 120, 30, 129 }; // mostly pacific south of Japan s_tiles[count++] = (struct tile) { 9, 129, 30, 150 }; // Persian Gulf / Arabian Sea s_tiles[count++] = (struct tile) { 9, 51, 30, 69 }; // India s_tiles[count++] = (struct tile) { 9, 69, 30, 90 }; // South Atlantic / South Africa s_tiles[count++] = (struct tile) { -90, -30, 9, 51 }; //Indian Ocean s_tiles[count++] = (struct tile) { -90, 51, 9, 111 }; // Australia s_tiles[count++] = (struct tile) { -90, 111, -18, 160 }; s_tiles[count++] = (struct tile) { -18, 111, 9, 160 }; // South Pacific and NZ s_tiles[count++] = (struct tile) { -90, 160, -42, -90 }; s_tiles[count++] = (struct tile) { -42, 160, 9, -90 }; // North South America s_tiles[count++] = (struct tile) { -9, -90, 9, -42 }; // South South America // west s_tiles[count++] = (struct tile) { -90, -90, -9, -63 }; // east s_tiles[count++] = (struct tile) { -21, -63, -9, -42 }; s_tiles[count++] = (struct tile) { -90, -63, -21, -42 }; s_tiles[count++] = (struct tile) { -90, -42, 9, -30 }; // Guatemala / Mexico s_tiles[count++] = (struct tile) { 9, -126, 33, -117 }; s_tiles[count++] = (struct tile) { 9, -117, 30, -102 }; // western gulf + east mexico s_tiles[count++] = (struct tile) { 9, -102, 27, -90 }; // Eastern Gulf of Mexico s_tiles[count++] = (struct tile) { 24, -90, 30, -84 }; // south of jamaica s_tiles[count++] = (struct tile) { 9, -90, 18, -69 }; // Cuba / Haiti s_tiles[count++] = (struct tile) { 18, -90, 24, -69 }; // Mediterranean s_tiles[count++] = (struct tile) { 36, 6, 42, 18 }; s_tiles[count++] = (struct tile) { 36, 18, 42, 30 }; // North Africa s_tiles[count++] = (struct tile) { 9, -9, 39, 6 }; s_tiles[count++] = (struct tile) { 9, 6, 36, 30 }; // Middle East s_tiles[count++] = (struct tile) { 9, 30, 42, 51 }; // west of Bermuda s_tiles[count++] = (struct tile) { 24, -75, 39, -69 }; // North Atlantic s_tiles[count++] = (struct tile) { 9, -69, 30, -33 }; s_tiles[count++] = (struct tile) { 30, -69, 60, -33 }; s_tiles[count++] = (struct tile) { 9, -33, 30, -9 }; s_tiles[count++] = (struct tile) { 30, -33, 60, -9 }; Modes.specialTileCount = count; if (count + 1 >= GLOBE_SPECIAL_INDEX) fprintf(stderr, "increase GLOBE_SPECIAL_INDEX please!\n"); Modes.json_globe_indexes = cmalloc(GLOBE_MAX_INDEX * sizeof(int32_t)); memset(Modes.json_globe_indexes, 0, GLOBE_MAX_INDEX * sizeof(int32_t)); Modes.json_globe_indexes_len = 0; for (int i = 0; i <= GLOBE_MAX_INDEX; i++) { if (i == Modes.specialTileCount) i = GLOBE_MIN_INDEX; if (i >= GLOBE_MIN_INDEX) { int index_index = globe_index_index(i); if (index_index != i) { if (index_index >= GLOBE_MIN_INDEX) { fprintf(stderr, "weird globe index: %d\n", i); } continue; } } Modes.json_globe_indexes[Modes.json_globe_indexes_len++] = i; } // testing out of bounds /* for (double lat = -90; lat < 90; lat += 0.5) { for (double lon = -180; lon < 180; lon += 0.5) { globe_index(lat, lon); } } */ } void cleanup_globe_index() { free(Modes.json_globe_indexes); Modes.json_globe_indexes = NULL; free(Modes.json_globe_special_tiles); Modes.json_globe_special_tiles = NULL; } int globe_index(double lat_in, double lon_in) { if (!Modes.json_globe_index) { return -5; } int grid = GLOBE_INDEX_GRID; int lat = grid * ((int) ((lat_in + 90) / grid)) - 90; int lon = grid * ((int) ((lon_in + 180) / grid)) - 180; struct tile *tiles = Modes.json_globe_special_tiles; for (int i = 0; tiles[i].south != 0 || tiles[i].north != 0; i++) { struct tile tile = tiles[i]; if (lat >= tile.south && lat < tile.north) { if (tile.west < tile.east && lon >= tile.west && lon < tile.east) { return i; } if (tile.west > tile.east && (lon >= tile.west || lon < tile.east)) { return i; } } } int i = (lat + 90) / grid; int j = (lon + 180) / grid; int res = (i * GLOBE_LAT_MULT + j + GLOBE_MIN_INDEX); if (res > GLOBE_MAX_INDEX) { fprintf(stderr, "globe_index: %d larger than GLOBE_MAX_INDEX: %d grid: %d,%d input: %.2f,%.2f\n", res, GLOBE_MAX_INDEX, lat, lon, lat_in, lon_in); return 0; } return res; // highest number returned: globe_index(90, 180) // first 1000 are reserved for special use } struct tm fifteenTime(int64_t now) { time_t fifteen_time = (now - 15 * MINUTES) / 1000; // in seconds struct tm fifteenAgo; gmtime_r(&fifteen_time, &fifteenAgo); return fifteenAgo; } // fiftyfive_ago changes day 55 min after midnight: stop writing the previous days traces struct tm fiftyfiveTime(int64_t now) { // this is in seconds, not milliseconds time_t fiftyfive_time = now / 1000 - 55 * 60; struct tm fiftyfive; gmtime_r(&fiftyfive_time, &fiftyfive); return fiftyfive; } int globe_index_index(int index) { double lat = ((index - GLOBE_MIN_INDEX) / GLOBE_LAT_MULT) * GLOBE_INDEX_GRID - 90; double lon = ((index - GLOBE_MIN_INDEX) % GLOBE_LAT_MULT) * GLOBE_INDEX_GRID - 180; return globe_index(lat, lon); } static void sprintDateDir(char *base_dir, struct tm *utc, char *dateDir) { char tstring[100]; strftime (tstring, 100, TDATE_FORMAT, utc); snprintf(dateDir, PATH_MAX * 3/4, "%s/%s", base_dir, tstring); } static void createDateDir(char *base_dir, struct tm *utc, char *dateDir) { if (strcmp(TDATE_FORMAT, "%Y/%m/%d")) { fprintf(stderr, "check TDATE_FORMAT\n"); } char yy[100]; char mm[100]; strftime (yy, 100, "%Y", utc); strftime (mm, 100, "%m", utc); char pathbuf[PATH_MAX]; snprintf(pathbuf, PATH_MAX, "%s/%s", base_dir, yy); mkdir_error(pathbuf, 0755, stderr); snprintf(pathbuf, PATH_MAX, "%s/%s/%s", base_dir, yy, mm); mkdir_error(pathbuf, 0755, stderr); sprintDateDir(base_dir, utc, dateDir); //fprintf(stderr, "making sure directory exists: %s\n", dateDir); mkdir_error(dateDir, 0755, stderr); } static void scheduleMemBothWrite(struct aircraft *a, int64_t schedTime) { a->trace_next_mw = schedTime; a->trace_writeCounter = 0xc0ffee; } // return first index at or after timestamp, return tb.len if all indexes are before the timestamp static int first_index_ge_timestamp(traceBuffer tb, int64_t timestamp) { int start = 0; int end = tb.len - 1; while (start + 32 < end) { int pivot = (start + end) / 2 + 1; int64_t pivot_ts = getState(tb.trace, pivot)->timestamp; if (pivot_ts < timestamp) { start = pivot + 1; } else { end = pivot; } } for (int i = start; i <= end; i++) { if (getState(tb.trace, i)->timestamp >= timestamp) { return i; } } return tb.len; } #define TRACE_PMAX 2048 static void writeRecent(struct aircraft *a, traceBuffer tb, threadpool_buffer_t *generate_buffer, int64_t now, int recent_points) { MODES_NOTUSED(now); struct char_buffer recent = { 0 }; mark_legs(tb, a, imax(0, tb.len - 4 * recent_points), 1); // statistics atomic_fetch_add(&Modes.recentTraceWrites, 1); // prepare the data for the trace_recent file in /run recent = generateTraceJson(a, tb, -2, -2, generate_buffer, 0, -1); //if (Modes.debug_traceCount && ++count2 % 1000 == 0) // fprintf(stderr, "recent trace write: %u\n", count2); //fprintf(stderr, "traceWrite() recent for %06x uncompressed %4d bytes\n", a->addr, (int) recent.len); if (recent.len > 0) { char filename[TRACE_PMAX]; snprintf(filename, TRACE_PMAX, "traces/%02x/trace_recent_%s%06x.json", a->addr % 256, (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); writeJsonToGzip(Modes.json_dir, filename, recent, 1); } } static int64_t fullIvalAdjust(int64_t age, int64_t ival) { if (age > 1 * HOURS) { ival *= 2; if (Modes.fullTraceDir) { ival *= 3; } } return ival; } static int writeFull(struct aircraft *a, traceBuffer tb, threadpool_buffer_t *generate_buffer, int64_t now, int startFull) { struct char_buffer full = { 0 } ; int memThreshold = Modes.traceRecentPoints - 2; int memWritten = a->trace_writeCounter; //if (Modes.debug_traceCount && ++count3 % 1000 == 0) // fprintf(stderr, "memory trace writes: %u\n", count3); if (a->addr == TRACE_FOCUS) fprintf(stderr, "full\n"); int64_t before = mono_milli_seconds(); mark_legs(tb, a, 0, 0); int64_t elapsed = mono_milli_seconds() - before; if (elapsed > 2 * SECONDS || (a->addr == Modes.leg_focus)) { fprintf(stderr, "%06x mark_legs() took %.1f s!\n", a->addr, elapsed / 1000.0); } // statistics atomic_fetch_add(&Modes.fullTraceWrites, 1); full = generateTraceJson(a, tb, startFull, -1, generate_buffer, 0, -1); if (full.len > 0) { char filename[TRACE_PMAX]; snprintf(filename, TRACE_PMAX, "traces/%02x/trace_full_%s%06x.json", a->addr % 256, (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); if (Modes.fullTraceDir) { writeJsonToGzip(Modes.fullTraceDir, filename, full, 5); char target[TRACE_PMAX]; snprintf(filename, TRACE_PMAX, "%s/traces/%02x/trace_full_%s%06x.json", Modes.json_dir, a->addr % 256, (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); snprintf(target, TRACE_PMAX, "%s/traces/%02x/trace_full_%s%06x.json", Modes.fullTraceDir, a->addr % 256, (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); int res = symlink(target, filename); if (res < 0 && errno != EEXIST) { fprintf(stderr, "%s -> %s errno: %s\n", target, filename, strerror(errno)); } } else { writeJsonToGzip(Modes.json_dir, filename, full, 5); } } if (a->trace_writeCounter >= 0xc0ffee) { // avoid CPU spikes by randomizing next full trace writes on startup int64_t ival = random() % (GLOBE_MEM_IVAL * 9 / 8); a->trace_next_mw = now + fullIvalAdjust(now - a->seenPosReliable, ival); if (now - a->seenPosReliable < 5 * MINUTES) { // only set this for active aircraft, not necessary for inactive ones a->trace_writeCounter = random() % memThreshold; } else { a->trace_writeCounter = 0; } } else { int64_t ival = GLOBE_MEM_IVAL + random() % (GLOBE_MEM_IVAL / 8); a->trace_next_mw = now + fullIvalAdjust(now - a->seenPosReliable, ival); a->trace_writeCounter = 0; } return memWritten; } static int writePerm(struct aircraft *a, traceBuffer tb, threadpool_buffer_t *generate_buffer, int64_t now) { struct char_buffer hist = { 0 }; int permWritten = 0; int64_t endStamp = 0; // fiftyfive_ago changes day 55 min after midnight: stop writing the previous days traces struct tm fiftyfive = fiftyfiveTime(now); if (!Modes.globe_history_dir) { // push timer back in perm_done goto perm_done; } if (a->addr == TRACE_FOCUS) { fprintf(stderr, "perm\n"); } struct tm tm_daystart = fiftyfive; tm_daystart.tm_sec = 0; tm_daystart.tm_min = 0; tm_daystart.tm_hour = 0; time_t epoch_daystart = timegm(&tm_daystart); int64_t start_of_day = 1000 * (int64_t) epoch_daystart; int64_t end_of_day = 1000 * (int64_t) (epoch_daystart + 86400); int start = first_index_ge_timestamp(tb, start_of_day); int end = first_index_ge_timestamp(tb, end_of_day) - 1; // end == -1 means we have no data before end_of_day thus we do not write the trace if (start < 0 || end < 0 || end < start) { goto perm_done; } struct state *startState = getState(tb.trace, start); struct state *endState = getState(tb.trace, end); endStamp = endState->timestamp; // only write permanent trace if we haven't already written up to the last timestamp if (a->trace_perm_last_timestamp == endStamp) { goto perm_done; } // don't write permanent trace for non icao traces that are on the ground if ((a->addr & MODES_NON_ICAO_ADDRESS) && ( (startState->on_ground || !startState->baro_alt_valid) && (endState->on_ground || !endState->baro_alt_valid) ) ) { goto perm_done; } static int64_t antiSpam; if (fiftyfive.tm_hour == 23 && fiftyfive.tm_min > 50 && now > antiSpam) { antiSpam = now + 30 * SECONDS; fprintf(stderr, "<3>%06x permanent trace written for yesterday was written successfully but a bit late," "persistent traces for the previous UTC day are in danger of not all getting done!" "consider alloting more CPU cores or increasing json-trace-interval!\n", a->addr); } int64_t before = mono_milli_seconds(); mark_legs(tb, a, 0, 0); int64_t elapsed = mono_milli_seconds() - before; if (elapsed > 2 * SECONDS || (a->addr == Modes.leg_focus)) { fprintf(stderr, "%06x mark_legs() took %.1f s!\n", a->addr, elapsed / 1000.0); } // statistics atomic_fetch_add(&Modes.permTraceWrites, 1); hist = generateTraceJson(a, tb, start, end, generate_buffer, start_of_day, end_of_day); if (hist.len > 0) { permWritten = 1; char tstring[100]; strftime (tstring, 100, TDATE_FORMAT, &fiftyfive); char filename[TRACE_PMAX]; snprintf(filename, TRACE_PMAX, "%s/traces/%02x/trace_full_%s%06x.json", tstring, a->addr % 256, (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); writeJsonToGzip(Modes.globe_history_dir, filename, hist, 9); //fprintf(stderr, "perm write %06x\n", a->addr); //if (Modes.debug_traceCount && ++count4 % 100 == 0) // fprintf(stderr, "perm trace writes: %u\n", count4); } perm_done: // note what we have written to disk a->trace_perm_last_timestamp = endStamp; a->traceWrittenForYesterday = Modes.triggerPermWriteDay; return permWritten; } #undef TRACE_PMAX void traceWrite(struct aircraft *a, threadpool_threadbuffers_t *buffer_group) { //static uint32_t count2, count3, count4; int trace_write = a->trace_write; if (Modes.replace_state_inhibit_traces_until) { trace_write &= WRECENT; a->trace_write &= ~WRECENT; } else { a->trace_write = 0; } if (!a->initialTraceWriteDone) { a->initialTraceWriteDone = 1; trace_write |= (WMEM | WRECENT); } if (a->trace_len == 0) { return; } int64_t now = mstime(); int recent_points = Modes.traceRecentPoints; int memThreshold = Modes.traceRecentPoints - 2; if (a->trace_writeCounter >= memThreshold) { trace_write |= (WMEM | WRECENT); } if (Modes.trace_hist_only) { int hist_only_mask = WPERM | WMEM | WRECENT; if (Modes.trace_hist_only & 8) { hist_only_mask = WPERM; if (Modes.trace_hist_only == 10) { if (a->trace_writeCounter > 0 && now > a->trace_next_mw) { a->trace_next_mw = now + 5 * MINUTES; trace_write |= WRECENT; hist_only_mask |= WRECENT; a->trace_writeCounter = 0; } } else { if (a->trace_writeCounter > recent_points) { hist_only_mask |= WRECENT; a->trace_writeCounter = 0; } } if (now > a->trace_next_mw) { hist_only_mask |= WMEM; } } if (Modes.trace_hist_only & 1) hist_only_mask &= ~ WRECENT; if (Modes.trace_hist_only & 2) hist_only_mask &= ~ WMEM; trace_write &= hist_only_mask; } if ((trace_write & WPERM) && a->trace_perm_last_timestamp == getState(a->trace_current, a->trace_current_len - 1)->timestamp) { trace_write &= ~WPERM; a->traceWrittenForYesterday = Modes.triggerPermWriteDay; } if (!trace_write) { // no need to go any further return; } traceBuffer tb = { 0 }; if (buffer_group->buffer_count < 2) { static int64_t antiSpam; if (now > antiSpam) { antiSpam = now + 5 * SECONDS; fprintf(stderr, "<3> FATAL: traceWrite: insufficient buffer_count\n"); } exit(1); } threadpool_buffer_t *reassemble_buffer = &buffer_group->buffers[0]; threadpool_buffer_t *generate_buffer = &buffer_group->buffers[1]; if (0 && (trace_write & WMEM)) { fprintTimePrecise(stderr, now); fprintf(stderr, " %s%06x %d %d\n", ((a->addr & MODES_NON_ICAO_ADDRESS) ? "" : " "), a->addr, (trace_write & WMEM), (trace_write & WRECENT) ); } if ((trace_write & (WPERM | WMEM))) { tb = reassembleTrace(a, -1, -1, reassemble_buffer); } else { tb = reassembleTrace(a, 2 * recent_points, -1, reassemble_buffer); } int startFull = 0; if ((Modes.trace_hist_only & 8) && (trace_write & WMEM)) { startFull = first_index_ge_timestamp(tb, now - 30 * MINUTES); } else { startFull = first_index_ge_timestamp(tb, now - Modes.keep_traces); } if (startFull >= tb.len) { // do not write recent / mem trace if all data is older than keep_traces // perm write checks the boundaries itself trace_write &= ~(WMEM | WRECENT); } if ((trace_write & WRECENT)) { writeRecent(a, tb, generate_buffer, now, recent_points); } if (trace_write && a->addr == TRACE_FOCUS) fprintf(stderr, "mw: %.0f, perm: %.0f, count: %d %x\n", ((int64_t) a->trace_next_mw - (int64_t) now) / 1000.0, ((int64_t) a->trace_next_perm - (int64_t) now) / 1000.0, a->trace_writeCounter, a->trace_writeCounter); int memWritten = 0; // prepare the data for the trace_full file in /run if ((trace_write & WMEM)) { // don't check for trace_writeCounter > 0 as before // unconditionally write trace_full to reduce memory usage via /run for aircraft that have // been inactive for some time memWritten = writeFull(a, tb, generate_buffer, now, startFull); } int permWritten = 0; // prepare writing the permanent history // until 20 min after midnight we only write permanent traces for the previous day if ((trace_write & WPERM)) { permWritten = writePerm(a, tb, generate_buffer, now); } if (Modes.debug_traceCount) { static uint32_t timedCount, pointsCount, permCount; int timed = 0; int byCounter = 0; if (permWritten || (memWritten && memWritten < 0xc0ffee)) { pthread_mutex_lock(&Modes.traceDebugMutex); { if (permWritten) { permCount++; } if (memWritten && memWritten < 0xc0ffee) { if (memWritten >= memThreshold) { byCounter = 1; pointsCount++; } else { timed = 1; timedCount++; } } } pthread_mutex_unlock(&Modes.traceDebugMutex); int print = 0; if (timed && timedCount % 500 == 0) { fprintf(stderr, "full_time :%6d", timedCount); print = 1; } if (byCounter && pointsCount % 500 == 0) { fprintf(stderr, "full_points:%6d", pointsCount); print = 1; } if (permWritten && permCount % 500 == 0) { fprintf(stderr, "perm :%6d", permCount); print = 1; } if (print) { fprintf(stderr, " hex: %06x mw: %6.0f, perm: %6.0f, count: %4d / %4d (%4x) \n", a->addr, ((int64_t) a->trace_next_mw - (int64_t) now) / 1000.0, ((int64_t) a->trace_next_perm - (int64_t) now) / 1000.0, a->trace_writeCounter, recent_points, a->trace_writeCounter); } } } if (0 && a->addr == TRACE_FOCUS) fprintf(stderr, "mw: %.0f, perm: %.0f, count: %d\n", ((int64_t) a->trace_next_mw - (int64_t) now) / 1000.0, ((int64_t) a->trace_next_perm - (int64_t) now) / 1000.0, a->trace_writeCounter); } static void free_aircraft_range(int start, int end) { for (int j = start; j < end; j++) { struct aircraft *a = Modes.aircraft[j], *na; /* Go through tracked aircraft chain and free up any used memory */ while (a) { na = a->next; if (a) { freeAircraft(a); } a = na; } } } static void save_blobs(void *arg, threadpool_threadbuffers_t *threadbuffers) { readsb_task_t *info = (readsb_task_t *) arg; for (int j = info->from; j < info->to; j++) { //fprintf(stderr, "save_blob(%d)\n", j); save_blob(j, &threadbuffers->buffers[0], &threadbuffers->buffers[1], Modes.state_dir); if (Modes.quickFree) { int stride = Modes.acBuckets / STATE_BLOBS; int start = stride * j; int end = start + stride; free_aircraft_range(start, end); } } } static size_t memcpySize(void *dest, const void *src, size_t n) { memcpy(dest, src, n); return n; } static int roundUp8(int value) { return ((value + 7) / 8) * 8; } static int load_aircraft(char **p, char *end, int64_t now, threadpool_buffer_t *passbuffer) { static int size_changed; ssize_t newSize = sizeof(struct aircraft); if (end - *p < (int) sizeof(uint64_t)) { return -1; } uint64_t tmp_u64; *p += memcpySize(&tmp_u64, *p, sizeof(tmp_u64)); ssize_t oldSize = tmp_u64; if (end - *p < oldSize) { return -1; } struct aircraft *source = (struct aircraft *) *p; struct aircraft *a = aircraftGet(source->addr); if (a) { if (0 && oldSize != newSize) { fprintf(stderr, "%06x size mismatch when replacing aircraft data, aborting!\n", source->addr); return -1; } //fprintf(stderr, "%06x aircraft already exists, overwriting old data\n", source->addr); //freeAircraft(a); quickRemove(a); // remove from active list if on it if (a->onActiveList) { ca_remove(&Modes.aircraftActive, a); } // remove from the globeList set_globe_index(a, -5); traceCleanupNoUnlink(a); } else { a = aircraftCreate(source->addr); } struct aircraft *preserveNext = a->next; memcpy(a, *p, imin(oldSize, newSize)); *p += oldSize; a->next = preserveNext; if (!size_changed && oldSize != newSize) { size_changed = 1; fprintf(stderr, "sizeof(struct aircraft) has changed from %ld to %ld bytes, this means the code changed and if the coder didn't think properly might result in bad aircraft data. If your map doesn't have weird stuff ... probably all good and just an upgrade.\n", (long) oldSize, (long) newSize); Modes.writeInternalState = 1; // immediately write in the new format } // if we are loading this data via the replace_state mechanism, make sure we write the permanent trace again if (Modes.replace_state_blob) { a->trace_perm_last_timestamp = 0; } aircraftZeroTail(a); if (a->lastMlatForce > now) { a->lastMlatForce = now; // reset this } // just in case we have bogus values saved, make sure they time out if (a->seen_pos > now + 1 * MINUTES) a->seen_pos = now - 26 * HOURS; if (a->seen > now + 1 * MINUTES) a->seen = now - 26 * HOURS; if (a->lastSignalTimestamp > now) { a->lastSignalTimestamp = 0; } if (a->globe_index > GLOBE_MAX_INDEX) a->globe_index = -5; if (a->addrtype_updated > now) a->addrtype_updated = now; int new_index = a->globe_index; a->globe_index = -5; if (a->pos_reliable_valid.source != SOURCE_INVALID) { set_globe_index(a, new_index); } if (a->onActiveList) { a->onActiveList = 1; ca_add(&Modes.aircraftActive, a); } updateValidities(a, now); // make sure we don't think an extra position is still buffered in the trace memory a->tracePosBuffered = 0; int traceLastSaved = (a->traceLast != NULL); // set trace pointers to zero before loading the trace a->trace_current_max = 0; a->trace_current = NULL; a->trace_chunks = NULL; a->traceLast = NULL; // recalculate overall trace chunk size a->trace_chunk_overall_bytes = 0; int discard_trace = 0; // check that the trace meta data make sense before loading it if (a->trace_len > 0) { if (a->trace_len > Modes.traceMax) { fprintf(stderr, "%06x unexpectedly long trace: %d!\n", a->addr, a->trace_len); } uint64_t tmp_u64; *p += memcpySize(&tmp_u64, *p, sizeof(tmp_u64)); ssize_t oldFourStateSize = tmp_u64; if (oldFourStateSize != sizeof(fourState)) { fprintf(stderr, "%06x sizeof(fourState) / SFOUR definition has changed, aborting state loading!\n", a->addr); traceCleanupNoUnlink(a); return -1; } int checkNo = 0; #define checkSize(size) if (++checkNo && ((end - *p < (ssize_t) size) || size < 0)) { fprintf(stderr, "loadAircraft: checkSize failed for hex %06x checkNo %d size %lld\n", a->addr, checkNo, (long long) size); traceCleanupNoUnlink(a); return -1; } if (a->trace_chunk_len > 0) { a->trace_chunks = cmalloc(a->trace_chunk_len * sizeof(stateChunk)); } else { a->trace_chunk_len = 0; } for (int k = 0; k < a->trace_chunk_len; k++) { stateChunk *chunk = &a->trace_chunks[k]; checkSize(sizeof(stateChunk)); *p += memcpySize(chunk, *p, sizeof(stateChunk)); checkSize(chunk->compressed_size); chunk->compressed = cmalloc(chunk->compressed_size); a->trace_chunk_overall_bytes += chunk->compressed_size; *p += memcpySize(chunk->compressed, *p, chunk->compressed_size); ssize_t padBytes = roundUp8(chunk->compressed_size) - chunk->compressed_size; *p += padBytes; if (chunk->numStates % SFOUR != 0) { fprintf(stderr, "<3> %06x load_aircraft: (chunk->numStates %% SFOUR != 0) ..... this would cause issues, throwing away trace data!\n", a->addr); discard_trace = 1; } } resizeTraceCurrent(a, now, 0, 0); if (a->trace_current_len) { checkSize(stateBytes(a->trace_current_len)); *p += memcpySize(a->trace_current, *p, stateBytes(a->trace_current_len)); } if (traceLastSaved) { //fprintf(stderr, "loading traceLast\n"); *p += memcpySize(&tmp_u64, *p, sizeof(tmp_u64)); int32_t oldTraceLastMax = tmp_u64; if (oldTraceLastMax == Modes.traceLastMax) { checkSize(stateBytes(Modes.traceLastMax)); a->traceLast = cmCalloc(stateBytes(Modes.traceLastMax)); *p += memcpySize(a->traceLast, *p, stateBytes(Modes.traceLastMax)); //fprintf(stderr, "loaded traceLast\n"); } else { *p += stateBytes(oldTraceLastMax); } } #undef checkSize if (!Modes.keep_traces) { traceCleanupNoUnlink(a); return 0; } traceMaintenance(a, now, passbuffer); if (a->addr == Modes.leg_focus) { a->trace_next_perm = now; scheduleMemBothWrite(a, now); fprintf(stderr, "leg_focus: %06x trace len: %d\n", a->addr, a->trace_len); a->trace_write |= (WRECENT | WPERM | WMEM); } // write traces into /run/readsb so they are present for the webinterface if (a->pos_reliable_valid.source != SOURCE_INVALID || now - a->seenPosReliable < 15 * MINUTES) { // write these trace immediately a->trace_writeCounter = 0xc0ffee; a->trace_write |= (WRECENT | WMEM); } } else { traceCleanupNoUnlink(a); } if (discard_trace) { traceCleanupNoUnlink(a); } return 0; } static void utc_string_from_ms(int64_t ts, char *target) { time_t time = ts / 1000; struct tm utc; gmtime_r(&time, &utc); strftime (target, 100, "%H:%M:%S", &utc); } static void mark_legs(traceBuffer tb, struct aircraft *a, int start, int recent) { if (tb.len < 20) return; if (start < 0) { start = 0; } int high = 0; int low = 100000; struct timespec watch = { 0 }; int64_t elapsed1 = 0; int64_t elapsed2 = 0; int focus = (a->addr == Modes.leg_focus && !recent); if (!recent) { startWatch(&watch); } int last_five_init_alt = 0; struct state *startState = getState(tb.trace, start); if (startState->baro_alt_valid) { last_five_init_alt = startState->baro_alt / _alt_factor; } int last_five[5]; uint32_t five_pos = 0; for (int i = 0; i < 5; i++) { last_five[i] = last_five_init_alt; } int32_t last_air_alt = INT32_MIN; double sum = 0; int count = 0; struct state *new_leg = NULL; int increment = SFOUR; if (tb.len > 256 * SFOUR) { increment = 4 * SFOUR; } float inverse_alt_factor = 1 / _alt_factor; for (int i = start - (start % SFOUR); i < tb.len; i += increment) { struct state *curr = getState(tb.trace, i); int on_ground = curr->on_ground; int altitude_valid = curr->baro_alt_valid; int altitude = curr->baro_alt * inverse_alt_factor; if (!altitude_valid && curr->geom_alt_valid) { altitude_valid = 1; altitude = curr->geom_alt * inverse_alt_factor; } if (on_ground || !altitude_valid) { if (last_air_alt == INT32_MIN) { int avg = 0; for (int i = 0; i < 5; i++) avg += last_five[i]; avg /= 5; last_air_alt = avg; } altitude = last_air_alt; } else { last_air_alt = INT32_MIN; last_five[five_pos] = altitude; five_pos = (five_pos + 1) % 5; } sum += altitude; count++; } int threshold = (int) (sum / (double) (count * 3)); if (!recent) { elapsed1 = lapWatch(&watch); } if (focus) { fprintf(stderr, "--------------------------\n"); fprintf(stderr, "start: %d\n", start); fprintf(stderr, "trace_len: %d\n", tb.len); fprintf(stderr, "threshold: %d\n", threshold); } if (threshold > 2500) threshold = 2500; if (threshold < 200) threshold = 200; high = 0; low = 100000; int64_t major_climb = 0; int64_t major_descent = 0; int major_climb_index = 0; int major_descent_index = 0; int64_t last_high = 0; int64_t last_low = 0; int last_high_index = 0; MODES_NOTUSED(last_high_index); int last_low_index = 0; int64_t last_airborne = 0; int64_t last_ground = 0; int64_t last_ground_index = 0; int64_t first_ground = 0; int64_t first_ground_index = 0; int last_5min_gap_index = -1; struct state last_5min_gap_state = { 0 }; int last_10min_gap_index = -1; MODES_NOTUSED(last_10min_gap_index); int was_ground = 0; last_air_alt = INT32_MIN; for (int i = 0; i < 5; i++) { last_five[i] = last_five_init_alt; } five_pos = 0; int32_t counter1 = 0; int32_t counter2 = 0; int32_t counter3 = 0; int32_t counter4 = 0; int32_t counter5 = 0; if (start < 1) { start = 1; } int prev_index = start - 1; struct state *state = getState(tb.trace, prev_index); int state_index = prev_index; struct state *prev; for (int index = start; index < tb.len; index++) { prev = state; prev_index = state_index; state = getState(tb.trace, index); state_index = index; int64_t elapsed = state->timestamp - prev->timestamp; if (elapsed < 5 * SECONDS) { state = prev; state_index = prev_index; continue; } if (elapsed > 5 * MINUTES) { last_5min_gap_index = state_index; last_5min_gap_state = *state; if (focus) { fprintf(stderr, "5 min gap detected with index %d\n", state_index); } if (elapsed > 10 * MINUTES) { last_10min_gap_index++; // shut up unused var last_10min_gap_index = state_index; } } int on_ground = state->on_ground; int altitude_valid = state->baro_alt_valid; int altitude = state->baro_alt * inverse_alt_factor; if (!altitude_valid && state->geom_alt_valid) { altitude_valid = 1; altitude = state->geom_alt * inverse_alt_factor; } if (on_ground || !altitude_valid) { if (last_air_alt == INT32_MIN) { int avg = 0; for (int i = 0; i < 5; i++) avg += last_five[i]; avg /= 5; last_air_alt = avg; } altitude = last_air_alt; } else { last_air_alt = INT32_MIN; last_five[five_pos] = altitude; five_pos = (five_pos + 1) % 5; } if (on_ground || was_ground) { // count the last point in time on ground to be when the aircraft is received airborn after being on ground if (state->timestamp > last_ground + 5 * MINUTES) { first_ground = state->timestamp; first_ground_index = index; } last_ground = state->timestamp; last_ground_index = index; } else { last_airborne = state->timestamp; } if (was_ground) { low = altitude; high = altitude; } if (altitude >= high) { high = altitude; if (0 && focus) { time_t nowish = state->timestamp/1000; struct tm utc; gmtime_r(&nowish, &utc); char tstring[100]; strftime (tstring, 100, "%H:%M:%S", &utc); fprintf(stderr, "high: %d %s\n", altitude, tstring); } } if (!on_ground && major_descent && last_ground >= major_descent && last_ground > first_ground + 1 * MINUTES && state->timestamp > last_ground + 15 * SECONDS && high - low > 200) { // fake major_climb after takeoff ... bit hacky high = low + threshold + 1; last_high = state->timestamp; last_high_index = index; last_low = last_ground; last_low_index = last_ground_index; } if (altitude <= low) { low = altitude; } if (abs(low - altitude) < threshold * 1 / 3) { last_low = state->timestamp; last_low_index = index; } if (abs(high - altitude) < threshold * 1 / 3) { last_high = state->timestamp; last_high_index++; last_high_index = index; if (0 && focus) { time_t nowish = state->timestamp/1000; struct tm utc; gmtime_r(&nowish, &utc); char tstring[100]; strftime (tstring, 100, "%H:%M:%S", &utc); fprintf(stderr, "last_high: %d %s\n", altitude, tstring); } } if (high - low > threshold) { if (last_high > last_low) { // only set new major climb time if this is after a major descent. // then keep that time associated with the climb // still report continuation of thta climb if (major_climb <= major_descent) { int bla = imin(tb.len - 1, last_low_index + 3); major_climb = getState(tb.trace, bla)->timestamp; major_climb_index = bla; } if (focus) { char climbString[100]; utc_string_from_ms(major_climb, climbString); char tstring[100]; utc_string_from_ms(state->timestamp, tstring); fprintf(stderr, "%s climb: %d %s high: %d low:%d index: %d\n", tstring, altitude, climbString, high, low, major_climb_index); } low = high - threshold * 9/10; } else if (last_low > last_high) { int k = imax(0, last_low_index - 3); for (; k > 0; k--) { counter1++; struct state *st = getState(tb.trace, k); if (0 && focus) { fprintf(stderr, "k: %d %d %d %d\n", k, (int) (st->baro_alt / _alt_factor), st->baro_alt_valid, st->on_ground); } if (st->baro_alt_valid && !st->on_ground) { break; } } if (k < 0) { fprintf(stderr, "look screwed up. Thaeth5g\n"); // because it's easy to mess up the logic of k decreasing after the last loop k = 0; } major_descent = getState(tb.trace, k)->timestamp; major_descent_index = k; if (focus) { char descString[100]; utc_string_from_ms(major_descent, descString); char tstring[100]; utc_string_from_ms(state->timestamp, tstring); fprintf(stderr, "%s desc: %d %s index: %d\n", tstring, altitude, descString, major_descent_index); } high = low + threshold * 9/10; } } int leg_now = 0; if ( (major_descent && (on_ground || was_ground) && elapsed > 25 * 60 * 1000) || (major_descent && on_ground && state->timestamp > last_airborne + 45 * 60 * 1000) ) { if (focus) { fprintf(stderr, "ground leg (on ground and time between reception > 25 min)\n"); } leg_now = 1; } int max_leg_alt = 20000; // disable .... let's see if we really need it if (0 && elapsed > 30 * 60 * 1000 && (state->on_ground || !state->baro_alt_valid || (state->baro_alt_valid && state->baro_alt / _alt_factor < max_leg_alt))) { double distance = greatcircle( (double) state->lat * 1e-6, (double) state->lon * 1e-6, (double) prev->lat * 1e-6, (double) prev->lon * 1e-6, 0 ); if (distance < 10E3 * (elapsed / (30 * 60 * 1000.0)) && distance > 1) { leg_now = 1; if (focus) { fprintf(stderr, "time/distance leg, elapsed: %0.fmin, distance: %0.f\n", elapsed / (60 * 1000.0), distance / 1000.0); } } } int leg_float = 0; if (major_climb && major_descent && major_climb > major_descent + 12 * MINUTES) { if (last_5min_gap_index >= 0 && last_5min_gap_index >= major_descent_index) { struct state *st = &last_5min_gap_state; if (focus) { fprintf(stderr, "checking for: float leg: 5 minutes between descent / climb, 5 minute reception gap in between somewhere\n"); } if (st->on_ground || !st->baro_alt_valid || (st->baro_alt_valid && st->baro_alt / _alt_factor < max_leg_alt)) { leg_float = 1; if (focus) { fprintf(stderr, "float leg: 5 minutes between descent / climb, 5 minute reception gap in between somewhere\n"); } } } } if (major_climb && major_descent && major_climb > major_descent + 1 * MINUTES && last_ground >= major_descent && last_ground > first_ground + 1 * MINUTES ) { leg_float = 1; if (focus) { fprintf(stderr, "float leg: 1 minutes between descent / climb, 1 minute on ground\n"); } } if (leg_float || leg_now) { int64_t leg_ts = 0; if (leg_now) { new_leg = state; for (int k = prev_index + 1; k < index; k++) { counter2++; struct state *state = getState(tb.trace, k); struct state *last = getState(tb.trace, k - 1); if (state->timestamp > last->timestamp + 5 * MINUTES) { new_leg = state; break; } } } else if (major_descent_index + 1 == major_climb_index) { new_leg = getState(tb.trace, major_climb_index); } else { for (int i = major_climb_index; i > major_descent_index; i--) { counter3++; struct state *state = getState(tb.trace, i); struct state *last = getState(tb.trace, i - 1); if (state->timestamp > last->timestamp + 5 * 60 * 1000) { new_leg = state; break; } } if (last_ground > major_descent) { int64_t half = first_ground + (last_ground - first_ground) / 2; for (int i = first_ground_index + 1; i <= last_ground_index; i++) { counter4++; struct state *state = getState(tb.trace, i); if (state->timestamp > half) { new_leg = state; break; } } } else { int64_t half = major_descent + (major_climb - major_descent) / 2; for (int i = major_descent_index + 1; i < major_climb_index; i++) { counter5++; struct state *state = getState(tb.trace, i); if (state->timestamp > half) { new_leg = state; break; } } } } if (new_leg) { leg_ts = new_leg->timestamp; new_leg->leg_marker = 1; // set leg marker } major_climb = 0; major_climb_index = 0; major_descent = 0; major_descent_index = 0; low += threshold; high -= threshold; if (new_leg && new_leg->on_ground) { // reset low / high completely high = 0; low = 100000; } if (focus) { if (new_leg) { time_t nowish = leg_ts/1000; struct tm utc; gmtime_r(&nowish, &utc); char tstring[100]; strftime (tstring, 100, "%H:%M:%S", &utc); fprintf(stderr, "leg: %s\n", tstring); } else { time_t nowish = state->timestamp/1000; struct tm utc; gmtime_r(&nowish, &utc); char tstring[100]; strftime (tstring, 100, "%H:%M:%S", &utc); fprintf(stderr, "resetting major_c/d without leg: %s\n", tstring); } } } was_ground = on_ground; } if (!recent) { elapsed2 = lapWatch(&watch); } if (focus || ((elapsed1 > 50 || elapsed2 > 50) && counter1 + counter2 + counter3 + counter4 + counter5 > 2000)) { fprintf(stderr, "%06x mark_legs loop1: %.3f loop2: %.3f counter1 %d counter2 %d counter3 %d counter4 %d counter5 %d\n", a->addr, elapsed1 / 1000.0, elapsed2 / 1000.0, counter1, counter2, counter3, counter4, counter5); } } void ca_lock_read(struct craftArray *ca) { pthread_mutex_lock(&ca->read_mutex); if (ca->reader_count == 0) { pthread_mutex_lock(&ca->write_mutex); } ca->reader_count++; if (0 && ca->reader_count > 1) { fprintf(stderr, "ca->reader_count %d\n", ca->reader_count); } pthread_mutex_unlock(&ca->read_mutex); } void ca_unlock_read(struct craftArray *ca) { pthread_mutex_lock(&ca->read_mutex); ca->reader_count--; if (ca->reader_count == 0) { pthread_mutex_unlock(&ca->write_mutex); } //fprintf(stderr, "ca->reader_count %d\n", ca->reader_count); pthread_mutex_unlock(&ca->read_mutex); } void ca_init (struct craftArray *ca) { memset(ca, 0x0, sizeof(struct craftArray)); pthread_mutex_init(&ca->change_mutex, NULL); pthread_mutex_init(&ca->read_mutex, NULL); pthread_mutex_init(&ca->write_mutex, NULL); } void ca_destroy (struct craftArray *ca) { if (ca->list) { sfree(ca->list); } pthread_mutex_destroy(&ca->change_mutex); pthread_mutex_destroy(&ca->read_mutex); pthread_mutex_destroy(&ca->write_mutex); memset(ca, 0x0, sizeof(struct craftArray)); } void ca_add (struct craftArray *ca, struct aircraft *a) { pthread_mutex_lock(&ca->change_mutex); if (ca->len == ca->alloc) { pthread_mutex_lock(&ca->write_mutex); if (ca->len == ca->alloc) { ca->alloc = ca->alloc * 2 + 16; ca->list = realloc(ca->list, ca->alloc * sizeof(struct aircraft *)); if (!ca->list) { fprintf(stderr, "ca_add(): out of memory!\n"); exit(1); } } pthread_mutex_unlock(&ca->write_mutex); } int duplicate = 0; for (int i = 0; i < ca->len; i++) { if (unlikely(a == ca->list[i])) { fprintf(stderr, "<3>hex: %06x, ca_add(): double add!\n", a->addr); duplicate = 1; } } if (!duplicate) { ca->list[ca->len] = a; // add at the end ca->len++; } pthread_mutex_unlock(&ca->change_mutex); } void ca_remove (struct craftArray *ca, struct aircraft *a) { pthread_mutex_lock(&ca->change_mutex); int found = 0; for (int i = 0; i < ca->len; i++) { if (ca->list[i] == a) { // replace with last element in array ca->list[i] = ca->list[ca->len - 1]; ca->list[ca->len - 1] = NULL; ca->len--; i--; found++; } } if (found == 0) { fprintf(stderr, "<3>hex: %06x, ca_remove(): pointer not in array!\n", a->addr); } else if (found > 1) { fprintf(stderr, "<3>hex: %06x, ca_remove(): pointer removed %d times!\n", a->addr, found); } pthread_mutex_unlock(&ca->change_mutex); } void set_globe_index (struct aircraft *a, int new_index) { if (!Modes.json_globe_index) return; int old_index = a->globe_index; a->globe_index = new_index; if (old_index == new_index) return; if (new_index > GLOBE_MAX_INDEX || old_index > GLOBE_MAX_INDEX) { fprintf(stderr, "hex: %06x, old_index: %d, new_index: %d, GLOBE_MAX_INDEX: %d\n", a->addr, old_index, new_index, GLOBE_MAX_INDEX); return; } if (old_index >= 0) { ca_remove(&Modes.globeLists[old_index], a); } if (new_index >= 0) { ca_add(&Modes.globeLists[new_index], a); } } static void traceUnlink(struct aircraft *a) { char filename[PATH_MAX]; if (!Modes.writeTraces || !Modes.json_dir) return; snprintf(filename, 1024, "%s/traces/%02x/trace_recent_%s%06x.json", Modes.json_dir, a->addr % 256, (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); unlink(filename); snprintf(filename, 1024, "%s/traces/%02x/trace_full_%s%06x.json", Modes.json_dir, a->addr % 256, (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); unlink(filename); if (Modes.fullTraceDir) { snprintf(filename, 1024, "%s/traces/%02x/trace_full_%s%06x.json", Modes.fullTraceDir, a->addr % 256, (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); unlink(filename); } //fprintf(stderr, "unlink %06x: %s\n", a->addr, filename); } static stateChunk *resizeTraceChunks(struct aircraft *a, int newLen) { int oldLen = a->trace_chunk_len; if (oldLen < 0 || newLen < 0) { fprintf(stderr, "resizeTraceChunks: oldLen < 0 || newLen < 0 ... this is a fatal error, exiting.\n"); exit(1); } if (oldLen > 0 && !a->trace_chunks) { fprintf(stderr, "resizeTraceChunks: oldLen > 0 && !a->trace_chunks ... this is a fatal error, exiting.\n"); exit(1); } a->trace_chunk_len = newLen; if (newLen == 0) { sfree(a->trace_chunks); return NULL; } if (oldLen == newLen) { //fprintf(stderr, "resizeTraceChunks: oldLen == newLen ... this is weird but shouldn't be an issue\n"); } int maxLen = INT_MAX / sizeof(stateChunk); if (maxLen < newLen || maxLen < oldLen) { fprintf(stderr, "resizeTraceChunks: wat? overflow3? this can't happen, shut up old gcc\n"); exit(1); } int newBytes = newLen * sizeof(stateChunk); int oldBytes = oldLen * sizeof(stateChunk); stateChunk *new = cmalloc(newBytes); if (!new) { return NULL; } if (oldLen > newLen) { int shrinkByLen = oldLen - newLen; if (shrinkByLen < 0) { fprintf(stderr, "resizeTraceChunks: wat? overflow1? this can't happen, shut up old gcc\n"); exit(1); } memcpy(new, a->trace_chunks + shrinkByLen, newBytes); } else { int growByBytes = newBytes - oldBytes; if (growByBytes < 0) { fprintf(stderr, "resizeTraceChunks: wat? overflow2? this can't happen, shut up old gcc\n"); exit(1); } memcpy(new, a->trace_chunks, oldBytes); if (growByBytes > 0) { memset(new + oldLen, 0x0, growByBytes); } } sfree(a->trace_chunks); a->trace_chunks = new; if (newLen > oldLen) { return &a->trace_chunks[a->trace_chunk_len - 1]; } else { return NULL; } } static void tracePrune(struct aircraft *a, int64_t now) { if (a->trace_len <= 0) { traceCleanup(a); return; } int64_t keep_after = now - Modes.keep_traces; if (a->trace_current_len > 0 && getState(a->trace_current, a->trace_current_len - 1)->timestamp < keep_after) { traceCleanup(a); return; } int deletedChunks = 0; for (int k = 0; k < a->trace_chunk_len; k++) { stateChunk *chunk = &a->trace_chunks[k]; if (chunk->lastTimestamp >= keep_after) { break; } deletedChunks++; a->trace_len -= chunk->numStates; a->trace_chunk_overall_bytes -= chunk->compressed_size; sfree(chunk->compressed); } if (deletedChunks > 0) { if (0 && Modes.verbose) { fprintf(stderr, "%06x deleting %d chunks\n", a->addr, deletedChunks); } resizeTraceChunks(a, a->trace_chunk_len - deletedChunks); } int deleteFs = 0; for (int k = 0; k < a->trace_current_len / SFOUR; k++) { fourState *fs = &a->trace_current[k]; int64_t ts = fs->no[SFOUR - 1].timestamp; if (ts >= keep_after) { break; } deleteFs++; } if (deleteFs * SFOUR >= Modes.traceReserve) { if (0 && Modes.verbose) { fprintf(stderr, "%s%06x tracePrune a->trace_current: %d\n", ((a->addr & MODES_NON_ICAO_ADDRESS) ? "." : ". "), a->addr, SFOUR * deleteFs); } a->trace_current_len -= SFOUR * deleteFs; a->trace_len -= SFOUR * deleteFs; // keep buffered position intact -> +1 memmove(a->trace_current, a->trace_current + deleteFs, stateBytes(a->trace_current_len + 1)); } } int traceUsePosBuffered(struct aircraft *a) { if (a->tracePosBuffered) { a->tracePosBuffered = 0; // bookkeeping: a->trace_len++; a->trace_current_len++; a->trace_write |= WRECENT; a->trace_writeCounter++; return 1; } else { return 0; } } static void destroyTraceCache(struct traceCache *cache) { if (!cache) { return; } sfree(cache->entries); memset(cache, 0x0, sizeof(struct traceCache)); } void traceCleanupNoUnlink(struct aircraft *a) { if (a->trace_chunks) { for (int k = 0; k < a->trace_chunk_len; k++) { sfree(a->trace_chunks[k].compressed); } } sfree(a->trace_chunks); a->trace_chunk_len = 0; a->trace_chunk_overall_bytes = 0; sfree(a->trace_current); a->trace_current_max = 0; a->trace_current_len = 0; a->tracePosBuffered = 0; a->trace_len = 0; sfree(a->traceLast); destroyTraceCache(&a->traceCache); } void traceCleanup(struct aircraft *a) { if (a->trace_current) { traceUnlink(a); } traceCleanupNoUnlink(a); } // reconstruct at least the last numPoints points from trace chunks / current_trace // numPoints < 0 => all data / whole trace static traceBuffer reassembleTrace(struct aircraft *a, int numPoints, int64_t after_timestamp, threadpool_buffer_t *buffer) { spinLock(&a->traceLock); int firstChunk = 0; int currentLen = a->trace_current_len; int allocLen = currentLen; if (numPoints >= 0) { firstChunk = a->trace_chunk_len; for (int k = a->trace_chunk_len - 1; k >= 0 && allocLen < numPoints; k--) { stateChunk *chunk = &a->trace_chunks[k]; allocLen += chunk->numStates; firstChunk = k; } } else if (after_timestamp > 0) { firstChunk = a->trace_chunk_len; for (int k = a->trace_chunk_len - 1; k >= 0; k--) { stateChunk *chunk = &a->trace_chunks[k]; if (after_timestamp > chunk->lastTimestamp) { break; } allocLen += chunk->numStates; firstChunk = k; } } else { for (int k = 0; k < a->trace_chunk_len; k++) { stateChunk *chunk = &a->trace_chunks[k]; allocLen += chunk->numStates; } } traceBuffer tb = { 0 }; //fprintf(stderr, "allocLen %ld fourStates %ld stateBytes %ld\n", (long) allocLen, (long) getFourStates(allocLen), (long) stateBytes(allocLen)); tb.trace = check_grow_threadpool_buffer_t(buffer, stateBytes(allocLen)); fourState *tp = tb.trace; int actual_len = 0; for (int k = firstChunk; k < a->trace_chunk_len; k++) { stateChunk *chunk = &a->trace_chunks[k]; actual_len += chunk->numStates; if (actual_len > allocLen) { fprintf(stderr, "remakeTrace buffer overflow, bailing eex5ioBu\n"); exit(1); } uint64_t uncompressed_len = stateBytes(chunk->numStates); if (!buffer->dctx) { buffer->dctx = ZSTD_createDCtx(); } size_t res = ZSTD_decompressDCtx(buffer->dctx, tp, uncompressed_len, chunk->compressed, chunk->compressed_size); if (ZSTD_isError(res)) { fprintf(stderr, "reassembleTrace() zstd error: %s\n", ZSTD_getErrorName(res)); tb.len = 0; traceCleanup(a); goto exit; } tp += getFourStates(chunk->numStates); } actual_len += currentLen; if (actual_len > allocLen) { fprintf(stderr, "remakeTrace buffer overflow, bailing eex5ioBu with actual_len %d allocLen %d\n", actual_len, allocLen); exit(1); } if (a->trace_current_len > 0) { memcpy(tp, a->trace_current, stateBytes(currentLen)); } // tp is not incremented here as it's not used anymore after this. tb.len = actual_len; exit: spinRelease(&a->traceLock); return tb; } static float recompressStateChunk(struct aircraft *a, struct stateChunk *chunk, threadpool_buffer_t *passbuffer) { a->chunkRecompressed = 1; if (Modes.traceChunkMaxBytes > 16 * 1024) { // priority on no delays when the chunks are bigger // recompressing takes a moment and it's only a 2% memory save // less when traceChunkPoints is > 128, then it's only 1% return 0.0f; } if (memcmp(zstd_magic, chunk->compressed, sizeof(zstd_magic)) != 0) { return 0.0f; } int64_t before = 0; if (Modes.verbose) { before = nsThreadTime(); }; if (!passbuffer->dctx) { passbuffer->dctx = ZSTD_createDCtx(); } int uncompressed_len = stateBytes(chunk->numStates); int maxSize = ZSTD_compressBound(uncompressed_len); int totalBuffer = uncompressed_len + maxSize; char *uncompressed = check_grow_threadpool_buffer_t(passbuffer, totalBuffer); char *compressed = uncompressed + uncompressed_len; size_t res = ZSTD_decompressDCtx(passbuffer->dctx, uncompressed, uncompressed_len, chunk->compressed, chunk->compressed_size); if (ZSTD_isError(res)) { fprintf(stderr, "recompress(): Corrupt trace chunk: zstd error: %s\n", ZSTD_getErrorName(res)); return 0.0f; } if (!passbuffer->cctx) { passbuffer->cctx = ZSTD_createCCtx(); } size_t compressedSize = ZSTD_compressCCtx( passbuffer->cctx, compressed, maxSize, uncompressed, uncompressed_len, 2); if (ZSTD_isError(compressedSize)) { fprintf(stderr, "recompress() zstd error: %s\n", ZSTD_getErrorName(compressedSize)); return 0.0f; } int oldSize = chunk->compressed_size; //fprintf(stderr, "%5d %5d\n", (int) compressedSize, oldSize); if ((int) compressedSize < oldSize) { sfree(chunk->compressed); chunk->compressed = cmalloc(compressedSize); memcpy(chunk->compressed, compressed, compressedSize); chunk->compressed_size = compressedSize; } int newSize = chunk->compressed_size; float recompressSavings = 0.0f; if (newSize == 0) { fprintf(stderr, "chunk->compressed_size == 0\n"); } else { recompressSavings = (float) (oldSize - newSize) / (float) oldSize; } if (Modes.verbose) { int64_t after = nsThreadTime(); int64_t now = mstime(); pthread_mutex_lock(&Modes.traceDebugMutex); static int64_t tOld = 1; // ensure to avoid zero division static int64_t tNew = 1; tOld += oldSize; tNew += newSize; fprintTimePrecise(stderr, now); fprintf(stderr, " %s%06x compressChunk: cpu%7.3f ms compressed %8d ratio %5.2f chunkTime %5.1fh points %5d" " savings %4.1f old %5d new %5d chunks %3d | totalSavings %7.3f\n", ((a->addr & MODES_NON_ICAO_ADDRESS) ? "" : " "), a->addr, (after - before) * 1e-6, chunk->compressed_size, stateBytes(chunk->numStates) / (double) chunk->compressed_size, (chunk->lastTimestamp - chunk->firstTimestamp) / (double) HOURS, chunk->numStates, recompressSavings * 100.0f, oldSize, newSize, a->trace_chunk_len, (double) (tOld - tNew) / tOld * 100.0); pthread_mutex_unlock(&Modes.traceDebugMutex); } return recompressSavings; } static int minCurrentPoints(struct aircraft *a, int64_t now) { if (now - a->seenPosReliable > 15 * MINUTES) { return alignSFOUR(8); } else { return alignSFOUR(16); } } static int64_t traceChunkDuration() { return 60 * MINUTES; } static int compressChunk(fourState *source, int pointCount, threadpool_buffer_t *passbuffer, struct aircraft *a) { int64_t before = 0; if (pointCount < SFOUR || pointCount % SFOUR != 0) { fprintf(stderr, "eeZ2avaH\n"); return 0; } stateChunk *target = NULL; int64_t chunkDuration = traceChunkDuration(); stateChunk *lastChunk = NULL; int extending = 0; if (a->trace_chunk_len > 0) { lastChunk = &a->trace_chunks[a->trace_chunk_len - 1]; int64_t refTs = lastChunk->firstTimestamp; int k = 0; while(k < pointCount / SFOUR) { int64_t ts2 = getState(source, k * SFOUR + (SFOUR - 1))->timestamp; int64_t diff_last = ts2 - refTs; // minimize duration of last and next chunk if (diff_last > chunkDuration) { break; } k++; } extending = k * SFOUR; if (extending && lastChunk->compressed_size > Modes.traceChunkMaxBytes) { // make new chunk if the last one is pretty big already //fprintf(stderr, "not extending: pretty big already\n"); extending = 0; } if (extending && memcmp(zstd_magic, lastChunk->compressed, sizeof(zstd_magic)) != 0) { //fprintf(stderr, "not extending: zstd_magic\n"); extending = 0; } } // if there is a single inactive span just adding all points to the fresh chunk would be fine // but it's possible there is another inactive span, in this case we can save some memory by // making an extra chunk that only has the timejump if (!extending) { int64_t refTs; // keeping the actual inactive time jump in the chunk doesn't hurt // thus use the first SFOUR unit forward as reference to ignore the inactive jump to // determine how many points this chunk is created with // when extending the chunk this is disregarded so this stays a small chunk // still useful if (pointCount > SFOUR) { refTs = getState(source, SFOUR)->timestamp; } else { refTs = getState(source, 0)->timestamp; } int k = 0; while(k < pointCount / SFOUR) { int64_t ts2 = getState(source, k * SFOUR)->timestamp; int64_t diff_last = ts2 - refTs; // minimize duration of last and next chunk if (diff_last > chunkDuration) { break; } k++; } pointCount = k * SFOUR; } int newBytes = 0; if (extending) { pointCount = extending; // add to existing chunk // do some bookkeeping, we add the compressed size of the newly compressed chunk back to it a->trace_chunk_overall_bytes -= lastChunk->compressed_size; // tell rest of the code to write new details into existing stateChunk struct target = lastChunk; lastChunk = NULL; target->numStates = target->numStates + pointCount; // target->firstTimestamp stays the same target->lastTimestamp = getState(source, pointCount - 1)->timestamp; newBytes = stateBytes(pointCount); } else { if (lastChunk) { // recompress finished buffer recompressStateChunk(a, lastChunk, passbuffer); } // make new chunk a->chunkRecompressed = 0; target = resizeTraceChunks(a, a->trace_chunk_len + 1); if (!target) { fprintf(stderr, "%06x compressChunk error, resizeTraceChunks returned NULL, treat this as fatal and exit.\n", a->addr); setExit(2); return 0; } target->numStates = pointCount; target->firstTimestamp = getState(source, 0)->timestamp; target->lastTimestamp = getState(source, pointCount - 1)->timestamp; newBytes = stateBytes(pointCount); } if (Modes.verbose) { before = nsThreadTime(); }; size_t compressedSize = 0; if (1) { if (!passbuffer->cctx) { passbuffer->cctx = ZSTD_createCCtx(); } //fprintf(stderr, "pbuffer->size: %ld src.len %ld\n", (long) pbuffer->size, (long) src.len); if (0 && Modes.json_dir) { char path[1024]; snprintf(path, 1024, "%s/tracechunk_samples/%06x", Modes.json_dir, a->addr); int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd >= 0) { check_write(fd, source, newBytes, path); close(fd); } } /* * size_t ZSTD_compressCCtx(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize, int compressionLevel); */ // when extending the compressed data, the newly data is compressed and simply concatenated // with the old compressed data // when asking zstd to decompress the concatenated range of bytes, it will transparently // decompress all those concatenated compressed objects int maxSize = ZSTD_compressBound(newBytes); int totalBuffer = maxSize; if (extending) { totalBuffer += target->compressed_size; } char *compressed = check_grow_threadpool_buffer_t(passbuffer, totalBuffer); if (extending) { memcpy(compressed, target->compressed, target->compressed_size); sfree(target->compressed); compressed += target->compressed_size; } int compressionLvl = 2; compressedSize = ZSTD_compressCCtx( passbuffer->cctx, compressed, maxSize, source, newBytes, compressionLvl); if (ZSTD_isError(compressedSize)) { fprintf(stderr, "compressChunk() zstd error: %s\n", ZSTD_getErrorName(compressedSize)); exit(1); } } if (extending) { target->compressed_size = target->compressed_size + compressedSize; } else { target->compressed_size = compressedSize; } target->compressed = cmalloc(target->compressed_size); memcpy(target->compressed, passbuffer->buf, target->compressed_size); a->trace_chunk_overall_bytes += target->compressed_size; if (0) { fprintf(stderr, "compressChunk bytes per state: %4.1f size: %d extending: %d\n", (double) compressedSize / target->numStates, (int) compressedSize, extending); } if (Modes.verbose) { int64_t after = nsThreadTime(); int64_t now = mstime(); pthread_mutex_lock(&Modes.traceDebugMutex); fprintTimePrecise(stderr, now); fprintf(stderr, " %s%06x compressChunk: cpu%7.3f ms compressed %8d ratio %5.2f chunkTime %5.1fh points %5d %5d\n", ((a->addr & MODES_NON_ICAO_ADDRESS) ? "" : " "), a->addr, (after - before) * 1e-6, target->compressed_size, stateBytes(target->numStates) / (double) target->compressed_size, (target->lastTimestamp - target->firstTimestamp) / (double) HOURS, target->numStates, extending); //lp %5.1fh //(now - (getState(a->trace_current, a->trace_current_len - 1))->timestamp) / (double) HOURS, pthread_mutex_unlock(&Modes.traceDebugMutex); } return pointCount; } static void setTrace(struct aircraft *a, fourState *source, int len, threadpool_buffer_t *passbuffer) { if (len == 0) { traceCleanup(a); return; } int64_t now = mstime(); //fprintf(stderr, "%06x setTrace, len %ld fourStates %ld stateBytes %ld\n", a->addr, (long) len, (long) getFourStates(len), (long) stateBytes(len)); traceCleanupNoUnlink(a); a->trace_len = len; fourState *p = source; int chunkSize = alignSFOUR(Modes.traceChunkPoints); while (len > chunkSize + minCurrentPoints(a, mstime())) { int res = compressChunk(p, chunkSize, passbuffer, a); len -= res; p += res / SFOUR; //fprintf(stderr, "setTrace reduce len: %ld\n", (long) len); } a->trace_current_len = len; resizeTraceCurrent(a, now, 0, 0); if (a->trace_current_max < a->trace_current_len) { fprintf(stderr, "%06x setTrace error, insufficient current trace, discarding some data\n", a->addr); a->trace_current_len = 0; } else if (a->trace_current_len > 0) { //fprintf(stderr, "%06x setTrace current memcpy, len %ld fourStates %ld stateBytes %ld\n", a->addr, (long) len, (long) getFourStates(len), (long) stateBytes(len)); // keep buffered position intact -> +1 memcpy(a->trace_current, p, stateBytes(a->trace_current_len + 1)); } } static int get_nominal_trace_current_points(struct aircraft *a, int64_t now) { if (now - a->seenPosReliable > 15 * MINUTES) { return alignSFOUR(Modes.traceReserve + minCurrentPoints(a, now) + SFOUR); } else { return minCurrentPoints(a, now) + imax(alignSFOUR(Modes.traceRecentPoints), alignSFOUR(Modes.traceReserve + Modes.traceChunkPoints)); } } static void resizeTraceCurrent(struct aircraft *a, int64_t now, int extra, int force) { int newPoints = get_nominal_trace_current_points(a, now); int minPoints = alignSFOUR(a->trace_current_len + Modes.traceReserve); if (newPoints < minPoints) { newPoints = minPoints; } if (extra) { newPoints = alignSFOUR(newPoints + extra); } if (newPoints == a->trace_current_max && a->trace_current && !force) { if (0 && Modes.verbose) { fprintf(stderr, "len %d max %d\n", a->trace_current_len, a->trace_current_max); } return; } int newBytes = stateBytes(newPoints); fourState *new = cmalloc(newBytes); memset(new, 0x0, newBytes); if (a->trace_current) { memcpy(new, a->trace_current, stateBytes(a->trace_current_len + 1)); // 1 extra for buffered pos sfree(a->trace_current); } a->trace_current = new; a->trace_current_max = newPoints; } static void compressCurrent(struct aircraft *a, threadpool_buffer_t *passbuffer, int64_t now) { int keep = minCurrentPoints(a, now); int chunkPoints = ((a->trace_current_len - keep) / SFOUR) * SFOUR; int newLen = a->trace_current_len - chunkPoints; if (chunkPoints < SFOUR || newLen < keep) { return; } if (chunkPoints % SFOUR != 0) { fprintf(stderr, "<3> %06x compressCurrent: error: (chunkPoints %% SFOUR != 0)\n", a->addr); return; } if (a->trace_current_len < chunkPoints) { fprintf(stderr, "<3> %06x compressCurrent: error: trace_current_len < chunkPoints\n", a->addr); return; } // return actually compressed points int res = compressChunk(a->trace_current, chunkPoints, passbuffer, a); // current_len + 1 to account for the buffered position int oldBytes = stateBytes(a->trace_current_len + 1); a->trace_current_len -= res; int newBytes = stateBytes(a->trace_current_len + 1); int diffBytes = stateBytes(res); char *src = ((char *) a->trace_current) + diffBytes; char *dest = (char *) a->trace_current; if (newBytes + diffBytes != oldBytes) { fprintf(stderr, "<3> %06x compressCurrent very wrong, very bad!\n", a->addr); } memmove(dest, src, newBytes); } void traceMaintenance(struct aircraft *a, int64_t now, threadpool_buffer_t *passbuffer) { // free trace cache for inactive aircraft if (a->traceCache.entries && now - a->seenPosReliable > TRACE_CACHE_LIFETIME) { //fprintf(stderr, "%06x free traceCache\n", a->addr); destroyTraceCache(&a->traceCache); } //fprintf(stderr, "%06x\n", a->addr); if (a->trace_len == 0) { return; } // throw out old data if older than keep_trace or trace is getting full tracePrune(a, now); if (a->trace_len == 0) { return; } if (Modes.writeTraces) { // (Modes.traceDay != Modes.triggerPermWriteDay) -> true for the window of 0:15 to 0:55 int triggerActive = ( Modes.traceDay != Modes.triggerPermWriteDay && a->traceWrittenForYesterday != Modes.triggerPermWriteDay ); int64_t permCheckIval = GLOBE_PERM_IVAL; if (now > a->trace_next_perm) { // wait until end of the day and aircraft is inactive to write permanent trace // then once the day is over make sure it's written out // soften IOPS spike by writing out data for inactive aircraft // less inactive time is required the closer we get to the trigger that will write the // remaining (active) aircraft struct tm fifteenAgo = fifteenTime(now); int64_t toTrigger = 24 * HOURS - (fifteenAgo.tm_hour * HOURS + fifteenAgo.tm_min * MINUTES + fifteenAgo.tm_sec * SECONDS); int64_t posElapsed = now - a->seenPosReliable; int condition = 0; if (triggerActive) { a->trace_write |= WPERM; a->trace_next_perm = now + 18 * HOURS + random() % permCheckIval; condition = 1; } else if (posElapsed > 15 * MINUTES && posElapsed > 6 * (toTrigger - 10 * MINUTES)) { a->trace_write |= WPERM; a->trace_next_perm = now + 1 * HOURS; condition = 2; } else { // reschedule a->trace_next_perm = now + permCheckIval / 2 + (random() % permCheckIval / 2); } if (0 && condition) { fprintf(stderr, "|= WPERM %d %06x posElapsed %4.1fh toTrigger %4.1fh %d %d %d\n", condition, a->addr, (double) posElapsed / HOURS, (double) toTrigger / HOURS, a->traceWrittenForYesterday, Modes.triggerPermWriteDay, a->trace_len); } } if (now > a->trace_next_mw) { a->trace_write |= WMEM; } // on day change write out the traces for yesterday // for which day and which time span is written is determined by traceday if (triggerActive && a->trace_next_perm > now + permCheckIval) { if (a->addr == TRACE_FOCUS) { fprintf(stderr, "schedule_perm\n"); } a->trace_next_perm = now + random() % permCheckIval; } } if (a->trace_current_len > 0) { // reset trace_current allocation to nominal size if possible / necessary if (a->trace_current_max != get_nominal_trace_current_points(a, now)) { if (a->trace_current_max > get_nominal_trace_current_points(a, now)) { compressCurrent(a, passbuffer, now); } resizeTraceCurrent(a, now, 0, 0); } // multiple passes in case compressCurrent() isn't making enough room in trace_current int passes = 0; while (passes < 8 && a->trace_current_len + Modes.traceReserve >= a->trace_current_max) { compressCurrent(a, passbuffer, now); passes++; } if (passes > 2) { fprintf(stderr, "%06x compressCurrent: why so many passes? %d\n", a->addr, passes); } if (passes > 0) { // regularly reallocate certain buffers to reduce fragmentation due to very long lived // allocations resizeTraceCurrent(a, now, 0, 1); if (a->trace_chunk_len > 0) { resizeTraceChunks(a, a->trace_chunk_len); } destroyTraceCache(&a->traceCache); } // not so sure this is a good approach // maybe just do the recompress once the next chunk is created if (now - a->seenPosReliable > traceChunkDuration() && !a->chunkRecompressed && a->trace_chunk_len > 0) { stateChunk *lastChunk = &a->trace_chunks[a->trace_chunk_len - 1]; compressCurrent(a, passbuffer, now); if (lastChunk == &a->trace_chunks[a->trace_chunk_len - 1]) { recompressStateChunk(a, lastChunk, passbuffer); } } } } static int traceAddInternal(struct aircraft *a, struct modesMessage *mm, int64_t now, int stale) { int traceDebug = (a->addr == Modes.trace_focus); int save_state_no_buf = 0; int posUsed = 0; int bufferedPosUsed = 0; double distance = 0; int64_t elapsed = 0; int64_t elapsed_buffered = 0; int duplicate = 0; float speed_diff = 0; float track_diff = 0; float baro_rate_diff = 0; struct state *last = NULL; int64_t max_elapsed = Modes.json_trace_interval; int64_t min_elapsed = imin(250, max_elapsed / 4); float turn_density = 5.0; float max_speed_diff = 5.0; int alt = a->baro_alt; int alt_valid = altBaroReliableTrace(now, a); if (alt_valid && a->baro_alt > 10000) { max_speed_diff *= 2; } if (max_elapsed > 5 * SECONDS && a->pos_reliable_valid.source == SOURCE_MLAT) { min_elapsed = 1500; max_elapsed = imax(max_elapsed / 2, 5 * SECONDS); } // some towers on MLAT .... create unnecessary data // only reduce data produced for configurations with trace interval more than 5 seconds, others migh want EVERY DOT :) if (a->squawk_valid.source != SOURCE_INVALID && a->squawk == 0x7777) { min_elapsed = max_elapsed; } int on_ground = 0; float track = -1; if (trackVState(now, &a->track_valid, &a->pos_reliable_valid) && a->track_valid.source != SOURCE_MLAT) { track = a->track; } else { track = -1; } int agValid = 0; if (trackDataValid(&a->airground_valid)) { agValid = 1; if (a->airground == AG_GROUND) { on_ground = 1; if (trackVState(now, &a->true_heading_valid, &a->pos_reliable_valid)) { track = a->true_heading; } else { track = -1; } } } if (max_elapsed > 5 * SECONDS && a->pos_reliable_valid.source != SOURCE_MLAT && track == -1) { max_elapsed = imax(max_elapsed / 4, 5 * SECONDS); } if (a->trace_current_len == 0) goto save_state; last = getState(a->trace_current, a->trace_current_len - 1); if (now >= last->timestamp) { elapsed = now - last->timestamp; } struct state *buffered = NULL; if (a->tracePosBuffered) { buffered = getState(a->trace_current, a->trace_current_len); elapsed_buffered = (int64_t) buffered->timestamp - (int64_t) last->timestamp; } if (elapsed_buffered < 0) { fprintf(stderr, "%06x traceAdd len: %d current_len %d elapsed: %.3f elapsed_buffered %.3f mstime: %.3f now: %.3f last->timesatmp: %.3f\n", a->addr, a->trace_len, a->trace_current_len, elapsed / 1000.0, elapsed_buffered / 1000.0, mstime() / 1000.0, now / 1000.0, last->timestamp / 1000.0); buffered = NULL; a->tracePosBuffered = 0; elapsed_buffered = 0; } if (elapsed < 0) { fprintf(stderr, "%06x traceAdd elapsed: %.3f elapsed_buffered %.3f mstime: %.3f now: %.3f last->timesatmp: %.3f\n", a->addr, elapsed / 1000.0, elapsed_buffered / 1000.0, mstime() / 1000.0, now / 1000.0, last->timestamp / 1000.0); } int32_t new_lat = (int32_t) nearbyint(a->lat * 1E6); int32_t new_lon = (int32_t) nearbyint(a->lon * 1E6); duplicate = (elapsed < 1 * SECONDS && new_lat == last->lat && new_lon == last->lon); int last_alt = last->baro_alt / _alt_factor; int last_alt_valid = last->baro_alt_valid; int alt_diff = 0; if (last_alt_valid && alt_valid) { alt_diff = abs(a->baro_alt - last_alt); } if (trackDataValid(&a->gs_valid) && last->gs_valid && a->gs_valid.source != SOURCE_MLAT) { speed_diff = fabs(last->gs / _gs_factor - a->gs); } if (trackDataValid(&a->baro_rate_valid) && last->baro_rate_valid && a->baro_rate_valid.source != SOURCE_MLAT) { baro_rate_diff = fabs(last->baro_rate / _rate_factor - a->baro_rate); } // keep the last air ground state if the current isn't valid if (!agValid && !alt_valid) { on_ground = last->on_ground; } if (on_ground) { // just do this twice so we cover the first point in a trace as well as using the last airground state if (trackVState(now, &a->true_heading_valid, &a->pos_reliable_valid)) { track = a->true_heading; } else { track = -1; } } float last_track = last->track / _track_factor; if (last->track_valid && track > -1) { track_diff = fabs(norm_diff(track - last_track, 180)); } distance = greatcircle(last->lat / 1E6, last->lon / 1E6, a->lat, a->lon, 0); if (distance < 5) traceDebug = 0; if (traceDebug) { fprintf(stderr, "%11.6f,%11.6f %5.1fs d:%5.0f a:%6d D%4d s:%4.0f D%3.0f t: %5.1f D%5.1f ", a->lat, a->lon, elapsed / 1000.0, distance, alt, alt_diff, a->gs, speed_diff, a->track, track_diff); } if (speed_diff >= max_speed_diff) { if (traceDebug) { fprintf(stderr, "speed_change: %0.1f %0.1f -> %0.1f", speed_diff, last->gs / _gs_factor, a->gs); } save_state_no_buf = 1; } if (baro_rate_diff >= 200) { if (traceDebug) { fprintf(stderr, "baro_rate_change: %0.0f %0.0f -> %0.0f", baro_rate_diff, last->baro_rate / _rate_factor, (double) a->baro_rate); } save_state_no_buf = 1; } // record ground air state changes precisely if (on_ground != last->on_ground) { traceUsePosBuffered(a); // save the previous position as well if it's not already saved goto save_state; } if (now - a->lastAirGroundChange < Modes.afterGroundTransitionHighRes && elapsed > 750) { // record one point every second for configured time after ground state change save_state_no_buf = 1; } // record non moving targets every 5 minutes if (elapsed > 10 * max_elapsed) { goto save_state; } if (alt_valid && !last_alt_valid) { goto save_state; } // check altitude change before minimum interval if (alt_diff > 0) { int max_diff = 250; if (alt <= 7000) { max_diff = 75; } else if (alt <= 12000) { max_diff = 200; } else { max_diff = 400; } if (alt_diff >= max_diff) { if (traceDebug) fprintf(stderr, "alt_change1: %d -> %d", last_alt, alt); if (alt_diff < 250 && alt_diff * 3 <= max_diff * 4) { save_state_no_buf = 1; } else { goto save_state; } } int base = 800; if (alt <= 7000) { base = 125; } else if (alt <= 12000) { base = 250; } int64_t too_long = ((max_elapsed / 4) * base / (float) alt_diff); if (alt_diff >= 25 && elapsed > too_long) { if (traceDebug) fprintf(stderr, "alt_change2: %d -> %d, %5.1f", last_alt, alt, too_long / 1000.0); if (buffered && alt == buffered->baro_alt) { goto save_state; } else { save_state_no_buf = 1; } } } // don't record unnecessary many points if (elapsed < min_elapsed) goto no_save_state; // even if the squawk gets invalid we continue to record more points if (a->squawk == 0x7700) { goto save_state; } // record trace precisely if we have a TCAS advisory if (trackDataValid(&a->acas_ra_valid) && trackDataAge(now, &a->acas_ra_valid) < 15 * SECONDS) { goto save_state; } if (!on_ground && elapsed > max_elapsed) // default 30000 ms goto save_state; // SS2 if (a->addr == 0xa19b53 && elapsed > max_elapsed / 4) goto save_state; if (stale) { // save a point if reception is spotty so we can mark track as spotty on display goto save_state; } if (on_ground) { if (elapsed > 4 * max_elapsed) { goto save_state; } if (distance > 10 && elapsed > max_elapsed) { goto save_state; } if (a->gs > 5 && elapsed > max_elapsed / 2) { goto save_state; } if (distance * track_diff > 130) { if (traceDebug) fprintf(stderr, "track_change: %0.1f %0.1f -> %0.1f", track_diff, last_track, a->track); goto save_state; } if (distance > 40) goto save_state; // the distance change above is good enough for high resolution traces on the ground if (0 && speed_diff > 2.5f) { save_state_no_buf = 1; } } if (track_diff > 0.5 && (elapsed / 1000.0 * track_diff * turn_density > 100.0) ) { if (traceDebug) fprintf(stderr, "track_change: %0.1f %0.1f -> %0.1f", track_diff, last_track, a->track); goto save_state; } if (save_state_no_buf) { goto save_state_no_buf; } goto no_save_state; save_state: if (Modes.debug_position_timing && last && elapsed < 10) { fprintf(stderr, "%06x elapsed < 10 ms: %11.6f,%11.6f -> %11.6f,%11.6f %lldms d:%5.0f s: %4.0f sc: %4.0f\n", a->addr, last->lat * 1e-6, last->lon * 1e-6, a->lat, a->lon, (long long) elapsed, distance, a->gs, (distance * 1000 / elapsed) * (3600.0f/1852.0f)); } if (elapsed_buffered && elapsed_buffered < 10) { } // always try using the buffered position instead of the current one // this should provide a better picture of changing track / speed / altitude if (1 || elapsed > max_elapsed || 2 * elapsed_buffered > elapsed || elapsed_buffered > 2700) { if (traceUsePosBuffered(a)) { if (traceDebug) fprintf(stderr, " buffer\n"); // in some cases we want to add the current point as well // if not, the current point will be put in the buffer traceAddInternal(a, mm, now, stale); // return so the point isn't used a second time or put in the buffer return 1; } } save_state_no_buf: posUsed = 1; //fprintf(stderr, "traceAdd: %06x elapsed: %8.1f s distance: %8.3f km\n", a->addr, elapsed / 1000.0, distance / 1000.0); no_save_state: if (duplicate) { // don't put a duplicate position in the buffer and don't use it for the trace return 0; } if (!a->trace_current) { resizeTraceCurrent(a, now, 0, 0); scheduleMemBothWrite(a, now); // rewrite full history file a->trace_next_perm = now + GLOBE_PERM_IVAL / 2; // schedule perm write //fprintf(stderr, "%06x: new trace\n", a->addr); } // current_len still needs to be a usable index after being incremented if (a->trace_current_len + 1 >= a->trace_current_max - 1) { static int64_t antiSpam; if (Modes.debug_traceAlloc || now > antiSpam + 5 * SECONDS) { double elapsed_seconds = elapsed * 0.001; fprintf(stderr, "%06x trace_current_max insufficient (%d/%d) %11.6f,%11.6f %5.1fs d:%5.0f s: %4.0f sc: %4.0f\n", a->addr, a->trace_current_len, a->trace_current_max, a->lat, a->lon, elapsed_seconds, distance, a->gs, (distance / elapsed_seconds) * (3600.0f/1852.0f)); antiSpam = now; //displayModesMessage(mm); } return 0; } // add points before landing if ( last && on_ground != last->on_ground && on_ground && a->traceLast && getState(a->traceLast, a->traceLastNext)->timestamp != 0 ) { int64_t replaceBeforeTimestamp = -1; { int maxAdd = Modes.beforeLandHighRes; for (int k = 0; k < imin(maxAdd, Modes.traceLastMax); k++) { int i = a->traceLastNext - k - 1; if (i < 0) { i += Modes.traceLastMax; } struct state *state = getState(a->traceLast, i); replaceBeforeTimestamp = state->timestamp; } } int replaceAfter = -1; int64_t replaceAfterTimestamp = -1; for (int k = a->trace_current_len - 1; k >= 0; k--) { struct state *state = getState(a->trace_current, k); if (state->timestamp < replaceBeforeTimestamp) { replaceAfter = k; replaceAfterTimestamp = state->timestamp; break; } } if (replaceAfter > -1) { if (replaceAfter + Modes.traceLastMax + Modes.traceReserve >= a->trace_current_max) { resizeTraceCurrent(a, now, Modes.traceLastMax, 0); } if (replaceAfter + Modes.traceLastMax + Modes.traceReserve > a->trace_current_max) { fprintf(stderr, "error phe8EiQu\n"); replaceAfter = -1; } } if (replaceAfter > -1) { int debug = 0; if (debug) { fprintf(stderr, "%06x ", a->addr); } a->tracePosBuffered = 0; int replace = replaceAfter + 1; int i = a->traceLastNext; for (int k = 0; k < Modes.traceLastMax; k++, i = (i + 1) % Modes.traceLastMax) { struct state *state = getState(a->traceLast, i); if (state->timestamp > replaceAfterTimestamp) { if (replace % SFOUR != i % SFOUR) { continue; } replace++; } } if (replace > a->trace_current_len + SFOUR) { // only do this if we actually add multiple points // to line up the state_all we can be missing up to 3 points // which is of course undesirable int replace = replaceAfter + 1; int i = a->traceLastNext; for (int k = 0; k < Modes.traceLastMax; k++, i = (i + 1) % Modes.traceLastMax) { struct state *state = getState(a->traceLast, i); struct state_all *stateAll = getStateAll(a->traceLast, i); if (state->timestamp > replaceAfterTimestamp) { if (replace % SFOUR != i % SFOUR) { if (debug) { fprintf(stderr, ","); } continue; } if (a->trace_current_max - replace <= SFOUR) { fprintf(stderr, "error lahN8quu\n"); break; } struct state *r = getState(a->trace_current, replace); struct state_all *rAll = getStateAll(a->trace_current, replace); memcpy(r, state, sizeof(struct state)); if (rAll && stateAll) { memcpy(rAll, stateAll, sizeof(struct state_all)); } replace++; if (debug) { fprintf(stderr, "."); } } } int added = replace - a->trace_current_len; if (debug) { fprintf(stderr, " %d ", added); } a->trace_current_len = replace; a->trace_writeCounter += added; } if (debug) { fprintf(stderr, "\n"); } } } if (Modes.traceLastMax && !a->traceLast) { a->traceLast = cmCalloc(stateBytes(Modes.traceLastMax)); a->traceLastNext = 0; } struct state *new = getState(a->trace_current, a->trace_current_len); to_state(a, new, now, on_ground, track, stale); if (Modes.traceLastMax) { struct state *newLast = getState(a->traceLast, a->traceLastNext); struct state_all *newLastAll = getStateAll(a->traceLast, a->traceLastNext); a->traceLastNext = (a->traceLastNext + 1) % Modes.traceLastMax; memcpy(newLast, new, sizeof(struct state)); if (newLastAll) { to_state_all(a, newLastAll, now); } } // trace_all stuff: struct state_all *new_all = getStateAll(a->trace_current, a->trace_current_len); if (new_all) { to_state_all(a, new_all, now); } if (posUsed) { if (traceDebug) fprintf(stderr, " normal\n"); a->tracePosBuffered = 0; // bookkeeping: a->trace_len++; a->trace_current_len++; a->trace_write |= WRECENT; a->trace_writeCounter++; } else { a->tracePosBuffered = 1; } if (traceDebug && !posUsed && !bufferedPosUsed) fprintf(stderr, " none\n"); return posUsed || bufferedPosUsed; } int traceAdd(struct aircraft *a, struct modesMessage *mm, int64_t now, int stale) { if (!Modes.keep_traces) return 0; spinLock(&a->traceLock); int res = traceAddInternal(a, mm, now, stale); spinRelease(&a->traceLock); return res; } void save_blob(int blob, threadpool_buffer_t *pbuffer1, threadpool_buffer_t *pbuffer2, char *stateDir) { if (!stateDir) return; //static int count; //fprintf(stderr, "Save blob: %02x, count: %d\n", blob, ++count); if (blob < 0 || blob > STATE_BLOBS) { fprintf(stderr, "save_blob: invalid argument: %02x", blob); return; } int zst = 1; char filename[PATH_MAX]; char tmppath[PATH_MAX]; if (zst) { snprintf(filename, 1024, "%s/blob_%02x.zstl", stateDir, blob); } else { snprintf(filename, 1024, "%s/blob_%02x", stateDir, blob); } snprintf(tmppath, PATH_MAX, "%s.readsb_tmp", filename); int fd = open(tmppath, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { fprintf(stderr, "open failed:"); perror(tmppath); return; } int stride = Modes.acBuckets / STATE_BLOBS; int start = stride * blob; int end = start + stride; int alloc = Modes.state_chunk_size; unsigned char *buf = check_grow_threadpool_buffer_t(pbuffer1, alloc); unsigned char *p = buf; //fprintf(stderr, "buf %p p %p \n", buf, p); char *zst_out = NULL; int zst_out_alloc; int zst_header_len = 2 * sizeof(uint32_t); if (zst) { if (!pbuffer2->cctx) { pbuffer2->cctx = ZSTD_createCCtx(); } zst_out_alloc = ZSTD_compressBound(alloc); zst_out = check_grow_threadpool_buffer_t(pbuffer2, zst_out_alloc + zst_header_len); } int chunk_ac_count = 0; struct aircraft copyback; struct aircraft *copy = ©back; for (int j = start; j < end; j++) { for (struct aircraft *a = Modes.aircraft[j]; a || (j == end - 1); a = a->next) { int size_state = 0; if (!a) { copy = NULL; } else { // work on local copy of aircraft for traceUsePosBuffered memcpy(copy, a, sizeof(struct aircraft)); traceUsePosBuffered(copy); size_state += sizeof(struct aircraft); if (copy->trace_chunk_len > 0 && copy->trace_chunks == NULL) { fprintf(stderr, "<3> %06x trace corrupted, copy->trace_chunks is NULL but copy->trace_chunk_len > 0\n", copy->addr); } for (int k = 0; k < copy->trace_chunk_len; k++) { stateChunk *chunk = ©->trace_chunks[k]; size_state += sizeof(stateChunk); size_state += roundUp8(chunk->compressed_size); } size_state += stateBytes(copy->trace_current_len); // add space for 2 magic constants / 2 struct sizes size_state += 4 * sizeof(uint64_t); if (copy->traceLast) { size_state += sizeof(uint64_t); size_state += stateBytes(Modes.traceLastMax); } } if (!copy || (p + size_state > buf + alloc)) { //fprintf(stderr, "save_blob writing %d bytes (buffer %p alloc %d)\n", (int) ((p - buf)), p, alloc); uint64_t magic_end = STATE_SAVE_MAGIC_END; memcpy(p, &magic_end, sizeof(magic_end)); p += sizeof(magic_end); if (p > buf + alloc) { fprintf(stderr, "save_blob: overran buffer! %ld\n", (long) (p - (buf + alloc))); } if (zst) { uint32_t uncompressed_len = p - buf; /* * size_t ZSTD_compressCCtx(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize, int compressionLevel); */ size_t compressedSize = ZSTD_compressCCtx(pbuffer2->cctx, zst_out + zst_header_len, zst_out_alloc, buf, uncompressed_len, 1); if (ZSTD_isError(compressedSize)) { fprintf(stderr, "save_blob() zstd error: %s\n", ZSTD_getErrorName(compressedSize)); goto error; } uint32_t compressed_len = compressedSize; // write header memcpy(zst_out, &compressed_len, sizeof(uint32_t)); memcpy(zst_out + sizeof(uint32_t), &uncompressed_len, sizeof(uint32_t)); // end header check_write(fd, zst_out, compressed_len + zst_header_len, tmppath); } else { check_write(fd, buf, p - buf, tmppath); } if (size_state > alloc) { int old_alloc = alloc; alloc = imax(2 * size_state, Modes.state_chunk_size); if (alloc > Modes.state_chunk_size) { Modes.state_chunk_size = alloc; // increase chunk size for later invocations fprintf(stderr, "%06x: Increasing state_chunk_size to %d! chunk_ac_count %d size_state %d old_alloc %d\n", copy->addr, (int) alloc, chunk_ac_count, (int) size_state, (int) old_alloc); } buf = check_grow_threadpool_buffer_t(pbuffer1, alloc); p = buf; } p = buf; chunk_ac_count = 0; } if (!copy) { break; } if (p + size_state > buf + alloc) { fprintf(stderr, "<3> %06x: Couldn't write internal state, check save_blob code! chunk_ac_count %d size_state %d alloc %d\n", copy->addr, chunk_ac_count, (int) size_state, alloc); continue; } chunk_ac_count++; uint64_t magic = STATE_SAVE_MAGIC; p += memcpySize(p, &magic, sizeof(magic)); uint64_t size_aircraft = sizeof(struct aircraft); p += memcpySize(p, &size_aircraft, sizeof(size_aircraft)); aircraftZeroTail(copy); p += memcpySize(p, copy, sizeof(struct aircraft)); if (copy->trace_len > 0) { uint64_t fourState_size = sizeof(fourState); p += memcpySize(p, &fourState_size, sizeof(fourState_size)); for (int k = 0; k < copy->trace_chunk_len; k++) { stateChunk *chunk = ©->trace_chunks[k]; p += memcpySize(p, chunk, sizeof(stateChunk)); p += memcpySize(p, chunk->compressed, chunk->compressed_size); ssize_t padBytes = roundUp8(chunk->compressed_size) - chunk->compressed_size; if (padBytes > 0) { memset(p, 0x0, padBytes); p += padBytes; } else if (padBytes < 0) { fprintf(stderr, "padBytes %ld roundUp8 %ld compressed_size %ld\n", (long) padBytes, (long) roundUp8(chunk->compressed_size), (long) chunk->compressed_size); } } p += memcpySize(p, copy->trace_current, stateBytes(copy->trace_current_len)); if (copy->traceLast) { uint64_t traceLastMax = Modes.traceLastMax; p += memcpySize(p, &traceLastMax, sizeof(traceLastMax)); p += memcpySize(p, copy->traceLast, stateBytes(Modes.traceLastMax)); } } } } if (fd != -1) { close(fd); } if (rename(tmppath, filename) == -1) { fprintf(stderr, "save_blob rename(): %s -> %s", tmppath, filename); perror(""); unlink(tmppath); } goto out; error: if (fd != -1) { close(fd); } unlink(tmppath); out: ; } static int load_aircrafts(char *p, char *end, char *filename, int64_t now, threadpool_buffer_t *passbuffer) { int count = 0; while (end - p > 0) { uint64_t value = 0; if (end - p >= (long) sizeof(value)) { p += memcpySize(&value, p, sizeof(value)); } if (value != STATE_SAVE_MAGIC) { if (value != STATE_SAVE_MAGIC_END) { fprintf(stderr, "Incomplete state file (or state format was changed and is incompatible with new format): %s\n", filename); return -1; } break; } load_aircraft(&p, end, now, passbuffer); count++; } return count; } void load_blob(char *blob, threadpool_threadbuffers_t * buffer_group) { int64_t now = mstime(); int fd = -1; struct char_buffer cb; char *p; char *end; int zst = 0; char filename[1024]; snprintf(filename, 1024, "%s.zstl", blob); fd = open(filename, O_RDONLY); if (fd != -1) { zst = 1; cb = readWholeFile(fd, filename); close(fd); } else { Modes.writeInternalState = 1; // not the primary load method, immediately write state snprintf(filename, 1024, "%s", blob); fd = open(blob, O_RDONLY); if (fd == -1) { fprintf(stderr, "missing state blob:"); snprintf(filename, 1024, "%s.zstl", blob); perror(filename); return; } cb = readWholeFile(fd, filename); close(fd); unlink(filename); } if (!cb.buffer) return; p = cb.buffer; end = p + cb.len; threadpool_buffer_t *pb1 = &buffer_group->buffers[0]; threadpool_buffer_t *pb2 = &buffer_group->buffers[1]; if (zst) { while (end - p > 0) { if (end - p < 2 * (ssize_t) sizeof(uint32_t)) { fprintf(stderr, "Corrupt state file (too small): %s\n", filename); goto out; } uint32_t compressed_len = *((uint32_t *) p); p += sizeof(compressed_len); uint32_t uncompressed_len = *((uint32_t *) p); p += sizeof(uncompressed_len); if (end - p < (ssize_t) compressed_len) { fprintf(stderr, "Corrupt state file (smaller than compressed_len): %s\n", filename); goto out; } if (!pb1->dctx) { pb1->dctx = ZSTD_createDCtx(); } char *uncompressed = check_grow_threadpool_buffer_t(pb1, uncompressed_len); char *compressed = p; size_t res = ZSTD_decompressDCtx(pb1->dctx, uncompressed, uncompressed_len, compressed, compressed_len); if (ZSTD_isError(res)) { fprintf(stderr, "Corrupt state file %s zstd error: %s\n", filename, ZSTD_getErrorName(res)); goto out; } if (load_aircrafts(uncompressed, uncompressed + uncompressed_len, filename, now, pb2) < 0) { goto out; } p += compressed_len; } } else { load_aircrafts(p, end, filename, now, pb2); } out: sfree(cb.buffer); } static void load_blobs(void *arg, threadpool_threadbuffers_t * buffer_group) { readsb_task_t *info = (readsb_task_t *) arg; for (int j = info->from; j < info->to; j++) { char blob[1024]; snprintf(blob, 1024, "%s/blob_%02x", Modes.state_dir, j); load_blob(blob, buffer_group); } } static inline void heatmapCheckAlloc(struct heatEntry **buffer, int64_t **slices, int64_t *alloc, int64_t len) { if (!*buffer || len + 8 >= *alloc) { *alloc += 8; *alloc *= 3; *buffer = realloc(*buffer, *alloc * sizeof(struct heatEntry)); *slices = realloc(*slices, *alloc * sizeof(int64_t)); } if (!*buffer || !*slices || *alloc < 0) { fprintf(stderr, "<3> FATAL: handleHeatmap not enough memory, trying to allocate %lld bytes\n", (((long long) * alloc) * sizeof(struct heatEntry))); exit(1); } } static void checkMiscBreak() { // take a break now and then and let maintenance functions run // wait in 50 ms increments while (priorityTasksPending()) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); threadTimedWait(&Threads.misc, &ts, 50); } } int handleHeatmap(int64_t now) { if (!Modes.heatmap) return 0; time_t nowish = (now - 30 * MINUTES)/1000; struct tm utc; gmtime_r(&nowish, &utc); int half_hour = utc.tm_hour * 2 + utc.tm_min / 30; if (Modes.heatmap_current_interval < -1) { Modes.heatmap_current_interval++; return 0; // startup delay before first time heatmap is written } // don't write on startup when persistent state isn't enabled if (!Modes.state_dir && Modes.heatmap_current_interval < 0) { Modes.heatmap_current_interval = half_hour; return 0; } // only do this every 30 minutes. if (half_hour == Modes.heatmap_current_interval) return 0; Modes.heatmap_current_interval = half_hour; utc.tm_hour = half_hour / 2; utc.tm_min = 30 * (half_hour % 2); utc.tm_sec = 0; int64_t start = 1000 * (int64_t) (timegm(&utc)); int64_t end = start + 30 * MINUTES; int64_t num_slices = (int64_t)((30 * MINUTES) / Modes.heatmap_interval); char pathbuf[PATH_MAX]; char tmppath[PATH_MAX]; int64_t len = 0; int64_t len2 = 0; int64_t alloc = (50 + Modes.globalStatsCount.readsb_aircraft_with_position) * num_slices; struct heatEntry *buffer = NULL; int64_t *slices = NULL; heatmapCheckAlloc(&buffer, &slices, &alloc, len); threadpool_buffer_t passbuffer = { 0 }; for (int j = 0; j < Modes.acBuckets; j++) { checkMiscBreak(); for (struct aircraft *a = Modes.aircraft[j]; a; a = a->next) { if ((a->addr & MODES_NON_ICAO_ADDRESS) && a->airground == AG_GROUND) continue; if (a->trace_len == 0) continue; traceBuffer tb = reassembleTrace(a, -1, start, &passbuffer); int64_t next = start; int64_t slice = 0; uint32_t squawk = 0x8888; // impossible squawk uint64_t callsign = 0; // quackery int64_t callsign_interval = imax(Modes.heatmap_interval, 1 * MINUTES); int64_t next_callsign = start; for (int i = 0; i < tb.len; i++) { struct state *state = getState(tb.trace, i); if (state->timestamp > end) break; struct state_all *all = getStateAll(tb.trace, i); if (state->timestamp >= start && all) { uint64_t *cs = (uint64_t *) &(all->callsign); if (state->timestamp >= next_callsign || *cs != callsign || squawk != all->squawk) { next_callsign = state->timestamp + callsign_interval; callsign = *cs; squawk = all->squawk; uint32_t s = all->squawk; int32_t d = (s & 0xF) + 10 * ((s & 0xF0) >> 4) + 100 * ((s & 0xF00) >> 8) + 1000 * ((s & 0xF000) >> 12); buffer[len].hex = a->addr; buffer[len].lat = (1 << 30) | d; memcpy(&buffer[len].lon, all->callsign, 8); //if (a->addr == Modes.leg_focus) { // fprintf(stderr, "squawk: %d %04x\n", d, s); //} slices[len] = slice; len++; heatmapCheckAlloc(&buffer, &slices, &alloc, len); } } if (state->timestamp < next) continue; while (state->timestamp > next + Modes.heatmap_interval) { next += Modes.heatmap_interval; slice++; } uint32_t addrtype_5bits = ((uint32_t) state->addrtype) & 0x1F; buffer[len].hex = a->addr | (addrtype_5bits << 27); buffer[len].lat = state->lat; buffer[len].lon = state->lon; // altitude encoded in steps of 25 ft ... file convention if (state->on_ground) buffer[len].alt = -123; // on ground else if (state->baro_alt_valid) buffer[len].alt = nearbyint(state->baro_alt / (_alt_factor * 25.0f)); else if (state->geom_alt_valid) buffer[len].alt = nearbyint(state->geom_alt / (_alt_factor * 25.0f)); else buffer[len].alt = -124; // unknown altitude if (state->gs_valid) buffer[len].gs = nearbyint(state->gs / _gs_factor * 10.0f); else buffer[len].gs = -1; // invalid slices[len] = slice; len++; heatmapCheckAlloc(&buffer, &slices, &alloc, len); next += Modes.heatmap_interval; slice++; } } } free_threadpool_buffer(&passbuffer); struct heatEntry *buffer2 = cmalloc(alloc * sizeof(struct heatEntry)); ssize_t indexSize = num_slices * sizeof(struct heatEntry); struct heatEntry *index = cmalloc(indexSize); if (!buffer2 || !index) { return 0; } //////////// UNLOCK MISC pthread_mutex_unlock(&Threads.misc.mutex); //////////// UNLOCK MISC memset(index, 0, indexSize); // avoid having to set zero individually for (int i = 0; i < num_slices; i++) { struct heatEntry specialSauce = (struct heatEntry) {0}; int64_t slice_stamp = start + i * Modes.heatmap_interval; specialSauce.hex = 0xe7f7c9d; specialSauce.lat = slice_stamp >> 32; specialSauce.lon = slice_stamp & ((1ULL << 32) - 1); specialSauce.alt = Modes.heatmap_interval; index[i].hex = len2 + num_slices; buffer2[len2++] = specialSauce; for (int k = 0; k < len; k++) { if (slices[k] == i) buffer2[len2++] = buffer[k]; } } char *base_dir = Modes.globe_history_dir; if (Modes.heatmap_dir) { base_dir = Modes.heatmap_dir; } mkdir_error(base_dir, 0755, stderr); char dateDir[PATH_MAX * 3/4]; createDateDir(base_dir, &utc, dateDir); snprintf(pathbuf, PATH_MAX, "%s/heatmap", dateDir); mkdir_error(pathbuf, 0755, stderr); snprintf(pathbuf, PATH_MAX, "%s/heatmap/%02d.bin.ttf", dateDir, half_hour); snprintf(tmppath, PATH_MAX, "%s.readsb_tmp", pathbuf); //fprintf(stderr, "%s using %d positions\n", pathbuf, len); int fd = open(tmppath, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror(tmppath); } else { int res; gzFile gzfp = gzdopen(fd, "wb"); if (!gzfp) fprintf(stderr, "heatmap: gzdopen fail"); if (gzbuffer(gzfp, GZBUFFER_BIG) < 0) fprintf(stderr, "gzbuffer fail"); res = gzsetparams(gzfp, 9, Z_DEFAULT_STRATEGY); if (res < 0) fprintf(stderr, "gzsetparams fail: %d", res); writeGz(gzfp, index, indexSize, tmppath); ssize_t toWrite = len2 * sizeof(struct heatEntry); writeGz(gzfp, buffer2, toWrite, tmppath); gzclose(gzfp); } if (rename(tmppath, pathbuf) == -1) { fprintf(stderr, "heatmap rename(): %s -> %s", tmppath, pathbuf); perror(""); } free(index); free(buffer); free(buffer2); free(slices); //////////// LOCK MISC pthread_mutex_lock(&Threads.misc.mutex); //////////// LOCK MISC return 1; } static void compressACAS(char *dateDir) { char filename[PATH_MAX]; snprintf(filename, PATH_MAX, "%s/acas/acas.csv", dateDir); gzipFile(filename); unlink(filename); snprintf(filename, PATH_MAX, "%s/acas/acas.json", dateDir); gzipFile(filename); unlink(filename); } void checkNewDay(int64_t now) { if (!Modes.globe_history_dir || !Modes.writeTraces) return; static int64_t next_check; if (now < next_check) { return; } next_check = now + 5 * SECONDS; char filename[PATH_MAX]; char dateDir[PATH_MAX * 3/4]; // at 15 min past midnight, start a permanent write of all traces // create the new directory for writing traces struct tm fifteenAgo = fifteenTime(now); if (fifteenAgo.tm_mday != Modes.triggerPermWriteDay) { Modes.triggerPermWriteDay = fifteenAgo.tm_mday; createDateDir(Modes.globe_history_dir, &fifteenAgo, dateDir); snprintf(filename, PATH_MAX, "%s/traces", dateDir); int err = mkdir_error(filename, 0755, stderr); // if the directory exists we assume we already have created the subdirectories // if the directory couldn't be created no need to try and create subdirectories it won't work. if (!err) { for (int i = 0; i < 256; i++) { snprintf(filename, PATH_MAX, "%s/traces/%02x", dateDir, i); mkdir_error(filename, 0755, stderr); } } time_t yesterday = now / 1000 - 24 * 3600; struct tm tm_yesterday; gmtime_r(&yesterday, &tm_yesterday); // this is just to change dateDir because, it usually doesn't create any directories as they // already exist createDateDir(Modes.globe_history_dir, &tm_yesterday, dateDir); // compress ACAS those files which switched over to new directory at midnight compressACAS(dateDir); } // fiftyfiveAgo changes day 55 min after midnight: stop writing the previous days traces struct tm fiftyfiveAgo = fiftyfiveTime(now); if (Modes.traceDay != fiftyfiveAgo.tm_mday) { Modes.traceDay = fiftyfiveAgo.tm_mday; } } // this blocks other stuff, so the compression is done a bit later in the checkNewDay function which // does not block other stuff void checkNewDayAcas(int64_t now) { if (!Modes.globe_history_dir || !Modes.writeTraces) return; struct tm utc; time_t time = now / 1000; gmtime_r(&time, &utc); if (utc.tm_mday != Modes.acasDay) { Modes.acasDay = utc.tm_mday; char filename[PATH_MAX]; char dateDir[PATH_MAX * 3/4]; createDateDir(Modes.globe_history_dir, &utc, dateDir); snprintf(filename, PATH_MAX, "%s/acas", dateDir); mkdir_error(filename, 0755, stderr); if (Modes.acasFD1 > -1) close(Modes.acasFD1); if (Modes.acasFD2 > -1) close(Modes.acasFD2); if (Modes.enableAcasCsv) { snprintf(filename, PATH_MAX, "%s/acas/acas.csv", dateDir); Modes.acasFD1 = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644); if (Modes.acasFD1 < 0) { fprintf(stderr, "open failed:"); perror(filename); } } if (Modes.enableAcasJson) { snprintf(filename, PATH_MAX, "%s/acas/acas.json", dateDir); Modes.acasFD2 = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644); if (Modes.acasFD2 < 0) { fprintf(stderr, "open failed:"); perror(filename); } } } } void writeRangeDirs() { if (!Modes.state_dir || !Modes.outline_json) { return; } char pathbuf[PATH_MAX]; snprintf(pathbuf, PATH_MAX, "%s/rangeDirs.gz", Modes.state_dir); gzFile gzfp = gzopen(pathbuf, "wb"); if (gzbuffer(gzfp, GZBUFFER_BIG) < 0) fprintf(stderr, "gzbuffer fail"); if (gzfp) { writeGz(gzfp, &Modes.lastRangeDirHour, sizeof(Modes.lastRangeDirHour), pathbuf); writeGz(gzfp, Modes.rangeDirs, RANGEDIRSSIZE, pathbuf); gzclose(gzfp); } } static void writeInternalMiscTask(void *arg, threadpool_threadbuffers_t * buffers) { MODES_NOTUSED(arg); MODES_NOTUSED(buffers); writeRangeDirs(); } static void readInternalMiscTask(void *arg, threadpool_threadbuffers_t * buffers) { MODES_NOTUSED(arg); MODES_NOTUSED(buffers); if (Modes.state_dir && Modes.outline_json) { char pathbuf[PATH_MAX]; struct char_buffer cb; snprintf(pathbuf, PATH_MAX, "%s/rangeDirs.gz", Modes.state_dir); gzFile gzfp = gzopen(pathbuf, "r"); if (gzfp) { cb = readWholeGz(gzfp, pathbuf); gzclose(gzfp); if (cb.len == sizeof(Modes.lastRangeDirHour) + RANGEDIRSSIZE) { fprintf(stderr, "actual range outline, read bytes: %zu\n", cb.len); char *p = cb.buffer; memcpy(&Modes.lastRangeDirHour, p, sizeof(Modes.lastRangeDirHour)); p += sizeof(Modes.lastRangeDirHour); memcpy(Modes.rangeDirs, p, RANGEDIRSSIZE); } free(cb.buffer); } } } void writeInternalState() { struct timespec watch; if (Modes.state_dir) { fprintf(stderr, "saving state .....\n"); startWatch(&watch); } int64_t now = mstime(); int parts = STATE_BLOBS; int stride = 1; threadpool_t *pool = threadpool_create(Modes.num_procs, 4); task_group_t *group = allocate_task_group(parts + 1); threadpool_task_t *tasks = group->tasks; readsb_task_t *infos = group->infos; // assign tasks int taskCount = 0; { threadpool_task_t *task = &tasks[taskCount]; task->function = writeInternalMiscTask; task->argument = NULL; taskCount++; } for (int i = 0; i < parts; i++) { threadpool_task_t *task = &tasks[taskCount]; readsb_task_t *range = &infos[taskCount]; range->now = now; range->from = i * stride; range->to = imin(STATE_BLOBS, range->from + stride); //fprintf(stderr, "from %d to %d\n", range->from, range->to); task->function = save_blobs; task->argument = range; taskCount++; } // run tasks threadpool_run(pool, tasks, taskCount); threadpool_destroy(pool); destroy_task_group(group); if (Modes.state_dir) { double elapsed = stopWatch(&watch) / 1000.0; fprintf(stderr, " .......... done, saved %llu aircraft in %.3f seconds!\n", (unsigned long long) Modes.total_aircraft_count, elapsed); } } void readInternalState() { int retval = mkdir(Modes.state_dir, 0755); if (retval != 0 && errno != EEXIST) { fprintf(stderr, "Unable to create state directory (%s): %s\n", Modes.state_dir, strerror(errno)); return; } if (retval == 0) { fprintf(stderr, "%s: state directory didn't exist, created it, possible reasons: " "first start with state enabled / directory not backed by persistent storage\n", Modes.state_dir); fprintf(stderr, "loading state ..... FAILED!\n"); return; } fprintf(stderr, "loading state .....\n"); struct timespec watch; startWatch(&watch); int64_t now = mstime(); int parts = STATE_BLOBS; int stride = 1; threadpool_t *pool = threadpool_create(Modes.num_procs, 4); task_group_t *group = allocate_task_group(parts + 1); threadpool_task_t *tasks = group->tasks; readsb_task_t *infos = group->infos; // assign tasks int taskCount = 0; { threadpool_task_t *task = &tasks[taskCount]; task->function = readInternalMiscTask; task->argument = NULL; taskCount++; } int k = STATE_BLOBS - 1; for (int i = 0; i < parts; i++) { threadpool_task_t *task = &tasks[taskCount]; readsb_task_t *range = &infos[taskCount]; range->now = now; range->from = k * stride; range->to = imin(STATE_BLOBS, (k + 1) * stride); k--; //fprintf(stderr, "from %d to %d\n", range->from, range->to); task->function = load_blobs; task->argument = range; taskCount++; } // run tasks threadpool_run(pool, tasks, taskCount); threadpool_destroy(pool); destroy_task_group(group); int64_t aircraftCount = 0; // includes quite old aircraft, just for checking hash table fill for (int j = 0; j < Modes.acBuckets; j++) { for (struct aircraft *a = Modes.aircraft[j]; a; a = a->next) { aircraftCount++; } } Modes.total_aircraft_count = aircraftCount; double elapsed = stopWatch(&watch) / 1000.0; fprintf(stderr, " .......... done, loaded %llu aircraft in %.3f seconds!\n", (unsigned long long) aircraftCount, elapsed); fprintf(stderr, "aircraft table fill: %0.1f\n", aircraftCount / (double) Modes.acBuckets ); } void unlinkPerm(struct aircraft *a) { if (!Modes.globe_history_dir) { return; } int64_t now = mstime(); a->trace_perm_last_timestamp = 0; // fiftyfive_ago changes day 55 min after midnight: stop writing the previous days traces struct tm fiftyfive = fiftyfiveTime(now); // we just use the day of the struct tm in the next lines fiftyfive.tm_sec = 0; fiftyfive.tm_min = 0; fiftyfive.tm_hour = 0; char tstring[100]; strftime (tstring, 100, TDATE_FORMAT, &fiftyfive); char filename[PATH_MAX]; snprintf(filename, PATH_MAX, "%s/%s/traces/%02x/trace_full_%s%06x.json", Modes.globe_history_dir, tstring, a->addr % 256, (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); filename[PATH_MAX - 101] = 0; unlink(filename); } void traceDelete() { struct hexInterval* entry = Modes.deleteTrace; if (!entry) { return; } threadpool_buffer_t passbuffer = { 0 }; while (entry) { struct hexInterval* curr = entry; struct aircraft *a = aircraftGet(curr->hex); if (!a) { fprintf(stderr, "Deleting trace points, aircraft not found: %06x\n", curr->hex); goto next; } traceUnlink(a); unlinkPerm(a); if (a->trace_len == 0) { fprintf(stderr, "Deleting trace points, aircraft has no trace: %06x\n", curr->hex); goto next; } traceUsePosBuffered(a); traceBuffer tb = reassembleTrace(a, -1, -1, &passbuffer); fourState *trace = tb.trace; int trace_len = tb.len; int old_len = trace_len; int start = 0; int end = trace_len + SFOUR; // point well past the end if not found int64_t from = curr->from * 1000; int64_t to = curr->to * 1000; for (int i = 0; i < trace_len; i += SFOUR) { int64_t timestamp = getState(trace, i)->timestamp; if (timestamp <= from) { start = i; } if (timestamp > to) { end = i; break; } } // align to fourState, delete whole fourState tuple if stuff we want to delete is contained start = start / SFOUR; // round down end = getFourStates(end); // end points past the last point to be deleted if (end >= getFourStates(trace_len)) { trace_len = imax(0, (start - 1) * SFOUR); } else if (end - start > 0) { memmove(trace + start, trace + end, (getFourStates(trace_len) - end) * sizeof(fourState)); trace_len -= (end - start) * SFOUR; } setTrace(a, trace, trace_len, &passbuffer); int64_t now = mstime(); a->trace_next_perm = now; scheduleMemBothWrite(a, now); traceMaintenance(a, now, &passbuffer); // write immediately: a->trace_write |= WRECENT; a->trace_write |= WPERM; a->trace_write |= WMEM; fprintf(stderr, "Deleted %06x from %lld to %lld trace_len %ld -> %ld\n", curr->hex, (long long) curr->from, (long long) curr->to, (long) old_len, (long) trace_len); next: entry = entry->next; sfree(curr); } Modes.deleteTrace = NULL; free_threadpool_buffer(&passbuffer); } /* void *load_state(void *arg) { int64_t now = mstime(); char pathbuf[PATH_MAX]; //struct stat fileinfo = {0}; //fstat(fd, &fileinfo); //off_t len = fileinfo.st_size; int thread_number = *((int *) arg); srandom(get_seed()); for (int i = 0; i < 256; i++) { if (i % Modes.io_threads != thread_number) continue; snprintf(pathbuf, PATH_MAX, "%s/%02x", Modes.state_dir, i); DIR *dp; struct dirent *ep; dp = opendir (pathbuf); if (dp == NULL) continue; while ((ep = readdir (dp))) { if (strlen(ep->d_name) < 6) continue; snprintf(pathbuf, PATH_MAX, "%s/%02x/%s", Modes.state_dir, i, ep->d_name); int fd = open(pathbuf, O_RDONLY); if (fd == -1) continue; struct char_buffer cb = readWholeFile(fd, pathbuf); if (!cb.buffer) continue; char *p = cb.buffer; char *end = p + cb.len; load_aircraft(&p, end, now); free(cb.buffer); close(fd); // old internal state format, no longer needed unlink(pathbuf); } closedir (dp); } return NULL; } */ readsb-3.16/globe_index.h000066400000000000000000000055731505057307600153550ustar00rootroot00000000000000#ifndef GLOBE_INDEX_H #define GLOBE_INDEX_H #define TRACE_FOCUS BADDR #define WRECENT (1<<10) #define WMEM (1<<11) #define WPERM (1<<12) // this setting is no longer an interval how often this is written, rather the interval to check if // it should be written #define GLOBE_PERM_IVAL (10 * MINUTES) #define GLOBE_MEM_IVAL (30 * MINUTES) #define GLOBE_INDEX_GRID 3 #define GLOBE_SPECIAL_INDEX 70 #define GLOBE_LAT_MULT (360 / GLOBE_INDEX_GRID + 1) #define GLOBE_MIN_INDEX (1000) #define GLOBE_MAX_INDEX (180 / GLOBE_INDEX_GRID * GLOBE_LAT_MULT + GLOBE_MIN_INDEX) #define TDATE_FORMAT "%Y/%m/%d" #define TRACE_STALE (15 * SECONDS) #ifndef TRACE_RECENT_POINTS #define TRACE_RECENT_POINTS (92) #endif #define TRACE_CACHE_LIFETIME (1 * MINUTES) #define TRACE_CACHE_EXTRA (8) struct tile { int south; int west; int north; int east; }; void checkNewDay(int64_t now); void checkNewDayAcas(int64_t now); int globe_index(double lat_in, double lon_in); int globe_index_index(int index); void init_globe_index(); void cleanup_globe_index(); void save_blob(int blob, threadpool_buffer_t *pbuffer1, threadpool_buffer_t *pbuffer2, char *stateDir); void load_blob(char *blob, threadpool_threadbuffers_t * buffer_group); void writeRangeDirs(); void writeInternalState(); void readInternalState(); void traceWrite(struct aircraft *a, threadpool_threadbuffers_t *buffer_group); void traceCleanup(struct aircraft *a); void traceCleanupNoUnlink(struct aircraft *a); int traceAdd(struct aircraft *a, struct modesMessage *mm, int64_t now, int stale); int traceUsePosBuffered(struct aircraft *a); void traceMaintenance(struct aircraft *a, int64_t now, threadpool_buffer_t *passbuffer); int handleHeatmap(int64_t now); struct craftArray { struct aircraft **list; int len; int alloc; // memory allocated for aircraft pointers // unclean changing of arrays, we always check for NULL when iterating pthread_mutex_t change_mutex; // be strict with reallocation operations pthread_mutex_t read_mutex; int reader_count; pthread_mutex_t write_mutex; }; void ca_lock_read(struct craftArray *ca); void ca_unlock_read(struct craftArray *ca); void ca_init (struct craftArray *ca); void ca_destroy (struct craftArray *ca); void ca_remove (struct craftArray *ca, struct aircraft *a); void ca_add (struct craftArray *ca, struct aircraft *a); void set_globe_index (struct aircraft *a, int new_index); // this format is fixed, don't change. // if the latitude has bit 30 set (lat & (1<<30)), it's an info entry: // the lowest 12 bits of the lat contain squawk digits as a decimal number // lon and alt together contain the 8 byte callsign struct heatEntry { int32_t hex; int32_t lat; int32_t lon; int16_t alt; int16_t gs; } __attribute__ ((__packed__)); void traceDelete(); struct hexInterval { struct hexInterval* next; uint32_t hex; int64_t from; int64_t to; }; #endif readsb-3.16/hello.c000066400000000000000000000001011505057307600141520ustar00rootroot00000000000000#include int main() { printf("Hello, world.\n"); } readsb-3.16/help.h000066400000000000000000000533371505057307600140270ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // help.h: main program help header // // Copyright (c) 2019 Michael Wolf // // This file 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 // any later version. // // This file 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 . #ifndef HELP_H #define HELP_H #include "argp.h" const char *argp_program_version = VERSION_STRING; const char *argp_program_bug_address = "Matthias Wirth "; const char *argp_program_credits = "\ antirez (original dump1090) \n\ Malcom Robb (work on his dump1090 fork)\n\ mutability (forked to dump1090-mutability and further to dump1090-fa)\n\ Mictronics (readsb as a fork of dump1090-fa)\n\ wiedehopf (this fork of Mictronics readsb)\ "; static error_t parse_opt (int key, char *arg, struct argp_state *state); // preprocessor sillyness, yes both lines are necessary. #define _stringize(x) #x #define stringize(x) _stringize(x) static struct argp_option optionsViewadsb[] = { {0,0,0,0, "General options:", 1}, {"lat", OptLat, "", 0, "Reference/receiver surface latitude", 1}, {"lon", OptLon, "", 0, "Reference/receiver surface longitude", 1}, {"no-interactive", OptNoInteractive, 0, 0, "Disable interactive mode, print to stdout", 1}, {"interactive-ttl", OptInteractiveTTL, "", 0, "Remove from list if idle for (default: 60)", 1}, {"modeac", OptModeAc, 0, 0, "Enable decoding of SSR Modes 3/A & 3/C", 1}, {"max-range", OptMaxRange, "", 0, "Absolute maximum range for position decoding (in nm, default: 300)", 1}, {"fix", OptFix, 0, 0, "Enable CRC single-bit error correction (default)", 1}, {"no-fix", OptNoFix, 0, 0, "Disable CRC single-bit error correction", 1}, {"metric", OptMetric, 0, 0, "Use metric units", 1}, {"show-only", OptShowOnly, "", 0, "Show only messages by given ICAO on stdout", 1}, {"filter-DF", OptFilterDF, "", 0, "Only network forward and display decoded ModeS messages on stdout only show this DF type", 1}, {"receiver-focus", OptReceiverFocus, "", 0, "only process messages from receiverId", 1}, {"cpr-focus", OptCprFocus, "", 0, "show CPR details for this hex", 1}, {"quiet", OptQuiet, 0, 0, "Disable output (default)", 1}, {"debug", OptDebug, "", 0, "Debug mode (verbose), n: network, P: CPR, S: speed check", 1}, {0,0,0,0, "Network options:", 2}, {"net-connector", OptNetConnector, "", 0, "Establish connection, can be specified multiple times (viewadsb default: --net-connector 127.0.0.1,30005,beast_in viewadsb first usage overrides default, second usage adds another input/output) Protocols: beast_out, beast_reduce_out, beast_reduce_plus_out, beast_in, raw_out, raw_in, sbs_in, sbs_in_jaero, sbs_out, sbs_out_jaero, vrs_out, json_out, gpsd_in, uat_in, uat_replay_out, planefinder_in, asterix_in, asterix_out (one failover ip/address,port can be specified: primary-address,primary-port,protocol,failover-address,failover-port) (any position in the comma separated list can also be either silent_fail or uuid=)", 2}, {0,0,0,0, "Help options:", 100}, { 0 } }; static struct argp_option optionsReadsb[] = { {0,0,0,0, "General options:", 1}, {"lat", OptLat, "", 0, "Reference/receiver surface latitude", 1}, {"lon", OptLon, "", 0, "Reference/receiver surface longitude", 1}, {"no-interactive", OptNoInteractive, 0, 0, "Disable interactive mode, print to stdout", 1}, {"interactive-ttl", OptInteractiveTTL, "", 0, "Remove from list if idle for (default: 60)", 1}, {"modeac", OptModeAc, 0, 0, "Enable decoding of SSR Modes 3/A & 3/C", 1}, {"modeac-auto", OptModeAcAuto, 0, 0, "Enable Mode A/C if requested by a Beast connection", 1}, {"max-range", OptMaxRange, "", 0, "Absolute maximum range for position decoding (in nm, default: 300)", 1}, {"fix", OptFix, 0, 0, "Enable CRC single-bit error correction (default)", 1}, {"no-fix", OptNoFix, 0, 0, "Disable CRC single-bit error correction", 1}, {"no-fix-df", OptNoFixDf, 0, 0, "Disable CRC single-bit error correction on the DF type to produce more DF17 messages (disabling reduces CPU usage)", 1}, {"metric", OptMetric, 0, 0, "Use metric units", 1}, {"show-only", OptShowOnly, "", 0, "Show only messages by given ICAO on stdout", 1}, {"process-only", OptProcessOnly, "", 0, "Process only messages by given ICAO", 1}, {"filter-DF", OptFilterDF, "", 0, "When displaying decoded ModeS messages on stdout only show this DF type", 1}, {"aggressive", OptAggressive, 0, OPTION_HIDDEN, "Enable two-bit CRC error correction", 1}, {"device-type", OptDeviceType, "", 0, "Select SDR type (this needs to be placed on the command line before any SDR type specific options)", 1}, {"gain", OptGain, "", 0, "Set gain (default: auto gain, possible values for rtl-sdr devices: auto auto-verbose 0.0 0.9 1.4 2.7 3.7 7.7 8.7 12.5 14.4 15.7 16.6 19.7 20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4 37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6 58)", 1}, {"freq", OptFreq, "", 0, "Set frequency (default: 1090 MHz)", 1}, {"interactive", OptInteractive, 0, 0, "Interactive mode refreshing data on screen. Implies --throttle", 1}, {"raw", OptRaw, 0, 0, "Show only messages hex values", 1}, {"preamble-threshold", OptPreambleThreshold, "<"stringize(PREAMBLE_THRESHOLD_MIN)"-"stringize(PREAMBLE_THRESHOLD_MAX)">", 0, "lower threshold --> more CPU usage (default: "stringize(PREAMBLE_THRESHOLD_DEFAULT)", pi zero / pi 1: "stringize(PREAMBLE_THRESHOLD_PIZERO)", hot CPU "stringize(PREAMBLE_THRESHOLD_HOT)")", 1}, {"forward-mlat", OptForwardMlat, 0, 0, "Forward received beast mlat results to beast output ports", 1}, {"forward-mlat-sbs", OptForwardMlatSbs, 0, 0, "Forward received mlat results to sbs output ports", 1}, {"mlat", OptMlat, 0, OPTION_HIDDEN, "Display raw messages in Beast ASCII mode", 1}, {"stats", OptStats, 0, 0, "Print stats at exit. No other output", 1}, {"stats-range", OptStatsRange, 0, 0, "Collect/show range histogram", 1}, {"stats-every", OptStatsEvery, "", 0, "Show and reset stats every seconds (rounded to the nearest 10 seconds due to implementation, first inteval can be up to 5 seconds shorter)", 1}, {"auto-exit", OptAutoExit, "", 0, "Run for X seconds, then exit (default: run indefinitely)", 1}, {"range-outline-hours", OptRangeOutlineDuration, "", 0, "Make the range outline retain data for the last X hours (float, default: 24.0)", 1}, {"onlyaddr", OptOnlyAddr, 0, 0, "Show only ICAO addresses", 1}, {"gnss", OptGnss, 0, 0, "Show altitudes as GNSS when available", 1}, {"snip", OptSnip, "", 0, "Strip IQ file removing samples < level", 1}, {"debug", OptDebug, "", 0, "Debug mode (verbose), n: network, P: CPR, S: speed check", 1}, {"devel", OptDevel, "", 0, "Development debugging mode, see source for options, can be specified more than once", 1}, {"receiver-focus", OptReceiverFocus, "", 0, "only process messages from receiverId", 1}, {"cpr-focus", OptCprFocus, "", 0, "show CPR details for this hex", 1}, {"leg-focus", OptLegFocus, "", 0, "show leg marking details for this hex", 1}, {"trace-focus", OptTraceFocus, "", 0, "show traceAdd details for this hex", 1}, {"quiet", OptQuiet, 0, 0, "Disable output (default)", 1}, {"dcfilter", OptDcFilter, 0, OPTION_HIDDEN, "Apply a 1Hz DC filter to input data (requires more CPU)", 1}, {"enable-biastee", OptBiasTee, 0, OPTION_HIDDEN, "Enable bias tee on supporting interfaces (default: disabled)", 1}, {"write-json", OptJsonDir, "", 0, "Periodically write json output to ", 1}, {"write-prom", OptPromFile, "", 0, "Periodically write prometheus output to ", 1}, {"write-globe-history", OptGlobeHistoryDir, "", 0, "Write traces to this directory, 1 gz compressed json per day and airframe", 1}, {"write-state", OptStateDir, "", 0, "Write state to disk to have traces after a restart", 1}, {"write-state-every", OptStateInterval, "", 0, "Continuously write state to disk every X seconds (default: 3600)", 1}, {"write-state-only-on-exit", OptStateOnlyOnExit, 0, 0, "Don't continously update state.", 1}, {"heatmap-dir", OptHeatmapDir, "", 0, "Change the directory where heatmaps are saved (default is in globe history dir)", 1}, {"heatmap", OptHeatmap, "", 0, "Make Heatmap, each aircraft at most every interval seconds (creates historydir/heatmap.bin and exit after that)", 1}, {"dump-beast", OptDumpBeastDir, ",,", 0, "Dump compressed beast files to this directory, start a new file evey interval seconds", 1}, {"write-json-every", OptJsonTime, "", 0, "Write json output and update API json every sec seconds (default 1)", 1}, {"json-location-accuracy", OptJsonLocAcc , "", 0, "Accuracy of receiver location: 0: no location / internal use only, 1: 2 decimals, 2: exact (default), 3: 1 decimals, 4: 0 decimals", 1}, {"ac-hash-bits", OptAcHashBits, "", 0, "Main hash map size: 2^n entries (default: AIRCRAFT_HASH_BITS)", 1}, {"write-json-globe-index", OptJsonGlobeIndex, 0, 0, "Write specially indexed globe_xxxx.json files (for tar1090)", 1}, {"write-receiver-id-json", OptNetReceiverIdJson, 0, 0, "Write receivers.json", 1}, {"json-trace-interval", OptJsonTraceInt, "", 0, "Interval after which a new position will guaranteed to be written to the trace and the json position output (default: 30)", 1}, {"json-trace-hist-only", OptJsonTraceHistOnly, "1,2,3,8", 0, "Don't write recent(1), full(2), either(3) traces to /run, only archive via write-globe-history (8: irregularly write limited traces to run, subject to change)", 1}, {"full-trace-dir", OptFullTraceDir, "", 0, "when using globe-index, write full traces to this directory instead of --write-json dir (typically /run/readsb), this can be used to reduce memory usage at the cost of roughly 100 IOPS for global traffic", 1}, {"write-json-gzip", OptJsonGzip, 0, 0, "Write aircraft.json also as aircraft.json.gz", 1}, {"write-json-binCraft-only", OptJsonOnlyBin, "", 0, "Use only binary binCraft format for globe files (1), for aircraft.json as well (2)", 1}, {"write-binCraft-old", OptEnableBinGz, 0, 0, "write old gzipped binCraft files\n", 1}, {"json-reliable", OptJsonReliable,"", 0, "Minimum position reliability to put it into json (default: 1, globe options will default set this to 2, disable speed filter: -1, max: 4)", 1}, {"position-persistence", OptPositionPersistence,"", 0, "Position persistence against outliers (default: 4), incremented by json-reliable minus 1", 1}, {"jaero-timeout", OptJaeroTimeout,"", 0, "How long in minutes JAERO positions remain valid and on the map in tar1090 (default:33)", 1}, {"db-file", OptDbFile, "", 0, "Default: \"none\" (as of writing a compatible file is available here: https://github.com/wiedehopf/tar1090-db/tree/csv)", 1}, {"db-file-lt", OptDbFileLongtype, 0, 0, "aircraft.json: add long type as field desc, add field ownOp for the owner, add field year", 1}, {0,0,0,0, "Network options:", 2}, {"net-connector", OptNetConnector, "", 0, "Establish connection, can be specified multiple times (e.g. 127.0.0.1,23004,beast_out) Protocols: beast_out, beast_in, raw_out, raw_in, sbs_in, sbs_in_jaero, sbs_out, sbs_out_jaero, vrs_out, json_out, gpsd_in, uat_in, uat_replay_out, planefinder_in, asterix_in, asterix_out (one failover ip/address,port can be specified: primary-address,primary-port,protocol,failover-address,failover-port) (any position in the comma separated list can also be either silent_fail or uuid=)", 2}, {"net", OptNet, 0, 0, "Enable networking", 2}, {"net-only", OptNetOnly, 0, 0, "Legacy Option, Enable networking, use --net instead", 2}, {"net-bind-address", OptNetBindAddr, "", 0, "IP address to bind to (default: Any; Use 127.0.0.1 for private)", 2}, {"net-bo-port", OptNetBoPorts, "", 0, "TCP Beast output listen ports (default: 0)", 2}, {"net-bi-port", OptNetBiPorts, "", 0, "TCP Beast input listen ports (default: 0)", 2}, {"net-ro-port", OptNetRoPorts, "", 0, "TCP raw output listen ports (default: 0)", 2}, {"net-ri-port", OptNetRiPorts, "", 0, "TCP raw input listen ports (default: 0)", 2}, {"net-uat-replay-port", OptNetUatReplayPorts, "", 0, "UAT replay output listen ports (default: 0)", 2}, {"net-uat-in-port", OptNetUatInPorts, "", 0, "UAT input listen ports (default: 0)", 2}, {"net-sbs-port", OptNetSbsPorts, "", 0, "TCP BaseStation output listen ports (default: 0)", 2}, {"net-sbs-in-port", OptNetSbsInPorts, "", 0, "TCP BaseStation input listen ports (default: 0)", 2}, {"net-sbs-jaero-port", OptNetJaeroPorts, "", 0, "TCP SBS Jaero output listen ports (default: 0)", 2}, {"net-sbs-jaero-in-port", OptNetJaeroInPorts, "", 0, "TCP SBS Jaero input listen ports (default: 0)", 2}, {"net-asterix-out-port", OptNetAsterixOutPorts, "", 0, "TCP Asterix output listen ports (default: 0)", 2}, {"net-asterix-in-port", OptNetAsterixInPorts, "", 0, "TCP Asterix input listen ports (default: 0)", 2}, {"net-asterix-reduce", OptNetAsterixReduce, 0, 0, "Apply beast reduce logic and interval to ASTERIX outputs", 2}, {"net-vrs-port", OptNetVRSPorts, "", 0, "TCP VRS json output listen ports (default: 0)", 2}, {"net-vrs-interval", OptNetVRSInterval, "", 0, "TCP VRS json output interval (default: 5.0)", 2}, {"net-json-port", OptNetJsonPorts, "", 0, "TCP json position output listen ports, sends one line with a json object containing aircraft details for every position received (default: 0) (consider raising --net-ro-size to 8192 for less fragmentation if this is a concern)", 2}, {"net-json-port-interval", OptNetJsonPortInterval, "", 0, "Set minimum interval between outputs per aircraft for TCP json output, default: 0.0 (every position)", 2}, {"net-json-port-include-noposition", OptNetJsonPortNoPos, 0, 0, "TCP json position output: include aircraft without position (state is sent for aircraft for every DF11 with CRC if the aircraft hasn't sent a position in the last 10 seconds and interval allowing)", 2}, {"net-api-port", OptNetApiPorts, "", 0, "TCP API listen port (in contrast to other listeners, only a single port is allowed) (update frequency controlled by write-json-every parameter) (default: 0)", 2}, {"api-shutdown-delay", OptApiShutdownDelay, "", 0, "Shutdown delay to server remaining API queries, new queries get a 503 response (default: 0)", 2}, {"tar1090-use-api", OptTar1090UseApi, 0, 0, "when running with globe-index, signal tar1090 use the readsb API to get data, requires webserver mapping of /tar1090/re-api to proxy_pass the requests to the --net-api-port, see nginx-readsb-api.conf in the tar1090 repository for details", 2}, {"net-beast-reduce-out-port", OptNetBeastReducePorts, "", 0, "TCP BeastReduce output listen ports (default: 0)", 2}, {"net-beast-reduce-interval", OptNetBeastReduceInterval, "", 0, "BeastReduce data update interval, longer means less data (default: 0.250, valid range: 0.000 - 14.999)", 2}, {"net-beast-reduce-optimize-for-mlat", OptNetBeastReduceOptimizeMlat, 0, 0, "BeastReduce output: keep all messages relevant to mlat-client", 2}, {"net-beast-reduce-filter-dist", OptNetBeastReduceFilterDist, "", 0, "beast-reduce: remove aircraft which are further than distance from the receiver", 2}, {"net-beast-reduce-filter-alt", OptNetBeastReduceFilterAlt, "", 0, "beast-reduce: remove aircraft which are above altitude", 2}, {"net-sbs-reduce", OptNetSbsReduce, 0, 0, "Apply beast reduce logic and interval to SBS outputs", 2}, {"net-receiver-id", OptNetReceiverId, 0, 0, "forward receiver ID", 2}, {"net-ingest", OptNetIngest, 0, 0, "primary ingest node", 2}, {"net-garbage", OptGarbage, "", 0, "timeout receivers, output messages from timed out receivers as beast on ", 2}, {"decode-threads", OptDecodeThreads, "", 0, "Number of decode threads, either 1 or 2 (default: 1). Only use 2 when you have beast traffic > 200 MBit/s, expect 1.4x speedup for 2x CPU", 2}, {"uuid-file", OptUuidFile, "", 0, "path to UUID file", 2}, {"net-ro-size", OptNetRoSize, "", 0, "TCP output flush size (maximum amount of internally buffered data before writing to network) (default: 1280)", 2}, {"net-ro-interval", OptNetRoInterval, "", 0, "TCP output flush interval in seconds (maximum delay between placing data in the output buffer and sending)(default: 0.05, valid values 0.0 - 1.0)", 2}, {"net-ro-interval-beast-reduce", OptNetRoIntervalBeastReduce, "", 0, "TCP output flush interval in seconds for beast-reduce outputs (default: value from --net-ro-interval, valid values 0.0 - 1.0)", 2}, {"net-connector-delay", OptNetConnectorDelay, "", 0, "Outbound re-connection delay (default: 15)", 2}, {"net-heartbeat", OptNetHeartbeat, "", 0, "TCP heartbeat rate in seconds (default: 60 sec; 0 to disable)", 2}, {"net-buffer", OptNetBuffer, "", 0, "control some buffer sizes: 8KB * (2^n) (default: n=1, 16KB)", 2}, {"net-verbatim", OptNetVerbatim, 0, 0, "Forward messages unchanged", 2}, {"sdr-buffer-size", OptSdrBufSize, "", 0, "SDR buffer / USB transfer size in kibibytes (default: 256 which is equivalent to around 54 ms using rtl-sdr, option might be ignored in future versions)", 2}, #ifdef ENABLE_RTLSDR {0,0,0,0, "RTL-SDR options:", 3}, {0,0,0, OPTION_DOC, "use with --device-type rtlsdr", 3}, {"device", OptDevice, "", 0, "Select device by index or serial number", 3}, {"enable-agc", OptRtlSdrEnableAgc, 0, 0, "Enable digital AGC (not tuner AGC!)", 3}, {"ppm", OptRtlSdrPpm, "", 0, "Set oscillator frequency correction in PPM", 3}, #endif #ifdef ENABLE_BLADERF {0,0,0,0, "BladeRF options:", 4}, {0,0,0, OPTION_DOC, "use with --device-type bladerf", 4}, {"device", OptDevice, "", 0, "Select device by bladeRF 'device identifier'", 4}, {"bladerf-fpga", OptBladeFpgaDir, "", 0, "Use alternative FPGA bitstream ('' to disable FPGA load)", 4}, {"bladerf-decimation", OptBladeDecim, "", 0, "Assume FPGA decimates by a factor of N", 4}, {"bladerf-bandwidth", OptBladeBw, "", 0, "Set LPF bandwidth ('bypass' to bypass the LPF)", 4}, #endif #ifdef ENABLE_HACKRF {0,0,0,0, "HackRF options:", 3}, {0,0,0, OPTION_DOC, "use with --device-type hackrf", 3}, {"device", OptDevice, "", 0, "Select device by serial number", 3}, {"hackrf-enable-ampgain", OptHackRfGainEnable, 0, 0, "Enable amp gain (RF stage) (~11 dB) (default: disabled)", 3}, {"hackrf-vgagain", OptHackRfVgaGain, "", 0, "Set gain (baseband stage) (default: 48, valid: 0-62, 2 dB steps)", 3}, #endif {0,0,0,0, "Modes-S Beast options, use with --device-type modesbeast:", 5}, {"beast-serial", OptBeastSerial, "", 0, "Path to Beast serial device (default /dev/ttyUSB0)", 5}, {"beast-df1117-on", OptBeastDF1117, 0, 0, "Turn ON DF11/17-only filter", 5}, {"beast-mlat-off", OptBeastMlatTimeOff, 0, 0, "Turn OFF MLAT time stamps", 5}, {"beast-crc-off", OptBeastCrcOff, 0, 0, "Turn OFF CRC checking", 5}, {"beast-df045-on", OptBeastDF045, 0, 0, "Turn ON DF0/4/5 filter", 5}, {"beast-fec-off", OptBeastFecOff, 0, 0, "Turn OFF forward error correction", 5}, {"beast-modeac", OptBeastModeAc, 0, 0, "Turn ON mode A/C", 5}, {"beast-baudrate", OptBeastBaudrate, "", 0, "Override Baudrate (default rate 3000000 baud, try 1000000 / 921600 as alternatives)", 5}, {0, 0, 0, 0, "GNS HULC options, use with --device-type gnshulc:", 6}, {0, 0, 0, OPTION_DOC, "Beast binary and HULC protocol input with hardware handshake enabled.", 6}, {"beast-serial", OptBeastSerial, "", 0, "Path to GNS HULC serial device (default /dev/ttyUSB0)", 6}, {0,0,0,0, "ifile-specific options, use with --device-type ifile:", 7}, {"ifile", OptIfileName, "", 0, "Read samples from given file ('-' for stdin)", 7}, {"iformat", OptIfileFormat, "", 0, "Set sample format (UC8, SC16, SC16Q11)", 7}, {"throttle", OptIfileThrottle, 0, 0, "Process samples at the original capture speed", 7}, #ifdef ENABLE_PLUTOSDR {0,0,0,0, "ADALM-Pluto SDR options:", 8}, {0,0,0, OPTION_DOC, "use with --device-type plutosdr", 8}, {"pluto-uri", OptPlutoUri, "", 0, "Create USB context from this URI.(eg. usb:1.2.5)", 8}, {"pluto-network", OptPlutoNetwork, "", 0, "Hostname or IP to create networks context. (default pluto.local)", 8}, #endif #ifdef ENABLE_SOAPYSDR {0,0,0,0, "SoapySDR options:", 9}, {0,0,0, OPTION_DOC, "use with --device-type soapysdr", 9}, {"soapy-device", OptDevice, "", 0, "Select device by SoapySDR key pair (ex. driver=lime)", 9}, {"soapy-antenna", OptSoapyAntenna, "", 0, "Select an antenna", 9}, {"soapy-bandwidth", OptSoapyBandwith, "", 0, "Select bandwidth", 9}, {"soapy-enable-agc", OptSoapyEnableAgc, 0, 0, "enable AGC", 9}, {"soapy-gain-element", OptSoapyGainElement, "", 0, "set SoapySDR gain element", 9}, #endif {0,0,0,0, "Help options:", 100}, { 0 } }; #undef stringize #undef _stringize #endif /* HELP_H */ readsb-3.16/hist_date_format_migrate.sh000077500000000000000000000007421505057307600203010ustar00rootroot00000000000000#!/bin/bash set -e TARGET=$1 if ! [[ -d "$TARGET" ]]; then echo argument is not a directory! exit 1 fi cd "$TARGET" UG=$(stat -c "%U:%G" "$TARGET") for DATE in ????-??-??; do YEAR="${DATE:0:4}" MM="${DATE:5:2}" DD="${DATE:8:2}" if ! [[ -d "$YEAR" ]]; then mkdir "$YEAR" chown "$UG" "$YEAR" fi if ! [[ -d "$YEAR/$MM" ]]; then mkdir "$YEAR/$MM" chown "$UG" "$YEAR/$MM" fi mv -T "$DATE" "$YEAR/$MM/$DD" done readsb-3.16/icao_filter.c000066400000000000000000000104361505057307600153430ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // icao_filter.c: hashtable for ICAO addresses // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #include "readsb.h" // Open-addressed hash table with linear probing. // Maintain two tables and switch between them to age out entries. static uint32_t filterBits; static uint32_t filterBuckets; static size_t filterSize; static uint32_t *icao_filter_a; static uint32_t *icao_filter_b; static uint32_t *icao_filter_active; static uint32_t occupied; static inline uint32_t filterHash(uint32_t addr) { return addrHash(addr, filterBits); } #define EMPTY 0xFFFFFFFF #define MINBITS 8 #define MAXBITS 20 void icaoFilterInit() { filterBits = MINBITS; filterBuckets = 1ULL << filterBits; filterSize = filterBuckets * sizeof(uint32_t); occupied = 0; sfree(icao_filter_a); sfree(icao_filter_b); icao_filter_a = cmalloc(filterSize); icao_filter_b = cmalloc(filterSize); memset(icao_filter_a, 0xFF, filterSize); memset(icao_filter_b, 0xFF, filterSize); icao_filter_active = icao_filter_a; } void icaoFilterDestroy() { sfree(icao_filter_a); sfree(icao_filter_b); } static void icaoFilterResize(uint32_t bits) { uint32_t oldBuckets = filterBuckets; uint32_t *oldActive = icao_filter_active; uint32_t *oldA = icao_filter_a; uint32_t *oldB = icao_filter_b; filterBits = bits; filterBuckets = 1ULL << filterBits; filterSize = filterBuckets * sizeof(uint32_t); if (filterBuckets > 256000) fprintf(stderr, "icao_filter: changing size to %d!\n", (int) filterBuckets); icao_filter_a = cmalloc(filterSize); icao_filter_b = cmalloc(filterSize); memset(icao_filter_a, 0xFF, filterSize); memset(icao_filter_b, 0xFF, filterSize); // reset occupied count occupied = 0; icao_filter_active = icao_filter_a; for (uint32_t i = 0; i < oldBuckets; i++) { if (oldActive[i] != EMPTY) { icaoFilterAdd(oldActive[i]); } } sfree(oldA); sfree(oldB); } // call this periodically: void icaoFilterExpire() { if (occupied < filterBuckets / 9 && filterBits > MINBITS) { icaoFilterResize(filterBits - 1); } // reset occupied count occupied = 0; if (icao_filter_active == icao_filter_a) { memset(icao_filter_b, 0xFF, filterSize); icao_filter_active = icao_filter_b; } else { memset(icao_filter_a, 0xFF, filterSize); icao_filter_active = icao_filter_a; } } void icaoFilterAdd(uint32_t addr) { uint32_t h, h0; h0 = h = filterHash(addr); while (icao_filter_active[h] != EMPTY && icao_filter_active[h] != addr) { h = (h + 1) & (filterBuckets - 1); if (h == h0) { fprintf(stderr, "ICAO hash table full, this shouldn't happen\n"); return; } } if (icao_filter_active[h] == EMPTY) { occupied++; icao_filter_active[h] = addr; } if (occupied > filterBuckets / 3 && filterBits < 20) { icaoFilterResize(filterBits + 1); } } int icaoFilterTest(uint32_t addr) { uint32_t h, h0; h0 = h = filterHash(addr); while (icao_filter_a[h] != EMPTY && icao_filter_a[h] != addr) { h = (h + 1) & (filterBuckets - 1); if (h == h0) break; } if (icao_filter_a[h] == addr) return 1; h = h0; while (icao_filter_b[h] != EMPTY && icao_filter_b[h] != addr) { h = (h + 1) & (filterBuckets - 1); if (h == h0) break; } if (icao_filter_b[h] == addr) return 1; return 0; } readsb-3.16/icao_filter.h000066400000000000000000000027641505057307600153550ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // icao_filter.c: prototypes for ICAO address hashtable // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #ifndef DUMP1090_ICAO_FILTER_H #define DUMP1090_ICAO_FILTER_H // Call once: void icaoFilterInit (); void icaoFilterDestroy(); // Add an address to the filter void icaoFilterAdd (uint32_t addr); // Test if the given address matches the filter int icaoFilterTest (uint32_t addr); // Test if the top 16 bits match any previously added address. // If they do, returns an arbitrary one of the matched // addresses. Returns 0 on failure. uint32_t icaoFilterTestFuzzy (uint32_t partial); // Call this periodically to allow the filter to expire // old entries. void icaoFilterExpire (); #endif readsb-3.16/interactive.c000066400000000000000000000251271505057307600154030ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // interactive.c: aircraft tracking and interactive display // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "readsb.h" #ifndef DISABLE_INTERACTIVE #include #endif #ifdef DISABLE_INTERACTIVE void interactiveInit() {} void interactiveCleanup(void) {} void interactiveShowData(void) {} #else // //========================= Interactive mode =============================== static double convert_distance(int meter) { if (Modes.metric) return meter / 1000.0; else return meter / 1852.0; } static int convert_altitude(int ft) { if (Modes.metric) return (ft / 3.2828); else return ft; } static int convert_speed(int kts) { if (Modes.metric) return (kts * 1.852); else return kts; } // //========================================================================= // // Show the currently captured interactive data on screen. // void interactiveInit() { if (!Modes.interactive) return; initscr(); clear(); refresh(); } void interactiveCleanup(void) { if (Modes.interactive) { endwin(); } } static int compareDist(const void *p1, const void *p2) { struct aircraft *a1 = *(struct aircraft**) p1; struct aircraft *a2 = *(struct aircraft**) p2; if (a1 == NULL) return 1; if (a2 == NULL) return -1; int valid1 = trackDataValid(&a1->position_valid); int valid2 = trackDataValid(&a2->position_valid); if (!valid1 && !valid2) { return a1->addr - a2->addr; } if (valid1 != valid2) { return valid2 - valid1; } return a1->receiver_distance - a2->receiver_distance; } static int compareAlt(const void *p1, const void *p2) { struct aircraft *a1 = *(struct aircraft**) p1; struct aircraft *a2 = *(struct aircraft**) p2; if (a1 == NULL) return 1; if (a2 == NULL) return -1; int valid1 = trackDataValid(&a1->baro_alt_valid); int valid2 = trackDataValid(&a2->baro_alt_valid); int g1 = trackDataValid(&a1->airground_valid) && a1->airground == AG_GROUND; int g2 = trackDataValid(&a2->airground_valid) && a2->airground == AG_GROUND; if (g1 || g2) { if (g1 && g2) { return a1->addr - a2->addr; } return g2 - g1; } if (!valid1 && !valid2) { return a1->addr - a2->addr; } if (valid1 != valid2) { return valid2 - valid1; } if (a1->baro_alt == a2->baro_alt) { return a1->addr - a2->addr; } return a1->baro_alt - a2->baro_alt; } void interactiveShowData(void) { static int64_t next_update; static int64_t next_clear; int64_t now = mstime(); char progress; char spinner[5] = "|/-\\"; // Refresh screen every (MODES_INTERACTIVE_REFRESH_TIME) miliseconde if (now < next_update) return; next_update = now + MODES_INTERACTIVE_REFRESH_TIME; // clear potential errors every 10 seconds if (now > next_clear) { next_clear = now + 10 * SECONDS; clear(); // print header if (Modes.userLocationValid) { mvprintw(0, 0, " Hex Mode Sqwk Flight Alt Spd Hdg Dist Dir RSSI Msgs Seen"); } else { mvprintw(0, 0, " Hex Mode Sqwk Flight Alt Spd Hdg Lat Long RSSI Msgs Seen"); } mvhline(1, 0, ACS_HLINE, 80); } progress = spinner[(now / 1000) % 4]; mvaddch(0, 79, progress); int rows = getmaxy(stdscr); int row = 2; struct craftArray *ca = &Modes.aircraftActive; // sort active list by altitude static int64_t next_sort; if (now > next_sort) { next_sort = now + 3 * SECONDS; pthread_mutex_lock(&ca->change_mutex); pthread_mutex_lock(&ca->write_mutex); if (Modes.userLocationValid) { qsort(ca->list, ca->len, sizeof(struct aircraft *), compareDist); } else { qsort(ca->list, ca->len, sizeof(struct aircraft *), compareAlt); } pthread_mutex_unlock(&ca->write_mutex); pthread_mutex_unlock(&ca->change_mutex); } ca_lock_read(ca); for (int i = 0; i < ca->len; i++) { struct aircraft *a = ca->list[i]; if (a && row < rows) { if ((now - a->seen) < Modes.interactive_display_ttl) { int msgs = a->messages; if (msgs > 1) { char strSquawk[5] = " "; char strFl[7] = " "; char strTt[5] = " "; char strGs[6] = " "; if (trackDataValid(&a->squawk_valid)) { snprintf(strSquawk, 5, "%04x", a->squawk); } if (trackDataValid(&a->gs_valid)) { snprintf(strGs, 6, "%4d", convert_speed(a->gs)); } if (trackDataValid(&a->track_valid)) { snprintf(strTt, 5, "%03.0f", a->track); } if (msgs > 99999) { msgs = 99999; } char strMode[5] = " "; char strLat[8] = " "; char strLon[9] = " "; double * pSig = a->signalLevel; double signalAverage = (pSig[0] + pSig[1] + pSig[2] + pSig[3] + pSig[4] + pSig[5] + pSig[6] + pSig[7]) / 8.0; strMode[0] = 'S'; if (a->modeA_hit) { strMode[2] = 'a'; } if (a->modeC_hit) { strMode[3] = 'c'; } if (trackDataValid(&a->position_valid)) { if (Modes.userLocationValid) { snprintf(strLat, 8, "%7.01f", convert_distance(a->receiver_distance)); snprintf(strLon, 9, "%8.00f", a->receiver_direction); } else { snprintf(strLat, 8, "%7.03f", a->lat); snprintf(strLon, 9, "%8.03f", a->lon); } } if (trackDataValid(&a->airground_valid) && a->airground == AG_GROUND) { snprintf(strFl, 7, " grnd "); } else if (Modes.use_gnss && trackDataValid(&a->geom_alt_valid)) { snprintf(strFl, 7, "%5dH", convert_altitude(a->geom_alt)); } else if (trackDataValid(&a->baro_alt_valid)) { snprintf(strFl, 7, "%5d ", convert_altitude(a->baro_alt)); } mvprintw(row, 0, "%s%06X %-4s %-4s %-8s %6s %4s %3s %7s %8s %5.1f %5d %2.0f", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : " ", (a->addr & 0xffffff), strMode, strSquawk, a->callsign, strFl, strGs, strTt, strLat, strLon, 10 * log10(signalAverage), msgs, (now - a->seen) / 1000.0); ++row; } } a = a->next; } } ca_unlock_read(ca); if (Modes.mode_ac) { for (unsigned i = 1; i < 4096 && row < rows; ++i) { if (modeAC_match[i] || modeAC_count[i] < 50 || modeAC_age[i] > 5) continue; char strMode[5] = " A "; char strFl[7] = " "; unsigned modeA = indexToModeA(i); int modeC = modeAToModeC(modeA); if (modeC != INVALID_ALTITUDE) { strMode[3] = 'C'; snprintf(strFl, 7, "%5d ", convert_altitude(modeC * 100)); } mvprintw(row, 0, "%7s %-4s %04x %-8s %6s %3s %3s %7s %8s %5s %5d %2d\n", "", /* address */ strMode, /* mode */ modeA, /* squawk */ "", /* callsign */ strFl, /* altitude */ "", /* gs */ "", /* heading */ "", /* lat */ "", /* lon */ "", /* signal */ modeAC_count[i], /* messages */ modeAC_age[i]); /* age */ ++row; } } move(row, 0); clrtobot(); refresh(); } #endif // //========================================================================= // readsb-3.16/json_out.c000066400000000000000000002450251505057307600147270ustar00rootroot00000000000000#include "readsb.h" /* __attribute__ ((format(printf, 3, 0))) static char *safe_vsnprintf(char *p, char *end, const char *format, va_list ap) { p += vsnprintf(p < end ? p : NULL, p < end ? (size_t) (end - p) : 0, format, ap); return p; } */ static const char *trimSpace(const char *in, char *out, int len) { out[len] = '\0'; int found = 0; for (int i = len - 1; i >= 0; i--) { if (!found && in[i] == ' ') { out[i] = '\0'; } else if (in[i] == '\0') { out[i] = '\0'; } else { out[i] = in[i]; found = 1; // found non space character } } return out; } // //========================================================================= // // Return a description of planes in json. No metric conversion // static const char *jsonEscapeString(const char *str, char *buf, int len) { const char *in = str; char *out = buf, *end = buf + len - 10; for (; *in && out < end; ++in) { unsigned char ch = *in; if (ch == '"' || ch == '\\') { *out++ = '\\'; *out++ = ch; } else if (ch < 32 || ch > 126) { out = safe_snprintf(out, end, "\\u%04x", ch); } else { *out++ = ch; } } *out++ = 0; return buf; } static char *append_flags(char *p, char *end, struct aircraft *a, datasource_t source) { p = safe_snprintf(p, end, "["); char *start = p; if (a->callsign_valid.source == source) p = safe_snprintf(p, end, "\"callsign\","); if (a->baro_alt_valid.source == source) { p = safe_snprintf(p, end, "\"altitude\","); p = safe_snprintf(p, end, "\"alt_baro\","); } if (a->geom_alt_valid.source == source) p = safe_snprintf(p, end, "\"alt_geom\","); if (a->gs_valid.source == source) p = safe_snprintf(p, end, "\"gs\","); if (a->ias_valid.source == source) p = safe_snprintf(p, end, "\"ias\","); if (a->tas_valid.source == source) p = safe_snprintf(p, end, "\"tas\","); if (a->mach_valid.source == source) p = safe_snprintf(p, end, "\"mach\","); if (a->track_valid.source == source) p = safe_snprintf(p, end, "\"track\","); if (a->track_rate_valid.source == source) p = safe_snprintf(p, end, "\"track_rate\","); if (a->roll_valid.source == source) p = safe_snprintf(p, end, "\"roll\","); if (a->mag_heading_valid.source == source) p = safe_snprintf(p, end, "\"mag_heading\","); if (a->true_heading_valid.source == source) p = safe_snprintf(p, end, "\"true_heading\","); if (a->baro_rate_valid.source == source) p = safe_snprintf(p, end, "\"baro_rate\","); if (a->geom_rate_valid.source == source) p = safe_snprintf(p, end, "\"geom_rate\","); if (a->squawk_valid.source == source) p = safe_snprintf(p, end, "\"squawk\","); if (a->emergency_valid.source == source) p = safe_snprintf(p, end, "\"emergency\","); if (a->nav_qnh_valid.source == source) p = safe_snprintf(p, end, "\"nav_qnh\","); if (a->nav_altitude_mcp_valid.source == source) p = safe_snprintf(p, end, "\"nav_altitude_mcp\","); if (a->nav_altitude_fms_valid.source == source) p = safe_snprintf(p, end, "\"nav_altitude_fms\","); if (a->nav_heading_valid.source == source) p = safe_snprintf(p, end, "\"nav_heading\","); if (a->nav_modes_valid.source == source) p = safe_snprintf(p, end, "\"nav_modes\","); if (a->pos_reliable_valid.source == source) p = safe_snprintf(p, end, "\"lat\",\"lon\",\"nic\",\"rc\","); if (a->nic_baro_valid.source == source) p = safe_snprintf(p, end, "\"nic_baro\","); if (a->nac_p_valid.source == source) p = safe_snprintf(p, end, "\"nac_p\","); if (a->nac_v_valid.source == source) p = safe_snprintf(p, end, "\"nac_v\","); if (a->sil_valid.source == source) p = safe_snprintf(p, end, "\"sil\",\"sil_type\","); if (a->gva_valid.source == source) p = safe_snprintf(p, end, "\"gva\","); if (a->sda_valid.source == source) p = safe_snprintf(p, end, "\"sda\","); if (p != start) --p; p = safe_snprintf(p, end, "]"); return p; } static struct { nav_modes_t flag; const char *name; } nav_modes_names[] = { { NAV_MODE_AUTOPILOT, "autopilot"}, { NAV_MODE_VNAV, "vnav"}, { NAV_MODE_ALT_HOLD, "althold"}, { NAV_MODE_APPROACH, "approach"}, { NAV_MODE_LNAV, "lnav"}, { NAV_MODE_TCAS, "tcas"}, { 0, NULL} }; static char *append_nav_modes(char *p, char *end, nav_modes_t flags, const char *quote, const char *sep) { int first = 1; for (int i = 0; nav_modes_names[i].name; ++i) { if (!(flags & nav_modes_names[i].flag)) { continue; } if (!first) { p = safe_snprintf(p, end, "%s", sep); } first = 0; p = safe_snprintf(p, end, "%s%s%s", quote, nav_modes_names[i].name, quote); } return p; } const char *nav_modes_flags_string(nav_modes_t flags) { static char buf[256]; buf[0] = 0; append_nav_modes(buf, buf + sizeof (buf), flags, "", " "); return buf; } void printACASInfoShort(uint32_t addr, unsigned char *MV, struct aircraft *a, struct modesMessage *mm, int64_t now) { char buf[512]; char *p = buf; char *end = buf + sizeof(buf); p = sprintACASInfoShort(p, end, addr, MV, a, mm, now); if (p == buf) // nothing written return; if (p - buf >= (int) sizeof(buf)) { fprintf(stderr, "printACAS buffer insufficient!\n"); return; } printf("%s\n", buf); fflush(stdout); // FLUSH } void logACASInfoShort(uint32_t addr, unsigned char *bytes, struct aircraft *a, struct modesMessage *mm, int64_t now) { bool rat = getbit(bytes, 27); // clear of conflict / RA terminated int64_t dedupMaxIval = 5000; int64_t deduplicationInterval = rat ? dedupMaxIval : 300; // in ms #define DEDUPSIZE 1024 #define LOGBYTES 7 static char lastLogBytes[DEDUPSIZE][LOGBYTES]; static uint32_t lastLogAddr[DEDUPSIZE]; static int64_t lastLogTimestamp[DEDUPSIZE]; static int nextIndex; for (int k = 0; k < nextIndex; k++) { if (lastLogAddr[k] == addr && now - lastLogTimestamp[k] < deduplicationInterval && !memcmp(lastLogBytes[k], bytes, 7)) { //fprintf(stderr, "."); return; } } //fprintf(stderr, ",\n"); lastLogAddr[nextIndex] = addr; lastLogTimestamp[nextIndex] = now; memcpy(lastLogBytes[nextIndex], bytes, 7); nextIndex++; int moveIndex = 0; for (; moveIndex < nextIndex && now - lastLogTimestamp[moveIndex] > dedupMaxIval; moveIndex++) { } if (nextIndex == DEDUPSIZE) { moveIndex = DEDUPSIZE * 1 / 8; } if (moveIndex > DEDUPSIZE / 128) { //int oldSize = nextIndex; int newSize = nextIndex - moveIndex; //fprintf(stderr, "old size: %d, move index: %d, new size: %d\n", oldSize, moveIndex, newSize); memmove(lastLogBytes[0], lastLogBytes[moveIndex], newSize * LOGBYTES); memmove(&lastLogAddr[0], &lastLogAddr[moveIndex], newSize * sizeof lastLogAddr[0]); memmove(&lastLogTimestamp[0], &lastLogTimestamp[moveIndex], newSize * sizeof(lastLogTimestamp[0])); nextIndex = newSize; } #undef DEDUPSIZE #undef LOGBYTES if (Modes.acasFD1 > 0) { char buf[512]; char *p = buf; char *end = buf + sizeof(buf); p = sprintACASInfoShort(p, end, addr, bytes, a, mm, now); p = safe_snprintf(p, end, "\n"); if (p - buf >= (int) sizeof(buf) - 1) { fprintf(stderr, "logACAS csv buffer insufficient!\n"); } else { check_write(Modes.acasFD1, buf, p - buf, "acas.csv"); } } if (Modes.acasFD2 > 0) { char buf[2048]; char *p = buf; char *end = buf + sizeof(buf); p = sprintAircraftObject(p, end, a, now, 0, mm); p = safe_snprintf(p, end, "\n"); if (p - buf >= (int) sizeof(buf) - 1) { fprintf(stderr, "logACAS json buffer insufficient!\n"); } else { check_write(Modes.acasFD2, buf, p - buf, "acas.json"); } } } static char *sprintACASJson(char *p, char *end, unsigned char *bytes, struct modesMessage *mm, int64_t now) { bool ara = getbit(bytes, 9); bool rat = getbit(bytes, 27); bool mte = getbit(bytes, 28); char timebuf[128]; struct tm utc; time_t time = now / 1000; gmtime_r(&time, &utc); strftime(timebuf, 128, "%F %T", &utc); timebuf[127] = 0; p = safe_snprintf(p, end, "{\"utc\":\"%s.%d\"", timebuf, (int)((now % 1000) / 100)); p = safe_snprintf(p, end, ",\"unix_timestamp\":%.2f", now / 1000.0); if (mm && mm->acas_ra_valid) { if (Modes.debug_ACAS && !checkAcasRaValid(bytes, mm, 0)) { p = safe_snprintf(p, end, ",\"debug\":true"); } p = safe_snprintf(p, end, ",\"df_type\":%d", mm->msgtype); p = safe_snprintf(p, end, ",\"full_bytes\":\""); for (int i = 0; i < mm->msgbits / 8; ++i) { p = safe_snprintf(p, end, "%02X", (unsigned) mm->msg[i]); } p = safe_snprintf(p, end, "\""); } p = safe_snprintf(p, end, ",\"bytes\":\""); for (int i = 0; i < 7; ++i) { p = safe_snprintf(p, end, "%02X", (unsigned) bytes[i]); } p = safe_snprintf(p, end, "\""); p = safe_snprintf(p, end, ",\"ARA\":\""); for (int i = 9; i <= 15; i++) p = safe_snprintf(p, end, "%u", getbit(bytes, i)); p = safe_snprintf(p, end, "\""); p = safe_snprintf(p, end, ",\"RAT\":\"%u\"", getbit(bytes, 27)); p = safe_snprintf(p, end, ",\"MTE\":\"%u\"", getbit(bytes, 28)); p = safe_snprintf(p, end, ",\"RAC\":\""); for (int i = 23; i <= 26; i++) p = safe_snprintf(p, end, "%u", getbit(bytes, i)); p = safe_snprintf(p, end, "\""); p = safe_snprintf(p, end, ",\"advisory_complement\":\""); if (getbits(bytes, 23, 26)) { bool notfirst = false; char *racs[4] = { "Do not pass below", "Do not pass above", "Do not turn left", "Do not turn right" }; for (int i = 23; i <= 26; i++) { if (getbit(bytes, i)) { if (notfirst) p = safe_snprintf(p, end, "; "); p = safe_snprintf(p, end, "%s", racs[i-23]); notfirst = true; } } } p = safe_snprintf(p, end, "\""); // https://mode-s.org/decode/book-the_1090mhz_riddle-junzi_sun.pdf // // https://www.faa.gov/documentlibrary/media/advisory_circular/tcas%20ii%20v7.1%20intro%20booklet.pdf /* RAs can be classified as positive (e.g., climb, descend) or negative (e.g., limit climb to 0 fpm, limit descend to 500 fpm). The term "Vertical Speed Limit" (VSL) is equivalent to "negative." RAs can also be classified as preventive or corrective, depending on whether own aircraft is, or is not, in conformance with the RA target altitude rate. Corrective RAs require a change in vertical speed; preventive RAs do not require a change in vertical speed */ p = safe_snprintf(p, end, ",\"advisory\":\""); if (rat) { p = safe_snprintf(p, end, "Clear of Conflict"); } else if (ara) { bool corr = getbit(bytes, 10); // corrective / preventive bool down = getbit(bytes, 11); // downward sense / upward sense bool increase = getbit(bytes, 12); // increase rate bool reversal = getbit(bytes, 13); // sense reversal bool crossing = getbit(bytes, 14); // altitude crossing bool positive = getbit(bytes, 15); // positive: (Maintain climb / descent) / (Climb / descend): requires more than 1500 fpm vertical rate // !positive: (Do not / reduce) (climb / descend) if (corr && positive) { if (!reversal) { // reversal has priority and comes later } else if (increase) { p = safe_snprintf(p, end, "Increase "); } if (down) p = safe_snprintf(p, end, "Descend"); else p = safe_snprintf(p, end, "Climb"); if (reversal) { if (down) p = safe_snprintf(p, end, "; Descend"); else p = safe_snprintf(p, end, "; Climb"); p = safe_snprintf(p, end, " NOW"); } if (crossing) { p = safe_snprintf(p, end, "; Crossing"); if (down) p = safe_snprintf(p, end, " Descend"); else p = safe_snprintf(p, end, " Climb"); } } if (corr && !positive) { p = safe_snprintf(p, end, "Level Off"); } if (!corr && positive) { p = safe_snprintf(p, end, "Maintain vertical Speed"); if (crossing) { p = safe_snprintf(p, end, "; Crossing Maintain"); } } if (!corr && !positive) { p = safe_snprintf(p, end, "Monitor vertical Speed"); } } else if (!ara && mte) { if (getbit(bytes, 10)) p = safe_snprintf(p, end, " Correct upwards;"); if (getbit(bytes, 11)) p = safe_snprintf(p, end, " Climb required;"); if (getbit(bytes, 12)) p = safe_snprintf(p, end, " Correct downwards;"); if (getbit(bytes, 13)) p = safe_snprintf(p, end, " Descent required;"); if (getbit(bytes, 14)) p = safe_snprintf(p, end, " Crossing;"); if (getbit(bytes, 15)) p = safe_snprintf(p, end, " Increase / Maintain vertical rate"); else p = safe_snprintf(p, end, " Reduce / Limit vertical rate"); } p = safe_snprintf(p, end, "\""); int tti = getbits(bytes, 29, 30); p = safe_snprintf(p, end, ",\"TTI\":\""); for (int i = 29; i <= 30; i++) p = safe_snprintf(p, end, "%u", getbit(bytes, i)); p = safe_snprintf(p, end, "\""); if (tti == 1) { uint32_t threatAddr = getbits(bytes, 31, 54); p = safe_snprintf(p, end, ",\"threat_id_hex\":\"%06x\"", threatAddr); } p = safe_snprintf(p, end, "}"); return p; } char *sprintACASInfoShort(char *p, char *end, uint32_t addr, unsigned char *bytes, struct aircraft *a, struct modesMessage *mm, int64_t now) { bool ara = getbit(bytes, 9); bool rat = getbit(bytes, 27); bool mte = getbit(bytes, 28); char timebuf[128]; struct tm utc; time_t time = now / 1000; gmtime_r(&time, &utc); strftime(timebuf, 128, "%F", &utc); timebuf[127] = 0; int debug = 0; if (Modes.debug_ACAS && mm && !checkAcasRaValid(bytes, mm, 0)) { debug = 1; p = safe_snprintf(p, end, "DEBUG "); } else { p = safe_snprintf(p, end, "%s", timebuf); } p = safe_snprintf(p, end, ","); strftime(timebuf, 128, "%T", &utc); timebuf[127] = 0; p = safe_snprintf(p, end, "%s.%d, %06x,DF:,", timebuf, (int)((now % 1000) / 100), addr); if (mm) p = safe_snprintf(p, end, "%2u", mm->msgtype); else p = safe_snprintf(p, end, " "); p = safe_snprintf(p, end, ",bytes:,"); for (int i = 0; i < 7; ++i) { p = safe_snprintf(p, end, "%02X", (unsigned) bytes[i]); } p = safe_snprintf(p, end, ","); if (a && trackDataValid(&a->pos_reliable_valid)) p = safe_snprintf(p, end, "%11.6f,", a->latReliable); else p = safe_snprintf(p, end, " ,"); if (a && trackDataValid(&a->pos_reliable_valid)) p = safe_snprintf(p, end, "%11.6f,", a->lonReliable); else p = safe_snprintf(p, end, " ,"); if (a && altBaroReliable(a)) p = safe_snprintf(p, end, "%5d,ft,", a->baro_alt); else p = safe_snprintf(p, end, " ,ft,"); if (a && trackDataValid(&a->geom_rate_valid)) { p = safe_snprintf(p, end, "%5d", a->geom_rate); } else if (a && trackDataValid(&a->baro_rate_valid)) { p = safe_snprintf(p, end, "%5d", a->baro_rate); } else { p = safe_snprintf(p, end, " "); } p = safe_snprintf(p, end, ",fpm,"); p = safe_snprintf(p, end, "ARA:,"); for (int i = 9; i <= 15; i++) p = safe_snprintf(p, end, "%u", getbit(bytes, i)); p = safe_snprintf(p, end, ",RAT:,%u", getbit(bytes, 27)); p = safe_snprintf(p, end, ",MTE:,%u", getbit(bytes, 28)); p = safe_snprintf(p, end, ",RAC:,"); for (int i = 23; i <= 26; i++) p = safe_snprintf(p, end, "%u", getbit(bytes, i)); p = safe_snprintf(p, end, ", "); if (getbits(bytes, 23, 26)) { char *racs[4] = { "not below", "not above", "not left ", "not right" }; for (int i = 23; i <= 26; i++) { if (getbit(bytes, i)) p = safe_snprintf(p, end, "%s", racs[i-23]); } } else { p = safe_snprintf(p, end, " "); } p = safe_snprintf(p, end, ", "); // https://mode-s.org/decode/book-the_1090mhz_riddle-junzi_sun.pdf // // https://www.faa.gov/documentlibrary/media/advisory_circular/tcas%20ii%20v7.1%20intro%20booklet.pdf /* RAs can be classified as positive (e.g., climb, descend) or negative (e.g., limit climb to 0 fpm, limit descend to 500 fpm). The term "Vertical Speed Limit" (VSL) is equivalent to "negative." RAs can also be classified as preventive or corrective, depending on whether own aircraft is, or is not, in conformance with the RA target altitude rate. Corrective RAs require a change in vertical speed; preventive RAs do not require a change in vertical speed */ if (rat) { p = safe_snprintf(p, end, "Clear of Conflict"); } else if (ara) { p = safe_snprintf(p, end, "RA:"); bool corr = getbit(bytes, 10); // corrective / preventive bool down = getbit(bytes, 11); // downward sense / upward sense bool increase = getbit(bytes, 12); // increase rate bool reversal = getbit(bytes, 13); // sense reversal bool crossing = getbit(bytes, 14); // altitude crossing bool positive = getbit(bytes, 15); // positive: (Maintain climb / descent) / (Climb / descend): requires more than 1500 fpm vertical rate // !positive: (Do not / reduce) (climb / descend) if (corr && positive) { if (!reversal) { // reversal has priority and comes later } else if (increase) { p = safe_snprintf(p, end, " Increase"); } if (down) p = safe_snprintf(p, end, " Descend"); else p = safe_snprintf(p, end, " Climb"); if (reversal) { if (down) p = safe_snprintf(p, end, "; Descend"); else p = safe_snprintf(p, end, "; Climb"); p = safe_snprintf(p, end, " NOW"); } if (crossing) { p = safe_snprintf(p, end, "; Crossing"); if (down) p = safe_snprintf(p, end, " Descend"); else p = safe_snprintf(p, end, " Climb"); } } if (corr && !positive) { p = safe_snprintf(p, end, " Level Off"); } if (!corr && positive) { p = safe_snprintf(p, end, " Maintain vertical Speed"); if (crossing) { p = safe_snprintf(p, end, "; Crossing Maintain"); } } if (!corr && !positive) { p = safe_snprintf(p, end, " Monitor vertical Speed"); } } else if (!ara && mte) { p = safe_snprintf(p, end, "RA multithreat:"); if (getbit(bytes, 10)) p = safe_snprintf(p, end, " correct upwards;"); if (getbit(bytes, 11)) p = safe_snprintf(p, end, " climb required;"); if (getbit(bytes, 12)) p = safe_snprintf(p, end, " correct downwards;"); if (getbit(bytes, 13)) p = safe_snprintf(p, end, " descent required;"); if (getbit(bytes, 14)) p = safe_snprintf(p, end, " crossing;"); if (getbit(bytes, 15)) p = safe_snprintf(p, end, " increase/maintain vertical rate"); else p = safe_snprintf(p, end, " reduce/limit vertical rate"); } int tti = getbits(bytes, 29, 30); uint32_t threatAddr = getbits(bytes, 31, 54); if (tti == 1) p = safe_snprintf(p, end, "; TIDh: %06x", threatAddr); if (debug) { p = safe_snprintf(p, end, "; TTI: "); for (int i = 29; i <= 30; i++) p = safe_snprintf(p, end, "%u", getbit(bytes, i)); } p = safe_snprintf(p, end, ","); return p; } char *sprintAircraftObject(char *p, char *end, struct aircraft *a, int64_t now, int printMode, struct modesMessage *mm) { // printMode == 0: aircraft.json / globe.json / apiBuffer // printMode == 1: trace.json // printMode == 2: jsonPositionOutput p = safe_snprintf(p, end, "{"); if (printMode == 2) p = safe_snprintf(p, end, "\"now\" : %.3f,", now / 1000.0); if (printMode != 1) p = safe_snprintf(p, end, "\"hex\":\"%s%06x\",", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); p = safe_snprintf(p, end, "\"type\":\"%s\"", addrtype_enum_string(a->addrtype)); if (trackDataValid(&a->callsign_valid)) { char buf[128]; p = safe_snprintf(p, end, ",\"flight\":\"%s\"", jsonEscapeString(a->callsign, buf, sizeof(buf))); } if (printMode != 1) { if (Modes.db) { if (a->registration[0]) p = safe_snprintf(p, end, ",\"r\":\"%.*s\"", (int) sizeof(a->registration), a->registration); if (a->typeCode[0]) p = safe_snprintf(p, end, ",\"t\":\"%.*s\"", (int) sizeof(a->typeCode), a->typeCode); if (a->dbFlags) { p = safe_snprintf(p, end, ",\"dbFlags\":%u", a->dbFlags); } if (Modes.jsonLongtype) { if (a->typeLong[0]) p = safe_snprintf(p, end, ",\"desc\":\"%.*s\"", (int) sizeof(a->typeLong), a->typeLong); if (a->ownOp[0]) p = safe_snprintf(p, end, ",\n\"ownOp\":\"%.*s\"", (int) sizeof(a->ownOp), a->ownOp); if (a->year[0]) p = safe_snprintf(p, end, ",\n\"year\":\"%.*s\"", (int) sizeof(a->year), a->year); } } if (trackDataValid(&a->airground_valid) && a->airground == AG_GROUND) { if (0) p = safe_snprintf(p, end, ",\"ground\":true"); else p = safe_snprintf(p, end, ",\"alt_baro\":\"ground\""); } else { if (altBaroReliable(a)) p = safe_snprintf(p, end, ",\"alt_baro\":%d", a->baro_alt); if (0) p = safe_snprintf(p, end, ",\"ground\":false"); } } if (trackDataValid(&a->geom_alt_valid)) p = safe_snprintf(p, end, ",\"alt_geom\":%d", a->geom_alt); if (printMode != 1 && trackDataValid(&a->gs_valid)) p = safe_snprintf(p, end, ",\"gs\":%.1f", a->gs); if (trackDataValid(&a->ias_valid)) p = safe_snprintf(p, end, ",\"ias\":%u", a->ias); if (trackDataValid(&a->tas_valid)) p = safe_snprintf(p, end, ",\"tas\":%u", a->tas); if (trackDataValid(&a->mach_valid)) p = safe_snprintf(p, end, ",\"mach\":%.3f", a->mach); if (now < a->wind_updated + TRACK_EXPIRE && abs(a->wind_altitude - a->baro_alt) < 500) { p = safe_snprintf(p, end, ",\"wd\":%.0f", a->wind_direction); p = safe_snprintf(p, end, ",\"ws\":%.0f", a->wind_speed); } if (now < a->oat_updated + TRACK_EXPIRE) { p = safe_snprintf(p, end, ",\"oat\":%.0f", a->oat); p = safe_snprintf(p, end, ",\"tat\":%.0f", a->tat); } if (trackDataValid(&a->track_valid)) { p = safe_snprintf(p, end, ",\"track\":%.2f", a->track); } else if (printMode != 1 && trackDataValid(&a->pos_reliable_valid) && !(trackDataValid(&a->airground_valid) && a->airground == AG_GROUND)) { p = safe_snprintf(p, end, ",\"calc_track\":%.0f", a->calc_track); } if (trackDataValid(&a->track_rate_valid)) p = safe_snprintf(p, end, ",\"track_rate\":%.2f", a->track_rate); if (trackDataValid(&a->roll_valid)) p = safe_snprintf(p, end, ",\"roll\":%.2f", a->roll); if (trackDataValid(&a->mag_heading_valid)) p = safe_snprintf(p, end, ",\"mag_heading\":%.2f", a->mag_heading); if (trackDataValid(&a->true_heading_valid)) p = safe_snprintf(p, end, ",\"true_heading\":%.2f", a->true_heading); if (trackDataValid(&a->baro_rate_valid)) p = safe_snprintf(p, end, ",\"baro_rate\":%d", a->baro_rate); if (trackDataValid(&a->geom_rate_valid)) p = safe_snprintf(p, end, ",\"geom_rate\":%d", a->geom_rate); if (trackDataValid(&a->squawk_valid)) p = safe_snprintf(p, end, ",\"squawk\":\"%04x\"", a->squawk); if (trackDataValid(&a->emergency_valid)) p = safe_snprintf(p, end, ",\"emergency\":\"%s\"", emergency_enum_string(a->emergency)); if (a->category != 0) p = safe_snprintf(p, end, ",\"category\":\"%02X\"", a->category); if (trackDataValid(&a->nav_qnh_valid)) p = safe_snprintf(p, end, ",\"nav_qnh\":%.1f", a->nav_qnh); if (trackDataValid(&a->nav_altitude_mcp_valid)) p = safe_snprintf(p, end, ",\"nav_altitude_mcp\":%d", a->nav_altitude_mcp); if (trackDataValid(&a->nav_altitude_fms_valid)) p = safe_snprintf(p, end, ",\"nav_altitude_fms\":%d", a->nav_altitude_fms); if (trackDataValid(&a->nav_heading_valid)) p = safe_snprintf(p, end, ",\"nav_heading\":%.2f", a->nav_heading); if (trackDataValid(&a->nav_modes_valid)) { p = safe_snprintf(p, end, ",\"nav_modes\":["); p = append_nav_modes(p, end, a->nav_modes, "\"", ","); p = safe_snprintf(p, end, "]"); } if (printMode != 1) { if (trackDataValid(&a->pos_reliable_valid)) { p = safe_snprintf(p, end, ",\"lat\":%f,\"lon\":%f,\"nic\":%u,\"rc\":%u,\"seen_pos\":%.3f", a->latReliable, a->lonReliable, a->pos_nic_reliable, a->pos_rc_reliable, (now < a->pos_reliable_valid.updated) ? 0 : ((now - a->pos_reliable_valid.updated) / 1000.0)); #if defined(TRACKS_UUID) { char uuid[32]; // needs 18 chars and null byte sprint_uuid1(a->lastPosReceiverId, uuid); p = safe_snprintf(p, end, ",\"rId\":\"%s\"", uuid); } #endif #if defined(PRINT_UUIDS) { char uuid[32]; // needs 18 chars and null byte p = safe_snprintf(p, end, ",\"recentReceiverIds\":["); int64_t printNewer = now - 3 * SECONDS; int first = 1; for (int i = 0; i < RECENT_RECEIVER_IDS; i++) { idTime *entry = &a->recentReceiverIds[i]; if (entry->id != 0 && entry->time > printNewer) { if (first) { first = 0; } else { p = safe_snprintf(p, end, ","); } sprint_uuid1(entry->id, uuid); p = safe_snprintf(p, end, "\"%s\"", uuid); } } p = safe_snprintf(p, end, "]"); } #endif if (Modes.userLocationValid && Modes.json_location_accuracy != 0) { p = safe_snprintf(p, end, ",\"r_dst\":%.3f,\"r_dir\":%.1f", a->receiver_distance / 1852.0, a->receiver_direction); } } else { if (now < a->rr_seen + 2 * MINUTES) { p = safe_snprintf(p, end, ",\"rr_lat\":%.1f,\"rr_lon\":%.1f", a->rr_lat, a->rr_lon); } if (now < a->seenPosReliable + 14 * 24 * HOURS) { p = safe_snprintf(p, end, ",\"lastPosition\":{\"lat\":%f,\"lon\":%f,\"nic\":%u,\"rc\":%u,\"seen_pos\":%.3f}", a->latReliable, a->lonReliable, a->pos_nic_reliable, a->pos_rc_reliable, (now < a->seenPosReliable) ? 0 : ((now - a->seenPosReliable) / 1000.0)); } } if (nogps(now, a)) { p = safe_snprintf(p, end, ",\"gpsOkBefore\":%.1f", a->seenAdsbReliable / 1000.0); if (a->seenAdsbLat || a->seenAdsbLon) { p = safe_snprintf(p, end, ",\"gpsOkLat\":%f,\"gpsOkLon\":%f", a->seenAdsbLat, a->seenAdsbLon); } } } if (printMode == 1 && trackDataValid(&a->pos_reliable_valid)) { p = safe_snprintf(p, end, ",\"nic\":%u,\"rc\":%u", a->pos_nic_reliable, a->pos_rc_reliable); } if (a->adsb_version >= 0) p = safe_snprintf(p, end, ",\"version\":%d", a->adsb_version); if (trackDataValid(&a->nic_baro_valid)) p = safe_snprintf(p, end, ",\"nic_baro\":%u", a->nic_baro); if (trackDataValid(&a->nac_p_valid)) p = safe_snprintf(p, end, ",\"nac_p\":%u", a->nac_p); if (trackDataValid(&a->nac_v_valid)) p = safe_snprintf(p, end, ",\"nac_v\":%u", a->nac_v); if (trackDataValid(&a->sil_valid)) p = safe_snprintf(p, end, ",\"sil\":%u", a->sil); if (a->sil_type != SIL_INVALID) p = safe_snprintf(p, end, ",\"sil_type\":\"%s\"", sil_type_enum_string(a->sil_type)); if (trackDataValid(&a->gva_valid)) p = safe_snprintf(p, end, ",\"gva\":%u", a->gva); if (trackDataValid(&a->sda_valid)) p = safe_snprintf(p, end, ",\"sda\":%u", a->sda); if (trackDataValid(&a->alert_valid)) p = safe_snprintf(p, end, ",\"alert\":%u", a->alert); if (trackDataValid(&a->spi_valid)) p = safe_snprintf(p, end, ",\"spi\":%u", a->spi); /* if (a->pos_reliable_valid.source == SOURCE_JAERO) p = safe_snprintf(p, end, ",\"jaero\": true"); if (a->pos_reliable_valid.source == SOURCE_SBS) p = safe_snprintf(p, end, ",\"sbs_other\": true"); */ if (printMode != 1) { p = safe_snprintf(p, end, ",\"mlat\":"); p = append_flags(p, end, a, SOURCE_MLAT); p = safe_snprintf(p, end, ",\"tisb\":"); p = append_flags(p, end, a, SOURCE_TISB); p = safe_snprintf(p, end, ",\"messages\":%u,\"seen\":%.1f,\"rssi\":%.1f", a->messages, (now < a->seen) ? 0 : ((now - a->seen) / 1000.0), getSignal(a)); } if (trackDataAge(now, &a->acas_ra_valid) < 15 * SECONDS || (mm && mm->acas_ra_valid)) { p = safe_snprintf(p, end, ",\"acas_ra\":"); p = sprintACASJson(p, end, a->acas_ra, (mm && mm->acas_ra_valid) ? mm : NULL, (mm && mm->acas_ra_valid) ? now : a->acas_ra_valid.updated); } p = safe_snprintf(p, end, "}"); return p; } char *sprintAircraftRecent(char *p, char *end, struct aircraft *a, int64_t now, int printMode, struct modesMessage *mm, int64_t recent) { if (printMode == 1) { } char *start = p; p = safe_snprintf(p, end, "{"); //p = safe_snprintf(p, end, "\"now\" : %.0f,", now / 1000.0); p = safe_snprintf(p, end, "\"hex\":\"%s%06x\",", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); p = safe_snprintf(p, end, "\"type\":\"%s\"", addrtype_enum_string(a->addrtype)); char *startRecent = p; if (recent > trackDataAge(now, &a->callsign_valid)) { char buf[128]; p = safe_snprintf(p, end, ",\"flight\":\"%s\"", jsonEscapeString(a->callsign, buf, sizeof(buf))); } if (recent > trackDataAge(now, &a->airground_valid)) { if (a->airground == AG_GROUND) { p = safe_snprintf(p, end, ",\"ground\":true"); } else if (a->airground == AG_AIRBORNE ) { p = safe_snprintf(p, end, ",\"ground\":false"); } } if (recent > trackDataAge(now, &a->baro_alt_valid)) p = safe_snprintf(p, end, ",\"alt_baro\":%d", a->baro_alt); if (recent > trackDataAge(now, &a->geom_alt_valid)) p = safe_snprintf(p, end, ",\"alt_geom\":%d", a->geom_alt); if (recent > trackDataAge(now, &a->gs_valid)) p = safe_snprintf(p, end, ",\"gs\":%.1f", a->gs); if (recent > trackDataAge(now, &a->ias_valid)) p = safe_snprintf(p, end, ",\"ias\":%u", a->ias); if (recent > trackDataAge(now, &a->tas_valid)) p = safe_snprintf(p, end, ",\"tas\":%u", a->tas); if (recent > trackDataAge(now, &a->mach_valid)) p = safe_snprintf(p, end, ",\"mach\":%.3f", a->mach); if (now < a->wind_updated + recent && abs(a->wind_altitude - a->baro_alt) < 500) { p = safe_snprintf(p, end, ",\"wd\":%.0f", a->wind_direction); p = safe_snprintf(p, end, ",\"ws\":%.0f", a->wind_speed); } if (now < a->oat_updated + recent) { p = safe_snprintf(p, end, ",\"oat\":%.0f", a->oat); p = safe_snprintf(p, end, ",\"tat\":%.0f", a->tat); } if (recent > trackDataAge(now, &a->track_valid)) p = safe_snprintf(p, end, ",\"track\":%.2f", a->track); if (recent > trackDataAge(now, &a->track_rate_valid)) p = safe_snprintf(p, end, ",\"track_rate\":%.2f", a->track_rate); if (recent > trackDataAge(now, &a->roll_valid)) p = safe_snprintf(p, end, ",\"roll\":%.2f", a->roll); if (recent > trackDataAge(now, &a->mag_heading_valid)) p = safe_snprintf(p, end, ",\"mag_heading\":%.2f", a->mag_heading); if (recent > trackDataAge(now, &a->true_heading_valid)) p = safe_snprintf(p, end, ",\"true_heading\":%.2f", a->true_heading); if (recent > trackDataAge(now, &a->baro_rate_valid)) p = safe_snprintf(p, end, ",\"baro_rate\":%d", a->baro_rate); if (recent > trackDataAge(now, &a->geom_rate_valid)) p = safe_snprintf(p, end, ",\"geom_rate\":%d", a->geom_rate); if (recent > trackDataAge(now, &a->squawk_valid)) p = safe_snprintf(p, end, ",\"squawk\":\"%04x\"", a->squawk); if (recent > trackDataAge(now, &a->emergency_valid)) p = safe_snprintf(p, end, ",\"emergency\":\"%s\"", emergency_enum_string(a->emergency)); if (recent > trackDataAge(now, &a->nav_qnh_valid)) p = safe_snprintf(p, end, ",\"nav_qnh\":%.1f", a->nav_qnh); if (recent > trackDataAge(now, &a->nav_altitude_mcp_valid)) p = safe_snprintf(p, end, ",\"nav_altitude_mcp\":%d", a->nav_altitude_mcp); if (recent > trackDataAge(now, &a->nav_altitude_fms_valid)) p = safe_snprintf(p, end, ",\"nav_altitude_fms\":%d", a->nav_altitude_fms); if (recent > trackDataAge(now, &a->nav_heading_valid)) p = safe_snprintf(p, end, ",\"nav_heading\":%.2f", a->nav_heading); if (recent > trackDataAge(now, &a->nav_modes_valid)) { p = safe_snprintf(p, end, ",\"nav_modes\":["); p = append_nav_modes(p, end, a->nav_modes, "\"", ","); p = safe_snprintf(p, end, "]"); } if (recent > trackDataAge(now, &a->pos_reliable_valid)) { p = safe_snprintf(p, end, ",\"lat\":%f,\"lon\":%f,\"nic\":%u,\"rc\":%u,\"seen_pos\":%.3f", a->latReliable, a->lonReliable, a->pos_nic_reliable, a->pos_rc_reliable, (now < a->pos_reliable_valid.updated) ? 0 : ((now - a->pos_reliable_valid.updated) / 1000.0)); if (a->adsb_version >= 0) p = safe_snprintf(p, end, ",\"version\":%d", a->adsb_version); if (a->category != 0) p = safe_snprintf(p, end, ",\"category\":\"%02X\"", a->category); } if (recent > trackDataAge(now, &a->nic_baro_valid)) p = safe_snprintf(p, end, ",\"nic_baro\":%u", a->nic_baro); if (recent > trackDataAge(now, &a->nac_p_valid)) p = safe_snprintf(p, end, ",\"nac_p\":%u", a->nac_p); if (recent > trackDataAge(now, &a->nac_v_valid)) p = safe_snprintf(p, end, ",\"nac_v\":%u", a->nac_v); if (recent > trackDataAge(now, &a->sil_valid)) { p = safe_snprintf(p, end, ",\"sil\":%u", a->sil); if (a->sil_type != SIL_INVALID) p = safe_snprintf(p, end, ",\"sil_type\":\"%s\"", sil_type_enum_string(a->sil_type)); } if (recent > trackDataAge(now, &a->gva_valid)) p = safe_snprintf(p, end, ",\"gva\":%u", a->gva); if (recent > trackDataAge(now, &a->sda_valid)) p = safe_snprintf(p, end, ",\"sda\":%u", a->sda); if (recent > trackDataAge(now, &a->alert_valid)) p = safe_snprintf(p, end, ",\"alert\":%u", a->alert); if (recent > trackDataAge(now, &a->spi_valid)) p = safe_snprintf(p, end, ",\"spi\":%u", a->spi); // nothing recent, print nothing if (startRecent == p) { return start; } /* p = safe_snprintf(p, end, ",\"mlat\":"); p = append_flags(p, end, a, SOURCE_MLAT); p = safe_snprintf(p, end, ",\"tisb\":"); p = append_flags(p, end, a, SOURCE_TISB); p = safe_snprintf(p, end, ",\"messages\":%u,\"seen\":%.1f,\"rssi\":%.1f", a->messages, (now < a->seen) ? 0 : ((now - a->seen) / 1000.0), 10 * log10((a->signalLevel[0] + a->signalLevel[1] + a->signalLevel[2] + a->signalLevel[3] + a->signalLevel[4] + a->signalLevel[5] + a->signalLevel[6] + a->signalLevel[7]) / 8 + 1.125e-5)); */ if (trackDataAge(now, &a->acas_ra_valid) < recent) { p = safe_snprintf(p, end, ",\"acas_ra_timestamp\":%.2f", now / 1000.0); if (mm && mm->acas_ra_valid) p = safe_snprintf(p, end, ",\"acas_ra_df_type\":%d", mm->msgtype); p = safe_snprintf(p, end, ",\"acas_ra_mv_mb_bytes_hex\":\""); for (int i = 0; i < 7; ++i) { p = safe_snprintf(p, end, "%02X", (unsigned) a->acas_ra[i]); } p = safe_snprintf(p, end, "\""); p = safe_snprintf(p, end, ",\"acas_ra_csvline\":\""); p = sprintACASInfoShort(p, end, a->addr, a->acas_ra, a, (mm && mm->acas_ra_valid) ? mm : NULL, a->acas_ra_valid.updated); p = safe_snprintf(p, end, "\""); } p = safe_snprintf(p, end, "}"); return p; } int includeAircraftJson(int64_t now, struct aircraft *a) { if (unlikely(a == NULL)) { fprintf(stderr, "includeAircraftJson: got NULL pointer\n"); return 0; } // include all aircraft with valid position if (a->pos_reliable_valid.source != SOURCE_INVALID) { return 1; } if (a->messages < 2 && a->addrtype != ADDR_JAERO && a->addrtype != ADDR_OTHER) { return 0; } if (a->nogpsCounter >= NOGPS_SHOW && now - a->seenAdsbReliable < NOGPS_DWELL) { return 1; } // include active aircraft if (now < a->seen + TRACK_EXPIRE) { return 1; } if (a->addrtype == ADDR_JAERO && now < a->seen + Modes.trackExpireJaero) { return 1; } return 0; } struct char_buffer generateAircraftBin(threadpool_buffer_t *pbuffer) { struct char_buffer cb; int64_t now = mstime(); struct aircraft *a; struct craftArray *ca = &Modes.aircraftActive; ca_lock_read(ca); size_t alloc = 4096 + (ca->len + 64) * sizeof(struct binCraft); char *buf = check_grow_threadpool_buffer_t(pbuffer, alloc); char *p = buf; char *end = buf + alloc; uint32_t elementSize = sizeof(struct binCraft); memset(p, 0, elementSize); #define memWrite(p, var) do { memcpy(p, &var, sizeof(var)); p += sizeof(var); } while(0) memWrite(p, now); memWrite(p, elementSize); uint32_t ac_count_pos = Modes.globalStatsCount.readsb_aircraft_with_position; memWrite(p, ac_count_pos); uint32_t index = 314159; // unnecessary memWrite(p, index); int16_t south = -90; int16_t west = -180; int16_t north = 90; int16_t east = 180; memWrite(p, south); memWrite(p, west); memWrite(p, north); memWrite(p, east); uint32_t messageCount = Modes.stats_current.messages_total + Modes.stats_alltime.messages_total; memWrite(p, messageCount); int32_t receiver_lat = 0; int32_t receiver_lon = 0; if (Modes.userLocationValid && Modes.json_location_accuracy != 0) { receiver_lat = (int32_t) nearbyint(Modes.fUserLat * 1E6); receiver_lon = (int32_t) nearbyint(Modes.fUserLon * 1E6); } memWrite(p, receiver_lat); memWrite(p, receiver_lon); memWrite(p, Modes.binCraftVersion); uint32_t messageRate = nearbyint(Modes.messageRate * 10); memWrite(p, messageRate); uint32_t flags = 0; if (Modes.json_globe_index || Modes.apiShutdownDelay) { flags |= (1 << 0); } memWrite(p, flags); if (p - buf > (int) elementSize) fprintf(stderr, "aircraftBin: header too large oos4tooT\n"); p = buf + elementSize; for (int i = 0; i < ca->len; i++) { a = ca->list[i]; if (a == NULL) continue; if (!includeAircraftJson(now, a)) { continue; } // check if we have enough space if ((p + 2 * sizeof(struct binCraft)) >= end) { fprintf(stderr, "increase buffer size: iXok9ieD\n"); break; } toBinCraft(a, (struct binCraft *) p, now); p += sizeof(struct binCraft); } ca_unlock_read(ca); cb.len = p - buf; cb.buffer = buf; return cb; #undef memWrite } struct char_buffer generateGlobeBin(int globe_index, int mil, threadpool_buffer_t *pbuffer) { struct char_buffer cb = { 0 }; int64_t now = mstime(); struct aircraft *a; ssize_t alloc = 4096 + 4 * sizeof(struct binCraft); struct craftArray *ca = NULL; if (globe_index == -1) { ca = &Modes.aircraftActive; } else if (globe_index <= GLOBE_MAX_INDEX) { ca = &Modes.globeLists[globe_index]; } if (!ca) { fprintf(stderr, "generateGlobeBin: bad globe_index: %d\n", globe_index); return cb; } ca_lock_read(ca); alloc += ca->len * sizeof(struct binCraft); char *buf = check_grow_threadpool_buffer_t(pbuffer, alloc); char *p = buf; char *end = buf + alloc; uint32_t elementSize = sizeof(struct binCraft); memset(p, 0, elementSize); #define memWrite(p, var) do { memcpy(p, &var, sizeof(var)); p += sizeof(var); } while(0) memWrite(p, now); memWrite(p, elementSize); uint32_t ac_count_pos = Modes.globalStatsCount.readsb_aircraft_with_position; memWrite(p, ac_count_pos); uint32_t index = globe_index < 0 ? 42777 : globe_index; memWrite(p, index); int16_t south = -90; int16_t west = -180; int16_t north = 90; int16_t east = 180; if (globe_index >= GLOBE_MIN_INDEX) { int grid = GLOBE_INDEX_GRID; south = ((globe_index - GLOBE_MIN_INDEX) / GLOBE_LAT_MULT) * grid - 90; west = ((globe_index - GLOBE_MIN_INDEX) % GLOBE_LAT_MULT) * grid - 180; north = south + grid; east = west + grid; } else if (globe_index >= 0) { struct tile *tiles = Modes.json_globe_special_tiles; struct tile tile = tiles[globe_index]; south = tile.south; west = tile.west; north = tile.north; east = tile.east; } memWrite(p, south); memWrite(p, west); memWrite(p, north); memWrite(p, east); uint32_t messageCount = Modes.stats_current.messages_total + Modes.stats_alltime.messages_total; memWrite(p, messageCount); int32_t dummy1 = 0; memWrite(p, dummy1); int32_t dummy2 = 0; memWrite(p, dummy2); memWrite(p, Modes.binCraftVersion); uint32_t messageRate = nearbyint(Modes.messageRate * 10); memWrite(p, messageRate); uint32_t flags = 0; if (Modes.json_globe_index || Modes.apiShutdownDelay) { flags |= (1 << 0); } memWrite(p, flags); if (p - buf > (int) elementSize) fprintf(stderr, "buffer overrun globeBin\n"); p = buf + elementSize; for (int i = 0; i < ca->len; i++) { a = ca->list[i]; if (a == NULL) continue; if (!includeAircraftJson(now, a)) continue; if (mil && !(a->dbFlags & 1)) continue; // check if we have enough space if ((p + 2 * sizeof(struct binCraft)) >= end) { fprintf(stderr, "buffer insufficient globeBin\n"); break; } toBinCraft(a, (struct binCraft *) p, now); p += sizeof(struct binCraft); } ca_unlock_read(ca); cb.len = p - buf; cb.buffer = buf; return cb; #undef memWrite } struct char_buffer generateGlobeJson(int globe_index, threadpool_buffer_t *pbuffer) { struct char_buffer cb = { 0 }; int64_t now = mstime(); struct aircraft *a; ssize_t alloc = 4096; // The initial buffer is resized as needed struct craftArray *ca = NULL; if (globe_index <= GLOBE_MAX_INDEX) { ca = &Modes.globeLists[globe_index]; } if (!ca) { fprintf(stderr, "generateAircraftJson: bad globe_index: %d\n", globe_index); return cb; } ca_lock_read(ca); alloc += ca->len * 2048; // 2048 bytes per potential aircraft object char *buf = check_grow_threadpool_buffer_t(pbuffer, alloc); char *p = buf; char *end = buf + alloc; p = safe_snprintf(p, end, "{ \"now\" : %.3f,\n" " \"messages\" : %u,\n", now / 1000.0, Modes.stats_current.messages_total + Modes.stats_alltime.messages_total); p = safe_snprintf(p, end, " \"global_ac_count_withpos\" : %d,\n", Modes.globalStatsCount.readsb_aircraft_with_position ); p = safe_snprintf(p, end, " \"globeIndex\" : %d, ", globe_index); if (globe_index >= GLOBE_MIN_INDEX) { int grid = GLOBE_INDEX_GRID; int lat = ((globe_index - GLOBE_MIN_INDEX) / GLOBE_LAT_MULT) * grid - 90; int lon = ((globe_index - GLOBE_MIN_INDEX) % GLOBE_LAT_MULT) * grid - 180; p = safe_snprintf(p, end, "\"south\" : %d, " "\"west\" : %d, " "\"north\" : %d, " "\"east\" : %d,\n", lat, lon, lat + grid, lon + grid); } else { struct tile *tiles = Modes.json_globe_special_tiles; struct tile tile = tiles[globe_index]; p = safe_snprintf(p, end, "\"south\" : %d, " "\"west\" : %d, " "\"north\" : %d, " "\"east\" : %d,\n", tile.south, tile.west, tile.north, tile.east); } p = safe_snprintf(p, end, " \"aircraft\" : ["); for (int i = 0; i < ca->len; i++) { a = ca->list[i]; if (a == NULL) continue; if (!includeAircraftJson(now, a)) continue; // check if we have enough space // disable this due to passbuffer .... this would be a memleak if (0 && (p + 2000) >= end) { int used = p - buf; alloc *= 2; buf = (char *) realloc(buf, alloc); p = buf + used; end = buf + alloc; } p = safe_snprintf(p, end, "\n"); p = sprintAircraftObject(p, end, a, now, 0, NULL); p = safe_snprintf(p, end, ","); if (p >= end) { fprintf(stderr, "buffer overrun aircraft json\n"); break; } } if (*(p-1) == ',') p--; p = safe_snprintf(p, end, "\n ]\n}\n"); ca_unlock_read(ca); cb.len = p - buf; cb.buffer = buf; return cb; } struct char_buffer generateAircraftJson(int64_t onlyRecent){ struct char_buffer cb; int64_t now = mstime(); struct aircraft *a; struct craftArray *ca = &Modes.aircraftActive; ca_lock_read(ca); size_t alloc = 4096 + ca->len * 2048; // The initial buffer is resized as needed char *buf = cmalloc(alloc); char *p = buf; char *end = buf + alloc; p = safe_snprintf(p, end, "{ \"now\" : %.3f,\n" " \"messages\" : %u,\n", now / 1000.0, Modes.stats_current.messages_total + Modes.stats_alltime.messages_total); p = safe_snprintf(p, end, " \"aircraft\" : ["); for (int i = 0; i < ca->len; i++) { a = ca->list[i]; if (a == NULL) continue; //fprintf(stderr, "a: %05x\n", a->addr); if (!includeAircraftJson(now, a)) continue; // check if we have enough space if ((p + 2000) >= end) { int used = p - buf; alloc *= 2; buf = (char *) realloc(buf, alloc); p = buf + used; end = buf + alloc; } char *beforeSprint = p; p = safe_snprintf(p, end, "\n"); if (onlyRecent) { p = sprintAircraftRecent(p, end, a, now, 0, NULL, onlyRecent); } else { p = sprintAircraftObject(p, end, a, now, 0, NULL); } if (p - beforeSprint < 5) { p = beforeSprint; } else { p = safe_snprintf(p, end, ","); } if (p >= end) fprintf(stderr, "buffer overrun aircraft json\n"); } if (*(p-1) == ',') p--; p = safe_snprintf(p, end, "\n ]\n}\n"); // fprintf(stderr, "%u\n", ac_counter); ca_unlock_read(ca); cb.len = p - buf; cb.buffer = buf; return cb; } static char *sprintTracePoint(char *p, char *end, struct state *state, struct state_all *state_all, int64_t referenceTs, int64_t now, struct aircraft *a) { int baro_alt = state->baro_alt / _alt_factor; int baro_rate = state->baro_rate / _rate_factor; int geom_alt = state->geom_alt / _alt_factor; int geom_rate = state->geom_rate / _rate_factor; int altitude = baro_alt; int altitude_valid = state->baro_alt_valid; int altitude_geom = 0; if (!altitude_valid && state->geom_alt_valid) { altitude = geom_alt; altitude_valid = 1; altitude_geom = 1; } int rate = baro_rate; int rate_valid = state->baro_rate_valid; int rate_geom = 0; if (!rate_valid && state->geom_rate_valid) { rate = geom_rate; rate_valid = 1; rate_geom = 1; } // in the air p = safe_snprintf(p, end, "\n[%.2f,%f,%f", (state->timestamp - referenceTs) / 1000.0, state->lat / 1E6, state->lon / 1E6); if (state->timestamp > now + 2 * SECONDS) { fprintf(stderr, "%06x WAT? trace timestamp in the future: %.3f > %.3f\n", a->addr, state->timestamp / 1000.0, now / 1000.0); } if (state->on_ground) p = safe_snprintf(p, end, ",\"ground\""); else if (altitude_valid) p = safe_snprintf(p, end, ",%d", altitude); else p = safe_snprintf(p, end, ",null"); if (state->gs_valid) p = safe_snprintf(p, end, ",%.1f", state->gs / _gs_factor); else p = safe_snprintf(p, end, ",null"); if (state->track_valid) p = safe_snprintf(p, end, ",%.1f", state->track / _track_factor); else p = safe_snprintf(p, end, ",null"); int bitfield = (altitude_geom << 3) | (rate_geom << 2) | (state->leg_marker << 1) | (state->stale << 0); p = safe_snprintf(p, end, ",%d", bitfield); if (rate_valid) p = safe_snprintf(p, end, ",%d", rate); else p = safe_snprintf(p, end, ",null"); if (state_all) { int64_t now = state->timestamp; struct aircraft b; memset(&b, 0, sizeof(struct aircraft)); struct aircraft *ac = &b; from_state_all(state_all, state, ac, now); p = safe_snprintf(p, end, ","); p = sprintAircraftObject(p, end, ac, now, 1, NULL); } else { p = safe_snprintf(p, end, ",null"); } p = safe_snprintf(p, end, ",\"%s\"", addrtype_enum_string(state->addrtype)); if (state->geom_alt_valid) p = safe_snprintf(p, end, ",%d", geom_alt); else p = safe_snprintf(p, end, ",null"); if (state->geom_rate_valid) p = safe_snprintf(p, end, ",%d", geom_rate); else p = safe_snprintf(p, end, ",null"); if (state->ias_valid) p = safe_snprintf(p, end, ",%d", state->ias); else p = safe_snprintf(p, end, ",null"); if (state->roll_valid) p = safe_snprintf(p, end, ",%.1f", state->roll / _roll_factor); else p = safe_snprintf(p, end, ",null"); #if defined(TRACKS_UUID) char uuid[32]; // needs 8 chars and null byte sprint_uuid1_partial(state->receiverId, uuid); p = safe_snprintf(p, end, ",\"%s\"", uuid); #endif p = safe_snprintf(p, end, "],"); return p; } static void checkTraceCache(struct aircraft *a, traceBuffer tb, int64_t now) { if (now - a->seenPosReliable > TRACE_CACHE_LIFETIME) { return; // don't create cache if the aircraft hasn't been seen recently } struct traceCache *cache = &a->traceCache; if (!cache->entries || !cache->json || !cache->json_max) { if (Modes.trace_hist_only & 8) { return; // no cache in this special case } int64_t elapsedReliable = now - a->seenPosReliable; if (elapsedReliable > TRACE_CACHE_LIFETIME / 2 || tb.len == 0) { //fprintf(stderr, "elapsedReliable: %.3f\n", elapsedReliable / 1000.0); return; } if (cache->entries || cache->json || cache->json_max) { fprintf(stderr, "%06x wtf Eijo0eep\n", a->addr); sfree(cache->entries); sfree(cache->json); } // reset cache for good measure memset(cache, 0x0, sizeof(struct traceCache)); ssize_t size_entries = Modes.traceCachePoints * sizeof(struct traceCacheEntry); cache->json_max = Modes.traceCachePoints * 35 * 8; // 280 per entry // allocate memory cache->totalAlloc = size_entries + cache->json_max; cache->entries = cmalloc(cache->totalAlloc); cache->json = ((char *) cache->entries) + size_entries; if (!cache->entries || !cache->json) { fprintf(stderr, "%06x wtf quae3OhG\n", a->addr); } memset(cache->entries, 0x0, size_entries); memset(cache->json, 0x0, cache->json_max); } char *p; char *end = cache->json + cache->json_max; int firstRecent = imax(0, tb.len - Modes.traceRecentPoints); int firstRecentCache = 0; int64_t firstRecentTs = getState(tb.trace, firstRecent)->timestamp; struct traceCacheEntry *entries = cache->entries; int cacheIndex = 0; int found = 0; while (cacheIndex < cache->entriesLen) { if (entries[cacheIndex].ts == firstRecentTs) { found = 1; firstRecentCache = cacheIndex; break; } cacheIndex++; } int resetCache = 1; // by default reset the cache instead of updating it if (a->addr == TRACE_FOCUS) { if (found) { fprintf(stderr, "%06x firstRecentTs found %d, entriesLen: %d\n", a->addr, firstRecentCache, cache->entriesLen); } else { fprintf(stderr, "%06x firstRecentTs not found, entriesLen: %d\n", a->addr, cache->entriesLen); } } if (found) { resetCache = 0; int recentPoints = tb.len - firstRecent; int usableCachePoints = cache->entriesLen - firstRecentCache; int newEntryCount = recentPoints - usableCachePoints; if (cache->entriesLen + newEntryCount > Modes.traceCachePoints) { // if the cache would get over capacity, do memmove fun! // first move the bookkeeping structs (struct traceCacheEntry) // second move the cached json, offsets and length of it are stored in the bookkeeping structs // remove indexes before firstRecentCache using memmove int moveIndexes = firstRecentCache; // remove json belonging to entries before firstRecentCache using memmove int moveDist = entries[moveIndexes].offset; struct traceCacheEntry *last = &entries[cache->entriesLen - 1]; int moveBytes = (last->offset + last->len) - moveDist; if (cache->entriesLen > Modes.traceCachePoints || moveIndexes <= 0 || moveIndexes > cache->entriesLen) { fprintf(stderr, "%06x unexpected value moveIndexes: %ld firstRecentCache: %ld newEntryCount: %ld cache->entriesLen: %ld Modes.traceCachePoints: %ld\n", a->addr, (long) moveIndexes, (long) firstRecentCache, (long) newEntryCount, (long) cache->entriesLen, (long) Modes.traceCachePoints); resetCache = 1; } if (moveDist + moveBytes > cache->json_max || moveBytes <= 0 || moveDist <= 0) { fprintf(stderr, "%06x in checkTraceCache: prevented illegal memmove: firstRecentCache: %ld moveIndexes: %ld newEntryCount: %ld moveBytes: %ld moveDist: %ld json_max: %ld cache->entriesLen: %ld\n", a->addr, (long) firstRecentCache, (long) moveIndexes, (long) newEntryCount, (long) moveBytes, (long) moveDist, (long) cache->json_max, (long) cache->entriesLen); resetCache = 1; } if (!resetCache) { cache->entriesLen -= moveIndexes; firstRecentCache -= moveIndexes; memmove(entries, entries + moveIndexes, cache->entriesLen * sizeof(struct traceCacheEntry)); memmove(cache->json, cache->json + moveDist, moveBytes); for (int x = 0; x < cache->entriesLen; x++) { entries[x].offset -= moveDist; } if (a->addr == TRACE_FOCUS) { fprintf(stderr, "%06x moveIndexes: %ld firstRecentCache: %ld newEntryCount: %ld cache->entriesLen: %ld Modes.traceCachePoints: %ld\n", a->addr, (long) moveIndexes, (long) firstRecentCache, (long) newEntryCount, (long) cache->entriesLen, (long) Modes.traceCachePoints); } } } } if (cache->referenceTs && firstRecentTs > cache->referenceTs + 8 * HOURS) { if (a->addr == TRACE_FOCUS) { fprintf(stderr, "%06x referenceTs diff: %.1f h\n", a->addr, (getState(tb.trace, firstRecent)->timestamp - cache->referenceTs) / (double) (1 * HOURS)); } // rebuild cache if referenceTs is too old to avoid very large numbers for the relative time resetCache = 1; } if (resetCache) { // reset / initialize stuff / rebuild cache cache->referenceTs = getState(tb.trace, firstRecent)->timestamp; firstRecentCache = 0; cache->entriesLen = 0; if (a->addr == TRACE_FOCUS) { fprintf(stderr, "%06x resetting traceCache\n", a->addr); } } cache->firstRecentCache = firstRecentCache; if (0 && a->addr == TRACE_FOCUS) { fprintf(stderr, "%06x sprintCache: %d points firstRecent starting %d (firstRecentCache starting %d, max %d)\n", a->addr, tb.len - firstRecent, firstRecent, firstRecentCache, Modes.traceCachePoints); } struct traceCacheEntry *entry = NULL; struct state *state = NULL; int64_t lastTs = 0; if (!cache->entries) { fprintf(stderr, "%06x wtf null pointer ?!?! aeV3Geih\n", a->addr); } if (!cache->json) { fprintf(stderr, "%06x wtf null pointer ?!?! ing5umuS\n", a->addr); } if (!cache->entries || !cache->json || !cache->json_max) { fprintf(stderr, "%06x wtf ANgo9Joo\n", a->addr); } for (int i = firstRecent, k = firstRecentCache; i < tb.len && k < Modes.traceCachePoints; i++, k++) { state = getState(tb.trace, i); entry = &entries[k]; if (k < cache->entriesLen && entry->ts == state->timestamp && state->leg_marker == entry->leg_marker) { // cache is up to date, advance continue; } // cache needs updating: cache->entriesLen = k; if (k < 0) { fprintf(stderr, "%06x wtf null pointer ?!?! oonae2OJ\n", a->addr); } if (k == 0) { p = cache->json; } else { struct traceCacheEntry *prev = &entries[k - 1]; p = cache->json + prev->offset + prev->len; if (p < cache->json) { fprintf(stderr, "%06x wtf null pointer ?!?! Ge2thiap\n", a->addr); } } struct state_all *state_all = getStateAll(tb.trace, i); if (!p) { fprintf(stderr, "%06x wtf null pointer ?!?! Mai9eice\n", a->addr); break; } if (!cache->entries || !cache->json) { fprintf(stderr, "%06x wtf null pointer ?!?! ohj4Ohbi\n", a->addr); break; } char *stringStart = p; p = sprintTracePoint(p, end, state, state_all, cache->referenceTs, now, a); if (p + 1 >= end) { fprintf(stderr, "traceCache full, not an issue but fix it! p + 1 - end: %lld json_max: %lld p - cache-json: %lld\n", (long long) (p + 1 - end), (long long) cache->json_max, (long long) (p - cache->json)); // not enough space to safely write another cache break; } entry->ts = state->timestamp; entry->offset = stringStart - cache->json; entry->len = p - stringStart; entry->leg_marker = state->leg_marker; cache->entriesLen = k + 1; if (cache->entriesLen > Modes.traceCachePoints) { fprintf(stderr, "%06x wtf phooTie1\n", a->addr); cache->entriesLen = Modes.traceCachePoints; break; } *p = '\0'; if (0 && state->timestamp < lastTs) { fprintf(stderr, "%06x trace timestamps wrong order: %.3f %.3f i: %d k: %d %s\n", a->addr, lastTs / 1000.0, state->timestamp / 1000.0, i, k, stringStart); } lastTs = state->timestamp; } if (cache->entriesLen < tb.len - firstRecent) { fprintf(stderr, "%06x traceCache FAIL, entriesLen %d recent points %d\n", a->addr, cache->entriesLen, tb.len - firstRecent); } else { if (a->addr == TRACE_FOCUS) { fprintf(stderr, "%06x traceCache succeeded, entriesLen %d recent points %d\n", a->addr, cache->entriesLen, tb.len - firstRecent); } } } struct char_buffer generateTraceJson(struct aircraft *a, traceBuffer tb, int start, int last, threadpool_buffer_t *buffer, int64_t referenceTs, int64_t endStamp) { struct char_buffer cb = { 0 }; if (!Modes.writeTraces) { fprintf(stderr, "generateTraceJson called when Modes.writeTraces not set, this is a bug, please report with configuration!\n"); } int64_t now = mstime(); int recent = (last == -2) ? 1 : 0; if (last < 0) { last = tb.len - 1; } if (recent) { start = imax(0, tb.len - Modes.traceRecentPoints); } if (start < 0) { fprintf(stderr, "WTF chu0Uub8\n"); start = 0; } int traceCount = imax(last - start + 1, 0); ssize_t alloc = (traceCount + Modes.traceLastMax) * 300 + 1024; char *buf = check_grow_threadpool_buffer_t(buffer, alloc); char *p = buf; char *end = buf + alloc; p = safe_snprintf(p, end, "{\"icao\":\"%s%06x\"", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); if (Modes.db) { char *regInfo = p; if (a->registration[0]) { p = safe_snprintf(p, end, ",\n\"r\":\"%.*s\"", (int) sizeof(a->registration), a->registration); } if (a->typeCode[0]) { p = safe_snprintf(p, end, ",\n\"t\":\"%.*s\"", (int) sizeof(a->typeCode), a->typeCode); } if (a->typeCode[0] || a->registration[0] || a->dbFlags) { p = safe_snprintf(p, end, ",\n\"dbFlags\":%u", a->dbFlags); } if (a->typeLong[0]) p = safe_snprintf(p, end, ",\"desc\":\"%.*s\"", (int) sizeof(a->typeLong), a->typeLong); if (a->ownOp[0]) p = safe_snprintf(p, end, ",\n\"ownOp\":\"%.*s\"", (int) sizeof(a->ownOp), a->ownOp); if (a->year[0]) p = safe_snprintf(p, end, ",\n\"year\":\"%.*s\"", (int) sizeof(a->year), a->year); if (p == regInfo) p = safe_snprintf(p, end, ",\n\"noRegData\":true"); } int64_t firstStamp = 0; if (start >= 0 && start < tb.len) { firstStamp = getState(tb.trace, start)->timestamp; if (!referenceTs || firstStamp < referenceTs) { referenceTs = firstStamp; } } else { referenceTs = now; } struct traceCache *tCache = NULL; struct traceCacheEntry *entries = NULL; // due to timestamping, only use trace cache for recent trace jsons if (recent && firstStamp != 0) { checkTraceCache(a, tb, now); tCache = &a->traceCache; if (tCache->entries && tCache->entriesLen > 0) { entries = tCache->entries; referenceTs = tCache->referenceTs; } else { tCache = NULL; } } p = safe_snprintf(p, end, ",\n\"version\": \"readsb "READSB_SHORT_VERSION" "READSB_SHORT_COMMIT"\""); p = safe_snprintf(p, end, ",\n\"timestamp\": %.3f", referenceTs / 1000.0); p = safe_snprintf(p, end, ",\n\"trace\":[ "); if (start >= 0) { if (tCache) { if (0 && a->addr == TRACE_FOCUS) { fprintf(stderr, "%06x using tCache starting with tCache->firstRecentCache %d stateIndex %d\n", a->addr, tCache->firstRecentCache, start); } struct traceCacheEntry *first = &entries[tCache->firstRecentCache]; struct traceCacheEntry *last = &entries[tCache->entriesLen - 1]; int bytes = last->offset - first->offset + last->len; memcpy(p, tCache->json + first->offset, bytes); p += bytes; } else { int useLast = 0; int64_t traceLastStart = 0; // only use traceLast if aircraft isn't active any more (so it's not as in many full // jsons in memory, make it easier on the browsers to show live traces unless it's // needed) if (!recent && a->traceLast && now > a->seenPosReliable + 60 * SECONDS) { struct state *state = getState(a->traceLast, a->traceLastNext); if (state->timestamp != 0) { // for simplictity only use if traceLast is fully populated (should usually happen quickly) useLast = 1; traceLastStart = state->timestamp; } } for (int i = start; i <= last && i < tb.len; i++) { struct state *state = getState(tb.trace, i); struct state_all *state_all = getStateAll(tb.trace, i); if (useLast && state->timestamp >= traceLastStart) { break; } p = sprintTracePoint(p, end, state, state_all, referenceTs, now, a); } if (useLast) { int i = a->traceLastNext; for (int k = 0; k < Modes.traceLastMax; k++) { struct state *state = getState(a->traceLast, i); // don't go past end of timeframe if specified if (endStamp > 0 && state->timestamp > endStamp) { //fprintf(stderr, "%06x traceLast limiting to %d points due to last / timeframe\n", a->addr, k); break; } struct state_all *state_all = getStateAll(a->traceLast, i); p = sprintTracePoint(p, end, state, state_all, referenceTs, now, a); i = (i + 1) % Modes.traceLastMax; } } } if (*(p-1) == ',') { p--; // remove last comma } } p = safe_snprintf(p, end, " ]\n"); p = safe_snprintf(p, end, " }\n"); cb.len = p - buf; cb.buffer = buf; if (p >= end) { fprintf(stderr, "buffer overrun trace json %zu %zu\n", cb.len, alloc); } return cb; } // // Return a description of the receiver in json. // struct char_buffer generateReceiverJson() { struct char_buffer cb; size_t buflen = 8192; char *buf = (char *) cmalloc(buflen), *p = buf, *end = buf + buflen; p = safe_snprintf(p, end, "{ " "\"refresh\": %.0f, " "\"history\": %d", 1.0 * Modes.json_interval, Modes.json_aircraft_history_next + 1); if (Modes.json_location_accuracy != 0 && Modes.userLocationValid) { p = safe_snprintf(p, end, ", " "\"lat\": %.6f, " "\"lon\": %.6f", Modes.fUserLat, Modes.fUserLon); if (Modes.fUserAlt > -1e6) { p = safe_snprintf(p, end, ", \"alt_m_amsl\": %.0f", Modes.fUserAlt); } } p = safe_snprintf(p, end, ", \"jaeroTimeout\": %.1f", ((double) Modes.trackExpireJaero) / (60 * SECONDS)); p = safe_snprintf(p, end, ", \"readsb\": true"); // for tar1090 so it can tell it's not dump1090-fa if (Modes.db || Modes.db2) { p = safe_snprintf(p, end, ", \"dbServer\": true"); } if (Modes.writeTraces) { p = safe_snprintf(p, end, ", \"haveTraces\": true"); p = safe_snprintf(p, end, ", \"json_trace_interval\": %.1f", ((double) Modes.json_trace_interval) / (1 * SECONDS)); } if (Modes.json_globe_index && !Modes.tar1090_no_globe) { p = safe_snprintf(p, end, ", \"globeIndexGrid\": %d", GLOBE_INDEX_GRID); p = safe_snprintf(p, end, ", \"globeIndexSpecialTiles\": [ "); struct tile *tiles = Modes.json_globe_special_tiles; for (int i = 0; tiles[i].south != 0 || tiles[i].north != 0; i++) { struct tile tile = tiles[i]; p = safe_snprintf(p, end, "{\"south\":%d,", tile.south); p = safe_snprintf(p, end, "\"east\":%d,", tile.east); p = safe_snprintf(p, end, "\"north\":%d,", tile.north); p = safe_snprintf(p, end, "\"west\":%d},", tile.west); } p -= 1; // get rid of comma at the end p = safe_snprintf(p, end, " ]"); } if (Modes.tar1090_use_api) { p = safe_snprintf(p, end, ", \"reapi\": true"); } p = safe_snprintf(p, end, ", \"binCraft\": true"); if (Modes.enable_zstd) { p = safe_snprintf(p, end, ", \"zstd\": true"); } if (Modes.outline_json) { p = safe_snprintf(p, end, ", \"outlineJson\": true"); } if (Modes.trace_hist_only) { p = safe_snprintf(p, end, ", \"trace_hist_only\": true"); } p = safe_snprintf(p, end, ", \"version\": \"%s\" }\n", MODES_READSB_VERSION); if (p >= end) fprintf(stderr, "buffer overrun receiver json\n"); cb.len = p - buf; cb.buffer = buf; return cb; } struct char_buffer generateOutlineJson() { struct char_buffer cb; size_t buflen = 1024 + RANGEDIRS_BUCKETS * 64; char *buf = (char *) cmalloc(buflen), *p = buf, *end = buf + buflen; // check for maximum over last 24 ivals and current ival struct distCoords record[RANGEDIRS_BUCKETS]; memset(record, 0, sizeof(record)); for (int hour = 0; hour < RANGEDIRS_IVALS; hour++) { for (int i = 0; i < RANGEDIRS_BUCKETS; i++) { struct distCoords curr = Modes.rangeDirs[hour][i]; if (curr.distance > record[i].distance) { record[i] = curr; } } } // print the records in each direction p = safe_snprintf(p, end, "{ \"actualRange\": { \"last24h\": { \"points\": ["); for (int i = 0; i < RANGEDIRS_BUCKETS; i++) { if (record[i].lat || record[i].lon) { p = safe_snprintf(p, end, "\n[%.4f,%.4f,%d],", record[i].lat, record[i].lon, record[i].alt); } } if (*(p-1) == ',') p--; // remove last comma if it exists p = safe_snprintf(p, end, "\n]}}}\n"); if (p >= end) fprintf(stderr, "buffer overrun outline json\n"); cb.len = p - buf; cb.buffer = buf; return cb; } // Write JSON to file static inline __attribute__((always_inline)) struct char_buffer writeJsonTo (const char* dir, const char *file, struct char_buffer cb, int gzip, int gzip_level) { char pathbuf[PATH_MAX]; char tmppath[PATH_MAX]; int fd; int len = cb.len; char *content = cb.buffer; if (dir) { snprintf(pathbuf, PATH_MAX, "%s/%s", dir, file); } else { snprintf(pathbuf, PATH_MAX, "%s", file); } snprintf(tmppath, PATH_MAX, "%s.readsb_tmp", pathbuf); int firstOpenFail = 1; open: fd = open(tmppath, O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd < 0) { if (firstOpenFail) { unlink(tmppath); firstOpenFail = 0; goto open; } fprintf(stderr, "writeJsonTo open(): "); perror(tmppath); goto error_2; } if (gzip) { gzFile gzfp = gzdopen(fd, "wb"); if (!gzfp) { goto error_1; } gzbuffer(gzfp, GZBUFFER_BIG); int name_len = strlen(file); if (name_len > 8 && strcmp("binCraft", file + (name_len - 8)) == 0) { gzsetparams(gzfp, gzip_level, Z_FILTERED); } else { gzsetparams(gzfp, gzip_level, Z_DEFAULT_STRATEGY); } int res = gzwrite(gzfp, content, len); if (res != len) { int error; fprintf(stderr, "%s: gzwrite of length %d failed: %s (res == %d)\n", pathbuf, len, gzerror(gzfp, &error), res); } if (gzclose(gzfp) < 0) goto error_2; } else { if (write(fd, content, len) != len) { fprintf(stderr, "writeJsonTo write(): "); perror(tmppath); goto error_1; } if (close(fd) < 0) goto error_2; } if (rename(tmppath, pathbuf) == -1) { fprintf(stderr, "writeJsonTo rename(): %s -> %s", tmppath, pathbuf); perror(""); goto error_2; } goto out; error_1: close(fd); error_2: unlink(tmppath); out: return cb; } struct char_buffer writeJsonToFile (const char* dir, const char *file, struct char_buffer cb) { return writeJsonTo(dir, file, cb, 0, 0); } struct char_buffer writeJsonToGzip (const char* dir, const char *file, struct char_buffer cb, int gzip) { return writeJsonTo(dir, file, cb, 1, gzip); } struct char_buffer generateVRS(int part, int n_parts, int reduced_data) { struct char_buffer cb; int64_t now = mstime(); struct aircraft *a; size_t buflen = 256*1024; // The initial buffer is resized as needed char *buf = (char *) cmalloc(buflen), *p = buf, *end = buf + buflen; int first = 1; int part_len = Modes.acBuckets / n_parts; int part_start = part * part_len; //fprintf(stderr, "%02d/%02d reduced_data: %d\n", part, n_parts, reduced_data); p = safe_snprintf(p, end, "{\"acList\":["); for (int j = part_start; j < part_start + part_len; j++) { for (a = Modes.aircraft[j]; a; a = a->next) { if (now > a->seen + 10 * SECONDS) // don't include stale aircraft in the JSON continue; // For now, suppress non-ICAO addresses if (a->addr & MODES_NON_ICAO_ADDRESS) continue; // also enforce same criteria as for aircraft.json if (!includeAircraftJson(now, a)) { continue; } if ((p + 2048) >= end) { int used = p - buf; buflen *= 2; buf = (char *) realloc(buf, buflen); p = buf + used; end = buf + buflen; //fprintf(stderr, "realloc at %s, line %d.\n", __FILE__, __LINE__); } if (first) first = 0; else *p++ = ','; p = safe_snprintf(p, end, "{\"Icao\":\"%s%06X\"", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); if (trackDataValid(&a->pos_reliable_valid)) { p = safe_snprintf(p, end, ",\"Lat\":%f,\"Long\":%f", a->latReliable, a->lonReliable); //p = safe_snprintf(p, end, ",\"PosTime\":%"PRIu64, a->pos_reliable_valid.updated); } if (altBaroReliable(a)) p = safe_snprintf(p, end, ",\"Alt\":%d", a->baro_alt); if (trackDataValid(&a->geom_rate_valid)) { p = safe_snprintf(p, end, ",\"Vsi\":%d", a->geom_rate); } else if (trackDataValid(&a->baro_rate_valid)) { p = safe_snprintf(p, end, ",\"Vsi\":%d", a->baro_rate); } if (trackDataValid(&a->track_valid)) { p = safe_snprintf(p, end, ",\"Trak\":%.1f", a->track); } else if (trackDataValid(&a->mag_heading_valid)) { p = safe_snprintf(p, end, ",\"Trak\":%.1f", a->mag_heading); } else if (trackDataValid(&a->true_heading_valid)) { p = safe_snprintf(p, end, ",\"Trak\":%.1f", a->true_heading); } if (trackDataValid(&a->gs_valid)) { p = safe_snprintf(p, end, ",\"Spd\":%.1f", a->gs); } else if (trackDataValid(&a->ias_valid)) { p = safe_snprintf(p, end, ",\"Spd\":%u", a->ias); } else if (trackDataValid(&a->tas_valid)) { p = safe_snprintf(p, end, ",\"Spd\":%u", a->tas); } if (trackDataValid(&a->geom_alt_valid)) p = safe_snprintf(p, end, ",\"GAlt\":%d", a->geom_alt); if (trackDataValid(&a->airground_valid) && a->airground == AG_GROUND) p = safe_snprintf(p, end, ",\"Gnd\":true"); else p = safe_snprintf(p, end, ",\"Gnd\":false"); if (trackDataValid(&a->squawk_valid)) p = safe_snprintf(p, end, ",\"Sqk\":\"%04x\"", a->squawk); if (trackDataValid(&a->nav_altitude_mcp_valid)) { p = safe_snprintf(p, end, ",\"TAlt\":%d", a->nav_altitude_mcp); } else if (trackDataValid(&a->nav_altitude_fms_valid)) { p = safe_snprintf(p, end, ",\"TAlt\":%d", a->nav_altitude_fms); } if (a->pos_reliable_valid.source != SOURCE_INVALID) { if (a->pos_reliable_valid.source == SOURCE_MLAT) p = safe_snprintf(p, end, ",\"Mlat\":true"); else if (a->pos_reliable_valid.source == SOURCE_TISB) p = safe_snprintf(p, end, ",\"Tisb\":true"); else if (a->pos_reliable_valid.source == SOURCE_JAERO) p = safe_snprintf(p, end, ",\"Sat\":true"); } if (reduced_data && a->addrtype != ADDR_JAERO && a->pos_reliable_valid.source != SOURCE_JAERO) goto skip_fields; if (trackDataAge(now, &a->callsign_valid) < 5 * MINUTES || (a->pos_reliable_valid.source == SOURCE_JAERO && trackDataAge(now, &a->callsign_valid) < 8 * HOURS) ) { char buf[128]; char buf2[16]; const char *trimmed = trimSpace(a->callsign, buf2, 8); if (trimmed[0] != 0) { p = safe_snprintf(p, end, ",\"Call\":\"%s\"", jsonEscapeString(trimmed, buf, sizeof(buf))); p = safe_snprintf(p, end, ",\"CallSus\":false"); } } if (trackDataValid(&a->nav_heading_valid)) p = safe_snprintf(p, end, ",\"TTrk\":%.1f", a->nav_heading); if (trackDataValid(&a->geom_rate_valid)) { p = safe_snprintf(p, end, ",\"VsiT\":1"); } else if (trackDataValid(&a->baro_rate_valid)) { p = safe_snprintf(p, end, ",\"VsiT\":0"); } if (trackDataValid(&a->track_valid)) { p = safe_snprintf(p, end, ",\"TrkH\":false"); } else if (trackDataValid(&a->mag_heading_valid)) { p = safe_snprintf(p, end, ",\"TrkH\":true"); } else if (trackDataValid(&a->true_heading_valid)) { p = safe_snprintf(p, end, ",\"TrkH\":true"); } p = safe_snprintf(p, end, ",\"Sig\":%d", get8bitSignal(a)); if (trackDataValid(&a->nav_qnh_valid)) p = safe_snprintf(p, end, ",\"InHg\":%.2f", a->nav_qnh * 0.02952998307); p = safe_snprintf(p, end, ",\"AltT\":%d", 0); if (a->pos_reliable_valid.source != SOURCE_INVALID) { if (a->pos_reliable_valid.source != SOURCE_MLAT) p = safe_snprintf(p, end, ",\"Mlat\":false"); if (a->pos_reliable_valid.source != SOURCE_TISB) p = safe_snprintf(p, end, ",\"Tisb\":false"); if (a->pos_reliable_valid.source != SOURCE_JAERO) p = safe_snprintf(p, end, ",\"Sat\":false"); } if (trackDataValid(&a->gs_valid)) { p = safe_snprintf(p, end, ",\"SpdTyp\":0"); } else if (trackDataValid(&a->ias_valid)) { p = safe_snprintf(p, end, ",\"SpdTyp\":2"); } else if (trackDataValid(&a->tas_valid)) { p = safe_snprintf(p, end, ",\"SpdTyp\":3"); } if (a->adsb_version >= 0) p = safe_snprintf(p, end, ",\"Trt\":%d", a->adsb_version + 3); else p = safe_snprintf(p, end, ",\"Trt\":%d", 1); //p = safe_snprintf(p, end, ",\"Cmsgs\":%ld", a->messages); skip_fields: p = safe_snprintf(p, end, "}"); } } p = safe_snprintf(p, end, "]}\n"); if (p >= end) fprintf(stderr, "buffer overrun vrs json\n"); cb.len = p - buf; cb.buffer = buf; return cb; } struct char_buffer generateClientsJson() { struct char_buffer cb; int64_t now = mstime(); size_t buflen = 1*1024*1024; // The initial buffer is resized as needed char *buf = (char *) cmalloc(buflen), *p = buf, *end = buf + buflen; p = safe_snprintf(p, end, "{ \"now\" : %.3f,\n", now / 1000.0); p = safe_snprintf(p, end, " \"format\" : " "[ \"receiverId\", \"host:port\", \"avg. kbit/s\", \"conn time(s)\"," " \"messages/s\", \"positions/s\", \"reduce_signal\", \"recent_rtt(ms)\", \"positions\" ],\n"); p = safe_snprintf(p, end, " \"clients\" : [\n"); for (struct net_service *service = Modes.services_in.services; service->descr; service++) { if (!service->read_handler) { continue; } for (struct client *c = service->clients; c; c = c->next) { if (!c->service) continue; // check if we have enough space if ((p + 1000) >= end) { int used = p - buf; buflen *= 2; buf = (char *) realloc(buf, buflen); p = buf + used; end = buf + buflen; } char uuid[64]; // needs 36 chars and null byte sprint_uuid(c->receiverId, c->receiverId2, uuid); //fprintf(stderr, "printing rId %016"PRIx64"%016"PRIx64" %s\n", c->receiverId, c->receiverId2, uuid); double elapsed = (now - c->connectedSince) / 1000.0; int reduceSignaled = c->service->writer == &Modes.beast_in && c->pingReceived + 120 * SECONDS > now; p = safe_snprintf(p, end, "[\"%s\",\"%49s\",%6.2f,%6.0f,%8.3f,%7.3f, %d,%5.0f, %10lld],\n", uuid, c->proxy_string, c->bytesReceived / 128.0 / elapsed, elapsed, (double) c->messageCounter / elapsed, (double) c->positionCounter / elapsed, reduceSignaled, c->recent_rtt, (long long) c->positionCounter); if (p >= end) fprintf(stderr, "buffer overrun client json\n"); } } if (*(p-2) == ',') *(p-2) = ' '; p = safe_snprintf(p, end, "\n ]\n}\n"); cb.len = p - buf; cb.buffer = buf; return cb; } readsb-3.16/json_out.h000066400000000000000000000056331505057307600147330ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // net_io.h: network handling. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #ifndef JSON_OUT_H #define JSON_OUT_H typedef struct traceBuffer traceBuffer; int includeAircraftJson(int64_t now, struct aircraft *a); void printACASInfoShort(uint32_t addr, unsigned char *MV, struct aircraft *a, struct modesMessage *mm, int64_t now); void logACASInfoShort(uint32_t addr, unsigned char *MV, struct aircraft *a, struct modesMessage *mm, int64_t now); char *sprintACASInfoShort(char *p, char *end, uint32_t addr, unsigned char *MV, struct aircraft *a, struct modesMessage *mm, int64_t now); char *sprintAircraftObject(char *p, char *end, struct aircraft *a, int64_t now, int printMode, struct modesMessage *mm); char *sprintAircraftRecent(char *p, char *end, struct aircraft *a, int64_t now, int printMode, struct modesMessage *mm, int64_t recent); struct char_buffer generateAircraftJson(int64_t onlyRecent); struct char_buffer generateAircraftBin(threadpool_buffer_t *pbuffer); struct char_buffer generateTraceJson(struct aircraft *a, traceBuffer tb, int start, int last, threadpool_buffer_t *buffer, int64_t referenceTs, int64_t endStamp); struct char_buffer generateGlobeBin(int globe_index, int mil, threadpool_buffer_t *buffer); struct char_buffer generateGlobeJson(int globe_index, threadpool_buffer_t *buffer); struct char_buffer generateReceiverJson (); struct char_buffer generateHistoryJson (); struct char_buffer generateClientsJson(); struct char_buffer generateOutlineJson(); struct char_buffer generateVRS(int part, int n_parts, int reduced_data); struct char_buffer writeJsonToFile (const char* dir, const char *file, struct char_buffer cb); struct char_buffer writeJsonToGzip (const char* dir, const char *file, struct char_buffer cb, int gzip); __attribute__ ((format(printf, 3, 4))) static inline char *safe_snprintf(char *p, char *end, const char *format, ...) { va_list ap; va_start(ap, format); p += vsnprintf(p < end ? p : NULL, p < end ? (size_t) (end - p) : 0, format, ap); if (p > end) p = end; va_end(ap); return p; } const char *nav_modes_flags_string(nav_modes_t flags); #endif readsb-3.16/massif.sh000077500000000000000000000002211505057307600145270ustar00rootroot00000000000000#!/bin/bash trap "ms_print --x=160 --y=40 .massif.out" EXIT export MASSIF="--vgdb=yes --tool=massif --massif-out-file=.massif.out" ./valgrind.sh readsb-3.16/mode_ac.c000066400000000000000000000146311505057307600144530ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // mode_ac.c: Mode A/C decoder. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "readsb.h" #include // //========================================================================= // // Input format is : 00:A4:A2:A1:00:B4:B2:B1:00:C4:C2:C1:00:D4:D2:D1 // static int modeAToCTable[4096]; static unsigned modeCToATable[4096]; static int internalModeAToModeC(unsigned int ModeA); void modeACInit() { for (unsigned i = 0; i < 4096; ++i) { unsigned modeA = indexToModeA(i); int modeC = internalModeAToModeC(modeA); modeAToCTable[i] = modeC; modeC += 13; if (modeC >= 0 && modeC < 4096) { assert(modeCToATable[modeC] == 0); modeCToATable[modeC] = modeA; } } } // Given a mode A value (hex-encoded, see above) // return the mode C value (signed multiple of 100s of feet) // or INVALID_ALITITUDE if not a valid mode C value int modeAToModeC(unsigned modeA) { unsigned i = modeAToIndex(modeA); if (i >= 4096) return INVALID_ALTITUDE; return modeAToCTable[i]; } // Given a mode C value (signed multiple of 100s of feet) // return the mode A value, or 0 if not a valid mode C value unsigned modeCToModeA(int modeC) { modeC += 13; if (modeC < 0 || modeC >= 4096) return 0; return modeCToATable[modeC]; } static int internalModeAToModeC(unsigned int ModeA) { unsigned int FiveHundreds = 0; unsigned int OneHundreds = 0; if ((ModeA & 0xFFFF8889) != 0 || // check zero bits are zero, D1 set is illegal (ModeA & 0x000000F0) == 0) { // C1,,C4 cannot be Zero return INVALID_ALTITUDE; } if (ModeA & 0x0010) { OneHundreds ^= 0x007; } // C1 if (ModeA & 0x0020) { OneHundreds ^= 0x003; } // C2 if (ModeA & 0x0040) { OneHundreds ^= 0x001; } // C4 // Remove 7s from OneHundreds (Make 7->5, snd 5->7). if ((OneHundreds & 5) == 5) { OneHundreds ^= 2; } // Check for invalid codes, only 1 to 5 are valid if (OneHundreds > 5) { return INVALID_ALTITUDE; } //if (ModeA & 0x0001) {FiveHundreds ^= 0x1FF;} // D1 never used for altitude if (ModeA & 0x0002) { FiveHundreds ^= 0x0FF; } // D2 if (ModeA & 0x0004) { FiveHundreds ^= 0x07F; } // D4 if (ModeA & 0x1000) { FiveHundreds ^= 0x03F; } // A1 if (ModeA & 0x2000) { FiveHundreds ^= 0x01F; } // A2 if (ModeA & 0x4000) { FiveHundreds ^= 0x00F; } // A4 if (ModeA & 0x0100) { FiveHundreds ^= 0x007; } // B1 if (ModeA & 0x0200) { FiveHundreds ^= 0x003; } // B2 if (ModeA & 0x0400) { FiveHundreds ^= 0x001; } // B4 // Correct order of OneHundreds. if (FiveHundreds & 1) { OneHundreds = 6 - OneHundreds; } return ((FiveHundreds * 5) + OneHundreds - 13); } // //========================================================================= // void decodeModeAMessage(struct modesMessage *mm, int ModeA) { mm->source = SOURCE_MODE_AC; mm->addrtype = ADDR_MODE_A; mm->msgtype = DFTYPE_MODEAC; // Valid Mode S DF's are DF-00 to DF-31. // so use DFTYPE_MODEAC (77) to indicate Mode A/C mm->msgbits = 16; // Fudge up a Mode S style data stream mm->msg[0] = mm->verbatim[0] = (ModeA >> 8); mm->msg[1] = mm->verbatim[1] = (ModeA); // Fudge an address based on Mode A (remove the Ident bit) mm->addr = (ModeA & 0x0000FF7F) | MODES_NON_ICAO_ADDRESS; // Set the Identity field to ModeA mm->squawkHex = ModeA & 0x7777; mm->squawkDec = squawkHex2Dec(mm->squawkHex); mm->squawk_valid = 1; // Flag ident in flight status mm->spi = (ModeA & 0x0080) ? 1 : 0; mm->spi_valid = 1; // Decode an altitude if this looks like a possible mode C if (!mm->spi) { int modeC = modeAToModeC(ModeA); if (modeC != INVALID_ALTITUDE) { mm->baro_alt = modeC * 100; mm->baro_alt_unit = UNIT_FEET; mm->baro_alt_valid = 1; } } // Not much else we can tell from a Mode A/C reply. // Just fudge up a few bits to keep other code happy mm->correctedbits = 0; } // // ===================== Mode A/C detection and decoding =================== // readsb-3.16/mode_s.c000066400000000000000000002246541505057307600143420ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // mode_s.c: Mode S message decoding. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014-2016 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "readsb.h" #include "ais_charset.h" /* for PRIX64 */ #include // // ===================== Mode S detection and decoding =================== // // // // //========================================================================= // // In the squawk (identity) field bits are interleaved as follows in // (message bit 20 to bit 32): // // C1-A1-C2-A2-C4-A4-ZERO-B1-D1-B2-D2-B4-D4 // // So every group of three bits A, B, C, D represent an integer from 0 to 7. // // The actual meaning is just 4 octal numbers, but we convert it into a hex // number tha happens to represent the four octal numbers. // // For more info: http://en.wikipedia.org/wiki/Gillham_code // static int decodeID13Field(int ID13Field) { int hexGillham = 0; if (ID13Field & 0x1000) {hexGillham |= 0x0010;} // Bit 12 = C1 if (ID13Field & 0x0800) {hexGillham |= 0x1000;} // Bit 11 = A1 if (ID13Field & 0x0400) {hexGillham |= 0x0020;} // Bit 10 = C2 if (ID13Field & 0x0200) {hexGillham |= 0x2000;} // Bit 9 = A2 if (ID13Field & 0x0100) {hexGillham |= 0x0040;} // Bit 8 = C4 if (ID13Field & 0x0080) {hexGillham |= 0x4000;} // Bit 7 = A4 //if (ID13Field & 0x0040) {hexGillham |= 0x0800;} // Bit 6 = X or M if (ID13Field & 0x0020) {hexGillham |= 0x0100;} // Bit 5 = B1 if (ID13Field & 0x0010) {hexGillham |= 0x0001;} // Bit 4 = D1 or Q if (ID13Field & 0x0008) {hexGillham |= 0x0200;} // Bit 3 = B2 if (ID13Field & 0x0004) {hexGillham |= 0x0002;} // Bit 2 = D2 if (ID13Field & 0x0002) {hexGillham |= 0x0400;} // Bit 1 = B4 if (ID13Field & 0x0001) {hexGillham |= 0x0004;} // Bit 0 = D4 return (hexGillham); } // //========================================================================= // // Decode the 13 bit AC altitude field (in DF 20 and others). // Returns the altitude, and set 'unit' to either UNIT_METERS or UNIT_FEET. // static int decodeAC13Field(int AC13Field, altitude_unit_t *unit, unsigned *mm_q_bit) { int m_bit = AC13Field & 0x0040; // set = meters, clear = feet int q_bit = AC13Field & 0x0010; // set = 25 ft encoding, clear = Gillham Mode C encoding if (q_bit) *mm_q_bit = 1; if (!m_bit) { *unit = UNIT_FEET; if (q_bit) { // N is the 11 bit integer resulting from the removal of bit Q and M int n = ((AC13Field & 0x1F80) >> 2) | ((AC13Field & 0x0020) >> 1) | (AC13Field & 0x000F); // The final altitude is resulting number multiplied by 25, minus 1000. return ((n * 25) - 1000); } else { // N is an 11 bit Gillham coded altitude int n = modeAToModeC(decodeID13Field(AC13Field)); if (n < -12) { return INVALID_ALTITUDE; } return (100 * n); } } else { *unit = UNIT_METERS; // TODO: Implement altitude when meter unit is selected return INVALID_ALTITUDE; } } // //========================================================================= // // Decode the 12 bit AC altitude field (in DF 17 and others). // static int decodeAC12Field(int AC12Field, altitude_unit_t *unit, unsigned *mm_q_bit) { int q_bit = AC12Field & 0x10; // Bit 48 = Q if (q_bit) *mm_q_bit = 1; *unit = UNIT_FEET; if (q_bit) { /// N is the 11 bit integer resulting from the removal of bit Q at bit 4 int n = ((AC12Field & 0x0FE0) >> 1) | (AC12Field & 0x000F); // The final altitude is the resulting number multiplied by 25, minus 1000. return ((n * 25) - 1000); } else { // Make N a 13 bit Gillham coded altitude by inserting M=0 at bit 6 int n = ((AC12Field & 0x0FC0) << 1) | (AC12Field & 0x003F); n = modeAToModeC(decodeID13Field(n)); if (n < -12) { return INVALID_ALTITUDE; } return (100 * n); } } // //========================================================================= // // Decode the 7 bit ground movement field PWL exponential style scale (ADS-B v2) // static float decodeMovementFieldV2(unsigned movement) { // Note : movement codes 0,125,126,127 are all invalid, but they are // trapped for before this function is called. // Each movement value is a range of speeds; // we return the midpoint of the range (rounded to the nearest integer) if (movement >= 125) return 0; // invalid else if (movement == 124) return 180; // gs > 175kt, pick a value.. else if (movement >= 109) return 100 + (movement - 109 + 0.5) * 5; // 100 < gs <= 175 in 5kt steps else if (movement >= 94) return 70 + (movement - 94 + 0.5) * 2; // 70 < gs <= 100 in 2kt steps else if (movement >= 39) return 15 + (movement - 39 + 0.5) * 1; // 15 < gs <= 70 in 1kt steps else if (movement >= 13) return 2 + (movement - 13 + 0.5) * 0.50; // 2 < gs <= 15 in 0.5kt steps else if (movement >= 9) return 1 + (movement - 9 + 0.5) * 0.25; // 1 < gs <= 2 in 0.25kt steps else if (movement >= 3) return 0.125 + (movement - 3 + 0.5) * 0.875 / 6; // 0.125 < gs <= 1 in 0.875/6 kt step else if (movement >= 2) return 0.125 / 2; // 0 < gs <= 0.125 // 1: stopped, gs = 0 // 0: no data else return 0; } // //========================================================================= // // Decode the 7 bit ground movement field PWL exponential style scale (ADS-B v0) // static float decodeMovementFieldV0(unsigned movement) { // Note : movement codes 0,125,126,127 are all invalid, but they are // trapped for before this function is called. // Each movement value is a range of speeds; // we return the midpoint of the range if (movement >= 125) return 0; // invalid else if (movement == 124) return 180; // gs >= 175kt, pick a value.. else if (movement >= 109) return 100 + (movement - 109 + 0.5) * 5; // 100 < gs <= 175 in 5kt steps else if (movement >= 94) return 70 + (movement - 94 + 0.5) * 2; // 70 < gs <= 100 in 2kt steps else if (movement >= 39) return 15 + (movement - 39 + 0.5) * 1; // 15 < gs <= 70 in 1kt steps else if (movement >= 13) return 2 + (movement - 13 + 0.5) * 0.50; // 2 < gs <= 15 in 0.5kt steps else if (movement >= 9) return 1 + (movement - 9 + 0.5) * 0.25; // 1 < gs <= 2 in 0.25kt steps else if (movement >= 2) return 0.125 + (movement - 2 + 0.5) * 0.125; // 0.125 < gs <= 1 in 0.125kt step // 1: stopped, gs < 0.125kt // 0: no data else return 0; } // Correct a decoded native-endian Address Announced field // (from bits 8-31) if it is affected by the given error // syndrome. Updates *addr and returns >0 if changed, 0 if // it was unaffected. static int correct_aa_field(uint32_t *addr, struct errorinfo *ei) { int i; int addr_errors = 0; if (!ei) return 0; for (i = 0; i < ei->errors; ++i) { if (ei->bit[i] >= 8 && ei->bit[i] <= 31) { *addr ^= 1 << (31 - ei->bit[i]); ++addr_errors; } } return addr_errors; } // Score how plausible this ModeS message looks. // The more positive, the more reliable the message is // 1000: DF 0/4/5/16/24 with a CRC-derived address matching a known aircraft // 1800: DF17/18 with good CRC and an address matching a known aircraft // 1400: DF17/18 with good CRC and an address not matching a known aircraft // 900: DF17/18 with 1-bit error and an address matching a known aircraft // 700: DF17/18 with 1-bit error and an address not matching a known aircraft // 450: DF17/18 with 2-bit error and an address matching a known aircraft // 350: DF17/18 with 2-bit error and an address not matching a known aircraft // 1600: DF11 with IID==0, good CRC and an address matching a known aircraft // 1000: DF11 with IID!=0, good CRC and an address matching a known aircraft // 800: DF11 with 1-bit error and an address matching a known aircraft // 750: DF11 with IID==0, good CRC and an address not matching a known aircraft // 1000: DF20/21 with a CRC-derived address matching a known aircraft // 500: DF20/21 with a CRC-derived address matching a known aircraft (bottom 16 bits only - overlay control in use) // -1: message might be valid, but we couldn't validate the CRC against a known ICAO // -2: bad message or unrepairable CRC error static unsigned char all_zeros[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // fix possible 1 bit errors in the DF type for DF17 only // return orig first msg byte if we changed the msgtype / first byte static inline __attribute__((always_inline)) unsigned char fixDF17msgtype(unsigned char *msg, int *msgtype) { if (!Modes.fixDF || !Modes.nfix_crc) { return 0; } unsigned char origByte; switch (*msgtype) { // possible values for msgtype with a single bit error of DF17 case 1: case 25: case 21: case 19: case 16: origByte = msg[0]; msg[0] &= 7; // keep last 3 bits, set first 5 bits to 0 msg[0] |= (17 << 3); // set first 5 bits to a value of 17 int res = modesChecksum(msg, MODES_LONG_MSG_BITS); if (res == 0) { *msgtype = 17; return origByte; } msg[0] = origByte; return 0; default: return 0; } } static void setSquawkFromID13(struct modesMessage *mm, int ID13Field) { mm->squawkHex = decodeID13Field(ID13Field); mm->squawkDec = squawkHex2Dec(mm->squawkHex); mm->squawk_valid = 1; } int scoreModesMessage(unsigned char *msg, int validbits) { int msgtype, msgbits, crc, iid; uint32_t addr; struct errorinfo *ei; if (validbits < 56) return -2; msgtype = getbits(msg, 1, 5); // Downlink Format if (validbits >= MODES_LONG_MSG_BITS) { unsigned char origByte = fixDF17msgtype(msg, &msgtype); if (origByte) { msg[0] = origByte; // restore byte // DF17 message with 1 bit error, return the correct score if (icaoFilterTest(getbits(msg, 9, 32))) // check addr return 1800 / 2; else return 1400 / 2; } } msgbits = modesMessageLenByType(msgtype); if (validbits < msgbits) return -2; // discard messages which have only zeros in the first short msg bytes if (!memcmp(all_zeros, msg, MODES_SHORT_MSG_BYTES)) return -2; crc = modesChecksum(msg, msgbits); switch (msgtype) { case 0: // short air-air surveillance case 4: // surveillance, altitude reply case 5: // surveillance, altitude reply case 16: // long air-air surveillance case 20: // Comm-B, altitude reply case 21: // Comm-B, identity reply #ifdef ENABLE_DF24 case 24: // Comm-D (ELM) case 25: // Comm-D (ELM) case 26: // Comm-D (ELM) case 27: // Comm-D (ELM) case 28: // Comm-D (ELM) case 29: // Comm-D (ELM) case 30: // Comm-D (ELM) case 31: // Comm-D (ELM) #endif return icaoFilterTest(crc) ? 1000 : -1; case 11: // All-call reply iid = crc & 0x7f; addr = getbits(msg, 9, 32); if (crc & 0xffff80) { // Try to diagnose based on the _full_ CRC // i.e. under the assumption that IID = 0 ei = modesChecksumDiagnose(crc, msgbits); if (!ei) return -2; // can't correct errors // see crc.c comments: we do not attempt to fix // more than single-bit errors, as two-bit // errors are ambiguous in DF11. if (ei->errors > 1) return -2; // can't correct errors // fix any errors in the address field correct_aa_field(&addr, ei); // here, IID = 0 implicitly if (icaoFilterTest(addr)) return 800; else return -1; } // CRC was correct (ish) if (iid == 0) { if (icaoFilterTest(addr)) return 1600; else return 750; } else { // iid != 0 if (icaoFilterTest(addr)) return 1000; else return -1; } case 17: // Extended squitter case 18: // Extended squitter/non-transponder ei = modesChecksumDiagnose(crc, msgbits); if (!ei) return -2; // can't correct errors // fix any errors in the address field addr = getbits(msg, 9, 32); correct_aa_field(&addr, ei); if (icaoFilterTest(addr)) return 1800 / (ei->errors + 1); else return 1400 / (ei->errors + 1); default: // unknown message type return -2; } } // //========================================================================= // // Decode a raw Mode S message demodulated as a stream of bytes by detectModeS(), // and split it into fields populating a modesMessage structure. // static void decodeExtendedSquitter(struct modesMessage *mm); // return 0 if all OK // -1: message might be valid, but we couldn't validate the CRC against a known ICAO // -2: bad message or unrepairable CRC error #define decode_return(X) \ do { \ mm->decodeResult = X; \ if (!Modes.decode_all) { \ return mm->decodeResult; \ } \ } while(0) int decodeModesMessage(struct modesMessage *mm) { if (Modes.net_verbatim) { // Preserve the original uncorrected copy for later forwarding memcpy(mm->verbatim, mm->msg, MODES_LONG_MSG_BYTES); } unsigned char* msg = mm->msg; mm->decodeResult = 0; // discard messages which have only zeros in the first short msg bytes if (!memcmp(all_zeros, msg, MODES_SHORT_MSG_BYTES)) { decode_return(-2); } // Get the message type ASAP as other operations depend on this mm->msgtype = getbits(msg, 1, 5); // Downlink Format mm->correctedbits = 0; // try and fix possible bit errors in DF type that could be DF17 if (fixDF17msgtype(msg, &mm->msgtype)) { mm->correctedbits = 1; } mm->msgbits = modesMessageLenByType(mm->msgtype); mm->crc = modesChecksum(msg, mm->msgbits); mm->addr = HEX_UNKNOWN; // set non zero default address mm->maybe_addr = HEX_UNKNOWN; // set non zero default address mm->addrtype = ADDR_UNKNOWN; // Do checksum work and set fields that depend on the CRC switch (mm->msgtype) { case 0: // short air-air surveillance case 4: // surveillance, altitude reply case 5: // surveillance, altitude reply case 16: // long air-air surveillance case 24: // Comm-D (ELM) case 25: // Comm-D (ELM) case 26: // Comm-D (ELM) case 27: // Comm-D (ELM) case 28: // Comm-D (ELM) case 29: // Comm-D (ELM) case 30: // Comm-D (ELM) case 31: // Comm-D (ELM) // These message types use Address/Parity, i.e. our CRC syndrome is the sender's ICAO address. // We can't tell if the CRC is correct or not as we don't know the correct address. // Accept the message if it appears to be from a previously-seen aircraft mm->maybe_addr = mm->crc; if (!icaoFilterTest(mm->crc)) { decode_return(-1); } mm->source = SOURCE_MODE_S; mm->addr = mm->crc; mm->addrtype = ADDR_MODE_S; break; case 11: // All-call reply // This message type uses Parity/Interrogator, i.e. our CRC syndrome is CL + IC from the uplink message // which we can't see. So we don't know if the CRC is correct or not. // // however! CL + IC only occupy the lower 7 bits of the CRC. So if we ignore those bits when testing // the CRC we can still try to detect/correct errors. mm->IID = mm->crc & 0x7f; mm->maybe_addr = getbits(msg, 9, 32); if (mm->crc & 0xffff80) { // Try to diagnose based on the _full_ CRC // i.e. under the assumption that IID = 0 struct errorinfo *ei = modesChecksumDiagnose(mm->crc, mm->msgbits); if (!ei) { decode_return(-2); // couldn't fix it } else { // see crc.c comments: we do not attempt to fix // more than single-bit errors, as two-bit // errors are ambiguous in DF11. if (ei->errors > 1) { decode_return(-2); // can't correct errors } else { mm->correctedbits = ei->errors; mm->IID = 0; modesChecksumFix(msg, ei); // check whether the corrected message looks sensible // we are conservative here: only accept corrected messages that // match an existing aircraft. int addr = getbits(msg, 9, 32); mm->maybe_addr = addr; if (!icaoFilterTest(addr)) { decode_return(-1); } } } } mm->source = SOURCE_MODE_S_CHECKED; mm->addrtype = ADDR_MODE_S; break; case 17: // Extended squitter case 18: { // Extended squitter/non-transponder struct errorinfo *ei; int addr1, addr2; // These message types use Parity/Interrogator, but are specified to set II=0 if (mm->crc != 0) { ei = modesChecksumDiagnose(mm->crc, mm->msgbits); if (!ei) { decode_return(-2); // couldn't fix it } else { addr1 = getbits(msg, 9, 32); mm->correctedbits = ei->errors; modesChecksumFix(msg, ei); addr2 = getbits(msg, 9, 32); mm->maybe_addr = addr2; // we are conservative here: only accept corrected messages that // match an existing aircraft. if (addr1 != addr2 && !icaoFilterTest(addr2)) { decode_return(-1); } } } mm->addrtype = ADDR_ADSB_ICAO; mm->source = SOURCE_ADSB; // TIS-B decoding will override this if needed break; } case 20: // Comm-B, altitude reply case 21: // Comm-B, identity reply // These message types either use Address/Parity (see DF0 etc) // or Data Parity where the requested BDS is also xored into the top byte. // So not only do we not know whether the CRC is right, we also don't know if // the ICAO is right! Ow. mm->maybe_addr = mm->crc; // Try an exact match if (icaoFilterTest(mm->crc)) { // OK. mm->addrtype = ADDR_MODE_S; mm->source = SOURCE_MODE_S; mm->addr = mm->crc; break; } // BDS / overlay control just doesn't work out. decode_return(-1); // no good break; default: // All other message types, we don't know how to handle their CRCs, give up decode_return(-2); } // decode the bulk of the message // AA (Address announced) if (mm->msgtype == 11 || mm->msgtype == 17 || mm->msgtype == 18) { mm->AA = getbits(msg, 9, 32); mm->maybe_addr = mm->AA; if (mm->decodeResult == 0) mm->addr = mm->AA; } // AC (Altitude Code) if (mm->msgtype == 0 || mm->msgtype == 4 || mm->msgtype == 16 || mm->msgtype == 20) { mm->AC = getbits(msg, 20, 32); if (mm->AC) { // Only attempt to decode if a valid (non zero) altitude is present unsigned q_bit = 0; mm->baro_alt = decodeAC13Field(mm->AC, &mm->baro_alt_unit, &q_bit); if (mm->baro_alt != INVALID_ALTITUDE) { mm->alt_q_bit = q_bit; mm->baro_alt_valid = 1; } } } // AF (DF19 Application Field) not decoded // CA (Capability) if (mm->msgtype == 11 || mm->msgtype == 17) { mm->CA = getbits(msg, 6, 8); switch (mm->CA) { case 0: mm->airground = AG_UNCERTAIN; break; case 4: mm->airground = AG_GROUND; break; case 5: mm->airground = AG_AIRBORNE; break; case 6: mm->airground = AG_UNCERTAIN; break; case 7: mm->airground = AG_UNCERTAIN; break; } } // CC (Cross-link capability) if (mm->msgtype == 0) { mm->CC = getbit(msg, 7); } // CF (Control field) if (mm->msgtype == 18) { mm->CF = getbits(msg, 6, 8); } // DR (Downlink Request) if (mm->msgtype == 4 || mm->msgtype == 5 || mm->msgtype == 20 || mm->msgtype == 21) { mm->DR = getbits(msg, 9, 13); } // FS (Flight Status) if (mm->msgtype == 4 || mm->msgtype == 5 || mm->msgtype == 20 || mm->msgtype == 21) { mm->FS = getbits(msg, 6, 8); mm->alert_valid = 1; mm->spi_valid = 1; switch (mm->FS) { case 0: mm->airground = AG_UNCERTAIN; break; case 1: mm->airground = AG_GROUND; break; case 2: mm->airground = AG_UNCERTAIN; mm->alert = 1; break; case 3: mm->airground = AG_GROUND; mm->alert = 1; break; case 4: mm->airground = AG_UNCERTAIN; mm->alert = 1; mm->spi = 1; break; case 5: mm->airground = AG_UNCERTAIN; mm->spi = 1; break; default: mm->spi_valid = 0; mm->alert_valid = 0; break; } } // ID (Identity) if (mm->msgtype == 5 || mm->msgtype == 21) { // Gillham encoded Squawk mm->ID = getbits(msg, 20, 32); if (mm->ID) { setSquawkFromID13(mm, mm->ID); } } // KE (Control, ELM) if (mm->msgtype >= 24 && mm->msgtype <= 31) { mm->KE = getbit(msg, 4); } // MB (messsage, Comm-B) if (mm->msgtype == 20 || mm->msgtype == 21) { memcpy(mm->MB, &msg[4], 7); decodeCommB(mm); } // MD (message, Comm-D) if (mm->msgtype >= 24 && mm->msgtype <= 31) { memcpy(mm->MD, &msg[1], 10); } // ME (message, extended squitter) if (mm->msgtype == 17 || mm->msgtype == 18) { memcpy(mm->ME, &msg[4], 7); decodeExtendedSquitter(mm); } // MV (message, ACAS) if (mm->msgtype == 16) { memcpy(mm->MV, &msg[4], 7); if (mm->MV[0] == 0x30) { mm->acas_ra_valid = 1; } } // ND (number of D-segment, Comm-D) if (mm->msgtype >= 24 && mm->msgtype <= 31) { mm->ND = getbits(msg, 5, 8); } // RI (Reply information, ACAS) if (mm->msgtype == 0 || mm->msgtype == 16) { mm->RI = getbits(msg, 14, 17); } // SL (Sensitivity level, ACAS) if (mm->msgtype == 0 || mm->msgtype == 16) { mm->SL = getbits(msg, 9, 11); } // UM (Utility Message) if (mm->msgtype == 4 || mm->msgtype == 5 || mm->msgtype == 20 || mm->msgtype == 21) { mm->UM = getbits(msg, 14, 19); } // VS (Vertical Status) if (mm->msgtype == 0 || mm->msgtype == 16) { mm->VS = getbit(msg, 6); if (mm->VS) mm->airground = AG_GROUND; else mm->airground = AG_UNCERTAIN; } if (mm->decodeResult == 0 && !mm->correctedbits && (mm->msgtype == 17 || (mm->msgtype == 11 && mm->IID == 0)) ) { // No CRC errors seen, and either it was an DF17 extended squitter // or a DF11 acquisition squitter with II = 0. We probably have the right address. // Don't do this for DF18, as a DF18 transmitter doesn't necessarily have a // Mode S transponder. // NB this is the only place that adds addresses! icaoFilterAdd(mm->addr); } // MLAT overrides all other sources if (mm->remote && mm->timestamp == MAGIC_MLAT_TIMESTAMP) { mm->source = SOURCE_MLAT; mm->addrtype = ADDR_MLAT; } // these are messages of general bad quality, treat them as garbage when garbage_ports is in use. if ((Modes.netIngest || Modes.garbage_ports) && mm->remote && mm->timestamp == 0 && (mm->msgtype != 18 || mm->addrtype == ADDR_ADSB_ICAO_NT)) { mm->garbage = 1; mm->source = SOURCE_SBS; if (mm->addrtype >= ADDR_OTHER) mm->addrtype = ADDR_OTHER; } // ignore DF18 from this hexrange, bogus hexes set // i'd like to not have such exceptions in this source but rather configure them some other way // for the time being still gonna do it this way if (mm->remote && mm->msgtype == 18 && mm->addr >= 0x899000 && mm->addr < 0x899200 && Modes.garbage_ports) { mm->garbage = 1; } // all done return mm->decodeResult; } #undef decode_return static void decodeESIdentAndCategory(struct modesMessage *mm) { // Aircraft Identification and Category unsigned char *me = mm->ME; mm->mesub = getbits(me, 6, 8); char *callsign = mm->callsign; callsign[0] = ais_charset[getbits(me, 9, 14)]; callsign[1] = ais_charset[getbits(me, 15, 20)]; callsign[2] = ais_charset[getbits(me, 21, 26)]; callsign[3] = ais_charset[getbits(me, 27, 32)]; callsign[4] = ais_charset[getbits(me, 33, 38)]; callsign[5] = ais_charset[getbits(me, 39, 44)]; callsign[6] = ais_charset[getbits(me, 45, 50)]; callsign[7] = ais_charset[getbits(me, 51, 56)]; callsign[8] = 0; mm->callsign_valid = 1; for (int i = 0; i < 8; ++i) { if ( (callsign[i] >= 'A' && callsign[i] <= 'Z') // -./0123456789 || (callsign[i] >= '-' && callsign[i] <= '9') || callsign[i] == ' ' || callsign[i] == '@' ) { // valid chars } else { mm->callsign_valid = 0; } } if (mm->callsign_valid == 0 && Modes.debug_callsign) { fprintf(stderr, "%06x %8s (len: %d)\n", mm->addr, callsign, (int) strlen(callsign)); } mm->category = ((0x0E - mm->metype) << 4) | mm->mesub; mm->category_valid = 1; } // Handle setting a non-ICAO address static void setIMF(struct modesMessage *mm) { mm->addr |= MODES_NON_ICAO_ADDRESS; switch (mm->addrtype) { case ADDR_ADSB_ICAO: case ADDR_ADSB_ICAO_NT: // Shouldn't happen, but let's try to handle it mm->addrtype = ADDR_ADSB_OTHER; break; case ADDR_TISB_ICAO: mm->addrtype = ADDR_TISB_TRACKFILE; break; case ADDR_ADSR_ICAO: mm->addrtype = ADDR_ADSR_OTHER; break; default: // Nothing. break; } } static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf) { // Airborne Velocity Message unsigned char *me = mm->ME; // 1-5: ME type // 6-8: ME subtype mm->mesub = getbits(me, 6, 8); if (mm->mesub < 1 || mm->mesub > 4) return; // 9: IMF or Intent Change if (check_imf && getbit(me, 9)) setIMF(mm); // 10: reserved // 11-13: NACv (NUCr in v0, maps directly to NACv in v2) mm->accuracy.nac_v_valid = 1; mm->accuracy.nac_v = getbits(me, 11, 13); // 14-35: speed/velocity depending on subtype switch (mm->mesub) { case 1: case 2: { // 14: E/W direction // 15-24: E/W speed // 25: N/S direction // 26-35: N/S speed unsigned ew_raw = getbits(me, 15, 24); unsigned ns_raw = getbits(me, 26, 35); if (ew_raw && ns_raw) { int ew_vel = (ew_raw - 1) * (getbit(me, 14) ? -1 : 1) * ((mm->mesub == 2) ? 4 : 1); int ns_vel = (ns_raw - 1) * (getbit(me, 25) ? -1 : 1) * ((mm->mesub == 2) ? 4 : 1); // Compute velocity and angle from the two speed components mm->gs.v0 = mm->gs.v2 = mm->gs.selected = sqrtf((ns_vel * ns_vel) + (ew_vel * ew_vel) + 0.5); mm->gs_valid = 1; if (mm->gs.selected > 0) { float ground_track = atan2f(ew_vel, ns_vel) * 180.0f / (float) M_PI; // We don't want negative values but a 0-360 scale if (ground_track < 0) ground_track += 360; mm->heading = ground_track; mm->heading_type = HEADING_GROUND_TRACK; mm->heading_valid = 1; } } break; } case 3: case 4: { // 14: heading status // 15-24: heading if (getbit(me, 14)) { mm->heading_valid = 1; mm->heading = getbits(me, 15, 24) * 360.0 / 1024.0; mm->heading_type = HEADING_MAGNETIC_OR_TRUE; } // 25: airspeed type // 26-35: airspeed unsigned airspeed = getbits(me, 26, 35); if (airspeed) { unsigned speed = (airspeed - 1) * (mm->mesub == 4 ? 4 : 1); if (getbit(me, 25)) { mm->tas_valid = 1; mm->tas = speed; } else { mm->ias_valid = 1; mm->ias = speed; } } break; } } // 36: vert rate source // 37: vert rate sign // 38-46: vert rate magnitude unsigned vert_rate = getbits(me, 38, 46); unsigned vert_rate_is_baro = getbit(me, 36); if (vert_rate) { int rate = (vert_rate - 1) * (getbit(me, 37) ? -64 : 64); if (vert_rate_is_baro) { mm->baro_rate = rate; mm->baro_rate_valid = 1; } else { mm->geom_rate = rate; mm->geom_rate_valid = 1; } } // 47-48: reserved // 49: baro/geom delta sign // 50-56: baro/geom delta magnitude unsigned raw_delta = getbits(me, 50, 56); if (raw_delta) { mm->geom_delta_valid = 1; mm->geom_delta = (raw_delta - 1) * (getbit(me, 49) ? -25 : 25); } } static void decodeESSurfacePosition(struct modesMessage *mm, int check_imf) { // Surface position and movement unsigned char *me = mm->ME; mm->airground = AG_GROUND; // definitely. mm->cpr_valid = 1; mm->cpr_type = CPR_SURFACE; // 6-12: Movement unsigned movement = getbits(me, 6, 12); if (movement > 0 && movement < 125) { mm->gs_valid = 1; mm->gs.selected = mm->gs.v0 = decodeMovementFieldV0(movement); // assumed v0 until told otherwise mm->gs.v2 = decodeMovementFieldV2(movement); } // 13: Heading/track status // 14-20: Heading/track if (getbit(me, 13)) { mm->heading_valid = 1; mm->heading = getbits(me, 14, 20) * 360.0 / 128.0; mm->heading_type = HEADING_TRACK_OR_HEADING; } // 21: IMF or T flag if (check_imf && getbit(me, 21)) setIMF(mm); // 22: F flag (odd/even) mm->cpr_odd = getbit(me, 22); // 23-39: CPR encoded latitude mm->cpr_lat = getbits(me, 23, 39); // 40-56: CPR encoded longitude mm->cpr_lon = getbits(me, 40, 56); } static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf) { // Airborne position and altitude unsigned char *me = mm->ME; // 6-7: surveillance status switch (getbits(me, 6, 7)) { case 0: // no status mm->alert_valid = mm->spi_valid = 1; mm->alert = mm->spi = 0; break; case 1: // permanent alert case 2: // temporary alert mm->alert_valid = 1; mm->alert = 1; // states 1/2 override state 3, so we don't know SPI status here. break; case 3: // SPI // we know there's no alert in this case mm->alert_valid = mm->spi_valid = 1; mm->alert = 0; mm->spi = 1; break; } // 8: IMF or NIC supplement-B if (check_imf) { if (getbit(me, 8)) setIMF(mm); } else { // NIC-B (v2) or SAF (v0/v1) mm->accuracy.nic_b_valid = 1; mm->accuracy.nic_b = getbit(me, 8); } // 9-20: altitude unsigned AC12Field = getbits(me, 9, 20); if (mm->metype == 0) { // no position information } else { // 21: T flag (UTC sync or not) // 22: F flag (odd or even) // 23-39: CPR encoded latitude // 40-56: CPR encoded longitude mm->cpr_lat = getbits(me, 23, 39); mm->cpr_lon = getbits(me, 40, 56); // Catch some common failure modes and don't mark them as valid // (so they won't be used for positioning) if (AC12Field == 0 && mm->cpr_lon == 0 && (mm->cpr_lat & 0x0fff) == 0 && mm->metype == 15) { // Seen from at least: // 400F3F (Eurocopter ECC155 B1) - Bristow Helicopters // 4008F3 (BAE ATP) - Atlantic Airlines // 400648 (BAE ATP) - Atlantic Airlines // altitude == 0, longitude == 0, type == 15 and zeros in latitude LSB. // Can alternate with valid reports having type == 14 Modes.stats_current.cpr_filtered++; } else { // Otherwise, assume it's valid. mm->cpr_valid = 1; mm->cpr_type = CPR_AIRBORNE; mm->cpr_odd = getbit(me, 22); } } if (AC12Field && mm->airground != AG_GROUND) {// Only attempt to decode if a valid (non zero) altitude is present and not on ground altitude_unit_t unit; unsigned q_bit = 0; int alt = decodeAC12Field(AC12Field, &unit, &q_bit); if (alt != INVALID_ALTITUDE) { mm->alt_q_bit = q_bit; if (mm->metype == 20 || mm->metype == 21 || mm->metype == 22) { mm->geom_alt = alt; mm->geom_alt_unit = unit; mm->geom_alt_valid = 1; } else { mm->baro_alt = alt; mm->baro_alt_unit = unit; mm->baro_alt_valid = 1; } } } } static void decodeESTestMessage(struct modesMessage *mm) { unsigned char *me = mm->ME; mm->mesub = getbits(me, 6, 8); if (mm->mesub == 7) { // (see 1090-WP-15-20) int ID13Field = getbits(me, 9, 21); if (ID13Field) { setSquawkFromID13(mm, ID13Field); } } } static void decodeESAircraftStatus(struct modesMessage *mm, int check_imf) { // Extended Squitter Aircraft Status (mm->metype == 28) unsigned char *me = mm->ME; mm->mesub = getbits(me, 6, 8); if (mm->mesub == 1) { // Emergency status squawk field mm->emergency_valid = 1; mm->emergency = (emergency_t) getbits(me, 9, 11); unsigned ID13Field = getbits(me, 12, 24); if (ID13Field) { setSquawkFromID13(mm, ID13Field); } if (check_imf && getbit(me, 56)) setIMF(mm); } if (mm->mesub == 2) { // ACAS RA Broadcast mm->acas_ra_valid = 1; } } static void decodeESTargetStatus(struct modesMessage *mm, int check_imf) { unsigned char *me = mm->ME; mm->mesub = getbits(me, 6, 7); // an unusual message: only 2 bits of subtype if (check_imf && getbit(me, 51)) setIMF(mm); if (mm->mesub == 0 && getbit(me, 11) == 0) { // Target state and status, V1 // 8-9: vertical source switch (getbits(me, 8, 9)) { case 1: mm->nav.altitude_source = NAV_ALT_MCP; break; case 2: mm->nav.altitude_source = NAV_ALT_AIRCRAFT; break; case 3: mm->nav.altitude_source = NAV_ALT_FMS; break; default: // nothing break; } // 10: target altitude type (MSL or Baro, ignored) // 11: backward compatibility bit, always 0 // 12-13: target alt capabilities (ignored) // 14-15: vertical mode switch (getbits(me, 14, 15)) { case 1: // "acquiring" mm->nav.modes_valid = 1; if (mm->nav.altitude_source == NAV_ALT_FMS) { mm->nav.modes |= NAV_MODE_VNAV; } else { mm->nav.modes |= NAV_MODE_AUTOPILOT; } break; case 2: // "maintaining" mm->nav.modes_valid = 1; if (mm->nav.altitude_source == NAV_ALT_FMS) { mm->nav.modes |= NAV_MODE_VNAV; } else if (mm->nav.altitude_source == NAV_ALT_AIRCRAFT) { mm->nav.modes |= NAV_MODE_ALT_HOLD; } else { mm->nav.modes |= NAV_MODE_AUTOPILOT; } break; default: // nothing break; } // 16-25: target altitude int alt = -1000 + 100 * getbits(me, 16, 25); switch (mm->nav.altitude_source) { case NAV_ALT_MCP: mm->nav.mcp_altitude_valid = 1; mm->nav.mcp_altitude = alt; break; case NAV_ALT_FMS: mm->nav.fms_altitude_valid = 1; mm->nav.fms_altitude = alt; break; default: // nothing break; } // 26-27: horizontal data source unsigned h_source = getbits(me, 26, 27); if (h_source != 0) { // 28-36: target heading/track mm->nav.heading_valid = 1; mm->nav.heading = getbits(me, 28, 36); // 37: track vs heading if (getbit(me, 37)) { mm->nav.heading_type = HEADING_GROUND_TRACK; } else { mm->nav.heading_type = HEADING_MAGNETIC_OR_TRUE; } } // 38-39: horizontal mode switch (getbits(me, 38, 39)) { case 1: // acquiring case 2: // maintaining mm->nav.modes_valid = 1; if (h_source == 3) { // FMS mm->nav.modes |= NAV_MODE_LNAV; } else { mm->nav.modes |= NAV_MODE_AUTOPILOT; } break; default: // nothing break; } // 40-43: NACp mm->accuracy.nac_p_valid = 1; mm->accuracy.nac_p = getbits(me, 40, 43); // 44: NICbaro mm->accuracy.nic_baro_valid = 1; mm->accuracy.nic_baro = getbit(me, 44); // 45-46: SIL mm->accuracy.sil = getbits(me, 45, 46); mm->accuracy.sil_type = SIL_UNKNOWN; // 47-51: reserved // 52-53: TCAS status switch (getbits(me, 52, 53)) { case 1: mm->nav.modes_valid = 1; // no tcas break; case 2: case 3: mm->nav.modes_valid = 1; mm->nav.modes |= NAV_MODE_TCAS; break; case 0: // assume TCAS if we had any other modes // but don't enable modes just for this mm->nav.modes |= NAV_MODE_TCAS; break; default: // nothing break; } // 54-56: emergency/priority mm->emergency_valid = 1; mm->emergency = (emergency_t) getbits(me, 54, 56); } else if (mm->mesub == 1) { // Target state and status, V2 // 8: SIL unsigned is_fms = getbit(me, 9); unsigned alt_bits = getbits(me, 10, 20); if (alt_bits != 0) { if (is_fms) { mm->nav.fms_altitude_valid = 1; mm->nav.fms_altitude = (alt_bits - 1) * 32; } else { mm->nav.mcp_altitude_valid = 1; mm->nav.mcp_altitude = (alt_bits - 1) * 32; } } unsigned baro_bits = getbits(me, 21, 29); if (baro_bits != 0) { mm->nav.qnh_valid = 1; mm->nav.qnh = 800.0 + (baro_bits - 1) * 0.8; } if (getbit(me, 30)) { mm->nav.heading_valid = 1; // two's complement -180..+180, which is conveniently // also the same as unsigned 0..360 mm->nav.heading = getbits(me, 31, 39) * 180.0 / 256.0; mm->nav.heading_type = HEADING_MAGNETIC_OR_TRUE; } // 40-43: NACp mm->accuracy.nac_p_valid = 1; mm->accuracy.nac_p = getbits(me, 40, 43); // 44: NICbaro mm->accuracy.nic_baro_valid = 1; mm->accuracy.nic_baro = getbit(me, 44); // 45-46: SIL mm->accuracy.sil = getbits(me, 45, 46); mm->accuracy.sil_type = SIL_UNKNOWN; // 47: mode bits validity if (getbit(me, 47)) { // 48-54: mode bits mm->nav.modes_valid = 1; mm->nav.modes = (getbit(me, 48) ? NAV_MODE_AUTOPILOT : 0) | (getbit(me, 49) ? NAV_MODE_VNAV : 0) | (getbit(me, 50) ? NAV_MODE_ALT_HOLD : 0) | // 51: IMF (getbit(me, 52) ? NAV_MODE_APPROACH : 0) | (getbit(me, 53) ? NAV_MODE_TCAS : 0) | (getbit(me, 54) ? NAV_MODE_LNAV : 0); } // 55-56 reserved } } static void decodeESOperationalStatus(struct modesMessage *mm, int check_imf) { unsigned char *me = mm->ME; mm->mesub = getbits(me, 6, 8); // Aircraft Operational Status if (check_imf && getbit(me, 56)) setIMF(mm); if (mm->mesub == 0 || mm->mesub == 1) { mm->opstatus.valid = 1; mm->opstatus.version = getbits(me, 41, 43); switch (mm->opstatus.version) { case 0: if (mm->mesub == 0 && getbits(me, 9, 10) == 0) { mm->opstatus.cc_acas = !getbit(me, 12); mm->opstatus.cc_cdti = getbit(me, 13); } break; case 1: if (getbits(me, 25, 26) == 0) { mm->opstatus.om_acas_ra = getbit(me, 27); mm->opstatus.om_ident = getbit(me, 28); mm->opstatus.om_atc = getbit(me, 29); } if (mm->mesub == 0 && getbits(me, 9, 10) == 0 && getbits(me, 13, 14) == 0) { // airborne mm->opstatus.cc_acas = !getbit(me, 11); mm->opstatus.cc_cdti = getbit(me, 12); mm->opstatus.cc_arv = getbit(me, 15); mm->opstatus.cc_ts = getbit(me, 16); mm->opstatus.cc_tc = getbits(me, 17, 18); } else if (mm->mesub == 1 && getbits(me, 9, 10) == 0 && getbits(me, 13, 14) == 0) { // surface mm->opstatus.cc_poa = getbit(me, 11); mm->opstatus.cc_cdti = getbit(me, 12); mm->opstatus.cc_b2_low = getbit(me, 15); mm->opstatus.cc_lw_valid = 1; mm->opstatus.cc_lw = getbits(me, 21, 24); } mm->accuracy.nic_a_valid = 1; mm->accuracy.nic_a = getbit(me, 44); mm->accuracy.nac_p_valid = 1; mm->accuracy.nac_p = getbits(me, 45, 48); mm->accuracy.sil_type = SIL_UNKNOWN; mm->accuracy.sil = getbits(me, 51, 52); mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; if (mm->mesub == 0) { mm->accuracy.nic_baro_valid = 1; mm->accuracy.nic_baro = getbit(me, 53); } else { // see DO=260B §2.2.3.2.7.2.12 // TAH=0 : surface movement reports ground track // TAH=1 : surface movement reports aircraft heading mm->opstatus.tah = getbit(me, 53) ? mm->opstatus.hrd : HEADING_GROUND_TRACK; } break; case 2: if (getbits(me, 25, 26) == 0) { mm->opstatus.om_acas_ra = getbit(me, 27); mm->opstatus.om_ident = getbit(me, 28); mm->opstatus.om_atc = getbit(me, 29); mm->opstatus.om_saf = getbit(me, 30); mm->accuracy.sda_valid = 1; mm->accuracy.sda = getbits(me, 31, 32); } if (mm->mesub == 0 && getbits(me, 9, 10) == 0) { // airborne mm->opstatus.cc_acas = getbit(me, 11); // nb inverted sense versus v0/v1 mm->opstatus.cc_1090_in = getbit(me, 12); mm->opstatus.cc_arv = getbit(me, 15); mm->opstatus.cc_ts = getbit(me, 16); mm->opstatus.cc_tc = getbits(me, 17, 18); mm->opstatus.cc_uat_in = getbit(me, 19); } else if (mm->mesub == 1 && getbits(me, 9, 10) == 0) { // surface mm->opstatus.cc_poa = getbit(me, 11); mm->opstatus.cc_1090_in = getbit(me, 12); mm->opstatus.cc_b2_low = getbit(me, 15); mm->opstatus.cc_uat_in = getbit(me, 16); mm->accuracy.nac_v_valid = 1; mm->accuracy.nac_v = getbits(me, 17, 19); mm->accuracy.nic_c_valid = 1; mm->accuracy.nic_c = getbit(me, 20); mm->opstatus.cc_lw_valid = 1; mm->opstatus.cc_lw = getbits(me, 21, 24); mm->opstatus.cc_antenna_offset = getbits(me, 33, 40); } mm->accuracy.nic_a_valid = 1; mm->accuracy.nic_a = getbit(me, 44); mm->accuracy.nac_p_valid = 1; mm->accuracy.nac_p = getbits(me, 45, 48); mm->accuracy.sil = getbits(me, 51, 52); mm->accuracy.sil_type = getbit(me, 55) ? SIL_PER_SAMPLE : SIL_PER_HOUR; mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; if (mm->mesub == 0) { mm->accuracy.gva_valid = 1; mm->accuracy.gva = getbits(me, 49, 50); mm->accuracy.nic_baro_valid = 1; mm->accuracy.nic_baro = getbit(me, 53); } else { // see DO=260B §2.2.3.2.7.2.12 // TAH=0 : surface movement reports ground track // TAH=1 : surface movement reports aircraft heading mm->opstatus.tah = getbit(me, 53) ? mm->opstatus.hrd : HEADING_GROUND_TRACK; } break; } } } static void decodeExtendedSquitter(struct modesMessage *mm) { unsigned char *me = mm->ME; unsigned metype = mm->metype = getbits(me, 1, 5); unsigned check_imf = 0; // Check CF on DF18 to work out the format of the ES and whether we need to look for an IMF bit if (mm->msgtype == 18) { switch (mm->CF) { case 0: // ADS-B Message from a non-transponder device, AA field holds 24-bit ICAO aircraft address mm->addrtype = ADDR_ADSB_ICAO_NT; break; case 1: // Reserved for ADS-B Message in which the AA field holds anonymous address or ground vehicle address or fixed obstruction address mm->addrtype = ADDR_ADSB_OTHER; mm->addr |= MODES_NON_ICAO_ADDRESS; break; case 2: // Fine TIS-B Message // IMF=0: AA field contains the 24-bit ICAO aircraft address // IMF=1: AA field contains the 12-bit Mode A code followed by a 12-bit track file number mm->source = SOURCE_TISB; mm->addrtype = ADDR_TISB_ICAO; check_imf = 1; break; case 3: // Coarse TIS-B airborne position and velocity. // IMF=0: AA field contains the 24-bit ICAO aircraft address // IMF=1: AA field contains the 12-bit Mode A code followed by a 12-bit track file number // For now we only look at the IMF bit. mm->source = SOURCE_TISB; mm->addrtype = ADDR_TISB_ICAO; if (getbit(me, 1)) setIMF(mm); return; case 5: // Fine TIS-B Message, AA field contains a non-ICAO 24-bit address mm->addrtype = ADDR_TISB_OTHER; mm->source = SOURCE_TISB; mm->addr |= MODES_NON_ICAO_ADDRESS; break; case 6: // Rebroadcast of ADS-B Message from an alternate data link // IMF=0: AA field holds 24-bit ICAO aircraft address // IMF=1: AA field holds anonymous address or ground vehicle address or fixed obstruction address mm->addrtype = ADDR_ADSR_ICAO; mm->source = SOURCE_ADSR; check_imf = 1; break; default: // All others, we don't know the format. mm->addrtype = ADDR_UNKNOWN; mm->addr |= MODES_NON_ICAO_ADDRESS; // assume non-ICAO return; } } switch (metype) { case 1: case 2: case 3: case 4: decodeESIdentAndCategory(mm); break; case 19: decodeESAirborneVelocity(mm, check_imf); break; case 5: case 6: case 7: case 8: decodeESSurfacePosition(mm, check_imf); break; case 0: // Airborne position, baro altitude only case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: // Airborne position, baro case 20: case 21: case 22: // Airborne position, geometric altitude (HAE or MSL) decodeESAirbornePosition(mm, check_imf); break; case 23: decodeESTestMessage(mm); break; case 24: // Reserved for Surface System Status break; case 28: decodeESAircraftStatus(mm, check_imf); break; case 29: decodeESTargetStatus(mm, check_imf); break; case 30: // Aircraft Operational Coordination break; case 31: decodeESOperationalStatus(mm, check_imf); break; default: break; } } static const char *df_names[33] = { /* 0 */ "Short Air-Air Surveillance", /* 1 */ NULL, /* 2 */ NULL, /* 3 */ NULL, /* 4 */ "Survelliance, Altitude Reply", /* 5 */ "Survelliance, Identity Reply", /* 6 */ NULL, /* 7 */ NULL, /* 8 */ NULL, /* 9 */ NULL, /* 10 */ NULL, /* 11 */ "All Call Reply", /* 12 */ NULL, /* 13 */ NULL, /* 14 */ NULL, /* 15 */ NULL, /* 16 */ "Long Air-Air ACAS", /* 17 */ "Extended Squitter", /* 18 */ "Extended Squitter (Non-Transponder)", /* 19 */ "Extended Squitter (Military)", /* 20 */ "Comm-B, Altitude Reply", /* 21 */ "Comm-B, Identity Reply", /* 22 */ "Military Use", /* 23 */ NULL, /* 24 */ "Comm-D Extended Length Message", /* 25 */ "Comm-D Extended Length Message", /* 26 */ "Comm-D Extended Length Message", /* 27 */ "Comm-D Extended Length Message", /* 28 */ "Comm-D Extended Length Message", /* 29 */ "Comm-D Extended Length Message", /* 30 */ "Comm-D Extended Length Message", /* 31 */ "Comm-D Extended Length Message", /* 32 */ "Mode A/C Reply", }; static const char *df_to_string(unsigned df) { if (df == DFTYPE_MODEAC) return "modeac"; if (df > 32) return "out of range"; if (!df_names[df]) return "reserved"; return df_names[df]; } static const char *altitude_unit_to_string(altitude_unit_t unit) { switch (unit) { case UNIT_FEET: return "ft"; case UNIT_METERS: return "m"; default: return "(unknown altitude unit)"; } } static const char *heading_type_to_string(heading_type_t type) { switch (type) { case HEADING_GROUND_TRACK: return "Ground track"; case HEADING_MAGNETIC: return "Mag heading"; case HEADING_TRUE: return "True heading"; case HEADING_MAGNETIC_OR_TRUE: return "Heading"; case HEADING_TRACK_OR_HEADING: return "Track/Heading"; default: return "unknown heading type"; } } static const char *commb_format_to_string(commb_format_t format) { switch (format) { case COMMB_EMPTY_RESPONSE: return "empty response"; case COMMB_AMBIGUOUS: return "ambiguous format"; case COMMB_DATALINK_CAPS: return "BDS1,0 Datalink capabilities"; case COMMB_GICB_CAPS: return "BDS1,7 Common usage GICB capabilities"; case COMMB_AIRCRAFT_IDENT: return "BDS2,0 Aircraft identification"; case COMMB_ACAS_RA: return "BDS3,0 ACAS resolution advisory"; case COMMB_VERTICAL_INTENT: return "BDS4,0 Selected vertical intention"; case COMMB_TRACK_TURN: return "BDS5,0 Track and turn report"; case COMMB_HEADING_SPEED: return "BDS6,0 Heading and speed report"; case COMMB_METEOROLOGICAL_ROUTINE: return "BDS4,4 Meteorological routine air report"; default: return "unknown format"; } } static const char *nav_modes_to_string(nav_modes_t flags) { static char buf[128]; buf[0] = 0; if (flags & NAV_MODE_AUTOPILOT) strcat(buf, "autopilot "); if (flags & NAV_MODE_VNAV) strcat(buf, "vnav "); if (flags & NAV_MODE_ALT_HOLD) strcat(buf, "althold "); if (flags & NAV_MODE_APPROACH) strcat(buf, "approach "); if (flags & NAV_MODE_LNAV) strcat(buf, "lnav "); if (flags & NAV_MODE_TCAS) strcat(buf, "tcas "); if (buf[0] != 0) buf[strlen(buf) - 1] = 0; return buf; } static const char *sil_type_to_string(sil_type_t type) { switch (type) { case SIL_UNKNOWN: return "unknown type"; case SIL_PER_HOUR: return "per flight hour"; case SIL_PER_SAMPLE: return "per sample"; default: return "invalid type"; } } static const char *emergency_to_string(emergency_t emergency) { switch (emergency) { case EMERGENCY_NONE: return "no emergency"; case EMERGENCY_GENERAL: return "general emergency (7700)"; case EMERGENCY_LIFEGUARD: return "lifeguard / medical emergency"; case EMERGENCY_MINFUEL: return "minimum fuel"; case EMERGENCY_NORDO: return "no communications (7600)"; case EMERGENCY_UNLAWFUL: return "unlawful interference (7500)"; case EMERGENCY_DOWNED: return "downed aircraft"; default: return "reserved"; } } static void print_hex_bytes(unsigned char *data, size_t len) { size_t i; for (i = 0; i < len; ++i) { printf("%02X", (unsigned) data[i]); } } static int esTypeHasSubtype(unsigned metype) { if (metype <= 18) { return 0; } if (metype >= 20 && metype <= 22) { return 0; } return 1; } static const char *esTypeName(unsigned metype, unsigned mesub) { switch (metype) { case 0: return "No position information (airborne or surface)"; case 1: case 2: case 3: case 4: return "Aircraft identification and category"; case 5: case 6: case 7: case 8: return "Surface position"; case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: return "Airborne position (barometric altitude)"; case 19: switch (mesub) { case 1: return "Airborne velocity over ground, subsonic"; case 2: return "Airborne velocity over ground, supersonic"; case 3: return "Airspeed and heading, subsonic"; case 4: return "Airspeed and heading, supersonic"; default: return "Unknown"; } case 20: case 21: case 22: return "Airborne position (geometric altitude)"; case 23: switch (mesub) { case 0: return "Test message"; case 7: return "National use / 1090-WP-15-20 Mode A squawk"; default: return "Unknown"; } case 24: return "Reserved for surface system status"; case 27: return "Reserved for trajectory change"; case 28: switch (mesub) { case 1: return "Emergency/priority status"; case 2: return "ACAS RA broadcast"; default: return "Unknown"; } case 29: switch (mesub) { case 0: return "Target state and status (V1)"; case 1: return "Target state and status (V2)"; default: return "Unknown"; } case 30: return "Aircraft Operational Coordination"; case 31: // Aircraft Operational Status switch (mesub) { case 0: return "Aircraft operational status (airborne)"; case 1: return "Aircraft operational status (surface)"; default: return "Unknown"; } default: return "Unknown"; } } void displayModesMessage(struct modesMessage *mm) { int j; if (0 && mm->cpr_valid && mm->cpr_decoded) { printf("systemTime: %.3fs\n", (mm->sysTimestamp % (5*MINUTES)) / 1000.0); printf(" CPR odd flag: %s\n", mm->cpr_odd ? "odd" : "even"); printf(" CPR latitude: %.6f (%u)\n" " CPR longitude: %.6f (%u)\n" " CPR decoding: %s\n", mm->decoded_lat, mm->cpr_lat, mm->decoded_lon, mm->cpr_lon, mm->cpr_relative ? "local" : "global"); } // Handle only addresses mode first. if (Modes.onlyaddr) { printf("%06x\n", mm->addr); return; // Enough for --onlyaddr mode } // Show the raw message. if (Modes.mlat && mm->timestamp) { printf("@%012" PRIX64, mm->timestamp); } else { printf("*"); } for (j = 0; j < mm->msgbits / 8; j++) printf("%02x", mm->msg[j]); printf(";\n"); if (Modes.raw) { fflush(stdout); // Provide data to the reader ASAP return; // Enough for --raw mode } char addr[64]; addr[0] = 0; if (mm->msgtype < 32) { if (mm->addr != HEX_UNKNOWN) sprintf(addr, "%s%06x ", (mm->addr & MODES_NON_ICAO_ADDRESS) ? "~" : " ", mm->addr & 0xFFFFFF); else if (mm->maybe_addr != HEX_UNKNOWN) sprintf(addr, "%s%06x ?", (mm->maybe_addr & MODES_NON_ICAO_ADDRESS) ? "~" : " ", mm->maybe_addr & 0xFFFFFF); else sprintf(addr, " unknown "); printf("hex: %s ", addr); printf("CRC: %06x ", mm->crc); printf("fixed bits: %d ", mm->correctedbits); printf("decode: %s\n", mm->decodeResult >= 0 ? "ok" : (mm->decodeResult == -1 ? "reject unknown hex" : "reject bad" )); } if (mm->signalLevel > 0) printf("RSSI: %8.1f dBFS ", 10 * log10(mm->signalLevel)); printf("reduce_forward: %d\n", mm->reduce_forward); if (mm->score) printf("Score: %d\n", mm->score); if (mm->receiverId && !Modes.debug_bogus) { char uuid[32]; // needs 18 chars and null byte sprint_uuid1(mm->receiverId, uuid); printf("receiverId: %s\n", uuid); } if (mm->timestamp == MAGIC_MLAT_TIMESTAMP) printf("This is a synthetic MLAT message.\n"); else if (mm->timestamp == MAGIC_UAT_TIMESTAMP) printf("This is a synthetic UAT message.\n"); else if (mm->timestamp == MAGIC_NOFORWARD_TIMESTAMP) printf("This is a no_forward message.\n"); else printf("receiverTime: %27.2fus\n", mm->timestamp / 12.0); if (1 || !Modes.debug_bogus) { time_t nowTime = floor(mm->sysTimestamp / 1000.0); struct tm local; gmtime_r(&nowTime, &local); char timebuf[512]; strftime(timebuf, 128, "%T", &local); printf("utcTime: %s.%03lld epoch: %.3f\n", timebuf, (long long) mm->sysTimestamp % 1000, mm->sysTimestamp / 1000.0); } if (mm->sbs_in) { printf("SBS addr:%s\n", addr); } else { switch (mm->msgtype) { case 0: printf("DF: 0 addr:%s VS:%u CC:%u SL:%u RI:%u AC:%u\n", addr, mm->VS, mm->CC, mm->SL, mm->RI, mm->AC); break; case 4: printf("DF: 4 addr:%s FS:%u DR:%u UM:%u AC:%u\n", addr, mm->FS, mm->DR, mm->UM, mm->AC); break; case 5: printf("DF: 5 addr:%s FS:%u DR:%u UM:%u ID:%u\n", addr, mm->FS, mm->DR, mm->UM, mm->ID); break; case 11: printf("DF: 11 AA:%06X IID:%u CA:%u\n", mm->AA, mm->IID, mm->CA); break; case 16: printf("DF: 16 addr:%s VS:%u SL:%u RI:%u AC:%u MV:", addr, mm->VS, mm->SL, mm->RI, mm->AC); print_hex_bytes(mm->MV, sizeof (mm->MV)); printf("\n"); if (mm->acas_ra_valid) printACASInfoShort(mm->addr, mm->MV, NULL, mm, mm->sysTimestamp); break; case 17: printf("DF: 17 AA:%06X CA:%u ME:", mm->AA, mm->CA); print_hex_bytes(mm->ME, sizeof (mm->ME)); printf("\n"); break; case 18: printf("DF: 18 AA:%06X CF:%u ME:", mm->AA, mm->CF); print_hex_bytes(mm->ME, sizeof (mm->ME)); printf("\n"); break; case 20: printf("DF: 20 addr:%s FS:%u DR:%u UM:%u AC:%u MB:", addr, mm->FS, mm->DR, mm->UM, mm->AC); print_hex_bytes(mm->MB, sizeof (mm->MB)); printf("\n"); break; case 21: printf("DF: 21 addr:%s FS:%u DR:%u UM:%u ID:%u MB:", addr, mm->FS, mm->DR, mm->UM, mm->ID); print_hex_bytes(mm->MB, sizeof (mm->MB)); printf("\n"); break; case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: printf("DF: 24 addr:%s KE:%u ND:%u MD:", addr, mm->KE, mm->ND); print_hex_bytes(mm->MD, sizeof (mm->MD)); printf("\n"); break; } } if (!mm->sbs_in) printf(" %s", df_to_string(mm->msgtype)); if (mm->msgtype == 17 || mm->msgtype == 18) { if (esTypeHasSubtype(mm->metype)) { printf(" %s (%u/%u)", esTypeName(mm->metype, mm->mesub), mm->metype, mm->mesub); } else { printf(" %s (%u)", esTypeName(mm->metype, mm->mesub), mm->metype); } } if (!mm->sbs_in) printf("\n"); if (mm->msgtype == 20 || mm->msgtype == 21) { printf(" Comm-B format: %s\n", commb_format_to_string(mm->commb_format)); } if (mm->addr != HEX_UNKNOWN) { if (mm->addr & MODES_NON_ICAO_ADDRESS) { printf(" Other Address: %06X (%s)\n", mm->addr & 0xFFFFFF, addrtype_enum_string(mm->addrtype)); } else { printf(" ICAO Address: %06X (%s)\n", mm->addr, addrtype_enum_string(mm->addrtype)); } } if (mm->airground != AG_INVALID) { printf(" Air/Ground: %s\n", airground_to_string(mm->airground)); } if (mm->baro_alt_valid) { printf(" Baro altitude: %8d %s\n", mm->baro_alt, altitude_unit_to_string(mm->baro_alt_unit)); } if (mm->geom_alt_valid) { printf(" Geom altitude: %8d %s\n", mm->geom_alt, altitude_unit_to_string(mm->geom_alt_unit)); } if (mm->geom_alt_derived) { printf(" Geom altitude (derived): %8d %s\n", mm->geom_alt, altitude_unit_to_string(mm->geom_alt_unit)); } if (mm->geom_delta_valid) { printf(" Geom - baro: %8d ft\n", mm->geom_delta); } if (mm->heading_valid) { printf(" %-13s %5.1f\n", heading_type_to_string(mm->heading_type), mm->heading); } if (mm->track_rate_valid) { printf(" Track rate: %6.2f deg/sec %s\n", mm->track_rate, mm->track_rate < 0 ? "left" : mm->track_rate > 0 ? "right" : ""); } if (mm->roll_valid) { printf(" Roll: %5.1f degrees %s\n", mm->roll, mm->roll < -0.05 ? "left" : mm->roll > 0.05 ? "right" : ""); } if (mm->gs_valid) { printf(" Groundspeed: %5.1f kt", mm->gs.selected); if (mm->gs.v0 != mm->gs.selected) { printf(" (v0: %.1f kt)", mm->gs.v0); } if (mm->gs.v2 != mm->gs.selected) { printf(" (v2: %.1f kt)", mm->gs.v2); } printf("\n"); } if (mm->ias_valid) { printf(" IAS: %3u kt\n", mm->ias); } if (mm->tas_valid) { printf(" TAS: %3u kt\n", mm->tas); } if (mm->mach_valid) { printf(" Mach number: %.3f\n", mm->mach); } if (mm->baro_rate_valid) { printf(" Baro rate: %6d ft/min\n", mm->baro_rate); } if (mm->geom_rate_valid) { printf(" Geom rate: %6d ft/min\n", mm->geom_rate); } if (mm->squawk_valid) { printf(" Squawk: %04d\n", mm->squawkDec); } if (mm->callsign_valid) { printf(" Ident: %s\n", mm->callsign); } if (mm->category_valid) { printf(" Category: %02X\n", mm->category); } if (mm->cpr_valid) { printf(" CPR type: %s\n" " CPR odd flag: %s\n", cpr_type_string(mm->cpr_type), mm->cpr_odd ? "odd" : "even"); if (mm->cpr_decoded) { printf(" CPR latitude: %11.6f (%5u)\n" " CPR longitude: %11.6f (%5u)\n" " CPR decoding: %s\n" " NIC: %u\n" " Rc: %.3f km / %.1f NM\n", mm->decoded_lat, mm->cpr_lat, mm->decoded_lon, mm->cpr_lon, mm->cpr_relative ? "local" : "global", mm->decoded_nic, mm->decoded_rc / 1000.0, mm->decoded_rc / 1852.0); } else { printf(" CPR latitude: (%u)\n" " CPR longitude: (%u)\n" " CPR decoding: none\n", mm->cpr_lat, mm->cpr_lon); } } if (mm->sbs_pos_valid) { printf(" Latitude: %.6f\n" " Longitude: %.6f\n", mm->decoded_lat, mm->decoded_lon); } if (mm->accuracy.nic_a_valid) { printf(" NIC-A: %d\n", mm->accuracy.nic_a); } if (mm->accuracy.nic_b_valid) { printf(" NIC-B: %d\n", mm->accuracy.nic_b); } if (mm->accuracy.nic_c_valid) { printf(" NIC-C: %d\n", mm->accuracy.nic_c); } if (mm->accuracy.nic_baro_valid) { printf(" NIC-baro: %d\n", mm->accuracy.nic_baro); } if (mm->accuracy.nac_p_valid) { printf(" NACp: %d\n", mm->accuracy.nac_p); } if (mm->accuracy.nac_v_valid) { printf(" NACv: %d\n", mm->accuracy.nac_v); } if (mm->accuracy.gva_valid) { printf(" GVA: %d\n", mm->accuracy.gva); } if (mm->accuracy.sil_type != SIL_INVALID) { const char *sil_description; switch (mm->accuracy.sil) { case 1: sil_description = "p <= 0.1%"; break; case 2: sil_description = "p <= 0.001%"; break; case 3: sil_description = "p <= 0.00001%"; break; default: sil_description = "p > 0.1%"; break; } printf(" SIL: %d (%s, %s)\n", mm->accuracy.sil, sil_description, sil_type_to_string(mm->accuracy.sil_type)); } if (mm->accuracy.sda_valid) { printf(" SDA: %d\n", mm->accuracy.sda); } if (mm->mlatEPU) { printf(" mlatEPU: %d\n", mm->mlatEPU); } if (mm->opstatus.valid) { printf(" Aircraft Operational Status:\n"); printf(" Version: %d\n", mm->opstatus.version); printf(" Capability classes: "); if (mm->opstatus.cc_acas) printf("ACAS "); if (mm->opstatus.cc_cdti) printf("CDTI "); if (mm->opstatus.cc_1090_in) printf("1090IN "); if (mm->opstatus.cc_arv) printf("ARV "); if (mm->opstatus.cc_ts) printf("TS "); if (mm->opstatus.cc_tc) printf("TC=%d ", mm->opstatus.cc_tc); if (mm->opstatus.cc_uat_in) printf("UATIN "); if (mm->opstatus.cc_poa) printf("POA "); if (mm->opstatus.cc_b2_low) printf("B2-LOW "); if (mm->opstatus.cc_lw_valid) printf("L/W=%d ", mm->opstatus.cc_lw); if (mm->opstatus.cc_antenna_offset) printf("GPS-OFFSET=%d ", mm->opstatus.cc_antenna_offset); printf("\n"); printf(" Operational modes: "); if (mm->opstatus.om_acas_ra) printf("ACASRA "); if (mm->opstatus.om_ident) printf("IDENT "); if (mm->opstatus.om_atc) printf("ATC "); if (mm->opstatus.om_saf) printf("SAF "); printf("\n"); if (mm->mesub == 1) printf(" Track/heading: %s\n", heading_type_to_string(mm->opstatus.tah)); printf(" Heading ref dir: %s\n", heading_type_to_string(mm->opstatus.hrd)); } if (mm->nav.heading_valid) printf(" Selected heading: %5.1f\n", mm->nav.heading); if (mm->nav.fms_altitude_valid) printf(" FMS selected altitude: %5u ft\n", mm->nav.fms_altitude); if (mm->nav.mcp_altitude_valid) printf(" MCP selected altitude: %5u ft\n", mm->nav.mcp_altitude); if (mm->nav.qnh_valid) printf(" QNH: %.1f millibars\n", mm->nav.qnh); if (mm->nav.altitude_source != NAV_ALT_INVALID) { printf(" Target altitude source: "); switch (mm->nav.altitude_source) { case NAV_ALT_AIRCRAFT: printf("aircraft altitude\n"); break; case NAV_ALT_MCP: printf("MCP selected altitude\n"); break; case NAV_ALT_FMS: printf("FMS selected altitude\n"); break; default: printf("unknown\n"); } } if (mm->nav.modes_valid) { printf(" Nav modes: %s\n", nav_modes_to_string(mm->nav.modes)); } if (mm->emergency_valid) { printf(" Emergency/priority: %s\n", emergency_to_string(mm->emergency)); } printf("\n"); fflush(stdout); } // // ===================== Mode S detection and decoding =================== // readsb-3.16/mode_s.h000066400000000000000000000071011505057307600143310ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // mode_s.h: Mode S message decoding (header) // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2017 FlightAware, LLC // Copyright (c) 2017 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #ifndef MODE_S_H #define MODE_S_H #include // // Functions exported from mode_s.c // int scoreModesMessage (unsigned char *msg, int validbits); int decodeModesMessage (struct modesMessage *mm); void displayModesMessage (struct modesMessage *mm); // datafield extraction helpers // The first bit (MSB of the first byte) is numbered 1, for consistency // with how the specs number them. // Extract one bit from a message. static inline __attribute__ ((always_inline)) unsigned getbit (unsigned char *data, unsigned bitnum) { unsigned bi = bitnum - 1; unsigned by = bi >> 3; unsigned mask = 1 << (7 - (bi & 7)); return (data[by] & mask) != 0; } // Extract some bits (firstbit .. lastbit inclusive) from a message. static inline __attribute__ ((always_inline)) unsigned getbits (unsigned char *data, unsigned firstbit, unsigned lastbit) { unsigned fbi = firstbit - 1; unsigned lbi = lastbit - 1; unsigned nbi = (lastbit - firstbit + 1); unsigned fby = fbi >> 3; unsigned lby = lbi >> 3; unsigned nby = (lby - fby) + 1; unsigned shift = 7 - (lbi & 7); unsigned topmask = 0xFF >> (fbi & 7); assert (fbi <= lbi); assert (nbi <= 32); assert (nby <= 5); if (nby == 5) { return ((data[fby] & topmask) << (32 - shift)) | (data[fby + 1] << (24 - shift)) | (data[fby + 2] << (16 - shift)) | (data[fby + 3] << (8 - shift)) | (data[fby + 4] >> shift); } else if (nby == 4) { return ((data[fby] & topmask) << (24 - shift)) | (data[fby + 1] << (16 - shift)) | (data[fby + 2] << (8 - shift)) | (data[fby + 3] >> shift); } else if (nby == 3) { return ((data[fby] & topmask) << (16 - shift)) | (data[fby + 1] << (8 - shift)) | (data[fby + 2] >> shift); } else if (nby == 2) { return ((data[fby] & topmask) << (8 - shift)) | (data[fby + 1] >> shift); } else if (nby == 1) { return (data[fby] & topmask) >> shift; } else { return 0; } } //========================================================================= // // Given the Downlink Format (DF) of the message, return the message length in bits. // // All known DF's 16 or greater are long. All known DF's 15 or less are short. // There are lots of unused codes in both category, so we can assume ICAO will stick to // these rules, meaning that the most significant bit of the DF indicates the length. // static inline int modesMessageLenByType(int type) { return (type & 0x10) ? MODES_LONG_MSG_BITS : MODES_SHORT_MSG_BITS; } #endif readsb-3.16/net_io.c000066400000000000000000006511001505057307600143370ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // net_io.c: network handling. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014-2016 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ais_charset.h" #include "readsb.h" #include #include #include #include #include #include #include "uat2esnt/uat2esnt.h" #define DLE 0x10 #define ETX 0x03 // ============================= Networking ============================= // // read_fn typedef read_handler functions static int handle_gpsd(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb); static int handleCommandSocket(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb); static int handleBeastCommand(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb); static int decodeBinMessage(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb); static int processHexMessage(struct client *c, char *hex, int remote, int64_t now, struct messageBuffer *mb); static int decodeUatMessage(struct client *c, char *msg, int remote, int64_t now, struct messageBuffer *mb); static int decodeSbsLine(struct client *c, char *line, int remote, int64_t now, struct messageBuffer *mb); static int decodeAsterixMessage(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb); static int decodeSbsLineMlat(struct client *c, char *line, int remote, int64_t now, struct messageBuffer *mb) { MODES_NOTUSED(remote); return decodeSbsLine(c, line, 64 + SOURCE_MLAT, now, mb); } static int decodeSbsLinePrio(struct client *c, char *line, int remote, int64_t now, struct messageBuffer *mb) { MODES_NOTUSED(remote); return decodeSbsLine(c, line, 64 + SOURCE_PRIO, now, mb); } static int decodeSbsLineJaero(struct client *c, char *line, int remote, int64_t now, struct messageBuffer *mb) { MODES_NOTUSED(remote); return decodeSbsLine(c, line, 64 + SOURCE_JAERO, now, mb); } static int decodePfMessage(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb); // end read handlers static void send_heartbeat(struct net_service *service); static void autoset_modeac(); static void *pthreadGetaddrinfo(void *param); static void modesCloseClient(struct client *c); static int flushClient(struct client *c, int64_t now); static char *read_uuid(struct client *c, char *p, char *eod); static void modesReadFromClient(struct client *c, struct messageBuffer *mb); static void drainMessageBuffer(struct messageBuffer *buf); // ModeAC all zero messag static const char beast_heartbeat_msg[] = {0x1a, '1', 0, 0, 0, 0, 0, 0, 0, 0, 0}; static const char raw_heartbeat_msg[] = "*0000;\n"; static const char sbs_heartbeat_msg[] = "\r\n"; // is there a better one? //static const char newline_heartbeat_msg[] = "\n"; // CAUTION: sizeof includes the trailing \0 byte static const heartbeat_t beast_heartbeat = { .msg = beast_heartbeat_msg, .len = sizeof(beast_heartbeat_msg) }; static const heartbeat_t raw_heartbeat = { .msg = raw_heartbeat_msg, .len = sizeof(raw_heartbeat_msg) - 1 }; static const heartbeat_t sbs_heartbeat = { .msg = sbs_heartbeat_msg, .len = sizeof(sbs_heartbeat_msg) - 1 }; static const heartbeat_t no_heartbeat = { .msg = NULL, .len = 0 }; /* static const heartbeat_t newline_heartbeat = { .msg = newline_heartbeat_msg, .len = sizeof(newline_heartbeat_msg) - 1 }; */ // //========================================================================= // // Networking "stack" initialization // // Init a service with the given read/write characteristics, return the new service. // Doesn't arrange for the service to listen or connect static struct net_service *serviceInit(struct net_service_group *group, const char *descr, struct net_writer *writer, heartbeat_t heartbeat_out, heartbeat_t heartbeat_in, read_mode_t mode, const char *sep, read_fn handler) { if (!descr) { fprintf(stderr, "Fatal: no service description\n"); exit(1); } if (!group->services) { group->alloc = NET_SERVICE_GROUP_MAX; group->services = cmalloc(group->alloc * sizeof(struct net_service)); } if (!group->services) { } group->len++; if (group->len + 1 > group->alloc) { fprintf(stderr, "FATAL: Increase NET_SERVICE_GROUP_MAX\n"); exit(1); } struct net_service *service = &group->services[group->len - 1]; memset(service, 0, 2 * sizeof(struct net_service)); // also set the extra service to zero, zero terminatd array service->group = group; service->descr = descr; service->listener_count = 0; service->pusher_count = 0; service->connections = 0; service->writer = writer; service->read_sep = sep; service->read_sep_len = sep ? strlen(sep) : 0; service->read_mode = mode; service->read_handler = handler; service->clients = NULL; service->heartbeat_out = heartbeat_out; service->heartbeat_in = heartbeat_in; if (service->writer) { if (service->writer->data) { fprintf(stderr, "FATAL: serviceInit() called twice on the same service: %s\n", descr); exit(1); } // set writer to zero memset(service->writer, 0, sizeof(struct net_writer)); service->writer->data = cmalloc(Modes.writerBufSize); service->writer->service = service; service->writer->dataUsed = 0; service->writer->lastWrite = mstime(); service->writer->lastReceiverId = 0; service->writer->connections = 0; if (service->writer == &Modes.beast_reduce_out) { service->writer->flushInterval = Modes.net_output_flush_interval_beast_reduce; } else { service->writer->flushInterval = Modes.net_output_flush_interval; } } return service; } static uint8_t char_to_ais(int ch) { char *match; if (!ch) return 32; match = strchr(ais_charset, ch); if (match) return (uint8_t)(match - ais_charset); else return 32; } static int sendFiveHeartbeats(struct client *c, int64_t now) { // only send 5 heartbeats for beast type output if (c->service->heartbeat_out.msg != beast_heartbeat.msg) { return 0; } //fprintf(stderr, "sending 5 hbs\n"); // send 5 heartbeats to signal that we are a client that can accomodate feedback .... some counterparts crash if they get stuff they don't understand // this is really a crutch, but there is no other good way to signal this without causing issues int repeats = 5; const char *heartbeat_msg = c->service->heartbeat_out.msg; int heartbeat_len = c->service->heartbeat_out.len; if (heartbeat_msg && c->sendq && c->sendq_len + repeats * heartbeat_len < c->sendq_max) { for (int k = 0; k < repeats; k++) { memcpy(c->sendq + c->sendq_len, heartbeat_msg, heartbeat_len); c->sendq_len += heartbeat_len; } } return flushClient(c, now); } static void setProxyString(struct client *c) { snprintf(c->proxy_string, sizeof(c->proxy_string), "%s port %s", c->host, c->port); if (!c->receiverIdLocked) { c->receiverId = fasthash64(c->proxy_string, strlen(c->proxy_string), 0x2127599bf4325c37ULL); } } static int getSNDBUF(struct net_service *service) { if (service->sendqOverrideSize) { return service->sendqOverrideSize; } else { return Modes.netBufSize; } } static int getRCVBUF(struct net_service *service) { if (service->recvqOverrideSize) { return service->recvqOverrideSize; } else { return Modes.netBufSize; } } static void setSockopts(struct client *c) { if (anetTcpNoDelay(Modes.aneterr, c->fd) != ANET_OK) { if (c->con) { fprintf(stderr, "%s: Unable to set TCP_NODELAY: connection to %s port %s ...\n", c->con->service->descr, c->con->address, c->con->port); } else { fprintf(stderr, "%s: Unable to set TCP_NODELAY on connection from %s local port %s\n", c->service->descr, c->host, c->port); } } if (anetTcpKeepAlive(Modes.aneterr, c->fd) != ANET_OK) { if (c->con) { fprintf(stderr, "%s: Unable to set keepalive: connection to %s port %s ...\n", c->con->service->descr, c->con->address, c->con->port); } else { fprintf(stderr, "%s: Unable to set keepalive on connection from %s local port %s\n", c->service->descr, c->host, c->port); } } if (Modes.tcpBuffersAuto) { return; } // explicitely setting tcp buffers causes failure of linux tcp window auto tuning // let's not dealy with this, set Modes.tcpBuffersAuto to 1 for the time being int sndsize = getSNDBUF(c->service); if (sndsize > 0 && setsockopt(c->fd, SOL_SOCKET, SO_SNDBUF, (void*)&sndsize, sizeof(sndsize)) == -1) { fprintf(stderr, "setsockopt SO_SNDBUF: %s", strerror(errno)); } if (0) { int rcvsize = getRCVBUF(c->service); // much better to just let the OS handle the receive buffer if (rcvsize > 0 && setsockopt(c->fd, SOL_SOCKET, SO_RCVBUF, (void*)&rcvsize, sizeof(rcvsize)) == -1) { fprintf(stderr, "setsockopt SO_RCVBUF: %s", strerror(errno)); } } } // Create a client attached to the given service using the provided socket FD ... not a socket in some exceptions static struct client *createSocketClient(struct net_service *service, int fd, char *uuid) { struct client *c; int64_t now = mstime(); if (!service || fd == -1) { fprintf(stderr, "<3> FATAL: createSocketClient called with invalid parameters!\n"); exit(1); } if (!(c = (struct client *) cmalloc(sizeof (struct client)))) { fprintf(stderr, "<3> FATAL: Out of memory allocating a new %s network client\n", service->descr); exit(1); } memset(c, 0, sizeof (struct client)); c->service = service; c->fd = fd; c->last_send = now; c->last_read = now; c->connectedSince = now; c->last_read_flush = now; c->proxy_string[0] = '\0'; c->host[0] = '\0'; c->port[0] = '\0'; c->receiverIdLocked = 0; c->receiverId2 = 0; if (uuid != NULL) { read_uuid(c, uuid, uuid + strlen(uuid)); if (c->receiverIdLocked) { //fprintf(stderr, "Using supplied uuid %s, c->receiverId: %016"PRIx64"\n", uuid, c->receiverId); } } else { c->receiverId = random(); c->receiverId <<= 22; c->receiverId ^= random(); c->receiverId <<= 22; c->receiverId ^= random(); //fprintf(stderr, "preliminary random uuid might be overwritten, c->receiverId: %016"PRIx64"\n", c->receiverId); } c->recent_rtt = -1; c->remote = 1; // Messages will be marked remote by default if ((c->fd == Modes.beast_fd) && (Modes.sdr_type == SDR_MODESBEAST || Modes.sdr_type == SDR_GNS)) { /* Message from a local connected Modes-S beast or GNS5894 are passed off the internet */ c->remote = 0; c->serial = 1; } //fprintf(stderr, "c->receiverId: %016"PRIx64"\n", c->receiverId); c->bufmax = Modes.netBufSize; if (service->recvqOverrideSize) { c->bufmax = service->recvqOverrideSize; } c->buf = cmalloc(c->bufmax); if (service->writer) { c->sendq_max = Modes.netBufSize; if (service->sendqOverrideSize) { c->sendq_max = service->sendqOverrideSize; } if (!(c->sendq = cmalloc(c->sendq_max))) { fprintf(stderr, "Out of memory allocating client SendQ\n"); exit(1); } service->writer->connections++; } service->connections++; Modes.modesClientCount++; c->next = service->clients; service->clients = c; if (Modes.debug_net && service->connections % 50 == 0) { fprintf(stderr, "%s connection count: %d\n", service->descr, service->connections); } if (service->writer && service->connections == 1) { service->writer->lastWrite = now; // suppress heartbeat initially } epoll_data_t data; data.ptr = c; c->epollEvent.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; c->epollEvent.data = data; if (epoll_ctl(Modes.net_epfd, EPOLL_CTL_ADD, c->fd, &c->epollEvent)) perror("epoll_ctl fail:"); return c; } static int sendUUID(struct client *c, int64_t now) { struct net_connector *con = c->con; // sending UUID for beast_reduce_plus output char uuid[150]; uuid[0] = '\0'; if ((c->sendq && c->sendq_len + 256 < c->sendq_max) && con && (con->enable_uuid_ping || Modes.debug_ping || Modes.debug_send_uuid)) { int res = -1; if (con->uuid) { strncpy(uuid, con->uuid, 135); res = strlen(uuid); } else if (Modes.uuidFile) { int fd = open(Modes.uuidFile, O_RDONLY); if (fd != -1) { res = read(fd, uuid, 128); close(fd); } } if (res >= 28) { if (uuid[res - 1] == '\n') { // remove trailing newline res--; } uuid[res] = '\0'; c->sendq[c->sendq_len++] = 0x1A; c->sendq[c->sendq_len++] = 0xE4; // uuid is padded with 'f', always send 36 chars memset(c->sendq + c->sendq_len, 'f', 36); strncpy(c->sendq + c->sendq_len, uuid, res); c->sendq_len += 36; } else { fprintf(stderr, "ERROR: Not a valid UUID: '%s' (to generate a valid uuid use this command: cat /proc/sys/kernel/random/uuid)\n", uuid); uuid[0] = '\0'; } // enable ping stuff // O for high resolution timer, both P and p already used for previous iterations c->sendq[c->sendq_len++] = 0x1a; c->sendq[c->sendq_len++] = 'W'; c->sendq[c->sendq_len++] = 'O'; return flushClient(c, now); } return -1; } static int suppressConnectError(struct net_connector *con) { uint32_t failLimit = 10; con->fail_counter += 1; // increment fail counter if (con->silent_fail) { return 1; } if (con->fail_counter < failLimit || Modes.debug_net) { return 0; } if (con->fail_counter == failLimit || con->fail_counter % 200 == 0) { fprintf(stderr, "%s: Connection to %s port %s failed %u times, suppressing most error messages until connection succeeds\n", con->service->descr, con->address, con->port, con->fail_counter); } return 1; } static void checkServiceConnected(struct net_connector *con, int64_t now) { if (!con->connecting) { return; } //fprintf(stderr, "checkServiceConnected fd: %d\n", con->fd); // delete dummyClient epollEvent for connection that is being established epoll_ctl(Modes.net_epfd, EPOLL_CTL_DEL, con->fd, &con->dummyClient.epollEvent); // we'll register new epollEvents in createSocketClient // At this point, we need to check getsockopt() to see if we succeeded or failed... int optval = -1; socklen_t optlen = sizeof(optval); if (getsockopt(con->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) { fprintf(stderr, "getsockopt failed: %d (%s)\n", errno, strerror(errno)); // Bad stuff going on, but clear this anyway con->connecting = 0; anetCloseSocket(con->fd); return; } if (optval != 0) { // only 0 means "connection ok" if (!suppressConnectError(con)) { fprintf(stderr, "%s: Connection to %s%s port %s failed (%u): %d (%s)\n", con->service->descr, con->address, con->resolved_addr, con->port, con->fail_counter, optval, strerror(optval)); } con->connecting = 0; anetCloseSocket(con->fd); return; } // If we're able to create this "client", save the sockaddr info and print a msg struct client *c; c = createSocketClient(con->service, con->fd, con->uuid); if (!c) { con->connecting = 0; fprintf(stderr, "createSocketClient failed on fd %d to %s%s port %s\n", con->fd, con->address, con->resolved_addr, con->port); anetCloseSocket(con->fd); return; } strncpy(c->host, con->address, sizeof(c->host) - 1); strncpy(c->port, con->port, sizeof(c->port) - 1); setProxyString(c); con->connecting = 0; con->connected = 1; con->lastConnect = now; // link connection and client so we have access from one to the other c->con = con; con->c = c; int uuid_sent = (sendUUID(c, now) == 0); sendFiveHeartbeats(c, now); if ((c->sendq && c->sendq_len + 256 < c->sendq_max) && strcmp(con->protocol, "gpsd_in") == 0) { if (Modes.debug_gps) { fprintTime(stderr, now); fprintf(stderr, " gpsdebug: sending \'?WATCH={\"enable\":true,\"json\":true};\\n\'\n"); } c->sendq_len += snprintf(c->sendq, 256, "?WATCH={\"enable\":true,\"json\":true};\n"); if (flushClient(c, now) < 0) { return; } } if (!Modes.interactive) { if (uuid_sent) { fprintf(stderr, "%s: Connection established: %s%s port %s (sent UUID)\n", con->service->descr, con->address, con->resolved_addr, con->port); } else { fprintf(stderr, "%s: Connection established: %s%s port %s\n", con->service->descr, con->address, con->resolved_addr, con->port); } } con->fail_counter = 0; // reset fail counter on successful connection } // Initiate an outgoing connection. static void serviceConnect(struct net_connector *con, int64_t now) { int fd; // make sure backoff is never too small con->backoff = imax(Modes.net_connector_delay_min, con->backoff); if (con->try_addr) { // iterate the address info linked list if we have one con->try_addr = con->try_addr->ai_next; } if (!con->try_addr) { // if ((!con->addr_info || now - con->lastResolve > 2 * Modes.net_connector_delay) && !con->gai_request_in_progress) { // keeping a DNS reply for any length of time without knowing lifetime is a bad idea // cause issues with recreating docker containers and connecting to the wrong container due // to the caching of the address info if (!con->gai_request_in_progress) { // launch a pthread for async getaddrinfo if (con->addr_info) { freeaddrinfo(con->addr_info); con->addr_info = NULL; } pthread_mutex_lock(&con->mutex); con->gai_request_done = 0; pthread_mutex_unlock(&con->mutex); if (0 && Modes.debug_net) { fprintf(stderr, "%s: calling getaddrinfo for %s port %s\n", con->service->descr, con->address, con->port); } if (pthread_create(&con->thread, NULL, pthreadGetaddrinfo, con)) { con->next_reconnect = now + Modes.net_connector_delay; fprintf(stderr, "%s: pthread_create ERROR for %s port %s: %s\n", con->service->descr, con->address, con->port, strerror(errno)); return; } con->gai_request_in_progress = 1; con->next_reconnect = now + 20; return; } if (con->gai_request_in_progress) { // gai request is in progress, let's check if it's done pthread_mutex_lock(&con->mutex); if (!con->gai_request_done) { con->next_reconnect = now + 20; pthread_mutex_unlock(&con->mutex); return; } pthread_mutex_unlock(&con->mutex); con->gai_request_in_progress = 0; // gai request is done, join the thread that performed it if (pthread_join(con->thread, NULL)) { fprintf(stderr, "%s: pthread_join ERROR for %s port %s: %s\n", con->service->descr, con->address, con->port, strerror(errno)); con->next_reconnect = now + Modes.net_connector_delay; return; } if (con->gai_error) { if (!con->silent_fail) { fprintf(stderr, "%s: Name resolution for %s failed: %s\n", con->service->descr, con->address, gai_strerror(con->gai_error)); } // limit name resolution attempts via backoff con->next_reconnect = now + con->backoff; con->backoff = imin(Modes.net_connector_delay, 2 * con->backoff); return; } con->lastResolve = now; // SUCCESS, we got the address info } // start with the first element of the linked list con->try_addr = con->addr_info; } // limit tcp connection attemtps via backoff con->next_reconnect = now + con->backoff; if (!con->try_addr->ai_next) { // only increase backoff once all DNS results have been tried con->backoff = imin(Modes.net_connector_delay, 2 * con->backoff); } struct timespec watch; startWatch(&watch); getnameinfo(con->try_addr->ai_addr, con->try_addr->ai_addrlen, con->resolved_addr, sizeof(con->resolved_addr) - 3, NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV); int64_t getnameinfoElapsed = lapWatch(&watch); if (getnameinfoElapsed > 1) { fprintf(stderr, "WARNING: getnameinfo() took %"PRId64" ms\n", getnameinfoElapsed); } if ( strcmp(con->resolved_addr, con->address) != 0 && ( strcmp(con->resolved_addr, "::") == 0 || strcmp(con->resolved_addr, "0.0.0.0") == 0 ) ) { fprintf(stderr, "%s: %s port %s: Ignoring this DNS reply: %s\n", con->service->descr, con->address, con->port, con->resolved_addr); return; } if (strcmp(con->resolved_addr, con->address) == 0) { con->resolved_addr[0] = '\0'; } else { char tmp[sizeof(con->resolved_addr)+3]; // shut up gcc snprintf(tmp, sizeof(tmp), " (%s)", con->resolved_addr); memcpy(con->resolved_addr, tmp, sizeof(con->resolved_addr)); } if (Modes.debug_net) { //fprintf(stderr, "%s: Attempting connection to %s port %s ... (gonna set SNDBUF %d RCVBUF %d)\n", con->service->descr, con->address, con->port, getSNDBUF(con->service), getRCVBUF(con->service)); fprintf(stderr, "%s: Attempting connection to %s%s port %s ...\n", con->service->descr, con->address, con->resolved_addr, con->port); } struct addrinfo *ai = con->try_addr; fd = anetCreateSocket(Modes.aneterr, ai->ai_family, SOCK_NONBLOCK); if (fd == ANET_ERR) { if (!suppressConnectError(con)) { fprintf(stderr, "%s: Connection to %s%s port %s failed: %s\n", con->service->descr, con->address, con->resolved_addr, con->port, Modes.aneterr); } return; } con->fd = fd; con->connecting = 1; con->connect_timeout = now + imin(Modes.net_connector_delay, 5000); // really if your connection won't establish after 5 seconds ... tough luck. //fprintf(stderr, "connect_timeout: %ld\n", (long) (con->connect_timeout - now)); // struct client for epoll purposes struct client *c = &con->dummyClient; c->service = con->service; c->fd = con->fd; c->net_connector_dummyClient = 1; c->epollEvent.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLERR | EPOLLHUP; c->epollEvent.data.ptr = c; c->con = con; if (epoll_ctl(Modes.net_epfd, EPOLL_CTL_ADD, c->fd, &c->epollEvent)) { perror("epoll_ctl fail:"); } setSockopts(c); if (connect(fd, ai->ai_addr, ai->ai_addrlen) < 0 && errno != EINPROGRESS) { epoll_ctl(Modes.net_epfd, EPOLL_CTL_DEL, con->fd, &con->dummyClient.epollEvent); con->connecting = 0; anetCloseSocket(con->fd); if (!suppressConnectError(con)) { fprintf(stderr, "%s: Connection to %s%s port %s failed: %s\n", con->service->descr, con->address, con->resolved_addr, con->port, strerror(errno)); } } } // Timer callback checking periodically whether the push service lost its server // connection and requires a re-connect. static void serviceReconnectCallback(int64_t now) { // Loop through the connectors, and // - If it's not connected: // - If it's "connecting", check to see if it timed out // - Otherwise, if enough time has passed, try reconnecting for (int i = 0; i < Modes.net_connectors_count; i++) { struct net_connector *con = &Modes.net_connectors[i]; if (!con->connected) { // If we've exceeded our connect timeout, close connection. if (con->connecting && now >= con->connect_timeout) { if (!suppressConnectError(con)) { fprintf(stderr, "%s: Connection to %s%s port %s timed out.\n", con->service->descr, con->address, con->resolved_addr, con->port); } con->connecting = 0; // delete dummyClient epollEvent for connection that is being established epoll_ctl(Modes.net_epfd, EPOLL_CTL_DEL, con->fd, &con->dummyClient.epollEvent); anetCloseSocket(con->fd); } //fprintf(stderr, "next_reconnect in: %lld\n", (long long) (con->next_reconnect - now)); if (!con->connecting && (now >= con->next_reconnect || Modes.synthetic_now)) { serviceConnect(con, now); } } else { // check for idle connection, this server version requires data // or a heartbeat, otherwise it will force a reconnect struct client *c = con->c; if (Modes.net_heartbeat_interval && c && now - c->last_read > 2 * Modes.net_heartbeat_interval && c->service->heartbeat_in.msg != NULL ) { fprintf(stderr, "%s: No data or heartbeat received for %.0f seconds, reconnecting: %s port %s\n", c->service->descr, (2 * Modes.net_heartbeat_interval) / 1000.0, c->host, c->port); modesCloseClient(c); } } } } // Set up the given service to listen on an address/port. // _exits_ on failure! void serviceListen(struct net_service *service, char *bind_addr, char *bind_ports, int epfd) { int *fds = NULL; int n = 0; char *p, *end; char buf[128]; if (service->listener_count > 0) { fprintf(stderr, "Tried to set up the service %s twice!\n", service->descr); exit(1); } if (!bind_ports || !strcmp(bind_ports, "") || !strcmp(bind_ports, "0")) return; if (0 && Modes.debug_net) { fprintf(stderr, "serviceListen: %s with SNDBUF %d RCVBUF %d)\n", service->descr, getSNDBUF(service), getRCVBUF(service)); } p = bind_ports; while (p && *p) { int newfds[16]; int nfds, i; int is_unix = 0; if (strncmp(p, "unix:", 5) == 0) { is_unix = 1; p += 5; } end = strpbrk(p, ", "); if (!end) { strncpy(buf, p, sizeof (buf) - 1); buf[sizeof (buf) - 1] = 0; p = NULL; } else { size_t len = end - p; if (len >= sizeof (buf)) len = sizeof (buf) - 1; memcpy(buf, p, len); buf[len] = 0; p = end + 1; } if (is_unix) { if (service->unixSocket) { fprintf(stderr, "Multiple unix sockets per service are not supported! %s (%s): %s\n", buf, service->descr, Modes.aneterr); exit(1); } sfree(service->unixSocket); service->unixSocket = strdup(buf); unlink(service->unixSocket); int fd = anetUnixSocket(Modes.aneterr, buf, SOCK_NONBLOCK); if (fd == ANET_ERR) { fprintf(stderr, "Error opening the listening port %s (%s): %s\n", buf, service->descr, Modes.aneterr); exit(1); } if (chmod(service->unixSocket, 0666) != 0) { perror("serviceListen: couldn't set permissions for unix socket due to:"); } newfds[0] = fd; nfds = 1; } else { //nfds = anetTcpServer(Modes.aneterr, buf, bind_addr, newfds, sizeof (newfds), SOCK_NONBLOCK, getSNDBUF(service), getRCVBUF(service)); // explicitely setting tcp buffers causes failure of linux tcp window auto tuning ... it just doesn't work well without the auto tuning nfds = anetTcpServer(Modes.aneterr, buf, bind_addr, newfds, sizeof (newfds), SOCK_NONBLOCK, -1, -1); if (nfds == ANET_ERR) { fprintf(stderr, "Error opening the listening port %s (%s): %s\n", buf, service->descr, Modes.aneterr); exit(1); } } char listenString[1024]; snprintf(listenString, 1023, "%5s: %s port", buf, service->descr); fprintf(stderr, "%-38s\n", listenString); fds = realloc(fds, (n + nfds) * sizeof (int)); if (!fds) { fprintf(stderr, "out of memory\n"); exit(1); } for (i = 0; i < nfds; ++i) { fds[n++] = newfds[i]; } } service->listener_count = n; service->listener_fds = fds; if (epfd >= 0) { service->listenSockets = cmalloc(service->listener_count * sizeof(struct client)); memset(service->listenSockets, 0, service->listener_count * sizeof(struct client)); for (int i = 0; i < service->listener_count; ++i) { // struct client for epoll purposes for each listen socket. struct client *c = &service->listenSockets[i]; c->service = service; c->fd = service->listener_fds[i]; c->acceptSocket = 1; c->epollEvent.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; c->epollEvent.data.ptr = c; if (epoll_ctl(epfd, EPOLL_CTL_ADD, c->fd, &c->epollEvent)) perror("epoll_ctl fail:"); } } } static void initMessageBuffers() { if (Modes.decodeThreads > 1) { pthread_mutex_init(&Modes.decodeLock, NULL); pthread_mutex_init(&Modes.trackLock, NULL); pthread_mutex_init(&Modes.outputLock, NULL); Modes.decodeTasks = allocate_task_group(Modes.decodeThreads); Modes.decodePool = threadpool_create(Modes.decodeThreads, 0); } Modes.netMessageBuffer = cmalloc(Modes.decodeThreads * sizeof(struct messageBuffer)); memset(Modes.netMessageBuffer, 0x0, Modes.decodeThreads * sizeof(struct messageBuffer)); for (int k = 0; k < Modes.decodeThreads; k++) { struct messageBuffer *buf = &Modes.netMessageBuffer[k]; buf->alloc = 256 << Modes.net_sndbuf_size; buf->len = 0; buf->id = k; buf->activeClient = NULL; int bytes = buf->alloc * sizeof(struct modesMessage); buf->msg = cmalloc(bytes); //fprintf(stderr, "netMessageBuffer alloc: %d size: %d\n", buf->alloc, bytes); } } void modesInitNet(void) { initMessageBuffers(); uat2esnt_initCrcTables(); if (0) { char *msg[4] = { "-00a974f135362f522fc408c9122e1b015900;", "-08a78bea35705f5283880459010227605809e00d40a2040be2a5c2a00004a0000000;rs=2;", "-10ad7233358a9d528bc40aa900be3120880000000000000000000000000b10000000;rs=4;", "-10a78bea3570b152830c0449010626e04800000000000000000000000004a0000000;rs=2;" }; for (int i = 0; i < 4; i++) { char output[2048]; uat2esnt_convert_message(msg[i], msg[i] + strlen(msg[i]), output, output + sizeof(output)); fprintf(stderr, "%s\n", output); } exit(1); } Modes.net_connector_delay_min = imax(50, Modes.net_connector_delay / 64); Modes.last_connector_fail = Modes.next_reconnect_callback = mstime(); if (!Modes.net) return; struct net_service *beast_out; struct net_service *beast_reduce_out; struct net_service *garbage_out; struct net_service *uat_replay_service; struct net_service *raw_out; struct net_service *raw_in; struct net_service *vrs_out; struct net_service *json_out; struct net_service *feedmap_out; struct net_service *sbs_out; struct net_service *sbs_out_replay; struct net_service *sbs_out_mlat; struct net_service *sbs_out_jaero; struct net_service *sbs_out_prio; struct net_service *asterix_out; struct net_service *asterix_in; struct net_service *sbs_in; struct net_service *sbs_in_mlat; struct net_service *sbs_in_jaero; struct net_service *sbs_in_prio; struct net_service *gpsd_in; struct net_service *planefinder_in; signal(SIGPIPE, SIG_IGN); Modes.net_epfd = my_epoll_create(&Modes.exitNowEventfd); // set up listeners raw_out = serviceInit(&Modes.services_out, "Raw TCP output", &Modes.raw_out, raw_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); serviceListen(raw_out, Modes.net_bind_address, Modes.net_output_raw_ports, Modes.net_epfd); uat_replay_service = serviceInit(&Modes.services_out, "UAT TCP replay output", &Modes.uat_replay_out, no_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); serviceListen(uat_replay_service, Modes.net_bind_address, Modes.net_output_uat_replay_ports, Modes.net_epfd); beast_out = serviceInit(&Modes.services_out, "Beast TCP output", &Modes.beast_out, beast_heartbeat, no_heartbeat, READ_MODE_BEAST_COMMAND, NULL, handleBeastCommand); serviceListen(beast_out, Modes.net_bind_address, Modes.net_output_beast_ports, Modes.net_epfd); beast_reduce_out = serviceInit(&Modes.services_out, "BeastReduce TCP output", &Modes.beast_reduce_out, beast_heartbeat, no_heartbeat, READ_MODE_BEAST_COMMAND, NULL, handleBeastCommand); serviceListen(beast_reduce_out, Modes.net_bind_address, Modes.net_output_beast_reduce_ports, Modes.net_epfd); garbage_out = serviceInit(&Modes.services_out, "Garbage TCP output", &Modes.garbage_out, beast_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); serviceListen(garbage_out, Modes.net_bind_address, Modes.garbage_ports, Modes.net_epfd); vrs_out = serviceInit(&Modes.services_out, "VRS json output", &Modes.vrs_out, no_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); serviceListen(vrs_out, Modes.net_bind_address, Modes.net_output_vrs_ports, Modes.net_epfd); json_out = serviceInit(&Modes.services_out, "Position json output", &Modes.json_out, no_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); serviceListen(json_out, Modes.net_bind_address, Modes.net_output_json_ports, Modes.net_epfd); feedmap_out = serviceInit(&Modes.services_out, "Forward feed map data", &Modes.feedmap_out, no_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); sbs_out = serviceInit(&Modes.services_out, "SBS TCP output ALL", &Modes.sbs_out, sbs_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); serviceListen(sbs_out, Modes.net_bind_address, Modes.net_output_sbs_ports, Modes.net_epfd); sbs_out_replay = serviceInit(&Modes.services_out, "SBS TCP output MAIN", &Modes.sbs_out_replay, sbs_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); sbs_out_prio = serviceInit(&Modes.services_out, "SBS TCP output PRIO", &Modes.sbs_out_prio, sbs_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); sbs_out_mlat = serviceInit(&Modes.services_out, "SBS TCP output MLAT", &Modes.sbs_out_mlat, sbs_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); sbs_out_jaero = serviceInit(&Modes.services_out, "SBS TCP output JAERO", &Modes.sbs_out_jaero, sbs_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); serviceListen(sbs_out_jaero, Modes.net_bind_address, Modes.net_output_jaero_ports, Modes.net_epfd); int sbs_port_len = strlen(Modes.net_output_sbs_ports); int pos = sbs_port_len - 1; if (sbs_port_len <= 5 && Modes.net_output_sbs_ports[pos] == '5') { char *replay = strdup(Modes.net_output_sbs_ports); replay[pos] = '6'; serviceListen(sbs_out_replay, Modes.net_bind_address, replay, Modes.net_epfd); char *mlat = strdup(Modes.net_output_sbs_ports); mlat[pos] = '7'; serviceListen(sbs_out_mlat, Modes.net_bind_address, mlat, Modes.net_epfd); char *prio = strdup(Modes.net_output_sbs_ports); prio[pos] = '8'; serviceListen(sbs_out_prio, Modes.net_bind_address, prio, Modes.net_epfd); char *jaero = strdup(Modes.net_output_sbs_ports); jaero[pos] = '9'; if (sbs_out_jaero->listener_count == 0) serviceListen(sbs_out_jaero, Modes.net_bind_address, jaero, Modes.net_epfd); sfree(replay); sfree(mlat); sfree(prio); sfree(jaero); } sbs_in = serviceInit(&Modes.services_in, "SBS TCP input MAIN", NULL, no_heartbeat, sbs_heartbeat, READ_MODE_ASCII, "\n", decodeSbsLine); serviceListen(sbs_in, Modes.net_bind_address, Modes.net_input_sbs_ports, Modes.net_epfd); sbs_in_mlat = serviceInit(&Modes.services_in, "SBS TCP input MLAT", NULL, no_heartbeat, sbs_heartbeat, READ_MODE_ASCII, "\n", decodeSbsLineMlat); sbs_in_prio = serviceInit(&Modes.services_in, "SBS TCP input PRIO", NULL, no_heartbeat, sbs_heartbeat, READ_MODE_ASCII, "\n", decodeSbsLinePrio); sbs_in_jaero = serviceInit(&Modes.services_in, "SBS TCP input JAERO", NULL, no_heartbeat, sbs_heartbeat, READ_MODE_ASCII, "\n", decodeSbsLineJaero); serviceListen(sbs_in_jaero, Modes.net_bind_address, Modes.net_input_jaero_ports, Modes.net_epfd); sbs_port_len = strlen(Modes.net_input_sbs_ports); pos = sbs_port_len - 1; if (sbs_port_len <= 5 && Modes.net_input_sbs_ports[pos] == '6') { char *mlat = strdup(Modes.net_input_sbs_ports); mlat[pos] = '7'; serviceListen(sbs_in_mlat, Modes.net_bind_address, mlat, Modes.net_epfd); char *prio = strdup(Modes.net_input_sbs_ports); prio[pos] = '8'; serviceListen(sbs_in_prio, Modes.net_bind_address, prio, Modes.net_epfd); char *jaero = strdup(Modes.net_input_sbs_ports); jaero[pos] = '9'; if (sbs_in_jaero->listener_count == 0) serviceListen(sbs_in_jaero, Modes.net_bind_address, jaero, Modes.net_epfd); sfree(mlat); sfree(prio); sfree(jaero); } asterix_out = serviceInit(&Modes.services_out, "ASTERIX output", &Modes.asterix_out, no_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); serviceListen(asterix_out, Modes.net_bind_address, Modes.net_output_asterix_ports, Modes.net_epfd); asterix_in = serviceInit(&Modes.services_in, "ASTERIX TCP input", NULL, no_heartbeat, no_heartbeat, READ_MODE_ASTERIX, NULL, decodeAsterixMessage); serviceListen(asterix_in, Modes.net_bind_address, Modes.net_input_asterix_ports, Modes.net_epfd); gpsd_in = serviceInit(&Modes.services_in, "GPSD TCP input", &Modes.gpsd_in, no_heartbeat, no_heartbeat, READ_MODE_ASCII, "\n", handle_gpsd); if (Modes.json_dir && Modes.json_globe_index && Modes.globe_history_dir) { /* command input */ struct net_service *commandService = serviceInit(&Modes.services_in, "command input", NULL, no_heartbeat, no_heartbeat, READ_MODE_ASCII, "\n", handleCommandSocket); char commandSocketFile[PATH_MAX]; char commandSocket[PATH_MAX]; snprintf(commandSocketFile, PATH_MAX, "%s/cmd.sock", Modes.json_dir); unlink(commandSocketFile); snprintf(commandSocket, PATH_MAX, "unix:%s/cmd.sock", Modes.json_dir); serviceListen(commandService, Modes.net_bind_address, commandSocket, Modes.net_epfd); chmod(commandSocket, 0600); } raw_in = serviceInit(&Modes.services_in, "Raw TCP input", NULL, no_heartbeat, raw_heartbeat, READ_MODE_ASCII, "\n", processHexMessage); serviceListen(raw_in, Modes.net_bind_address, Modes.net_input_raw_ports, Modes.net_epfd); /* Beast input via network */ Modes.beast_in_service = serviceInit(&Modes.services_in, "Beast TCP input", &Modes.beast_in, no_heartbeat, beast_heartbeat, READ_MODE_BEAST, NULL, decodeBinMessage); if (Modes.netIngest) { Modes.beast_in_service->sendqOverrideSize = 2 * 1024; Modes.beast_in_service->recvqOverrideSize = 4 * 1024; // --net-buffer won't increase receive buffer for ingest server to avoid running out of memory using lots of connections } serviceListen(Modes.beast_in_service, Modes.net_bind_address, Modes.net_input_beast_ports, Modes.net_epfd); /* Planefinder input via network */ planefinder_in = serviceInit(&Modes.services_in, "Planefinder TCP input", NULL, no_heartbeat, no_heartbeat, READ_MODE_PLANEFINDER, NULL, decodePfMessage); serviceListen(planefinder_in, Modes.net_bind_address, Modes.net_input_planefinder_ports, Modes.net_epfd); Modes.uat_in_service = serviceInit(&Modes.services_in, "UAT TCP input", NULL, no_heartbeat, no_heartbeat, READ_MODE_ASCII, "\n", decodeUatMessage); serviceListen(Modes.uat_in_service, Modes.net_bind_address, Modes.net_input_uat_ports, Modes.net_epfd); for (int i = 0; i < Modes.net_connectors_count; i++) { struct net_connector *con = &Modes.net_connectors[i]; if (strcmp(con->protocol, "beast_out") == 0) con->service = beast_out; else if (strcmp(con->protocol, "beast_in") == 0) con->service = Modes.beast_in_service; else if (strcmp(con->protocol, "beast_reduce_out") == 0) con->service = beast_reduce_out; else if (strcmp(con->protocol, "beast_reduce_plus_out") == 0) { con->service = beast_reduce_out; con->enable_uuid_ping = 1; } else if (strcmp(con->protocol, "raw_out") == 0) con->service = raw_out; else if (strcmp(con->protocol, "raw_in") == 0) con->service = raw_in; else if (strcmp(con->protocol, "planefinder_in") == 0) con->service = planefinder_in; else if (strcmp(con->protocol, "vrs_out") == 0) con->service = vrs_out; else if (strcmp(con->protocol, "json_out") == 0) con->service = json_out; else if (strcmp(con->protocol, "feedmap_out") == 0) con->service = feedmap_out; else if (strcmp(con->protocol, "sbs_out") == 0) con->service = sbs_out; else if (strcmp(con->protocol, "asterix_out") == 0) con->service = asterix_out; else if (strcmp(con->protocol, "asterix_in") == 0) con->service = asterix_in; else if (strcmp(con->protocol, "sbs_in") == 0) con->service = sbs_in; else if (strcmp(con->protocol, "sbs_in_mlat") == 0) con->service = sbs_in_mlat; else if (strcmp(con->protocol, "sbs_in_jaero") == 0) con->service = sbs_in_jaero; else if (strcmp(con->protocol, "sbs_in_prio") == 0) con->service = sbs_in_prio; else if (strcmp(con->protocol, "sbs_out_mlat") == 0) con->service = sbs_out_mlat; else if (strcmp(con->protocol, "sbs_out_jaero") == 0) con->service = sbs_out_jaero; else if (strcmp(con->protocol, "sbs_out_prio") == 0) con->service = sbs_out_prio; else if (strcmp(con->protocol, "sbs_out_replay") == 0) con->service = sbs_out_replay; else if (strcmp(con->protocol, "gpsd_in") == 0) con->service = gpsd_in; else if (strcmp(con->protocol, "uat_in") == 0) con->service = Modes.uat_in_service; else if (strcmp(con->protocol, "uat_replay_out") == 0) con->service = uat_replay_service; } if (Modes.dump_beast_dir) { int res = mkdir(Modes.dump_beast_dir, 0755); if (res != 0 && errno != EEXIST) { perror("issue creating dump-beast-dir"); } else { Modes.dump_fw = createZstdFw(4 * 1024 * 1024); Modes.dump_beast_index = -1; dump_beast_check(mstime()); } } } // //========================================================================= // Accept new connections static void modesAcceptClients(struct client *c, int64_t now) { if (!c || !c->acceptSocket) return; int listen_fd = c->fd; struct net_service *s = c->service; struct sockaddr_storage storage; struct sockaddr *saddr = (struct sockaddr *) &storage; socklen_t slen = sizeof(storage); int fd; errno = 0; while ((fd = anetGenericAccept(Modes.aneterr, listen_fd, saddr, &slen, SOCK_NONBLOCK)) >= 0) { if (Modes.modesClientCount > Modes.max_fds_net) { // drop new modes clients if the count gets near resource limits anetCloseSocket(c->fd); static int64_t antiSpam; if (now > antiSpam) { antiSpam = now + 30 * SECONDS; fprintf(stderr, "<3> Can't accept new connection, limited to %d clients, consider increasing ulimit!\n", Modes.max_fds_net); } } c = createSocketClient(s, fd, NULL); if (s->unixSocket && c) { strcpy(c->host, s->unixSocket); fprintf(stderr, "%s: new c at %s\n", c->service->descr, s->unixSocket); } else if (c) { // We created the client, save the sockaddr info and 'hostport' getnameinfo(saddr, slen, c->host, sizeof(c->host), c->port, sizeof(c->port), NI_NUMERICHOST | NI_NUMERICSERV); setProxyString(c); if (Modes.debug_net && (!Modes.netIngest || c->service->group == &Modes.services_out)) { fprintf(stderr, "%s: new c from %s port %s (fd %d)\n", c->service->descr, c->host, c->port, fd); } } else { fprintf(stderr, "%s: Fatal: createSocketClient shouldn't fail!\n", s->descr); exit(1); } setSockopts(c); sendFiveHeartbeats(c, now); } if (errno != EMFILE && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { fprintf(stderr, "%s: Error accepting new connection: %s\n", s->descr, Modes.aneterr); } } // //========================================================================= // // On error free the client, collect the structure, adjust maxfd if needed. // static void modesCloseClient(struct client *c) { if (!c->service) { fprintf(stderr, "warning: double close of net client\n"); return; } if (Modes.netIngest || Modes.netReceiverId || Modes.debug_no_discard) { double elapsed = (mstime() - c->connectedSince + 1) / 1000.0; double kbitpersecond = c->bytesReceived / 128.0 / elapsed; char uuid[64]; // needs 36 chars and null byte sprint_uuid(c->receiverId, c->receiverId2, uuid); fprintf(stderr, "disc: %s %6.1f s %6.2f kbit/s rId %s %s\n", (c->rtt > PING_DISCONNECT) ? "RTT " : ((c->garbage >= GARBAGE_THRESHOLD) ? "garb." : " "), elapsed, kbitpersecond, uuid, c->proxy_string); } epoll_ctl(Modes.net_epfd, EPOLL_CTL_DEL, c->fd, &c->epollEvent); if (c->serial) { if (close(c->fd) < 0) { fprintf(stderr, "Serial client close error: %s\n", strerror(errno)); } } else { anetCloseSocket(c->fd); } c->service->connections--; Modes.modesClientCount--; if (c->service->writer) { c->service->writer->connections--; } struct net_connector *con = c->con; if (con) { int64_t now = mstime(); // Clean this up and set the next_reconnect timer for another try. con->connecting = 0; con->connected = 0; con->c = NULL; int64_t sinceLastConnect = now - con->lastConnect; if (sinceLastConnect > Modes.net_connector_delay) { // reset backoff con->backoff = Modes.net_connector_delay_min; } // force fresh DNS lookup on disconnect if connected for more than 5 seconds if (sinceLastConnect > 1 * SECONDS) { con->try_addr = NULL; } con->next_reconnect = con->lastConnect + con->backoff; Modes.next_reconnect_callback = imin(Modes.next_reconnect_callback, con->next_reconnect); Modes.last_connector_fail = now; } // mark it as inactive and ready to be freed c->fd = -1; c->service = NULL; c->modeac_requested = 0; if (Modes.mode_ac_auto) autoset_modeac(); } static void lockReceiverId(struct client *c) { if (c->receiverIdLocked) return; c->receiverIdLocked = 1; if (Modes.netIngest && Modes.debug_net && c->garbage < 50) { char uuid[64]; // needs 36 chars and null byte sprint_uuid(c->receiverId, c->receiverId2, uuid); fprintf(stderr, "new client: rId %s %s\n", uuid, c->proxy_string); } } static inline uint32_t readPingEscaped(char *p) { unsigned char *pu = (unsigned char *) p; uint32_t res = 0; res += *pu++ << 16; if (*pu == 0x1a) pu++; res += *pu++ << 8; if (*pu == 0x1a) pu++; res += *pu++; if (*pu == 0x1a) pu++; if (0 && Modes.debug_ping) fprintf(stderr, "readPing: %d\n", res); return res; } static inline uint32_t readPing(char *p) { unsigned char *pu = (unsigned char *) p; uint32_t res = 0; res += *pu++ << 16; res += *pu++ << 8; res += *pu++; if (0 && Modes.debug_ping) fprintf(stderr, "readPing: %d\n", res); return res; } static void pingClient(struct client *c, uint32_t ping) { // truncate to 24 bit for clarity ping = ping & ((1 << 24) - 1); if (c->sendq_len + 8 >= c->sendq_max) return; char *p = c->sendq + c->sendq_len; *p++ = 0x1a; *p++ = 'P'; *p++ = (uint8_t) (ping >> 16); if (*(p-1) == 0x1a) *p++ = 0x1a; *p++ = (uint8_t) (ping >> 8); if (*(p-1) == 0x1a) *p++ = 0x1a; *p++ = (uint8_t) (ping >> 0); if (*(p-1) == 0x1a) *p++ = 0x1a; c->sendq_len = p - c->sendq; if (0 && Modes.debug_ping) fprintf(stderr, "Sending Ping c: %d\n", ping); } static void pong(struct client *c, int64_t now) { if (now < c->pingReceived) fprintf(stderr, "WAT?! now < c->pingReceived\n"); pingClient(c, c->ping + (now - c->pingReceived)); } static void pingSenders(struct net_service *service, int64_t now) { if (!Modes.ping) return; uint32_t newPing = now & ((1 << 24) - 1); // only send a ping every 50th interval or every 5 seconds // the respoder will interpolate using the local clock if (newPing >= Modes.currentPing + 5000 || newPing < Modes.currentPing) { Modes.currentPing = newPing; if (Modes.debug_ping) fprintf(stderr, "Sending Ping: %d\n", newPing); for (struct client *c = service->clients; c; c = c->next) { if (!c->service) continue; // some devices can't deal with any data on the backchannel // for the time being only send to receivers that enable it on connect if (Modes.netIngest && !c->pingEnabled) continue; if (!c->pongReceived && now > c->connectedSince + 20 * SECONDS) continue; pingClient(c, newPing); if (flushClient(c, now) < 0) { continue; } } } } static int pongReceived(struct client *c, int64_t now) { c->pongReceived = now; static int64_t antiSpam; // times in milliseconds int64_t pong = c->pong; int64_t current = now & ((1LL << 24) - 1); // handle 24 bit overflow by making the 2 numbers comparable if (labs((long) (current - pong)) > (1LL << 24) * 7 / 8) { if (current < pong) { current += (1 << 24); } else { pong += (1 << 24); } } if (current < pong) { // even without overflow, current can be smaller than pong due to // the other clock ticking up a ms just after receiving the ping (with sub ms latency) // but this clock ticked up just before sending the ping // other clock anomalies can make this happen as well, // even when debugging let's not log it unless the it's more than 3 ms if (Modes.debug_ping && pong - current > 3 && now > antiSpam) { antiSpam = now + 100; fprintf(stderr, "pongReceived strange: current < pong by %ld\n", (long) (pong - current)); } } c->rtt = current - pong; if (c->rtt < 0) { c->rtt = 0; } int32_t bucket = 0; float bucketsize = PING_BUCKETBASE; float bucketmax = 0; for (int i = 0; i < PING_BUCKETS; i++) { bucketmax += bucketsize; bucketmax = nearbyint(bucketmax / 10) * 10; bucketsize *= PING_BUCKETMULT; bucket = i; if (c->rtt <= bucketmax) { break; } } Modes.stats_current.remote_ping_rtt[bucket]++; // more quickly arrive at a sensible average if (c->recent_rtt <= 0) { c->recent_rtt = c->rtt; } else if (c->bytesReceived < 5000) { c->recent_rtt = c->recent_rtt * 0.9 + c->rtt * 0.1; } else { c->recent_rtt = c->recent_rtt * 0.995 + c->rtt * 0.005; } if (c->latest_rtt <= 0) { c->latest_rtt = c->rtt; } else { c->latest_rtt = c->latest_rtt * 0.9 + c->rtt * 0.1; } if (Modes.debug_ping && 0) { char uuid[64]; // needs 36 chars and null byte sprint_uuid(c->receiverId, c->receiverId2, uuid); fprintf(stderr, "rId %s %ld %4.0f %s current: %ld pong: %ld\n", uuid, (long) c->rtt, c->recent_rtt, c->proxy_string, (long) current, (long) pong); } // only log if the average is greater the rejection threshold, don't log for single packet events // actual discard / rejection happens elsewhere int the code if (c->rtt > Modes.ping_reject && now > antiSpam) { char uuid[64]; // needs 36 chars and null byte sprint_uuid(c->receiverId, c->receiverId2, uuid); if (Modes.debug_nextra) { antiSpam = now + 250; // limit to 4 messages a second } else { antiSpam = now + 30 * SECONDS; } if (Modes.netIngest) { fprintf(stderr, "reject: %6.0fms %6.0fms %6.0fms rId %s %s\n", (double) c->rtt, c->latest_rtt, c->recent_rtt, uuid, c->proxy_string); } else { fprintf(stderr, "high network delay: %6lld ms; discarding data: %s rId %s\n", (long long) c->rtt, c->proxy_string, uuid); } } if (Modes.netIngest && c->latest_rtt > Modes.ping_reduce) { // tell the client to slow down via beast command // misuse pingReceived as a timeout variable if (now > c->pingReceived + PING_REDUCE_DURATION / 2) { if (Modes.debug_nextra && now > antiSpam) { antiSpam = now + 250; // limit to 4 messages a second char uuid[64]; // needs 36 chars and null byte sprint_uuid(c->receiverId, c->receiverId2, uuid); fprintf(stderr, "reduce: %6.0f ms %6.0f ms rId %s %s\n", c->latest_rtt, c->recent_rtt, uuid, c->proxy_string); } if (c->sendq_len + 3 < c->sendq_max) { c->sendq[c->sendq_len++] = 0x1a; c->sendq[c->sendq_len++] = 'W'; c->sendq[c->sendq_len++] = 'S'; c->pingReceived = now; } if (flushClient(c, now) < 0) { return 1; } } } if (Modes.netIngest && c->rtt > PING_DISCONNECT) { return 1; // disconnect the client if the messages are delayed too much } return 0; } static void dropHalfUntil(int64_t now, struct client *c, int64_t until) { if ( now > c->dropHalfAntiSpam && (now - c->connectedSince > 10 * SECONDS || Modes.debug_net || Modes.debug_flush) ) { int suppress; if (Modes.debug_flush) { suppress = 2; } else if (Modes.debug_net) { suppress = 10; } else { suppress = 300; } c->dropHalfAntiSpam = now + suppress * SECONDS; double lostPercent = 100.0 - (double) c->bytesSent / (double) c->bytesFromWriter * 100.0; fprintf(stderr, "%s: %s port %s: Bad connection, dropping data" " (connection lost %4.1f%% of data) (suppress msg for %d s)\n", c->service->descr, c->host, c->port, lostPercent, suppress); } c->dropHalfUntil = until; c->dropHalfDrop = 1; } static int flushClient(struct client *c, int64_t now) { if (!c->service) { fprintf(stderr, "report error: Ahlu8pie\n"); return -1; } int toWrite = c->sendq_len; if (toWrite == 0) { return 0; } int bytesWritten = send(c->fd, c->sendq, toWrite, 0); int err = errno; // If we get -1, it's only fatal if it's not EAGAIN/EWOULDBLOCK if (bytesWritten < 0) { if (err != EAGAIN && err != EWOULDBLOCK) { fprintf(stderr, "%s: Send Error: %s: %s port %s (fd %d, SendQ %d, RecvQ %d)\n", c->service->descr, strerror(err), c->host, c->port, c->fd, c->sendq_len, c->buflen); modesCloseClient(c); return -1; } } if (bytesWritten < toWrite) { dropHalfUntil(now, c, now + 2 * SECONDS); } if (bytesWritten > toWrite) { fprintf(stderr, "%s: send() weirdness: bytesWritten > toWrite: %s: %s port %s (fd %d, SendQ %d, RecvQ %d)\n", c->service->descr, strerror(err), c->host, c->port, c->fd, c->sendq_len, c->buflen); modesCloseClient(c); return -1; } if (0 && bytesWritten < toWrite && Modes.debug_flush) { fprintTimePrecise(stderr, now); fprintf(stderr, " %s: send wrote: %d/%d bytes (%s port %s fd %d, SendQ %d)\n", c->service->descr, bytesWritten, toWrite, c->host, c->port, c->fd, c->sendq_len); } if (bytesWritten > 0) { Modes.stats_current.network_bytes_out += bytesWritten; // Advance buffer toWrite -= bytesWritten; c->sendq_len -= bytesWritten; c->last_send = now; // If we wrote anything, update this. if (toWrite > 0) { memmove((void*)c->sendq, c->sendq + bytesWritten, toWrite); } } if (toWrite > 0 && !(c->epollEvent.events & EPOLLOUT)) { // if we couldn't flush our buffer, make epoll tell us when we can write again c->epollEvent.events |= EPOLLOUT; if (epoll_ctl(Modes.net_epfd, EPOLL_CTL_MOD, c->fd, &c->epollEvent)) perror("epoll_ctl fail:"); } if (toWrite == 0 && (c->epollEvent.events & EPOLLOUT)) { // if set, remove EPOLLOUT from epoll if flush was successful c->epollEvent.events ^= EPOLLOUT; if (epoll_ctl(Modes.net_epfd, EPOLL_CTL_MOD, c->fd, &c->epollEvent)) perror("epoll_ctl fail:"); } // if we haven't been able to send any data on this connection for 2 seconds, drop it int64_t sendTimeout = 5 * SECONDS; if (now - c->last_send > sendTimeout && now - c->connectedSince > 15 * SECONDS) { fprintf(stderr, "%s: Couldn't send any data for %.2fs (Insufficient bandwidth?): disconnecting: %s port %s (fd %d, SendQ %d)\n", c->service->descr, sendTimeout / 1000.0, c->host, c->port, c->fd, c->sendq_len); modesCloseClient(c); return -1; } return bytesWritten; } // //========================================================================= // // Send the write buffer for the specified writer to all connected clients // static void flushWrites(struct net_writer *writer) { if (writer->dataUsed == 0) { return; } int64_t now = mstime(); if (0 && Modes.debug_flush) { fprintTimePrecise(stderr, now); fprintf(stderr, " %s: flushWrites %5d bytes\n", writer->service->descr, writer->dataUsed); } for (struct client *c = writer->service->clients; c; c = c->next) { if (!c->service) continue; if (c->service->writer == writer->service->writer) { if (c->pingEnabled) { pong(c, now); } if (writer->dataUsed > c->sendq_max) { fprintf(stderr, "%s: ERROR: dataUsed > sendq_max: report this bug!\n", c->service->descr); continue; } c->bytesFromWriter += writer->dataUsed; int bufferInsufficient = (c->sendq_len + writer->dataUsed > c->sendq_max); if (bufferInsufficient) { dropHalfUntil(now, c, now + 2 * SECONDS); } if ((c->dropHalfUntil > now && c->dropHalfDrop) || bufferInsufficient) { // drop this chunk of data } else { // Append the data to the end of the queue, increment len memcpy(c->sendq + c->sendq_len, writer->data, writer->dataUsed); c->sendq_len += writer->dataUsed; c->bytesSent += writer->dataUsed; if (0) { double lostPercent = 100.0 - (double) c->bytesSent / (double) c->bytesFromWriter * 100.0; fprintf(stderr, "%s: %s port %s: lost %4.1f%% of data\n", c->service->descr, c->host, c->port, lostPercent); } } if (c->dropHalfUntil > now) { c->dropHalfDrop = !c->dropHalfDrop; } // Try flushing... if (flushClient(c, now) < 0) { continue; } if (!c->service) { continue; } } } writer->lastReceiverId = 0; // unconditionally emit receiver id on start of new "packet" writer->dataUsed = 0; writer->lastWrite = now; return; } // Prepare to write up to 'len' bytes to the given net_writer. // Returns a pointer to write to, or NULL to skip this write. static void *prepareWrite(struct net_writer *writer, int len) { if (!writer->connections) { return NULL; } if (writer->dataUsed && writer->dataUsed + len > Modes.net_output_flush_size) { flushWrites(writer); } if (writer->dataUsed + len > Modes.writerBufSize) { // this shouldn't happen, flushWrites never fails and writerBufSize // must be larger than the output_flush_size fprintf(stderr, "%s: prepareWrite: not enough space in writer buffer, requested len: %d, already in buffer: %d\n", writer->service->descr, len, writer->dataUsed); return NULL; } //fprintf(stderr, "%s: requested len: %d, dataUsed: %d, bufSize: %d\n", writer->service->descr, len, writer->dataUsed, Modes.writerBufSize); return writer->data + writer->dataUsed; } // Complete a write previously begun by prepareWrite. // endptr should point one byte past the last byte written // to the buffer returned from prepareWrite. static void completeWrite(struct net_writer *writer, void *endptr) { if (writer->dataUsed == 0 && endptr - writer->data > 0) { int64_t now = mstime(); if (0 && Modes.debug_flush) { fprintTimePrecise(stderr, now); fprintf(stderr, " completeWrite starting packet for %s\n", writer->service->descr); } writer->nextFlush = now + writer->flushInterval; } writer->dataUsed = endptr - writer->data; if (writer->dataUsed > Modes.writerBufSize) { fprintf(stderr, "%s: ERROR: overflow: dataUsed: %d, bufSize: %d\n", writer->service->descr, writer->dataUsed, Modes.writerBufSize); } if (writer->dataUsed >= Modes.net_output_flush_size) { flushWrites(writer); } } static char *netTimestamp(char *p, int64_t timestamp) { unsigned char ch; /* timestamp, big-endian */ *p++ = (ch = (timestamp >> 40)); if (0x1A == ch) { *p++ = ch; } *p++ = (ch = (timestamp >> 32)); if (0x1A == ch) { *p++ = ch; } *p++ = (ch = (timestamp >> 24)); if (0x1A == ch) { *p++ = ch; } *p++ = (ch = (timestamp >> 16)); if (0x1A == ch) { *p++ = ch; } *p++ = (ch = (timestamp >> 8)); if (0x1A == ch) { *p++ = ch; } *p++ = (ch = (timestamp)); if (0x1A == ch) { *p++ = ch; } return p; } // //========================================================================= // // Write raw output in Beast Binary format with Timestamp to TCP clients // static void modesSendBeastOutput(struct modesMessage *mm, struct net_writer *writer) { int msgLen = mm->msgbits / 8; // 0x1a 0xe3 receiverId(2*8) 0x1a msgType timestamp+signal(2*7) message(2*msgLen) char *p = prepareWrite(writer, (2 + 2 * 8 + 2 + 2 * 7) + 2 * msgLen); unsigned char ch; int j; int sig; unsigned char *msg = (Modes.net_verbatim ? mm->verbatim : mm->msg); if (!p) return; // receiverId, big-endian, in own message to make it backwards compatible // only send the receiverId when it changes if (Modes.netReceiverId && writer->lastReceiverId != mm->receiverId) { writer->lastReceiverId = mm->receiverId; *p++ = 0x1a; // other dump1090 / readsb versions or beast implementations should discard unknown message types *p++ = 0xe3; // good enough guess no one is using this. for (int i = 7; i >= 0; i--) { *p++ = (ch = ((mm->receiverId >> (8 * i)) & 0xFF)); if (0x1A == ch) { *p++ = ch; } } } *p++ = 0x1a; if (msgLen == MODES_SHORT_MSG_BYTES) { *p++ = '2'; } else if (msgLen == MODES_LONG_MSG_BYTES) { *p++ = '3'; } else if (msgLen == MODEAC_MSG_BYTES) { *p++ = '1'; } else { return; } /* timestamp, big-endian */ p = netTimestamp(p, mm->timestamp); sig = nearbyint(sqrt(mm->signalLevel) * 255); if (mm->signalLevel > 0 && sig < 1) sig = 1; if (sig > 255) sig = 255; *p++ = ch = (char) sig; if (0x1A == ch) { *p++ = ch; } for (j = 0; j < msgLen; j++) { *p++ = (ch = msg[j]); if (0x1A == ch) { *p++ = ch; } } completeWrite(writer, p); } static void modesDumpBeastData(struct modesMessage *mm) { if (!Modes.dump_fw) { return; } int msgLen = mm->msgbits / 8; // 0x1a 0xe3 receiverId(2*8) 0x1a msgType timestamp+signal(2*7) message(2*msgLen) char store[(2 + 2 * 8 + 2 + 2 * 7) + 2 * MODES_LONG_MSG_BYTES]; char *p = store; unsigned char ch; int j; int sig; unsigned char *msg = (Modes.net_verbatim ? mm->verbatim : mm->msg); char *start = p; // receiverId, big-endian, in own message to make it backwards compatible // only send the receiverId when it changes if (Modes.netReceiverId && Modes.dump_lastReceiverId != mm->receiverId) { Modes.dump_lastReceiverId = mm->receiverId; *p++ = 0x1a; // other dump1090 / readsb versions or beast implementations should discard unknown message types *p++ = 0xe3; // good enough guess no one is using this. for (int i = 7; i >= 0; i--) { *p++ = (ch = ((mm->receiverId >> (8 * i)) & 0xFF)); if (0x1A == ch) { *p++ = ch; } } } *p++ = 0x1a; if (msgLen == MODES_SHORT_MSG_BYTES) { *p++ = '2'; } else if (msgLen == MODES_LONG_MSG_BYTES) { *p++ = '3'; } else if (msgLen == MODEAC_MSG_BYTES) { *p++ = '1'; } else { return; } /* timestamp, big-endian */ if (Modes.dump_reduce && mm->timestamp && !(mm->timestamp >= MAGIC_MLAT_TIMESTAMP && mm->timestamp <= MAGIC_MLAT_TIMESTAMP + 10)) { // clobber timestamp for better compression p = netTimestamp(p, MAGIC_ANY_TIMESTAMP); } else { p = netTimestamp(p, mm->timestamp); } sig = nearbyint(sqrt(mm->signalLevel) * 255); if (mm->signalLevel > 0 && sig < 1) sig = 1; if (sig > 255) sig = 255; *p++ = ch = (char) sig; if (0x1A == ch) { *p++ = ch; } for (j = 0; j < msgLen; j++) { *p++ = (ch = msg[j]); if (0x1A == ch) { *p++ = ch; } } int64_t now = mm->sysTimestamp; if (now > Modes.dump_next_ts) { //fprintf(stderr, "%ld\n", (long) now); Modes.dump_next_ts = now + 1; const char dump_ts_prefix[] = { 0x1A, 0xe8 }; zstdFwPutData(Modes.dump_fw, (uint8_t *) dump_ts_prefix, sizeof(dump_ts_prefix)); zstdFwPutData(Modes.dump_fw, (uint8_t *) &now, sizeof(int64_t)); } zstdFwPutData(Modes.dump_fw, (uint8_t *) start, p - start); } static void send_heartbeat(struct net_service *service) { if (!service->writer || !service->heartbeat_out.msg) { return; } char *p = prepareWrite(service->writer, service->heartbeat_out.len); if (!p) { return; } memcpy(p, service->heartbeat_out.msg, service->heartbeat_out.len); p += service->heartbeat_out.len; completeWrite(service->writer, p); } // //========================================================================= // // Turn an hex digit into its 4 bit decimal value. // Returns -1 if the digit is not in the 0-F range. // static inline __attribute__((always_inline)) int hexDigitVal(int c) { if (c >= '0' && c <= '9') return c - '0'; else if (c >= 'A' && c <= 'F') return c - 'A' + 10; else if (c >= 'a' && c <= 'f') return c - 'a' + 10; else return -1; } // //========================================================================= // // Print the two hex digits to a string for a single byte. // static inline __attribute__((always_inline)) void printHexDigit(char *p, unsigned char c) { const char hex_lookup[] = "0123456789ABCDEF"; p[0] = hex_lookup[(c >> 4) & 0x0F]; p[1] = hex_lookup[c & 0x0F]; } // //========================================================================= // // Write raw output to TCP clients // static void modesSendRawOutput(struct modesMessage *mm) { int msgLen = mm->msgbits / 8; char *p = prepareWrite(&Modes.raw_out, msgLen * 2 + 15); int j; unsigned char *msg = (Modes.net_verbatim ? mm->verbatim : mm->msg); if (!p) return; if (Modes.mlat && mm->timestamp) { /* timestamp, big-endian */ sprintf(p, "@%012" PRIX64, mm->timestamp); p += 13; } else *p++ = '*'; for (j = 0; j < msgLen; j++) { printHexDigit(p, msg[j]); p += 2; } *p++ = ';'; *p++ = '\n'; completeWrite(&Modes.raw_out, p); } // // Read Asterix FSPEC // static uint8_t * readFspec(char **p){ uint8_t* fspec = malloc(24*sizeof(uint8_t*)); for (int i = 1; i < 24; i++) { fspec[i] = 0; } fspec[0] = **p; (*p)++; for (int i = 1; *(*p - 1) & 0x1; i++){ fspec[i] = *(*p); (*p)++; } return fspec; } // // Read Asterix Time // static uint64_t readAsterixTime(char **p) { int rawtime = ((*(*p) & 0xff) << 16) + ((*(*p + 1) & 0xff) << 8) + (*(*p + 2) & 0xff); int mssm = (int)(rawtime / .128); long midnight = (long)(mstime() / 86400000) * 86400000; int diff = (int)(midnight + mssm - mstime()); (*p) += 3; if (abs(diff) > 43200000){ return midnight - 86400000 + mssm; } return midnight + mssm; } // // Read Asterix High Precision Time // static void readAsterixHighPrecisionTime(uint64_t *timeStamp, char **p) { uint8_t fsi = (**p & 0xc0) >> 6; double offset = ((**p & 0x3f) << 24) + ((*(*p + 1) & 0xff) << 16) + ((*(*p + 2) & 0xff) << 8) + (*(*p + 3) & 0xff); (*p) += 4; offset = offset * pow(2, -27); uint64_t wholesecond = (int)(*timeStamp / 1000) * 1000; switch(fsi) { case 1: wholesecond += 1; break; case 2: wholesecond -= 1; break; } *timeStamp = wholesecond + offset; } // //========================================================================= // // Read ASTERIX input from TCP clients // static int decodeAsterixMessage(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb) { //uint16_t msgLen = (*(p + 1) << 8) + *(p + 2); //int j; unsigned char category; struct modesMessage *mm = netGetMM(mb); mm->client = c; MODES_NOTUSED(c); if (remote >= 64) mm->source = remote - 64; else mm->source = SOURCE_INDIRECT; mm->remote = 1; mm->sbs_in = 1; mm->signalLevel = 0; category = *p; // Get the category p += 3; uint8_t *fspec = readFspec(&p); mm->receiverId = c->receiverId; if (unlikely(Modes.incrementId)) { mm->receiverId += now / (10 * MINUTES); } mm->sysTimestamp = -1; switch(category){ case 21: // ADS-B Message if(!(fspec[1] & 0x10)){ // no address. this is useless to us free(fspec); return -1; } if (fspec[0] & 0x80){ // ID021/010 Data Source Identification p += 2; } uint8_t addrtype = 3; if (fspec[0] & 0x40){ // ID021/040 Target Report Descriptor uint8_t *trd = readFspec(&p); addrtype = (trd[0] & 0xE0) >> 5; if (!(trd[0] & 0x18)){ mm->alt_q_bit = 1; } if (trd[1] & 0x40){ mm->airground = AG_GROUND; } else { mm->airground = AG_AIRBORNE; } free(trd); } if (fspec[0] & 0x20){ // I021/161 Track Number p += 2; } if (fspec[0] & 0x10){ // I021/015 Service Identification p += 1; } if (fspec[0] & 0x8){ // I021/071 Time of Applicability for Position 3 mm->sysTimestamp = readAsterixTime(&p); } if (fspec[0] & 0x4){ // I021/130 Position in WGS-84 co-ordinates int lat = (*p & 0xff) << 16; lat += (*(p + 1) & 0xff) << 8; lat += (*(p + 2) & 0xff); p += 3; int lon = (*p & 0xff) << 16; lon += (*(p + 1) & 0xff) << 8; lon += (*(p + 2) & 0xff); p += 3; if (lat >= 0x800000){ lat -= 0x1000000; } if (lon >= 0x800000){ lon -= 0x1000000; } double latitude = lat * (180 / pow(2, 23)); double longitude = lon * (180 / pow(2, 23)); if (latitude <= 90 && latitude >= -90 && longitude >= -180 && longitude <= 180){ mm->sbs_pos_valid = true; mm->decoded_lat = latitude; mm->decoded_lon = longitude; } } if (fspec[0] & 0x2){ // I021/131 Position in WGS-84 co-ordinates, high res. int lat = (*p & 0xff) << 24; lat += (*(p + 1) & 0xff) << 16; lat += (*(p + 2) & 0xff) << 8; lat += (*(p + 3) & 0xff); p += 4; int lon = *p << 24; lon += (*(p + 1) & 0xff) << 16; lon += (*(p + 2) & 0xff) << 8; lon += (*(p + 3) & 0xff); p += 4; double latitude = lat * (180 / pow(2, 30)); double longitude = lon * (180 / pow(2, 30)); if (latitude <= 90 && latitude >= -90 && longitude >= -180 && longitude <= 180){ mm->sbs_pos_valid = true; mm->decoded_lat = latitude; mm->decoded_lon = longitude; } } if (fspec[1] & 0x80){ // I021/072 Time of Applicability for Velocity if (mm->sysTimestamp == -1){ mm->sysTimestamp = readAsterixTime(&p); } else { p += 3; } } if (fspec[1] & 0x40){ // I021/150 Air Speed uint16_t raw_speed = (*p & 0x7f) << 8; raw_speed += *(p + 1) & 0xff; if (*p & 0x80){ //Mach mm->mach = raw_speed * 0.001; mm->mach_valid = true; } else{ // IAS mm->ias = (raw_speed * pow(2, -14)) * 3600; mm->ias_valid = true; } p += 2; } if (fspec[1] & 0x20){ // I021/151 True Airspeed uint16_t raw_speed = (*p & 0x7f) << 8; raw_speed += *(p + 1) & 0xff; if (!(*p & 0x80)){ mm->tas_valid = true; mm->tas = raw_speed; } p += 2; } // I021/080 Target Address mm->addr = (((*p & 0xff) << 16) + ((*(p + 1) & 0xff) << 8) + (*(p + 2) & 0xff)) & 0xffffff; if (addrtype == 3){ mm->addr |= MODES_NON_ICAO_ADDRESS; } p += 3; if (fspec[1] & 0x8){ // I021/073 Time of Message Reception of Position if (mm->cpr_decoded || mm->sbs_pos_valid){ uint64_t ts = readAsterixTime(&p); if (fspec[1] & 0x4){ // I021/074 Time of Message Reception of Position=High Precision readAsterixHighPrecisionTime(&ts, &p); } if (mm->sysTimestamp == -1){ mm->sysTimestamp = ts; } } else if (fspec[1] & 0x4) { p += 7; } else { p += 3; } } if (fspec[1] & 0x2){ // I021/075 Time of Message Reception of Velocity if (mm->ias_valid || mm->mach_valid || mm->gs_valid){ uint64_t ts = readAsterixTime(&p); if (fspec[2] & 0x80){ // I021/076 Time of Message Reception of Velocity=High Precision readAsterixHighPrecisionTime(&ts, &p); } if (mm->sysTimestamp == -1){ mm->sysTimestamp = ts; } } else if (fspec[2] & 0x80) { p += 7; } else { p += 3; } } if (fspec[2] & 0x40){ // I021/140 Geometric Height int16_t raw_alt = (((*p & 0xff) << 8) + (*(p + 1) & 0xff)); double alt = raw_alt * 6.25; if (alt >= -1500 && alt <= 150000){ mm->geom_alt_valid = true; mm->geom_alt_unit = UNIT_FEET; mm->geom_alt = alt; } p += 2; } uint8_t *qi; //uint8_t nucp_or_nic; uint8_t nucr_or_nacv; uint8_t nicbaro = 0; uint8_t sil; uint8_t nacp = 0; uint8_t sils; uint8_t sda = 0; uint8_t gva = 0; //uint8_t pic; if (fspec[2] & 0x20){ // I021/090 Quality Indicators qi = readFspec(&p); //nucp_or_nic = (qi[0] & 0x1e) >> 1; nucr_or_nacv = (qi[0] & 0xe0) >> 5; mm->accuracy.nac_v_valid = true; mm->accuracy.nac_v = nucr_or_nacv; if (qi[0] & 0x1){ nicbaro = (qi[1] & 0x80) >> 7; sil = (qi[1] & 0x60) >> 5; mm->accuracy.sil = sil; nacp = (qi[1] & 0x1e) >> 1; if (qi[1] & 0x1){ sils = (qi[2] & 0x20) >> 5; if (sils){ mm->accuracy.sil_type = SIL_PER_SAMPLE; } else{ mm->accuracy.sil_type = SIL_PER_HOUR; } sda = (qi[2] & 0x18) >> 3; gva = (qi[2] & 0x6) >> 1; //pic = (qi[3] & 0xf0) >> 4; } else{ mm->accuracy.sil_type = SIL_UNKNOWN; } } free(qi); } if (fspec[2] & 0x10){ // I021/210 MOPS Version mm->opstatus.valid = true; mm->opstatus.version = ((*p) & 0x38) >> 3; uint8_t ltt = (*p) & 0x7; p++; switch(mm->opstatus.version){ case 1: mm->accuracy.nac_p_valid = true; mm->accuracy.nac_p = nacp; mm->accuracy.nic_baro_valid = true; mm->accuracy.nic_baro = nicbaro; mm->accuracy.sil_type = SIL_UNKNOWN; break; case 2: mm->accuracy.nac_p_valid = true; mm->accuracy.nac_p = nacp; mm->accuracy.gva_valid = true; mm->accuracy.gva = gva; mm->accuracy.sda_valid = true; mm->accuracy.sda = sda; mm->accuracy.nic_baro_valid = true; mm->accuracy.nic_baro = nicbaro; break; } switch (ltt) { case 0: if (!addrtype){ mm->addrtype = ADDR_TISB_ICAO; } else{ mm->addrtype = ADDR_TISB_OTHER; } break; case 1: if (!addrtype){ mm->addrtype = ADDR_ADSR_ICAO; } else{ mm->addrtype = ADDR_ADSR_OTHER; } break; case 2: if (!addrtype){ mm->addrtype = ADDR_ADSB_ICAO; } else{ mm->addrtype = ADDR_ADSB_OTHER; } break; default: mm->addrtype = ADDR_UNKNOWN; break; } } if (fspec[2] & 0x8){ // I021/070 Mode 3/A Code mm->squawkHex = (((*p & 0xe) << 11) + ((*p & 0x1) << 10) + ((*(p + 1) & 0xC0) << 2) + ((*(p + 1) & 0x38) << 1) + ((*(p + 1) & 0x7))) ; mm->squawkDec = squawkHex2Dec(mm->squawkHex); mm->squawk_valid = true; p += 2; } if (fspec[2] & 0x4){ // I021/230 Roll Angle int16_t roll = ((*p & 0xff) << 8) + (*(p + 1) & 0xff); mm->roll = roll * 0.01; mm->roll_valid = true; p += 2; } if (fspec[2] & 0x2){ // I021/145 Flight Level int16_t alt = ((*p & 0xff) << 8) + (*(p + 1) & 0xff); mm->baro_alt_valid = true; mm->baro_alt = alt * 25; mm->baro_alt_unit = UNIT_FEET; p += 2; } if (fspec[3] & 0x80){ // I021/152 Magnetic Heading mm->heading_valid = true; mm->heading_type = HEADING_MAGNETIC; uint16_t heading = ((*p & 0xff) << 8) + (*(p + 1) & 0xff); mm->heading = heading * (360 / pow(2, 16)); p += 2; } if (fspec[3] & 0x40){ // I021/200 Target Status mm->spi_valid = true; mm->alert_valid = true; mm->emergency_valid = true; mm->nav.modes_valid = true; mm->nav.modes |= (*p & 0b01000000) >> 4; mm->emergency = (*p & 0b00011100) >> 2; mm->alert = (*p & 0b11); mm->spi = (*p & 0b11) == 3; p++; } if (fspec[3] & 0x20){ // ID021/155 Barometric Vertical Rate if (*p & 0x80){ //range exceeded p += 2; } else{ int16_t vr = ((*p & 0x7f) << 9) + ((*(p + 1) & 0xff) << 1); mm->baro_rate_valid = true; mm->baro_rate = vr * 3.125; p += 2; } } if (fspec[3] & 0x10){ // ID021/157 Geometric Vertical Rate if (*p & 0x80){ //range exceeded p += 2; } else{ int16_t vr = ((*p & 0x7f) << 9) + ((*(p + 1) & 0xff) << 1); mm->geom_rate_valid = true; mm->geom_rate = vr * 3.125; p += 2; } } if (fspec[3] & 0x8){ // ID021/160 Airborne Ground Vector if (*p & 0x80){ //range exceeded p += 4; } else{ uint16_t gs = ((*p & 0x7f) << 8) + ((*(p + 1) & 0xff)); p += 2; uint16_t ta = ((*p & 0xff) << 8) + ((*(p + 1) & 0xff)); p += 2; mm->gs_valid = true; mm->heading_valid = true; mm->heading_type = HEADING_GROUND_TRACK; mm->gs.v0 = gs * pow(2, -14) * 3600; mm->heading = ta * (360 / pow(2, 16)); } } if (fspec[3] & 0x4){ // ID021/165 Track Angle Rate p += 2; } if (fspec[3] & 0x2){ // ID021/077 Time of Report Transmission uint64_t tt = readAsterixTime(&p); if (mm->sysTimestamp == -1){ mm->sysTimestamp = tt; } } if (fspec[4] & 0x80){ // ID021/170 Target Identification uint64_t cs = ((uint64_t)(*p & 0xff) << 40) + ((uint64_t)(*(p + 1) & 0xff) << 32) + ((uint64_t)(*(p + 2) & 0xff) << 24) + ((uint64_t)(*(p + 3) & 0xff) << 16) + ((uint64_t)(*(p + 4) & 0xff) << 8) + (uint64_t)(*(p + 5) & 0xff); char *callsign = mm->callsign; callsign[0] = ais_charset[((cs & 0xFC0000000000) >> 42)]; callsign[1] = ais_charset[((cs & 0x3F000000000) >> 36)]; callsign[2] = ais_charset[((cs & 0xFC0000000) >> 30)]; callsign[3] = ais_charset[((cs & 0x3F000000) >> 24)]; callsign[4] = ais_charset[((cs & 0xFC0000) >> 18)]; callsign[5] = ais_charset[((cs & 0x3F000) >> 12)]; callsign[6] = ais_charset[((cs & 0xFC0) >> 6)]; callsign[7] = ais_charset[(cs & 0x3F)]; callsign[8] = 0; mm->callsign_valid = 1; for (int i = 0; i < 8; ++i) { if ( (callsign[i] >= 'A' && callsign[i] <= 'Z') // -./0123456789 || (callsign[i] >= '-' && callsign[i] <= '9') || callsign[i] == ' ' || callsign[i] == '@' ) { // valid chars } else { mm->callsign_valid = 0; } } p += 6; } if (fspec[4] & 0x40){ // ID021/020 Emitter Category int tc = 0; int ca = 0; uint8_t ecat = *p++ & 0xFF; switch (ecat) { case 0: tc = 0x0e; ca = 0; break; case 1: case 2: case 3: case 4: case 5: case 6: tc = 4; ca = ecat; break; case 10: tc = 4; ca = 7; break; case 11: tc = 3; ca = 1; break; case 12: tc = 3; ca = 2; break; case 13: tc = 3; ca = 6; break; case 14: tc = 3; ca = 7; break; case 15: tc = 3; ca = 4; break; case 16: tc = 3; ca = 3; break; case 20: tc = 2; ca = 1; break; case 21: tc = 2; ca = 3; break; case 22: tc = 2; ca = 4; break; case 23: tc = 2; ca = 5; break; case 24: tc = 2; ca = 6; break; } mm->category = ((0x0E - tc) << 4) | ca; mm->category_valid = 1; } if (fspec[4] & 0x20) { // I021/220 Met Information uint8_t *met = readFspec(&p); free(met); } if (fspec[4] & 0x10) { // I021/146 Selected Altitude if (*p & 0x80 && *p & 0x60) { int16_t alt = (*p & 0x1F) << 8; alt += *(p + 1) & 0xFF; if (alt < 0x1000){ if ((*p & 0x60) == 0x40) { // MCP mm->nav.mcp_altitude_valid = 1; mm->nav.mcp_altitude = alt * 25; } else if ((*p & 0x60) == 0x60) { //FMS mm->nav.fms_altitude_valid = 1; mm->nav.fms_altitude = alt * 25; } } } p += 2; } netUseMessage(mm); break; } free(fspec); if (mm->sysTimestamp == -1){ mm->sysTimestamp = mstime(); } //mm->decoded_nic = 0; //mm->decoded_rc = RC_UNKNOWN; return 0; } // //========================================================================= // // Write ASTERIX output to TCP clients // static void modesSendAsterixOutput(struct modesMessage *mm, struct net_writer *writer) { int64_t now = mstime(); uint8_t category; unsigned char bytes[3 * 128]; memset(bytes, 0x0, 3 * 128); uint8_t fspec[7]; for (size_t i = 0; i < 7; i++) { fspec[i] = 0; } int p = 0; if (mm->from_mlat) // CAT 20 return; if (mm->from_tisb) return; else { // CAT 21 category = 21; // I021/010 Data Source Identification fspec[0] |= 1 << 7; bytes[p++] = 000; //SAC bytes[p++] = 001; //SIC // I021/040 Target Report Descriptor fspec[0] |= 1 << 6; if (mm->addr & MODES_NON_ICAO_ADDRESS){ bytes[p] |= (3 << 5); } else if (mm->addrtype == ADDR_ADSB_OTHER || mm->addrtype == ADDR_TISB_OTHER || mm->addrtype == ADDR_ADSR_OTHER){ bytes[p] |= (2 << 5); } if (mm->alt_q_bit == 0) bytes[p] |= (1 << 3); if (mm->airground == AG_GROUND) bytes[p + 1] |= 1 << 6; if (bytes[p + 1]){ bytes[p] |= 1; p++; } p++; // I021/130 Position in WGS-84 co-ordinates if (mm->cpr_decoded || mm->sbs_pos_valid){ fspec[0] |= 1 << 2; int32_t lat; int32_t lon; lat = mm->decoded_lat / (180 / pow(2,23)); lon = mm->decoded_lon / (180 / pow(2,23)); if (lat < 0){ lat += 0x1000000; } if (lon < 0){ lon += 0x1000000; } bytes[p++] = (lat & 0xFF0000) >> 16; bytes[p++] = (lat & 0xFF00) >> 8; bytes[p++] = (lat & 0xFF); bytes[p++] = (lon & 0xFF0000) >> 16; bytes[p++] = (lon & 0xFF00) >> 8; bytes[p++] = (lon & 0xFF); } // I021/131 Position in WGS-84 co-ordinates, high res. /* if (mm->cpr_decoded || mm->sbs_pos_valid){ fspec[0] |= 1 << 1; int32_t lat; int32_t lon; lat = mm->decoded_lat / (180 / pow(2,30)); lon = mm->decoded_lon / (180 / pow(2,30)); bytes[p++] = (lat & 0xFF000000) >> 24; bytes[p++] = (lat & 0xFF0000) >> 16; bytes[p++] = (lat & 0xFF00) >> 8; bytes[p++] = (lat & 0xFF); bytes[p++] = (lon & 0xFF000000) >> 24; bytes[p++] = (lon & 0xFF0000) >> 16; bytes[p++] = (lon & 0xFF00) >> 8; bytes[p++] = (lon & 0xFF); } */ // I021/150 Air Speed if(mm->ias_valid || mm->mach_valid){ fspec[1] |= 1 << 6; uint16_t speedval; if (mm->mach_valid){ bytes[p] = (1 << 7); speedval = mm->mach * 1000; } else{ speedval = (mm->ias / 3600.0) * pow(2,14); } bytes[p++] |= (speedval & 0x7f00) >> 8; bytes[p++] = (speedval & 0xff); } // I021/151 True Air Speed if(mm->tas_valid){ fspec[1] |= 1 << 5; bytes[p++] = (mm->tas & 0x7f00) >> 8; bytes[p++] = (mm->tas & 0xff); } // I021/080 Target Address fspec[1] |= 1 << 4; bytes[p++] = (mm->addr & 0xff0000) >> 16; bytes[p++] = (mm->addr & 0xff00) >> 8; bytes[p++] = (mm->addr & 0xff); struct aircraft *a = aircraftGet(mm->addr); if (!a) { // If it's a currently unknown aircraft.... a = aircraftCreate(mm->addr); // ., create a new record for it, } // I021/073 Time of Message Reception of Position if (fspec[0] & 0b110){ fspec[1] |= 1 << 3; long midnight = (long)(time(NULL) / 86400) * 86400000; int tsm = (mm->sysTimestamp) - midnight; if (tsm < 0) tsm += 86400000; tsm = (int)(tsm * 0.128); bytes[p++] = (tsm & 0xff0000) >> 16; bytes[p++] = (tsm & 0xff00) >> 8; bytes[p++] = tsm & 0xff; } // I021/075 Time of Message Reception of Velocity if (mm->gs_valid && mm->heading_valid && mm->heading_type == HEADING_GROUND_TRACK){ fspec[1] |= 1 << 1; long midnight = (long)(time(NULL) / 86400) * 86400000; int tsm = (mm->sysTimestamp) - midnight; if (tsm < 0) tsm += 86400000; tsm = (int)(tsm * 0.128); bytes[p++] = (tsm & 0xff0000) >> 16; bytes[p++] = (tsm & 0xff00) >> 8; bytes[p++] = tsm & 0xff; } // I021/140 Geometric Height if (mm->geom_alt_valid){ fspec[2] |= 1 << 6; int16_t alt; if(mm->geom_alt_unit == UNIT_FEET) alt = mm->geom_alt / 6.25; else alt = mm->geom_alt / 20.5053; bytes[p++] = (alt & 0xff00) >> 8; bytes[p++] = alt & 0xff; } else if (mm->geom_delta_valid){ fspec[2] |= 1 << 6; int16_t alt = (int)((a->baro_alt + mm->geom_delta) / 6.25); bytes[p++] = (alt & 0xff00) >> 8; bytes[p++] = alt & 0xff; } // I021/090 Quality Indicators fspec[2] |= 1 << 5; if (mm->accuracy.nac_v_valid) bytes[p] += mm->accuracy.nac_v << 5; if (mm->cpr_decoded) bytes[p] |= mm->cpr_nucp << 1; if (mm->accuracy.nic_baro_valid) bytes[p + 1] |= mm->accuracy.nic_baro << 7; if (mm->accuracy.sil_type != SIL_INVALID) bytes[p + 1] |= mm->accuracy.sil << 5; if (mm->accuracy.nac_p_valid) bytes[p + 1] |= mm->accuracy.nac_p << 1; if (bytes[p + 1]){ bytes[p] |= 1; p++; } if (mm->accuracy.sil_type == SIL_PER_SAMPLE) bytes[p + 1] |= 1 << 5; if (mm->accuracy.sda_valid) bytes[p + 1] |= mm->accuracy.sda << 3; if (mm->accuracy.gva_valid) bytes[p + 1] |= mm->accuracy.gva << 1; if (bytes[p + 1]){ bytes[p] |= 1; p++; } p++; // I021/210 MOPS Version if (mm->opstatus.valid){ fspec[2] |= 1 << 4; if (mm->remote) { switch (mm->addrtype){ case ADDR_ADSB_ICAO: case ADDR_ADSB_OTHER: bytes[p] = 2; break; case ADDR_ADSR_ICAO: case ADDR_ADSR_OTHER: bytes[p] = 1; break; default: bytes[p] = 0; break; } } else { switch (mm->source){ case SOURCE_ADSB: bytes[p] = 2; break; case SOURCE_ADSR: bytes[p] = 1; break; default: bytes[p] = 0; break; } } bytes[p++] |= (mm->opstatus.version) << 3; } // I021/070 Mode 3/A Code if(mm->squawk_valid){ fspec[2] |= 1 << 3; uint16_t squawk = mm->squawkHex; bytes[p] |= ((squawk & 0x7000)) >> 11; bytes[p++] |= ((squawk & 0x0400)) >> 10; bytes[p] |= ((squawk & 0x0300)) >> 2; bytes[p] |= ((squawk & 0x0070)) >> 1; bytes[p++] |= ((squawk & 0x0007)); } // I021/230 Roll Angle if(mm->roll_valid){ fspec[2] |= 1 << 2; int16_t roll = mm->roll * 100; bytes[p++] = (roll & 0xFF00) >> 8; bytes[p++] = (roll & 0xFF); } // I021/145 Flight Level if(mm->baro_alt_valid){ fspec[2] |= 1 << 1; int16_t value = mm->baro_alt / 25; if (mm->baro_alt_unit == UNIT_METERS) value = (int)(mm-> baro_alt * 3.2808); bytes[p++] = (value & 0xff00) >> 8; bytes[p++] = value & 0xff; } // I021/152 Magnetic Heading if(mm->heading_valid && mm->heading_type == HEADING_MAGNETIC){ fspec[3] |= 1 << 7; double adj_trk = mm->heading * 182.0444; uint16_t trk = (int)adj_trk; bytes[p++] = (trk & 0xff00) >> 8; bytes[p++] = trk & 0xff; } // I021/200 Target Status if (mm->spi_valid || mm->alert_valid || mm->emergency_valid || mm->nav.modes_valid){ fspec[3] |= 1 << 6; if (mm->nav.modes_valid){ if (mm->nav.modes & 0b00000010) bytes[p] |= 1 << 6; } if (mm->emergency_valid) bytes[p] |= (mm->emergency << 2); if (mm->alert_valid) bytes[p] |= (mm->alert); else if (mm->spi_valid && mm->spi) bytes[p] |=3; p++; } // I021/155 Barometric Vertical Rate if (mm->baro_rate_valid){ fspec[3] |= 1 << 5; int value = ((int16_t)(mm->baro_rate / 3.125)) >> 1; bytes[p++] = (value & 0x7f00) >> 8; bytes[p++] = value & 0xff; } // I021/157 Geometric Vertical Rate if (mm->geom_rate_valid){ fspec[3] |= 1 << 4; int value = ((int16_t)(mm->geom_rate / 3.125)) >> 1; bytes[p++] = (value & 0x7f00) >> 8; bytes[p++] = value & 0xff; } // I021/160 Airborne Ground Vector if (mm->gs_valid && mm->heading_valid && mm->heading_type == HEADING_GROUND_TRACK){ fspec[3] |= 1 << 3; double adj_gs = mm->gs.v0 * 4.5511; bytes[p++] = ((int)adj_gs & 0x7f00) >> 8; bytes[p++] = (int)adj_gs & 0xff; double adj_trk = mm->heading * (pow(2,16) / 360.0); uint16_t trk = (int)adj_trk; bytes[p++] = (trk & 0xff00) >> 8; bytes[p++] = trk & 0xff; } // I021/077 Time of Report Transmission { fspec[3] |= 1 << 1; long midnight = (long)(time(NULL) / 86400) * 86400000; int tsm = now - midnight; if (tsm < 0) tsm += 86400000; tsm = (int)(tsm * 0.128); bytes[p++] = (tsm & 0xff0000) >> 16; bytes[p++] = (tsm & 0xff00) >> 8; bytes[p++] = tsm & 0xff; } // I021/170 Target Identification if(mm->callsign_valid){ fspec[4] |= 1 << 7; uint64_t enc_callsign = 0; for (int i = 0; i <= 7; i++) { uint8_t ch = char_to_ais(mm->callsign[i]); enc_callsign = (enc_callsign << 6) + (ch & 0x3F); } bytes[p++] = (enc_callsign & 0xff0000000000) >> 40; bytes[p++] = (enc_callsign & 0xff00000000) >> 32; bytes[p++] = (enc_callsign & 0xff000000) >> 24; bytes[p++] = (enc_callsign & 0xff0000) >> 16; bytes[p++] = (enc_callsign & 0xff00) >> 8; bytes[p++] = (enc_callsign & 0xff); } // I021/020 Emitter Category if (mm->category_valid){ fspec[4] |= 1 << 6; int tc = 0x0e - ((mm->category & 0x1F0) >> 4); int ca = mm->category & 7; if (ca){ switch (tc) { case 1: break; case 2: switch (ca){ case 1: bytes[p++] = 20; break; case 3: bytes[p++] = 21; break; case 4: case 5: case 6: case 7: bytes[p++] = 22; break; } break; case 3: switch (ca){ case 1: bytes[p++] = 11; break; case 2: bytes[p++] = 12; break; case 3: bytes[p++] = 16; break; case 4: bytes[p++] = 15; break; case 6: bytes[p++] = 13; break; case 7: bytes[p++] = 14; break; } break; case 4: switch (ca){ case 1: case 2: case 3: case 4: case 5: case 6: bytes[p++] = ca; break; case 7: bytes[p++] = 10; } break; } } else { bytes[p++] = 0; } } else if (!(a->category)){ fspec[4] |= 1 << 6; bytes[p++] = 0; } // I021/220 Met Information //if (ac && ((now < ac->oat_updated + TRACK_EXPIRE) || (now < ac->wind_updated + TRACK_EXPIRE && abs(ac->wind_altitude - ac->baro_alt) < 500))){} if (mm->wind_valid || mm->oat_valid || mm->turbulence_valid || mm->static_pressure_valid || mm->humidity_valid) { fspec[4] |= 1 << 5; bool wind = false; bool temp = false; //if (now < ac->wind_updated + TRACK_EXPIRE && abs(ac->wind_altitude - ac->baro_alt) < 500){} if (mm->wind_valid) { bytes[p] |= 0xC0; wind = true; } //if (now < ac->oat_updated + TRACK_EXPIRE){} if (mm->oat_valid) { bytes[p] |= 0x20; temp = true; } p++; if (wind){ uint16_t ws = (int)(mm->wind_speed); uint16_t wd = (int)(mm->wind_direction); bytes[p++] = (ws & 0xFF00) >> 8; bytes[p++] = ws & 0xFF; bytes[p++] = (wd & 0xFF00) >> 8; bytes[p++] = wd & 0xFF; } if (temp){ int16_t oat = (int16_t)((mm->oat) * 4); bytes[p++] = (oat & 0xFF00) >> 8; bytes[p++] = oat & 0xFF; } } // I021/146 Selected Altitude if (mm->nav.fms_altitude_valid || mm->nav.mcp_altitude_valid){ fspec[4] |= 1 << 4; int alt = 0; if (mm->nav.mcp_altitude_valid){ alt = mm->nav.mcp_altitude; bytes[p] |= 0xC0; } else if (mm->nav.fms_altitude_valid){ alt = mm->nav.fms_altitude; bytes[p] |= 0xE0; } alt /= 25; bytes[p++] |= (alt & 0x1F00) >> 8; bytes[p++] = (alt & 0xFF); } // I021/008 Aircraft Operational Status if (mm->opstatus.valid){ if (mm->opstatus.om_acas_ra || mm->opstatus.cc_tc || mm->opstatus.cc_ts || mm->opstatus.cc_arv || mm->opstatus.cc_cdti || (!mm->opstatus.cc_acas)){ fspec[5] |= 1 << 7; bytes[p] |= (mm->opstatus.om_acas_ra & 0x1) << 7; bytes[p] |= (mm->opstatus.cc_tc & 0x3) << 5; bytes[p] |= (mm->opstatus.cc_ts & 0x1) << 4; bytes[p] |= (mm->opstatus.cc_arv & 0x1) << 3; bytes[p] |= (mm->opstatus.cc_cdti & 0x1) << 2; bytes[p] |= ((!mm->opstatus.cc_acas) & 0x1) << 1; p++; } } // I021/400 Receiver ID if (mm->receiverId){ fspec[5] |= 1 << 2; bytes[p++] = (mm->receiverId) & 0xFF; } // I021/295 Data Ages /* if (fspec[4] == 0b100000){ fspec[5] |= 1 << 1; bytes[p++] |= 1; bytes[p++] |= 1; bytes[p++] |= 1 << 2; if((now < ac->wind_updated + TRACK_EXPIRE && abs(ac->wind_altitude - ac->baro_alt) < 500) && (now < ac->oat_updated + TRACK_EXPIRE)) { uint64_t wind_age = now - ac->wind_updated; uint64_t oat_age = now - ac->oat_updated; if (wind_age > oat_age){ bytes[p++] = (int)(wind_age / 100); } else { bytes[p++] = (int)(oat_age / 100); } } else if (now < ac->wind_updated + TRACK_EXPIRE && abs(ac->wind_altitude - ac->baro_alt) < 500){ uint64_t wind_age = now - ac->wind_updated; bytes[p++] = (int)(wind_age / 100); } else if (now < ac->oat_updated + TRACK_EXPIRE){ uint64_t oat_age = now - ac->oat_updated; bytes[p++] = (int)(oat_age / 100); } } */ int fspec_len = 1; for (int i = 5; i >= 0; i--) { if (fspec[i + 1]){ fspec[i] |= 1; fspec_len++; } } if (p > 2 * 128) { fprintf(stderr, "ERROR: REPORT THIS BUG: asterix byte len too large: %d\n", p); } uint16_t msgLen = p + 3 + fspec_len; uint8_t msgLenA = (msgLen & 0xFF00) >> 8; uint8_t msgLenB = msgLen & 0xFF; char *w = prepareWrite(writer, msgLen); memcpy(w, &category, 1); w++; memcpy(w, &msgLenA, 1); memcpy(w + 1, &msgLenB, 1); w+=2; memcpy(w, &fspec, fspec_len); w += fspec_len; memcpy(w, &bytes, p); w += p; completeWrite(writer, w); } } // //========================================================================= // // Read SBS input from TCP clients // static int decodeSbsLine(struct client *c, char *line, int remote, int64_t now, struct messageBuffer *mb) { size_t line_len = strlen(line); size_t max_len = 200; if (Modes.receiver_focus && c->receiverId != Modes.receiver_focus) return 0; if (line_len < 2) // heartbeat return 0; if (line_len < 20 || line_len >= max_len) goto basestation_invalid; struct modesMessage *mm = netGetMM(mb); mm->client = c; char *p = line; char *t[23]; // leave 0 indexed entry empty, place 22 tokens into array MODES_NOTUSED(c); if (remote >= 64) mm->source = remote - 64; else mm->source = SOURCE_SBS; switch(mm->source) { case SOURCE_SBS: mm->addrtype = ADDR_OTHER; break; case SOURCE_MLAT: mm->addrtype = ADDR_MLAT; break; case SOURCE_JAERO: mm->addrtype = ADDR_JAERO; break; case SOURCE_PRIO: mm->addrtype = ADDR_OTHER; break; default: mm->addrtype = ADDR_OTHER; } // Mark messages received over the internet as remote so that we don't try to // pass them off as being received by this instance when forwarding them mm->remote = 1; mm->signalLevel = 0; mm->sbs_in = 1; char *endptr = NULL; int badValue = 0; // set this to 1 if a field couldn't be parsed // SBS fields: // MSG,3,1,1,icaoHex,1,messageDate,messageTime,currentDate,currentTime,callsign_8char,altitude_ft,groundspeed_kts,track,lat,lon,vert_rate_fpm,squawk,squawkChangeAlert,squawkEmergencyFlag,squawkIdentFlag,groundFlag_0airborne_-1ground\r\n // sample message from mlat-client basestation output //MSG,3,1,1,4AC8B3,1,2019/12/10,19:10:46.320,2019/12/10,19:10:47.789,,36017,,,51.1001,10.1915,,,,,, // for (int i = 1; i < 23; i++) { t[i] = strsep(&p, ","); if (!p && i < 22) goto basestation_invalid; } // check field 1 if (!t[1] || strcmp(t[1], "MSG") != 0) goto basestation_invalid; if (!t[2] || strlen(t[2]) != 1) goto basestation_invalid; mm->sbsMsgType = atoi(t[2]); if (!t[5] || strlen(t[5]) < 6 || strlen(t[5]) > 7) // icao must be 6 characters goto basestation_invalid; char *icao = t[5]; int non_icao = 0; if (icao[0] == '~') { icao++; non_icao = 1; } unsigned char *chars = (unsigned char *) &(mm->addr); for (int j = 0; j < 6; j += 2) { int high = hexDigitVal(icao[j]); int low = hexDigitVal(icao[j + 1]); if (high == -1 || low == -1) goto basestation_invalid; chars[2 - j / 2] = (high << 4) | low; } if (non_icao) { mm->addr |= MODES_NON_ICAO_ADDRESS; } // date t7: 2019/12/10 // time t8: 19:10:46.320 // separate milliseconds in the time field int milli = 0; char *msp = t[8]; // millisecond pointer // discard strsep result it's just t[8] // strsep is used to advance msp past the decimal separator strsep(&msp, "."); if (msp) { milli = strtol(msp, NULL, 10); } // for easy parsing override the null byte between t7 and t8 with a space *(t[8] - 1) = ' '; struct tm tm; memset(&tm, 0, sizeof(tm)); char *parseRes = strptime(t[7], "%Y/%m/%d %H:%M:%S", &tm); if (parseRes) { mm->sysTimestamp = timegm(&tm) * 1000LL + milli; if (mm->sysTimestamp > now + 1 * SECONDS || mm->sysTimestamp < now - 20 * MINUTES) { // inhibit future and past timestamps and timegm errors (returns -1) mm->sysTimestamp = now; } } else { // record reception time as the time we read it. mm->sysTimestamp = now; } //fprintf(stderr, "%x type %s: ", mm->addr, t[2]); //fprintf(stderr, "%x: %d, %0.5f, %0.5f\n", mm->addr, mm->baro_alt, mm->decoded_lat, mm->decoded_lon); //field 11, callsign if (t[11] && strlen(t[11]) > 0) { strncpy(mm->callsign, t[11], 9); mm->callsign[8] = '\0'; mm->callsign_valid = 1; for (unsigned i = 0; i < 8; ++i) { if (mm->callsign[i] == '\0') mm->callsign[i] = ' '; if (!(mm->callsign[i] >= 'A' && mm->callsign[i] <= 'Z') && !(mm->callsign[i] >= '0' && mm->callsign[i] <= '9') && mm->callsign[i] != ' ') { // Bad callsign, ignore it mm->callsign_valid = 0; break; } } //fprintf(stderr, "call: %s, ", mm->callsign); } // field 12, altitude if (t[12] && strlen(t[12]) > 0) { double tmp = strtod(t[12], &endptr); if (endptr != t[12] && isfinite(tmp)) { mm->baro_alt = tmp; mm->baro_alt_valid = 1; mm->baro_alt_unit = UNIT_FEET; } else { badValue = 1; } //fprintf(stderr, "alt: %d, ", mm->baro_alt); } // field 13, groundspeed if (t[13] && strlen(t[13]) > 0) { double tmp = strtod(t[13], &endptr); if (endptr != t[13] && isfinite(tmp)) { mm->gs_valid = 1; mm->gs.v0 = tmp; } else { badValue = 1; } //fprintf(stderr, "gs: %.1f, ", mm->gs.selected); } //field 14, heading if (t[14] && strlen(t[14]) > 0) { mm->heading = strtod(t[14], &endptr); if (endptr != t[14] && isfinite(mm->heading)) { mm->heading_valid = 1; mm->heading_type = HEADING_GROUND_TRACK; } else { badValue = 1; } //fprintf(stderr, "track: %.1f, ", mm->heading); } // field 15 and 16, position if (t[15] && strlen(t[15]) && t[16] && strlen(t[16])) { mm->decoded_lat = strtod(t[15], &endptr); char *endptr2 = NULL; mm->decoded_lon = strtod(t[16], &endptr2); if ( endptr != t[15] && endptr2 != t[16] && isfinite(mm->decoded_lat) && isfinite(mm->decoded_lon) && mm->decoded_lat <= 90 && mm->decoded_lat >= -90 && mm->decoded_lon >= -180 && mm->decoded_lon <= 180 ) { mm->sbs_pos_valid = 1; } else { badValue = 1; } //fprintf(stderr, "pos: (%.2f, %.2f)\n", mm->decoded_lat, mm->decoded_lon); } // field 17 vertical rate, assume baro if (t[17] && strlen(t[17]) > 0) { double tmp = strtod(t[17], &endptr); if (endptr != t[17] && isfinite(tmp)) { mm->baro_rate = tmp; mm->baro_rate_valid = 1; } else { badValue = 1; } //fprintf(stderr, "vRate: %d, ", mm->baro_rate); } // field 18 squawk if (t[18] && strlen(t[18]) > 0) { long int tmp = strtol(t[18], &endptr, 10); if (endptr != t[18]) { mm->squawkDec = tmp; mm->squawkHex = squawkDec2Hex(mm->squawkDec); mm->squawk_valid = 1; //fprintf(stderr, "squawk: %04x %s, ", mm->squawkHex, t[18]); } else { badValue = 1; } } // field 19 (originally squawk change) used to indicate by some versions of mlat-server the number of receivers which contributed to the postiions if (t[19] && strlen(t[19]) > 0) { long int tmp = strtol(t[19], &endptr, 10); if (tmp > 0 && mm->source == SOURCE_MLAT) { mm->receiverCountMlat = tmp; } else if (!strcmp(t[19], "0")) { mm->alert_valid = 1; mm->alert = 0; } else if (!strcmp(t[19], "-1")) { mm->alert_valid = 1; mm->alert = 1; } } // field 20 (originally emergency status) used to indicate by some versions of mlat-server the estimated error in km if (t[20] && strlen(t[20]) > 0) { long tmp = strtol(t[20], &endptr, 10); if (tmp > 0 && mm->source == SOURCE_MLAT) { mm->mlatEPU = tmp; if (tmp > UINT16_MAX) mm->mlatEPU = UINT16_MAX; //fprintf(stderr, "mlatEPU: %d\n", mm->mlatEPU); } else if (!strcmp(t[21], "0")) { mm->squawk_emergency_valid = 1; mm->squawk_emergency = 0; } else if (!strcmp(t[21], "-1")) { mm->squawk_emergency_valid = 1; mm->squawk_emergency = 1; } } // Field 21 is the Squawk Ident flag if (t[21] && strlen(t[21]) > 0) { if (!strcmp(t[21], "1")) { mm->spi_valid = 1; mm->spi = 1; } else if (!strcmp(t[21], "0")) { mm->spi_valid = 1; mm->spi = 0; } } // field 22 ground status if (t[22] && strlen(t[22]) > 0) { if (!strncmp(t[22], "-1", 2)) { mm->airground = AG_GROUND; } else if (!strncmp(t[22], "0", 1)) { mm->airground = AG_AIRBORNE; } //fprintf(stderr, "onground, "); } // set nic / rc to 0 / unknown mm->decoded_nic = 0; mm->decoded_rc = RC_UNKNOWN; //fprintf(stderr, "\n"); netUseMessage(mm); Modes.stats_current.remote_received_basestation_valid++; if (Modes.debug_garbage && badValue) { for (size_t i = 0; i < line_len; i++) { line[i] = (line[i] == '\0' ? ',' : line[i]); } fprintf(stderr, "SBS badValue: %.*s%s\n", (int) imin(200, line_len), line, line_len > 200 ? " [ ... ] " : ""); } return 0; basestation_invalid: if (Modes.debug_garbage) { for (size_t i = 0; i < line_len; i++) { line[i] = (line[i] == '\0' ? ',' : line[i]); } fprintf(stderr, "SBS invalid: %.*s%s\n", (int) imin(200, line_len), line, line_len > 200 ? " [ ... ] " : ""); } Modes.stats_current.remote_received_basestation_invalid++; return 0; } // //========================================================================= // // Write SBS output to TCP clients // static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a, struct net_writer *writer) { char *p; struct timespec now; struct tm stTime_receive, stTime_now; int msgType; p = prepareWrite(writer, 200); if (!p) return; // // SBS BS style output checked against the following reference // http://www.homepages.mcb.net/bones/SBS/Article/Barebones42_Socket_Data.htm - seems comprehensive // // SBS fields: // MSG,3,1,1,icaoHex,1,messageDate,messageTime,currentDate,currentTime,callsign_8char,altitude_ft,groundspeed_kts,track,lat,lon,vert_rate_fpm,squawk,squawkChangeAlert,squawkEmergencyFlag,squawkIdentFlag,groundFlag_0airborne_-1ground\r\n if (mm->sbs_in) { msgType = mm->sbsMsgType; } else { // Decide on the basic SBS Message Type switch (mm->msgtype) { case 4: case 20: msgType = 5; break; break; case 5: case 21: msgType = 6; break; case 0: case 16: msgType = 7; break; case 11: msgType = 8; break; case 17: case 18: if (mm->metype >= 1 && mm->metype <= 4) { msgType = 1; } else if (mm->metype >= 5 && mm->metype <= 8) { msgType = 2; } else if (mm->metype >= 9 && mm->metype <= 18) { msgType = 3; } else if (mm->metype == 19) { msgType = 4; } else { return; } break; default: return; } } // Fields 1 to 6 : SBS message type and ICAO address of the aircraft and some other stuff p += sprintf(p, "MSG,%d,1,1,%s%06X,1,", msgType, (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); // Find current system time clock_gettime(CLOCK_REALTIME, &now); gmtime_r(&now.tv_sec, &stTime_now); // Find message reception time time_t received = (time_t) (mm->sysTimestamp / 1000); gmtime_r(&received, &stTime_receive); // Fields 7 & 8 are the message reception time and date p += sprintf(p, "%04d/%02d/%02d,", (stTime_receive.tm_year + 1900), (stTime_receive.tm_mon + 1), stTime_receive.tm_mday); p += sprintf(p, "%02d:%02d:%02d.%03u,", stTime_receive.tm_hour, stTime_receive.tm_min, stTime_receive.tm_sec, (unsigned) (mm->sysTimestamp % 1000)); // Fields 9 & 10 are the current time and date p += sprintf(p, "%04d/%02d/%02d,", (stTime_now.tm_year + 1900), (stTime_now.tm_mon + 1), stTime_now.tm_mday); p += sprintf(p, "%02d:%02d:%02d.%03u", stTime_now.tm_hour, stTime_now.tm_min, stTime_now.tm_sec, (unsigned) (now.tv_nsec / 1000000U)); // Field 11 is the callsign (if we have it) if (mm->callsign_valid) { p += sprintf(p, ",%s", mm->callsign); } else { p += sprintf(p, ","); } // Field 12 is the altitude (if we have it) if (Modes.use_gnss) { if (mm->geom_alt_valid) { p += sprintf(p, ",%dH", mm->geom_alt); } else if (mm->baro_alt_valid && trackDataValid(&a->geom_delta_valid)) { p += sprintf(p, ",%dH", mm->baro_alt + a->geom_delta); } else if (mm->baro_alt_valid) { p += sprintf(p, ",%d", mm->baro_alt); } else { p += sprintf(p, ","); } } else { if (mm->baro_alt_valid) { p += sprintf(p, ",%d", mm->baro_alt); } else if (mm->geom_alt_valid && trackDataValid(&a->geom_delta_valid)) { p += sprintf(p, ",%d", mm->geom_alt - a->geom_delta); } else { p += sprintf(p, ","); } } // Field 13 is the ground Speed (if we have it) if (mm->gs_valid) { p += sprintf(p, ",%.0f", mm->gs.selected); } else { p += sprintf(p, ","); } // Field 14 is the ground Heading (if we have it) if (mm->heading_valid && mm->heading_type == HEADING_GROUND_TRACK) { p += sprintf(p, ",%.0f", mm->heading); } else { p += sprintf(p, ","); } // Fields 15 and 16 are the Lat/Lon (if we have it) if (mm->cpr_decoded || mm->sbs_pos_valid) { p += sprintf(p, ",%1.6f,%1.6f", mm->decoded_lat, mm->decoded_lon); } else { p += sprintf(p, ",,"); } // Field 17 is the VerticalRate (if we have it) if (Modes.use_gnss) { if (mm->geom_rate_valid) { p += sprintf(p, ",%dH", mm->geom_rate); } else if (mm->baro_rate_valid) { p += sprintf(p, ",%d", mm->baro_rate); } else { p += sprintf(p, ","); } } else { if (mm->baro_rate_valid) { p += sprintf(p, ",%d", mm->baro_rate); } else if (mm->geom_rate_valid) { p += sprintf(p, ",%d", mm->geom_rate); } else { p += sprintf(p, ","); } } // Field 18 is the Squawk (if we have it) if (Modes.sbsOverrideSquawk != -1) { p += sprintf(p, ",%04d", Modes.sbsOverrideSquawk); } else if (mm->squawk_valid) { p += sprintf(p, ",%04d", mm->squawkDec); } else { p += sprintf(p, ","); } if (mm->receiverCountMlat) { p += sprintf(p, ",%d", mm->receiverCountMlat); } else if (mm->alert_valid) { // Field 19 is the Squawk Changing Alert flag (if we have it) if (mm->alert) { p += sprintf(p, ",-1"); } else { p += sprintf(p, ",0"); } } else { p += sprintf(p, ","); } if (mm->mlatEPU) { p += sprintf(p, ",%d", mm->mlatEPU); } else if (mm->squawk_emergency_valid) { // Field 20 is the Squawk Emergency flag (if we have it) if (mm->squawk_emergency) { p += sprintf(p, ",-1"); } else { p += sprintf(p, ",0"); } } else if (mm->squawk_valid) { // Field 20 is the Squawk Emergency flag (if we have it) if ((mm->squawkHex == 0x7500) || (mm->squawkHex == 0x7600) || (mm->squawkHex == 0x7700)) { p += sprintf(p, ",-1"); } else { p += sprintf(p, ",0"); } } else { p += sprintf(p, ","); } // Field 21 is the Squawk Ident flag (if we have it) if (mm->spi_valid) { if (mm->spi) { p += sprintf(p, ",-1"); } else { p += sprintf(p, ",0"); } } else { p += sprintf(p, ","); } // Field 22 is the OnTheGround flag (if we have it) switch (mm->airground) { case AG_GROUND: p += sprintf(p, ",-1"); break; case AG_AIRBORNE: p += sprintf(p, ",0"); break; default: p += sprintf(p, ","); break; } p += sprintf(p, "\r\n"); completeWrite(writer, p); } void jsonPositionOutput(struct modesMessage *mm, struct aircraft *a) { MODES_NOTUSED(mm); int buflen = 8192; char buf[8192]; char *p = buf; char *end = buf + buflen; p = sprintAircraftObject(p, end, a, mm->sysTimestamp, 2, NULL); if (p + 1 >= end) { fprintf(stderr, "buffer insufficient jsonPositionOutput()\n"); return; } *p++ = '\n'; int size = p - buf; char *w = prepareWrite(&Modes.json_out, size); if (!w) { return; } memcpy(w, buf, size); w += size; completeWrite(&Modes.json_out, w); } void sendData(struct net_writer *output, char *data, int len) { char *p; int buflen = Modes.writerBufSize; while (len > 0) { p = prepareWrite(output, buflen); if (!p) return; int tsize = imin(len, buflen); memcpy(p, data, tsize); len -= tsize; p += tsize; completeWrite(output, p); } } // Decode a little-endian IEEE754 float (binary32) float ieee754_binary32_le_to_float(uint8_t *data) { double sign = (data[3] & 0x80) ? -1.0 : 1.0; int16_t raw_exponent = ((data[3] & 0x7f) << 1) | ((data[2] & 0x80) >> 7); uint32_t raw_significand = ((data[2] & 0x7f) << 16) | (data[1] << 8) | data[0]; if (raw_exponent == 0) { if (raw_significand == 0) { /* -0 is treated like +0 */ return 0; } else { /* denormal */ return ldexp(sign * raw_significand, -126 - 23); } } if (raw_exponent == 255) { if (raw_significand == 0) { /* +/-infinity */ return sign < 0 ? -INFINITY : INFINITY; } else { /* NaN */ #ifdef NAN return NAN; #else return 0.0f; #endif } } /* normalized value */ return ldexp(sign * ((1 << 23) | raw_significand), raw_exponent - 127 - 23); } static void handle_radarcape_position(float lat, float lon, float alt) { if (Modes.netIngest || Modes.netReceiverId) { return; } if (!isfinite(lat) || lat < -90 || lat > 90 || !isfinite(lon) || lon < -180 || lon > 180 || !isfinite(alt)) { return; } if (!Modes.userLocationValid) { Modes.fUserLat = lat; Modes.fUserLon = lon; Modes.userLocationValid = 1; receiverPositionChanged(lat, lon, alt); } } /** * Convert 32bit binary angular measure to double degree. * See https://www.globalspec.com/reference/14722/160210/Chapter-7-5-3-Binary-Angular-Measure * @param data Data buffer start (MSB first) * @return Angular degree. */ static double bam32ToDouble(uint32_t bam) { return (double) ((int32_t) ntohl(bam) * 8.38190317153931E-08); } // //========================================================================= // // This function decodes a GNS HULC protocol message static void decodeHulcMessage(char *p) { // silently ignore these messages if proper SDR isn't set if (Modes.sdr_type != SDR_GNS) return; int alt = 0; double lat = 0.0; double lon = 0.0; char id = *p++; //Get message id unsigned char len = *p++; // Get message length hulc_status_msg_t hsm; if (id == 0x01 && len == 0x18) { // HULC Status message for (int j = 0; j < len; j++) { hsm.buf[j] = *p++; // unescape if (*p == 0x1A) { p++; } } /* // Antenna serial Modes.receiver.antenna_serial = ntohl(hsm.status.serial); // Antenna status flags Modes.receiver.antenna_flags = ntohs(hsm.status.flags); // Reserved for internal use Modes.receiver.antenna_reserved = ntohs(hsm.status.reserved); // Antenna Unix epoch (not used) // Antenna GPS satellites used for fix Modes.receiver.antenna_gps_sats = hsm.status.satellites; // Antenna GPS HDOP*10, thus 12 is HDOP 1.2 Modes.receiver.antenna_gps_hdop = hsm.status.hdop; */ // Antenna GPS latitude lat = bam32ToDouble(hsm.status.latitude); // Antenna GPS longitude lon = bam32ToDouble(hsm.status.longitude); // Antenna GPS altitude alt = ntohs(hsm.status.altitude); uint32_t antenna_flags = ntohs(hsm.status.flags); // Use only valid GPS position if ((antenna_flags & 0xE000) == 0xE000) { if (!isfinite(lat) || lat < -90 || lat > 90 || !isfinite(lon) || lon < -180 || lon > 180) { return; } // only use when no fixed location is defined if (!Modes.userLocationValid) { Modes.fUserLat = lat; Modes.fUserLon = lon; Modes.userLocationValid = 1; receiverPositionChanged(lat, lon, alt); } } } else if (id == 0x01 && len > 0x18) { // Future use planed. } else if (id == 0x24 && len == 0x10) { // Response to command #00 fprintf(stderr, "Firmware: v%0u.%0u.%0u\n", *(p + 5), *(p + 6), *(p + 7)); } } // recompute global Mode A/C setting static void autoset_modeac() { if (!Modes.mode_ac_auto) return; Modes.mode_ac = 0; for (struct net_service *service = Modes.services_out.services; service->descr; service++) { for (struct client *c = service->clients; c; c = c->next) { if (c->modeac_requested) { Modes.mode_ac = 1; break; } } } } // Send some Beast settings commands to a client void sendBeastSettings(int fd, const char *settings) { int len; char *buf, *p; len = strlen(settings) * 3; buf = p = alloca(len); while (*settings) { *p++ = 0x1a; *p++ = '1'; *p++ = *settings++; } anetWrite(fd, buf, len); } static int handle_gpsd(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb) { MODES_NOTUSED(c); MODES_NOTUSED(remote); MODES_NOTUSED(now); MODES_NOTUSED(mb); if (Modes.debug_gps) { fprintTime(stderr, now); fprintf(stderr, " gpsdebug: received from GPSD: \'%s\'\n", p); } struct char_buffer msg; msg.alloc = strlen(p) + 128; msg.buffer = cmalloc(msg.alloc); sprintf(msg.buffer, "%s\n", p); msg.len = strlen(msg.buffer); // remove spaces in place char *d = p; char *s = p; do { while (*s == ' ') { s++; } *d = *s++; } while (*d++); // filter all messages but TPV type if (!strstr(p, "\"class\":\"TPV\"")) { if (Modes.debug_gps) { fprintf(stderr, "gpsdebug: class is not \"TPV\" : ignoring message.\n"); } goto exit; } // filter all messages which don't have lat / lon char *latp = strstr(p, "\"lat\":"); char *lonp = strstr(p, "\"lon\":"); char *altp = strstr(p, "\"alt\":"); if (!latp || !lonp) { if (Modes.debug_gps) { fprintf(stderr, "gpsdebug: lat / lon not present: ignoring message.\n"); } goto exit; } latp += 6; lonp += 6; char *saveptr = NULL; strtok_r(latp, ",", &saveptr); saveptr = NULL; strtok_r(lonp, ",", &saveptr); double lat = strtod(latp, NULL); double lon = strtod(lonp, NULL); double alt_m = -2e6; if (altp) { altp += 6; saveptr = NULL; strtok_r(altp, ",", &saveptr); alt_m = strtod(altp, NULL); } if (Modes.debug_gps) { if (alt_m > -1e6) { fprintf(stderr, "gpsdebug: parsed lat,lon: %11.6f,%11.6f (alt: %.0f m)\n", lat, lon, alt_m); } else { fprintf(stderr, "gpsdebug: parsed lat,lon: %11.6f,%11.6f (no alt)\n", lat, lon); } } //fprintf(stderr, "%11.6f %11.6f\n", lat, lon); if (!isfinite(lat) || lat < -89.9 || lat > 89.9 || !isfinite(lon) || lon < -180 || lon > 180) { if (Modes.debug_gps) { fprintf(stderr, "gpsdebug: lat lon implausible, ignoring\n"); } goto exit; } if (fabs(lat) < 0.1 && fabs(lon) < 0.1) { if (Modes.debug_gps) { fprintf(stderr, "gpsdebug: lat lon implausible, ignoring\n"); } goto exit; } if (Modes.debug_gps) { fprintf(stderr, "gpsdebug: Updating position, writing receiver.json\n"); } Modes.fUserLat = lat; Modes.fUserLon = lon; if (alt_m > -1e6) { Modes.fUserAlt = alt_m; } Modes.userLocationValid = 1; if (Modes.json_dir) { free(writeJsonToFile(Modes.json_dir, "receiver.json", generateReceiverJson()).buffer); // location changed if (msg.len) { writeJsonToFile(Modes.json_dir, "gpsd.json", msg); } } exit: free(msg.buffer); return 0; } static int handleCommandSocket(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb) { MODES_NOTUSED(c); MODES_NOTUSED(remote); MODES_NOTUSED(now); MODES_NOTUSED(mb); char *saveptr = NULL; char *cmd = strtok_r(p, " ", &saveptr); if (strcmp(cmd, "deleteTrace") == 0) { char *t1 = strtok_r(NULL, " ", &saveptr); char *t2 = strtok_r(NULL, " ", &saveptr); char *t3 = strtok_r(NULL, " ", &saveptr); if (!t1 || !t2 || !t3) { fprintf(stderr, "commandSocket deleteTrace: not enough tokens\n"); return 0; } struct hexInterval* new = cmalloc(sizeof(struct hexInterval)); new->hex = (uint32_t) strtol(t1, NULL, 16); new->from = (int64_t) strtol(t2, NULL, 10); new->to = (int64_t) strtol(t3, NULL, 10); new->next = Modes.deleteTrace; Modes.deleteTrace = new; fprintf(stderr, "Deleting %06x from %lld to %lld\n", new->hex, (long long) new->from, (long long) new->to); } else { fprintf(stderr, "commandSocket: unrecognized command\n"); } return 0; } // // Handle a Beast command message. // Currently, we just look for the Mode A/C command message // and ignore everything else. // static int handleBeastCommand(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb) { MODES_NOTUSED(remote); MODES_NOTUSED(now); MODES_NOTUSED(mb); if (p[0] == 'P') { // got ping c->ping = readPingEscaped(p+1); c->pingReceived = now; c->pingEnabled = 1; if (0 && Modes.debug_ping) fprintf(stderr, "Got Ping: %d\n", c->ping); } else if (p[0] == '1') { switch (p[1]) { case 'j': c->modeac_requested = 0; break; case 'J': c->modeac_requested = 1; break; } autoset_modeac(); } else if (p[0] == 'W') { switch (p[1]) { case 'S': dropHalfUntil(now, c, now + PING_REDUCE_DURATION); break; } } return 0; } // //========================================================================= // // This function decodes a Beast binary format message // // The message is passed to the higher level layers, so it feeds // the selected screen output, the network output and so forth. // // If the message looks invalid it is silently discarded. // // The function always returns 0 (success) to the caller as there is no // case where we want broken messages here to close the client connection. // // to save a couple cycles we remove the escapes in the calling function and expect nonescaped messages here static int decodeBinMessage(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb) { uint16_t msgLen = 0; int j; unsigned char ch; struct modesMessage *mm = netGetMM(mb); unsigned char *msg = mm->msg; mm->client = c; ch = *p++; /// Get the message type mm->receiverId = c->receiverId; if (unlikely(Modes.incrementId)) { mm->receiverId += now / (10 * MINUTES); } if (ch == '2') { msgLen = MODES_SHORT_MSG_BYTES; } else if (ch == '3') { msgLen = MODES_LONG_MSG_BYTES; } else if (ch == '1') { if (!Modes.mode_ac) { if (remote) { Modes.stats_current.remote_received_modeac++; } else { Modes.stats_current.demod_modeac++; } return 0; } msgLen = MODEAC_MSG_BYTES; } else if (ch == '5') { // Special case for Radarcape position messages. float lat, lon, alt; unsigned char msg[21]; for (j = 0; j < 21; j++) { // and the data msg[j] = ch = *p++; } lat = ieee754_binary32_le_to_float(msg + 4); lon = ieee754_binary32_le_to_float(msg + 8); alt = ieee754_binary32_le_to_float(msg + 12); handle_radarcape_position(lat, lon, alt); return 0; } else if (ch == 'H') { decodeHulcMessage(p); return 0; } else if (ch == 'P') { // pong message // only accept pong message if not ingest or client ping "enabled" if (!Modes.netIngest || c->pingEnabled) { c->pong = readPing(p); return pongReceived(c, now); } return 0; } else { // unknown msg type return 0; } /* Beast messages are marked depending on their source. From internet they are marked * remote so that we don't try to pass them off as being received by this instance * when forwarding them. */ mm->remote = remote; mm->timestamp = 0; // Grab the timestamp (big endian format) for (j = 0; j < 6; j++) { ch = *p++; mm->timestamp = mm->timestamp << 8 | (ch & 255); } // record reception time as the time we read it. mm->sysTimestamp = now; //fprintf(stderr, "epoch: %.6f\n", mm->sysTimestamp / 1000.0); ch = *p++; // Grab the signal level mm->signalLevel = ((unsigned char) ch / 255.0); mm->signalLevel = mm->signalLevel * mm->signalLevel; /* In case of Mode-S Beast use the signal level per message for statistics */ if (c == Modes.serial_client) { Modes.stats_current.signal_power_sum += mm->signalLevel; Modes.stats_current.signal_power_count += 1; if (mm->signalLevel > Modes.stats_current.peak_signal_power) Modes.stats_current.peak_signal_power = mm->signalLevel; if (mm->signalLevel > 0.50119) Modes.stats_current.strong_signal_count++; // signal power above -3dBFS } for (j = 0; j < msgLen; j++) { // and the data msg[j] = ch = *p++; } int result = -10; if (msgLen == MODEAC_MSG_BYTES) { // ModeA or ModeC if (remote) { Modes.stats_current.remote_received_modeac++; } else { Modes.stats_current.demod_modeac++; } decodeModeAMessage(mm, ((msg[0] << 8) | msg[1])); result = 0; } else { if (remote) { Modes.stats_current.remote_received_modes++; } else { Modes.stats_current.demod_preambles++; } result = decodeModesMessage(mm); if (result < 0) { if (result == -1) { if (remote) { Modes.stats_current.remote_rejected_unknown_icao++; } else { Modes.stats_current.demod_rejected_unknown_icao++; } } else { if (remote) { Modes.stats_current.remote_rejected_bad++; } else { Modes.stats_current.demod_rejected_bad++; } } } else { if (remote) { Modes.stats_current.remote_accepted[mm->correctedbits]++; } else { Modes.stats_current.demod_accepted[mm->correctedbits]++; } } } if (c->pongReceived && c->pongReceived > now + 100) { // if messages are received with more than 100 ms delay after a pong, recalculate c->rtt pongReceived(c, now); } if (c->rtt > Modes.ping_reject && Modes.netIngest) { // don't discard CPRs, if we have better data speed_check generally will take care of delayed CPR messages // this way we get basic data even from high latency receivers // super high latency receivers are getting disconnected in pongReceived() if (!mm->cpr_valid) { Modes.stats_current.remote_rejected_delayed++; return 0; // discard } } if (c->unreasonable_messagerate) { mm->garbage = 1; } if ((Modes.garbage_ports || Modes.netReceiverId) && receiverCheckBad(mm->receiverId, now)) { mm->garbage = 1; } netUseMessage(mm); return 0; } // Planefinder uses bit stuffing, so if we see a DLE byte, we need the next byte static inline unsigned char getNextPfUnstuffedByte(char **p) { if (**p == DLE) { (*p)++; } return *(*p)++; } // // //========================================================================= // // This function decodes a planefinder binary format message // // The message is passed to the higher level layers, so it feeds // the selected screen output, the network output and so forth. // // If the message looks invalid it is silently discarded. // // The function always returns 0 (success) to the caller as there is no // case where we want broken messages here to close the client connection. // // For packet ID 0x41, the format is: // Byte Value Notes // 0 // 1 ID 0x41 // 2 padding always 0 // 3 byte the lower 4 bits map to: 0 = mode AC, 1 = mode S short, 2 = mode S long. Bit 4 indicates CRC. 5-7 is undefined in the spec I received, although bit 5 is in use (it can be ignored). // 4 byte signal strength // 5-8 long epoch time // 9-12 long nanoseconds // 13-27 byte data, mode AC/S static int decodePfMessage(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb) { MODES_NOTUSED(remote); int msgLen = 0; int j; unsigned char ch; struct modesMessage *mm = netGetMM(mb); unsigned char *msg = mm->msg; mm->client = c; mm->remote = 1; // Skip the DLE in the beginning p++; // Packet ID / type ch = getNextPfUnstuffedByte(&p); /// Get the message type // This shouldn't happen because we check it in the readPlanefinder() function if (ch != 0xc1) { return 0; } // Padding getNextPfUnstuffedByte(&p); // Packet type ch = getNextPfUnstuffedByte(&p); if (ch & 0x10) { // CRC: ignore field } if ((ch & 0xF) == 0) { if (!Modes.mode_ac) { return 0; } msgLen = MODEAC_MSG_BYTES; } else if ((ch & 0xF) == 1) { msgLen = MODES_SHORT_MSG_BYTES; } else if ((ch & 0xF) == 2) { msgLen = MODES_LONG_MSG_BYTES; } else { if (Modes.debug_planefinder) { fprintf(stderr, "Unknown message type: %d\n", ch); } return 0; } // Signal strength ch = getNextPfUnstuffedByte(&p); mm->signalLevel = ((unsigned char) ch / 255.0); mm->signalLevel = mm->signalLevel * mm->signalLevel; // square it to get power mm->timestamp = 0; int64_t seconds = 0; for (j = 0; j < 4; j++) { ch = getNextPfUnstuffedByte(&p); seconds = seconds << 8 | (ch & 255); } int64_t nanoseconds = 0; for (j = 0; j < 4; j++) { ch = getNextPfUnstuffedByte(&p); nanoseconds = nanoseconds << 8 | (ch & 255); } if (Modes.debug_planefinder) { fprintf(stderr, "sec: %12lld ns: %12lld\n", (long long) seconds, (long long) nanoseconds); } mm->timestamp = seconds * 1000000000LL + nanoseconds; // record reception time as the time we read it. mm->sysTimestamp = now; for (j = 0; j < msgLen; j++) { // and the data msg[j] = getNextPfUnstuffedByte(&p); } int result = -10; if (msgLen == MODEAC_MSG_BYTES) { // ModeA or ModeC Modes.stats_current.remote_received_modeac++; decodeModeAMessage(mm, ((msg[0] << 8) | msg[1])); result = 0; } else { Modes.stats_current.remote_received_modes++; result = decodeModesMessage(mm); if (result < 0) { if (result == -1) { Modes.stats_current.remote_rejected_unknown_icao++; } else { Modes.stats_current.remote_rejected_bad++; } } else { Modes.stats_current.remote_accepted[mm->correctedbits]++; } } if (c->unreasonable_messagerate) { mm->garbage = 1; } if ((Modes.garbage_ports || Modes.netReceiverId) && receiverCheckBad(mm->receiverId, now)) { mm->garbage = 1; } if (Modes.debug_planefinder && (Modes.mode_ac || msgLen != MODEAC_MSG_BYTES)) { displayModesMessage(mm); } netUseMessage(mm); return 0; } // exception decoding subroutine, return 1 for success, 0 for failure static int decodeHexMessage(struct client *c, char *hex, int64_t now, struct modesMessage *mm) { int l = strlen(hex), j; unsigned char *msg = mm->msg; mm->client = c; // Mark messages received over the internet as remote so that we don't try to // pass them off as being received by this instance when forwarding them mm->remote = 1; mm->signalLevel = 0; // Remove spaces on the left and on the right while (l && isspace(hex[l - 1])) { hex[l - 1] = '\0'; l--; } while (isspace(*hex)) { hex++; l--; } // https://www.aerobits.pl/wp-content/uploads/2021/07/OEM_MC_Datasheet.pdf // *RAW_FRAME;(SIGS,SIGQ,TS)\r\n // signal strength mV, signal quality mV, time from last PPS pulse in hex (microseconds?, document doesn't say) // let's just create a bogus timestamp and parse the signal strength .... if (hex[l - 1] == ')') { hex[l - 1] = '\0'; int pos = l - 1; while (pos && hex[pos] != '(') { pos--; } // find opening ( if (pos) { hex[pos] = '\0'; l = pos; } else { return 0; } // incomplete char *saveptr = NULL; char *token = strtok_r(&hex[pos + 1], ",", &saveptr); if (!token) return 0; mm->signalLevel = strtol(token, NULL, 10); mm->signalLevel /= 1000; // let's assume 1000 mV max .. i only have a small sample, specification isn't clear mm->signalLevel = mm->signalLevel * mm->signalLevel; // square it to get power mm->signalLevel = fmin(1.0, mm->signalLevel); // cap at 1 // token = strtok_r(NULL, ",", &saveptr); // discard signal quality if (!token) return 0; token = strtok_r(NULL, ",", &saveptr); if (token) { int after_pps = strtol(token, NULL, 16); // round down to current second, go to microseconds and add after_pps, go to 12 MHz clock int64_t seconds = now / 1000; if (after_pps / 1000 > (now % 1000) + 500) { seconds -= 1; // assume our clock is one second in front of the GPS clock, go back one second before adding the after pps time } mm->timestamp = (seconds * (1000 * 1000) + after_pps) * 12; } else { mm->timestamp = now * 12e3; // make 12 MHz timestamp from microseconds } } // Turn the message into binary. // Accept // *-AVR: raw // @-AVR: beast_ts+raw // %-AVR: timeS+raw (CRC good) // <-AVR: beast_ts+sigL+raw // and some AVR records that we can understand if (hex[l - 1] != ';') { return (0); } // not complete - abort if (l <= 2 * MODEAC_MSG_BYTES) return (0); // too short switch (hex[0]) { // timestamp = (mm->timestamp << 4) | hexDigitVal(*hex); hex++; l--; } if (l < 2) return (0); mm->signalLevel = ((hexDigitVal(hex[0]) << 4) | hexDigitVal(hex[1])) / 255.0; hex += 2; l -= 2; mm->signalLevel = mm->signalLevel * mm->signalLevel; break; } case '@': // No CRC check // example timestamp 03BA2A7C1DD1, should be 12 MHz treat it as such // example message: @03BA2A7C1DD15D4CA7F9A0B84B; { // CRC is OK hex++; l -= 2; // Skip @ and ; if (l <= 12) // if we have only enough hex for the timestamp or less it's invalid return (0); for (j = 0; j < 12; j++) { mm->timestamp = (mm->timestamp << 4) | hexDigitVal(*hex); hex++; } l -= 12; // timestamp now processed break; } case '%': { // CRC is OK hex += 13; l -= 14; // Skip @,%, and timestamp, and ; break; } case '*': case ':': { hex++; l -= 2; // Skip * and ; break; } default: { return (0); // We don't know what this is, so abort break; } } if ((l != (MODEAC_MSG_BYTES * 2)) && (l != (MODES_SHORT_MSG_BYTES * 2)) && (l != (MODES_LONG_MSG_BYTES * 2))) { return (0); } // Too short or long message... broken if ((0 == Modes.mode_ac) && (l == (MODEAC_MSG_BYTES * 2))) { return (0); } // Right length for ModeA/C, but not enabled for (j = 0; j < l; j += 2) { int high = hexDigitVal(hex[j]); int low = hexDigitVal(hex[j + 1]); if (high == -1 || low == -1) return 0; msg[j / 2] = (high << 4) | low; } // record reception time as the time we read it. mm->sysTimestamp = now; if (l == (MODEAC_MSG_BYTES * 2)) { // ModeA or ModeC Modes.stats_current.remote_received_modeac++; decodeModeAMessage(mm, ((msg[0] << 8) | msg[1])); } else { // Assume ModeS int result; Modes.stats_current.remote_received_modes++; result = decodeModesMessage(mm); if (result < 0) { if (result == -1) Modes.stats_current.remote_rejected_unknown_icao++; else Modes.stats_current.remote_rejected_bad++; return 0; } else { Modes.stats_current.remote_accepted[mm->correctedbits]++; } } return 1; } // // //========================================================================= // // This function decodes a string representing message in raw hex format // like: *8D4B969699155600E87406F5B69F; The string is null-terminated. // // The message is passed to the higher level layers, so it feeds // the selected screen output, the network output and so forth. // // If the message looks invalid it is silently discarded. // // The function always returns 0 (success) to the caller as there is no // case where we want broken messages here to close the client connection. // static int processHexMessage(struct client *c, char *hex, int remote, int64_t now, struct messageBuffer *mb) { MODES_NOTUSED(remote); struct modesMessage *mm = netGetMM(mb); int success = decodeHexMessage(c, hex, now, mm); if (success) { netUseMessage(mm); } return (0); } static void replayUatMsg(char *msg, int msgLen) { char *p = prepareWrite(&Modes.uat_replay_out, msgLen + 1); if (!p) { return; } memcpy(p, msg, msgLen); p += msgLen; *p++ = '\n'; completeWrite(&Modes.uat_replay_out, p); return; } static int decodeUatMessage(struct client *c, char *msg, int remote, int64_t now, struct messageBuffer *mb) { MODES_NOTUSED(remote); int msgLen = strlen(msg); char *end = msg + msgLen; char output[512]; replayUatMsg(msg, msgLen); uat2esnt_convert_message(msg, end, output, output + sizeof(output)); char *som = output; char *eod = som + strlen(som); char *p; while (((p = memchr(som, '\n', eod - som)) != NULL)) { *p = '\0'; struct modesMessage *mm = netGetMM(mb); int success = decodeHexMessage(c, som, now, mm); if (success) { struct aircraft *a = aircraftGet(mm->addr); if (!a) { // If it's a currently unknown aircraft.... a = aircraftCreate(mm->addr); // ., create a new record for it, } // ignore the first UAT message if (now > a->seen + 300 * SECONDS) { //fprintf(stderr, "IGNORING first UAT message from: %06x\n", a->addr); a->seen = now; return 0; } netUseMessage(mm); } som = p + 1; } return 0; } static const char *hexDumpString(const char *str, int strlen, char *buf, int buflen) { int max = buflen / 4 - 4; if (max <= 0) { // fail silently buf[0] = 0; return buf; } char *out = buf; char *end = buf + buflen; for (int k = 0; k < max && k < strlen; k++) { out = safe_snprintf(out, end, "%02x ", (unsigned char) str[k]); } out = safe_snprintf(out, end, "|"); for (int k = 0; k < max && k < strlen; k++) { unsigned char ch = str[k]; if (ch < 32 || ch > 126) { out = safe_snprintf(out, end, "."); } else { out = safe_snprintf(out, end, "%c", (unsigned char) ch); } } out = safe_snprintf(out, end, "|"); if (out >= end) { fprintf(stderr, "hexDumpstring: check logic\n"); } return buf; } // //========================================================================= // // This function polls the clients using read() in order to receive new // messages from the net. // static int readClient(struct client *c, int64_t now) { int nread = 0; if (c->discard) c->buflen = 0; int left = c->bufmax - c->buflen - 4; // leave 4 extra byte for NUL termination in the ASCII case // If our buffer is full discard it, this is some badly formatted shit if (left <= 0) { c->garbage += c->buflen; Modes.stats_current.remote_malformed_beast += c->buflen; c->buflen = 0; c->som = c->buf; c->eod = c->buf + c->buflen; left = c->bufmax - c->buflen - 4; // leave 4 extra byte for NUL termination in the ASCII case // If there is garbage, read more to discard it ASAP } if (c->remote) { nread = recv(c->fd, c->buf + c->buflen, left, 0); } else { // read instead of recv for modesbeast / gns-hulc .... if (0 && Modes.debug_serial) { fprintTimePrecise(stderr, mstime()); fprintf(stderr, " serial read ... fd: %d maxbytes: %d\n", c->fd, left); } nread = read(c->fd, c->buf + c->buflen, left); if (nread > 0 && Modes.debug_serial) { fprintTimePrecise(stderr, mstime()); fprintf(stderr, " serial read return value: %d\n", nread); } static int64_t lastData; if (nread > 0 || !lastData) { lastData = now; } if (now - lastData > 5 * MINUTES) { fprintTimePrecise(stderr, mstime()); fprintf(stderr, " no data from device for 5 minutes\n"); lastData = now; // reset this so we don't keep printing it } } int err = errno; // If we didn't get all the data we asked for, then return once we've processed what we did get. if (nread != left) { c->bContinue = 0; // also note that we (likely) emptied the system network buffer c->last_read_flush = now; } if (nread < 0) { if (err == EAGAIN || err == EWOULDBLOCK) { // No data available, check later! return 0; } // Other errors if (c->serial) { fprintf(stderr, "Serial client read error: %s\n", strerror(err)); } if (Modes.debug_net) { fprintf(stderr, "%s: Socket Error: %s: %s port %s (fd %d, SendQ %d, RecvQ %d)\n", c->service->descr, strerror(err), c->host, c->port, c->fd, c->sendq_len, c->buflen); } modesCloseClient(c); return 0; } // End of file if (nread == 0) { if (c->serial) { // for serial this just means we're doing non-blocking reads and there are no bytes available return 0; } if (c->con) { if (Modes.synthetic_now) { Modes.synthetic_now = 0; } fprintf(stderr, "%s: Remote server disconnected: %s port %s (fd %d, SendQ %d, RecvQ %d)\n", c->service->descr, c->con->address, c->con->port, c->fd, c->sendq_len, c->buflen); } else if (Modes.debug_net && !Modes.netIngest) { fprintf(stderr, "%s: Listen client disconnected: %s port %s (fd %d, SendQ %d, RecvQ %d)\n", c->service->descr, c->host, c->port, c->fd, c->sendq_len, c->buflen); } if (!c->con && Modes.debug_bogus) { setExit(1); } modesCloseClient(c); return 0; } // nread > 0 here Modes.stats_current.network_bytes_in += nread; if (Modes.netIngest) { int windowSeconds = 2; int aboveRate = (c->recentMessages > windowSeconds * Modes.ingestLimitRate); if (aboveRate) { c->unreasonable_messagerate = 1; if (now > c->unreasonableRateReset) { char uuid[64]; // needs 36 chars and null byte sprint_uuid(c->receiverId, c->receiverId2, uuid); fprintf(stderr, "GARBAGE due to high message rate %ld > %d rId %s %s\n", (long int) (c->recentMessages / windowSeconds), Modes.ingestLimitRate, uuid, c->proxy_string); } c->unreasonableRateReset = now + 20 * SECONDS; } if (now > c->recentMessagesReset) { if (0) { char uuid[64]; // needs 36 chars and null byte sprint_uuid(c->receiverId, c->receiverId2, uuid); fprintf(stderr, "message rate %ld rId %s %s\n", (long int) (c->recentMessages / windowSeconds), uuid, c->proxy_string); } if (!aboveRate && now > c->unreasonableRateReset) { c->unreasonable_messagerate = 0; } c->recentMessagesReset = now + windowSeconds * SECONDS; c->recentMessages = 0; } } if (!Modes.debug_no_discard && !c->discard && now - c->last_read < 800 && now - c->last_read_flush > 2400 && !Modes.synthetic_now) { c->discard = 1; if (Modes.netIngest && c->proxy_string[0] != '\0') { fprintf(stderr, "<3>ERROR, not enough CPU: Discarding data from: %s\n", c->proxy_string); } else { fprintf(stderr, "<3>%s: ERROR, not enough CPU: Discarding data from: %s port %s (fd %d)\n", c->service->descr, c->host, c->port, c->fd); } } c->last_read = now; if (c->discard) { return nread; } c->buflen += nread; c->bytesReceived += nread; return nread; } static int readBeastcommand(struct client *c, int64_t now, struct messageBuffer *mb) { char *p; while (c->som < c->eod && ((p = memchr(c->som, (char) 0x1a, c->eod - c->som)) != NULL)) { // The first byte of buffer 'should' be 0x1a char *eom; // one byte past end of message c->som = p; // consume garbage up to the 0x1a ++p; // skip 0x1a if (p >= c->eod) { // Incomplete message in buffer, retry later break; } if (*p == '1') { eom = p + 2; } else if (*p == 'W') { // W command eom = p + 2; } else if (*p == 'P') { // ping from the receiver eom = p + 4; } else { // Not a valid beast command, skip 0x1a and try again ++c->som; continue; } // we need to be careful of double escape characters in the message body for (p = c->som + 1; p < c->eod && p < eom; p++) { if (0x1A == *p) { p++; eom++; } } if (eom > c->eod) { // Incomplete message in buffer, retry later break; } char *start = c->som + 1; // advance to next message c->som = eom; // Pass message to handler. if (c->service->read_handler(c, start, c->remote, now, mb)) { modesCloseClient(c); return -1; } } return 0; } static int readAscii(struct client *c, int64_t now, struct messageBuffer *mb) { // // This is the ASCII scanning case, AVR RAW or HTTP at present // If there is a complete message still in the buffer, there must be the separator 'sep' // in the buffer, note that we full-scan the buffer at every read for simplicity. // char *p; // replace null bytes with newlines so the routine doesn't stop working // while null bytes are an illegal input, let's deal with them regardless if (memchr(c->som, '\0', c->eod - c->som)) { p = c->som; while (p < c->eod) { if (*p == '\0') { *p = '\n'; } p++; } // warn about illegal input static int64_t antiSpam; if (Modes.debug_garbage && now > antiSpam) { antiSpam = now + 30 * SECONDS; fprintf(stderr, "%s from %s port %s: Bad format, at least one null byte in input data!\n", c->service->descr, c->host, c->port); } } while (c->som < c->eod && (p = strstr(c->som, c->service->read_sep)) != NULL) { // end of first message if found *p = '\0'; // The handler expects null terminated strings // remove \r for strings that still have it at the end if (p - 1 > c->som && *(p - 1) == '\r') { *(p - 1) = '\0'; } char *start = c->som; c->som = p + c->service->read_sep_len; // Move to start of next message if (c->service->read_handler(c, start, c->remote, now, mb)) { // Pass message to handler. if (Modes.debug_net) { fprintf(stderr, "%s: Closing connection from %s port %s\n", c->service->descr, c->host, c->port); } modesCloseClient(c); // Handler returns 1 on error to signal we . return -1; // should close the client connection } } return 0; } static int readAsterix(struct client *c, int64_t now, struct messageBuffer *mb) { while (c->som < c->eod) { char *p = c->som; uint16_t msgLen = (*(p + 1) << 8) + *(p + 2); char *end = c->som + msgLen; c->som = end; if (c->service->read_handler(c, p, c->remote, now, mb)) { if (Modes.debug_net) { fprintf(stderr, "%s: Closing connection from %s port %s\n", c->service->descr, c->host, c->port); } modesCloseClient(c); return -1; } } return 0; } // Spec for Planefinder message. // All messages begin with a DLE and end with a DLE, ETX. DLE cannot appear in the middle of a message unless it's escaped with another DLE (i.e., bit stuffing) // Message format: // Byte Value Notes // 0 header // 1 ID Only packet id 0xc1 is recognized here // 2 - n Data Depends on the packet type // n+1 escape // n+2 footer static int readPlanefinder(struct client *c, int64_t now, struct messageBuffer *mb) { char *p; unsigned char pid; char *start; char *end; // Scan the entire buffer, see if we can find one or more messages. while (c->som < c->eod && ((p = memchr(c->som, DLE, c->eod - c->som)) != NULL)) { end = NULL; // Make sure we didn't jump to a DLE that's in the middle of a message. TBD if we need this if (p+1 < c->eod && *(p+1) != DLE && *(p+1) != ETX) { // Good to go! } else { c->som = p+1; continue; } // Now, check if we have the end of the message in the buffer start = p; p++; // Skip start DLE p++; // Skip packet ID while (p < c->eod) { if (*p == DLE) { // Potential message end found; it's either a DLE, ETX sequence or a DLE, DLE (the first is an escape for the second) if (p+1 < c->eod && *(p+1) == ETX) { // We found an actual end! end = p+1; break; } } p++; } if (p >= c->eod) { // We reached the end of the buffer and didn't find a message. We'll call this function again when there's more data available return 0; } #if 0 fprintf(stderr, "Message found from 0x%p to 0x%p: ", c->som, end); for (char * byte = start; byte<=end; byte++) { fprintf(stderr, "%02x", (unsigned char)*byte & 0xFF); } fprintf(stderr, "\n"); #endif // Next time we loop through this, start from the next message c->som = end+1; // We only process messages with ID 0xc1. Others are valid, but not relevant for us pid = *(start+1); if (pid != 0xc1) { continue; } // Pass message to handler. if (c->service->read_handler(c, start, c->remote, now, mb)) { modesCloseClient(c); return -1; } } return 0; } static int readBeast(struct client *c, int64_t now, struct messageBuffer *mb) { // This is the Beast Binary scanning case. // If there is a complete message still in the buffer, there must be the separator 'sep' // in the buffer, note that we full-scan the buffer at every read for simplicity. char *p; //fprintf(stderr, "readBeast\n"); while (c->som < c->eod && ((p = memchr(c->som, (char) 0x1a, c->eod - c->som)) != NULL)) { // The first byte of buffer 'should' be 0x1a c->garbage += p - c->som; Modes.stats_current.remote_malformed_beast += p - c->som; //lastSom = p; c->som = p; // consume garbage up to the 0x1a ++p; // skip 0x1a if (p >= c->eod) { // Incomplete message in buffer, retry later break; } char *eom; // one byte past end of message unsigned char ch; if (!c->service) { fprintf(stderr, "c->service null ohThee9u\n"); } if (Modes.synthetic_now) { now = Modes.synthetic_now; Modes.syntethic_now_suppress_errors = 0; } ch = *p; if (ch == 0xe8) { // message with synthetic timestamp from --dump-beast file prepended p++; int64_t ts; if (p + sizeof(int64_t) > c->eod) { break; } memcpy(&ts, p, sizeof(int64_t)); p += sizeof(int64_t); int64_t old_now = now; if (Modes.dump_accept_synthetic_now) { now = Modes.synthetic_now = ts; } else if (Modes.dump_ignore_synthetic_now) { // do nothing } else { static int64_t antiSpam; if (now > antiSpam) { antiSpam = now + 30 * SECONDS; char sample[256]; hexDumpString(c->som, c->eod - c->som, sample, sizeof(sample)); sample[sizeof(sample) - 1] = '\0'; fprintf(stderr, "%s: Synthetic timestamp detected without --devel=accept_synthetic" " or --devel=ignore_synthetic specified, disconnecting client: %s port %s," " hexdump of data containing 0x1A 0xE8: %s\n", c->service->descr, c->host, c->port, sample); } if (Modes.netIngest) { modesCloseClient(c); return -1; } } //fprintf(stderr, "%ld %ld\n", (long) now, (long) (c->eod - c->som)); c->som = p; // set start of next message if (*p != 0x1A) { //fprintf(stderr, "..\n"); continue; } p++; // skip 0x1a if (p >= c->eod) { // Incomplete message in buffer, retry later break; } if (Modes.synthetic_now) { if (priorityTasksPending()) { if (now - old_now > 5 * SECONDS) { Modes.syntethic_now_suppress_errors = 1; } pthread_mutex_unlock(&Threads.decode.mutex); priorityTasksRun(); pthread_mutex_lock(&Threads.decode.mutex); Modes.syntethic_now_suppress_errors = 0; } } } else if (ch == 0xe3) { // message with receiverId prepended p++; uint64_t receiverId = 0; eom = p + 8; // we need to be careful of double escape characters in the receiverId for (int j = 0; j < 8 && p < c->eod && p < eom; j++) { ch = *p++; if (ch == 0x1A) { ch = *p++; eom++; if (p < c->eod && ch != 0x1A) { // check that it's indeed a double escape // might be start of message rather than double escape. c->garbage += p - 1 - c->som; Modes.stats_current.remote_malformed_beast += p - 1 - c->som; c->som = p - 1; goto beastWhileContinue; } } // Grab the receiver id (big endian format) receiverId = receiverId << 8 | (ch & 255); } if (eom + 2 > c->eod)// Incomplete message in buffer, retry later break; if (!Modes.netIngest) { c->receiverId = receiverId; } c->som = p; // set start of next message if (*p != 0x1A) { continue; } p++; // skip 0x1a if (p >= c->eod) { // Incomplete message in buffer, retry later break; } } if (!c->service) { fprintf(stderr, "c->service null waevem0E\n"); } ch = *p; if (ch == '2') { eom = p + 1 + 6 + 1 + MODES_SHORT_MSG_BYTES; } else if (ch == '3') { eom = p + 1 + 6 + 1 + MODES_LONG_MSG_BYTES; } else if (ch == '1') { eom = p + 1 + 6 + 1 + MODEAC_MSG_BYTES; if (0) { char sample[256]; char *sampleStart = c->som - 32; if (sampleStart < c->buf) sampleStart = c->buf; *c->som = 'X'; hexDumpString(sampleStart, c->eod - sampleStart, sample, sizeof(sample)); *c->som = 0x1a; sample[sizeof(sample) - 1] = '\0'; fprintf(stderr, "modeAC: som pos %d, sample %s, eom > c->eod %d\n", (int) (c->som - c->buf), sample, eom > c->eod); } } else if (ch == '5') { eom = p + MODES_LONG_MSG_BYTES + 8; } else if (ch == 0xeb) { // encapsulated UAT message, variable length but guaranteed not to have 0x1a because // it's only readable ascii chars eom = memchr(p, '\n', c->eod - p); if (!eom) { if (memchr(p, (char) 0x1A, c->eod - p)) { // malformed message, skip continue; } else { // incomplete message, wait for rest to arrive break; } } *eom = '\0'; p++; decodeUatMessage(c, p, 1, now, mb); } else if (ch == 0xe4) { p++; if (c->eod - p < 36) { if (!memchr(p, (char) 0x1A, c->eod - p)) { // incomplete uuid break; } else { // malformed uuid continue; } } // read UUID and continue with next message c->som = read_uuid(c, p, c->eod); continue; } else if (ch == 'P') { //unsigned char *pu = (unsigned char*) p; //fprintf(stderr, "%x %x %x %x %x\n", pu[0], pu[1], pu[2], pu[3], pu[4]); eom = p + 4; } else if (ch == 'W') { // read command p++; ch = *p; if (ch == 'O') { // O for high resolution timer, both P and p already used for previous iterations // explicitely enable ping for this client c->pingEnabled = 1; uint32_t newPing = now & ((1 << 24) - 1); if (Modes.debug_ping) fprintf(stderr, "Initial Ping: %d\n", newPing); pingClient(c, newPing); if (!c->service) { fprintf(stderr, "c->service null Ieseey5s\n"); return -1; } if (flushClient(c, now) < 0) { return -1; } if (!c->service) { fprintf(stderr, "c->service null EshaeC7n\n"); return -1; } } c->som += 2; continue; } else { // Not a valid beast message, skip 0x1a // Skip following byte as well: // either: 0x1a (likely not a start of message but rather escaped 0x1a) // or: any other char is skipped anyhow when looking for the next 0x1a c->som += 2; Modes.stats_current.remote_malformed_beast += 2; c->garbage += 2; continue; } if (!c->service) { fprintf(stderr, "c->service null quooJ1ea\n"); return -1; } if (eom > c->eod) // Incomplete message in buffer, retry later break; char noEscapeStorage[MODES_LONG_MSG_BYTES + 8 + 16]; // 16 extra for good measure char *noEscape = p; // we need to be careful of double escape characters in the message body if (memchr(p, (char) 0x1A, eom - p)) { char *t = noEscapeStorage; while (p < eom) { if (*p == (char) 0x1A) { p++; eom++; if (eom > c->eod) { // Incomplete message in buffer, retry later break; } if (*p != (char) 0x1A) { // check that it's indeed a double escape // might be start of message rather than double escape. // c->garbage += p - 1 - c->som; Modes.stats_current.remote_malformed_beast += p - 1 - c->som; c->som = p - 1; if (0) { char sample[256]; char *sampleStart = c->som - 32; if (sampleStart < c->buf) sampleStart = c->buf; *c->som = 'X'; hexDumpString(sampleStart, c->eod - sampleStart, sample, sizeof(sample)); *c->som = 0x1a; sample[sizeof(sample) - 1] = '\0'; fprintf(stderr, "not a double Escape: som pos %d, sample %s, eom - som %d\n", (int) (c->som - c->buf), sample, (int) (eom - c->som)); } goto beastWhileContinue; } } *t++ = *p++; } noEscape = noEscapeStorage; } if (eom > c->eod) // Incomplete message in buffer, retry later break; if (!c->service) { fprintf(stderr, "c->service null hahGh1Sh\n"); return -1; } // if we get some valid data, reduce the garbage counter. if (c->garbage > 128) c->garbage -= 128; // advance to next message c->som = eom; // Have a 0x1a followed by 1/2/3/4/5 - pass message to handler. int res = c->service->read_handler(c, noEscape, c->remote, now, mb); if (!c->service) { return -1; } if (res) { modesCloseClient(c); return -1; } beastWhileContinue: ; } if (c->eod - c->som > 256) { //fprintf(stderr, "beastWhile too much data remaining, garbage?!\n"); c->garbage += c->eod - c->som; Modes.stats_current.remote_malformed_beast += c->eod - c->som; c->som = c->eod; } return 0; } static int readProxy(struct client *c) { char *proxy = strstr(c->som, "PROXY "); char *eop = strstr(c->som, "\r\n"); if (proxy && proxy == c->som) { if (!eop) { // incomplete proxy string (shouldn't happen but let's check anyhow) return -2; } *eop = '\0'; strncpy(c->proxy_string, proxy + 6, sizeof(c->proxy_string) - 1); c->proxy_string[sizeof(c->proxy_string) - 1] = '\0'; // make sure it's null terminated //fprintf(stderr, "%s\n", c->proxy_string); *eop = '\r'; // expected string example: "PROXY TCP4 172.12.2.132 172.191.123.45 40223 30005" char *space = proxy; space = memchr(space + 1, ' ', eop - space - 1); space = memchr(space + 1, ' ', eop - space - 1); space = memchr(space + 1, ' ', eop - space - 1); // hash up to 3rd space if (eop - proxy > 10) { //fprintf(stderr, "%ld %ld %s\n", eop - proxy, space - proxy, space); c->receiverId = fasthash64(proxy, space - proxy, 0x2127599bf4325c37ULL); } c->som = eop + 2; } return 0; } void requestCompression(struct client *c, int64_t now) { //memcpy(c->sendq + c->sendq_len, heartbeat_msg, heartbeat_len); //c->sendq_len += heartbeat_len; flushClient(c, now); } // //========================================================================= // // The message is supposed to be separated from the next message by the // separator 'sep', which is a null-terminated C string. // // Every full message received is decoded and passed to the higher layers // calling the function's 'handler'. // // The handler returns 0 on success, or 1 to signal this function we should // close the connection with the client in case of non-recoverable errors. // // // // static int processClient(struct client *c, int64_t now, struct messageBuffer *mb) { struct net_service *service = c->service; read_mode_t read_mode = service->read_mode; //fprintf(stderr, "processing count %d, buf->id %d\n", c->processing, mb->id); if (now - c->connectedSince < 5 * SECONDS) { // check for PROXY v1 header if connection is new / low bytes received if ((Modes.netIngest || Modes.readProxy) && c->som == c->buf) { if (c->eod - c->som >= 6 && c->som[0] == 'P' && c->som[1] == 'R') { int res = readProxy(c); if (res != 0) { return res; } } } const char *hb = service->heartbeat_in.msg; int hb_len = service->heartbeat_in.len; if (hb && c->eod - c->som >= 5 * hb_len) { int res = 0; for (int k = 0; k < 5; k++) { res += memcmp(hb, c->som + k * hb_len, hb_len); } if (res == 0) { requestCompression(c, now); } } } if (read_mode == READ_MODE_BEAST) { int res = readBeast(c, now, mb); if (res != 0) { return res; } } else if (read_mode == READ_MODE_ASCII) { int res = readAscii(c, now, mb); if (res != 0) { return res; } } else if (read_mode == READ_MODE_IGNORE) { // drop the bytes on the floor c->som = c->eod; } else if (read_mode == READ_MODE_BEAST_COMMAND) { int res = readBeastcommand(c, now, mb); if (res != 0) { return res; } } else if (read_mode == READ_MODE_ASTERIX) { int res = readAsterix(c, now, mb); if (res != 0) { return res; } } else if (read_mode == READ_MODE_PLANEFINDER) { int res = readPlanefinder(c, now, mb); if (res != 0) { return res; } } if (!c->receiverIdLocked && (c->bytesReceived > 512 || now > c->connectedSince + 10000)) { lockReceiverId(c); } return 0; } static void modesReadFromClient(struct client *c, struct messageBuffer *mb) { if (!c->service) { fprintf(stderr, "c->service null jahFuN3e\n"); return; } if (!c->bufferToProcess) { c->bContinue = 1; } for (int k = 0; c->bContinue; k++) { int64_t now = mstime(); // guarantee at least one read before obeying the network time limit if (k > 0 && now > Modes.network_time_limit) { return; } if (Modes.synthetic_now && priorityTasksPending()) { return; } if (!c->service) { return; } if (!c->bufferToProcess) { // get more buffer to process int read = readClient(c, now); //fprintTimePrecise(stderr, now); fprintf(stderr, "readClient returned: %d\n", read); if (!read) { return; } if (c->discard) { continue; } c->som = c->buf; c->eod = c->buf + c->buflen; // one byte past end of data // Always NUL-terminate so we are free to use strstr() // nb: we never fill the last byte of the buffer with read data (see above) so this is safe if (likely(c->buflen < c->bufmax)) { *c->eod = '\0'; } else { fprintf(stderr, "wtf Dieh2hau\n"); } c->bufferToProcess = 1; mb->activeClient = c; } // process buffer c->processing++; //fprintf(stderr, "%d", c->processing); int res = processClient(c, now, mb); c->processing--; c->bufferToProcess = 0; mb->activeClient = NULL; if (res < 0) { return; } if (c->som > c->buf) { // We processed something - so c->buflen = c->eod - c->som; // Update the unprocessed buffer length if (c->buflen > 0) { memmove(c->buf, c->som, c->buflen); // Move what's remaining to the start of the buffer } else { if (c->buflen < 0) { c->buflen = 0; fprintf(stderr, "codepoint Si0wereH\n"); } } c->som = c->buf; c->eod = c->buf + c->buflen; // one byte past end of data // Always NUL-terminate so we are free to use strstr() // nb: we never fill the last byte of the buffer with read data (see above) so this is safe } // disconnect garbage feeds // (only disconnect after a lot of garbage unless --net-ingest is specified) if ((c->garbage >= GARBAGE_THRESHOLD && Modes.netIngest) || c->garbage >= 1024 * 1024) { *c->eod = '\0'; char sample[256]; hexDumpString(c->som, c->eod - c->som, sample, sizeof(sample)); sample[sizeof(sample) - 1] = '\0'; if (c->proxy_string[0] != '\0') { fprintf(stderr, "Garbage: Close: %s sample: %s\n", c->proxy_string, sample); } else { fprintf(stderr, "Garbage: Close: %s port %s sample: %s\n", c->host, c->port, sample); } modesCloseClient(c); return; } } // reset discard status c->discard = 0; } /* static inline unsigned unsigned_difference(unsigned v1, unsigned v2) { return (v1 > v2) ? (v1 - v2) : (v2 - v1); } static inline float heading_difference(float h1, float h2) { float d = fabs(h1 - h2); return (d < 180) ? d : (360 - d); } */ const char *airground_enum_string(airground_t ag) { switch (ag) { case AG_AIRBORNE: return "A+"; case AG_GROUND: return "G+"; default: return "?"; } } static void serviceFreeClients(struct net_service *s) { struct client *c, **prev; for (prev = &s->clients, c = *prev; c; c = *prev) { if (c->fd == -1) { // Recently closed, prune from list *prev = c->next; sfree(c->sendq); sfree(c->buf); sfree(c); } else { prev = &c->next; } } } // Unlink and free closed clients static void netFreeClients() { for (struct net_service *service = Modes.services_out.services; service->descr; service++) { serviceFreeClients(service); } for (struct net_service *service = Modes.services_in.services; service->descr; service++) { serviceFreeClients(service); } } static void handleEpoll(struct net_service_group *group, struct messageBuffer *mb) { // Only process each epoll even in one thread // the variables for this are specific to the service group, // using locking each group can only be processed by one thread at a time // modesReadFromClient can unlock this lock which is fine as the while head // is lock protected while (group->event_progress < Modes.net_event_count) { int k = group->event_progress; group->event_progress += 1; struct epoll_event event = Modes.net_events[k]; if (event.data.ptr == &Modes.exitNowEventfd) { return; } struct client *cl = (struct client *) Modes.net_events[k].data.ptr; if (!cl) { fprintf(stderr, "handleEpoll: epollEvent.data.ptr == NULL\n"); continue; } if (!cl->service) { // client is closed //fprintf(stderr, "handleEpoll(): client closed\n"); continue; } if (cl->service->group != group) { //fprintf(stderr, "handleEpoll(): wrong group\n"); continue; } if (cl->acceptSocket || cl->net_connector_dummyClient) { if (cl->acceptSocket) { modesAcceptClients(cl, mstime()); } if (cl->net_connector_dummyClient) { checkServiceConnected(cl->con, mstime()); } } else { if ((event.events & EPOLLOUT)) { // check if we need to flush a client because the send buffer was full previously if (flushClient(cl, mstime()) < 0) { continue; } } if ((event.events & (EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP))) { modesReadFromClient(cl, mb); } } } } static int64_t checkFlushService(struct net_service *service, int64_t now) { int64_t default_wait = 1000; if (!service->writer) { return now + default_wait; } struct net_writer *writer = service->writer; if (Modes.net_heartbeat_interval && service->heartbeat_out.msg && now - writer->lastWrite >= Modes.net_heartbeat_interval) { // If we have generated no messages for a while, send a heartbeat send_heartbeat(service); } if (writer->dataUsed && now >= writer->nextFlush) { flushWrites(writer); } if (writer->dataUsed) { return writer->nextFlush; } else { return now + default_wait; } } static void decodeTask(void *arg, threadpool_threadbuffers_t *buffer_group) { MODES_NOTUSED(buffer_group); readsb_task_t *info = (readsb_task_t *) arg; struct messageBuffer *mb = &Modes.netMessageBuffer[info->from]; //fprintf(stderr, "%.3f decodeTask %d\n", mstime()/1000.0, mb->id); pthread_mutex_lock(&Modes.decodeLock); //fprintf(stderr, "%.3f decoding %d\n", mstime()/1000.0, mb->id); handleEpoll(&Modes.services_in, mb); for (int kt = 0; kt < Modes.decodeThreads; kt++) { struct messageBuffer *otherbuf = &Modes.netMessageBuffer[kt]; struct client *cl = otherbuf->activeClient; if (cl && cl->service) { modesReadFromClient(cl, mb); } } drainMessageBuffer(mb); pthread_mutex_unlock(&Modes.decodeLock); pthread_mutex_lock(&Modes.outputLock); handleEpoll(&Modes.services_out, mb); pthread_mutex_unlock(&Modes.outputLock); } // // Perform periodic network work // void modesNetPeriodicWork(void) { static int64_t check_flush; static int64_t next_tcp_json; static struct timespec watch; if (!Modes.net_events) { epollAllocEvents(&Modes.net_events, &Modes.net_maxEvents); } int64_t now = mstime(); dump_beast_check(now); int64_t wait_ms; if (Modes.sdr_type != SDR_NONE && Modes.sdr_type != SDR_MODESBEAST && Modes.sdr_type != SDR_GNS) { // NO WAIT WHEN USING AN SDR !! IMPORTANT !! wait_ms = 0; } else { // wait in net-only mode (unless we get network packets, that wakes the wait immediately) wait_ms = imax(0, check_flush - now); // modify wait for next flush timer wait_ms = imin(wait_ms, Modes.next_reconnect_callback - now); // modify wait for reconnect callback timer wait_ms = imax(wait_ms, 0); // don't allow negative values if (0 && Modes.debug_serial) { wait_ms = 1000; } } #ifdef NO_EVENT_FD wait_ms = imin(wait_ms, 100); // no event_fd, limit sleep to 100 ms #endif // unlock decode mutex for waiting in handleEpoll pthread_mutex_unlock(&Threads.decode.mutex); if (priorityTasksPending()) { sched_yield(); } Modes.net_event_count = epoll_wait(Modes.net_epfd, Modes.net_events, Modes.net_maxEvents, (int) wait_ms); Modes.services_in.event_progress = 0; Modes.services_out.event_progress = 0; if (0 && Modes.debug_serial && Modes.net_event_count > 0) { fprintTimePrecise(stderr, mstime()); fprintf(stderr, " event count %d wait_ms %d\n", Modes.net_event_count, (int) wait_ms); } if (0 && Modes.net_event_count > 0) { fprintTimePrecise(stderr, now); fprintf(stderr, " event count %d wait_ms %d\n", Modes.net_event_count, (int) wait_ms); } pthread_mutex_lock(&Threads.decode.mutex); int64_t interval = lapWatch(&watch); now = mstime(); Modes.network_time_limit = now + 100; struct messageBuffer *mb = &Modes.netMessageBuffer[0]; if (Modes.decodeThreads == 1) { handleEpoll(&Modes.services_in, mb); drainMessageBuffer(mb); handleEpoll(&Modes.services_out, mb); } else { readsb_task_t *infos = Modes.decodeTasks->infos; threadpool_task_t *tasks = Modes.decodeTasks->tasks; int taskCount = 0; for (int kt = 0; kt < Modes.decodeThreads; kt++) { threadpool_task_t *task = &tasks[kt]; readsb_task_t *range = &infos[kt]; range->from = kt; task->function = decodeTask; task->argument = range; taskCount++; } struct timespec before = threadpool_get_cumulative_thread_time(Modes.decodePool); threadpool_run(Modes.decodePool, tasks, taskCount); struct timespec after = threadpool_get_cumulative_thread_time(Modes.decodePool); timespec_add_elapsed(&before, &after, &Modes.stats_current.background_cpu); } /* Beast input from local Modes-S Beast via USB */ if (Modes.sdrInitialized && (Modes.sdr_type == SDR_MODESBEAST || Modes.sdr_type == SDR_GNS)) { if (!Modes.serial_client) { if (1 || Modes.debug_serial) { fprintTimePrecise(stderr, mstime()); fprintf(stderr, " serial: creating socket client ... \n"); } Modes.serial_client = createSocketClient(Modes.beast_in_service, Modes.beast_fd, NULL); if (1 || Modes.debug_serial) { fprintTimePrecise(stderr, mstime()); fprintf(stderr, " serial: creating socket client ... done\n"); } } if (Modes.serial_client->service) { modesReadFromClient(Modes.serial_client, mb); drainMessageBuffer(mb); } if (!Modes.serial_client->service) { fprintf(stderr, "Serial client closed unexpectedly, exiting!\n"); setExit(2); } } if (Modes.net_event_count == Modes.net_maxEvents) { epollAllocEvents(&Modes.net_events, &Modes.net_maxEvents); } int64_t elapsed1 = lapWatch(&watch); now = mstime(); pingSenders(Modes.beast_in_service, now); int64_t elapsed2 = lapWatch(&watch); // If we have data that has been waiting to be written for a while, write it now. if (Modes.sdr_type != SDR_NONE || now >= check_flush || Modes.net_event_count > 0) { //fprintTimePrecise(stderr, now); fprintf(stderr, " checkFlush\n"); check_flush = now + 200; for (struct net_service *service = Modes.services_out.services; service->descr; service++) { int64_t nextFlush = checkFlushService(service, now); check_flush = imin(check_flush, nextFlush); } for (struct net_service *service = Modes.services_in.services; service->descr; service++) { int64_t nextFlush = checkFlushService(service, now); check_flush = imin(check_flush, nextFlush); } } if (now >= Modes.next_reconnect_callback) { //fprintTimePrecise(stderr, now); fprintf(stderr, " reconnectCallback\n"); int64_t since_fail = now - Modes.last_connector_fail; if (since_fail < 2 * SECONDS) { Modes.next_reconnect_callback = now + 20 + since_fail * Modes.net_connector_delay_min / ( 3 * SECONDS ); } else { Modes.next_reconnect_callback = now + Modes.net_connector_delay_min; } serviceReconnectCallback(now); } static int64_t next_free_clients; int64_t free_client_interval = 1 * SECONDS; if (now > next_free_clients) { next_free_clients = now + free_client_interval; netFreeClients(); if (Modes.receiverTable) { static uint32_t upcount; int nParts = RECEIVER_MAINTENANCE_INTERVAL / free_client_interval; receiverTimeout((upcount % nParts), nParts, now); upcount++; if (Modes.receiverCount > Modes.receiver_table_size * 3 / 4 && Modes.receiver_table_hash_bits < 16) { // we clear the table, rehashing would be nicer but more complex // this should be fine receiverCleanup(); Modes.receiver_table_hash_bits = 16; receiverInit(); fprintf(stderr, "receiverTable table size increased to: %d\n", Modes.receiver_table_size); } } } int64_t elapsed3 = lapWatch(&watch); static int64_t antiSpam; if ((elapsed1 > 2 * SECONDS || elapsed2 > 150 || elapsed3 > 150 || interval > 1 * SECONDS + Modes.net_output_flush_interval) && now > antiSpam + 5 * SECONDS) { antiSpam = now; fprintf(stderr, "<3>High load: modesNetPeriodicWork() elapsed1/2/3/interval %"PRId64"/%"PRId64"/%"PRId64"/%"PRId64" ms, suppressing for 5 seconds!\n", elapsed1, elapsed2, elapsed3, interval); } // supply JSON to vrs_out writer if (Modes.vrs_out.service && Modes.vrs_out.service->connections && now >= next_tcp_json) { static uint32_t part; static uint32_t count; uint32_t n_parts = 16; // must be 16 :) next_tcp_json = now + Modes.net_output_vrs_interval / n_parts; writeJsonToNet(&Modes.vrs_out, generateVRS(part, n_parts, (count % n_parts / 2 != part % 8))); if (++part == n_parts) { part = 0; count += 2; } } } void writeJsonToNet(struct net_writer *writer, struct char_buffer cb) { int len = cb.len; int written = 0; char *content = cb.buffer; char *pos; int bytes = Modes.writerBufSize; char *p = prepareWrite(writer, bytes); if (!p) { sfree(content); return; } pos = content; while (p && written < len) { if (bytes > len - written) { bytes = len - written; } memcpy(p, pos, bytes); p += bytes; pos += bytes; written += bytes; completeWrite(writer, p); p = prepareWrite(writer, bytes); } flushWrites(writer); sfree(content); } // // =============================== Network IO =========================== // static void *pthreadGetaddrinfo(void *param) { struct net_connector *con = (struct net_connector *) param; struct addrinfo gai_hints; // no flags needed gai_hints.ai_flags = 0; // return both IPv4 and IPv6 addresses gai_hints.ai_family = AF_UNSPEC; // this is for protocol port lookups not for DNS, set to zero gai_hints.ai_protocol = 0; // setting this to zero returns the same address multiple times // just set it to TCP, should work just as well for UDP though gai_hints.ai_socktype = SOCK_STREAM; // these struct members don't seem to apply to the hints passed // they are used to return results but not in the hints gai_hints.ai_addrlen = 0; gai_hints.ai_addr = NULL; gai_hints.ai_canonname = NULL; gai_hints.ai_next = NULL; if (con->use_addr && con->address1) { con->address = con->address1; if (con->port1) con->port = con->port1; con->use_addr = 0; } else { con->address = con->address0; con->port = con->port0; con->use_addr = 1; } con->gai_error = getaddrinfo(con->address, con->port, &gai_hints, &con->addr_info); pthread_mutex_lock(&con->mutex); con->gai_request_done = 1; pthread_mutex_unlock(&con->mutex); return NULL; } static void cleanupService(struct net_service *s) { //fprintf(stderr, "cleanupService %s\n", s->descr); struct client *c = s->clients, *nc; while (c) { nc = c->next; anetCloseSocket(c->fd); c->sendq_len = 0; sfree(c->sendq); sfree(c->buf); sfree(c); c = nc; } if (s->listenSockets) { for (int i = 0; i < s->listener_count; ++i) { struct client *c = &s->listenSockets[i]; // not really a client epoll_ctl(Modes.net_epfd, EPOLL_CTL_DEL, c->fd, &c->epollEvent); anetCloseSocket(s->listener_fds[i]); } sfree(s->listenSockets); } sfree(s->listener_fds); if (s->writer && s->writer->data) { sfree(s->writer->data); } if (s->unixSocket) { unlink(s->unixSocket); sfree(s->unixSocket); } memset(s, 0, sizeof(struct net_service)); } static void serviceGroupCleanup(struct net_service_group *group) { for (struct net_service *service = group->services; service->descr != NULL; service++) { cleanupService(service); } sfree(group->services); memset(group, 0x0, sizeof(struct net_service_group)); } static void cleanupMessageBuffers() { if (Modes.decodeThreads > 1) { pthread_mutex_destroy(&Modes.decodeLock); pthread_mutex_destroy(&Modes.trackLock); pthread_mutex_destroy(&Modes.outputLock); threadpool_destroy(Modes.decodePool); destroy_task_group(Modes.decodeTasks); } for (int k = 0; k < Modes.decodeThreads; k++) { struct messageBuffer *buf = &Modes.netMessageBuffer[k]; sfree(buf->msg); buf->len = 0; buf->alloc = 0; } sfree(Modes.netMessageBuffer); } void cleanupNetwork(void) { cleanupMessageBuffers(); if (Modes.dump_fw) { zstdFwFinishFile(Modes.dump_fw); destroyZstdFw(Modes.dump_fw); } if (!Modes.net) { return; } serviceGroupCleanup(&Modes.services_out); serviceGroupCleanup(&Modes.services_in); close(Modes.net_epfd); for (int i = 0; i < Modes.net_connectors_count; i++) { struct net_connector *con = &Modes.net_connectors[i]; if (con->gai_request_in_progress) { pthread_join(con->thread, NULL); } sfree(con->connect_string); freeaddrinfo(con->addr_info); pthread_mutex_destroy(&con->mutex); } sfree(Modes.net_connectors); sfree(Modes.net_events); Modes.net_connectors_count = 0; } static char *read_uuid(struct client *c, char *p, char *eod) { if (c->receiverIdLocked) { // only allow the receiverId to be set once return p + 32; } unsigned char ch; char *start = p; uint64_t receiverId = 0; uint64_t receiverId2 = 0; // read ascii to binary int j = 0; char *breakReason = ""; for (int i = 0; i < 128 && j < 32; i++) { if (p >= eod) { breakReason = "eod"; break; } ch = *p++; //fprintf(stderr, "%c", ch); if (0x1A == ch) { breakReason = "0x1a"; break; } if ('-' == ch || ' ' == ch) { continue; } unsigned char x = 0xff; if (ch <= 'f' && ch >= 'a') { x = ch - 'a' + 10; } else if (ch <= '9' && ch >= '0') { x = ch - '0'; } else if (ch <= 'F' && ch >= 'A') { x = ch - 'A' + 10; } else { breakReason = "ill"; break; } if (j < 16) receiverId = receiverId << 4 | x; // set 4 bits and shift them up else if (j < 32) receiverId2 = receiverId2 << 4 | x; // set 4 bits and shift them up j++; } int valid = j; if (j < 32) { while (j < 32) { if (j < 16) receiverId = receiverId << 4 | 0; else if (j < 32) receiverId2 = receiverId2 << 4 | 0; j++; } if (1 || valid > 5) { char uuid[64]; // needs 36 chars and null byte sprint_uuid(receiverId, receiverId2, uuid); fprintf(stderr, "read_uuid() incomplete (%s): UUID |%.*s| -> |%s|\n", breakReason, (int) imin(36, eod - start), start, uuid); } } if (valid >= 16) { c->receiverId = receiverId; c->receiverId2 = receiverId2; if (Modes.debug_uuid) { char uuid[64]; // needs 36 chars and null byte sprint_uuid(receiverId, receiverId2, uuid); fprintf(stderr, "reading UUID |%.*s| -> |%s|\n", (int) imin(36, eod - start), start, uuid); //fprintf(stderr, "ADDR %s,%s rId %016"PRIx64" UUID %.*s\n", c->host, c->port, c->receiverId, (int) imin(eod - start, 36), start); } lockReceiverId(c); } return p; } static void outputMessage(struct modesMessage *mm) { // filter messages with unwanted DF types (sbs_in are unknown DF type, filter them all, this is arbitrary but no one cares anyway) if (Modes.filterDF && (mm->sbs_in || !(Modes.filterDFbitset & (1 << mm->msgtype)))) { return; } if (!Modes.net) { return; } struct aircraft *ac = mm->aircraft; if (ac && ac->messages < Modes.net_forward_min_messages) { return; } int noforward = (mm->timestamp == MAGIC_NOFORWARD_TIMESTAMP) && !Modes.beast_forward_noforward; int64_t orig_ts = mm->timestamp; if (Modes.beast_set_noforward_timestamp) { mm->timestamp = MAGIC_NOFORWARD_TIMESTAMP; } // Suppress the first message when using an SDR // messages with crc 0 have an explicit checksum and are more reliable, don't suppress them when there was no CRC fix performed if (!mm->sbs_in && (Modes.net_only || Modes.net_verbatim || (ac && ac->messages > 1) || (mm->crc == 0 && mm->correctedbits == 0) || mm->msgtype == DFTYPE_MODEAC) ) { int is_mlat = (mm->source == SOURCE_MLAT); if (Modes.garbage_ports && (mm->garbage || mm->pos_bad) && !mm->pos_old && Modes.garbage_out.connections) { modesSendBeastOutput(mm, &Modes.garbage_out); } if (ac && (!Modes.sbsReduce || mm->reduce_forward)) { if ((!is_mlat || Modes.forward_mlat_sbs) && Modes.sbs_out.connections) { modesSendSBSOutput(mm, ac, &Modes.sbs_out); } if (is_mlat && Modes.sbs_out_mlat.connections) { modesSendSBSOutput(mm, ac, &Modes.sbs_out_mlat); } } if (!noforward && !is_mlat && (Modes.net_verbatim || mm->correctedbits < 2) && Modes.raw_out.connections) { // Forward 2-bit-corrected messages via raw output only if --net-verbatim is set // Don't ever forward mlat messages via raw output. modesSendRawOutput(mm); } if (!noforward && (!is_mlat || Modes.forward_mlat) && (mm->correctedbits < 2 || Modes.net_verbatim)) { // Forward 2-bit-corrected messages via beast output only if --net-verbatim is set // Forward mlat messages via beast output only if --forward-mlat is set if (Modes.beast_out.connections) { modesSendBeastOutput(mm, &Modes.beast_out); } if (mm->reduce_forward && Modes.beast_reduce_out.connections) { modesSendBeastOutput(mm, &Modes.beast_reduce_out); } } if (Modes.dump_fw && (!Modes.dump_reduce || mm->reduce_forward)) { modesDumpBeastData(mm); } if (Modes.asterix_out.connections && (!Modes.asterixReduce || mm->reduce_forward)){ modesSendAsterixOutput(mm, &Modes.asterix_out); } } if (mm->jsonPositionOutputEmit && Modes.json_out.connections) { jsonPositionOutput(mm, ac); } if (mm->sbs_in && ac) { if (mm->reduce_forward || !Modes.sbsReduce) { if (Modes.sbs_out.connections) { modesSendSBSOutput(mm, ac, &Modes.sbs_out); } struct net_writer *extra_writer = NULL; switch(mm->source) { case SOURCE_SBS: extra_writer = &Modes.sbs_out_replay; break; case SOURCE_MLAT: extra_writer = &Modes.sbs_out_mlat; break; case SOURCE_JAERO: extra_writer = &Modes.sbs_out_jaero; break; case SOURCE_PRIO: extra_writer = &Modes.sbs_out_prio; break; default: extra_writer = NULL; } if (extra_writer && extra_writer->connections) { modesSendSBSOutput(mm, ac, extra_writer); } } } mm->timestamp = orig_ts; } static inline int skipMessage(struct modesMessage *mm) { if (Modes.debug_yeet && mm->addr % 0x100 != 0xd) { return 1; } if (Modes.process_only != BADDR && mm->addr != Modes.process_only) { return 1; } if (Modes.receiver_focus && mm->receiverId != Modes.receiver_focus) { return 1; } return 0; } static void drainMessageBuffer(struct messageBuffer *buf) { //fprintf(stderr, "drainMessageBuffer: %d\n", buf->len); if (Modes.decodeThreads < 2) { for (int k = 0; k < buf->len; k++) { struct modesMessage *mm = &buf->msg[k]; if (skipMessage(mm)) { continue; } trackUpdateFromMessage(mm); } for (int k = 0; k < buf->len; k++) { struct modesMessage *mm = &buf->msg[k]; if (skipMessage(mm)) { continue; } outputMessage(mm); } buf->len = 0; } else { pthread_mutex_unlock(&Modes.decodeLock); sched_yield(); //fprintf(stderr, "thread %d draining\n", buf->id); pthread_mutex_lock(&Modes.trackLock); for (int k = 0; k < buf->len; k++) { struct modesMessage *mm = &buf->msg[k]; if (skipMessage(mm)) { continue; } trackUpdateFromMessage(mm); } pthread_mutex_unlock(&Modes.trackLock); pthread_mutex_lock(&Modes.outputLock); for (int k = 0; k < buf->len; k++) { struct modesMessage *mm = &buf->msg[k]; if (skipMessage(mm)) { continue; } outputMessage(mm); } pthread_mutex_unlock(&Modes.outputLock); buf->len = 0; pthread_mutex_lock(&Modes.decodeLock); //fprintf(stderr, "thread %d drain done, back to decoding\n", buf->id); } } // get a zeroed spot in in the message buffer, only messages from this buffer may be passed to netUseMessage struct modesMessage *netGetMM(struct messageBuffer *buf) { struct modesMessage *mm = &buf->msg[buf->len]; memset(mm, 0x0, sizeof(struct modesMessage)); mm->messageBuffer = buf; return mm; } // //========================================================================= // // When a new message is available, because it was decoded from the RTL device, // file, or received in the TCP input port, or any other way we can receive a // decoded message, we call this function in order to use the message. // // Basically this function passes a raw message to the upper layers for further // processing and visualization // void netUseMessage(struct modesMessage *mm) { struct messageBuffer *buf = mm->messageBuffer; if (mm != &buf->msg[buf->len]) { fprintf(stderr, "FATAL: fix netUseMessage / get_mm\n"); exit(1); } buf->len++; if (buf->len == buf->alloc) { drainMessageBuffer(buf); } } void netDrainMessageBuffers() { for (int kt = 0; kt < Modes.decodeThreads; kt++) { struct messageBuffer *mb = &Modes.netMessageBuffer[kt]; drainMessageBuffer(mb); } } readsb-3.16/net_io.h000066400000000000000000000167661505057307600143610ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // net_io.h: network handling. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014,2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #ifndef NETIO_H #define NETIO_H #include // Describes a networking service (group of connections) struct aircraft; struct modesMessage; struct client; struct net_service; struct net_service_group; struct messageBuffer; typedef int (*read_fn)(struct client *, char *, int, int64_t, struct messageBuffer *); typedef enum { READ_MODE_IGNORE, READ_MODE_BEAST, READ_MODE_BEAST_COMMAND, READ_MODE_ASCII, READ_MODE_ASTERIX, READ_MODE_PLANEFINDER } read_mode_t; typedef struct { const char *msg; int32_t len; } heartbeat_t; // Describes one network service (a group of clients with common behaviour) struct net_service { const char *descr; struct net_service_group *group; struct net_writer *writer; // shared writer state int listener_count; // number of listeners int pusher_count; // Number of push servers connected to int connections; // number of active clients int serial_service; // 1 if this is a service for serial devices read_mode_t read_mode; read_fn read_handler; int read_sep_len; const char *read_sep; // hander details for input data struct client *clients; // linked list of clients connected to this service int *listener_fds; // listening FDs struct client *listenSockets; // dummy client structs for all open sockets for epoll commonality char* unixSocket; // path of unix socket int sendqOverrideSize; int recvqOverrideSize; heartbeat_t heartbeat_in; heartbeat_t heartbeat_out; }; #define NET_SERVICE_GROUP_MAX 16 struct net_service_group { struct net_service *services; int len; int alloc; int event_progress; }; // Structure used to describe a networking client struct client { struct client* next; // Pointer to next client struct net_service *service; // Service this client is part of char *buf; // read buffer char *som; char *eod; int buflen; // Amount of data on read buffer int bufmax; // size of the read buffer int fd; // File descriptor int8_t bufferToProcess; int8_t remote; int8_t serial; int8_t bContinue; int8_t discard; int8_t processing; int8_t acceptSocket; // not really a client but rather an accept Socket ... only fd and epollEvent will be valid int8_t net_connector_dummyClient; // dummy client used by net_connector int8_t pingEnabled; int8_t modeac_requested; // 1 if this Beast output connection has asked for A/C int8_t receiverIdLocked; // receiverId has been transmitted by other side. int8_t unreasonable_messagerate; int8_t dropHalfDrop; int64_t dropHalfUntil; char *sendq; // Write buffer - allocated later int sendq_len; // Amount of data in SendQ int sendq_max; // Max size of SendQ uint32_t ping; // only 24 bit are ever sent uint32_t pong; // only 24 bit are ever sent int64_t recentMessages; int64_t recentMessagesReset; int64_t unreasonableRateReset; int64_t pingReceived; int64_t pongReceived; uint64_t bytesSent; uint64_t bytesFromWriter; uint64_t bytesReceived; uint64_t receiverId; uint64_t receiverId2; int64_t last_send; int64_t last_read; // This is used on write-only clients to help check for dead connections int64_t last_read_flush; int64_t connectedSince; uint64_t messageCounter; // counter for incoming data uint64_t positionCounter; // counter for incoming data uint64_t garbage; // amount of garbage we have received from this client int64_t rtt; // last reported rtt in milliseconds double latest_rtt; // in milliseconds, pseudo average with factor 0.9 // crude IIR pseudo rolling average, old value factor 0.995 // cumulative weigth of last 100 packets is 0.39 // cumulative weigth of last 300 packets is 0.78 // cumulative weigth of last 600 packets is 0.95 // usually around 300 packets a minute for an ro-interval of 0.2 double recent_rtt; // in milliseconds struct epoll_event epollEvent; struct net_connector *con; char proxy_string[256]; // store string received from PROXY protocol v1 (v2 not supported currently) char host[NI_MAXHOST]; // For logging char port[NI_MAXSERV]; int64_t dropHalfAntiSpam; }; // Client connection struct net_connector { char *connect_string; char *address; char *address0; char *address1; char *port; char *port0; char *port1; char *protocol; struct net_service *service; struct client* c; int use_addr; int connected; int connecting; uint32_t fail_counter; int silent_fail; int fd; int64_t next_reconnect; int64_t connect_timeout; int64_t lastConnect; // timestamp for last connection establish int64_t backoff; int64_t lastResolve; char resolved_addr[NI_MAXHOST+3]; struct addrinfo *addr_info; struct addrinfo *try_addr; // pointer walking addr_info list int gai_error; int gai_request_in_progress; int gai_request_done; pthread_t thread; pthread_mutex_t mutex; struct client dummyClient; // client struct for epoll connection handling before we have a fully established connection int enable_uuid_ping; char *uuid; }; // Common writer state for all output sockets of one type struct net_writer { void *data; // shared write buffer, sized MODES_OUT_BUF_SIZE int dataUsed; // number of bytes of write buffer currently used int connections; // number of active clients struct net_service *service; // owning service int64_t lastWrite; // time of last write to clients int64_t nextFlush; int64_t flushInterval; uint64_t lastReceiverId; int noTimestamps; }; void serviceListen (struct net_service *service, char *bind_addr, char *bind_ports, int epfd); void serviceClose(struct net_service *s); void sendBeastSettings (int fd, const char *settings); void sendData(struct net_writer *output, char *data, int len); void modesInitNet (void); void modesQueueOutput (struct modesMessage *mm); void jsonPositionOutput(struct modesMessage *mm, struct aircraft *a); void modesNetPeriodicWork (void); void cleanupNetwork(void); void writeJsonToNet(struct net_writer *writer, struct char_buffer cb); // GNS HULC status message typedef union __packed { unsigned char buf[24]; struct _packed { uint32_t serial; uint16_t flags; uint16_t reserved; uint32_t epoch; uint32_t latitude; uint32_t longitude; uint16_t altitude; uint8_t satellites; uint8_t hdop; } status; } hulc_status_msg_t; void netUseMessage(struct modesMessage *mm); void netDrainMessageBuffers(); struct modesMessage *netGetMM(struct messageBuffer *buf); #endif readsb-3.16/nogrind.sh000077500000000000000000000011501505057307600147070ustar00rootroot00000000000000#!/bin/bash systemctl stop test rm -rf /run/test mkdir -p /run/test chown readsb /run/test source /etc/default/test cp -f readsb /tmp/test123 #gdb -ex=run --args /tmp/test123 --quiet $RECEIVER_OPTIONS $DECODER_OPTIONS $NET_OPTIONS $JSON_OPTIONS $@ gdb -batch -ex 'set confirm off' -ex 'handle SIGTERM nostop print pass' -ex 'handle SIGINT nostop print pass' -ex run -ex 'bt full' --args \ /tmp/test123 --quiet $RECEIVER_OPTIONS $DECODER_OPTIONS $NET_OPTIONS $JSON_OPTIONS $@ #sudo -u readsb ./readsb --quiet $RECEIVER_OPTIONS $DECODER_OPTIONS $NET_OPTIONS $JSON_OPTIONS --net-connector 127.0.0.1,50006,beast_in $@ readsb-3.16/oneoff/000077500000000000000000000000001505057307600141675ustar00rootroot00000000000000readsb-3.16/oneoff/convert_benchmark.c000066400000000000000000000122211505057307600200230ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // convert_benchmark.c: benchmarks for IQ sample converters // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // // This file 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 // any later version. // // This file 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 . #include "../readsb.h" static void **testdata_uc8; static void **testdata_sc16; static void **testdata_sc16q11; static uint16_t *outdata; // SC16Q11_TABLE_BITS notes: // 11 bits (8MB) gives you full precision, but a large table that doesn't fit in cache // 9 bits (512kB) will fit in the Pi 2/3's shared L2 cache // (but there will be contention from other cores) // 8 bits (128kB) will fit in the Pi 1's L2 cache // 7 bits (32kB) will fit in the Pi 1/2/3's L1 cache // Sample results for "SC16Q11, no DC": // Core i7-3610QM @ 2300MHz // SC16Q11_TABLE_BITS undefined: 152.80M samples/second // SC16Q11_TABLE_BITS=11: 101.22M samples/second // SC16Q11_TABLE_BITS=9: 243.04M samples/second // SC16Q11_TABLE_BITS=8: 316.84M samples/second // SC16Q11_TABLE_BITS=7: 375.70M samples/second // Pi3B @ 1200MHz // SC16Q11_TABLE_BITS undefined: 22.19M samples/second // SC16Q11_TABLE_BITS=11: 5.86M samples/second // SC16Q11_TABLE_BITS=9: 19.33M samples/second // SC16Q11_TABLE_BITS=8: 33.50M samples/second // SC16Q11_TABLE_BITS=7: 59.78M samples/second // Pi1B @ 700MHz // SC16Q11_TABLE_BITS undefined: 5.24M samples/second // SC16Q11_TABLE_BITS=11: 2.53M samples/second // SC16Q11_TABLE_BITS=9: 3.23M samples/second // SC16Q11_TABLE_BITS=8: 5.77M samples/second // SC16Q11_TABLE_BITS=7: 10.23M samples/second void prepare() { srand(1); testdata_uc8 = calloc(10, sizeof(void*)); testdata_sc16 = calloc(10, sizeof(void*)); testdata_sc16q11 = calloc(10, sizeof(void*)); outdata = calloc(MODES_MAG_BUF_SAMPLES, sizeof(uint16_t)); for (int buf = 0; buf < 10; ++buf) { uint8_t *uc8 = calloc(MODES_MAG_BUF_SAMPLES, 2); testdata_uc8[buf] = uc8;; uint16_t *sc16 = calloc(MODES_MAG_BUF_SAMPLES, 4); testdata_sc16[buf] = sc16; uint16_t *sc16q11 = calloc(MODES_MAG_BUF_SAMPLES, 4); testdata_sc16q11[buf] = sc16q11; for (unsigned i = 0; i < MODES_MAG_BUF_SAMPLES; ++i) { double I = 2.0 * rand() / (RAND_MAX + 1.0) - 1.0; double Q = 2.0 * rand() / (RAND_MAX + 1.0) - 1.0; uc8[i*2] = (uint8_t) (I * 128 + 128); uc8[i*2+1] = (uint8_t) (Q * 128 + 128); sc16[i*2] = htole16( (int16_t) (I * 32768.0) ); sc16[i*2+1] = htole16( (int16_t) (Q * 32768.0) ); sc16q11[i*2] = htole16( (int16_t) (I * 2048.0) ); sc16q11[i*2+1] = htole16( (int16_t) (Q * 2048.0) ); } } } void test(const char *what, input_format_t format, void **data, double sample_rate, bool filter_dc) { fprintf(stderr, "Benchmarking: %s ", what); struct converter_state *state; iq_convert_fn converter = init_converter(format, sample_rate, filter_dc, &state); if (!converter) { fprintf(stderr, "Can't initialize converter\n"); return; } struct timespec total = { 0, 0 }; int iterations = 0; // Run it once to force init. converter(data[0], outdata, MODES_MAG_BUF_SAMPLES, state, NULL, NULL); while (total.tv_sec < 5) { fprintf(stderr, "."); struct timespec start; start_cpu_timing(&start); for (int i = 0; i < 10; ++i) { converter(data[i], outdata, MODES_MAG_BUF_SAMPLES, state, NULL, NULL); } end_cpu_timing(&start, &total); iterations++; } fprintf(stderr, "\n"); cleanup_converter(&state); double samples = 10.0 * iterations * MODES_MAG_BUF_SAMPLES; double nanos = total.tv_sec * 1e9 + total.tv_nsec; fprintf(stderr, " %.2fM samples in %.6f seconds\n", samples / 1e6, nanos / 1e9); fprintf(stderr, " %.2fM samples/second\n", samples / nanos * 1e3); } int main(int argc, char **argv) { MODES_NOTUSED(argc); MODES_NOTUSED(argv); prepare(); test("SC16Q11, DC", INPUT_SC16Q11, testdata_sc16q11, 2400000, true); test("SC16Q11, no DC", INPUT_SC16Q11, testdata_sc16q11, 2400000, false); test("UC8, DC", INPUT_UC8, testdata_uc8, 2400000, true); test("UC8, no DC", INPUT_UC8, testdata_uc8, 2400000, false); test("SC16, DC", INPUT_SC16, testdata_sc16, 2400000, true); test("SC16, no DC", INPUT_SC16, testdata_sc16, 2400000, false); } readsb-3.16/oneoff/decode_comm_b.c000066400000000000000000000111751505057307600170770ustar00rootroot00000000000000#include #include "../readsb.h" #include "../comm_b.h" char last_callsign[8]; double last_callsign_ts = 0; double last_track = -1; double last_track_ts = 0; double last_magnetic = -1; double last_magnetic_ts = 0; double last_gs = -1; double last_gs_ts = 0; double last_ias = -1; double last_ias_ts = 0; double last_tas = -1; double last_tas_ts = 0; double last_mach = -1; double last_mach_ts = 0; double angle_difference(double h1, double h2) { float delta = fabs(h1 - h2); if (delta > 180.0) delta = 360.0 - delta; return delta; } void process(double timestamp, const char *line, struct modesMessage *mm) { decodeCommB(mm); printf("line\t%s\tformat\t", line); switch (mm->commb_format) { #define EMIT(x) case COMMB_ ## x: printf("%s", #x); break EMIT(UNKNOWN); EMIT(AMBIGUOUS); EMIT(EMPTY_RESPONSE); EMIT(DATALINK_CAPS); EMIT(GICB_CAPS); EMIT(AIRCRAFT_IDENT); EMIT(ACAS_RA); EMIT(VERTICAL_INTENT); EMIT(TRACK_TURN); EMIT(HEADING_SPEED); #undef EMIT default: printf("%s", "UNHANDLED"); break; } int suspicious = 0; if (mm->callsign_valid) { printf("\tcallsign\t%s", mm->callsign); if ((timestamp - last_callsign_ts) < 30.0 && strcmp(last_callsign, mm->callsign)) { suspicious = 1; } memcpy(last_callsign, mm->callsign, sizeof(last_callsign)); last_callsign_ts = timestamp; } if (mm->heading_valid && mm->heading_type == HEADING_GROUND_TRACK) { printf("\ttrack\t%.1f", mm->heading); if ((timestamp - last_track_ts) < 10.0 && angle_difference(last_track, mm->heading) > 45) { suspicious = 1; } if ((timestamp - last_magnetic_ts) < 10.0 && angle_difference(last_magnetic, mm->heading) > 45) { suspicious = 1; } last_track = mm->heading; last_track_ts = timestamp; } if (mm->heading_valid && mm->heading_type == HEADING_MAGNETIC) { printf("\tmagnetic\t%.1f", mm->heading); if ((timestamp - last_magnetic_ts) < 10.0 && angle_difference(last_magnetic, mm->heading) > 45) { suspicious = 1; } if ((timestamp - last_track_ts) < 10.0 && angle_difference(last_track, mm->heading) > 45) { suspicious = 1; } last_magnetic = mm->heading; last_magnetic_ts = timestamp; } if (mm->track_rate_valid) { printf("\ttrack_rate\t%.2f", mm->track_rate); } if (mm->roll_valid) { printf("\troll\t%.1f", mm->roll); } if (mm->gs_valid) { printf("\tgs\t%.1f", mm->gs.selected); if ((timestamp - last_gs_ts) < 10.0 && fabs(last_gs - mm->gs.selected) > 50) { suspicious = 1; } last_gs = mm->gs.selected; last_gs_ts = timestamp; } if (mm->ias_valid) { printf("\tias\t%d", mm->ias); if ((timestamp - last_ias_ts) < 10.0 && abs(last_ias - mm->ias) > 50) { suspicious = 1; } last_ias = mm->ias; last_ias_ts = timestamp; } if (mm->tas_valid) { printf("\ttas\t%d", mm->tas); if ((timestamp - last_tas_ts) < 10.0 && abs(last_tas - mm->tas) > 50) { suspicious = 1; } last_tas = mm->tas; last_tas_ts = timestamp; } if (mm->mach_valid) { printf("\tmach\t%.3f", mm->mach); if ((timestamp - last_mach_ts) < 10.0 && abs(last_mach - mm->mach) > 0.1) { suspicious = 1; } last_mach = mm->mach; last_mach_ts = timestamp; } if (suspicious) { printf("\tsuspicious\tyes!"); } printf("\n"); } int main(int argc, char **argv) { /* unused */ (void)argc; /* unused */ (void)argv; char line[1024]; while (fgets(line, sizeof(line), stdin)) { if (line[strlen(line)-1] == '\n') { line[strlen(line)-1] = '\0'; } double timestamp = 0; int index = 0; if (sscanf(line, "%lf %n", ×tamp, &index) < 1) { goto bad; } char *hex = line + index; static struct modesMessage mmZero; struct modesMessage mm = mmZero; for (unsigned i = 0; i < sizeof(mm.MB); ++i) { if (!isxdigit(hex[i*2]) || !isxdigit(hex[i*2 + 1])) { goto bad; } unsigned xvalue = 0; if (sscanf(hex + i*2, "%2x", &xvalue) < 1) { goto bad; } mm.MB[i] = xvalue; } process(timestamp, line, &mm); continue; bad: fprintf(stderr, "failed to scan line: %s", line); continue; } } readsb-3.16/oneoff/extract-comm-b.py000077500000000000000000000007671505057307600174000ustar00rootroot00000000000000#!/usr/bin/env python3 # Run me like this: # viewadsb --no-interactive | ./extract-comm-b.py import re, sys, time from contextlib import closing commb_match = re.compile(r'^DF:\d+ addr:([a-zA-Z0-9]{6}) FS:\d+ DR:\d+ UM:\d+ (?:ID|AC):\d+ MB:([a-zA-Z0-9]{14})$') for line in sys.stdin: match = commb_match.match(line) if match: addr, mb = match.groups() with closing(open('commb/' + addr.upper() + '.txt', 'a')) as f: print('%.3f %s' % (time.time(), mb), file=f) readsb-3.16/readsb.c000066400000000000000000003463471505057307600143400ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // readsb.c: main program & miscellany // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014-2016 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "readsb.h" #include "help.h" #include #include struct _Modes Modes; struct _Threads Threads; static void loadReplaceState(); static void checkReplaceState(); static void backgroundTasks(int64_t now); static error_t parse_opt(int key, char *arg, struct argp_state *state); // // ============================= Utility functions ========================== // static void cleanup_and_exit(int code); void setExit(int arg) { // Signal to main loop that program is exiting soon, delay depends on cli arguments // see main loop for details Modes.exitSoon = arg; #ifndef NO_EVENT_FD uint64_t one = 1; ssize_t res = write(Modes.exitSoonEventfd, &one, sizeof(one)); MODES_NOTUSED(res); #endif } static void exitHandler(int sig) { setExit(1); char *sigX = NULL; if (sig == SIGTERM) { sigX = "SIGTERM"; } if (sig == SIGINT) { sigX = "SIGINT"; } if (sig == SIGQUIT) { sigX = "SIGQUIT"; } if (sig == SIGHUP) { sigX = "SIGHUP"; } log_with_timestamp("Caught %s, shutting down...", sigX); } static void adjustUserLocationAccuracy(int verbose) { char *accString = ""; if (Modes.json_location_accuracy == 2) { accString = "exact"; } else if (Modes.json_location_accuracy == 1) { accString = "rounded to 2 decimals"; Modes.fUserLat = nearbyint(Modes.fUserLat * 1E2) / 1E2; Modes.fUserLon = nearbyint(Modes.fUserLon * 1E2) / 1E2; } else if (Modes.json_location_accuracy == 3) { accString = "rounded to 1 decimal"; Modes.fUserLat = nearbyint(Modes.fUserLat * 1E1) / 1E1; Modes.fUserLon = nearbyint(Modes.fUserLon * 1E1) / 1E1; } else if (Modes.json_location_accuracy == 4) { accString = "rounded to 0 decimals"; Modes.fUserLat = nearbyint(Modes.fUserLat * 1E0) / 1E0; Modes.fUserLon = nearbyint(Modes.fUserLon * 1E0) / 1E0; } else { Modes.json_location_accuracy = 0; accString = "rounded to 0 decimals, only used internally"; Modes.fUserLat = nearbyint(Modes.fUserLat * 1E0) / 1E0; Modes.fUserLon = nearbyint(Modes.fUserLon * 1E0) / 1E0; } if (verbose >= VERBOSE) { fprintf(stderr, "Using lat: %.6f, lon: %.6f (location accuracy: %s)\n", Modes.fUserLat, Modes.fUserLon, accString); } } void receiverPositionChanged(float lat, float lon, float alt) { log_with_timestamp("Autodetected receiver location: %.5f, %.5f at %.0fm AMSL", lat, lon, alt); adjustUserLocationAccuracy(SILENT); if (Modes.json_dir) { free(writeJsonToFile(Modes.json_dir, "receiver.json", generateReceiverJson()).buffer); // location changed } } // // =============================== Initialization =========================== // static void configSetDefaults(void) { // Default everything to zero/NULL memset(&Modes, 0, sizeof (Modes)); // Now initialise things that should not be 0/NULL to their defaults Modes.gain = MODES_MAX_GAIN; Modes.acHashBits = AIRCRAFT_HASH_BITS; Modes.acBuckets = 1 << Modes.acHashBits; // this is critical for hashing purposes Modes.receiver_table_hash_bits = 6; // dynamically resized, start very small Modes.freq = MODES_DEFAULT_FREQ; Modes.check_crc = 1; Modes.net_heartbeat_interval = MODES_NET_HEARTBEAT_INTERVAL; //Modes.db_file = strdup("/usr/local/share/tar1090/git-db/aircraft.csv.gz"); Modes.db_file = NULL; Modes.latString = strdup(""); Modes.lonString = strdup(""); Modes.net_input_raw_ports = strdup("0"); Modes.net_output_raw_ports = strdup("0"); Modes.net_output_uat_replay_ports = strdup("0"); Modes.net_input_uat_ports = strdup("0"); Modes.net_output_sbs_ports = strdup("0"); Modes.net_input_sbs_ports = strdup("0"); Modes.net_input_beast_ports = strdup("0"); Modes.net_input_planefinder_ports = strdup("0"); Modes.net_output_beast_ports = strdup("0"); Modes.net_output_beast_reduce_ports = strdup("0"); Modes.net_output_beast_reduce_interval = 250; Modes.beast_reduce_filter_distance = -1; Modes.beast_reduce_filter_altitude = -1; Modes.net_output_vrs_ports = strdup("0"); Modes.net_output_vrs_interval = 5 * SECONDS; Modes.net_output_json_ports = strdup("0"); Modes.net_output_api_ports = strdup("0"); Modes.net_input_jaero_ports = strdup("0"); Modes.net_output_jaero_ports = strdup("0"); Modes.net_connector_delay = 15 * 1000; Modes.interactive_display_ttl = MODES_INTERACTIVE_DISPLAY_TTL; Modes.json_interval = 1000; Modes.json_location_accuracy = 2; Modes.maxRange = 1852 * 450; // 450 nmi default max range Modes.nfix_crc = 1; Modes.biastee = 0; Modes.position_persistence = 4; Modes.tcpBuffersAuto = 1; Modes.net_sndbuf_size = 1; Modes.net_output_flush_size = 1280; // Default to 1280 Bytes Modes.net_output_flush_interval = 50; // Default to 50 ms Modes.net_output_flush_interval_beast_reduce = -1; // default to net_output_flush_interval after config parse if not configured Modes.netReceiverId = 0; Modes.netIngest = 0; Modes.ingestLimitRate = 5420; Modes.json_trace_interval = 20 * 1000; Modes.traceLastMax = 96; Modes.beforeLandHighRes = 128; Modes.afterGroundTransitionHighRes = 60 * SECONDS; Modes.state_write_interval = 1 * HOURS; Modes.heatmap_current_interval = -15; Modes.heatmap_interval = 60 * SECONDS; Modes.json_reliable = -13; Modes.acasFD1 = -1; // set to -1 so it's clear we don't have that fd Modes.acasFD2 = -1; // set to -1 so it's clear we don't have that fd Modes.sbsOverrideSquawk = -1; Modes.mlatForceInterval = 30 * SECONDS; Modes.mlatForceDistance = 25 * 1e3; // 25 km Modes.fUserAlt = -2e6; Modes.enable_zstd = 1; Modes.currentTask = "unset"; Modes.joinTimeout = 30 * SECONDS; Modes.state_chunk_size = 12 * 1024 * 1024; Modes.state_chunk_size_read = Modes.state_chunk_size; Modes.decodeThreads = 1; Modes.filterDF = 0; Modes.filterDFbitset = 0; Modes.cpr_focus = BADDR; Modes.leg_focus = BADDR; Modes.trace_focus = BADDR; Modes.show_only = BADDR; Modes.process_only = BADDR; Modes.outline_json = 1; // enable by default Modes.range_outline_duration = 24 * HOURS; //Modes.receiver_focus = 0x123456; // Modes.trackExpireJaero = TRACK_EXPIRE_JAERO; Modes.fixDF = 1; sdrInitConfig(); reset_stats(&Modes.stats_current); for (int i = 0; i < 90; ++i) { reset_stats(&Modes.stats_10[i]); } //receiverTest(); struct rlimit limits; int res = getrlimit(RLIMIT_NOFILE, &limits); if (res != 0) { fprintf(stderr, "WARNING: getrlimit(RLIMIT_NOFILE, &limits) returned the following error: \"%s\". Assuming bad limit query function, using up to 64 file descriptors \n", strerror(errno)); Modes.max_fds = 64; } else { uint64_t limit = limits.rlim_cur; // check limit for unreasonableness if (limit < (uint64_t) 64) { fprintf(stderr, "WARNING: getrlimit(RLIMIT_NOFILE, &limits) returned less than 64 fds for readsb to work with. Assuming bad limit query function, using up to 64 file descriptors. Fix RLIMIT!\n"); Modes.max_fds = 64; } else if (limits.rlim_cur == RLIM_INFINITY) { Modes.max_fds = INT32_MAX; } else if (limit > (uint64_t) INT32_MAX) { fprintf(stderr, "WARNING: getrlimit(RLIMIT_NOFILE, &limits) returned more than INT32_MAX ... This is just weird.\n"); Modes.max_fds = INT32_MAX; } else { Modes.max_fds = (int) limit; } } Modes.max_fds -= 32; // reserve some fds for things we don't account for later like json writing. // this is an high estimate ... if ppl run out of fds for other stuff they should up rlimit Modes.sdr_buf_size = 16 * 16 * 1024; // in seconds, default to 1 hour Modes.dump_interval = 60 * 60; Modes.dump_compressionLevel = 4; Modes.ping_reduce = PING_REDUCE; Modes.ping_reject = PING_REJECT; Modes.binCraftVersion = 20250403; Modes.messageRateMult = 1.0f; Modes.apiShutdownDelay = 0 * SECONDS; // default this on Modes.enableAcasCsv = 1; Modes.enableAcasJson = 1; } // //========================================================================= // static void modesInit(void) { //fprintf(stderr, "sizeof aircraftBack %ld\n", sizeof(struct aircraftBack)); int64_t now = mstime(); Modes.next_stats_update = roundSeconds(10, 5, now + 10 * SECONDS); Modes.next_stats_display = now + Modes.stats_display_interval; if (Modes.keep_traces || Modes.netReceiverId || Modes.acHashBits > 14) { Modes.thp = 1; } if (Modes.thp) { Modes.aircraft = cmMmap(Modes.acBuckets * sizeof(struct aircraft *)); memset(Modes.aircraft, 0x0, Modes.acBuckets * sizeof(struct aircraft *)); } else { Modes.aircraft = cmCalloc(Modes.acBuckets * sizeof(struct aircraft *)); } pthread_mutex_init(&Modes.traceDebugMutex, NULL); pthread_mutex_init(&Modes.hungTimerMutex, NULL); pthread_mutex_init(&Modes.sdrControlMutex, NULL); pthread_mutex_init(&Modes.aircraftBackMutex, NULL); pthread_mutex_init(&Modes.aircraftCreateMutex, NULL); threadInit(&Threads.reader, "reader"); threadInit(&Threads.upkeep, "upkeep"); threadInit(&Threads.decode, "decode"); threadInit(&Threads.json, "json"); threadInit(&Threads.globeJson, "globeJson"); threadInit(&Threads.globeBin, "globeBin"); threadInit(&Threads.misc, "misc"); threadInit(&Threads.apiUpdate, "apiUpdate"); if (Modes.json_globe_index || Modes.netReceiverId || Modes.acHashBits >= 16) { Modes.allPoolSize = imin(8, Modes.num_procs); } else { Modes.allPoolSize = 1; } Modes.allTasks = allocate_task_group(2 * Modes.allPoolSize); Modes.allPool = threadpool_create(Modes.allPoolSize, 4); if (Modes.json_globe_index) { Modes.globeLists = cmCalloc(sizeof(struct craftArray) * (GLOBE_MAX_INDEX + 1)); for (int i = 0; i <= GLOBE_MAX_INDEX; i++) { ca_init(&Modes.globeLists[i]); } } ca_init(&Modes.aircraftActive); geomag_init(); Modes.sample_rate = (double)2400000.0; // Allocate the various buffers used by Modes Modes.trailing_samples = (unsigned)((MODES_PREAMBLE_US + MODES_LONG_MSG_BITS + 16) * 1e-6 * Modes.sample_rate); if (!Modes.net_only) { for (int i = 0; i < MODES_MAG_BUFFERS; ++i) { size_t alloc = (Modes.sdr_buf_samples + Modes.trailing_samples) * sizeof (uint16_t); if ((Modes.mag_buffers[i].data = cmalloc(alloc)) == NULL) { fprintf(stderr, "Out of memory allocating magnitude buffer.\n"); exit(1); } memset(Modes.mag_buffers[i].data, 0, alloc); Modes.mag_buffers[i].length = 0; Modes.mag_buffers[i].dropped = 0; Modes.mag_buffers[i].sampleTimestamp = 0; } } // Prepare error correction tables modesChecksumInit(Modes.nfix_crc); icaoFilterInit(); modeACInit(); icaoFilterAdd(Modes.show_only); if (Modes.json_globe_index) { init_globe_index(); } quickInit(); if (Modes.outline_json) { Modes.rangeDirs = cmCalloc(RANGEDIRSSIZE); } } static void lockThreads() { for (int i = 0; i < Modes.lockThreadsCount; i++) { //fprintf(stderr, "locking %s\n", Modes.lockThreads[i]->name); Modes.currentTask = Modes.lockThreads[i]->name; pthread_mutex_lock(&Modes.lockThreads[i]->mutex); } } static void unlockThreads() { for (int i = Modes.lockThreadsCount - 1; i >= 0; i--) { //fprintf(stderr, "unlocking %s\n", Modes.lockThreads[i]->name); pthread_mutex_unlock(&Modes.lockThreads[i]->mutex); } } static int64_t ms_until_priority() { int64_t now = mstime(); int64_t mono = mono_milli_seconds(); int64_t ms_until_run = imin( Modes.next_stats_update - now, Modes.next_remove_stale - mono ); if (Modes.replace_state_inhibit_traces_until) { if (mono > Modes.replace_state_inhibit_traces_until) { Modes.replace_state_inhibit_traces_until = 0; } else if (ms_until_run > 100) { ms_until_run = 80; } } if (ms_until_run > 0 && Modes.replace_state_blob) { ms_until_run = 0; } return ms_until_run; } // function to check if priorityTasksRun() needs to run int priorityTasksPending() { if (ms_until_priority() <= 0) { return 1; // something to do } else { return 0; // nothing to do } } // // Entry point for periodic updates // void priorityTasksRun() { if (0) { int64_t now = mstime(); time_t nowTime = floor(now / 1000.0); struct tm local; gmtime_r(&nowTime, &local); char timebuf[512]; strftime(timebuf, 128, "%T", &local); printf("priorityTasksRun: utcTime: %s.%03lld epoch: %.3f\n", timebuf, (long long) now % 1000, now / 1000.0); } pthread_mutex_lock(&Modes.hungTimerMutex); startWatch(&Modes.hungTimer1); pthread_mutex_unlock(&Modes.hungTimerMutex); Modes.currentTask = "priorityTasks_start"; { int64_t mono = mono_milli_seconds(); // reset allocated buffers every minute static int64_t next_buffer_reset; if (mono > next_buffer_reset) { next_buffer_reset = mono + 1 * MINUTES; threadpool_reset_buffers(Modes.allPool); } } // stop all threads so we can remove aircraft from the list. // adding aircraft does not need to be done with locking: // the worst case is that the newly added aircraft is skipped as it's not yet // in the cache used by the json threads. Modes.currentTask = "locking"; lockThreads(); Modes.currentTask = "locked"; int64_t now = mstime(); int64_t mono = mono_milli_seconds(); int removed_stale = 0; static int64_t last_periodic_mono; int64_t periodic_interval = mono - last_periodic_mono; if (periodic_interval > 5 * SECONDS && periodic_interval < 9999 * HOURS && last_periodic_mono && !(Modes.syntethic_now_suppress_errors && Modes.synthetic_now)) { fprintf(stderr, "<3> priorityTasksRun didn't run for %.1f seconds!\n", periodic_interval / 1000.0); } last_periodic_mono = mono; struct timespec watch; startWatch(&watch); struct timespec start_time; start_monotonic_timing(&start_time); struct timespec before = threadpool_get_cumulative_thread_time(Modes.allPool); if (Modes.replace_state_blob) { loadReplaceState(); checkReplaceState(); } // finish db update under lock if (dbFinishUpdate()) { // don't do trackRemoveStale at the same time as dbFinishUpdate } else if (mono >= Modes.next_remove_stale) { pthread_mutex_lock(&Modes.hungTimerMutex); startWatch(&Modes.hungTimer2); pthread_mutex_unlock(&Modes.hungTimerMutex); Modes.currentTask = "trackRemoveStale"; trackRemoveStale(now); traceDelete(); int64_t interval = mono - Modes.next_remove_stale; if (interval > 5 * SECONDS && interval < 9999 * HOURS && Modes.next_remove_stale && !(Modes.syntethic_now_suppress_errors && Modes.synthetic_now)) { fprintf(stderr, "<3> removeStale didn't run for %.1f seconds!\n", interval / 1000.0); } Modes.next_remove_stale = mono + REMOVE_STALE_INTERVAL; removed_stale = 1; } int64_t elapsed1 = lapWatch(&watch); if (now >= Modes.next_stats_update) { Modes.updateStats = 1; } if (Modes.updateStats) { Modes.currentTask = "statsUpdate"; statsUpdate(now); // needs to happen under lock } int64_t elapsed2 = lapWatch(&watch); Modes.currentTask = "unlocking"; unlockThreads(); Modes.currentTask = "unlocked"; static int64_t antiSpam; if (0 || (Modes.debug_removeStaleDuration) || ((elapsed1 > 150 || elapsed2 > 150) && mono > antiSpam + 30 * SECONDS)) { fprintf(stderr, "<3>High load: removeStale took %"PRIi64"/%"PRIi64" ms! stats: %d (suppressing for 30 seconds)\n", elapsed1, elapsed2, Modes.updateStats); antiSpam = mono; } //fprintf(stderr, "running for %ld ms\n", getUptime()); //fprintf(stderr, "removeStale took %"PRIu64" ms, running for %ld ms\n", elapsed, getUptime()); if (Modes.updateStats) { Modes.currentTask = "statsReset"; statsResetCount(); int64_t now = mstime(); Modes.currentTask = "statsCount"; statsCountAircraft(now); Modes.currentTask = "statsProcess"; statsProcess(now); Modes.updateStats = 0; if (Modes.json_dir) { free(writeJsonToFile(Modes.json_dir, "status.json", generateStatusJson(now)).buffer); free(writeJsonToFile(Modes.json_dir, "status.prom", generateStatusProm(now)).buffer); } } end_monotonic_timing(&start_time, &Modes.stats_current.remove_stale_cpu); if (removed_stale) { struct timespec after = threadpool_get_cumulative_thread_time(Modes.allPool); timespec_add_elapsed(&before, &after, &Modes.stats_current.remove_stale_cpu); } Modes.currentTask = "priorityTasks_end"; } // //========================================================================= // // We read data using a thread, so the main thread only handles decoding // without caring about data acquisition // static void *readerEntryPoint(void *arg) { MODES_NOTUSED(arg); srandom(get_seed()); if (!sdrOpen()) { Modes.sdrOpenFailed = 1; setExit(2); // unexpected exit log_with_timestamp("sdrOpen() failed, exiting!"); return NULL; } setPriorityPthread(); if (sdrHasRun()) { sdrRun(); // Wake the main thread (if it's still waiting) if (!Modes.exit) setExit(2); // unexpected exit } else { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); pthread_mutex_lock(&Threads.reader.mutex); while (!Modes.exit) { threadTimedWait(&Threads.reader, &ts, 15 * SECONDS); } pthread_mutex_unlock(&Threads.reader.mutex); } sdrClose(); return NULL; } static void *jsonEntryPoint(void *arg) { MODES_NOTUSED(arg); srandom(get_seed()); // set this thread low priority setLowestPriorityPthread(); int64_t next_history = mstime(); struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); pthread_mutex_lock(&Threads.json.mutex); threadpool_buffer_t pass_buffer = { 0 }; threadpool_buffer_t zstd_buffer = { 0 }; ZSTD_CCtx* cctx = NULL; if (Modes.enable_zstd) { //if (Modes.debug_zstd) { fprintf(stderr, "calling ZSTD_createCCtx()\n"); } cctx = ZSTD_createCCtx(); //if (Modes.debug_zstd) { fprintf(stderr, "ZSTD_createCCtx() returned %p\n", cctx); } } while (!Modes.exit) { struct timespec start_time; start_cpu_timing(&start_time); int64_t now = mstime(); // old direct creation, slower when creating json for an aircraft more than once //struct char_buffer cb = generateAircraftJson(0); if (Modes.onlyBin < 2) { // new way: use the apiBuffer of json fragments struct char_buffer cb = apiGenerateAircraftJson(&pass_buffer); if (Modes.json_gzip) { writeJsonToGzip(Modes.json_dir, "aircraft.json.gz", cb, 2); } writeJsonToFile(Modes.json_dir, "aircraft.json", cb); if ((Modes.legacy_history || ((ALL_JSON) && Modes.onlyBin < 2)) && now >= next_history) { char filebuf[PATH_MAX]; snprintf(filebuf, PATH_MAX, "history_%d.json", Modes.json_aircraft_history_next); writeJsonToFile(Modes.json_dir, filebuf, cb); if (!Modes.json_aircraft_history_full) { free(writeJsonToFile(Modes.json_dir, "receiver.json", generateReceiverJson()).buffer); // number of history entries changed if (Modes.json_aircraft_history_next == HISTORY_SIZE - 1) Modes.json_aircraft_history_full = 1; } Modes.json_aircraft_history_next = (Modes.json_aircraft_history_next + 1) % HISTORY_SIZE; next_history = now + HISTORY_INTERVAL; } } if (Modes.debug_recent) { struct char_buffer cb = generateAircraftJson(1 * SECONDS); writeJsonToFile(Modes.json_dir, "aircraft_recent.json", cb); sfree(cb.buffer); } struct char_buffer cb3 = generateAircraftBin(&pass_buffer); if (Modes.enableBinGz) { writeJsonToGzip(Modes.json_dir, "aircraft.binCraft", cb3, 1); } //fprintf(stderr, "uncompressed size %ld\n", (long) cb3.len); if (Modes.enable_zstd) { writeJsonToFile(Modes.json_dir, "aircraft.binCraft.zst", generateZstd(cctx, &zstd_buffer, cb3, 1)); } if (Modes.json_globe_index) { struct char_buffer cb2 = generateGlobeBin(-1, 1, &pass_buffer); if (Modes.enableBinGz) { writeJsonToGzip(Modes.json_dir, "globeMil_42777.binCraft", cb2, 1); } if (Modes.enable_zstd) { writeJsonToFile(Modes.json_dir, "globeMil_42777.binCraft.zst", ident(generateZstd(cctx, &zstd_buffer, cb2, 1))); } } end_cpu_timing(&start_time, &Modes.stats_current.aircraft_json_cpu); //fprintTimePrecise(stderr, mstime()); //fprintf(stderr, " wrote --write-json-every stuff to --write-json \n"); // we should exit this wait early due to a cond_signal from api.c threadTimedWait(&Threads.json, &ts, Modes.json_interval * 3); } ZSTD_freeCCtx(cctx); free_threadpool_buffer(&zstd_buffer); free_threadpool_buffer(&pass_buffer); pthread_mutex_unlock(&Threads.json.mutex); return NULL; } static void *globeJsonEntryPoint(void *arg) { MODES_NOTUSED(arg); srandom(get_seed()); // set this thread low priority setLowestPriorityPthread(); if (Modes.onlyBin > 0) return NULL; pthread_mutex_lock(&Threads.globeJson.mutex); threadpool_buffer_t pass_buffer = { 0 }; struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); while (!Modes.exit) { struct timespec start_time; start_cpu_timing(&start_time); for (int j = 0; j <= Modes.json_globe_indexes_len; j++) { int index = Modes.json_globe_indexes[j]; char filename[32]; snprintf(filename, 31, "globe_%04d.json", index); struct char_buffer cb = apiGenerateGlobeJson(index, &pass_buffer); writeJsonToGzip(Modes.json_dir, filename, cb, 1); } end_cpu_timing(&start_time, &Modes.stats_current.globe_json_cpu); // we should exit this wait early due to a cond_signal from api.c threadTimedWait(&Threads.globeJson, &ts, Modes.json_interval * 3); } free_threadpool_buffer(&pass_buffer); pthread_mutex_unlock(&Threads.globeJson.mutex); return NULL; } static void *globeBinEntryPoint(void *arg) { MODES_NOTUSED(arg); srandom(get_seed()); // set this thread low priority setLowestPriorityPthread(); int part = 0; int n_parts = 8; // power of 2 int64_t sleep_ms = Modes.json_interval / n_parts; pthread_mutex_lock(&Threads.globeBin.mutex); threadpool_buffer_t pass_buffer = { 0 }; threadpool_buffer_t zstd_buffer = { 0 }; ZSTD_CCtx* cctx = NULL; if (Modes.enable_zstd) { cctx = ZSTD_createCCtx(); } struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); while (!Modes.exit) { char filename[32]; struct timespec start_time; start_cpu_timing(&start_time); for (int j = 0; j < Modes.json_globe_indexes_len; j++) { if (j % n_parts != part) continue; int index = Modes.json_globe_indexes[j]; struct char_buffer cb2 = generateGlobeBin(index, 0, &pass_buffer); if (Modes.enableBinGz) { snprintf(filename, 31, "globe_%04d.binCraft", index); writeJsonToGzip(Modes.json_dir, filename, cb2, 1); } if (Modes.enable_zstd) { snprintf(filename, 31, "globe_%04d.binCraft.zst", index); writeJsonToFile(Modes.json_dir, filename, ident(generateZstd(cctx, &zstd_buffer, cb2, 1))); } struct char_buffer cb3 = generateGlobeBin(index, 1, &pass_buffer); if (Modes.enableBinGz) { snprintf(filename, 31, "globeMil_%04d.binCraft", index); writeJsonToGzip(Modes.json_dir, filename, cb3, 1); } if (Modes.enable_zstd) { snprintf(filename, 31, "globeMil_%04d.binCraft.zst", index); writeJsonToFile(Modes.json_dir, filename, ident(generateZstd(cctx, &zstd_buffer, cb3, 1))); } } part++; part %= n_parts; end_cpu_timing(&start_time, &Modes.stats_current.bin_cpu); threadTimedWait(&Threads.globeBin, &ts, sleep_ms); } ZSTD_freeCCtx(cctx); free_threadpool_buffer(&zstd_buffer); free_threadpool_buffer(&pass_buffer); pthread_mutex_unlock(&Threads.globeBin.mutex); return NULL; } static void gainStatistics(struct mag_buf *buf) { static uint64_t loudEvents; static uint64_t noiseLowSamples; static uint64_t noiseHighSamples; static uint64_t totalSamples; static int slowRise; static int64_t nextRaiseAgc; static float loudRebound; loudEvents += buf->loudEvents; noiseLowSamples += buf->noiseLowSamples; noiseHighSamples += buf->noiseHighSamples; totalSamples += buf->length; double interval = 0.5; double riseTime = 15; double reboundTime = 1.5; if (totalSamples < interval * Modes.sample_rate) { return; } double noiseLowPercent = noiseLowSamples / (double) totalSamples * 100.0; double noiseHighPercent = noiseHighSamples / (double) totalSamples * 100.0; if (!Modes.autoGain) { goto reset; } // 29 gain values for typical rtl-sdr // allow startup to sweep entire range quickly, almost half it for double steps int noiseLow = noiseLowPercent > 5; // too many samples < noiseLowThreshold int noiseHigh = noiseHighPercent < 1; // too few samples < noiseHighThreshold int loud = loudEvents > 0; int veryLoud = loudEvents > 5; if (loud || noiseHigh) { Modes.lowerGain = 1; if (veryLoud && !Modes.gainStartup) { Modes.lowerGain = 2; } if (loud) { loudRebound += Modes.lowerGain; } } else if (noiseLow) { if ( Modes.gainStartup || slowRise >= riseTime / interval || (loudRebound > 1 && slowRise >= reboundTime / interval) ) { slowRise = 0; Modes.increaseGain = 1; if (loudRebound > 0) { loudRebound *= 0.95f; loudRebound -= Modes.increaseGain; } } else { slowRise++; } } if (Modes.increaseGain && Modes.gain == 496 && buf->sysTimestamp < nextRaiseAgc) { goto reset; } if (Modes.increaseGain || Modes.lowerGain) { if (Modes.gainStartup) { Modes.lowerGain *= Modes.gainStartup; Modes.increaseGain *= Modes.gainStartup; } char *reason = ""; if (veryLoud) { reason = "decreasing gain, many strong signals found: "; } else if (loud) { reason = "decreasing gain, strong signal found: "; } else if (noiseHigh) { reason = "decreasing gain, noise too high: "; } else if (noiseLow) { reason = "increasing gain, noise too low: "; } sdrSetGain(reason); if (Modes.gain == MODES_RTL_AGC) { // switching to AGC is only done every 5 minutes to avoid oscillations due to the large step nextRaiseAgc = buf->sysTimestamp + 5 * MINUTES; } if (0) { fprintf(stderr, "%s noiseLow: %5.2f %% noiseHigh: %5.2f %% loudEvents: %4lld\n", reason, noiseLowPercent, noiseHighPercent, (long long) loudEvents); } } reset: Modes.gainStartup /= 2; loudEvents = 0; noiseLowSamples = 0; noiseHighSamples = 0; totalSamples = 0; } static void timingStatistics(struct mag_buf *buf) { static int64_t last_ts; int64_t elapsed_ts = buf->sysMicroseconds - last_ts; // nominal time in us between two SDR callbacks int64_t nominal = Modes.sdr_buf_samples * 1000LL * 1000LL / Modes.sample_rate; int64_t jitter = elapsed_ts - nominal; if (last_ts && Modes.log_usb_jitter && fabs((double)jitter) > Modes.log_usb_jitter) { fprintf(stderr, "libusb callback jitter: %6.0f us\n", (double) jitter); } static int64_t last_sys; if (last_sys || buf->sampleTimestamp * (1 / 12e6) > 10) { static int64_t last_sample; static int64_t interval; int64_t nominal_interval = 30 * SECONDS * 1000; if (!last_sys) { last_sys = buf->sysMicroseconds; last_sample = buf->sampleTimestamp; interval = nominal_interval; } double elapsed_sys = buf->sysMicroseconds - last_sys; // every 30 seconds if ((elapsed_sys > interval && fabs((double) jitter) < 100) || elapsed_sys > interval * 3 / 2) { // adjust interval heuristically interval += (nominal_interval - elapsed_sys) / 4; double elapsed_sample = buf->sampleTimestamp - last_sample; double freq_ratio = elapsed_sample / (elapsed_sys * 12.0); double diff_us = elapsed_sample / 12.0 - elapsed_sys; double ppm = (freq_ratio - 1) * 1e6; Modes.estimated_ppm = ppm; if (Modes.devel_log_ppm && fabs(ppm) > Modes.devel_log_ppm) { fprintf(stderr, "SDR ppm: %8.1f elapsed: %6.0f ms diff: %6.0f us last jitter: %6.0f\n", ppm, elapsed_sys / 1000.0, diff_us, (double) jitter); } if (fabs(ppm) > 600) { if (ppm < -1000) { int packets_lost = (int) nearbyint(ppm / -1820); Modes.stats_current.samples_lost += packets_lost * Modes.sdr_buf_samples; fprintf(stderr, "Lost %d packets (%.1f us) on USB, MLAT could be UNSTABLE, check sync! (ppm: %.0f)" "(or the system clock jumped for some reason)\n", packets_lost, diff_us, ppm); } else { fprintf(stderr, "SDR ppm out of specification (could cause MLAT issues) or local clock jumped / not syncing with ntp or chrony! ppm: %.0f\n", ppm); } } last_sys = buf->sysMicroseconds; last_sample = buf->sampleTimestamp; } } last_ts = buf->sysMicroseconds; } static void *decodeEntryPoint(void *arg) { MODES_NOTUSED(arg); srandom(get_seed()); pthread_mutex_lock(&Threads.decode.mutex); modesInitNet(); /* If the user specifies --net-only, just run in order to serve network * clients without reading data from the RTL device. * This rules also in case a local Mode-S Beast is connected via USB. */ //fprintf(stderr, "startup complete after %.3f seconds.\n", getUptime() / 1000.0); interactiveInit(); struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); int64_t now = mstime(); int64_t mono = mono_milli_seconds(); if (Modes.net_only) { while (!Modes.exit) { struct timespec start_time; // in case we're not waiting in backgroundTasks and priorityTasks doesn't have a chance to schedule mono = mono_milli_seconds(); if (mono > Modes.next_remove_stale + 5 * SECONDS) { threadTimedWait(&Threads.decode, &ts, 1); } start_cpu_timing(&start_time); // sleep via epoll_wait in net_periodic_work now = mstime(); backgroundTasks(now); end_cpu_timing(&start_time, &Modes.stats_current.background_cpu); } } else { int watchdogCounter = 200; // roughly 20 seconds while (!Modes.exit) { struct timespec start_time; lockReader(); // reader is locked, and possibly we have data. // copy out reader CPU time and reset it add_timespecs(&Modes.reader_cpu_accumulator, &Modes.stats_current.reader_cpu, &Modes.stats_current.reader_cpu); Modes.reader_cpu_accumulator.tv_sec = 0; Modes.reader_cpu_accumulator.tv_nsec = 0; struct mag_buf *buf = NULL; if (Modes.first_free_buffer != Modes.first_filled_buffer) { // FIFO is not empty, process one buffer. buf = &Modes.mag_buffers[Modes.first_filled_buffer]; } else { buf = NULL; } unlockReader(); if (buf) { start_cpu_timing(&start_time); demodulate2400(buf); if (Modes.mode_ac) { demodulate2400AC(buf); } gainStatistics(buf); timingStatistics(buf); Modes.stats_current.samples_lost += Modes.sdr_buf_samples - buf->length; Modes.stats_current.samples_processed += buf->length; Modes.stats_current.samples_dropped += buf->dropped; end_cpu_timing(&start_time, &Modes.stats_current.demod_cpu); // Mark the buffer we just processed as completed. lockReader(); Modes.first_filled_buffer = (Modes.first_filled_buffer + 1) % MODES_MAG_BUFFERS; pthread_cond_signal(&Threads.reader.cond); unlockReader(); watchdogCounter = 100; // roughly 10 seconds } else { // Nothing to process this time around. if (--watchdogCounter <= 0) { fprintf(stderr, "<3>SDR wedged, exiting! (check power supply / avoid using an USB extension / SDR might be defective)\n"); setExit(2); break; } } start_cpu_timing(&start_time); now = mstime(); backgroundTasks(now); end_cpu_timing(&start_time, &Modes.stats_current.background_cpu); lockReader(); int newData = (Modes.first_free_buffer != Modes.first_filled_buffer); unlockReader(); if (!newData) { /* wait for more data. * we should be getting data every 50-60ms. wait for max 80 before we give up and do some background work. * this is fairly aggressive as all our network I/O runs out of the background work! */ threadTimedWait(&Threads.decode, &ts, 80); } mono = mono_milli_seconds(); if (mono > Modes.next_remove_stale + REMOVE_STALE_INTERVAL) { //fprintf(stderr, "%.3f >? %3.f\n", mono / 1000.0, (Modes.next_remove_stale + REMOVE_STALE_INTERVAL)/ 1000.0); // don't force as this can cause issues (code left in for possible re-enabling if absolutely necessary) // if memory serves right the main point of this was for SDR_IFILE / faster than real time if (Modes.synthetic_now) { pthread_mutex_unlock(&Threads.decode.mutex); priorityTasksRun(); pthread_mutex_lock(&Threads.decode.mutex); } } } sdrCancel(); } pthread_mutex_unlock(&Threads.decode.mutex); return NULL; } static void traceWriteTask(void *arg, threadpool_threadbuffers_t *buffer_group) { readsb_task_t *info = (readsb_task_t *) arg; if (mono_milli_seconds() > Modes.traceWriteTimelimit) { return; } struct aircraft *a; // increment info->from to mark this part of the task as finshed for (int j = info->from; j < info->to; j++, info->from++) { for (a = Modes.aircraft[j]; a; a = a->next) { if (Modes.triggerPastDayTraceWrite && a->trace_len > 0 && !a->initialTraceWriteDone) { a->trace_writeCounter = 0xc0ffee; a->trace_write |= (WRECENT | WMEM); } if (a->trace_write) { int64_t before = mono_milli_seconds(); if (before > Modes.traceWriteTimelimit) { return; } traceWrite(a, buffer_group); int64_t elapsed = mono_milli_seconds() - before; if (elapsed > 4 * SECONDS) { fprintf(stderr, "<3>traceWrite() for %06x took %.1f s!\n", a->addr, elapsed / 1000.0); } } } } } static void writeTraces(int64_t mono) { static int lastRunFinished; static int part; static int64_t lastCompletion; static int64_t nextResetCycleDuration; static int firstRunDone; if (!Modes.tracePool) { // reduce priority int oldPrio = getpriority(PRIO_PROCESS, 0); setpriority(PRIO_PROCESS, 0, oldPrio + 10); Modes.tracePoolSize = imin(8, imax(1, Modes.num_procs * 3 / 4)); Modes.tracePool = threadpool_create(Modes.tracePoolSize, 4); Modes.traceTasks = allocate_task_group(8 * Modes.tracePoolSize); lastRunFinished = 1; lastCompletion = mono; // restore previous priority setpriority(PRIO_PROCESS, 0, oldPrio); } int taskCount = Modes.traceTasks->task_count; threadpool_task_t *tasks = Modes.traceTasks->tasks; readsb_task_t *infos = Modes.traceTasks->infos; // how long until we want to have checked every aircraft if a trace needs to be written int completeTime = 4 * SECONDS; // how many invocations we get in that timeframe int invocations = imax(1, completeTime / PERIODIC_UPDATE); // how many parts we want to split the complete workload into int n_parts = taskCount * invocations; int thread_section_len = Modes.acBuckets / n_parts; int extra = Modes.acBuckets % n_parts; // only assign new task if we finished the last set of tasks if (lastRunFinished) { for (int i = 0; i < taskCount; i++) { int64_t elapsed = mono - lastCompletion; if (elapsed > Modes.writeTracesActualDuration) { nextResetCycleDuration = mono + 60 * SECONDS; Modes.writeTracesActualDuration = elapsed; } if (part >= n_parts) { part = 0; if (mono > nextResetCycleDuration) { nextResetCycleDuration = mono + 60 * SECONDS; Modes.writeTracesActualDuration = elapsed; } if (Modes.triggerPastDayTraceWrite) { // deactivate after one sweep Modes.triggerPastDayTraceWrite = 0; fprintf(stderr, "Wrote live traces for aircraft active within the last 24 hours. This took %.1f seconds (roughly %.0f minutes).\n", elapsed / 1000.0, elapsed / (double) MINUTES); } if (!firstRunDone) { firstRunDone = 1; fprintf(stderr, "Wrote live traces for aircraft active within the last 15 minutes. This took %.1f seconds (roughly %.0f minutes).\n", elapsed / 1000.0, elapsed / (double) MINUTES); Modes.triggerPastDayTraceWrite = 1; // activated for one sweep } if (elapsed > 30 * SECONDS && getUptime() > 10 * MINUTES) { fprintf(stderr, "trace writing iteration took %.1f seconds (roughly %.0f minutes), live traces will lag behind (historic traces are fine), " "consider alloting more CPU cores or increasing json-trace-interval!\n", elapsed / 1000.0, elapsed / (double) MINUTES); } lastCompletion = mono; } threadpool_task_t *task = &tasks[i]; readsb_task_t *range = &infos[i]; int thread_start = part * thread_section_len + imin(extra, part); int thread_end = thread_start + thread_section_len + (part < extra ? 1 : 0); part++; //fprintf(stderr, "%8d %8d %8d\n", thread_start, thread_end, Modes.acBuckets); if (thread_end > Modes.acBuckets) { thread_end = Modes.acBuckets; fprintf(stderr, "check traceWriteTask distribution\n"); } range->from = thread_start; range->to = thread_end; task->function = traceWriteTask; task->argument = range; if (part >= n_parts) { if (thread_end != Modes.acBuckets || i != taskCount - 1) { fprintf(stderr, "check traceWriteTask distribution\n"); } } } } struct timespec before = threadpool_get_cumulative_thread_time(Modes.tracePool); threadpool_run(Modes.tracePool, tasks, taskCount); struct timespec after = threadpool_get_cumulative_thread_time(Modes.tracePool); timespec_add_elapsed(&before, &after, &Modes.stats_current.trace_json_cpu); lastRunFinished = 1; for (int i = 0; i < taskCount; i++) { readsb_task_t *range = &infos[i]; if (range->from != range->to) { lastRunFinished = 0; } } //fprintf(stderr, "n_parts %4d part %4d lastRunFinished %d\n", n_parts, part, lastRunFinished); // reset allocated buffers every minute static int64_t next_buffer_reset; if (mono > next_buffer_reset) { next_buffer_reset = mono + 1 * MINUTES; threadpool_reset_buffers(Modes.tracePool); } } static void *upkeepEntryPoint(void *arg) { MODES_NOTUSED(arg); srandom(get_seed()); pthread_mutex_lock(&Threads.upkeep.mutex); Modes.lockThreads[Modes.lockThreadsCount++] = &Threads.misc; Modes.lockThreads[Modes.lockThreadsCount++] = &Threads.apiUpdate; Modes.lockThreads[Modes.lockThreadsCount++] = &Threads.globeJson; Modes.lockThreads[Modes.lockThreadsCount++] = &Threads.globeBin; Modes.lockThreads[Modes.lockThreadsCount++] = &Threads.json; Modes.lockThreads[Modes.lockThreadsCount++] = &Threads.decode; if (Modes.lockThreadsCount > LOCK_THREADS_MAX) { fprintf(stderr, "FATAL: LOCK_THREADS_MAX insufficient!\n"); exit(1); } struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); while (!Modes.exit) { priorityTasksRun(); if (Modes.writeTraces) { // writing a trace takes some time, to increase timing precision the priority tasks, allot a little less time than available // this isn't critical though int64_t time_alloted = ms_until_priority(); if (time_alloted > 50) { int64_t mono = mono_milli_seconds(); Modes.traceWriteTimelimit = mono + time_alloted; struct timespec watch; startWatch(&watch); Modes.currentTask = "writeTraces_start"; writeTraces(mono); Modes.currentTask = "writeTraces_end"; int64_t elapsed = stopWatch(&watch); if (elapsed > 4 * SECONDS) { fprintf(stderr, "<3>writeTraces() took %"PRIu64" ms!\n", elapsed); } } } checkReplaceState(); int64_t wait = ms_until_priority(); if (Modes.synthetic_now) { wait = 5000; } if (0) { fprintf(stderr, "upkeep wait: %ld ms\n", (long) wait); } if (wait > 0) { clock_gettime(CLOCK_REALTIME, &ts); threadTimedWait(&Threads.upkeep, &ts, wait); } } pthread_mutex_unlock(&Threads.upkeep.mutex); return NULL; } // // ============================== Snip mode ================================= // // Get raw IQ samples and filter everything is < than the specified level // for more than 256 samples in order to reduce example file size // static void snipMode(int level) { int i, q; uint64_t c = 0; while ((i = getchar()) != EOF && (q = getchar()) != EOF) { if (abs(i - 127) < level && abs(q - 127) < level) { c++; if (c > MODES_PREAMBLE_SIZE) continue; } else { c = 0; } putchar(i); putchar(q); } } // //========================================================================= // // This function is called a few times every second by main in order to // perform tasks we need to do continuously, like accepting new clients // from the net, refreshing the screen in interactive mode, and so forth // static void backgroundTasks(int64_t now) { if (Modes.net) { modesNetPeriodicWork(); } // Refresh screen when in interactive mode static int64_t next_interactive; if (Modes.interactive && now > next_interactive) { interactiveShowData(); next_interactive = now + 42; } static int64_t next_flip = 0; if (now >= next_flip) { icaoFilterExpire(); next_flip = now + MODES_ICAO_FILTER_TTL; } static int64_t next_every_second; if (now > next_every_second) { next_every_second = now + 1 * SECONDS; checkNewDayAcas(now); if (Modes.mode_ac) { trackMatchAC(now); } } } static void print_commandline(int argc, char **argv) { fprintf(stderr, "invoked by: "); for (int k = 0; k < argc; k++) { fprintf(stderr, "%s", argv[k]); if (k < argc - 1) { fprintf(stderr, " "); } } fprintf(stderr, "\n"); } //========================================================================= // Clean up memory prior to exit. static void cleanup_and_exit(int code) { if (Modes.acasFD1 > -1) close(Modes.acasFD1); if (Modes.acasFD2 > -1) close(Modes.acasFD2); // Free any used memory geomag_destroy(); interactiveCleanup(); cleanup_globe_index(); sfree(Modes.dev_name); sfree(Modes.filename); sfree(Modes.prom_file); sfree(Modes.json_dir); sfree(Modes.globe_history_dir); sfree(Modes.heatmap_dir); sfree(Modes.dump_beast_dir); sfree(Modes.state_dir); sfree(Modes.globalStatsCount.rssi_table); sfree(Modes.net_bind_address); sfree(Modes.db_file); sfree(Modes.net_input_beast_ports); sfree(Modes.net_input_planefinder_ports); sfree(Modes.net_output_beast_ports); sfree(Modes.net_output_beast_reduce_ports); sfree(Modes.net_output_vrs_ports); sfree(Modes.net_input_raw_ports); sfree(Modes.net_output_raw_ports); sfree(Modes.net_output_uat_replay_ports); sfree(Modes.net_input_uat_ports); sfree(Modes.net_output_sbs_ports); sfree(Modes.net_input_sbs_ports); sfree(Modes.net_input_jaero_ports); sfree(Modes.net_output_jaero_ports); sfree(Modes.net_output_json_ports); sfree(Modes.net_output_api_ports); sfree(Modes.net_output_asterix_ports); sfree(Modes.garbage_ports); sfree(Modes.beast_serial); sfree(Modes.uuidFile); sfree(Modes.dbIndex); sfree(Modes.dbRaw.buffer); sfree(Modes.db); sfree(Modes.latString); sfree(Modes.lonString); sfree(Modes.rangeDirs); int i; for (i = 0; i < MODES_MAG_BUFFERS; ++i) { sfree(Modes.mag_buffers[i].data); } crcCleanupTables(); receiverCleanup(); if (Modes.json_globe_index) { for (int i = 0; i <= GLOBE_MAX_INDEX; i++) { ca_destroy(&Modes.globeLists[i]); } } ca_destroy(&Modes.aircraftActive); icaoFilterDestroy(); quickDestroy(); sfree(Modes.globeLists); if (Modes.thp) { cmMunmap(Modes.aircraft, Modes.acBuckets * sizeof(struct aircraft *)); } else { sfree(Modes.aircraft); } freeAircraftBack(); exit(code); } static int make_net_connector(char *arg) { if (!Modes.net_connectors || Modes.net_connectors_count + 1 > Modes.net_connectors_size) { Modes.net_connectors_size = Modes.net_connectors_count * 2 + 8; Modes.net_connectors = realloc(Modes.net_connectors, sizeof(struct net_connector) * (size_t) Modes.net_connectors_size); if (!Modes.net_connectors) { fprintf(stderr, "realloc error net_connectors\n"); exit(1); } } struct net_connector *con = &Modes.net_connectors[Modes.net_connectors_count++]; memset(con, 0x0, sizeof(struct net_connector)); con->connect_string = strdup(arg); int maxTokens = 128; char* token[maxTokens]; tokenize(&con->connect_string, ",", token, maxTokens); int m = 0; for(int k = 0; k < 128 && token[k]; k++) { if (strcmp(token[k], "silent_fail") == 0) { con->silent_fail = 1; continue; // don't increase m counter } if (strncmp(token[k], "uuid=", 5) == 0) { con->uuid = token[k] + 5; //fprintf(stderr, "con->uuid: %s\n", con->uuid); continue; // don't increase m counter } if (m == 0) { con->address = con->address0 = token[k]; } if (m == 1) { con->port = con->port0 = token[k]; } if (m == 2) { con->protocol = token[k]; } if (m == 3) { con->address1 = token[k]; } if (m == 4) { con->port1 = token[k]; } m++; } if (pthread_mutex_init(&con->mutex, NULL)) { fprintf(stderr, "Unable to initialize connector mutex!\n"); exit(1); } //fprintf(stderr, "%d %s\n", Modes.net_connectors_count, con->protocol); if (!con->address || !con->port || !con->protocol) { fprintf(stderr, "--net-connector: Wrong format: %s\n", arg); fprintf(stderr, "Correct syntax: --net-connector=ip,port,protocol\n"); return 1; } if (strcmp(con->protocol, "beast_out") != 0 && strcmp(con->protocol, "beast_reduce_out") != 0 && strcmp(con->protocol, "beast_reduce_plus_out") != 0 && strcmp(con->protocol, "beast_in") != 0 && strcmp(con->protocol, "raw_out") != 0 && strcmp(con->protocol, "raw_in") != 0 && strcmp(con->protocol, "vrs_out") != 0 && strcmp(con->protocol, "sbs_in") != 0 && strcmp(con->protocol, "sbs_in_mlat") != 0 && strcmp(con->protocol, "sbs_in_jaero") != 0 && strcmp(con->protocol, "sbs_in_prio") != 0 && strcmp(con->protocol, "sbs_out") != 0 && strcmp(con->protocol, "sbs_out_replay") != 0 && strcmp(con->protocol, "sbs_out_mlat") != 0 && strcmp(con->protocol, "sbs_out_jaero") != 0 && strcmp(con->protocol, "sbs_out_prio") != 0 && strcmp(con->protocol, "asterix_out") != 0 && strcmp(con->protocol, "asterix_in") != 0 && strcmp(con->protocol, "json_out") != 0 && strcmp(con->protocol, "feedmap_out") != 0 && strcmp(con->protocol, "gpsd_in") != 0 && strcmp(con->protocol, "uat_in") != 0 && strcmp(con->protocol, "planefinder_in") != 0 ) { fprintf(stderr, "--net-connector: Unknown protocol: %s\n", con->protocol); fprintf(stderr, "Supported protocols: beast_out, beast_in, beast_reduce_out, beast_reduce_plus_out, raw_out, raw_in, \n" "sbs_out, sbs_out_replay, sbs_out_mlat, sbs_out_jaero, \n" "sbs_in, sbs_in_mlat, sbs_in_jaero, \n" "sbs_out_prio, asterix_out, asterix_in, \n" "vrs_out, json_out, gpsd_in, uat_in, \n" "planefinder_in\n"); return 1; } if (strcmp(con->address, "") == 0 || strcmp(con->address, "") == 0) { fprintf(stderr, "--net-connector: ip and port can't be empty!\n"); fprintf(stderr, "Correct syntax: --net-connector=ip,port,protocol\n"); return 1; } if (atol(con->port) > (1<<16) || atol(con->port) < 1) { fprintf(stderr, "--net-connector: port must be in range 1 to 65536\n"); return 1; } return 0; } static int parseLongs(char *p, long long *results, int result_size) { char *saveptr = NULL; char *endptr = NULL; int count = 0; char *tok = strtok_r(p, ",", &saveptr); while (tok && count < result_size) { results[count] = strtoll(tok, &endptr, 10); if (tok != endptr) count++; tok = strtok_r(NULL, ",", &saveptr); } return count; } static void parseGainOpt(char *arg) { Modes.gainStartup = 8; int maxTokens = 128; char* token[maxTokens]; if (!arg) { fprintf(stderr, "parseGainOpt called wiht argument null\n"); return; } if (strcasestr(arg, "auto") == arg) { if (Modes.sdr_type != SDR_RTLSDR) { fprintf(stderr, "autogain not supported for non rtl-sdr devices\n"); return; } if (strcasestr(arg, "auto-verbose") == arg) { fprintf(stderr, "autogain enabled, verbose mode\n"); Modes.gainQuiet = 0; } else { fprintf(stderr, "autogain enabled, silent mode, suppressing gain changing messages\n"); Modes.gainQuiet = 1; } Modes.autoGain = 1; Modes.gain = 300; char *argdup = strdup(arg); tokenize(&argdup, ",", token, maxTokens); if (token[1]) { Modes.minGain = (int) (atof(token[1])*10); // Gain is in tens of DBs } else { Modes.minGain = 0; } if (Modes.gain < Modes.minGain) { Modes.gain = Modes.minGain; } if (token[2]) { Modes.noiseLowThreshold = atoi(token[2]); } else { Modes.noiseLowThreshold = 27; } if (token[3]) { Modes.noiseHighThreshold = atoi(token[3]); } else { Modes.noiseHighThreshold = 31; } if (token[4]) { Modes.loudThreshold = atoi(token[4]); } else { Modes.loudThreshold = 243; } fprintf(stderr, "lowestGain: %4.1f noiseLowThreshold: %3d noiseHighThreshold: %3d loudThreshold: %3d\n", Modes.minGain / 10.0, Modes.noiseLowThreshold, Modes.noiseHighThreshold, Modes.loudThreshold); } else { Modes.gain = (int) (atof(arg)*10); // Gain is in tens of DBs Modes.autoGain = 0; Modes.gainQuiet = 0; Modes.minGain = 0; } } static error_t parse_opt(int key, char *arg, struct argp_state *state) { //fprintf(stderr, "parse_opt(%d, %s, argp_state*)\n", key, arg); int maxTokens = 128; char* token[maxTokens]; switch (key) { case OptDevice: Modes.dev_name = strdup(arg); break; case OptGain: sfree(Modes.gainArg); Modes.gainArg = strdup(arg); break; case OptFreq: Modes.freq = (int) strtoll(arg, NULL, 10); break; case OptDcFilter: Modes.dc_filter = 1; break; case OptBiasTee: Modes.biastee = 1; break; case OptFix: Modes.nfix_crc = 1; break; case OptNoFix: Modes.nfix_crc = 0; break; case OptNoFixDf: Modes.fixDF = 0; break; case OptRaw: Modes.raw = 1; break; case OptPreambleThreshold: Modes.preambleThreshold = (uint32_t) (imax(imin(strtoll(arg, NULL, 10), PREAMBLE_THRESHOLD_MAX), PREAMBLE_THRESHOLD_MIN)); break; case OptNet: Modes.net = 1; break; case OptNetOnly: Modes.net = 1; break; case OptModeAc: Modes.mode_ac = 1; break; case OptModeAcAuto: Modes.mode_ac_auto = 1; break; case OptQuiet: Modes.quiet = 1; break; case OptNoInteractive: Modes.interactive = 0; if (Modes.viewadsb) Modes.quiet = 0; break; case OptProcessOnly: Modes.process_only = (uint32_t) strtol(arg, NULL, 16); fprintf(stderr, "process-only: %06x\n", Modes.process_only); break; case OptShowOnly: Modes.show_only = (uint32_t) strtol(arg, NULL, 16); Modes.decode_all = 1; Modes.interactive = 0; Modes.quiet = 1; //Modes.cpr_focus = Modes.show_only; fprintf(stderr, "show-only: %06x\n", Modes.show_only); break; case OptFilterDF: Modes.filterDF = 1; Modes.filterDFbitset = 0; // reset it #define dfs_size 128 long long dfs[dfs_size]; int count = parseLongs(arg, dfs, dfs_size); for (int i = 0; i < count; i++) { Modes.filterDFbitset |= (1 << dfs[i]); } fprintf(stderr, "filter-DF: %s\n", arg); #undef dfs_size break; case OptMlat: Modes.mlat = 1; break; case OptForwardMlat: Modes.forward_mlat = 1; break; case OptForwardMlatSbs: Modes.forward_mlat_sbs = 1; break; case OptOnlyAddr: Modes.onlyaddr = 1; break; case OptMetric: Modes.metric = 1; break; case OptGnss: Modes.use_gnss = 1; break; case OptAggressive: Modes.nfix_crc = MODES_MAX_BITERRORS; break; case OptInteractive: Modes.interactive = 1; Modes.quiet = 1; break; case OptInteractiveTTL: Modes.interactive_display_ttl = (int64_t) (1000 * atof(arg)); break; case OptLat: sfree(Modes.latString); Modes.latString = strdup(arg); Modes.fUserLat = atof(arg); break; case OptLon: sfree(Modes.lonString); Modes.lonString = strdup(arg); Modes.fUserLon = atof(arg); break; case OptMaxRange: Modes.maxRange = atof(arg) * 1852.0; // convert to metres break; case OptStats: if (!Modes.stats_display_interval) Modes.stats_display_interval = ((int64_t) 1) << 60; // "never" break; case OptStatsRange: Modes.stats_range_histo = 1; break; case OptAutoExit: Modes.auto_exit = atof(arg) * SECONDS; break; case OptStatsEvery: Modes.stats_display_interval = ((int64_t) nearbyint(atof(arg) / 10.0)) * 10 * SECONDS; if (Modes.stats_display_interval == 0) { Modes.stats_display_interval = 10 * SECONDS; } break; case OptRangeOutlineDuration: Modes.range_outline_duration = (int64_t) (atof(arg) * HOURS); if (Modes.range_outline_duration < 1 * MINUTES) { Modes.range_outline_duration = 1 * MINUTES; fprintf(stderr, "--range-outline-hours too small, using minimum value 0.016\n"); } break; case OptSnip: snipMode(atoi(arg)); cleanup_and_exit(0); break; case OptPromFile: Modes.prom_file = strdup(arg); break; case OptJsonDir: sfree(Modes.json_dir); Modes.json_dir = strdup(arg); break; case OptHeatmap: Modes.heatmap = 1; if (atof(arg) > 0) Modes.heatmap_interval = (int64_t)(1000.0 * atof(arg)); break; case OptHeatmapDir: sfree(Modes.heatmap_dir); Modes.heatmap_dir = strdup(arg); break; case OptDumpBeastDir: { char *argdup = strdup(arg); tokenize(&argdup, ",", token, maxTokens); if (!token[0]) { sfree(argdup); break; } sfree(Modes.dump_beast_dir); Modes.dump_beast_dir = strdup(token[0]); if (token[1]) { Modes.dump_interval = atoi(token[1]); } if (token[2]) { Modes.dump_compressionLevel = atoi(token[2]); } // enable networking as this is required Modes.net = 1; sfree(argdup); } break; case OptGlobeHistoryDir: sfree(Modes.globe_history_dir); Modes.globe_history_dir = strdup(arg); break; case OptStateOnlyOnExit: Modes.state_only_on_exit = 1; break; case OptStateInterval: Modes.state_write_interval = (int64_t) (atof(arg) * 1.0 * SECONDS); if (Modes.state_write_interval < 59 * SECONDS) { fprintf(stderr, "ERROR: --write-state-every less than 60 seconds (specified: %s)\n", arg); exit(1); Modes.state_write_interval = 1 * HOURS; } break; case OptStateDir: sfree(Modes.state_parent_dir); Modes.state_parent_dir = strdup(arg); break; case OptJsonTime: Modes.json_interval = (int64_t) (1000.0 * atof(arg)); if (Modes.json_interval < 100) // 0.1s Modes.json_interval = 100; break; case OptJsonLocAcc: Modes.json_location_accuracy = (int8_t) atoi(arg); break; case OptJaeroTimeout: Modes.trackExpireJaero = (int64_t) (atof(arg) * MINUTES); break; case OptPositionPersistence: Modes.position_persistence = imax(0, atoi(arg)); break; case OptJsonReliable: Modes.json_reliable = atoi(arg); if (Modes.json_reliable < -1) Modes.json_reliable = -1; if (Modes.json_reliable > 4) Modes.json_reliable = 4; break; case OptDbFileLongtype: Modes.jsonLongtype = 1; break; case OptDbFile: sfree(Modes.db_file); if (strcmp(arg, "tar1090") == 0) { Modes.db_file = strdup("/usr/local/share/tar1090/git-db/aircraft.csv.gz"); } else { Modes.db_file = strdup(arg); } if (strlen(Modes.db_file) == 0 || strcmp(Modes.db_file, "none") == 0) { sfree(Modes.db_file); Modes.db_file = NULL; } break; case OptJsonGzip: Modes.json_gzip = 1; break; case OptEnableBinGz: Modes.enableBinGz = 1; break; case OptJsonOnlyBin: Modes.onlyBin = (int8_t) atoi(arg); break; case OptJsonTraceHistOnly: Modes.trace_hist_only = (int8_t) atoi(arg); break; case OptFullTraceDir: sfree(Modes.fullTraceDir); Modes.fullTraceDir = strdup(arg); break; case OptJsonTraceInt: Modes.json_trace_interval = (int64_t)(1000 * atof(arg)); break; case OptAcHashBits: if (atoi(arg) > 24) { Modes.acHashBits = 24; } else if (atoi(arg) < 8) { Modes.acHashBits = 8; } else { Modes.acHashBits = atoi(arg); } Modes.acBuckets = 1 << Modes.acHashBits; // this is critical for hashing purposes break; case OptJsonGlobeIndex: Modes.json_globe_index = 1; break; case OptNetHeartbeat: Modes.net_heartbeat_interval = (int64_t) (1000 * atof(arg)); break; case OptNetRoSize: Modes.net_output_flush_size = atoi(arg); break; case OptNetRoInterval: Modes.net_output_flush_interval = (int64_t) (1000 * atof(arg)); break; case OptNetRoIntervalBeastReduce: Modes.net_output_flush_interval_beast_reduce = (int64_t) (1000 * atof(arg)); break; case OptNetUatReplayPorts: sfree(Modes.net_output_uat_replay_ports); Modes.net_output_uat_replay_ports = strdup(arg); break; case OptNetUatInPorts: sfree(Modes.net_input_uat_ports); Modes.net_input_uat_ports = strdup(arg); break; case OptNetRoPorts: sfree(Modes.net_output_raw_ports); Modes.net_output_raw_ports = strdup(arg); break; case OptNetRiPorts: sfree(Modes.net_input_raw_ports); Modes.net_input_raw_ports = strdup(arg); break; case OptNetBoPorts: sfree(Modes.net_output_beast_ports); Modes.net_output_beast_ports = strdup(arg); break; case OptNetBiPorts: sfree(Modes.net_input_beast_ports); Modes.net_input_beast_ports = strdup(arg); break; case OptNetAsterixOutPorts: sfree(Modes.net_output_asterix_ports); Modes.net_output_asterix_ports = strdup(arg); break; case OptNetAsterixInPorts: sfree(Modes.net_input_asterix_ports); Modes.net_input_asterix_ports = strdup(arg); break; case OptNetAsterixReduce: Modes.asterixReduce = 1; break; case OptNetBeastReducePorts: sfree(Modes.net_output_beast_reduce_ports); Modes.net_output_beast_reduce_ports = strdup(arg); break; case OptNetBeastReduceFilterAlt: if (atof(arg) > 0) Modes.beast_reduce_filter_altitude = (float) atof(arg); break; case OptNetBeastReduceFilterDist: if (atof(arg) > 0) Modes.beast_reduce_filter_distance = (float) atof(arg) * 1852.0f; // convert to meters break; case OptNetBeastReduceOptimizeMlat: Modes.beast_reduce_optimize_mlat = 1; break; case OptNetBeastReduceInterval: if (atof(arg) >= 0) Modes.net_output_beast_reduce_interval = (int64_t) (1000 * atof(arg)); if (Modes.net_output_beast_reduce_interval > 15000) Modes.net_output_beast_reduce_interval = 15000; break; case OptNetSbsReduce: Modes.sbsReduce = 1; break; case OptNetBindAddr: sfree(Modes.net_bind_address); Modes.net_bind_address = strdup(arg); break; case OptNetSbsPorts: sfree(Modes.net_output_sbs_ports); Modes.net_output_sbs_ports = strdup(arg); break; case OptNetJsonPortNoPos: Modes.net_output_json_include_nopos = 1; break; case OptNetJsonPortInterval: Modes.net_output_json_interval = (int64_t)(atof(arg) * SECONDS); break; case OptNetJsonPorts: sfree(Modes.net_output_json_ports); Modes.net_output_json_ports = strdup(arg); break; case OptTar1090UseApi: Modes.tar1090_use_api = 1; break; case OptNetApiPorts: sfree(Modes.net_output_api_ports); Modes.net_output_api_ports = strdup(arg); Modes.api = 1; break; case OptApiShutdownDelay: Modes.apiShutdownDelay = atof(arg) * SECONDS; break; case OptNetSbsInPorts: sfree(Modes.net_input_sbs_ports); Modes.net_input_sbs_ports = strdup(arg); break; case OptNetJaeroPorts: sfree(Modes.net_output_jaero_ports); Modes.net_output_jaero_ports = strdup(arg); break; case OptNetJaeroInPorts: sfree(Modes.net_input_jaero_ports); Modes.net_input_jaero_ports = strdup(arg); break; case OptNetVRSPorts: sfree(Modes.net_output_vrs_ports); Modes.net_output_vrs_ports = strdup(arg); break; case OptNetVRSInterval: if (atof(arg) > 0) Modes.net_output_vrs_interval = (int64_t)(atof(arg) * SECONDS); break; case OptNetBuffer: Modes.net_sndbuf_size = atoi(arg); break; case OptTcpBuffersAuto: Modes.tcpBuffersAuto = 1; break; case OptNetVerbatim: Modes.net_verbatim = 1; break; case OptSdrBufSize: Modes.sdr_buf_size = atoi(arg) * 1024; break; case OptNetReceiverId: Modes.netReceiverId = 1; Modes.ping = 1; Modes.tcpBuffersAuto = 1; break; case OptNetReceiverIdJson: Modes.netReceiverIdJson = 1; break; case OptGarbage: sfree(Modes.garbage_ports); Modes.garbage_ports = strdup(arg); break; case OptDecodeThreads: Modes.decodeThreads = imax(1, atoi(arg)); break; case OptNetIngest: Modes.netIngest = 1; break; case OptUuidFile: sfree(Modes.uuidFile); Modes.uuidFile = strdup(arg); break; case OptNetConnector: if (make_net_connector(arg)) { return ARGP_ERR_UNKNOWN; } break; case OptNetConnectorDelay: Modes.net_connector_delay = (int64_t) (1000 * atof(arg)); break; case OptTraceFocus: Modes.trace_focus = (uint32_t) strtol(arg, NULL, 16); Modes.interactive = 0; fprintf(stderr, "trace_focus = %06x\n", Modes.trace_focus); break; case OptCprFocus: Modes.cpr_focus = (uint32_t) strtol(arg, NULL, 16); Modes.interactive = 0; fprintf(stderr, "cpr_focus = %06x\n", Modes.cpr_focus); break; case OptLegFocus: Modes.leg_focus = (uint32_t) strtol(arg, NULL, 16); fprintf(stderr, "leg_focus = %06x\n", Modes.leg_focus); break; case OptReceiverFocus: { char rfocus[16]; char *p = arg; for (uint32_t i = 0; i < sizeof(rfocus); i++) { if (*p == '-') p++; rfocus[i] = *p; if (*p != 0) p++; } Modes.receiver_focus = strtoull(rfocus, NULL, 16); fprintf(stderr, "receiver_focus = %016"PRIx64"\n", Modes.receiver_focus); } break; case OptDevel: { char *argdup = strdup(arg); tokenize(&argdup, ",", token, maxTokens); if (!token[0]) { sfree(argdup); break; } if (strcasecmp(token[0], "lastStatus") == 0) { if (token[1]) { Modes.debug_lastStatus = atoi(token[1]); fprintf(stderr, "lastStatus: %d\n", Modes.debug_lastStatus); } else { Modes.debug_lastStatus = 1; } } if (strcasecmp(token[0], "apiThreads") == 0) { if (token[1]) { Modes.apiThreadCount = atoi(token[1]); } } if (strcasecmp(token[0], "legacy_history") == 0) { Modes.legacy_history = 1; } if (strcasecmp(token[0], "readProxy") == 0) { Modes.readProxy = 1; } if (strcasecmp(token[0], "beast_forward_noforward") == 0) { Modes.beast_forward_noforward = 1; } if (strcasecmp(token[0], "beast_set_noforward_timestamp") == 0) { Modes.beast_set_noforward_timestamp = 1; } if (strcasecmp(token[0], "accept_synthetic") == 0) { Modes.dump_accept_synthetic_now = 1; } if (strcasecmp(token[0], "ignore_synthetic") == 0) { Modes.dump_ignore_synthetic_now = 1; } if (strcasecmp(token[0], "dump_reduce") == 0) { Modes.dump_reduce = 1; fprintf(stderr, "Modes.dump_reduce: %d\n", Modes.dump_reduce); } if (strcasecmp(token[0], "ping_reject") == 0 && token[1]) { Modes.ping_reject = atoi(token[1]); Modes.ping_reduce = Modes.ping_reject / 2; } if (strcasecmp(token[0], "log_usb_jitter") == 0 && token[1]) { Modes.log_usb_jitter = atoi(token[1]); } if (strcasecmp(token[0], "log_ppm") == 0) { if (token[1]) { Modes.devel_log_ppm = atoi(token[1]); } if (Modes.devel_log_ppm == 0) { Modes.devel_log_ppm = -1; } // setting to -1 to enable due to the following check // if (Modes.devel_log_ppm && fabs(ppm) > Modes.devel_log_ppm) { } // use traceLast to add granular data to full and history traces // this is useful for accidents in which the transponder suddenly stops transmitting // it provides high granularity data for by default for 64 data points before end of // transmission --devel=traceLast,128 would double this to 128 if (strcasecmp(token[0], "traceLast") == 0 && token[1]) { Modes.traceLastMax = atoi(token[1]); } // maximum high resolution points to be used to show before landing // can't use more points than are present due to traceLast // default 128 right now if (strcasecmp(token[0], "beforeLandHighRes") == 0 && token[1]) { Modes.beforeLandHighRes = atoi(token[1]); } // record one point every second for configured time after ground state change if (strcasecmp(token[0], "afterGroundTransitionHighRes") == 0 && token[1]) { Modes.afterGroundTransitionHighRes = atoi(token[1]) * SECONDS; } if (strcasecmp(token[0], "ingestLimitRate") == 0 && token[1]) { Modes.ingestLimitRate = atoi(token[1]); } if (strcasecmp(token[0], "sbs_override_squawk") == 0 && token[1]) { Modes.sbsOverrideSquawk = atoi(token[1]); } if (strcasecmp(token[0], "messageRateMult") == 0 && token[1]) { Modes.messageRateMult = atof(token[1]); } if (strcasecmp(token[0], "mlatForceInterval") == 0 && token[1]) { Modes.mlatForceInterval = atof(token[1]) * SECONDS; } if (strcasecmp(token[0], "mlatForceDistance") == 0 && token[1]) { Modes.mlatForceDistance = atof(token[1]) * 1e3; } if (strcasecmp(token[0], "forwardMinMessages") == 0 && token[1]) { Modes.net_forward_min_messages = atoi(token[1]); fprintf(stderr, "forwardMinMessages: %u\n", Modes.net_forward_min_messages); } if (strcasecmp(token[0], "incrementId") == 0) { Modes.incrementId = 1; } if (strcasecmp(token[0], "debugPlanefinder") == 0) { Modes.debug_planefinder = 1; } if (strcasecmp(token[0], "debugFlush") == 0) { Modes.debug_flush = 1; } if (strcasecmp(token[0], "omitGlobeFiles") == 0) { Modes.omitGlobeFiles = 1; } if (strcasecmp(token[0], "disableAcasCsv") == 0) { Modes.enableAcasCsv = 0; } if (strcasecmp(token[0], "disableAcasJson") == 0) { Modes.enableAcasJson = 0; } if (strcasecmp(token[0], "enableClientsJson") == 0) { Modes.enableClientsJson = 1; } if (strcasecmp(token[0], "tar1090NoGlobe") == 0) { Modes.tar1090_no_globe = 1; } if (strcasecmp(token[0], "provokeSegfault") == 0) { Modes.debug_provoke_segfault = 1; } if (strcasecmp(token[0], "debugGPS") == 0) { Modes.debug_gps = 1; } if (strcasecmp(token[0], "debugSerial") == 0) { Modes.debug_serial = 1; } if (strcasecmp(token[0], "debugZstd") == 0) { Modes.debug_zstd = 1; } if (strcasecmp(token[0], "disableZstd") == 0) { Modes.enable_zstd = 0; Modes.enableBinGz = 1; } sfree(argdup); } break; case OptDebug: while (*arg) { switch (*arg) { case 'n': Modes.debug_net = 1; break; case 'N': Modes.debug_nextra = 1; break; case 'P': Modes.debug_cpr = 1; break; case 'R': Modes.debug_receiver = 1; break; case 'S': Modes.debug_speed_check = 1; break; case 'G': Modes.debug_garbage = 1; break; case 't': Modes.debug_traceAlloc = 1; break; case 'T': Modes.debug_traceCount = 1; break; case 'K': Modes.debug_sampleCounter = 1; break; case 'O': Modes.debug_rough_receiver_location = 1; break; case 'U': Modes.debug_dbJson = 1; break; case 'A': Modes.debug_ACAS = 1; fprintf(stderr, "debug_ACAS enabled!\n"); break; case 'a': Modes.debug_api = 1; break; case 'C': Modes.debug_recent = 1; break; case 's': Modes.debug_squawk = 1; break; case 'p': Modes.debug_ping = 1; break; case 'c': Modes.debug_callsign = 1; break; case 'g': Modes.debug_nogps = 1; break; case 'u': Modes.debug_uuid = 1; break; case 'b': Modes.debug_bogus = 1; Modes.decode_all = 1; break; case 'm': Modes.debug_maxRange = 1; break; case 'r': Modes.debug_removeStaleDuration = 1; break; case 'X': Modes.debug_receiverRangeLimit = 1; break; case 'v': Modes.verbose = 1; break; case 'Y': Modes.debug_yeet = 1; break; case '7': Modes.debug_7700 = 1; break; case 'D': Modes.debug_send_uuid = 1; break; case 'd': Modes.debug_no_discard = 1; break; case 'y': Modes.debug_position_timing = 1; break; default: fprintf(stderr, "Unknown debugging flag: %c\n", *arg); break; } arg++; } break; #ifdef ENABLE_RTLSDR case OptRtlSdrEnableAgc: case OptRtlSdrPpm: #endif case OptBeastSerial: case OptBeastBaudrate: case OptBeastDF1117: case OptBeastDF045: case OptBeastMlatTimeOff: case OptBeastCrcOff: case OptBeastFecOff: case OptBeastModeAc: case OptIfileName: case OptIfileFormat: case OptIfileThrottle: #ifdef ENABLE_BLADERF case OptBladeFpgaDir: case OptBladeDecim: case OptBladeBw: #endif #ifdef ENABLE_HACKRF case OptHackRfGainEnable: case OptHackRfVgaGain: #endif #ifdef ENABLE_PLUTOSDR case OptPlutoUri: case OptPlutoNetwork: #endif #ifdef ENABLE_SOAPYSDR case OptSoapyAntenna: case OptSoapyBandwith: case OptSoapyEnableAgc: case OptSoapyGainElement: #endif if (Modes.sdr_type == SDR_NONE) { fprintf(stderr, "ERROR: SDR / device type specific options must be specified AFTER the --device-type xyz parameter.\n"); return ARGP_ERR_UNKNOWN; } /* Forward interface option to the specific device handler */ if (sdrHandleOption(key, arg) == false) { fprintf(stderr, "ERROR: Unknown SDR specific option / Mismatch between option and specified device type.\n"); return ARGP_ERR_UNKNOWN; } break; case OptDeviceType: // Select device type if (sdrHandleOption(key, arg) == false) { fprintf(stderr, "ERROR: Unknown device type:%s\n", arg); return ARGP_ERR_UNKNOWN; } break; case ARGP_KEY_ARG: return ARGP_ERR_UNKNOWN; break; case ARGP_KEY_END: if (state->arg_num > 0) { /* We use only options but no arguments */ return ARGP_ERR_UNKNOWN; } break; default: return ARGP_ERR_UNKNOWN; } return 0; } int parseCommandLine(int argc, char **argv) { // check if we are running as viewadsb and set according behaviour if (strstr(argv[0], "viewadsb")) { Modes.viewadsb = 1; Modes.net = 1; Modes.sdr_type = SDR_NONE; Modes.net_only = 1; #ifndef DISABLE_INTERACTIVE Modes.interactive = 1; Modes.quiet = 1; #endif Modes.net_connector_delay = 5 * 1000; // let this get overwritten in case the command line specifies a net-connector make_net_connector("127.0.0.1,30005,beast_in"); Modes.net_connectors_count--; // we count it back up if it's still zero after the arg parse so it's used } // This is a little silly, but that's how the preprocessor works.. #define _stringize(x) #x const char *doc = "readsb Mode-S/ADSB/TIS Receiver " "\nBuild options: " #ifdef ENABLE_RTLSDR "ENABLE_RTLSDR " #endif #ifdef ENABLE_BLADERF "ENABLE_BLADERF " #endif #ifdef ENABLE_PLUTOSDR "ENABLE_PLUTOSDR " #endif #ifdef ENABLE_SOAPYSDR "ENABLE_SOAPYSDR " #endif #ifdef SC16Q11_TABLE_BITS #define stringize(x) _stringize(x) "SC16Q11_TABLE_BITS=" stringize(SC16Q11_TABLE_BITS) #undef stringize #endif ""; #undef _stringize #undef verstring if (Modes.viewadsb) { doc = "vieadsb Mode-S/ADSB/TIS commandline viewer " "\n\nBy default, viewadsb will TCP connect to 127.0.0.1:30005 as a data source." "\nTypical readsb / dump1090 installs will provide beast data on port 30005." ; } struct argp_option *options = Modes.viewadsb ? optionsViewadsb : optionsReadsb; const char* args_doc = ""; struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; if (argp_parse(&argp, argc, argv, ARGP_NO_EXIT, 0, 0)) { fprintf(stderr, "Error parsing the given command line parameters, check readsb --usage and readsb --help for valid parameters.\n"); print_commandline(argc, argv); cleanup_and_exit(1); } if (argc >= 2 && ( !strcmp(argv[1], "--help") || !strcmp(argv[1], "--usage") || !strcmp(argv[1], "--version") || !strcmp(argv[1], "-V") || !strcmp(argv[1], "-?") ) ) { exit(0); } print_commandline(argc, argv); if (Modes.viewadsb) { log_with_timestamp("viewadsb starting up."); } else { log_with_timestamp("readsb starting up."); } fprintf(stderr, VERSION_STRING"\n"); return 0; } static void configAfterParse() { Modes.sdr_buf_samples = Modes.sdr_buf_size / 2; Modes.trackExpireMax = Modes.trackExpireJaero + TRACK_EXPIRE_LONG + 1 * MINUTES; if (Modes.sdr_type == SDR_RTLSDR && !Modes.gainArg) { parseGainOpt("auto"); } else if (Modes.gainArg) { parseGainOpt(Modes.gainArg); sfree(Modes.gainArg); } if (Modes.json_globe_index || Modes.globe_history_dir) { Modes.keep_traces = 24 * HOURS + 60 * MINUTES; // include 60 minutes overlap Modes.writeTraces = 1; } else if (Modes.heatmap || Modes.trace_focus != BADDR) { Modes.keep_traces = 35 * MINUTES; // heatmap is written every 30 minutes } if (Modes.globe_history_dir && Modes.traceLastMax > 0) { //ensure traceLastMax is always dividable by SFOUR if (Modes.traceLastMax % SFOUR != 0) { Modes.traceLastMax = (Modes.traceLastMax / SFOUR + 1) * SFOUR; } } else { Modes.traceLastMax = 0; } Modes.traceMax = alignSFOUR((Modes.keep_traces + 1 * HOURS) / 1000 * 3); // 3 position per second, usually 2 per second is max Modes.traceReserve = alignSFOUR(16); Modes.traceChunkPoints = alignSFOUR(2 * 64); Modes.traceChunkMaxBytes = 16 * 1024; if (Modes.json_trace_interval < 1) { Modes.json_trace_interval = 1; // 1 ms } if (Modes.json_trace_interval < 4 * SECONDS) { //double oversize = 2.0 / fmax(1, (double) Modes.json_trace_interval / 1000.0); double oversize = 2; Modes.traceChunkPoints = alignSFOUR(Modes.traceChunkPoints * oversize); Modes.traceChunkMaxBytes *= oversize; } //Modes.traceChunkPoints = alignSFOUR(4); Modes.traceRecentPoints = alignSFOUR(TRACE_RECENT_POINTS); Modes.traceCachePoints = alignSFOUR(Modes.traceRecentPoints + TRACE_CACHE_EXTRA); if (Modes.traceChunkPoints < Modes.traceRecentPoints) { Modes.traceChunkPoints = Modes.traceRecentPoints; } if (Modes.verbose) { fprintf(stderr, "traceChunkPoints: %d size: %ld traceChunkMaxBytes %d\n", Modes.traceChunkPoints, (long) stateBytes(Modes.traceChunkPoints), Modes.traceChunkMaxBytes); } Modes.num_procs = 1; // default this value to 1 cpu_set_t mask; if (sched_getaffinity(getpid(), sizeof(mask), &mask) == 0) { Modes.num_procs = CPU_COUNT(&mask); #if (defined(__arm__)) if (Modes.num_procs < 2 && !Modes.preambleThreshold && Modes.sdr_type != SDR_NONE) { fprintf(stderr, "WARNING: Reducing preamble threshold / decoding performance as this system has only 1 core (explicitely set --preamble-threshold to disable this behaviour)!\n"); Modes.preambleThreshold = PREAMBLE_THRESHOLD_PIZERO; Modes.fixDF = 0; } #endif } if (Modes.num_procs < 1) { // there is at least 1 processor Modes.num_procs = 1; } if (!Modes.preambleThreshold) { Modes.preambleThreshold = PREAMBLE_THRESHOLD_DEFAULT; } if (Modes.mode_ac) Modes.mode_ac_auto = 0; if (!Modes.quiet) Modes.decode_all = 1; if (Modes.viewadsb && Modes.net_connectors_count == 0) { Modes.net_connectors_count++; // activate the default net-connector for viewadsb } if (Modes.heatmap) { if (!Modes.globe_history_dir && !Modes.heatmap_dir) { fprintf(stderr, "Heatmap requires globe history dir or heatmap dir to be set, disabling heatmap!\n"); Modes.heatmap = 0; } } // Validate the users Lat/Lon home location inputs if ((Modes.fUserLat > 90.0) // Latitude must be -90 to +90 || (Modes.fUserLat < -90.0) // and || (Modes.fUserLon > 360.0) // Longitude must be -180 to +360 || (Modes.fUserLon < -180.0)) { fprintf(stderr, "INVALID lat: %s, lon: %s\n", Modes.latString, Modes.lonString); Modes.fUserLat = Modes.fUserLon = 0.0; } else if (Modes.fUserLon > 180.0) { // If Longitude is +180 to +360, make it -180 to 0 Modes.fUserLon -= 360.0; } // If both Lat and Lon are 0.0 then the users location is either invalid/not-set, or (s)he's in the // Atlantic ocean off the west coast of Africa. This is unlikely to be correct. // Set the user LatLon valid flag only if either Lat or Lon are non zero. Note the Greenwich meridian if ((Modes.fUserLat != 0.0) || (Modes.fUserLon != 0.0)) { Modes.userLocationValid = 1; adjustUserLocationAccuracy(VERBOSE); } if (!Modes.userLocationValid || !Modes.json_dir || Modes.json_location_accuracy == 0) { Modes.outline_json = 0; // disable outline_json } if (Modes.json_reliable == -13) { if (Modes.userLocationValid && Modes.maxRange != 0) Modes.json_reliable = 1; else Modes.json_reliable = 2; } //fprintf(stderr, "json_reliable: %d\n", Modes.json_reliable); // check if we can use the user location as a reference Modes.userLocationRef = Modes.userLocationValid && Modes.maxRange != 0 && Modes.maxRange < 1852 * 800; //fprintf(stderr, "userLocationRef %d\n", Modes.userLocationRef); if (Modes.position_persistence < Modes.json_reliable) { Modes.position_persistence = imax(0, Modes.json_reliable); fprintf(stderr, "position-persistence must be >= json-reliable! setting position-persistence: %d\n", Modes.position_persistence); } if (Modes.net_output_flush_size < 750) { Modes.net_output_flush_size = 750; } if (Modes.net_output_flush_interval > (MODES_OUT_FLUSH_INTERVAL)) { Modes.net_output_flush_interval = MODES_OUT_FLUSH_INTERVAL; } if (Modes.net_output_flush_interval < 0) Modes.net_output_flush_interval = 0; if (Modes.net_output_flush_interval < 51 && Modes.sdr_type != SDR_NONE && !(Modes.sdr_type == SDR_MODESBEAST || Modes.sdr_type == SDR_GNS)) { // the SDR code runs the network tasks about every 50ms // avoid delay by just flushing every call of the network tasks // somewhat hacky, anyone reading this code surprised at this point? Modes.net_output_flush_interval = 0; } if (Modes.net_output_flush_interval_beast_reduce < 0) { Modes.net_output_flush_interval_beast_reduce = Modes.net_output_flush_interval; } if (Modes.net_sndbuf_size > (MODES_NET_SNDBUF_MAX)) { fprintf(stderr, "automatically clamping --net-buffer to 7 which is %d bytes\n", MODES_NET_SNDBUF_SIZE << MODES_NET_SNDBUF_MAX); Modes.net_sndbuf_size = MODES_NET_SNDBUF_MAX; } Modes.netBufSize = MODES_NET_SNDBUF_SIZE << Modes.net_sndbuf_size; // make the buffer large enough to hold the output flush size easily // make it at least 8k so there is no issues even when one message is rather large Modes.writerBufSize = imax(8 * 1024, Modes.net_output_flush_size * 2); // the net buffer needs to be large enough to hold the writer buffer if (Modes.writerBufSize > Modes.netBufSize) { Modes.netBufSize = Modes.writerBufSize; } if (Modes.net_connector_delay <= 50) { Modes.net_connector_delay = 50; } if ((Modes.net_connector_delay > 600 * 1000)) { Modes.net_connector_delay = 600 * 1000; } if (Modes.sdr_type == SDR_NONE) { if (Modes.net) Modes.net_only = 1; if (!Modes.net_only) { fprintf(stderr, "No networking or SDR input selected, exiting! Try '--device-type rtlsdr'! See 'readsb --help'\n"); cleanup_and_exit(1); } } else if (Modes.sdr_type == SDR_MODESBEAST || Modes.sdr_type == SDR_GNS) { Modes.net = 1; Modes.net_only = 1; } else { Modes.net_only = 0; } if (Modes.api) { Modes.max_fds_api = Modes.max_fds / 2; Modes.max_fds_net = Modes.max_fds / 2; } else { Modes.max_fds_api = 0; Modes.max_fds_net = Modes.max_fds; } } static void notask_save_blob(uint32_t blob, char *stateDir) { threadpool_buffer_t pbuffer1 = { 0 }; threadpool_buffer_t pbuffer2 = { 0 }; save_blob(blob, &pbuffer1, &pbuffer2, stateDir); free_threadpool_buffer(&pbuffer1); free_threadpool_buffer(&pbuffer2); } static void loadReplaceState() { if (!Modes.replace_state_blob) { return; } fprintf(stderr, "overriding current state with this blob: %s\n", Modes.replace_state_blob); threadpool_buffer_t buffers[4]; memset(buffers, 0x0, sizeof(buffers)); threadpool_threadbuffers_t group = { .buffer_count = 4, .buffers = buffers }; load_blob(Modes.replace_state_blob, &group); char blob[1024]; snprintf(blob, 1024, "%s.zstl", Modes.replace_state_blob); unlink(blob); for (uint32_t k = 0; k < group.buffer_count; k++) { free_threadpool_buffer(&buffers[k]); } free(Modes.replace_state_blob); Modes.replace_state_blob = NULL; } static int checkWriteStateDir(char *baseDir) { if (!baseDir) { return 0; } char filename[PATH_MAX]; snprintf(filename, PATH_MAX, "%s/writeState", baseDir); int fd = open(filename, O_RDONLY); if (fd <= 0) { return 0; } char tmp[3]; int len = read(fd, tmp, 2); close(fd); tmp[2] = '\0'; if (len == 0) { // this ignores baseDir and always writes it to state_dir / disk writeInternalState(); } else if (len == 2) { uint32_t suffix = strtol(tmp, NULL, 16); notask_save_blob(suffix, baseDir); fprintf(stderr, "save_blob: %02x\n", suffix); } unlink(filename); // unlink only after writing state, if the file doesn't exist that's fine as well // this is a hack to detect from a shell script when the task is done return 1; } static int checkWriteState() { if (Modes.json_dir) { char getStateDir[PATH_MAX]; snprintf(getStateDir, PATH_MAX, "%s/getState", Modes.json_dir); if (checkWriteStateDir(getStateDir)) { return 1; } } return checkWriteStateDir(Modes.state_dir); } static void checkReplaceStateDir(char *baseDir) { if (!baseDir) { return; } if (Modes.replace_state_blob) { return; } char filename[PATH_MAX]; snprintf(filename, PATH_MAX, "%s/replaceState", baseDir); if (access(filename, R_OK) == 0) { for (int j = 0; j < STATE_BLOBS; j++) { char blob[1024]; snprintf(blob, 1024, "%s/blob_%02x.zstl", filename, j); if (access(blob, R_OK) == 0) { snprintf(blob, 1024, "%s/blob_%02x", filename, j); Modes.replace_state_blob = strdup(blob); int64_t mono = mono_milli_seconds(); Modes.replace_state_inhibit_traces_until = mono + 10 * SECONDS; break; } } } } static void checkReplaceState() { checkReplaceStateDir(Modes.state_dir); checkReplaceStateDir(Modes.json_dir); } static void writeOutlineJson() { if (!Modes.outline_json) { return; } free(writeJsonToFile(Modes.json_dir, "outline.json", generateOutlineJson()).buffer); } static void checkSetGain() { if (!Modes.json_dir) { return; } char filename[PATH_MAX]; snprintf(filename, PATH_MAX, "%s/setGain", Modes.json_dir); int fd = open(filename, O_RDONLY); if (fd <= 0) { return; } char tmp[128]; int len = read(fd, tmp, 127); close(fd); unlink(filename); if (len <= 0) { return; } tmp[len] = '\0'; if (strcasestr(tmp, "setLatLon")) { int maxTokens = 128; char* token[maxTokens]; char* chopThis = tmp; tokenize(&chopThis, ",", token, maxTokens); if (!token[1] || !token[2]) { fprintf(stderr, "setLatLon: invalid format\n"); return; } double lat = strtod(token[1], NULL); double lon = strtod(token[2], NULL); if (!isfinite(lat) || lat < -89.9 || lat > 89.9 || !isfinite(lon) || lon < -180 || lon > 180) { fprintf(stderr, "setLatLon: ignoring invalid lat: %f lon: %f\n", lat, lon); return; } fprintf(stderr, "setLatLon: lat: %f lon: %f\n", lat, lon); Modes.fUserLat = lat; Modes.fUserLon = lon; Modes.userLocationValid = 1; if (Modes.json_dir) { free(writeJsonToFile(Modes.json_dir, "receiver.json", generateReceiverJson()).buffer); // location changed } return; } if (strcasestr(tmp, "resetRangeOutline")) { if (Modes.outline_json) { fprintf(stderr, "resetting range outline\n"); memset(Modes.rangeDirs, 0, RANGEDIRSSIZE); writeOutlineJson(); } return; } parseGainOpt(tmp); sdrSetGain(""); //fprintf(stderr, "Modes.gain (tens of dB): %d\n", Modes.gain); } static void miscStuff(int64_t now) { checkNewDay(now); if (Modes.outline_json) { static int64_t nextOutlineWrite; if (now > nextOutlineWrite) { writeOutlineJson(); nextOutlineWrite = now + 15 * SECONDS; } static int64_t nextRangeDirsWrite; if (now > nextRangeDirsWrite) { nextRangeDirsWrite = now + 5 * MINUTES; if (Modes.state_only_on_exit) { nextRangeDirsWrite = now + 6 * HOURS; } writeRangeDirs(); } } checkSetGain(); // don't do everything at once ... this stuff isn't that time critical it'll get its turn if (checkWriteState()) { return; } if (Modes.state_dir) { static uint32_t blob; // current blob static int64_t next_blob; // only continuously write state if we keep permanent trace if (!Modes.state_only_on_exit && now > next_blob) { //fprintf(stderr, "save_blob: %02x\n", blob); int64_t blob_interval = Modes.state_write_interval / STATE_BLOBS; next_blob = now + blob_interval; struct timespec watch; startWatch(&watch); notask_save_blob(blob, Modes.state_dir); int64_t elapsed = stopWatch(&watch); if (elapsed > 0.5 * SECONDS || elapsed > blob_interval) { fprintf(stderr, "WARNING: save_blob %02x took %"PRIu64" ms!\n", blob, elapsed); } blob = (blob + 1) % STATE_BLOBS; return; } } // function can unlock / lock misc mutex if (handleHeatmap(now)) { return; } static int64_t next_clients_json; if (Modes.json_dir && now > next_clients_json) { next_clients_json = now + 10 * SECONDS; if (Modes.netIngest || Modes.enableClientsJson) { free(writeJsonToFile(Modes.json_dir, "clients.json", generateClientsJson()).buffer); } if (Modes.netReceiverIdJson) { free(writeJsonToFile(Modes.json_dir, "receivers.json", generateReceiversJson()).buffer); } return; } if (dbUpdate(now)) { return; } } static void *miscEntryPoint(void *arg) { MODES_NOTUSED(arg); // set this thread low priority setLowestPriorityPthread(); pthread_mutex_lock(&Threads.misc.mutex); srandom(get_seed()); struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); while (!Modes.exit) { threadTimedWait(&Threads.misc, &ts, 200); // check every 0.2 seconds if there is something to do // priorityTasksRun has priority if (priorityTasksPending()) { continue; } struct timespec watch; startWatch(&watch); struct timespec start_time; start_cpu_timing(&start_time); int64_t now = mstime(); // function can unlock / lock misc mutex miscStuff(now); end_cpu_timing(&start_time, &Modes.stats_current.heatmap_and_state_cpu); int64_t elapsed = stopWatch(&watch); static int64_t antiSpam2; if (elapsed > 12 * SECONDS && now > antiSpam2 + 30 * SECONDS) { fprintf(stderr, "<3>High load: heatmap_and_stuff took %"PRIu64" ms! Suppressing for 30 seconds\n", elapsed); antiSpam2 = now; } } pthread_mutex_unlock(&Threads.misc.mutex); pthread_exit(NULL); } static void _sigaction_range(struct sigaction *sa, int first, int last) { int sig; for (sig = first; sig <= last; ++sig) { if (sig == SIGKILL || sig == SIGSTOP) { continue; } if (sigaction(sig, sa, NULL)) { fprintf(stderr, "sigaction(%s[%i]) failed: %s\n", strsignal(sig), sig, strerror(errno)); } } } static void configureSignals() { // signal handling stuff // block all signals while we maniplate signal handlers sigset_t mask; sigfillset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); // ignore all signals, then reenable some struct sigaction sa; memset(&sa, 0, sizeof(sa)); sigfillset(&sa.sa_mask); sa.sa_handler = SIG_IGN; _sigaction_range(&sa, 1, 31); signal(SIGINT, exitHandler); signal(SIGTERM, exitHandler); signal(SIGQUIT, exitHandler); signal(SIGHUP, exitHandler); // unblock signals now that signals are configured sigemptyset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); } // //========================================================================= // int main(int argc, char **argv) { if (0) { // basic spinlock test volatile atomic_int lock = 0; spinLock(&lock); spinLock(&lock); exit(0); } configureSignals(); if (0) { unlink("test.gz"); gzipFile("test"); exit(1); } srandom(get_seed()); // Set sane defaults configSetDefaults(); Modes.startup_time_mono = mono_milli_seconds(); Modes.startup_time = mstime(); #ifdef NO_EVENT_FD // we don't set up eventfds for apple to reduce complexity Modes.exitNowEventfd = -1; Modes.exitSoonEventfd = -1; #else Modes.exitNowEventfd = eventfd(0, EFD_NONBLOCK); Modes.exitSoonEventfd = eventfd(0, EFD_NONBLOCK); #endif if (argc >= 2 && !strcmp(argv[1], "--structs")) { fprintf(stderr, VERSION_STRING"\n"); fprintf(stderr, "struct aircraft: %zu\n", sizeof(struct aircraft)); fprintf(stderr, "struct validity: %zu\n", sizeof(data_validity)); fprintf(stderr, "state: %zu\n", sizeof(struct state)); fprintf(stderr, "state_all: %zu\n", sizeof(struct state_all)); fprintf(stderr, "fourState: %zu\n", sizeof(fourState)); fprintf(stderr, "binCraft: %zu\n", sizeof(struct binCraft)); fprintf(stderr, "apiEntry: %zu\n", sizeof(struct apiEntry)); //fprintf(stderr, "%zu\n", sizeof(struct state_flags)); fprintf(stderr, "modesMessage: %zu\n", sizeof(struct modesMessage)); fprintf(stderr, "stateChunk: %zu\n", sizeof(stateChunk)); //struct aircraft dummy; //fprintf(stderr, "dbFlags offset %zu\n", (char*) &dummy.dbFlags - (char*) &dummy); exit(0); } // Parse the command line options parseCommandLine(argc, argv); configAfterParse(); // Initialization //log_with_timestamp("%s starting up.", MODES_READSB_VARIANT); modesInit(); receiverInit(); // init stats: Modes.stats_current.start = Modes.stats_current.end = Modes.stats_alltime.start = Modes.stats_alltime.end = Modes.stats_periodic.start = Modes.stats_periodic.end = Modes.stats_1min.start = Modes.stats_1min.end = Modes.stats_5min.start = Modes.stats_5min.end = Modes.stats_15min.start = Modes.stats_15min.end = mstime(); for (int j = 0; j < STAT_BUCKETS; ++j) Modes.stats_10[j].start = Modes.stats_10[j].end = Modes.stats_current.start; if (Modes.json_dir) { mkdir_error(Modes.json_dir, 0755, stderr); char pathbuf[PATH_MAX]; snprintf(pathbuf, PATH_MAX, "%s/getState", Modes.json_dir); mkdir_error(pathbuf, 0755, stderr); } if (Modes.json_dir && Modes.writeTraces) { char pathbuf[PATH_MAX]; snprintf(pathbuf, PATH_MAX, "%s/traces", Modes.json_dir); mkdir_error(pathbuf, 0755, stderr); for (int i = 0; i < 256; i++) { snprintf(pathbuf, PATH_MAX, "%s/traces/%02x", Modes.json_dir, i); mkdir_error(pathbuf, 0755, stderr); } if (Modes.fullTraceDir) { snprintf(pathbuf, PATH_MAX, "%s/traces", Modes.fullTraceDir); mkdir_error(pathbuf, 0755, stderr); for (int i = 0; i < 256; i++) { snprintf(pathbuf, PATH_MAX, "%s/traces/%02x", Modes.fullTraceDir, i); mkdir_error(pathbuf, 0755, stderr); } } } if (Modes.state_parent_dir) { Modes.state_dir = malloc(PATH_MAX); snprintf(Modes.state_dir, PATH_MAX, "%s/internal_state", Modes.state_parent_dir); } else if (Modes.globe_history_dir) { Modes.state_dir = malloc(PATH_MAX); snprintf(Modes.state_dir, PATH_MAX, "%s/internal_state", Modes.globe_history_dir); } if (Modes.state_parent_dir && mkdir(Modes.state_parent_dir, 0755) && errno != EEXIST) { fprintf(stderr, "Unable to create state directory (%s): %s\n", Modes.state_parent_dir, strerror(errno)); } if (Modes.globe_history_dir && mkdir(Modes.globe_history_dir, 0755) && errno != EEXIST) { fprintf(stderr, "Unable to create globe history directory (%s): %s\n", Modes.globe_history_dir, strerror(errno)); } checkNewDay(mstime()); checkNewDayAcas(mstime()); if (Modes.state_dir) { readInternalState(); if (Modes.writeInternalState) { Modes.writeInternalState = 0; writeInternalState(); } } if (Modes.sdr_type != SDR_NONE) { threadCreate(&Threads.reader, NULL, readerEntryPoint, NULL); } threadCreate(&Threads.decode, NULL, decodeEntryPoint, NULL); threadCreate(&Threads.misc, NULL, miscEntryPoint, NULL); if (Modes.api || (Modes.json_dir && Modes.onlyBin < 2)) { // provide a json buffer Modes.apiUpdate = 1; apiBufferInit(); if (Modes.api) { // after apiBufferInit() apiInit(); } } if (Modes.json_globe_index && !Modes.omitGlobeFiles) { threadCreate(&Threads.globeBin, NULL, globeBinEntryPoint, NULL); } if (Modes.json_dir) { threadCreate(&Threads.json, NULL, jsonEntryPoint, NULL); if (Modes.json_globe_index && !Modes.omitGlobeFiles && !Modes.tar1090_no_globe) { // globe_xxxx.json threadCreate(&Threads.globeJson, NULL, globeJsonEntryPoint, NULL); } free(writeJsonToFile(Modes.json_dir, "receiver.json", generateReceiverJson()).buffer); } struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); threadCreate(&Threads.upkeep, NULL, upkeepEntryPoint, NULL); if (Modes.debug_provoke_segfault) { msleep(666); fprintf(stderr, "devel=provokeSegfault -> provoking SEGFAULT now!\n"); int *a = NULL; *a = 0; } int mainEpfd = my_epoll_create(&Modes.exitSoonEventfd); struct epoll_event *events = NULL; int maxEvents = 1; epollAllocEvents(&events, &maxEvents); // init hungtimers pthread_mutex_lock(&Modes.hungTimerMutex); startWatch(&Modes.hungTimer1); startWatch(&Modes.hungTimer2); pthread_mutex_unlock(&Modes.hungTimerMutex); struct timespec mainloopTimer; startWatch(&mainloopTimer); while (!Modes.exit) { int64_t wait_time = 5 * SECONDS; if (Modes.auto_exit) { int64_t uptime = getUptime(); if (uptime + wait_time >= Modes.auto_exit) { wait_time = imax(1, Modes.auto_exit - uptime); } if (uptime >= Modes.auto_exit) { setExit(1); } } #ifdef NO_EVENT_FD wait_time = imin(wait_time, 100); // no event_fd, limit sleep to 100 ms #endif epoll_wait(mainEpfd, events, maxEvents, wait_time); if (Modes.exitSoon) { if (Modes.apiShutdownDelay) { // delay for graceful api shutdown fprintf(stderr, "Waiting %.3f seconds (--api-shutdown-delay) ...\n", Modes.apiShutdownDelay / 1000.0); msleep(Modes.apiShutdownDelay); } // Signal to threads that program is exiting Modes.exit = Modes.exitSoon; uint64_t one = 1; ssize_t res = write(Modes.exitNowEventfd, &one, sizeof(one)); MODES_NOTUSED(res); } int64_t elapsed0 = lapWatch(&mainloopTimer); pthread_mutex_lock(&Modes.hungTimerMutex); int64_t elapsed1 = stopWatch(&Modes.hungTimer1); int64_t elapsed2 = stopWatch(&Modes.hungTimer2); pthread_mutex_unlock(&Modes.hungTimerMutex); if (elapsed0 > 10 * SECONDS) { fprintf(stderr, "<3> readsb was suspended for more than 5 seconds, this isn't healthy you know!\n"); } else { if (elapsed1 > 60 * SECONDS && !Modes.synthetic_now) { fprintf(stderr, "<3>FATAL: priorityTasksRun() interval %.1f seconds! Trying for an orderly shutdown as well as possible!\n", (double) elapsed1 / SECONDS); fprintf(stderr, "<3>lockThreads() probably hung on %s\n", Modes.currentTask); setExit(2); // don't break here, otherwise exitNowEventfd isn't signaled and we don't have a proper exit // setExit signals exitSoonEventfd, the main loop will then signal exitNowEventfd } if (elapsed2 > 60 * SECONDS && !Modes.synthetic_now) { fprintf(stderr, "<3>FATAL: removeStale() interval %.1f seconds! Trying for an orderly shutdown as well as possible!\n", (double) elapsed2 / SECONDS); setExit(2); } } } // all threads will be joined soon, wake them all so they exit due to Modes.exit for (int i = 0; i < Modes.lockThreadsCount; i++) { pthread_cond_signal(&Modes.lockThreads[i]->cond); } close(mainEpfd); sfree(events); if (Modes.json_dir) { // mark this instance as deactivated, webinterface won't load char pathbuf[PATH_MAX]; snprintf(pathbuf, PATH_MAX, "%s/receiver.json", Modes.json_dir); unlink(pathbuf); } threadSignalJoin(&Threads.misc); if (Modes.sdr_type != SDR_NONE) { threadSignalJoin(&Threads.reader); } threadSignalJoin(&Threads.upkeep); if (Modes.json_dir) { threadSignalJoin(&Threads.json); if (Modes.json_globe_index) { threadSignalJoin(&Threads.globeJson); threadSignalJoin(&Threads.globeBin); } } // after miscThread for the moment if (Modes.api) { apiCleanup(); } if (Modes.apiUpdate) { // after apiCleanup() apiBufferCleanup(); } threadSignalJoin(&Threads.decode); if (Modes.exit < 2) { // force stats to be done, this must happen before network cleanup as it checks network stuff Modes.next_stats_update = 0; priorityTasksRun(); /* Cleanup network setup */ cleanupNetwork(); } threadDestroyAll(); pthread_mutex_destroy(&Modes.traceDebugMutex); pthread_mutex_destroy(&Modes.hungTimerMutex); pthread_mutex_destroy(&Modes.sdrControlMutex); pthread_mutex_destroy(&Modes.aircraftBackMutex); pthread_mutex_destroy(&Modes.aircraftCreateMutex); if (Modes.debug_bogus) { display_total_short_range_stats(); } // If --stats were given, print statistics if (Modes.stats_display_interval) { display_total_stats(); } if (Modes.allPool) { threadpool_destroy(Modes.allPool); destroy_task_group(Modes.allTasks); } if (Modes.tracePool) { threadpool_destroy(Modes.tracePool); destroy_task_group(Modes.traceTasks); } if (Modes.state_dir && Modes.sdrOpenFailed) { fprintf(stderr, "not saving state: SDR failed\n"); sfree(Modes.state_dir); Modes.state_dir = NULL; } Modes.quickFree = 1; // certain shortcuts, only used on exit when the state matters not // frees aircraft when Modes.quickFree is set // writes state if Modes.state_dir is set writeInternalState(); { char *exitString = "Normal exit."; if (Modes.exit != 1) { exitString = "Abnormal exit."; } int64_t uptime = getUptime(); int days = uptime / (24 * HOURS); uptime -= days * (24 * HOURS); int hours = uptime / HOURS; uptime -= hours * HOURS; int minutes = uptime / MINUTES; uptime -= minutes * MINUTES; double seconds = uptime / (double) SECONDS; log_with_timestamp("%s uptime: %2dd %2dh %2dm %.3fs", exitString, days, hours, minutes, seconds); } if (Modes.exit != 1) { cleanup_and_exit(1); } cleanup_and_exit(0); } readsb-3.16/readsb.h000066400000000000000000001267171505057307600143420ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // readsb.h: main program header // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014-2016 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef __DUMP1090_H #define __DUMP1090_H #ifndef ALL_JSON #define ALL_JSON 0 #endif // Default version number, if not overriden by the Makefile #ifndef MODES_READSB_VERSION #define MODES_READSB_VERSION "Unknown" #endif #ifndef MODES_READSB_VARIANT #define MODES_READSB_VARIANT "readsb" #endif #define VERSION_STRING MODES_READSB_VARIANT " version: " MODES_READSB_VERSION // ============================= Include files ========================== #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "threadpool.h" #include #include #include #ifndef __APPLE__ #include #include #endif #include "compat/compat.h" #define SILENT 0 #define VERBOSE 1 // ============================= #defines =============================== #define MODES_DEFAULT_FREQ 1090000000 #define MODES_RTL_BUFFERS 16 // Number of RTL buffers #define MODES_MAG_BUFFERS 12 // Number of magnitude buffers (should be smaller than RTL_BUFFERS for flowcontrol to work) #define MODES_AUTO_GAIN -100 // Use automatic gain #define MODES_MAX_GAIN 999999 // Use max available gain #define MODES_RTL_AGC 590 // Use rtl tuner agc #define MODEAC_MSG_BYTES 2 #define MODES_PREAMBLE_US 8 // microseconds = bits #define MODES_PREAMBLE_SAMPLES (MODES_PREAMBLE_US * 2) #define MODES_PREAMBLE_SIZE (MODES_PREAMBLE_SAMPLES * sizeof(uint16_t)) #define MODES_LONG_MSG_BYTES 14 #define MODES_SHORT_MSG_BYTES 7 #define MODES_LONG_MSG_BITS (MODES_LONG_MSG_BYTES * 8) #define MODES_SHORT_MSG_BITS (MODES_SHORT_MSG_BYTES * 8) #define MODES_LONG_MSG_SAMPLES (MODES_LONG_MSG_BITS * 2) #define MODES_SHORT_MSG_SAMPLES (MODES_SHORT_MSG_BITS * 2) #define MODES_LONG_MSG_SIZE (MODES_LONG_MSG_SAMPLES * sizeof(uint16_t)) #define MODES_SHORT_MSG_SIZE (MODES_SHORT_MSG_SAMPLES * sizeof(uint16_t)) #define MODES_OS_PREAMBLE_SAMPLES (20) #define MODES_OS_PREAMBLE_SIZE (MODES_OS_PREAMBLE_SAMPLES * sizeof(uint16_t)) #define MODES_OS_LONG_MSG_SAMPLES (268) #define MODES_OS_SHORT_MSG_SAMPLES (135) #define MODES_OS_LONG_MSG_SIZE (MODES_LONG_MSG_SAMPLES * sizeof(uint16_t)) #define MODES_OS_SHORT_MSG_SIZE (MODES_SHORT_MSG_SAMPLES * sizeof(uint16_t)) #define MODES_OUT_FLUSH_INTERVAL (500) // max flush interval #define MODES_NET_SNDBUF_SIZE (8*1024) #define MODES_NET_SNDBUF_MAX (7) #define HEX_UNKNOWN (0xDEADBEEF) #define DFTYPE_MODEAC 77 #define INVALID_ALTITUDE (-9999) #define CANARY (0x665225ca79e653a3) #define MESSAGE_RATE_CALC_POINTS (2) // size of various on stack buffers used across the code, let's just be conservative and assume 1 MB of stack // without heavy recursion 3 of those stack buffers can be in use at the same time, at most we expect to to be in use #define QUARTER_STACK (256 * 1024) /* Where did a bit of data arrive from? In order of increasing priority */ typedef enum { SOURCE_INVALID, /* data is not valid */ SOURCE_INDIRECT, /* data is of unknown quality */ SOURCE_MODE_AC, /* A/C message */ SOURCE_SBS, /* data is of unknown quality */ SOURCE_MLAT, /* derived from mlat */ SOURCE_MODE_S, /* data from a Mode S message, no full CRC */ SOURCE_JAERO, /* data is from satellite ADS-C */ SOURCE_MODE_S_CHECKED, /* data from a Mode S message with full CRC */ SOURCE_TISB, /* data from a TIS-B extended squitter message */ SOURCE_ADSR, /* data from a ADS-R extended squitter message */ SOURCE_ADSB, /* data from a ADS-B extended squitter message */ SOURCE_PRIO, /* priority input */ } datasource_t; /* What sort of address is this and who sent it? * (Earlier values are higher priority) */ typedef enum { ADDR_ADSB_ICAO = 0, /* ADS-B, ICAO address, transponder sourced */ ADDR_ADSB_ICAO_NT = 1, /* ADS-B, ICAO address, non-transponder */ ADDR_ADSR_ICAO = 2, /* ADS-R, ICAO address */ ADDR_TISB_ICAO = 3, /* TIS-B, ICAO address */ ADDR_JAERO = 4, ADDR_MLAT = 5, ADDR_OTHER = 6, ADDR_MODE_S = 7, ADDR_ADSB_OTHER = 8, /* ADS-B, other address format */ ADDR_ADSR_OTHER = 9, /* ADS-R, other address format */ ADDR_TISB_TRACKFILE = 10, /* TIS-B, Mode A code + track file number */ ADDR_TISB_OTHER = 11, /* TIS-B, other address format */ ADDR_MODE_A = 12, /* Mode A */ ADDR_UNKNOWN = 15/* unknown address format */ } addrtype_t; // number of types as defined above #define NUM_TYPES 14 typedef enum { UNIT_FEET, UNIT_METERS } altitude_unit_t; typedef enum { ALTITUDE_BARO, ALTITUDE_GEOM } altitude_source_t; typedef enum { AG_INVALID = 0, AG_GROUND = 1, AG_AIRBORNE = 2, AG_UNCERTAIN = 3 } airground_t; typedef enum { SIL_INVALID, SIL_UNKNOWN, SIL_PER_SAMPLE, SIL_PER_HOUR } sil_type_t; typedef enum { CPR_INVALID, CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE } cpr_type_t; typedef enum { CPR_NONE, CPR_LOCAL, CPR_GLOBAL } cpr_local_t; typedef enum { HEADING_INVALID, // Not set HEADING_GROUND_TRACK, // Direction of track over ground, degrees clockwise from true north HEADING_TRUE, // Heading, degrees clockwise from true north HEADING_MAGNETIC, // Heading, degrees clockwise from magnetic north HEADING_MAGNETIC_OR_TRUE, // HEADING_MAGNETIC or HEADING_TRUE depending on the HRD bit in opstatus HEADING_TRACK_OR_HEADING // GROUND_TRACK / MAGNETIC / TRUE depending on the TAH bit in opstatus } heading_type_t; typedef enum { COMMB_UNKNOWN, COMMB_AMBIGUOUS, COMMB_EMPTY_RESPONSE, COMMB_DATALINK_CAPS, COMMB_GICB_CAPS, COMMB_AIRCRAFT_IDENT, COMMB_ACAS_RA, COMMB_VERTICAL_INTENT, COMMB_TRACK_TURN, COMMB_HEADING_SPEED, COMMB_METEOROLOGICAL_ROUTINE } commb_format_t; typedef enum { NAV_MODE_AUTOPILOT = 1, NAV_MODE_VNAV = 2, NAV_MODE_ALT_HOLD = 4, NAV_MODE_APPROACH = 8, NAV_MODE_LNAV = 16, NAV_MODE_TCAS = 32 } nav_modes_t; // Matches encoding of the ES type 28/1 emergency/priority status subfield typedef enum { EMERGENCY_NONE = 0, EMERGENCY_GENERAL = 1, EMERGENCY_LIFEGUARD = 2, EMERGENCY_MINFUEL = 3, EMERGENCY_NORDO = 4, EMERGENCY_UNLAWFUL = 5, EMERGENCY_DOWNED = 6, EMERGENCY_RESERVED = 7 } emergency_t; typedef enum { NAV_ALT_INVALID, NAV_ALT_UNKNOWN, NAV_ALT_AIRCRAFT, NAV_ALT_MCP, NAV_ALT_FMS } nav_altitude_source_t; #define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses #define BADDR (0xff123456) // invalid address used to set stuff like cpr_focus and show_only default value #define MODES_INTERACTIVE_REFRESH_TIME 500 // Milliseconds #define MODES_INTERACTIVE_DISPLAY_TTL 60000 // Delete from display after 60 seconds #define MODES_NET_HEARTBEAT_INTERVAL 60000 // milliseconds //#define ENABLE_DF24 #define HISTORY_SIZE 120 #define HISTORY_INTERVAL 30000 #define MODES_NOTUSED(V) ((void) V) #ifndef AIRCRAFT_HASH_BITS #define AIRCRAFT_HASH_BITS 14 #endif #define MODES_ICAO_FILTER_TTL 60000 #define DB_HASH_BITS 19 #define DB_BUCKETS (1 << DB_HASH_BITS) // this is critical for hashing purposes #define STATE_BLOBS 256 // change naming scheme if increasing this #define LOCK_THREADS_MAX 64 #define PERIODIC_UPDATE (1 * SECONDS) #define REMOVE_STALE_INTERVAL (1 * SECONDS) #define STAT_BUCKETS 90 // 90 * 10 seconds = 15 min (max interval in stats.json) #define RANGEDIRS_BUCKETS 360 #define RANGEDIRS_IVALS 64 #define PING_REJECT (3 * SECONDS) #define PING_DISCONNECT (15 * SECONDS) #define PING_BUCKETS 20 // statistics on round trip time #define PING_BUCKETBASE (24) // milliseconds of first bucket #define PING_BUCKETMULT (1.2) // each bucket will grow by that factor #define PING_REDUCE (1500) // 1.5 seconds #define PING_REDUCE_DURATION (15 * SECONDS) #define GARBAGE_THRESHOLD (512) /* A timestamp that indicates the data is synthetic, created from a * multilateration result */ #define MAGIC_MLAT_TIMESTAMP 0xFF004D4C4154LL #define MAGIC_UAT_TIMESTAMP 0xFF004D4C4155LL #define MAGIC_NOFORWARD_TIMESTAMP 0xFF004D4C4160LL #define MAGIC_ANY_TIMESTAMP 0xFFFFFFFFFFFFULL #define likely(x) __builtin_expect((x),1) #define unlikely(x) __builtin_expect((x),0) #if defined(__llvm__) #define _unroll_8 _Pragma ("unroll 8") #define _unroll_16 _Pragma ("unroll 16") #define _unroll_32 _Pragma ("unroll 32") #elif defined(__GNUC__) #if __GNUC__ >= 8 #define _unroll_8 _Pragma ("GCC unroll 8") #define _unroll_16 _Pragma ("GCC unroll 16") #define _unroll_32 _Pragma ("GCC unroll 32") #else #define _unroll_8 #define _unroll_16 #define _unroll_32 #endif #else #define _unroll_8 #define _unroll_16 #define _unroll_32 #endif void setExit(int arg); int priorityTasksPending(); void priorityTasksRun(); #define MemoryAlignment 32 #define ALIGNED __attribute__((aligned(MemoryAlignment))) static inline void *malloc_or_exit(size_t alignment, size_t size, const char *file, int line) { void *buf = NULL; if (alignment && 0) { // disabled for the moment size_t mod = size % alignment; if (mod != 0) { size += (alignment - mod); } buf = aligned_alloc(alignment, size); if (0) { mod = size % alignment; if (mod != 0) { fprintf(stderr, "aligned_alloc bad alignment: %ld\n", (long) mod); } } } else { buf = malloc(size); } if (unlikely(!buf)) { fprintf(stderr, "FATAL: malloc_or_exit() of size %lld failed: %s:%d (insufficient memory?)\n", (long long) size, file, line); #ifdef CRCDEBUG exit(1); #else setExit(2); // irregular exit ... soon #endif } return buf; } static inline void *calloc_or_exit(size_t alignment, size_t size, const char *file, int line) { void *buf = malloc_or_exit(alignment, size, file, line); memset(buf, 0x0, size); return buf; } static inline void *mmap_or_exit(size_t size, const char *file, int line) { void *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (buf == MAP_FAILED) { fprintf(stderr, "FATAL: cmMmap() of size %lld failed: %s:%d (%s)\n", (long long) size, file, line, strerror(errno)); #ifdef CRCDEBUG exit(1); #else setExit(2); // irregular exit ... soon #endif buf = NULL; } return buf; } static inline void munmap_or_exit(void *ptr, size_t size, const char *file, int line) { int res = munmap(ptr, size); if (res < 0) { fprintf(stderr, "FATAL: cmMunmap() failed: %s:%d (%s)\n", file, line, strerror(errno)); abort(); } return; } #define cmMmap(size) mmap_or_exit(size, __FILE__, __LINE__) #define cmMunmap(ptr, size) munmap_or_exit(ptr, size, __FILE__, __LINE__) // disable this ... maybe it test in the future if it makes a diff if i'm bored #if 0 #define cmalloc(size) malloc_or_exit(MemoryAlignment, size, __FILE__, __LINE__) #define cmCalloc(size) calloc_or_exit(MemoryAlignment, size, __FILE__, __LINE__) #else #define cmalloc(size) malloc_or_exit(0, size, __FILE__, __LINE__) #define cmCalloc(size) calloc_or_exit(0, size, __FILE__, __LINE__) #endif // Include subheaders after all the #defines are in place #include "toString.h" #include "util.h" #include "fasthash.h" #include "anet.h" #include "net_io.h" #include "crc.h" #include "demod_2400.h" #include "stats.h" #include "cpr.h" #include "icao_filter.h" #include "convert.h" #include "sdr.h" #include "aircraft.h" #include "globe_index.h" #include "receiver.h" #include "geomag.h" #include "json_out.h" #include "api.h" //======================== structure declarations ========================= typedef enum { SDR_NONE = 0, SDR_IFILE, SDR_RTLSDR, SDR_BLADERF, SDR_MICROBLADERF, SDR_HACKRF, SDR_MODESBEAST, SDR_PLUTOSDR, SDR_SOAPYSDR, SDR_GNS } sdr_type_t; // Structure representing one magnitude buffer struct mag_buf { int64_t sampleTimestamp; // Clock timestamp of the start of this block, 12MHz clock double mean_level; // Mean of normalized (0..1) signal level double mean_power; // Mean of normalized (0..1) power level uint32_t dropped; // Number of dropped samples preceding this buffer unsigned length; // Number of valid samples _after_ overlap. Total buffer length is buf->length + Modes.trailing_samples. int64_t sysTimestamp; // Estimated system time at start of block int64_t sysMicroseconds; // sysTimestamp in microseconds uint32_t loudEvents; uint32_t noiseLowSamples; uint32_t noiseHighSamples; uint32_t padding2; uint16_t *data; // Magnitude data. Starts with Modes.trailing_samples worth of overlap from the previous block #if defined(__arm__) /*padding 4 bytes*/ uint32_t padding; #endif }; // Program global state struct _Threads { threadT upkeep; // runs priorityTasksUpdate, locks most other threads when doing its thing threadT decode; // thread doing demodulation, decoding and networking threadT reader; threadT json; // thread writing json threadT globeJson; // thread writing json threadT globeBin; // thread writing binCraft threadT misc; threadT apiUpdate; }; extern struct _Threads Threads; struct modeMessage; struct messageBuffer { struct modesMessage *msg; int len; int alloc; int id; struct client *activeClient; }; struct _Modes { // Internal state pthread_mutex_t traceDebugMutex; int num_procs; int allPoolSize; threadpool_t *allPool; task_group_t *allTasks; uint32_t sdr_buf_size; uint32_t sdr_buf_samples; int64_t traceWriteTimelimit; int tracePoolSize; threadpool_t *tracePool; task_group_t *traceTasks; int triggerPastDayTraceWrite; int lockThreadsCount; threadT *lockThreads[LOCK_THREADS_MAX]; struct timespec hungTimer1; struct timespec hungTimer2; pthread_mutex_t hungTimerMutex; char *currentTask; int64_t joinTimeout; unsigned first_free_buffer; // Entry in mag_buffers that will next be filled with input. unsigned first_filled_buffer; // Entry in mag_buffers that has valid data and will be demodulated next. If equal to next_free_buffer, there is no unprocessed data. unsigned trailing_samples; // extra trailing samples in magnitude buffers int volatile exit; // Exit from the main loop when true int volatile exitSoon; int fd; // --ifile option file descriptor input_format_t input_format; // --iformat option iq_convert_fn converter_function; char * dev_name; pthread_mutex_t sdrControlMutex; int8_t sdrInitialized; int8_t sdrOpenFailed; int8_t increaseGain; int8_t lowerGain; int8_t autoGain; int8_t gainQuiet; int8_t gainStartup; char *gainArg; uint32_t loudThreshold; uint32_t noiseLowThreshold; uint32_t noiseHighThreshold; int minGain; int gain; int dc_filter; // should we apply a DC filter? int enable_agc; sdr_type_t sdr_type; // where are we getting data from? int freq; int ppm_error; char aneterr[ANET_ERR_LEN]; struct net_service_group services_in; // Active services which primarily receive data struct net_service_group services_out; // Active services which primarily send data int exitNowEventfd; int exitSoonEventfd; int net_epfd; // epoll fd used for most network stuff int net_event_count; int net_maxEvents; struct epoll_event *net_events; struct messageBuffer *netMessageBuffer; int decodeThreads; threadpool_t *decodePool; task_group_t *decodeTasks; pthread_mutex_t decodeLock; pthread_mutex_t trackLock; pthread_mutex_t outputLock; int max_fds; int max_fds_api; int max_fds_net; int modesClientCount; int api_fds_per_thread; int total_aircraft_count; int json_aircraft_count; float estimated_ppm; uint64_t trace_chunk_size; uint64_t trace_cache_size; uint64_t trace_current_size; uint64_t trace_last_size; uint64_t aircraft_data_size; ssize_t volatile state_chunk_size; ssize_t volatile state_chunk_size_read; int acHashBits; int acBuckets; struct aircraft **aircraft; struct aircraftBack *aircraftBack; struct aircraft *aircraftBackFree; pthread_mutex_t aircraftBackMutex; pthread_mutex_t aircraftCreateMutex; atomic_int aircraftBackSpinlock; struct craftArray *globeLists; int receiver_table_hash_bits; int receiver_table_size; struct receiver **receiverTable; struct craftArray aircraftActive; dbEntry *db; dbEntry **dbIndex; struct char_buffer dbRaw; dbEntry *db2; dbEntry **db2Index; struct char_buffer db2Raw; int64_t dbModificationTime; int64_t receiverCount; struct net_writer raw_out; // Raw output struct net_writer beast_out; // Beast-format output struct net_writer beast_reduce_out; // Reduced data Beast-format output struct net_writer beast_in; // for sending pings to clients sending us beast data struct net_writer garbage_out; // Beast-format output struct net_writer sbs_out; // SBS-format output struct net_writer sbs_out_replay; // SBS-format output struct net_writer sbs_out_mlat; // SBS-format output struct net_writer sbs_out_jaero; // SBS-format output struct net_writer sbs_out_prio; // SBS-format output struct net_writer json_out; // SBS-format output struct net_writer asterix_out; // Asterix output struct net_writer feedmap_out; // SBS-format output struct net_writer vrs_out; // SBS-format output struct net_writer fatsv_out; // FATSV-format output struct net_writer gpsd_in; // for sending 1 line to gpsd struct net_writer uat_replay_out; // UAT replay output struct net_service *beast_in_service; struct net_service *uat_in_service; struct hexInterval* deleteTrace; int write_state_blob; int writeInternalState; char *replace_state_blob; int64_t replace_state_inhibit_traces_until; int64_t network_time_limit; uint32_t currentPing; int8_t apiUpdate; // creates json snippets also by non api stuff int8_t api; // enable api output int8_t apiBufferInitDone; int apiThreadCount; atomic_int apiWorkerCpuMicro; atomic_uint apiRequestCounter; atomic_int recentTraceWrites; atomic_int fullTraceWrites; atomic_int permTraceWrites; struct net_service apiService; struct apiCon **apiListeners; struct apiBuffer apiBuffer[2]; atomic_int *apiFlip; struct apiThread *apiThread; pthread_mutex_t apiFlipMutex; // mutex to read apiFlip float messageRate; uint32_t messageRateAcc[MESSAGE_RATE_CALC_POINTS]; int64_t nextMessageRateCalc; // Configuration int8_t nfix_crc; // Number of crc bit error(s) to correct int8_t fixDF; // fix message type single bit errors that become DF17 int8_t check_crc; // Only display messages with good CRC int8_t raw; // Raw output format int8_t mode_ac; // Enable decoding of SSR Modes A & C int8_t mode_ac_auto; // allow toggling of A/C by Beast commands int8_t debug_net; int8_t debug_serial; int8_t debug_flush; int8_t debug_no_discard; int8_t debug_nextra; int8_t debug_cpr; int8_t debug_speed_check; int8_t debug_garbage; int8_t debug_receiver; int8_t debug_rough_receiver_location; int8_t debug_traceCount; int8_t debug_traceAlloc; int8_t debug_sampleCounter; int8_t debug_dbJson; int8_t debug_ACAS; int8_t debug_api; int8_t debug_recent; int8_t debug_squawk; int8_t debug_ping; int8_t debug_callsign; int8_t debug_nogps; int8_t debug_uuid; int8_t debug_bogus; int8_t decode_all; int8_t debug_maxRange; int8_t debug_removeStaleDuration; int8_t debug_receiverRangeLimit; int8_t debug_yeet; int8_t debug_7700; int8_t debug_send_uuid; int8_t debug_provoke_segfault; int8_t debug_position_timing; int8_t debug_lastStatus; int8_t debug_gps; int8_t debug_planefinder; int8_t debug_zstd; int8_t legacy_history; int8_t enable_zstd; int8_t incrementId; int8_t omitGlobeFiles; int8_t tar1090_no_globe; int8_t enableAcasCsv; int8_t enableAcasJson; int8_t dump_accept_synthetic_now; int8_t dump_ignore_synthetic_now; int8_t syntethic_now_suppress_errors; int8_t tar1090_use_api; int8_t verbose; int8_t net_verbatim; // if true, send the original message, not the CRC-corrected one int8_t netReceiverId; int8_t ping; int8_t netReceiverIdPrint; int8_t netReceiverIdJson; int8_t netIngest; int8_t readProxy; int8_t enableClientsJson; int8_t forward_mlat; // forward beast mlat messages to beast output ports int8_t forward_mlat_sbs; // forward mlat messages to sbs output ports int8_t beast_forward_noforward; int8_t beast_set_noforward_timestamp; int8_t quiet; // Suppress stdout int8_t interactive; // Interactive mode int8_t stats_range_histo; // Collect/show a range histogram? int8_t outline_json; // write a range outline json file int8_t onlyaddr; // Print only ICAO addresses int8_t metric; // Use metric units int8_t use_gnss; // Use GNSS altitudes with H suffix ("HAE", though it isn't always) when available int8_t mlat; // Use Beast ascii format for raw data output, i.e. @...; iso *...; int8_t json_location_accuracy; // Accuracy of location metadata: 0=none, 1=approx, 2=exact int8_t net; // Enable networking int8_t net_only; // Enable just networking int8_t jsonLongtype; int8_t viewadsb; int8_t sbsReduce; // apply beast reduce logic to SBS messages int8_t asterixReduce; // apply beast reduce logic to SBS messages int8_t beast_reduce_optimize_mlat; // keep all messages relevant to mlat-client (best-effort) // use mmap for certain allocations to try and take advantage of transparent hugepages int8_t thp; int ingestLimitRate; int position_persistence; // Maximum number of consecutive implausible positions from global CPR to invalidate a known position int json_reliable; uint32_t filterDF; // Only show messages with certain DF types uint32_t filterDFbitset; // Bitset, Only show messages with these DF types int64_t trackExpireJaero; int64_t trackExpireMax; uint32_t cpr_focus; uint32_t trace_focus; uint32_t leg_focus; uint32_t show_only; // Only show messages from this ICAO uint32_t process_only; // Only process messages from this ICAO uint64_t receiver_focus; uint32_t preambleThreshold; uint32_t net_forward_min_messages; int net_output_flush_size; // Minimum Size of output data int writerBufSize; // Maximum Size of output data int32_t net_output_beast_reduce_interval; // Position update interval for data reduction int32_t ping_reduce; int32_t ping_reject; int32_t log_usb_jitter; int32_t devel_log_ppm; int32_t traceLastMax; int32_t beforeLandHighRes; int32_t afterGroundTransitionHighRes; float beast_reduce_filter_distance; float beast_reduce_filter_altitude; int64_t net_connector_delay; int64_t net_connector_delay_min; int64_t next_reconnect_callback; int64_t last_connector_fail; int32_t net_heartbeat_interval; // TCP heartbeat interval (milliseconds) int32_t net_output_flush_interval; // Maximum interval (in milliseconds) between outputwrites int32_t net_output_flush_interval_beast_reduce; // Maximum interval (in milliseconds) between outputwrites double fUserLat; // Users receiver/antenna lat/lon needed for initial surface location double fUserLon; // Users receiver/antenna lat/lon needed for initial surface location double fUserAlt; // receiver altitude AMSL in meters double maxRange; // Absolute maximum decoding range, in *metres* char *latString; char *lonString; double sample_rate; // actual sample rate in use (in hz) int64_t interactive_display_ttl; // Interactive mode: TTL display int64_t json_interval; // Interval between rewriting the json aircraft file, in milliseconds; also the advertised map refresh interval int64_t stats_display_interval; // Interval (millis) between stats dumps, int64_t range_outline_duration; int64_t writeTracesActualDuration; // how long the trace writing cycle took int64_t auto_exit; double mlatForceDistance; int64_t mlatForceInterval; char *db_file; char *net_output_raw_ports; // List of raw output TCP ports char *net_input_raw_ports; // List of raw input TCP ports char *net_output_uat_replay_ports; // List of UAT replay output TCP ports char *net_input_uat_ports; // List of UAT input TCP ports char *net_input_planefinder_ports; // List of planefinder input TCP ports char *net_output_sbs_ports; // List of SBS output TCP ports char *net_input_sbs_ports; // List of SBS input TCP ports char *net_output_jaero_ports; // jaero SBS output ports char *net_input_jaero_ports; // jaero SBS input ports char *net_input_beast_ports; // List of Beast input TCP ports char *net_output_beast_ports; // List of Beast output TCP ports char *net_output_beast_reduce_ports; // List of Beast output TCP ports char *net_output_asterix_ports; // List of Asterix output TCP ports char *net_input_asterix_ports; // List of Asterix input TCP ports char *net_output_json_ports; char *net_output_api_ports; char *garbage_ports; char *net_output_vrs_ports; // List of VRS output TCP ports int64_t net_output_vrs_interval; int64_t net_output_json_interval; int net_output_json_include_nopos; struct net_connector *net_connectors; // client connectors int net_connectors_count; int net_connectors_size; int64_t synthetic_now; char *uuidFile; char *filename; // Input form file, --ifile option char *net_bind_address; // Bind address char *json_dir; // Path to json base directory, or NULL not to write json. char *globe_history_dir; char *fullTraceDir; // reduce memory usage in /run tmpfs by writing full traces to this directory char *state_dir; char *state_parent_dir; char *dump_beast_dir; // write raw beast with a timestamp every millisecond for low level replay zstd_fw_t *dump_fw; int64_t dump_next_ts; // last timestamp sent int32_t dump_interval; int32_t dump_beast_index; uint64_t dump_lastReceiverId; int8_t dump_compressionLevel; int8_t writeTraces; int8_t dump_reduce; // only dump beast that would be sent out according to reduce_interval int8_t state_only_on_exit; int8_t quickFree; int64_t state_write_interval; char *prom_file; int64_t heatmap_current_interval; int64_t heatmap_interval; // don't change data type int heatmap; char *heatmap_dir; int64_t keep_traces; // how long traces are saved in internal memory int64_t json_trace_interval; // max time ignoring new positions for trace int32_t traceMax; // max trace length int32_t traceReserve; // grow trace allocation if we have less than traceReserve free spots int32_t traceRecentPoints; int32_t traceCachePoints; int32_t traceChunkPoints; int32_t traceChunkMaxBytes; int json_globe_index; // Enable extra globe indexed json files. int acasFD1; // file descriptor to write acasFDs to int acasFD2; struct tile *json_globe_special_tiles; int32_t *json_globe_indexes; int32_t json_globe_indexes_len; int specialTileCount; int json_gzip; // Enable extra globe indexed json files. int beast_fd; // Local Modes-S Beast handler int beast_baudrate; // Mode-S beast and similar baud rate char *beast_serial; // Modes-S Beast device path struct client *serial_client; int net_sndbuf_size; // TCP output buffer size (2^n multiplier) int netBufSize; // TCP output buffer size int json_aircraft_history_next; int json_aircraft_history_full; int trace_hist_only; int sbsOverrideSquawk; float messageRateMult; uint32_t binCraftVersion; // never change the type for this variable int8_t userLocationValid; int8_t userLocationRef; int8_t biastee; int8_t triggerPermWriteDay; int8_t acasDay; int8_t traceDay; int8_t onlyBin; // only write binCraft for globe (1) and also aircraft.json (2) int8_t enableBinGz; int8_t updateStats; int8_t staleStop; int8_t tcpBuffersAuto; struct timespec reader_cpu_accumulator; // CPU time used by the reader thread, copied out and reset by the main thread under the mutex ALIGNED struct mag_buf mag_buffers[MODES_MAG_BUFFERS]; // Converted magnitude buffers from RTL or file input int64_t startup_time; int64_t startup_time_mono; int64_t next_stats_update; int64_t next_stats_display; int64_t next_api_update; int64_t next_remove_stale; int stats_bucket; // index that has just been writte to ALIGNED struct stats stats_10[STAT_BUCKETS]; struct stats stats_current; struct stats stats_alltime; struct stats stats_periodic; struct stats stats_1min; struct stats stats_5min; struct stats stats_15min; struct statsCount globalStatsCount; int lastRangeDirHour; struct distCoords (*rangeDirs)[RANGEDIRS_BUCKETS]; int64_t apiShutdownDelay; }; extern struct _Modes Modes; // The struct we use to store information about a decoded message. struct modesMessage { // Generic fields unsigned char msg[MODES_LONG_MSG_BYTES]; // Binary message. unsigned char verbatim[MODES_LONG_MSG_BYTES]; // Binary message, as originally received before correction double signalLevel; // RSSI, in the range [0..1], as a fraction of full-scale power struct client *client; // network client this message came from, NULL otherwise struct aircraft *aircraft; // tracked aircraft associated with this message or NULL struct messageBuffer *messageBuffer; int64_t timestamp; // Timestamp of the message (12MHz clock) int64_t sysTimestamp; // Timestamp of the message (system time) uint64_t receiverId; // zero if not transmitted int msgtype; // Downlink format # int msgbits; // Number of bits in message int score; // Scoring from scoreModesMessage, if used int receiverCountMlat; // number of receivers for MLAT messages int mlatEPU; // estimated position uncertainty int correctedbits; // No. of bits corrected int decodeResult; uint32_t crc; // Message CRC uint32_t addr; // Address Announced uint32_t maybe_addr; // probably the address, good chance to be wrong addrtype_t addrtype; // address format / source int8_t remote; // If set this message is from a remote station int8_t sbs_in; // Signifies this message is coming from basestation input int8_t address_reliable; int8_t sbsMsgType; // SBS message type int8_t reduce_forward; // forward this message for reduced beast output int8_t garbage; // from garbage receiver int8_t duplicate; // associated position is a duplicate int8_t duplicate_checked; // duplicate check done int8_t pos_bad; // speed_check failed int8_t pos_ignore; // associated position is old / delayed / misc error int8_t pos_old; // associated position is old / delayed / misc error int8_t pos_receiver_range_exceeded; int8_t trackUnreliable; int8_t speedUnreliable; int8_t in_disc_cache; int8_t jsonPositionOutputEmit; datasource_t source; // Characterizes the overall message source // Raw data, just extracted directly from the message // The names reflect the field names in Annex 4 unsigned IID; // extracted from CRC of DF11s unsigned AA; unsigned AC; unsigned CA; unsigned CC; unsigned CF; unsigned DR; unsigned FS; unsigned ID; unsigned KE; unsigned ND; unsigned RI; unsigned SL; unsigned UM; unsigned VS; unsigned metype; // DF17/18 ME type unsigned mesub; // DF17/18 ME subtype unsigned char MB[7]; unsigned char MD[10]; unsigned char ME[7]; unsigned char MV[7]; // Decoded data bool baro_alt_valid; bool geom_alt_valid; bool track_valid; bool track_rate_valid; bool heading_valid; bool roll_valid; bool gs_valid; bool ias_valid; bool tas_valid; bool mach_valid; bool baro_rate_valid; bool geom_rate_valid; bool squawk_valid; bool callsign_valid; bool cpr_valid; bool cpr_odd; bool cpr_decoded; bool cpr_relative; bool category_valid; bool geom_delta_valid; bool from_mlat; bool from_tisb; bool spi_valid; bool spi; bool alert_valid; bool alert; bool emergency_valid; bool sbs_pos_valid; bool alt_q_bit; bool acas_ra_valid; bool geom_alt_derived; bool wind_valid; bool oat_valid; bool static_pressure_valid; bool turbulence_valid; bool humidity_valid; bool met_source_valid; bool squawk_emergency_valid; bool squawk_emergency; // valid if baro_alt_valid: int baro_alt; // Altitude in either feet or meters altitude_unit_t baro_alt_unit; // the unit used for altitude // valid if geom_alt_valid: int geom_alt; // Altitude in either feet or meters altitude_unit_t geom_alt_unit; // the unit used for altitude // following fields are valid if the corresponding _valid field is set: int geom_delta; // Difference between geometric and baro alt float heading; // ground track or heading, degrees (0-359). Reported directly or computed from from EW and NS velocity heading_type_t heading_type; // how to interpret 'track_or_heading' float track_rate; // Rate of change of track, degrees/second float roll; // Roll, degrees, negative is left roll struct { // Groundspeed, kts, reported directly or computed from from EW and NS velocity // For surface movement, this has different interpretations for v0 and v2; both // fields are populated. The tracking layer will update "gs.selected". float v0; float v2; float selected; } gs; unsigned ias; // Indicated airspeed, kts unsigned tas; // True airspeed, kts double mach; // Mach number int baro_rate; // Rate of change of barometric altitude, feet/minute int geom_rate; // Rate of change of geometric (GNSS / INS) altitude, feet/minute char callsign[16]; // 8 chars flight number, NUL-terminated uint32_t squawkHex; // 13 bits identity (Squawk), encoded as 4 hex digits uint32_t squawkDec; // Squawk as a decimal number unsigned category; // A0 - D7 encoded as a single hex byte emergency_t emergency; // emergency/priority status // valid if cpr_valid cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B) uint32_t cpr_lat; // Non decoded latitude. uint32_t cpr_lon; // Non decoded longitude. uint32_t cpr_nucp; // NUCp/NIC value implied by message type airground_t airground; // air/ground state // valid if cpr_decoded: double decoded_lat; double decoded_lon; unsigned decoded_nic; unsigned decoded_rc; double distance_traveled; // set in speed_check, zero is invalid double receiver_distance; // distance to receiver float calculated_track; // set in speed_check, -1 is invalid // meteorological int wind_speed; float wind_direction; float oat; int static_pressure; int turbulence; float humidity; int met_source; commb_format_t commb_format; // Inferred format of a comm-b message // various integrity/accuracy things struct { bool nic_a_valid; bool nic_b_valid; bool nic_c_valid; bool nic_baro_valid; bool nac_p_valid; bool nac_v_valid; bool gva_valid; bool sda_valid; bool nic_a; // if nic_a_valid bool nic_b; // if nic_b_valid bool nic_c; // if nic_c_valid bool nic_baro; // if nic_baro_valid unsigned nac_p; // if nac_p_valid unsigned nac_v; // if nac_v_valid unsigned sil; // if sil_type != SIL_INVALID unsigned gva; // if gva_valid unsigned sda; // if sda_valid sil_type_t sil_type; } accuracy; // Operational Status struct { sil_type_t sil_type; heading_type_t tah; heading_type_t hrd; enum { ANGLE_HEADING, ANGLE_TRACK } track_angle; unsigned cc_lw; unsigned cc_antenna_offset; unsigned valid : 1; unsigned version : 3; unsigned om_acas_ra : 1; unsigned om_ident : 1; unsigned om_atc : 1; unsigned om_saf : 1; unsigned cc_acas : 1; unsigned cc_cdti : 1; unsigned cc_1090_in : 1; unsigned cc_arv : 1; unsigned cc_ts : 1; unsigned cc_tc : 2; unsigned cc_uat_in : 1; unsigned cc_poa : 1; unsigned cc_b2_low : 1; unsigned cc_lw_valid : 1; } opstatus; // combined: // Target State & Status (ADS-B V2 only) // Comm-B BDS4,0 Vertical Intent struct { unsigned fms_altitude; // FMS selected altitude unsigned mcp_altitude; // MCP/FCU selected altitude float qnh; // altimeter setting (QFE or QNH/QNE), millibars float heading; // heading, degrees (0-359) (could be magnetic or true heading; magnetic recommended) bool heading_valid; bool fms_altitude_valid; bool mcp_altitude_valid; bool qnh_valid; bool modes_valid; heading_type_t heading_type; nav_altitude_source_t altitude_source; nav_modes_t modes; } nav; }; /* All the program options */ enum { OptDeviceType = 700, OptDevice, OptGain, OptFreq, OptInteractive, OptNoInteractive, OptInteractiveTTL, OptRaw, OptPreambleThreshold, OptModeAc, OptModeAcAuto, OptForwardMlat, OptForwardMlatSbs, OptLat, OptLon, OptMaxRange, OptFix, OptNoFix, OptNoFixDf, OptAggressive, OptMlat, OptAutoExit, OptStats, OptStatsRange, OptStatsEvery, OptRangeOutlineDuration, OptOnlyAddr, OptMetric, OptGnss, OptSnip, OptDebug, OptDevel, OptReceiverFocus, OptCprFocus, OptLegFocus, OptTraceFocus, OptQuiet, OptShowOnly, OptProcessOnly, OptFilterDF, OptJsonDir, OptJsonGzip, OptJsonOnlyBin, OptEnableBinGz, OptJsonReliable, OptPositionPersistence, OptJaeroTimeout, OptDbFile, OptDbFileLongtype, OptPromFile, OptGlobeHistoryDir, OptStateDir, OptStateInterval, OptStateOnlyOnExit, OptHeatmap, OptHeatmapDir, OptDumpBeastDir, OptJsonTime, OptJsonLocAcc, OptJsonGlobeIndex, OptAcHashBits, OptJsonTraceInt, OptJsonTraceHistOnly, OptFullTraceDir, OptDcFilter, OptBiasTee, OptNet, OptNetOnly, OptNetBindAddr, OptNetRiPorts, OptNetRoPorts, OptNetUatReplayPorts, OptNetUatInPorts, OptNetSbsPorts, OptNetSbsInPorts, OptNetJaeroPorts, OptNetJaeroInPorts, OptNetBiPorts, OptNetBoPorts, OptNetAsterixInPorts, OptNetAsterixOutPorts, OptNetAsterixReduce, OptNetBeastReducePorts, OptNetBeastReduceInterval, OptNetBeastReduceOptimizeMlat, OptNetBeastReduceFilterAlt, OptNetBeastReduceFilterDist, OptNetSbsReduce, OptNetVRSPorts, OptNetVRSInterval, OptNetJsonPorts, OptNetJsonPortInterval, OptNetJsonPortNoPos, OptNetApiPorts, OptApiShutdownDelay, OptTar1090UseApi, OptNetRoSize, OptNetRoInterval, OptNetRoIntervalBeastReduce, OptNetConnector, OptNetConnectorDelay, OptNetHeartbeat, OptNetBuffer, OptTcpBuffersAuto, OptNetVerbatim, OptNetReceiverId, OptNetReceiverIdJson, OptNetIngest, OptSdrBufSize, OptGarbage, OptDecodeThreads, OptUuidFile, OptRtlSdrEnableAgc, OptRtlSdrPpm, OptBeastSerial, OptBeastBaudrate, OptBeastDF1117, OptBeastDF045, OptBeastMlatTimeOff, OptBeastCrcOff, OptBeastFecOff, OptBeastModeAc, OptIfileName, OptIfileFormat, OptIfileThrottle, OptBladeFpgaDir, OptBladeDecim, OptBladeBw, OptHackRfGainEnable, OptHackRfVgaGain, OptPlutoUri, OptPlutoNetwork, OptSoapyAntenna, OptSoapyBandwith, OptSoapyEnableAgc, OptSoapyGainElement, }; // This one needs modesMessage: #include "track.h" #include "mode_s.h" #include "comm_b.h" // ======================== function declarations ========================= #ifdef __cplusplus extern "C" { #endif // // Functions exported from mode_ac.c // int detectModeA (uint16_t *m, struct modesMessage *mm); void decodeModeAMessage (struct modesMessage *mm, int ModeA); void modeACInit (); int modeAToModeC (unsigned int modeA); unsigned modeCToModeA (int modeC); // // Functions exported from interactive.c // void interactiveInit (void); void interactiveShowData (void); void interactiveCleanup (void); // Provided by readsb.c & viewadsb.c void receiverPositionChanged (float lat, float lon, float alt); #ifdef __cplusplus } #endif #endif // __DUMP1090_H readsb-3.16/receiver.c000066400000000000000000000317551505057307600146760ustar00rootroot00000000000000#include "readsb.h" #define RECEIVER_MAX_RANGE 800e3 uint32_t receiverHash(uint64_t id) { uint64_t h = 0x30732349f7810465ULL ^ (4 * 0x2127599bf4325c37ULL); h ^= mix_fasthash(id); h -= (h >> 32); h &= (1ULL << 32) - 1; h -= (h >> Modes.receiver_table_hash_bits); return h & (Modes.receiver_table_size - 1); } struct receiver *receiverGet(uint64_t id) { if (!Modes.receiverTable) { return NULL; } struct receiver *r = Modes.receiverTable[receiverHash(id)]; while (r && r->id != id) { r = r->next; } return r; } struct receiver *receiverCreate(uint64_t id) { if (!Modes.receiverTable) { return NULL; } struct receiver *r = receiverGet(id); if (r) { return r; } if (Modes.receiverCount > 2 * Modes.receiver_table_size) { return NULL; } uint32_t hash = receiverHash(id); r = cmalloc(sizeof(struct receiver)); *r = (struct receiver) {0}; r->id = id; r->next = Modes.receiverTable[hash]; r->firstSeen = r->lastSeen = mstime(); Modes.receiverTable[hash] = r; Modes.receiverCount++; if (Modes.receiverCount > Modes.receiver_table_size * 3 / 4) { static int64_t antiSpam; int64_t now = mstime(); if (now > antiSpam && Modes.receiver_table_hash_bits == 16) { antiSpam = now + 60 * SECONDS; fprintf(stderr, "receiverTable fill: %0.8f\n", Modes.receiverCount / (double) Modes.receiver_table_size); } } if (Modes.debug_receiver && Modes.receiverCount % 128 == 0) fprintf(stderr, "receiverCount: %"PRIu64"\n", Modes.receiverCount); return r; } static void receiverDebugPrint(struct receiver *r, char *message) { if (1) { return; } fprintf(stderr, "%016"PRIx64" %9lld %6.1f %6.1f %6.1f %6.1f %s\n", r->id, (long long) r->positionCounter, r->latMin, r->latMax, r->lonMin, r->lonMax, message); } static void receiverMaintenance(struct receiver *r) { double decay = 0.005 * RECEIVER_MAINTENANCE_INTERVAL / SECONDS; receiverDebugPrint(r, "beforeDecay"); // only decay if extent is pretty large if (r->latMax - r->latMin > 10) { r->latMax -= decay; r->latMin += decay; } if (r->lonMax - r->lonMin > 10) { r->lonMax -= decay; r->lonMin += decay; } receiverDebugPrint(r, "afterDecay"); } void receiverTimeout(int part, int nParts, int64_t now) { if (!Modes.receiverTable) { return; } int stride = Modes.receiver_table_size / nParts; int start = stride * part; int end = start + stride; //fprintf(stderr, "START: %8d END: %8d\n", start, end); for (int i = start; i < end; i++) { struct receiver **r = &Modes.receiverTable[i]; struct receiver *del; while (*r) { /* receiver *b = *r; fprintf(stderr, "%016"PRIx64" %9"PRu64" %4.0f %4.0f %4.0f %4.0f\n", b->id, b->positionCounter, b->latMin, b->latMax, b->lonMin, b->lonMax); */ if ( (Modes.receiverCount > Modes.receiver_table_size && (*r)->lastSeen < now - 20 * MINUTES) || (now > (*r)->lastSeen + 24 * HOURS) || ((*r)->badExtent && now > (*r)->badExtent + 30 * MINUTES) ) { del = *r; *r = (*r)->next; Modes.receiverCount--; free(del); } else { receiverMaintenance(*r); r = &(*r)->next; } } } } void receiverInit() { Modes.receiver_table_size = 1 << Modes.receiver_table_hash_bits; Modes.receiverTable = cmalloc(Modes.receiver_table_size * sizeof(struct receiver *)); memset(Modes.receiverTable, 0x0, Modes.receiver_table_size * sizeof(struct receiver *)); } void receiverCleanup() { if (!Modes.receiverTable) { return; } for (int i = 0; i < Modes.receiver_table_size; i++) { struct receiver *r = Modes.receiverTable[i]; struct receiver *next; while (r) { next = r->next; free(r); r = next; } } sfree(Modes.receiverTable); Modes.receiverCount = 0; } int receiverPositionReceived(struct aircraft *a, struct modesMessage *mm, double lat, double lon, int64_t now) { uint64_t id = mm->receiverId; if (id == 0 || lat > 85.0 || lat < -85.0 || lon < -179.9 || lon > 179.9) { return RECEIVER_RANGE_UNCLEAR; } int reliabilityRequired = Modes.position_persistence * 3 / 4; if (Modes.viewadsb || Modes.receiver_focus) { reliabilityRequired = imin(2, Modes.position_persistence); } // we only use ADS-B positions in the air with sufficient reliability to affect the receiver position / extent int noModifyReceiver = (mm->source != SOURCE_ADSB || mm->cpr_type == CPR_SURFACE || a->pos_reliable_odd < reliabilityRequired || a->pos_reliable_even < reliabilityRequired); struct receiver *r = receiverGet(id); if (!r || r->positionCounter == 0) { if (noModifyReceiver) { return RECEIVER_RANGE_UNCLEAR; } r = receiverCreate(id); if (!r) { return RECEIVER_RANGE_UNCLEAR; } r->lonMin = lon; r->lonMax = lon; r->latMin = lat; r->latMax = lat; } // diff before applying new position (we'll just get distance zero for a new receiver, this is fine) struct receiver before = *r; double latDiff = before.latMax - before.latMin; double lonDiff = before.lonMax - before.lonMin; double rlat = r->latMin + latDiff / 2; double rlon = r->lonMin + lonDiff / 2; double distance = greatcircle(rlat, rlon, lat, lon, 1); if (!noModifyReceiver) { if (distance < RECEIVER_MAX_RANGE) { r->lonMin = fmin(r->lonMin, lon); r->latMin = fmin(r->latMin, lat); r->lonMax = fmax(r->lonMax, lon); r->latMax = fmax(r->latMax, lat); if ( before.latMin != r->latMin || before.latMax != r->latMax || before.lonMin != r->lonMin || before.lonMax != r->lonMax ) { //receiverDebugPrint(r, "growingExtent"); } r->goodCounter++; r->badCounter = fmax(0, r->badCounter - 0.5); } if (!r->badExtent && distance > RECEIVER_MAX_RANGE) { int badExtent = 1; for (int i = 0; i < RECEIVER_BAD_AIRCRAFT; i++) { struct bad_ac *bad = &r->badAircraft[i]; if (bad->addr == a->addr) { badExtent = 0; break; } } for (int i = 0; i < RECEIVER_BAD_AIRCRAFT; i++) { struct bad_ac *bad = &r->badAircraft[i]; if (now - bad->ts > 3 * MINUTES) { // new entry bad->ts = now; bad->addr = a->addr; badExtent = 0; break; } } if (badExtent) { r->badExtent = now; if (Modes.debug_receiver) { char uuid[32]; // needs 18 chars and null byte sprint_uuid1(r->id, uuid); fprintf(stderr, "receiverBadExtent: %0.0f nmi hex: %06x id: %s #pos: %9"PRIu64" %12.5f %12.5f %4.0f %4.0f %4.0f %4.0f\n", distance / 1852.0, a->addr, uuid, r->positionCounter, lat, lon, before.latMin, before.latMax, before.lonMin, before.lonMax); } } } } if (!noModifyReceiver) { r->positionCounter++; r->lastSeen = now; } if (distance > RECEIVER_MAX_RANGE) { return RECEIVER_RANGE_BAD; } return RECEIVER_RANGE_GOOD; } struct receiver *receiverGetReference(uint64_t id, double *lat, double *lon, struct aircraft *a, int noDebug) { if (!Modes.receiverTable) { return NULL; } struct receiver *r = receiverGet(id); if (!(Modes.debug_receiver && a && a->addr == Modes.cpr_focus)) { noDebug = 1; } if (!r) { if (!noDebug) { fprintf(stderr, "id:%016"PRIx64" NOREF: receiverId not known\n", id); } return NULL; } double latDiff = r->latMax - r->latMin; double lonDiff = r->lonMax - r->lonMin; *lat = r->latMin + latDiff / 2; *lon = r->lonMin + lonDiff / 2; uint32_t positionCounterRequired = (Modes.viewadsb || Modes.receiver_focus) ? 4 : 100; if (r->positionCounter < positionCounterRequired || r->badExtent) { if (!noDebug) { fprintf(stderr, "id:%016"PRIx64" NOREF: #posCounter:%9"PRIu64" refLoc: %4.0f,%4.0f lat: %4.0f to %4.0f lon: %4.0f to %4.0f\n", r->id, r->positionCounter, *lat, *lon, r->latMin, r->latMax, r->lonMin, r->lonMax); } return NULL; } if (!noDebug) { fprintf(stderr, "id:%016"PRIx64" #posCounter:%9"PRIu64" refLoc: %4.0f,%4.0f lat: %4.0f to %4.0f lon: %4.0f to %4.0f\n", r->id, r->positionCounter, *lat, *lon, r->latMin, r->latMax, r->lonMin, r->lonMax); } return r; } void receiverTest() { int64_t now = mstime(); for (uint64_t i = 0; i < (1<<22); i++) { uint64_t id = i << 22; receiver *r = receiverGet(id); if (!r) r = receiverCreate(id); if (r) r->lastSeen = now; } printf("%"PRIu64"\n", Modes.receiverCount); for (int i = 0; i < (1<<22); i++) { receiver *r = receiverGet(i); if (!r) r = receiverCreate(i); } printf("%"PRIu64"\n", Modes.receiverCount); receiverTimeout(0, 1, mstime()); printf("%"PRIu64"\n", Modes.receiverCount); } int receiverCheckBad(uint64_t id, int64_t now) { struct receiver *r = receiverGet(id); if (r && now < r->timedOutUntil) return 1; else return 0; } struct receiver *receiverBad(uint64_t id, uint32_t addr, int64_t now) { if (!Modes.receiverTable) { return NULL; } struct receiver *r = receiverGet(id); if (!r) r = receiverCreate(id); int64_t timeout = 12 * SECONDS; if (r && now + (timeout * 2 / 3) > r->timedOutUntil) { r->lastSeen = now; r->badCounter++; if (r->badCounter > 5.99) { r->timedOutCounter++; if (Modes.debug_garbage) { char uuid[32]; // needs 18 chars and null byte sprint_uuid1(r->id, uuid); fprintf(stderr, "timeout receiverId: %s hex: %06x #good: %6d #bad: %5.0f #timeouts: %u\n", uuid, addr, r->goodCounter, r->badCounter, r->timedOutCounter); } r->timedOutUntil = now + timeout; r->goodCounter = 0; r->badCounter = 0; } return r; } else { return NULL; } } struct char_buffer generateReceiversJson() { struct char_buffer cb; int64_t now = mstime(); size_t buflen = 1*1024*1024; // The initial buffer is resized as needed char *buf = (char *) cmalloc(buflen), *p = buf, *end = buf + buflen; p = safe_snprintf(p, end, "{ \"now\" : %.1f,\n", now / 1000.0); //p = safe_snprintf(p, end, " \"columns\" : [ \"receiverId\", \"\"],\n"); p = safe_snprintf(p, end, " \"receivers\" : [\n"); struct receiver *r; if (Modes.receiverTable) { for (int j = 0; j < Modes.receiver_table_size; j++) { for (r = Modes.receiverTable[j]; r; r = r->next) { // check if we have enough space if ((p + 1000) >= end) { int used = p - buf; buflen *= 2; buf = (char *) realloc(buf, buflen); p = buf + used; end = buf + buflen; } char uuid[64]; sprint_uuid1(r->id, uuid); double elapsed = (r->lastSeen - r->firstSeen) / 1000.0 + 1.0; p = safe_snprintf(p, end, " [ \"%s\", %6.2f, %6.2f, %6.2f, %6.2f, %7.2f, %7.2f, %d, %0.2f,%0.2f ],\n", uuid, r->positionCounter / elapsed, r->timedOutCounter * 3600.0 / elapsed, r->latMin, r->latMax, r->lonMin, r->lonMax, r->badExtent ? 1 : 0, r->latMin + (r->latMax - r->latMin) / 2.0, r->lonMin + (r->lonMax - r->lonMin) / 2.0); if (p >= end) fprintf(stderr, "buffer overrun client json\n"); } } } if (*(p-2) == ',') *(p-2) = ' '; p = safe_snprintf(p, end, " ]\n}\n"); cb.len = p - buf; cb.buffer = buf; return cb; } readsb-3.16/receiver.h000066400000000000000000000030721505057307600146720ustar00rootroot00000000000000#ifndef RECEIVER_H #define RECEIVER_H #define RECEIVER_BAD_AIRCRAFT (2) #define RECEIVER_RANGE_GOOD (7) #define RECEIVER_RANGE_BAD (-7) #define RECEIVER_RANGE_UNCLEAR (-1) #define RECEIVER_MAINTENANCE_INTERVAL (5 * MINUTES) struct bad_ac { uint32_t addr; int64_t ts; }; typedef struct receiver { uint64_t id; struct receiver *next; int64_t firstSeen; int64_t lastSeen; uint64_t positionCounter; double latMin; double latMax; double lonMin; double lonMax; int64_t badExtent; // timestamp of first lat/lon (max-min) > MAX_DIFF (receiver.c) struct bad_ac badAircraft[RECEIVER_BAD_AIRCRAFT]; float badCounter; // plus one for a bad position, -0.5 for a good position int32_t goodCounter; // plus one for a good position // reset both counters on timing out a receiver. int64_t timedOutUntil; uint32_t timedOutCounter; // how many times a receiver has been timed out } receiver; uint32_t receiverHash(uint64_t id); struct receiver *receiverGet(uint64_t id); struct receiver *receiverCreate(uint64_t id); struct char_buffer generateReceiversJson(); int receiverPositionReceived(struct aircraft *a, struct modesMessage *mm, double lat, double lon, int64_t now); void receiverTimeout(int part, int nParts, int64_t now); void receiverInit(); void receiverCleanup(); void receiverTest(); struct receiver *receiverGetReference(uint64_t id, double *lat, double *lon, struct aircraft *a, int noDebug); int receiverCheckBad(uint64_t id, int64_t now); struct receiver *receiverBad(uint64_t id, uint32_t addr, int64_t now); #endif readsb-3.16/replicate_state.sh000077500000000000000000000042351505057307600164260ustar00rootroot00000000000000#!/bin/bash set -e # ssh access required for both the source and target box # this script will transfer the traces for last 24h and current aircraft positions # it reads the state from the source and will replace the target state except for aircraft that exist on the target but not on the source # source SHOST=root@box1 SDIR=/run/readsb # target THOST=root@box2 TDIR=/run/readsb echo "$(date -u --rfc-3339=s) starting transfer from $SHOST to $THOST, 10 seconds to cancel with Ctrl-C" sleep 10 SSHDIR="$HOME/.vee0za6ugohru6Id0ziK3ahv1ietahva" rm -rf "$SSHDIR" mkdir -p "$SSHDIR" chmod 700 "$SSHDIR" SSHPERSIST="-o ControlMaster=auto -o ControlPersist=30s" SSHCOMMON="-o StrictHostKeyChecking=no" P1="-S $SSHDIR/1cm-%r@%h:%p" P2="-S $SSHDIR/2cm-%r@%h:%p" SCMD="ssh $SHOST $SSHCOMMON $SSHPERSIST $P1" TCMD="ssh $THOST $SSHCOMMON $SSHPERSIST $P2" if ! $SCMD "cd $SDIR/getState" || ! $TCMD "cd $TDIR/getState"; then echo "---------------------------------------------------------" echo "ERROR: readsb version too old, use replicate_state_old.sh or update readsb to version >= v3.14.1618" echo "---------------------------------------------------------" exit 1 fi GDIR="$SDIR/getState" RDIR="$TDIR/replaceState" TTDIR="$TDIR/replaceTmp" $TCMD "mkdir -p $TTDIR && mkdir -p $RDIR && chmod a+w $RDIR" suffix="zstl" for num in $(seq 0 255); do blob="$(printf "%02x\n" "$num")" TRIGGER="$GDIR/writeState" BLOB="blob_${blob}.${suffix}" $SCMD "echo $blob > $TRIGGER; while [[ -f $TRIGGER ]]; do sleep 0.01; done;" $SCMD "tar -C $GDIR -c -f - $BLOB && rm -f $GDIR/$BLOB" | $TCMD "tar -C $TTDIR --overwrite -x -f - && chmod a+w $TTDIR/$BLOB && mv -f $TTDIR/$BLOB $RDIR/$BLOB;" echo "$(date -u --rfc-3339=s) transferring $BLOB" done wait # wait for last transfer echo "$(date -u --rfc-3339=s) transfer done, waiting for completion of state load on the target side" $TCMD "while ls $RDIR | grep -qs -v -e tmp; do sleep 1; done" $TCMD "if ls $RDIR | grep -qs ${suffix}; then echo transfer or state loading incomplete, check target readsb log; else echo $(date -u --rfc-3339=s) state loading completed on target; fi" $TCMD "rm -rf $RDIR $TTDIR" rm -rf "$SSHDIR" readsb-3.16/replicate_state_old.sh000077500000000000000000000035531505057307600172660ustar00rootroot00000000000000#!/bin/bash set -e # ssh access required for both the source and target box # this script will transfer the traces for last 24h and current aircraft positions # it reads the state from the source and will replace the target state except for aircraft that exist on the target but not on the source # source SHOST=box1 SDIR=/var/globe_history/internal_state # target THOST=box2 TDIR=/var/globe_history/internal_state echo "$(date -u --rfc-3339=s) starting transfer from $SHOST to $THOST" sleep 10 SSHDIR="$HOME/.vee0za6ugohru6Id0ziK3ahv1ietahva" rm -rf "$SSHDIR" mkdir -p "$SSHDIR" chmod 700 "$SSHDIR" SSHPERSIST="-o ControlMaster=auto -o ControlPersist=30s" SSHCOMMON="-o StrictHostKeyChecking=no" P1="-S $SSHDIR/1cm-%r@%h:%p" P2="-S $SSHDIR/2cm-%r@%h:%p" SCMD="ssh $SHOST $SSHCOMMON $SSHPERSIST $P1" TCMD="ssh $THOST $SSHCOMMON $SSHPERSIST $P2" RDIR="$TDIR/replaceState" TTDIR="$TDIR/tmp" $TCMD "mkdir -p $TTDIR; mkdir -p $RDIR; chmod a+w $RDIR" suffix="zstl" for num in $(seq 0 255); do blob="$(printf "%02x\n" "$num")" TRIGGER="$SDIR/writeState" BLOB="blob_${blob}.${suffix}" $SCMD "echo $blob > $TRIGGER; while [[ -f $TRIGGER ]]; do sleep 0.01; done;" wait # wait for previous transfer to finish before starting new transfer $SCMD "tar -C $SDIR -c -f - $BLOB" | $TCMD "tar -C $TTDIR --overwrite -x -f - && chmod a+w $TTDIR/$BLOB && mv -f $TTDIR/$BLOB $RDIR/$BLOB;" & echo "$(date -u --rfc-3339=s) transferring $BLOB" done wait # wait for last transfer echo "$(date -u --rfc-3339=s) transfer done, waiting for completion of state load on the target side" $TCMD "while ls $RDIR | grep -qs -v -e tmp; do sleep 1; done" $TCMD "if ls $RDIR | grep -qs ${suffix}; then echo transfer or state loading incomplete, check target readsb log; else echo $(date -u --rfc-3339=s) state loading completed on target; fi" $TCMD "rm -rf $RDIR $TTDIR" rm -rf "$SSHDIR" readsb-3.16/required_packages000066400000000000000000000001761505057307600163200ustar00rootroot00000000000000gcc make libc6-dev libusb-1.0-0-dev librtlsdr-dev librtlsdr0 libncurses-dev zlib1g zlib1g-dev libzstd1 libzstd-dev pkg-config readsb-3.16/sdr.c000066400000000000000000000147371505057307600136630ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr.c: generic SDR infrastructure // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // // This file 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 // any later version. // // This file 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 . #include "readsb.h" #include "sdr_ifile.h" #ifdef ENABLE_RTLSDR #include "sdr_rtlsdr.h" #endif #ifdef ENABLE_BLADERF #include "sdr_bladerf.h" #include "sdr_ubladerf.h" #endif #ifdef ENABLE_HACKRF #include "sdr_hackrf.h" #endif #ifdef ENABLE_PLUTOSDR #include "sdr_plutosdr.h" #endif #ifdef ENABLE_SOAPYSDR #include "sdr_soapy.h" #endif #include "sdr_beast.h" #define SDR_TIMEOUT 5000 // timeout for sdr open / cancel / close calls in milliseconds typedef struct { void (*initConfig)(); bool(*handleOption)(int, char*); bool(*open)(); void (*run)(); void (*cancel)(); void (*close)(); const char *name; sdr_type_t sdr_type; pthread_t manageThread; void (*setGain)(char *reason); } sdr_handler; static void noInitConfig() { } static bool noHandleOption(int key, char *arg) { MODES_NOTUSED(key); MODES_NOTUSED(arg); return false; } static bool noOpen() { fprintf(stderr, "No SDR device or file selected.\n"); return true; } static void noRun() { } static void noCancel() { } static void noClose() { } static void noSetGain() { } static bool unsupportedOpen() { fprintf(stderr, "Support for this SDR type was not enabled in this build.\n"); return false; } static sdr_handler sdr_handlers[] = { #ifdef ENABLE_RTLSDR { rtlsdrInitConfig, rtlsdrHandleOption, rtlsdrOpen, rtlsdrRun, rtlsdrCancel, rtlsdrClose, "rtlsdr", SDR_RTLSDR, 0, rtlsdrSetGain}, #endif #ifdef ENABLE_BLADERF { bladeRFInitConfig, bladeRFHandleOption, bladeRFOpen, bladeRFRun, noCancel, bladeRFClose, "bladerf", SDR_BLADERF, 0, noSetGain}, { ubladeRFInitConfig, ubladeRFHandleOption, ubladeRFOpen, ubladeRFRun, noCancel, ubladeRFClose, "ubladerf", SDR_MICROBLADERF, 0, noSetGain}, #endif #ifdef ENABLE_HACKRF { hackRFInitConfig, hackRFHandleOption, hackRFOpen, hackRFRun, noCancel, hackRFClose, "hackrf", SDR_HACKRF, 0, noSetGain}, #endif #ifdef ENABLE_PLUTOSDR { plutosdrInitConfig, plutosdrHandleOption, plutosdrOpen, plutosdrRun, noCancel, plutosdrClose, "plutosdr", SDR_PLUTOSDR, 0, noSetGain}, #endif #ifdef ENABLE_SOAPYSDR { soapyInitConfig, soapyHandleOption, soapyOpen, soapyRun, noCancel, soapyClose, "soapysdr", SDR_SOAPYSDR, 0 , noSetGain}, #endif { beastInitConfig, beastHandleOption, beastOpen, noRun, noCancel, noClose, "modesbeast", SDR_MODESBEAST, 0, noSetGain}, { beastInitConfig, beastHandleOption, beastOpen, noRun, noCancel, noClose, "gnshulc", SDR_GNS, 0, noSetGain}, { ifileInitConfig, ifileHandleOption, ifileOpen, ifileRun, noCancel, ifileClose, "ifile", SDR_IFILE, 0, noSetGain}, { noInitConfig, noHandleOption, noOpen, noRun, noCancel, noClose, "none", SDR_NONE, 0, noSetGain}, { NULL, NULL, NULL, NULL, NULL, NULL, NULL, SDR_NONE, 0, NULL} /* must come last */ }; void sdrInitConfig() { // Default SDR is the first type available in the handlers array. // rather don't have a default SDR .... // Modes.sdr_type = sdr_handlers[0].sdr_type; for (int i = 0; sdr_handlers[i].name; ++i) { sdr_handlers[i].initConfig(); } } bool sdrHandleOption(int key, char *arg) { switch (key) { case OptDeviceType: for (int i = 0; sdr_handlers[i].name; ++i) { if (!strcasecmp(sdr_handlers[i].name, arg)) { Modes.sdr_type = sdr_handlers[i].sdr_type; return true; } } break; default: for (int i = 0; sdr_handlers[i].sdr_type; ++i) { if (Modes.sdr_type == sdr_handlers[i].sdr_type) { return sdr_handlers[i].handleOption(key, arg); } } } fprintf(stderr, "SDR type '%s' not recognized; supported SDR types are:\n", arg); for (int i = 0; sdr_handlers[i].name; ++i) { fprintf(stderr, " %s\n", sdr_handlers[i].name); } return false; } static sdr_handler *current_handler() { static sdr_handler unsupported_handler = {noInitConfig, noHandleOption, unsupportedOpen, noRun, noCancel, noClose, "unsupported", SDR_NONE, 0, noSetGain}; for (int i = 0; sdr_handlers[i].name; ++i) { if (Modes.sdr_type == sdr_handlers[i].sdr_type) { return &sdr_handlers[i]; } } return &unsupported_handler; } // avoid synchronous calls for all SDR handlers // in particular rtlsdr_cancel_async() and rtlsdr_close() are suspect // of never returning in some cases // bool sdrOpen() { pthread_mutex_lock(&Modes.sdrControlMutex); bool success = current_handler()->open(); if (success) { Modes.sdrInitialized = 1; } pthread_mutex_unlock(&Modes.sdrControlMutex); return success; } bool sdrHasRun() { return (current_handler()->run != noRun); } void sdrRun() { // Create the thread that will read the data from the device. current_handler()->run(); } void sdrCancel() { pthread_mutex_lock(&Modes.sdrControlMutex); Modes.sdrInitialized = 0; current_handler()->cancel(); pthread_mutex_unlock(&Modes.sdrControlMutex); } void sdrClose() { pthread_mutex_lock(&Modes.sdrControlMutex); Modes.sdrInitialized = 0; current_handler()->close(); pthread_mutex_unlock(&Modes.sdrControlMutex); } void sdrSetGain(char *reason){ pthread_mutex_lock(&Modes.sdrControlMutex); if (Modes.sdrInitialized) { current_handler()->setGain(reason); } pthread_mutex_unlock(&Modes.sdrControlMutex); } void lockReader() { pthread_mutex_lock(&Threads.reader.mutex); } void unlockReader() { pthread_mutex_unlock(&Threads.reader.mutex); } void wakeDecode() { pthread_cond_signal(&Threads.decode.cond); } readsb-3.16/sdr.h000066400000000000000000000024131505057307600136540ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr.h: generic SDR infrastructure (header) // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // // This file 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 // any later version. // // This file 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 . #ifndef SDR_H #define SDR_H // Common interface to different SDR inputs. void sdrInitConfig (); bool sdrHandleOption (int argc, char *argv); bool sdrOpen (); void sdrRun (); bool sdrHasRun(); void sdrCancel (); void sdrClose (); void sdrSetGain (char *reason); void lockReader(); void unlockReader(); void wakeDecode(); #endif readsb-3.16/sdr_beast.c000066400000000000000000000176621505057307600150410ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_beast.c: Mode-S Beast and GNS5894 support // // Copyright (c) 2019 Michael Wolf // // This file 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 // any later version. // // This file 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 . #include #include "readsb.h" #include "sdr_beast.h" static struct { bool filter_df045; bool filter_df1117; bool mode_ac; bool mlat_timestamp; bool fec; bool crc; uint16_t padding; } BeastSettings; static void beastSetOption(char opt) { char optionsmsg[3] = { 0x1a, '1', opt }; if (write(Modes.beast_fd, optionsmsg, 3) < 3) { fprintf(stderr, "Beast failed to set option: %s", strerror(errno)); } } void beastInitConfig(void) { free(Modes.beast_serial); Modes.beast_serial = strdup("/dev/ttyUSB0"); BeastSettings.filter_df045 = false; BeastSettings.filter_df1117 = false; BeastSettings.mode_ac = false; Modes.mode_ac = 0; BeastSettings.mlat_timestamp = true; BeastSettings.fec = true; BeastSettings.crc = true; } static speed_t get_baud(int baud) { switch (baud) { case 9600: return B9600; case 19200: return B19200; case 38400: return B38400; case 57600: return B57600; case 115200: return B115200; case 230400: return B230400; case 460800: return B460800; case 500000: return B500000; case 576000: return B576000; case 921600: return B921600; case 921800: // close enough return B921600; case 1000000: return B1000000; case 1152000: return B1152000; case 1500000: return B1500000; case 2000000: return B2000000; case 2500000: return B2500000; case 3000000: return B3000000; case 3500000: return B3500000; case 4000000: return B4000000; default: return 0; } } bool beastHandleOption(int key, char *arg) { switch(key){ case OptBeastSerial: free(Modes.beast_serial); Modes.beast_serial = strdup(arg); break; case OptBeastBaudrate: Modes.beast_baudrate = (int) strtoll(arg, NULL, 10); break; case OptBeastDF1117: BeastSettings.filter_df1117 = true; break; case OptBeastDF045: BeastSettings.filter_df045 = true; break; case OptBeastMlatTimeOff: BeastSettings.mlat_timestamp = false; break; case OptBeastCrcOff: BeastSettings.crc = false; break; case OptBeastFecOff: BeastSettings.fec = false; break; case OptBeastModeAc: BeastSettings.mode_ac = true; Modes.mode_ac = 1; Modes.mode_ac_auto = 0; break; default: return false; } return true; } bool beastOpen(void) { struct termios tios; speed_t baud = B3000000; int flags = O_RDWR | O_NOCTTY; //flags |= O_NONBLOCK; Modes.beast_fd = open(Modes.beast_serial, flags); if (Modes.beast_fd < 0) { fprintf(stderr, "Failed to open serial device %s: %s\n", Modes.beast_serial, strerror(errno)); fprintf(stderr, "In case of permission denied try: sudo chmod a+rw %s\nor permanent permission: sudo adduser readsb dialout\n", Modes.beast_serial); return false; } if (tcgetattr(Modes.beast_fd, &tios) < 0) { fprintf(stderr, "tcgetattr(%s): %s\n", Modes.beast_serial, strerror(errno)); return false; } tios.c_iflag = IGNPAR; tios.c_oflag = 0; tios.c_lflag = 0; tios.c_cflag = CS8 | CRTSCTS; //tios.c_cc[VMIN] = 11; // read returns when a minimum of 11 characters are available tios.c_cc[VMIN] = 0; // polling read with vtime 0 tios.c_cc[VTIME] = 0; if (Modes.sdr_type == SDR_GNS) { baud = B921600; } if (Modes.beast_baudrate) { baud = get_baud(Modes.beast_baudrate); if (!baud) { fprintf(stderr, "invalid baudrate: %d\n", Modes.beast_baudrate); return false; } } #ifdef __APPLE__ if (cfsetspeed(&tios, baud) < 0) { /* If standard rate setting fails, try IOKit's special baudrate setting */ if (ioctl(Modes.beast_fd, IOSSIOSPEED, &baud) < 0) { fprintf(stderr, "Beast set speed(%s, %lu): %s\n", Modes.beast_serial, (unsigned long)baud, strerror(errno)); return false; } } #else if (cfsetispeed(&tios, baud) < 0 || cfsetospeed(&tios, baud) < 0) { fprintf(stderr, "Beast set speed(%s, %lu): %s\n", Modes.beast_serial, (unsigned long)baud, strerror(errno)); return false; } #endif tcflush(Modes.beast_fd, TCIFLUSH); if (tcsetattr(Modes.beast_fd, TCSANOW, &tios) < 0) { fprintf(stderr, "Beast tcsetattr(%s): %s\n", Modes.beast_serial, strerror(errno)); return false; } if (Modes.sdr_type == SDR_MODESBEAST) { /* set options */ beastSetOption('B'); /* set classic beast mode */ beastSetOption('C'); /* use binary format */ beastSetOption('H'); /* RTS enabled */ if (BeastSettings.filter_df1117) beastSetOption('D'); /* enable DF11/17-only filter*/ else beastSetOption('d'); /* disable DF11/17-only filter, deliver all messages */ if (BeastSettings.mlat_timestamp) beastSetOption('E'); /* enable mlat timestamps */ else beastSetOption('e'); /* disable mlat timestamps */ if (BeastSettings.crc) beastSetOption('f'); /* enable CRC checks */ else beastSetOption('F'); /* disable CRC checks */ if (BeastSettings.filter_df045) beastSetOption('G'); /* enable DF0/4/5 filter */ else beastSetOption('g'); /* disable DF0/4/5 filter, deliver all messages */ if (Modes.nfix_crc || BeastSettings.fec) beastSetOption('i'); /* FEC enabled */ else beastSetOption('I'); /* FEC disbled */ if (Modes.mode_ac || BeastSettings.mode_ac) beastSetOption('J'); /* Mode A/C enabled */ else beastSetOption('j'); /* Mode A/C disabled */ } // Request firmware message from GNS HULC if (Modes.sdr_type == SDR_GNS) { char optionsmsg[4] = {'#', '0', '0', '\r'}; if (write(Modes.beast_fd, optionsmsg, 4) < 4) { fprintf(stderr, "GNS HULC request firmware failed: %s\n", strerror(errno)); } } /* Kick on handshake and start reception */ int RTSDTR_flag = TIOCM_RTS | TIOCM_DTR; int res = ioctl(Modes.beast_fd, TIOCMBIS, &RTSDTR_flag); //Set RTS&DTR pin if (res == -1) { fprintf(stderr, "Serial device, reception start failed: %s\n", strerror(errno)); return false; } if (Modes.sdr_type == SDR_MODESBEAST) { fprintf(stderr, "Running Mode-S Beast via serial (over USB).\n"); } else { fprintf(stderr, "Running GNS HULC via serial (over USB).\n"); } return true; } void beastRun() { } void beastClose() { /* Beast device will be closed in the main cleanup_and_exit function when * clients are freed. */ } readsb-3.16/sdr_beast.h000066400000000000000000000017441505057307600150400ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_beast.h: Mode-S Beast and GNS5894 support (header) // // Copyright (c) 2019 Michael Wolf // // This file 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 // any later version. // // This file 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 . #ifndef SDR_BEAST_H #define SDR_BEAST_H void beastInitConfig(); bool beastHandleOption(int argc, char *argv); bool beastOpen(); void beastRun(); void beastClose(); #endif /* SDR_BEAST_H */ readsb-3.16/sdr_bladerf.c000066400000000000000000000424301505057307600153310ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_bladerf.c: bladeRF support // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2017 FlightAware LLC // // This file 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 // any later version. // // This file 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 . #include "readsb.h" #include "sdr_bladerf.h" #include #include static struct { const char *device_str; const char *fpga_path; unsigned decimation; bladerf_lpf_mode lpf_mode; unsigned lpf_bandwidth; unsigned block_size; struct bladerf *device; iq_convert_fn converter; struct converter_state *converter_state; } BladeRF; void bladeRFInitConfig() { BladeRF.device_str = NULL; BladeRF.fpga_path = NULL; BladeRF.decimation = 1; BladeRF.lpf_mode = BLADERF_LPF_NORMAL; BladeRF.lpf_bandwidth = 1750000; BladeRF.device = NULL; } bool bladeRFHandleOption(int key, char *arg) { switch (key) { case OptBladeFpgaDir: BladeRF.fpga_path = strdup(arg); break; case OptBladeDecim: BladeRF.decimation = atoi(arg); break; case OptBladeBw: if (!strcasecmp(arg, "bypass")) { BladeRF.lpf_mode = BLADERF_LPF_BYPASSED; } else { BladeRF.lpf_mode = BLADERF_LPF_NORMAL; BladeRF.lpf_bandwidth = atoi(arg); } break; default: return false; } return true; } static int lna_gain_db(bladerf_lna_gain gain) { switch (gain) { case BLADERF_LNA_GAIN_BYPASS: return 0; case BLADERF_LNA_GAIN_MID: return BLADERF_LNA_GAIN_MID_DB; case BLADERF_LNA_GAIN_MAX: return BLADERF_LNA_GAIN_MAX_DB; default: return -1; } } static void show_config() { int status; unsigned rate; #if defined(LIBBLADERF_API_VERSION) && (LIBBLADERF_API_VERSION >= 0x02020000) bladerf_frequency freq; #else unsigned freq; #endif bladerf_lpf_mode lpf_mode; unsigned lpf_bw; bladerf_lna_gain lna_gain; int rxvga1_gain; int rxvga2_gain; int16_t lms_dc_i, lms_dc_q; int16_t fpga_phase, fpga_gain; struct bladerf_lms_dc_cals dc_cals; if ((status = bladerf_get_sample_rate(BladeRF.device, BLADERF_MODULE_RX, &rate)) < 0 || (status = bladerf_get_frequency(BladeRF.device, BLADERF_MODULE_RX, &freq)) < 0 || (status = bladerf_get_lpf_mode(BladeRF.device, BLADERF_MODULE_RX, &lpf_mode)) < 0 || (status = bladerf_get_bandwidth(BladeRF.device, BLADERF_MODULE_RX, &lpf_bw)) < 0 || (status = bladerf_get_lna_gain(BladeRF.device, &lna_gain)) < 0 || (status = bladerf_get_rxvga1(BladeRF.device, &rxvga1_gain)) < 0 || (status = bladerf_get_rxvga2(BladeRF.device, &rxvga2_gain)) < 0 || (status = bladerf_get_correction(BladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_LMS_DCOFF_I, &lms_dc_i)) < 0 || (status = bladerf_get_correction(BladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_LMS_DCOFF_Q, &lms_dc_q)) < 0 || (status = bladerf_get_correction(BladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_FPGA_PHASE, &fpga_phase)) < 0 || (status = bladerf_get_correction(BladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_FPGA_GAIN, &fpga_gain)) < 0 || (status = bladerf_lms_get_dc_cals(BladeRF.device, &dc_cals)) < 0) { fprintf(stderr, "bladeRF: couldn't read back device configuration\n"); return; } fprintf(stderr, "bladeRF: sampling rate: %.1f MHz\n", rate / 1e6); fprintf(stderr, "bladeRF: frequency: %.1f MHz\n", freq / 1e6); fprintf(stderr, "bladeRF: LNA gain: %ddB\n", lna_gain_db(lna_gain)); fprintf(stderr, "bladeRF: RXVGA1 gain: %ddB\n", rxvga1_gain); fprintf(stderr, "bladeRF: RXVGA2 gain: %ddB\n", rxvga2_gain); switch (lpf_mode) { case BLADERF_LPF_NORMAL: fprintf(stderr, "bladeRF: LPF bandwidth: %.2f MHz\n", lpf_bw / 1e6); break; case BLADERF_LPF_BYPASSED: fprintf(stderr, "bladeRF: LPF bypassed\n"); break; case BLADERF_LPF_DISABLED: fprintf(stderr, "bladeRF: LPF disabled\n"); break; default: fprintf(stderr, "bladeRF: LPF in unknown state\n"); break; } fprintf(stderr, "bladeRF: calibration settings:\n"); fprintf(stderr, " LMS DC adjust: I=%d Q=%d\n", lms_dc_i, lms_dc_q); fprintf(stderr, " FPGA phase adjust: %+.3f degrees\n", fpga_phase * 10.0 / 4096); fprintf(stderr, " FPGA gain adjust: %+.3f\n", fpga_gain * 1.0 / 4096); fprintf(stderr, " LMS LPF tuning: %d\n", dc_cals.lpf_tuning); fprintf(stderr, " LMS RX LPF filter: I=%d Q=%d\n", dc_cals.rx_lpf_i, dc_cals.rx_lpf_q); fprintf(stderr, " LMS RXVGA2 DC ref: %d\n", dc_cals.dc_ref); fprintf(stderr, " LMS RXVGA2A: I=%d Q=%d\n", dc_cals.rxvga2a_i, dc_cals.rxvga2a_q); fprintf(stderr, " LMS RXVGA2B: I=%d Q=%d\n", dc_cals.rxvga2b_i, dc_cals.rxvga2b_q); } bool bladeRFOpen() { if (BladeRF.device) { return true; } int status; bladerf_set_usb_reset_on_open(true); if ((status = bladerf_open(&BladeRF.device, Modes.dev_name)) < 0) { fprintf(stderr, "Failed to open bladeRF: %s\n", bladerf_strerror(status)); goto error; } const char *fpga_path; if (BladeRF.fpga_path) { fpga_path = BladeRF.fpga_path; } else { bladerf_fpga_size size; if ((status = bladerf_get_fpga_size(BladeRF.device, &size)) < 0) { fprintf(stderr, "bladerf_get_fpga_size failed: %s\n", bladerf_strerror(status)); goto error; } switch (size) { case BLADERF_FPGA_40KLE: fpga_path = "/usr/share/Nuand/bladeRF/hostedx40.rbf"; break; case BLADERF_FPGA_115KLE: fpga_path = "/usr/share/Nuand/bladeRF/hostedx115.rbf"; break; default: fprintf(stderr, "bladeRF: unknown FPGA size, skipping FPGA load"); fpga_path = NULL; break; } } if (fpga_path && fpga_path[0]) { fprintf(stderr, "bladeRF: loading FPGA bitstream from %s\n", fpga_path); if ((status = bladerf_load_fpga(BladeRF.device, fpga_path)) < 0) { fprintf(stderr, "bladerf_load_fpga() failed: %s\n", bladerf_strerror(status)); goto error; } } switch (bladerf_device_speed(BladeRF.device)) { case BLADERF_DEVICE_SPEED_HIGH: BladeRF.block_size = 1024; break; case BLADERF_DEVICE_SPEED_SUPER: BladeRF.block_size = 2048; break; default: fprintf(stderr, "couldn't determine bladerf device speed\n"); goto error; } if ((status = bladerf_set_sample_rate(BladeRF.device, BLADERF_MODULE_RX, Modes.sample_rate * BladeRF.decimation, NULL)) < 0) { fprintf(stderr, "bladerf_set_sample_rate failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_set_frequency(BladeRF.device, BLADERF_MODULE_RX, Modes.freq)) < 0) { fprintf(stderr, "bladerf_set_frequency failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_set_lpf_mode(BladeRF.device, BLADERF_MODULE_RX, BladeRF.lpf_mode)) < 0) { fprintf(stderr, "bladerf_set_lpf_mode failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_set_bandwidth(BladeRF.device, BLADERF_MODULE_RX, BladeRF.lpf_bandwidth, NULL)) < 0) { fprintf(stderr, "bladerf_set_lpf_bandwidth failed: %s\n", bladerf_strerror(status)); goto error; } /* turn the tx gain right off, just in case */ if ((status = bladerf_set_gain(BladeRF.device, BLADERF_MODULE_TX, -100)) < 0) { fprintf(stderr, "bladerf_set_gain(TX) failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_set_gain(BladeRF.device, BLADERF_MODULE_RX, Modes.gain / 10.0)) < 0) { fprintf(stderr, "bladerf_set_gain(RX) failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_set_loopback(BladeRF.device, BLADERF_LB_NONE)) < 0) { fprintf(stderr, "bladerf_set_loopback() failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_calibrate_dc(BladeRF.device, BLADERF_DC_CAL_LPF_TUNING)) < 0) { fprintf(stderr, "bladerf_calibrate_dc(LPF_TUNING) failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_calibrate_dc(BladeRF.device, BLADERF_DC_CAL_RX_LPF)) < 0) { fprintf(stderr, "bladerf_calibrate_dc(RX_LPF) failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_calibrate_dc(BladeRF.device, BLADERF_DC_CAL_RXVGA2)) < 0) { fprintf(stderr, "bladerf_calibrate_dc(RXVGA2) failed: %s\n", bladerf_strerror(status)); goto error; } show_config(); BladeRF.converter = init_converter(INPUT_SC16Q11, Modes.sample_rate, Modes.dc_filter, &BladeRF.converter_state); if (!BladeRF.converter) { fprintf(stderr, "can't initialize sample converter\n"); goto error; } return true; error: if (BladeRF.device) { bladerf_close(BladeRF.device); BladeRF.device = NULL; } return false; } static struct timespec thread_cpu; static unsigned timeouts = 0; static void *handle_bladerf_samples(struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta, void *samples, size_t num_samples, void *user_data) { static uint64_t nextTimestamp = 0; static bool dropping = false; int64_t sysMicroseconds = mono_micro_seconds(); int64_t sysTimestamp = mstime(); MODES_NOTUSED(dev); MODES_NOTUSED(stream); MODES_NOTUSED(meta); MODES_NOTUSED(user_data); MODES_NOTUSED(num_samples); lockReader(); if (Modes.exit) { unlockReader(); return BLADERF_STREAM_SHUTDOWN; } unsigned next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS; struct mag_buf *outbuf = &Modes.mag_buffers[Modes.first_free_buffer]; struct mag_buf *lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS]; unsigned free_bufs = (Modes.first_filled_buffer - next_free_buffer + MODES_MAG_BUFFERS) % MODES_MAG_BUFFERS; unlockReader(); if (free_bufs == 0 || (dropping && free_bufs < MODES_MAG_BUFFERS / 2)) { // FIFO is full. Drop this block. dropping = true; return samples; } dropping = false; // Copy trailing data from last block (or reset if not valid) if (outbuf->dropped == 0) { memcpy(outbuf->data, lastbuf->data + lastbuf->length, Modes.trailing_samples * sizeof (uint16_t)); } else { memset(outbuf->data, 0, Modes.trailing_samples * sizeof (uint16_t)); } // start handling metadata blocks outbuf->dropped = 0; outbuf->length = 0; outbuf->mean_level = outbuf->mean_power = 0; outbuf->sysTimestamp = sysTimestamp; outbuf->sysMicroseconds = sysMicroseconds; unsigned blocks_processed = 0; unsigned samples_per_block = (BladeRF.block_size - 16) / 4; static bool overrun = true; // ignore initial overruns as we get up to speed static bool first_buffer = true; for (unsigned offset = 0; offset < Modes.sdr_buf_samples * 4; offset += BladeRF.block_size) { // read the next metadata header uint8_t *header = ((uint8_t*) samples) + offset; uint64_t metadata_magic = le32toh(*(uint32_t*) (header)); uint64_t metadata_timestamp = le64toh(*(uint64_t*) (header + 4)); uint32_t metadata_flags = le32toh(*(uint32_t*) (header + 12)); void *sample_data = header + 16; if (metadata_magic != 0x12344321) { // first buffer is often in the wrong mode if (!first_buffer) { fprintf(stderr, "bladeRF: wrong metadata header magic value, skipping rest of buffer\n"); } break; } if (metadata_flags & BLADERF_META_STATUS_OVERRUN) { if (!overrun) { fprintf(stderr, "bladeRF: receive overrun\n"); } overrun = true; } else { overrun = false; } #ifndef BROKEN_FPGA_METADATA // this needs a fixed decimating FPGA image that handles the timestamp correctly if (nextTimestamp && nextTimestamp != metadata_timestamp) { // dropped data or lost sync. start again. if (metadata_timestamp > nextTimestamp) outbuf->dropped += (metadata_timestamp - nextTimestamp); outbuf->dropped += outbuf->length; outbuf->length = 0; blocks_processed = 0; outbuf->mean_level = outbuf->mean_power = 0; nextTimestamp = metadata_timestamp; } #else MODES_NOTUSED(metadata_timestamp); #endif if (!blocks_processed) { // Compute the sample timestamp for the start of the block outbuf->sampleTimestamp = nextTimestamp * 12e6 / Modes.sample_rate / BladeRF.decimation; } // Convert a block of data double mean_level, mean_power; BladeRF.converter(sample_data, &outbuf->data[Modes.trailing_samples + outbuf->length], samples_per_block, BladeRF.converter_state, &mean_level, &mean_power); outbuf->length += samples_per_block; outbuf->mean_level += mean_level; outbuf->mean_power += mean_power; nextTimestamp += samples_per_block * BladeRF.decimation; ++blocks_processed; timeouts = 0; } first_buffer = false; if (blocks_processed) { // Get the approx system time for the start of this block int64_t block_duration = 1e3 * outbuf->length / Modes.sample_rate; outbuf->sysTimestamp -= block_duration; outbuf->sysMicroseconds -= 1000 * block_duration; outbuf->mean_level /= blocks_processed; outbuf->mean_power /= blocks_processed; // Push the new data to the demodulation thread lockReader(); // accumulate CPU while holding the mutex, and restart measurement end_cpu_timing(&thread_cpu, &Modes.reader_cpu_accumulator); start_cpu_timing(&thread_cpu); Modes.mag_buffers[next_free_buffer].dropped = 0; Modes.mag_buffers[next_free_buffer].length = 0; // just in case Modes.first_free_buffer = next_free_buffer; wakeDecode(); unlockReader(); } return samples; } void bladeRFRun() { if (!BladeRF.device) { return; } unsigned transfers = 7; int status; struct bladerf_stream *stream = NULL; void **buffers = NULL; if ((status = bladerf_init_stream(&stream, BladeRF.device, handle_bladerf_samples, &buffers, /* num_buffers */ transfers, BLADERF_FORMAT_SC16_Q11_META, /* samples_per_buffer */ Modes.sdr_buf_samples, /* num_transfers */ transfers, /* user_data */ NULL)) < 0) { fprintf(stderr, "bladerf_init_stream() failed: %s\n", bladerf_strerror(status)); goto out; } unsigned ms_per_transfer = 1000 * Modes.sdr_buf_samples / Modes.sample_rate; if ((status = bladerf_set_stream_timeout(BladeRF.device, BLADERF_MODULE_RX, ms_per_transfer * (transfers + 2))) < 0) { fprintf(stderr, "bladerf_set_stream_timeout() failed: %s\n", bladerf_strerror(status)); goto out; } if ((status = bladerf_enable_module(BladeRF.device, BLADERF_MODULE_RX, true) < 0)) { fprintf(stderr, "bladerf_enable_module(RX, true) failed: %s\n", bladerf_strerror(status)); goto out; } start_cpu_timing(&thread_cpu); timeouts = 0; // reset to zero when we get a callback with some data retry: if ((status = bladerf_stream(stream, BLADERF_MODULE_RX)) < 0) { fprintf(stderr, "bladerf_stream() failed: %s\n", bladerf_strerror(status)); if (status == BLADERF_ERR_TIMEOUT) { if (++timeouts < 5) goto retry; fprintf(stderr, "bladerf is wedged, giving up.\n"); } goto out; } out: if ((status = bladerf_enable_module(BladeRF.device, BLADERF_MODULE_RX, false) < 0)) { fprintf(stderr, "bladerf_enable_module(RX, false) failed: %s\n", bladerf_strerror(status)); } if (stream) { bladerf_deinit_stream(stream); } } void bladeRFClose() { if (BladeRF.converter) { cleanup_converter(&BladeRF.converter_state); BladeRF.converter = NULL; } if (BladeRF.device) { bladerf_close(BladeRF.device); BladeRF.device = NULL; } } readsb-3.16/sdr_bladerf.h000066400000000000000000000021301505057307600153270ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_bladerf.h: bladeRF support (header) // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2017 FlightAware LLC // // This file 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 // any later version. // // This file 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 . #ifndef BLADERF_H #define BLADERF_H // Support for the Nuand bladeRF SDR void bladeRFInitConfig (); bool bladeRFHandleOption (int argc, char *argv); bool bladeRFOpen (); void bladeRFRun (); void bladeRFClose (); #endif readsb-3.16/sdr_hackrf.c000066400000000000000000000214051505057307600151670ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_hackrf.c: HackRF support // // Copyright (c) 2023 Timothy Mullican // // This code is based on dump1090_sdrplus. // // Copyright (C) 2012 by Salvatore Sanfilippo // HackRF One support added by Ilker Temir // // This file 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 // any later version. // // This file 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 . #include "readsb.h" #include #include static struct { const char *device_str; unsigned block_size; hackrf_device *device; iq_convert_fn converter; struct converter_state *converter_state; // HackRF has three gain controls // RF ("amp", 0 or ~11 dB) // IF ("lna", 0 to 40 dB in 8 dB steps) // baseband ("vga", 0 to 62 dB in 2 dB steps) bool rf_gain; unsigned vga_gain; } hackRF; void hackRFInitConfig() { hackRF.device_str = NULL; hackRF.device = NULL; hackRF.rf_gain = false; hackRF.vga_gain = 48; } bool hackRFHandleOption(int key, char *arg) { switch (key) { case OptHackRfGainEnable: hackRF.rf_gain = true; break; case OptHackRfVgaGain: hackRF.vga_gain = atoi(arg); break; default: return false; } return true; } bool hackRFOpen() { if (hackRF.device) { return true; } int status; status = hackrf_init(); if ((status = hackrf_init()) != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_init failed: %s\n", hackrf_error_name(status)); goto error; } fprintf(stderr, "Opening HackRF: %s\n", Modes.dev_name); if (Modes.dev_name) { status = hackrf_open_by_serial(Modes.dev_name, &hackRF.device); } else { status = hackrf_open(&hackRF.device); } if (status != HACKRF_SUCCESS) { fprintf(stderr, "Failed to open hackRF: %s\n", hackrf_error_name(status)); goto error; } if ((status = hackrf_set_sample_rate(hackRF.device, Modes.sample_rate)) != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_set_sample_rate failed: %s\n", hackrf_error_name(status)); goto error; } if ((status = hackrf_set_freq(hackRF.device, Modes.freq)) != HACKRF_SUCCESS ) { fprintf(stderr, "hackrf_set_freq failed: %s\n", hackrf_error_name(status)); goto error; } if (Modes.gain == MODES_AUTO_GAIN || Modes.gain >= 400) { // hackRF doesn't have automatic gain control Modes.gain = 400; } if (Modes.gain < 0) { // gain is unsigned Modes.gain = 0; } if (hackRF.rf_gain) { if ((status = hackrf_set_amp_enable(hackRF.device, 1)) != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_set_amp_enable failed: %s\n", hackrf_error_name(status)); goto error; } } if ((status = hackrf_set_lna_gain(hackRF.device, Modes.gain / 10)) != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_set_lna_gain failed: %s\n", hackrf_error_name(status)); goto error; } if ((status = hackrf_set_vga_gain(hackRF.device, hackRF.vga_gain)) != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_set_vga_gain failed: %s\n", hackrf_error_name(status)); goto error; } if (Modes.biastee) { fprintf(stderr, "Enabling Bias Tee\n"); if ((status = hackrf_set_antenna_enable(hackRF.device, 1)) != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_set_antenna_enable failed: %s\n", hackrf_error_name(status)); } } fprintf (stderr, "HackRF successfully initialized " "(AMP Enable: %i, LNA Gain: %i, VGA Gain: %i).\n", hackRF.rf_gain, Modes.gain / 10, hackRF.vga_gain); hackRF.converter = init_converter(INPUT_UC8, Modes.sample_rate, Modes.dc_filter, &hackRF.converter_state); if (!hackRF.converter) { fprintf(stderr, "can't initialize sample converter\n"); goto error; } return true; error: if (hackRF.device) { hackrf_close(hackRF.device); hackrf_exit(); hackRF.device = NULL; } return false; } static struct timespec thread_cpu; static int hackrfCallback(hackrf_transfer *transfer) { struct mag_buf *outbuf; struct mag_buf *lastbuf; uint32_t slen; unsigned next_free_buffer; unsigned free_bufs; int64_t block_duration; static int was_odd = 0; static int dropping = 0; static uint64_t sampleCounter = 0; uint8_t *buf = transfer->buffer; uint32_t len = transfer->buffer_length; int64_t sysMicroseconds = mono_micro_seconds(); int64_t sysTimestamp = mstime(); // Lock the data buffer variables before accessing them lockReader(); // HackRF one returns signed IQ values, convert them to unsigned for (uint32_t i = 0; i < len; i++) { buf[i] ^= 0x80; // Flip the MSB to convert } next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS; outbuf = &Modes.mag_buffers[Modes.first_free_buffer]; lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS]; free_bufs = (Modes.first_filled_buffer - next_free_buffer + MODES_MAG_BUFFERS) % MODES_MAG_BUFFERS; if (len != Modes.sdr_buf_size) { fprintf(stderr, "weirdness: hackRF gave us a block with an unusual size (got %u bytes, expected %u bytes)\n", (unsigned) len, (unsigned) Modes.sdr_buf_size); if (len > Modes.sdr_buf_size) { unsigned discard = (len - Modes.sdr_buf_size + 1) / 2; outbuf->dropped += discard; buf += discard * 2; len -= discard * 2; } } if (was_odd) { ++buf; --len; ++outbuf->dropped; } was_odd = (len & 1); slen = len / 2; // Drops any trailing odd sample, that's OK if (free_bufs == 0 || (dropping && free_bufs < MODES_MAG_BUFFERS / 2)) { // FIFO is full. Drop this block. dropping = 1; outbuf->dropped += slen; sampleCounter += slen; // make extra sure that the decode thread isn't sleeping unlockReader(); return 1; } dropping = 0; unlockReader(); // Compute the sample timestamp and system timestamp for the start of the block outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate; sampleCounter += slen; // Get the approx system time for the start of this block block_duration = 1e3 * slen / Modes.sample_rate; outbuf->sysTimestamp = sysTimestamp; outbuf->sysMicroseconds = sysMicroseconds; outbuf->sysTimestamp -= block_duration; outbuf->sysMicroseconds -= block_duration * 1000; // Copy trailing data from last block (or reset if not valid) if (outbuf->dropped == 0) { memcpy(outbuf->data, lastbuf->data + lastbuf->length, Modes.trailing_samples * sizeof (uint16_t)); } else { memset(outbuf->data, 0, Modes.trailing_samples * sizeof (uint16_t)); } // Convert the new data outbuf->length = slen; hackRF.converter(buf, &outbuf->data[Modes.trailing_samples], slen, hackRF.converter_state, &outbuf->mean_level, &outbuf->mean_power); // Push the new data to the demodulation thread lockReader(); Modes.mag_buffers[next_free_buffer].dropped = 0; Modes.mag_buffers[next_free_buffer].length = 0; // just in case Modes.first_free_buffer = next_free_buffer; // accumulate CPU while holding the mutex, and restart measurement end_cpu_timing(&thread_cpu, &Modes.reader_cpu_accumulator); start_cpu_timing(&thread_cpu); wakeDecode(); unlockReader(); return 0; } void hackRFRun() { if (!hackRF.device) { return; } start_cpu_timing(&thread_cpu); int status; if ((status = hackrf_start_rx(hackRF.device, hackrfCallback, NULL)) != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_start_rx failed: %s\n", hackrf_error_name(status)); } struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); while (!Modes.exit) { threadTimedWait(&Threads.reader, &ts, 50); } } void hackRFClose() { hackrf_stop_rx(hackRF.device); if (hackRF.converter) { cleanup_converter(&hackRF.converter_state); hackRF.converter = NULL; } if (hackRF.device) { hackrf_close(hackRF.device); hackRF.device = NULL; } } readsb-3.16/sdr_hackrf.h000066400000000000000000000022621505057307600151740ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_hackrf.c: HackRF support // // Copyright (c) 2023 Timothy Mullican // // This code is based on dump1090_sdrplus. // // Copyright (C) 2012 by Salvatore Sanfilippo // HackRF One support added by Ilker Temir // // This file 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 // any later version. // // This file 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 . #ifndef HACKRF_H #define HACKRF_H // Support for the Great Scott Gadgets HackRF One SDR void hackRFInitConfig (); bool hackRFHandleOption (int argc, char *argv); bool hackRFOpen (); void hackRFRun (); void hackRFClose (); #endif readsb-3.16/sdr_ifile.c000066400000000000000000000225741505057307600150310ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_ifile.c: "file" SDR support // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "readsb.h" #include "sdr_ifile.h" static struct { input_format_t input_format; int fd; unsigned bytes_per_sample; bool throttle; uint8_t padding1; uint16_t padding2; void *readbuf; iq_convert_fn converter; struct converter_state *converter_state; const char *filename; } ifile; void ifileInitConfig(void) { ifile.filename = NULL; ifile.input_format = INPUT_UC8; ifile.throttle = false; ifile.fd = -1; ifile.bytes_per_sample = 0; ifile.readbuf = NULL; ifile.converter = NULL; ifile.converter_state = NULL; } bool ifileHandleOption(int key, char *arg) { switch (key) { case OptIfileName: ifile.filename = strdup(arg); Modes.sdr_type = SDR_IFILE; break; case OptIfileFormat: if (!strcasecmp(arg, "uc8")) { ifile.input_format = INPUT_UC8; } else if (!strcasecmp(arg, "sc16")) { ifile.input_format = INPUT_SC16; } else if (!strcasecmp(arg, "sc16q11")) { ifile.input_format = INPUT_SC16Q11; } else { fprintf(stderr, "Input format '%s' not understood (supported values: UC8, SC16, SC16Q11)\n", arg); return false; } break; case OptIfileThrottle: ifile.throttle = true; break; default: return false; } return true; } // //========================================================================= // // This is used when --ifile is specified in order to read data from file // instead of using an RTLSDR device // bool ifileOpen(void) { if (!ifile.filename) { fprintf(stderr, "SDR type 'ifile' requires an --ifile argument\n"); return false; } if (strcmp(ifile.filename, "-") == 0) { ifile.fd = STDIN_FILENO; } else if ((ifile.fd = open(ifile.filename, O_RDONLY)) < 0) { fprintf(stderr, "ifile: could not open %s: %s\n", ifile.filename, strerror(errno)); return false; } if (strcmp(ifile.filename, "-") != 0) { Modes.synthetic_now = mstime(); } switch (ifile.input_format) { case INPUT_UC8: ifile.bytes_per_sample = 2; break; case INPUT_SC16: case INPUT_SC16Q11: ifile.bytes_per_sample = 4; break; default: fprintf(stderr, "ifile: unhandled input format\n"); ifileClose(); return false; } if (!(ifile.readbuf = cmalloc(Modes.sdr_buf_samples * ifile.bytes_per_sample))) { fprintf(stderr, "ifile: failed to allocate read buffer\n"); ifileClose(); return false; } ifile.converter = init_converter(ifile.input_format, Modes.sample_rate, Modes.dc_filter, &ifile.converter_state); if (!ifile.converter) { fprintf(stderr, "ifile: can't initialize sample converter\n"); ifileClose(); return false; } return true; } void ifileRun() { if (ifile.fd < 0) return; int eof = 0; struct timespec next_buffer_delivery; struct timespec thread_cpu; start_cpu_timing(&thread_cpu); uint64_t sampleCounter = 0; clock_gettime(CLOCK_MONOTONIC, &next_buffer_delivery); struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); lockReader(); while (!Modes.exit && !eof) { ssize_t nread, toread; void *r; struct mag_buf *outbuf, *lastbuf; unsigned next_free_buffer; unsigned slen; next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS; if (next_free_buffer == Modes.first_filled_buffer) { // no space for output yet threadTimedWait(&Threads.reader, &ts, 50); continue; } outbuf = &Modes.mag_buffers[Modes.first_free_buffer]; lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS]; unlockReader(); // Compute the sample timestamp for the start of the block outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate; // Copy trailing data from last block (or reset if not valid) if (lastbuf->length >= Modes.trailing_samples) { memcpy(outbuf->data, lastbuf->data + lastbuf->length, Modes.trailing_samples * sizeof (uint16_t)); } else { memset(outbuf->data, 0, Modes.trailing_samples * sizeof (uint16_t)); } // Get the system time for the start of this block outbuf->sysTimestamp = outbuf->sampleTimestamp / 12000U + Modes.startup_time; outbuf->sysMicroseconds = outbuf->sampleTimestamp / 12U + Modes.startup_time * 1000; //fprintf(stderr, "sysTimestamp %.3f\n", outbuf->sysTimestamp / 1000.0); toread = Modes.sdr_buf_samples * ifile.bytes_per_sample; r = ifile.readbuf; while (toread) { nread = read(ifile.fd, r, toread); if (nread <= 0) { if (nread < 0) { fprintf(stderr, "ifile: error reading input file: %s\n", strerror(errno)); } // Done. eof = 1; break; } r += nread; toread -= nread; } slen = outbuf->length = Modes.sdr_buf_samples - toread / ifile.bytes_per_sample; sampleCounter += slen; // Convert the new data ifile.converter(ifile.readbuf, &outbuf->data[Modes.trailing_samples], slen, ifile.converter_state, &outbuf->mean_level, &outbuf->mean_power); if (ifile.throttle || Modes.interactive) { // Wait until we are allowed to release this buffer to the main thread while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_buffer_delivery, NULL) == EINTR) ; // compute the time we can deliver the next buffer. next_buffer_delivery.tv_nsec += outbuf->length * 1e9 / Modes.sample_rate; normalize_timespec(&next_buffer_delivery); } // Push the new data to the main thread lockReader(); Modes.first_free_buffer = next_free_buffer; // accumulate CPU while holding the mutex, and restart measurement end_cpu_timing(&thread_cpu, &Modes.reader_cpu_accumulator); start_cpu_timing(&thread_cpu); wakeDecode(); } // Wait for the main thread to consume all data (reader still locked here) while (!Modes.exit && Modes.first_filled_buffer != Modes.first_free_buffer) { wakeDecode(); threadTimedWait(&Threads.reader, &ts, 50); } Modes.exit = 1; unlockReader(); } void ifileClose() { if (ifile.converter) { cleanup_converter(&ifile.converter_state); ifile.converter = NULL; } if (ifile.readbuf) { free(ifile.readbuf); ifile.readbuf = NULL; } if (ifile.fd >= 0 && ifile.fd != STDIN_FILENO) { close(ifile.fd); ifile.fd = -1; } } readsb-3.16/sdr_ifile.h000066400000000000000000000022351505057307600150260ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_ifile.c: "file" SDR support (header) // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // // This file 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 // any later version. // // This file 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 . #ifndef SDR_IFILE_H #define SDR_IFILE_H // Pseudo-SDR that reads from a sample file void ifileInitConfig (); bool ifileHandleOption (int argc, char *argv); bool ifileOpen (); void ifileRun (); void ifileClose (); #endif readsb-3.16/sdr_plutosdr.c000066400000000000000000000231471505057307600156120ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_pluto.c: PlutoSDR support // // Copyright (c) 2019 Michael Wolf // // This file 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 // any later version. // // This file 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 . #include #include #include "readsb.h" #include "sdr_plutosdr.h" static struct { input_format_t input_format; int dev_index; struct iio_channel *rx0_i; struct iio_channel *rx0_q; struct iio_buffer *rxbuf; struct iio_context *ctx; struct iio_device *dev; int16_t *readbuf; iq_convert_fn converter; struct converter_state *converter_state; char *uri; char *network; } PLUTOSDR; static struct timespec thread_cpu; void plutosdrInitConfig() { PLUTOSDR.readbuf = NULL; PLUTOSDR.converter = NULL; PLUTOSDR.converter_state = NULL; PLUTOSDR.uri = NULL; PLUTOSDR.network = strdup("pluto.local"); } bool plutosdrHandleOption(int key, char *arg) { switch (key) { case OptPlutoUri: PLUTOSDR.uri = strdup(arg); break; case OptPlutoNetwork: PLUTOSDR.network = strdup(arg); break; default: return false; } return true; } bool plutosdrOpen() { PLUTOSDR.ctx = iio_create_default_context(); if (PLUTOSDR.ctx == NULL && PLUTOSDR.uri != NULL) { PLUTOSDR.ctx = iio_create_context_from_uri(PLUTOSDR.uri); } else if (PLUTOSDR.ctx == NULL) { PLUTOSDR.ctx = iio_create_network_context(PLUTOSDR.network); } if (PLUTOSDR.ctx == NULL) { char buf[1024]; iio_strerror(errno, buf, sizeof(buf)); fprintf(stderr, "plutosdr: Failed creating IIO context: %s\n", buf); return false; } struct iio_scan_context *ctx; struct iio_context_info **info; ctx = iio_create_scan_context(NULL, 0); if (ctx) { int info_count = iio_scan_context_get_info_list(ctx, &info); if(info_count > 0) { fprintf(stderr, "plutosdr: %s\n", iio_context_info_get_description(info[0])); iio_context_info_list_free(info); } iio_scan_context_destroy(ctx); } int device_count = iio_context_get_devices_count(PLUTOSDR.ctx); if (!device_count) { fprintf(stderr, "plutosdr: No supported PLUTOSDR devices found.\n"); plutosdrClose(); } fprintf(stderr, "plutosdr: Context has %d device(s).\n", device_count); PLUTOSDR.dev = iio_context_find_device(PLUTOSDR.ctx, "cf-ad9361-lpc"); if (PLUTOSDR.dev == NULL) { fprintf(stderr, "plutosdr: Error opening the PLUTOSDR device: %s\n", strerror(errno)); plutosdrClose(); } struct iio_channel* phy_chn = iio_device_find_channel(iio_context_find_device(PLUTOSDR.ctx, "ad9361-phy"), "voltage0", false); iio_channel_attr_write(phy_chn, "rf_port_select", "A_BALANCED"); iio_channel_attr_write_longlong(phy_chn, "rf_bandwidth", (long long)1750000); iio_channel_attr_write_longlong(phy_chn, "sampling_frequency", (long long)Modes.sample_rate); if (Modes.gain == MODES_AUTO_GAIN) { iio_channel_attr_write(phy_chn, "gain_control_mode", "slow_attack"); } else { // We use 10th of dB here, max is 77dB up to 1300MHz if (Modes.gain > 770) Modes.gain = 770; iio_channel_attr_write(phy_chn, "gain_control_mode", "manual"); iio_channel_attr_write_longlong(phy_chn, "hardwaregain", Modes.gain / 10); } iio_channel_attr_write_bool( iio_device_find_channel(iio_context_find_device(PLUTOSDR.ctx, "ad9361-phy"), "altvoltage1", true) , "powerdown", true); // Turn OFF TX LO iio_channel_attr_write_longlong( iio_device_find_channel(iio_context_find_device(PLUTOSDR.ctx, "ad9361-phy"), "altvoltage0", true) , "frequency", (long long)Modes.freq); // Set RX LO frequency PLUTOSDR.rx0_i = iio_device_find_channel(PLUTOSDR.dev, "voltage0", false); if (!PLUTOSDR.rx0_i) PLUTOSDR.rx0_i = iio_device_find_channel(PLUTOSDR.dev, "altvoltage0", false); PLUTOSDR.rx0_q = iio_device_find_channel(PLUTOSDR.dev, "voltage1", false); if (!PLUTOSDR.rx0_q) PLUTOSDR.rx0_q = iio_device_find_channel(PLUTOSDR.dev, "altvoltage1", false); ad9361_set_bb_rate(iio_context_find_device(PLUTOSDR.ctx, "ad9361-phy"), Modes.sample_rate); iio_channel_enable(PLUTOSDR.rx0_i); iio_channel_enable(PLUTOSDR.rx0_q); PLUTOSDR.rxbuf = iio_device_create_buffer(PLUTOSDR.dev, Modes.sdr_buf_samples, false); if (!PLUTOSDR.rxbuf) { perror("plutosdr: Could not create RX buffer"); } if (!(PLUTOSDR.readbuf = cmalloc(Modes.sdr_buf_size * 4))) { fprintf(stderr, "plutosdr: Failed to allocate read buffer\n"); plutosdrClose(); return false; } PLUTOSDR.converter = init_converter(INPUT_SC16, Modes.sample_rate, Modes.dc_filter, &PLUTOSDR.converter_state); if (!PLUTOSDR.converter) { fprintf(stderr, "plutosdr: Can't initialize sample converter\n"); plutosdrClose(); return false; } return true; } static void plutosdrCallback(int16_t *buf, uint32_t len) { struct mag_buf *outbuf; struct mag_buf *lastbuf; uint32_t slen; unsigned next_free_buffer; unsigned free_bufs; int64_t block_duration; static int was_odd = 0; static int dropping = 0; static uint64_t sampleCounter = 0; int64_t sysMicroseconds = mono_micro_seconds(); int64_t sysTimestamp = mstime(); lockReader(); next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS; outbuf = &Modes.mag_buffers[Modes.first_free_buffer]; lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS]; free_bufs = (Modes.first_filled_buffer - next_free_buffer + MODES_MAG_BUFFERS) % MODES_MAG_BUFFERS; if (len != Modes.sdr_buf_size) { fprintf(stderr, "weirdness: plutosdr gave us a block with an unusual size (got %u bytes, expected %u bytes)\n", (unsigned) len, (unsigned) Modes.sdr_buf_size); if (len > Modes.sdr_buf_size) { unsigned discard = (len - Modes.sdr_buf_size + 1) / 2; outbuf->dropped += discard; buf += discard * 2; len -= discard * 2; } } if (was_odd) { ++buf; --len; ++outbuf->dropped; } was_odd = (len & 1); slen = len / 2; if (free_bufs == 0 || (dropping && free_bufs < MODES_MAG_BUFFERS / 2)) { dropping = 1; outbuf->dropped += slen; sampleCounter += slen; unlockReader(); return; } dropping = 0; unlockReader(); outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate; sampleCounter += slen; block_duration = 1e3 * slen / Modes.sample_rate; outbuf->sysTimestamp = sysTimestamp; outbuf->sysMicroseconds = sysMicroseconds; outbuf->sysTimestamp -= block_duration; outbuf->sysMicroseconds -= block_duration * 1000; if (outbuf->dropped == 0) { memcpy(outbuf->data, lastbuf->data + lastbuf->length, Modes.trailing_samples * sizeof (uint16_t)); } else { memset(outbuf->data, 0, Modes.trailing_samples * sizeof (uint16_t)); } outbuf->length = slen; PLUTOSDR.converter(buf, &outbuf->data[Modes.trailing_samples], slen, PLUTOSDR.converter_state, &outbuf->mean_level, &outbuf->mean_power); lockReader(); Modes.mag_buffers[next_free_buffer].dropped = 0; Modes.mag_buffers[next_free_buffer].length = 0; Modes.first_free_buffer = next_free_buffer; end_cpu_timing(&thread_cpu, &Modes.reader_cpu_accumulator); start_cpu_timing(&thread_cpu); wakeDecode(); unlockReader(); } void plutosdrRun() { void *p_dat, *p_end; ptrdiff_t p_inc; if (!PLUTOSDR.dev) { return; } start_cpu_timing(&thread_cpu); while (!Modes.exit) { int16_t *p = PLUTOSDR.readbuf; uint32_t len = (uint32_t) iio_buffer_refill(PLUTOSDR.rxbuf) / 2; p_inc = iio_buffer_step(PLUTOSDR.rxbuf); p_end = iio_buffer_end(PLUTOSDR.rxbuf); p_dat = iio_buffer_first(PLUTOSDR.rxbuf, PLUTOSDR.rx0_i); for (p_dat = iio_buffer_first(PLUTOSDR.rxbuf, PLUTOSDR.rx0_i); p_dat < p_end; p_dat += p_inc) { *p++ = ((int16_t*) p_dat)[0]; // Real (I) *p++ = ((int16_t*) p_dat)[1]; // Imag (Q) } plutosdrCallback(PLUTOSDR.readbuf, len); } } void plutosdrClose() { if(PLUTOSDR.readbuf) { free(PLUTOSDR.readbuf); } if (PLUTOSDR.rxbuf) { iio_buffer_destroy(PLUTOSDR.rxbuf); } if (PLUTOSDR.rx0_i) { iio_channel_disable(PLUTOSDR.rx0_i); } if (PLUTOSDR.rx0_q) { iio_channel_disable(PLUTOSDR.rx0_q); } if (PLUTOSDR.ctx) { iio_context_destroy(PLUTOSDR.ctx); } free(PLUTOSDR.network); free(PLUTOSDR.uri); } readsb-3.16/sdr_plutosdr.h000066400000000000000000000020011505057307600156010ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_pluto.h: PlutoSDR support (header) // // Copyright (c) 2019 Michael Wolf // // This file 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 // any later version. // // This file 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 . #ifndef SDR_PLUTO_H #define SDR_PLUTO_H void plutosdrInitConfig(); bool plutosdrHandleOption(int argc, char *argv); bool plutosdrOpen(); void plutosdrRun(); void plutosdrClose(); #endif /* SDR_PLUTO_H */ readsb-3.16/sdr_rtlsdr.c000066400000000000000000000374041505057307600152510ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_rtlsdr.c: rtlsdr dongle support // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "readsb.h" #include "sdr_rtlsdr.h" #include #if (defined(__arm__) || defined(__aarch64__)) && !defined(DISABLE_RTLSDR_ZEROCOPY_WORKAROUND) // Assume we need to use a bounce buffer to avoid performance problems on Pis running kernel 5.x and using zerocopy # define USE_BOUNCE_BUFFER #endif static struct { iq_convert_fn converter; struct converter_state *converter_state; rtlsdr_dev_t *dev; uint8_t *bounce_buffer; int ppm_error; bool digital_agc; bool use_rtl_agc; int numgains; int *gains; int curGain; int tunerAgcEnabled; } RTLSDR; // // =============================== RTLSDR handling ========================== // void rtlsdrInitConfig() { RTLSDR.dev = NULL; RTLSDR.digital_agc = false; RTLSDR.use_rtl_agc = false; RTLSDR.ppm_error = 0; RTLSDR.converter = NULL; RTLSDR.converter_state = NULL; RTLSDR.bounce_buffer = NULL; RTLSDR.numgains = 0; RTLSDR.gains = NULL; RTLSDR.tunerAgcEnabled = 0; } static int getClosestGainIndex(int target) { target = (target == MODES_MAX_GAIN ? 9999 : target); int closest = 0; for (int i = 0; i < RTLSDR.numgains; ++i) { if (abs(RTLSDR.gains[i] - target) < abs(RTLSDR.gains[closest] - target)) { closest = i; } } return closest; } void rtlsdrSetGain(char *reason) { if (RTLSDR.use_rtl_agc && (Modes.gain == MODES_AUTO_GAIN || Modes.gain >= 520)) { Modes.gain = MODES_RTL_AGC; } if (Modes.increaseGain || Modes.lowerGain) { int closest = getClosestGainIndex(Modes.gain); if (Modes.increaseGain) { closest += Modes.increaseGain; } if (Modes.lowerGain) { closest -= Modes.lowerGain; } if (closest >= RTLSDR.numgains) { closest = RTLSDR.numgains - 1; } if (closest < 0) { closest = 0; } Modes.increaseGain = 0; Modes.lowerGain = 0; if (RTLSDR.gains[closest] < Modes.minGain) { // new gain less than minimum, nothing to do return; } if (Modes.gain == RTLSDR.gains[closest]) { // same gain, nothing to do return; } // change gain Modes.gain = RTLSDR.gains[closest]; } if (Modes.gain < 0) { Modes.gain = 0; } if (RTLSDR.use_rtl_agc && Modes.gain == MODES_RTL_AGC) { RTLSDR.tunerAgcEnabled = 1; if (!Modes.gainQuiet) { fprintf(stderr, "%srtlsdr: tuner gain set to 59.0 dB (tuner AGC)\n", reason); } if (rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 0)) { fprintf(stderr, "rtlsdr: enabling tuner AGC failed\n"); return; } } else { int closest = getClosestGainIndex(Modes.gain); int newGain = RTLSDR.gains[closest]; if (RTLSDR.tunerAgcEnabled) { if (rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 1)) { fprintf(stderr, "rtlsdr: disabling tuner AGC failed\n"); return; } RTLSDR.tunerAgcEnabled = 0; usleep(1000); } if (rtlsdr_set_tuner_gain(RTLSDR.dev, newGain)) { fprintf(stderr, "rtlsdr: setting tuner gain failed\n"); return; } else { if (!Modes.gainQuiet) { fprintf(stderr, "%srtlsdr: tuner gain set to %4.1f dB\n", reason, newGain / 10.0); } Modes.gain = newGain; } } } static void show_rtlsdr_devices() { int device_count = rtlsdr_get_device_count(); fprintf(stderr, "rtlsdr: found %d device(s):\n", device_count); for (int i = 0; i < device_count; i++) { char vendor[256], product[256], serial[256]; if (rtlsdr_get_device_usb_strings(i, vendor, product, serial) != 0) { fprintf(stderr, " %d: unable to read device details\n", i); } else { fprintf(stderr, " %d: %s, %s, SN: %s\n", i, vendor, product, serial); } } } static int find_device_index(char *s) { int device_count = rtlsdr_get_device_count(); if (!device_count) { return -1; } /* does string look like raw id number */ if (!strcmp(s, "0")) { return 0; } else if (s[0] != '0') { char *s2; int device = (int) strtol(s, &s2, 10); if (s2[0] == '\0' && device >= 0 && device < device_count) { return device; } } /* does string exact match a serial */ for (int i = 0; i < device_count; i++) { char serial[256]; if (rtlsdr_get_device_usb_strings(i, NULL, NULL, serial) == 0 && !strcmp(s, serial)) { return i; } } /* does string prefix match a serial */ for (int i = 0; i < device_count; i++) { char serial[256]; if (rtlsdr_get_device_usb_strings(i, NULL, NULL, serial) == 0 && !strncmp(s, serial, strlen(s))) { return i; } } /* does string suffix match a serial */ for (int i = 0; i < device_count; i++) { char serial[256]; if (rtlsdr_get_device_usb_strings(i, NULL, NULL, serial) == 0 && strlen(s) < strlen(serial) && !strcmp(serial + strlen(serial) - strlen(s), s)) { return i; } } return -1; } bool rtlsdrHandleOption(int key, char *arg) { switch (key) { case OptRtlSdrEnableAgc: RTLSDR.digital_agc = true; break; case OptRtlSdrPpm: RTLSDR.ppm_error = atoi(arg); break; default: return false; } return true; } bool rtlsdrOpen(void) { if (!rtlsdr_get_device_count()) { fprintf(stderr, "FATAL: rtlsdr: no supported devices found.\n"); return false; } int dev_index = 0; if (Modes.dev_name) { if ((dev_index = find_device_index(Modes.dev_name)) < 0) { fprintf(stderr, "FATAL: rtlsdr: no device matching '%s' found.\n", Modes.dev_name); show_rtlsdr_devices(); return false; } } char manufacturer[256]; char product[256]; char serial[256]; if (rtlsdr_get_device_usb_strings(dev_index, manufacturer, product, serial) < 0) { fprintf(stderr, "FATAL: rtlsdr: error querying device #%d: %s\n", dev_index, strerror(errno)); return false; } fprintf(stderr, "rtlsdr: using device #%d: %s (%s, %s, SN %s)\n", dev_index, rtlsdr_get_device_name(dev_index), manufacturer, product, serial); if (rtlsdr_open(&RTLSDR.dev, dev_index) < 0) { fprintf(stderr, "FATAL: rtlsdr: error opening the RTLSDR device: %s\n", strerror(errno)); return false; } RTLSDR.numgains = rtlsdr_get_tuner_gains(RTLSDR.dev, NULL); if (RTLSDR.numgains <= 0) { fprintf(stderr, "FATAL: rtlsdr: error getting tuner gains\n"); return false; } // allocate numgains + 1 for the AGC being added as a pseudo gain level (hacky) RTLSDR.gains = cmalloc((RTLSDR.numgains + 1) * sizeof (int)); if (rtlsdr_get_tuner_gains(RTLSDR.dev, RTLSDR.gains) != RTLSDR.numgains) { fprintf(stderr, "FATAL: rtlsdr: error getting tuner gains\n"); free(RTLSDR.gains); return false; } //fprintf(stderr, "numgains: %d\n", RTLSDR.numgains); // one extra step for tuner agc / max (but only for rtl tuners) if (RTLSDR.numgains == 29) { // this is hacky and relies on the fact that we allocate 1 extra element RTLSDR.use_rtl_agc = true; // set agc at 590 (MODES_RTL_AGC) RTLSDR.gains[RTLSDR.numgains] = MODES_RTL_AGC; RTLSDR.numgains++; } rtlsdrSetGain(""); if (RTLSDR.digital_agc) { fprintf(stderr, "rtlsdr: enabling digital AGC\n"); rtlsdr_set_agc_mode(RTLSDR.dev, 1); } // Set frequency, sample rate, and reset the device rtlsdr_set_freq_correction(RTLSDR.dev, RTLSDR.ppm_error); rtlsdr_set_center_freq(RTLSDR.dev, Modes.freq); rtlsdr_set_sample_rate(RTLSDR.dev, (unsigned) Modes.sample_rate); #ifdef ENABLE_RTLSDR_BIASTEE // Enable or disable bias tee on GPIO pin 0. (Works only for rtl-sdr.com v3 dongles) rtlsdr_set_bias_tee(RTLSDR.dev, Modes.biastee); #endif rtlsdr_reset_buffer(RTLSDR.dev); RTLSDR.converter = init_converter(INPUT_UC8, Modes.sample_rate, Modes.dc_filter, &RTLSDR.converter_state); if (!RTLSDR.converter) { fprintf(stderr, "FATAL: rtlsdr: can't initialize sample converter\n"); rtlsdrClose(); return false; } #ifdef USE_BOUNCE_BUFFER if (!(RTLSDR.bounce_buffer = cmalloc(Modes.sdr_buf_size))) { fprintf(stderr, "FATAL: rtlsdr: can't allocate bounce buffer\n"); rtlsdrClose(); return false; } #endif return true; } static struct timespec rtlsdr_thread_cpu; void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) { struct mag_buf *outbuf; struct mag_buf *lastbuf; uint32_t slen; unsigned next_free_buffer; unsigned free_bufs; int64_t block_duration; static int dropping = 0; static uint64_t sampleCounter = 0; static int antiSpam; static int antiSpam2; int64_t sysMicroseconds = mono_micro_seconds(); int64_t sysTimestamp = mstime(); // simulating missed USB packets: if (0) { static int fail; if (fail++ % (35 * 20) == 0) { fprintf(stderr, "ignoring rtsdrCallback\n"); return; } } MODES_NOTUSED(ctx); // Lock the data buffer variables before accessing them lockReader(); next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS; outbuf = &Modes.mag_buffers[Modes.first_free_buffer]; lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS]; free_bufs = (Modes.first_filled_buffer - next_free_buffer + MODES_MAG_BUFFERS) % MODES_MAG_BUFFERS; unlockReader(); if (len != Modes.sdr_buf_size) { fprintf(stderr, "weirdness: rtlsdr gave us a block with an unusual size (got %u bytes, expected %u bytes)\n", (unsigned) len, (unsigned) Modes.sdr_buf_size); if (len > Modes.sdr_buf_size) { // wat?! Discard the start. unsigned discard = (len - Modes.sdr_buf_size + 1) / 2; outbuf->dropped += discard; buf += discard * 2; len -= discard * 2; } } slen = len / 2; // Drops any trailing odd sample, that's OK if (free_bufs == 0 || (dropping && free_bufs < MODES_MAG_BUFFERS / 2)) { // FIFO is full. Drop this block. dropping = 1; outbuf->dropped += slen; sampleCounter += slen; // make extra sure that the decode thread isn't sleeping wakeDecode(); if (--antiSpam <= 0 && !Modes.exit) { fprintf(stderr, "FIFO dropped, suppressing this message for 30 seconds.\n"); antiSpam = 300; } return; } dropping = 0; // Compute the sample timestamp and system timestamp for the start of the block outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate; sampleCounter += slen; if (Modes.debug_sampleCounter && --antiSpam2 <= 0) { fprintf(stderr, "sampleTimestamp: %020llu\n", (unsigned long long) outbuf->sampleTimestamp); antiSpam2 = 3000; } // Get the approx system time for the start of this block block_duration = 1e3 * slen / Modes.sample_rate; outbuf->sysTimestamp = sysTimestamp; outbuf->sysMicroseconds = sysMicroseconds; outbuf->sysTimestamp -= block_duration; outbuf->sysMicroseconds -= block_duration * 1000; // Copy trailing data from last block (or reset if not valid) if (outbuf->dropped == 0) { memcpy(outbuf->data, lastbuf->data + lastbuf->length, Modes.trailing_samples * sizeof (uint16_t)); } else { memset(outbuf->data, 0, Modes.trailing_samples * sizeof (uint16_t)); } #ifdef USE_BOUNCE_BUFFER // Work around zero-copy slowness on Pis with 5.x kernels memcpy(RTLSDR.bounce_buffer, buf, slen * 2); buf = RTLSDR.bounce_buffer; #endif // Convert the new data outbuf->length = slen; RTLSDR.converter(buf, &outbuf->data[Modes.trailing_samples], slen, RTLSDR.converter_state, &outbuf->mean_level, &outbuf->mean_power); // Push the new data to the demodulation thread lockReader(); Modes.mag_buffers[next_free_buffer].dropped = 0; Modes.mag_buffers[next_free_buffer].length = 0; // just in case Modes.first_free_buffer = next_free_buffer; // accumulate CPU while holding the mutex, and restart measurement end_cpu_timing(&rtlsdr_thread_cpu, &Modes.reader_cpu_accumulator); start_cpu_timing(&rtlsdr_thread_cpu); wakeDecode(); unlockReader(); } void rtlsdrRun() { if (!RTLSDR.dev) { return; } start_cpu_timing(&rtlsdr_thread_cpu); rtlsdr_read_async(RTLSDR.dev, rtlsdrCallback, NULL, MODES_RTL_BUFFERS, Modes.sdr_buf_size); if (!Modes.exit) { fprintf(stderr,"FATAL: rtlsdr_read_async returned unexpectedly, probably lost the USB device, bailing out\n"); } } void rtlsdrCancel() { rtlsdr_cancel_async(RTLSDR.dev); // interrupt read_async } void rtlsdrClose() { if (RTLSDR.dev) { rtlsdr_close(RTLSDR.dev); RTLSDR.dev = NULL; } if (RTLSDR.converter) { cleanup_converter(&RTLSDR.converter_state); RTLSDR.converter = NULL; } free(RTLSDR.gains); free(RTLSDR.bounce_buffer); RTLSDR.bounce_buffer = NULL; } readsb-3.16/sdr_rtlsdr.h000066400000000000000000000022621505057307600152500ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_rtlsdr.h: rtlsdr dongle support (header) // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // // This file 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 // any later version. // // This file 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 . #ifndef SDR_RTLSDR_H #define SDR_RTLSDR_H void rtlsdrInitConfig (); bool rtlsdrOpen (); void rtlsdrRun (); void rtlsdrCancel(); void rtlsdrClose (); bool rtlsdrHandleOption (int argc, char *argv); void rtlsdrSetGain(char *reason); #endif readsb-3.16/sdr_soapy.c000066400000000000000000000546031505057307600150720ustar00rootroot00000000000000// Part of dump1090, a Mode S message decoder for RTLSDR devices. // // sdr_soapy.c: SoapySDR support // // Copyright (c) 2014-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // // This file is free software: you may copy, redistribute and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 2 of the License, or (at your // option) any later version. // // This file 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 . #include "readsb.h" #include "sdr_soapy.h" #include #include #include static struct { SoapySDRDevice *dev; SoapySDRStream *stream; iq_convert_fn converter; struct converter_state *converter_state; size_t channel; const char* antenna; double bandwidth; bool enable_agc; int num_gain_elements; char **gain_elements; SoapySDRRange gain_range; int current_gain_step; } SOAPY; static struct timespec thread_cpu; // Polyfill some differences between SoapySDR 0.7 and 0.8 #if !defined(SOAPY_SDR_API_VERSION) || (SOAPY_SDR_API_VERSION < 0x00080000) static void polyfill_SoapySDR_free(void *ignored) { (void) ignored; } static SoapySDRStream *polyfill_SoapySDRDevice_setupStream(SoapySDRDevice *device, const int direction, const char *format, const size_t *channels, const size_t numChans, const SoapySDRKwargs *args) { SoapySDRStream *result; if (SoapySDRDevice_setupStream(device, &result, direction, format, channels, numChans, args) == 0) return result; else return NULL; } #define SoapySDR_free polyfill_SoapySDR_free #define SoapySDRDevice_setupStream polyfill_SoapySDRDevice_setupStream #endif /* pre-0.8 API */ // // =============================== SoapySDR handling ========================== // void soapyInitConfig() { SOAPY.dev = NULL; SOAPY.stream = NULL; SOAPY.converter = NULL; SOAPY.converter_state = NULL; SOAPY.channel = 0; SOAPY.antenna = NULL; SOAPY.bandwidth = 0; SOAPY.enable_agc = false; SOAPY.num_gain_elements = 0; SOAPY.gain_elements = NULL; } /*void soapyShowHelp() { printf(" SoapySDR-specific options (use with --device-type soapy)\n"); printf("\n"); printf("--device select/configure device\n"); printf("--channel select channel if device supports multiple channels (default: 0)\n"); printf("--antenna select antenna (default depends on device)\n"); printf("--bandwidth set the baseband filter width (default: 3MHz, SDRPlay: 5MHz)\n"); printf("--enable-agc enable Automatic Gain Control if supported by device\n"); printf("--gain-element : set gain in dB for a named gain element\n"); printf("\n"); }*/ bool soapyHandleOption(int key, char *arg) { switch (key) { case OptSoapyAntenna: SOAPY.antenna = strdup(arg); break; case OptSoapyBandwith: SOAPY.bandwidth = atoi(arg); break; case OptSoapyEnableAgc: SOAPY.enable_agc = true; break; case OptSoapyGainElement: ++SOAPY.num_gain_elements; if (! (SOAPY.gain_elements = realloc(SOAPY.gain_elements, SOAPY.num_gain_elements * sizeof(*SOAPY.gain_elements))) ) { perror("realloc"); abort(); } if (! (SOAPY.gain_elements[SOAPY.num_gain_elements-1] = strdup(arg)) ) { perror("strdup"); abort(); } break; default: return false; } return true; } static void soapyShowDevices(SoapySDRKwargs *results, size_t length) { for (size_t i = 0; i < length; ++i) { fprintf(stderr, " #%zu: ", i); for (size_t j = 0; j < results[i].size; ++j) { if (j) fprintf(stderr, ", "); fprintf(stderr, "%s=%s", results[i].keys[j], results[i].vals[j]); } fprintf(stderr, "\n"); } } static void soapyShowAllDevices() { size_t length = 0; SoapySDRKwargs *results = SoapySDRDevice_enumerate(NULL, &length); soapyShowDevices(results, length); SoapySDRKwargsList_clear(results, length); } bool soapyOpen(void) { size_t length = 0; SoapySDRKwargs *results = SoapySDRDevice_enumerateStrArgs(Modes.dev_name ? Modes.dev_name : "", &length); if (length == 0) { SoapySDRKwargsList_clear(results, length); fprintf(stderr, "soapy: no matching devices found; available devices:\n"); soapyShowAllDevices(); return false; } if (length > 1) { fprintf(stderr, "soapy: more than one matching device found; matching devices:\n"); soapyShowDevices(results, length); SoapySDRKwargsList_clear(results, length); fprintf(stderr, "soapy: please select a single device with --device\n"); return false; } fprintf(stderr, "soapy: selected device: "); for (size_t j = 0; j < results[0].size; j++) { if (j) fprintf(stderr, ", "); fprintf(stderr, "%s=%s", results[0].keys[j], results[0].vals[j]); } fprintf(stderr, "\n"); SoapySDRKwargsList_clear(results, length); SOAPY.dev = SoapySDRDevice_makeStrArgs(Modes.dev_name ? Modes.dev_name : ""); if (!SOAPY.dev) { fprintf(stderr, "soapy: failed to create device: %s\n", SoapySDRDevice_lastError()); return false; } SoapySDRKwargs result = SoapySDRDevice_getHardwareInfo(SOAPY.dev); if (result.size > 0) { fprintf(stderr, "soapy: hardware info: "); for (size_t i = 0; i < result.size; ++i) { if (i) fprintf(stderr, ", "); fprintf(stderr, "%s=%s", result.keys[i], result.vals[i]); } fprintf(stderr, "\n"); } SoapySDRKwargs_clear(&result); char* driver_key = SoapySDRDevice_getDriverKey(SOAPY.dev); if (driver_key) { fprintf(stderr, "soapy: driver key: %s\n", driver_key); // Apply driver-specific defaults if (!strcmp(driver_key, "SDRplay")) { // Default to 5MHz bandwidth if (SOAPY.bandwidth == 0) SOAPY.bandwidth = 5.0e6; } SoapySDR_free(driver_key); } char* hw_key = SoapySDRDevice_getHardwareKey(SOAPY.dev); if (hw_key) { fprintf(stderr, "soapy: hardware key: %s\n", hw_key); SoapySDR_free(hw_key); } // // Apply generic defaults // if (SOAPY.bandwidth == 0) { SOAPY.bandwidth = 3.0e6; } // // Configure everything // if (SOAPY.channel) { size_t supported_channels = SoapySDRDevice_getNumChannels(SOAPY.dev, SOAPY_SDR_RX); if (SOAPY.channel >= supported_channels) { fprintf(stderr, "soapy: device only supports %zu channels, not %zu\n", supported_channels, SOAPY.channel + 1); goto error; } } if (SoapySDRDevice_setSampleRate(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, Modes.sample_rate) != 0) { fprintf(stderr, "soapy: setSampleRate failed: %s\n", SoapySDRDevice_lastError()); goto error; } if (SOAPY.antenna && SoapySDRDevice_setAntenna(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, SOAPY.antenna) != 0) { fprintf(stderr, "soapy: setAntenna(%s) failed: %s\n", SOAPY.antenna, SoapySDRDevice_lastError()); length = 0; char** available_antennas = SoapySDRDevice_listAntennas(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, &length); fprintf(stderr, "soapy: available antennas: "); for (size_t i = 0; i < length; i++) { fprintf(stderr, "%s", available_antennas[i]); if (i+1 < length) fprintf(stderr, ", "); } fprintf(stderr, "\n"); if (available_antennas) SoapySDRStrings_clear(&available_antennas, length); goto error; } if (SoapySDRDevice_setFrequency(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, Modes.freq, NULL) != 0) { fprintf(stderr, "soapy: setFrequency failed: %s\n", SoapySDRDevice_lastError()); goto error; } SOAPY.gain_range = SoapySDRDevice_getGainRange(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel); if (SOAPY.gain_range.step <= 0) SOAPY.gain_range.step = 1.0; else if (SOAPY.gain_range.step <= 0.1) SOAPY.gain_range.step = 0.1; bool has_agc = SoapySDRDevice_hasGainMode(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel); if (SOAPY.enable_agc) { if (has_agc) { if (SoapySDRDevice_setGainMode(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, 1) != 0) { fprintf(stderr, "soapy: setGainMode failed: %s\n", SoapySDRDevice_lastError()); goto error; }else { fprintf(stdout, "soapy: AGC enabled!\n"); } } else { fprintf(stderr, "soapy: device does not support enabling AGC\n"); goto error; } } else { // Manual gain path if (has_agc) { if (SoapySDRDevice_setGainMode(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, 0) != 0) { fprintf(stderr, "soapy: setGainMode failed: %s\n", SoapySDRDevice_lastError()); goto error; } } //double gain = (Modes.gain == MODES_DEFAULT_GAIN ? SOAPY.gain_range.maximum : Modes.gain); double gain = (Modes.gain == MODES_MAX_GAIN ? SOAPY.gain_range.maximum : Modes.gain); if (SoapySDRDevice_setGain(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, gain) < 0) { fprintf(stderr, "soapy: setGain(%.1fdB) failed\n", gain); goto error; } for (int i = 0; i < SOAPY.num_gain_elements; ++i) { char *element = SOAPY.gain_elements[i]; char *sep = strchr(element, ':'); printf("number gains: %i\n", SOAPY.num_gain_elements); // hier gehts nicht mehr weiter if (!sep || !sep[1]) { fprintf(stderr, "soapy: don't understand a gain element setting of '%s' (should be formatted as :)\n", element); goto error; } *sep = 0; char *endptr = NULL; double gain = strtod(sep + 1, &endptr); if ((gain == 0 && !endptr) || *endptr) { fprintf(stderr, "soapy: don't understand a gain value of '%s' for gain element %s (should be a floating-point gain in dB)\n", sep + 1, element); goto error; } if (SoapySDRDevice_setGainElement(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, element, gain) != 0) { fprintf(stderr, "soapy: setGainElement(%s,%.1fdB) failed: %s\n", element, gain, SoapySDRDevice_lastError()); goto error; } } } SOAPY.current_gain_step = (int) round( (SoapySDRDevice_getGain(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel) - SOAPY.gain_range.minimum) / SOAPY.gain_range.step ); //if (Modes.adaptive_range_target == 0) // Modes.adaptive_range_target = (SOAPY.gain_range.maximum - SOAPY.gain_range.minimum) * 0.6; // just a wild guess if (SoapySDRDevice_setBandwidth(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, SOAPY.bandwidth) != 0) { fprintf(stderr, "soapy: setBandwidth(%.1f MHz) failed: %s\n", SOAPY.bandwidth / 1e6, SoapySDRDevice_lastError()); goto error; } // // Done configuring, report the final device state // fprintf(stderr, "soapy: total gain: %.1fdB", SoapySDRDevice_getGain(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel)); char** gain_elements = SoapySDRDevice_listGains(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, &length); if (gain_elements) { for (size_t i = 0; i < length; i++) { fprintf(stderr, "; %s=%.1fdB", gain_elements[i], SoapySDRDevice_getGainElement(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, gain_elements[i])); } SoapySDRStrings_clear(&gain_elements, length); } fprintf(stderr, "\n"); fprintf(stderr, "soapy: frequency: %.1f MHz\n", SoapySDRDevice_getFrequency(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel) / 1e6); fprintf(stderr, "soapy: sample rate: %.1f MHz\n", SoapySDRDevice_getSampleRate(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel) / 1e6); fprintf(stderr, "soapy: bandwidth: %.1f MHz\n", SoapySDRDevice_getBandwidth(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel) / 1e6); if (has_agc) { fprintf(stderr, "soapy: AGC mode: %s\n", SoapySDRDevice_getGainMode(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel) ? "automatic" : "manual"); } char* antenna = SoapySDRDevice_getAntenna(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel); if (antenna) { fprintf(stderr, "soapy: antenna: %s\n", antenna); SoapySDR_free(antenna); } if (SoapySDRDevice_hasDCOffset(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel)) { fprintf(stderr, "soapy: DC offset mode: %s\n", SoapySDRDevice_getDCOffsetMode(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel) ? "automatic" : "manual"); double offsetI; double offsetQ; if (!SoapySDRDevice_getDCOffset(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, &offsetI, &offsetQ)) { fprintf(stderr, "soapy: DC offset: I=%.3f Q=%.3f\n", offsetI, offsetQ); } } if (SoapySDRDevice_hasIQBalance(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel)) { #ifdef SOAPY_SDR_API_HAS_IQ_BALANCE_MODE fprintf(stderr, "soapy: IQ balance mode: %s\n", SoapySDRDevice_getIQBalanceMode(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel) ? "automatic" : "manual"); #endif double balanceI; double balanceQ; if (!SoapySDRDevice_getIQBalance(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, &balanceI, &balanceQ)) { fprintf(stderr, "soapy: IQ balance is I=%.1f, Q=%.1f\n", balanceI, balanceQ); } } if (SoapySDRDevice_hasFrequencyCorrection(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel)) { fprintf(stderr, "soapy: freq correction: %.1f ppm\n", SoapySDRDevice_getFrequencyCorrection(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel)); } size_t channels[1] = { SOAPY.channel }; SoapySDRKwargs stream_args = { 0, NULL, NULL }; if ((SOAPY.stream = SoapySDRDevice_setupStream(SOAPY.dev, SOAPY_SDR_RX, SOAPY_SDR_CS16, channels, 1, &stream_args)) == NULL) { fprintf(stderr, "soapy: setupStream failed: %s\n", SoapySDRDevice_lastError()); goto error; } SOAPY.converter = init_converter(INPUT_SC16, Modes.sample_rate, Modes.dc_filter, &SOAPY.converter_state); if (!SOAPY.converter) { fprintf(stderr, "soapy: can't initialize sample converter\n"); goto error; } return true; error: if (SOAPY.dev != NULL) { SoapySDRDevice_unmake(SOAPY.dev); SOAPY.dev = NULL; } return false; } void soapyRun() { if (!SOAPY.dev) { return; } start_cpu_timing(&thread_cpu); if (SoapySDRDevice_activateStream(SOAPY.dev, SOAPY.stream, 0, 0, 0) != 0) { fprintf(stderr, "soapy: activateStream failed: %s\n", SoapySDRDevice_lastError()); return; } struct mag_buf *outbuf; struct mag_buf *lastbuf; uint32_t slen; unsigned next_free_buffer; unsigned free_bufs; int64_t block_duration; static int dropping = 0; static uint64_t sampleCounter = 0; static int antiSpam; static int antiSpam2; uint8_t* buf; const int buffer_elements = Modes.sdr_buf_samples; buf = malloc(buffer_elements * 4); while (!Modes.exit) { int flags; long long timeNs; int32_t samples_read = SoapySDRDevice_readStream(SOAPY.dev, SOAPY.stream, (void *) &buf, buffer_elements, &flags, &timeNs, 5000000); if (samples_read == 0) { usleep(500); continue; } if (samples_read < 0) { fprintf(stderr, "soapy: readStream failed: %s\n", SoapySDRDevice_lastError()); break; } int64_t sysMicroseconds = mono_micro_seconds(); int64_t sysTimestamp = mstime(); // Lock the data buffer variables before accessing them lockReader(); next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS; outbuf = &Modes.mag_buffers[Modes.first_free_buffer]; lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS]; free_bufs = (Modes.first_filled_buffer - next_free_buffer + MODES_MAG_BUFFERS) % MODES_MAG_BUFFERS; unlockReader(); slen = (uint32_t) samples_read; if (slen != Modes.sdr_buf_samples) { fprintf(stderr, "weirdness: soapysdr gave us a block with an unusual size (got %u samples, expected %u samples)\n", (unsigned) slen, (unsigned) Modes.sdr_buf_samples); if (slen > Modes.sdr_buf_samples) { // wat?! Discard the start. unsigned discard = slen - Modes.sdr_buf_samples; outbuf->dropped += discard; buf += discard * 2; slen -= discard * 2; } } if (free_bufs == 0 || (dropping && free_bufs < MODES_MAG_BUFFERS / 2)) { // FIFO is full. Drop this block. dropping = 1; outbuf->dropped += slen; sampleCounter += slen; // make extra sure that the decode thread isn't sleeping wakeDecode(); if (--antiSpam <= 0 && !Modes.exit) { fprintf(stderr, "FIFO dropped, suppressing this message for 30 seconds.\n"); antiSpam = 300; } continue; } dropping = false; // Compute the sample timestamp and system timestamp for the start of the block outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate; sampleCounter += slen; if (Modes.debug_sampleCounter && --antiSpam2 <= 0) { fprintf(stderr, "sampleTimestamp: %020llu\n", (unsigned long long) outbuf->sampleTimestamp); antiSpam2 = 3000; } // Get the approx system time for the start of this block block_duration = 1e3 * slen / Modes.sample_rate; outbuf->sysTimestamp = sysTimestamp; outbuf->sysMicroseconds = sysMicroseconds; outbuf->sysTimestamp -= block_duration; outbuf->sysMicroseconds -= block_duration * 1000; // Copy trailing data from last block (or reset if not valid) if (outbuf->dropped == 0) { memcpy(outbuf->data, lastbuf->data + lastbuf->length, Modes.trailing_samples * sizeof (uint16_t)); } else { memset(outbuf->data, 0, Modes.trailing_samples * sizeof (uint16_t)); } // Convert the new data outbuf->length = slen; SOAPY.converter(buf, &outbuf->data[Modes.trailing_samples], slen, SOAPY.converter_state, &outbuf->mean_level, &outbuf->mean_power); // Push the new data to the demodulation thread lockReader(); Modes.mag_buffers[next_free_buffer].dropped = 0; Modes.mag_buffers[next_free_buffer].length = 0; // just in case Modes.first_free_buffer = next_free_buffer; // accumulate CPU while holding the mutex, and restart measurement end_cpu_timing(&thread_cpu, &Modes.reader_cpu_accumulator); start_cpu_timing(&thread_cpu); wakeDecode(); unlockReader(); } } void soapyClose() { if (SOAPY.stream) { SoapySDRDevice_closeStream(SOAPY.dev, SOAPY.stream); SOAPY.stream = NULL; } if (SOAPY.dev) { SoapySDRDevice_unmake(SOAPY.dev); SOAPY.dev = NULL; } if (SOAPY.converter) { cleanup_converter(&SOAPY.converter_state); SOAPY.converter = NULL; SOAPY.converter_state = NULL; } for (int i = 0; i < SOAPY.num_gain_elements; ++i) { free(SOAPY.gain_elements[i]); } free(SOAPY.gain_elements); SOAPY.gain_elements = NULL; } // // This is a bit horrible; since soapy doesn't tell us the actual device steps, // we track the last requested gain step ourselves and always report that as // the current step. Otherwise adaptive gain will get confused if there are // any step values that can't actually be set, i.e.: // // current gain is at step 4 // adaptive wants to increase gain, set gain = current + 1 = 5 // request gain 5, hardware can't actually do that, nearest is step 4 // adaptive wants to increase gain further, set gain = current + 1 = 5 // we make no progress int soapyGetGain() { return SOAPY.current_gain_step; } int soapyGetMaxGain() { return (int) ceil( (SOAPY.gain_range.maximum - SOAPY.gain_range.minimum) / SOAPY.gain_range.step ); } double soapyGetGainDb(int step) { double gain = SOAPY.gain_range.minimum + step * SOAPY.gain_range.step; if (gain < SOAPY.gain_range.minimum) gain = SOAPY.gain_range.minimum; if (gain > SOAPY.gain_range.maximum) gain = SOAPY.gain_range.maximum; return gain; } int soapySetGain(int step) { double gainDb = soapyGetGainDb(step); if (SoapySDRDevice_setGain(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, gainDb) != 0) { fprintf(stderr, "soapy: setGain(%.1fdB) failed: %s\n", gainDb, SoapySDRDevice_lastError()); return -1; } fprintf(stderr, "soapy: total gain set to %.1fdB", SoapySDRDevice_getGain(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel)); size_t length = 0; char** gain_elements = SoapySDRDevice_listGains(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, &length); for (size_t i = 0; i < length; i++) { fprintf(stderr, "; %s=%.1fdB", gain_elements[i], SoapySDRDevice_getGainElement(SOAPY.dev, SOAPY_SDR_RX, SOAPY.channel, gain_elements[i])); } if (gain_elements) SoapySDRStrings_clear(&gain_elements, length); fprintf(stderr, "\n"); SOAPY.current_gain_step = step; return step; } readsb-3.16/sdr_soapy.h000066400000000000000000000022231505057307600150660ustar00rootroot00000000000000// Part of dump1090, a Mode S message decoder for RTLSDR devices. // // sdr_soapy.h: SoapySDR dongle support (header) // // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // // This file is free software: you may copy, redistribute and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 2 of the License, or (at your // option) any later version. // // This file 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 . #ifndef SDR_SOAPY_H #define SDR_SOAPY_H void soapyInitConfig(); void soapyShowHelp(); bool soapyOpen(); void soapyRun(); void soapyClose(); bool soapyHandleOption(int argc, char *argv); int soapyGetGain(); int soapyGetMaxGain(); double soapyGetGainDb(int step); int soapySetGain(int step); #endifreadsb-3.16/sdr_ubladerf.c000066400000000000000000000473221505057307600155230ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_ubladerf.c: bladeRF 2.0 Micro support // // Copyright (c) 2019 Michael Wolf // // This file 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 // any later version. // // This file 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 . #include "readsb.h" #include "sdr_ubladerf.h" #include #include static struct { const char *device_str; const char *fpga_path; unsigned decimation; bladerf_lpf_mode lpf_mode; unsigned lpf_bandwidth; unsigned block_size; struct bladerf *device; iq_convert_fn converter; struct converter_state *converter_state; } uBladeRF; void ubladeRFInitConfig() { uBladeRF.device_str = NULL; uBladeRF.fpga_path = NULL; uBladeRF.decimation = 1; uBladeRF.lpf_mode = BLADERF_LPF_NORMAL; uBladeRF.lpf_bandwidth = 1750000; uBladeRF.device = NULL; } bool ubladeRFHandleOption(int key, char *arg) { switch (key) { case OptBladeFpgaDir: uBladeRF.fpga_path = strdup(arg); break; case OptBladeDecim: uBladeRF.decimation = atoi(arg); break; case OptBladeBw: if (!strcasecmp(arg, "bypass")) { uBladeRF.lpf_mode = BLADERF_LPF_BYPASSED; } else { uBladeRF.lpf_mode = BLADERF_LPF_NORMAL; uBladeRF.lpf_bandwidth = atoi(arg); } break; default: return false; } return true; } static void show_config() { int status; #if defined(LIBBLADERF_API_VERSION) && (LIBBLADERF_API_VERSION >= 0x02020000) bladerf_sample_rate rate; bladerf_frequency freq; bladerf_gain gain; bladerf_bandwidth bw; #else unsigned rate; unsigned freq; unsigned bw; int gain; #endif bladerf_lpf_mode lpf_mode; int16_t lms_dc_i, lms_dc_q; int16_t fpga_phase, fpga_gain; struct bladerf_lms_dc_cals dc_cals; bool biastee; if ((status = bladerf_get_sample_rate(uBladeRF.device, BLADERF_MODULE_RX, &rate)) < 0) { fprintf(stderr, "bladeRF: couldn't read back device sample rate: %s\n", bladerf_strerror(status)); return; } if ((status = bladerf_get_frequency(uBladeRF.device, BLADERF_MODULE_RX, &freq)) < 0) { fprintf(stderr, "bladeRF: couldn't read back device frequency: %s\n", bladerf_strerror(status)); return; } if ((status = bladerf_get_bandwidth(uBladeRF.device, BLADERF_MODULE_RX, &bw)) < 0) { fprintf(stderr, "bladeRF: couldn't read back device bandwidth: %s\n", bladerf_strerror(status)); return; } if ((status = bladerf_get_gain(uBladeRF.device, BLADERF_MODULE_RX, &gain)) < 0) { fprintf(stderr, "bladeRF: couldn't read back device gain: %s\n", bladerf_strerror(status)); } if ((status = bladerf_get_correction(uBladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_LMS_DCOFF_I, &lms_dc_i)) < 0 || (status = bladerf_get_correction(uBladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_LMS_DCOFF_Q, &lms_dc_q)) < 0 || (status = bladerf_get_correction(uBladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_FPGA_PHASE, &fpga_phase)) < 0 || (status = bladerf_get_correction(uBladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_FPGA_GAIN, &fpga_gain)) < 0 ) { fprintf(stderr, "bladeRF: couldn't read back device configuration (correction values)\n"); //return; } if (!strcmp("bladerf1", bladerf_get_board_name(uBladeRF.device))) { if ((status = bladerf_get_lpf_mode(uBladeRF.device, BLADERF_MODULE_RX, &lpf_mode)) < 0 || (status = bladerf_lms_get_dc_cals(uBladeRF.device, &dc_cals)) < 0) { fprintf(stderr, "bladeRF: couldn't read back device configuration (BladeRF 1 values)\n"); return; } } if (!strcmp("bladerf2", bladerf_get_board_name(uBladeRF.device))) { if ((status = bladerf_get_bias_tee(uBladeRF.device, BLADERF_CHANNEL_RX(0), &biastee)) < 0) { fprintf(stderr, "bladeRF: couldn't read back BladeRF Micro bias tee configuration\n"); } } fprintf(stderr, "bladeRF: sampling rate: %.1f MHz\n", rate / 1e6); fprintf(stderr, "bladeRF: frequency: %.1f MHz\n", freq / 1e6); fprintf(stderr, "bladeRF: gain: %ddB\n", gain); fprintf(stderr, "bladeRF: biastee: %d\n", (int)biastee); switch (lpf_mode) { case BLADERF_LPF_NORMAL: fprintf(stderr, "bladeRF: LPF bandwidth: %.2f MHz\n", bw/1e6); break; case BLADERF_LPF_BYPASSED: fprintf(stderr, "bladeRF: LPF bypassed\n"); break; case BLADERF_LPF_DISABLED: fprintf(stderr, "bladeRF: LPF disabled\n"); break; default: fprintf(stderr, "bladeRF: LPF in unknown state\n"); break; } fprintf(stderr, "bladeRF: calibration settings:\n"); fprintf(stderr, " LMS DC adjust: I=%d Q=%d\n", lms_dc_i, lms_dc_q); fprintf(stderr, " FPGA phase adjust: %+.3f degrees\n", fpga_phase * 10.0 / 4096); fprintf(stderr, " FPGA gain adjust: %+.3f\n", fpga_gain * 1.0 / 4096); fprintf(stderr, " LMS LPF tuning: %d\n", dc_cals.lpf_tuning); fprintf(stderr, " LMS RX LPF filter: I=%d Q=%d\n", dc_cals.rx_lpf_i, dc_cals.rx_lpf_q); fprintf(stderr, " LMS RXVGA2 DC ref: %d\n", dc_cals.dc_ref); fprintf(stderr, " LMS RXVGA2A: I=%d Q=%d\n", dc_cals.rxvga2a_i, dc_cals.rxvga2a_q); fprintf(stderr, " LMS RXVGA2B: I=%d Q=%d\n", dc_cals.rxvga2b_i, dc_cals.rxvga2b_q); } bool ubladeRFOpen() { if (uBladeRF.device) { return true; } int status; bladerf_set_usb_reset_on_open(true); fprintf(stderr, "Opening BladeRF: %s\n", Modes.dev_name); if ((status = bladerf_open(&uBladeRF.device, Modes.dev_name)) < 0) { fprintf(stderr, "Failed to open bladeRF: %s\n", bladerf_strerror(status)); goto error; } const char *fpga_path; if (uBladeRF.fpga_path) { fpga_path = uBladeRF.fpga_path; } else { bladerf_fpga_size size; if ((status = bladerf_get_fpga_size(uBladeRF.device, &size)) < 0) { fprintf(stderr, "bladerf_get_fpga_size failed: %s\n", bladerf_strerror(status)); goto error; } switch (size) { case BLADERF_FPGA_40KLE: fpga_path = "/usr/share/Nuand/bladeRF/hostedx40.rbf"; break; case BLADERF_FPGA_115KLE: fpga_path = "/usr/share/Nuand/bladeRF/hostedx115.rbf"; break; case BLADERF_FPGA_A4: fpga_path = "/usr/share/Nuand/bladeRF/hostedxA4.rbf"; break; default: fprintf(stderr, "bladeRF: unknown FPGA size, skipping FPGA load"); fpga_path = NULL; break; } } if (fpga_path && fpga_path[0]) { fprintf(stderr, "bladeRF: loading FPGA bitstream from %s\n", fpga_path); if ((status = bladerf_load_fpga(uBladeRF.device, fpga_path)) < 0) { fprintf(stderr, "bladerf_load_fpga() failed: %s\n", bladerf_strerror(status)); goto error; } } switch (bladerf_device_speed(uBladeRF.device)) { case BLADERF_DEVICE_SPEED_HIGH: uBladeRF.block_size = 1024; break; case BLADERF_DEVICE_SPEED_SUPER: uBladeRF.block_size = 2048; break; default: fprintf(stderr, "couldn't determine bladerf device speed\n"); goto error; } // Close and re-open the bladeRF, otherwise we get "An unexpected error occurred" in later calls. bladerf_close(uBladeRF.device); if ((status = bladerf_open(&uBladeRF.device, Modes.dev_name)) < 0) { fprintf(stderr, "Failed to open bladeRF: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_set_sample_rate(uBladeRF.device, BLADERF_MODULE_RX, Modes.sample_rate * uBladeRF.decimation, NULL)) < 0) { fprintf(stderr, "bladerf_set_sample_rate failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_set_frequency(uBladeRF.device, BLADERF_MODULE_RX, Modes.freq)) < 0) { fprintf(stderr, "bladerf_set_frequency failed: %s\n", bladerf_strerror(status)); goto error; } if (!strcmp("bladerf1", bladerf_get_board_name(uBladeRF.device))) { if ((status = bladerf_set_lpf_mode(uBladeRF.device, BLADERF_MODULE_RX, uBladeRF.lpf_mode)) < 0) { fprintf(stderr, "bladerf_set_lpf_mode failed: %s\n", bladerf_strerror(status)); goto error; } } if ((status = bladerf_set_bandwidth(uBladeRF.device, BLADERF_MODULE_RX, uBladeRF.lpf_bandwidth, NULL)) < 0) { fprintf(stderr, "bladerf_set_bandwidth failed: %s\n", bladerf_strerror(status)); goto error; } /* turn the tx gain right off, just in case */ if ((status = bladerf_set_gain(uBladeRF.device, BLADERF_MODULE_TX, -100)) < 0) { fprintf(stderr, "bladerf_set_gain(TX) failed: %s\n", bladerf_strerror(status)); goto error; } /* Gain = -100 is AGC */ if (Modes.gain == -100) { fprintf(stderr, "BladeRF: using AGC\n"); /* Note: we should really query the BladeRF library to find out what modes we are allowed to use */ if ((status = bladerf_set_gain_mode(uBladeRF.device, BLADERF_MODULE_RX, BLADERF_GAIN_DEFAULT)) < 0) { fprintf(stderr, "bladerf_set_gain_mode to default/AGC failed: %s\n", bladerf_strerror(status)); } } else { if ((status = bladerf_set_gain_mode(uBladeRF.device, BLADERF_MODULE_RX, BLADERF_GAIN_MGC)) < 0) { fprintf(stderr, "bladerf_set_gain_mode to manual failed: %s\n", bladerf_strerror(status)); } fprintf(stderr, "BladeRF: setting manual gain to %d\n", Modes.gain / 10); if ((status = bladerf_set_gain(uBladeRF.device, BLADERF_MODULE_RX, Modes.gain / 10)) < 0) { fprintf(stderr, "bladerf_set_gain(RX) failed: %s\n", bladerf_strerror(status)); goto error; } } if (!strcmp("bladerf2", bladerf_get_board_name(uBladeRF.device))) { if (Modes.biastee) { // Note: the BladeRF micro enables/disables on both RX channels at the same time fprintf(stderr, "Enabling Bias on RX channels\n"); if ((status = bladerf_set_bias_tee(uBladeRF.device, BLADERF_CHANNEL_RX(0), true)) < 0) { fprintf(stderr, "bladerf_set_bias_tee failed for channel 0: %s\n", bladerf_strerror(status)); } } } if (!strcmp("bladerf1", bladerf_get_board_name(uBladeRF.device))) { if ((status = bladerf_set_loopback(uBladeRF.device, BLADERF_LB_NONE)) < 0) { fprintf(stderr, "bladerf_set_loopback() failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_calibrate_dc(uBladeRF.device, BLADERF_DC_CAL_LPF_TUNING)) < 0) { fprintf(stderr, "bladerf_calibrate_dc(LPF_TUNING) failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_calibrate_dc(uBladeRF.device, BLADERF_DC_CAL_RX_LPF)) < 0) { fprintf(stderr, "bladerf_calibrate_dc(RX_LPF) failed: %s\n", bladerf_strerror(status)); goto error; } if ((status = bladerf_calibrate_dc(uBladeRF.device, BLADERF_DC_CAL_RXVGA2)) < 0) { fprintf(stderr, "bladerf_calibrate_dc(RXVGA2) failed: %s\n", bladerf_strerror(status)); goto error; } } show_config(); uBladeRF.converter = init_converter(INPUT_SC16Q11, Modes.sample_rate, Modes.dc_filter, &uBladeRF.converter_state); if (!uBladeRF.converter) { fprintf(stderr, "can't initialize sample converter\n"); goto error; } return true; error: if (uBladeRF.device) { bladerf_close(uBladeRF.device); uBladeRF.device = NULL; } return false; } static struct timespec thread_cpu; static unsigned timeouts = 0; static void *handle_bladerf_samples(struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta, void *samples, size_t num_samples, void *user_data) { static uint64_t nextTimestamp = 0; static bool dropping = false; int64_t sysMicroseconds = mono_micro_seconds(); int64_t sysTimestamp = mstime(); MODES_NOTUSED(dev); MODES_NOTUSED(stream); MODES_NOTUSED(meta); MODES_NOTUSED(user_data); MODES_NOTUSED(num_samples); lockReader(); if (Modes.exit) { unlockReader(); return BLADERF_STREAM_SHUTDOWN; } unsigned next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS; struct mag_buf *outbuf = &Modes.mag_buffers[Modes.first_free_buffer]; struct mag_buf *lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS]; unsigned free_bufs = (Modes.first_filled_buffer - next_free_buffer + MODES_MAG_BUFFERS) % MODES_MAG_BUFFERS; if (free_bufs == 0 || (dropping && free_bufs < MODES_MAG_BUFFERS / 2)) { // FIFO is full. Drop this block. dropping = true; unlockReader(); return samples; } dropping = false; unlockReader(); outbuf->sysTimestamp = sysTimestamp; outbuf->sysMicroseconds = sysMicroseconds; // Copy trailing data from last block (or reset if not valid) if (outbuf->dropped == 0) { memcpy(outbuf->data, lastbuf->data + lastbuf->length, Modes.trailing_samples * sizeof (uint16_t)); } else { memset(outbuf->data, 0, Modes.trailing_samples * sizeof (uint16_t)); } // start handling metadata blocks outbuf->dropped = 0; outbuf->length = 0; outbuf->mean_level = outbuf->mean_power = 0; unsigned blocks_processed = 0; unsigned samples_per_block = (uBladeRF.block_size - 16) / 4; static bool overrun = true; // ignore initial overruns as we get up to speed static bool first_buffer = true; for (unsigned offset = 0; offset < Modes.sdr_buf_samples * 4; offset += uBladeRF.block_size) { // read the next metadata header uint8_t *header = ((uint8_t*) samples) + offset; uint64_t metadata_magic = le32toh(*(uint32_t*) (header)); uint64_t metadata_timestamp = le64toh(*(uint64_t*) (header + 4)); uint32_t metadata_flags = le32toh(*(uint32_t*) (header + 12)); void *sample_data = header + 16; if (metadata_magic != 0x12344321) { // first buffer is often in the wrong mode if (!first_buffer) { fprintf(stderr, "bladeRF: wrong metadata header magic value, skipping rest of buffer\n"); } break; } if (metadata_flags & BLADERF_META_STATUS_OVERRUN) { if (!overrun) { fprintf(stderr, "bladeRF: receive overrun\n"); } overrun = true; } else { overrun = false; } #ifndef BROKEN_FPGA_METADATA // this needs a fixed decimating FPGA image that handles the timestamp correctly if (nextTimestamp && nextTimestamp != metadata_timestamp) { // dropped data or lost sync. start again. if (metadata_timestamp > nextTimestamp) outbuf->dropped += (metadata_timestamp - nextTimestamp); outbuf->dropped += outbuf->length; outbuf->length = 0; blocks_processed = 0; outbuf->mean_level = outbuf->mean_power = 0; nextTimestamp = metadata_timestamp; } #else MODES_NOTUSED(metadata_timestamp); #endif if (!blocks_processed) { // Compute the sample timestamp for the start of the block outbuf->sampleTimestamp = nextTimestamp * 12e6 / Modes.sample_rate / uBladeRF.decimation; } // Convert a block of data double mean_level, mean_power; uBladeRF.converter(sample_data, &outbuf->data[Modes.trailing_samples + outbuf->length], samples_per_block, uBladeRF.converter_state, &mean_level, &mean_power); outbuf->length += samples_per_block; outbuf->mean_level += mean_level; outbuf->mean_power += mean_power; nextTimestamp += samples_per_block * uBladeRF.decimation; ++blocks_processed; timeouts = 0; } first_buffer = false; if (blocks_processed) { // Get the approx system time for the start of this block int64_t block_duration = 1e3 * outbuf->length / Modes.sample_rate; outbuf->sysTimestamp -= block_duration; outbuf->sysMicroseconds -= 1000 * block_duration; outbuf->mean_level /= blocks_processed; outbuf->mean_power /= blocks_processed; // Push the new data to the demodulation thread lockReader(); // accumulate CPU while holding the mutex, and restart measurement end_cpu_timing(&thread_cpu, &Modes.reader_cpu_accumulator); start_cpu_timing(&thread_cpu); Modes.mag_buffers[next_free_buffer].dropped = 0; Modes.mag_buffers[next_free_buffer].length = 0; // just in case Modes.first_free_buffer = next_free_buffer; wakeDecode(); unlockReader(); } return samples; } void ubladeRFRun() { if (!uBladeRF.device) { return; } unsigned transfers = 7; int status; struct bladerf_stream *stream = NULL; void **buffers = NULL; if ((status = bladerf_init_stream(&stream, uBladeRF.device, handle_bladerf_samples, &buffers, /* num_buffers */ transfers, BLADERF_FORMAT_SC16_Q11_META, /* samples_per_buffer */ Modes.sdr_buf_samples, /* num_transfers */ transfers, /* user_data */ NULL)) < 0) { fprintf(stderr, "bladerf_init_stream() failed: %s\n", bladerf_strerror(status)); goto out; } unsigned ms_per_transfer = 1000 * Modes.sdr_buf_samples / Modes.sample_rate; if ((status = bladerf_set_stream_timeout(uBladeRF.device, BLADERF_MODULE_RX, ms_per_transfer * (transfers + 2))) < 0) { fprintf(stderr, "bladerf_set_stream_timeout() failed: %s\n", bladerf_strerror(status)); goto out; } if ((status = bladerf_enable_module(uBladeRF.device, BLADERF_MODULE_RX, true) < 0)) { fprintf(stderr, "bladerf_enable_module(RX, true) failed: %s\n", bladerf_strerror(status)); goto out; } start_cpu_timing(&thread_cpu); timeouts = 0; // reset to zero when we get a callback with some data retry: if ((status = bladerf_stream(stream, BLADERF_MODULE_RX)) < 0) { fprintf(stderr, "bladerf_stream() failed: %s\n", bladerf_strerror(status)); if (status == BLADERF_ERR_TIMEOUT) { if (++timeouts < 5) goto retry; fprintf(stderr, "bladerf is wedged, giving up.\n"); } goto out; } out: if ((status = bladerf_enable_module(uBladeRF.device, BLADERF_MODULE_RX, false) < 0)) { fprintf(stderr, "bladerf_enable_module(RX, false) failed: %s\n", bladerf_strerror(status)); } if (stream) { bladerf_deinit_stream(stream); } } void ubladeRFClose() { if (uBladeRF.converter) { cleanup_converter(&uBladeRF.converter_state); uBladeRF.converter = NULL; } if (uBladeRF.device) { bladerf_close(uBladeRF.device); uBladeRF.device = NULL; } } readsb-3.16/sdr_ubladerf.h000066400000000000000000000021371505057307600155230ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_bladerf.h: bladeRF support (header) // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2017 FlightAware LLC // // This file 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 // any later version. // // This file 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 . #ifndef UBLADERF_H #define UBLADERF_H // Support for the Nuand bladeRF SDR void ubladeRFInitConfig (); bool ubladeRFHandleOption (int argc, char *argv); bool ubladeRFOpen (); void ubladeRFRun (); void ubladeRFClose (); #endif readsb-3.16/stats.c000066400000000000000000001353541505057307600142300ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // stats.c: statistics helpers. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "readsb.h" void add_timespecs(const struct timespec *x, const struct timespec *y, struct timespec *z) { z->tv_sec = x->tv_sec + y->tv_sec; z->tv_nsec = x->tv_nsec + y->tv_nsec; z->tv_sec += z->tv_nsec / 1000000000L; z->tv_nsec = z->tv_nsec % 1000000000L; } static void display_range_histogram(struct stats *st); void display_stats(struct stats *st) { int j; time_t tt_start, tt_end; struct tm tm_start, tm_end; char tb_start[30], tb_end[30]; printf("\n\n"); tt_start = st->start / 1000; localtime_r(&tt_start, &tm_start); strftime(tb_start, sizeof (tb_start), "%c %Z", &tm_start); tt_end = st->end / 1000; localtime_r(&tt_end, &tm_end); strftime(tb_end, sizeof (tb_end), "%c %Z", &tm_end); printf("Statistics: %s - %s\n", tb_start, tb_end); if (Modes.sdr_type != SDR_NONE) { printf("Local receiver:\n"); printf(" %llu samples processed\n", (unsigned long long) st->samples_processed); printf(" %llu samples dropped\n", (unsigned long long) st->samples_dropped); printf(" %llu samples lost\n", (unsigned long long) st->samples_lost); printf(" %u Mode A/C messages received\n", st->demod_modeac); printf(" %u Mode-S message preambles received\n", st->demod_preambles); printf(" %u with bad message format or invalid CRC\n", st->demod_rejected_bad); printf(" %u with unrecognized ICAO address\n", st->demod_rejected_unknown_icao); printf(" %u accepted with correct CRC\n", st->demod_accepted[0]); for (j = 1; j <= Modes.nfix_crc; ++j) printf(" %u accepted with %d-bit error repaired\n", st->demod_accepted[j], j); if (st->noise_power_sum > 0 && st->noise_power_count > 0) { printf(" %.1f dBFS noise power\n", 10 * log10(st->noise_power_sum / st->noise_power_count)); } if (st->signal_power_sum > 0 && st->signal_power_count > 0) { printf(" %.1f dBFS mean signal power\n", 10 * log10(st->signal_power_sum / st->signal_power_count)); } if (st->peak_signal_power > 0) { printf(" %.1f dBFS peak signal power\n", 10 * log10(st->peak_signal_power)); } printf(" %u messages with signal power above -3dBFS\n", st->strong_signal_count); printf("\n Phase stats"); printf("\n "); for (int i = 0; i < 5; i++) printf(" %8u", i + 3); printf("\n "); for (int i = 0; i < 5; i++) printf(" %8u", st->demod_preamblePhase[i]); printf("\n "); for (int i = 0; i < 5; i++) printf(" %8u", i + 4); printf("\n "); for (int i = 0; i < 5; i++) printf(" %8u", st->demod_bestPhase[i]); printf("\n\n"); } if (Modes.net) { printf("Network:\n"); printf(" %.6f MBytes received\n", st->network_bytes_in / 1e6); printf(" %.6f MBytes sent\n", st->network_bytes_out / 1e6); printf("Messages from network clients:\n"); printf(" %u Mode A/C messages received\n", st->remote_received_modeac); printf(" %u Mode A/C messages received\n", st->remote_received_modeac); printf(" %u Mode S messages received\n", st->remote_received_modes); printf(" %u with bad message format or invalid CRC\n", st->remote_rejected_bad); printf(" %u with unrecognized ICAO address\n", st->remote_rejected_unknown_icao); printf(" %u accepted with correct CRC\n", st->remote_accepted[0]); for (j = 1; j <= Modes.nfix_crc; ++j) printf(" %u accepted with %d-bit error repaired\n", st->remote_accepted[j], j); } printf("%u total usable messages\n", st->messages_total); printf("%u surface position messages received\n" "%u airborne position messages received\n" "%u global CPR attempts with valid positions\n" "%u global CPR attempts with bad data\n" " %u global CPR attempts that failed the range check\n" " %u global CPR attempts that failed the speed check\n" "%u global CPR attempts with insufficient data\n" "%u local CPR attempts with valid positions\n" " %u aircraft-relative positions\n" " %u receiver-relative positions\n" "%u local CPR attempts that did not produce useful positions\n" " %u local CPR attempts that failed the range check\n" " %u local CPR attempts that failed the speed check\n" "%u CPR messages that look like transponder failures filtered\n", st->cpr_surface, st->cpr_airborne, st->cpr_global_ok, st->cpr_global_bad, st->cpr_global_range_checks, st->cpr_global_speed_checks, st->cpr_global_skipped, st->cpr_local_ok, st->cpr_local_aircraft_relative, st->cpr_local_receiver_relative, st->cpr_local_skipped, st->cpr_local_range_checks, st->cpr_local_speed_checks, st->cpr_filtered); printf("%u non-ES altitude messages from ES-equipped aircraft ignored\n", st->suppressed_altitude_messages); printf("%u unique aircraft tracks\n", st->unique_aircraft); printf("%u aircraft tracks where only one message was seen\n", st->single_message_aircraft); { int64_t demod_cpu_millis = (int64_t) st->demod_cpu.tv_sec * 1000LL + st->demod_cpu.tv_nsec / 1000000LL; int64_t reader_cpu_millis = (int64_t) st->reader_cpu.tv_sec * 1000LL + st->reader_cpu.tv_nsec / 1000000LL; int64_t background_cpu_millis = (int64_t) st->background_cpu.tv_sec * 1000LL + st->background_cpu.tv_nsec / 1000000LL; printf("CPU load: %.1f%%\n" " %llu ms for demodulation\n" " %llu ms for reading from USB\n" " %llu ms for network input and background tasks\n", 100.0 * (demod_cpu_millis + reader_cpu_millis + background_cpu_millis) / (st->end - st->start + 1), (unsigned long long) demod_cpu_millis, (unsigned long long) reader_cpu_millis, (unsigned long long) background_cpu_millis); } if (Modes.stats_range_histo) display_range_histogram(st); fflush(stdout); } static void display_range_histogram(struct stats *st) { uint32_t peak; int i, j; int heights[RANGE_BUCKET_COUNT]; #if 0 #define NPIXELS 4 char *pixels[NPIXELS] = {".", "o", "O", "|"}; #else // UTF-8 bar symbols #define NPIXELS 8 char *pixels[NPIXELS] = { "\xE2\x96\x81", "\xE2\x96\x82", "\xE2\x96\x83", "\xE2\x96\x84", "\xE2\x96\x85", "\xE2\x96\x86", "\xE2\x96\x87", "\xE2\x96\x88" }; #endif printf("Range histogram:\n\n"); for (i = 0, peak = 0; i < RANGE_BUCKET_COUNT; ++i) { if (st->range_histogram[i] > peak) peak = st->range_histogram[i]; } for (i = 0; i < RANGE_BUCKET_COUNT; ++i) { heights[i] = st->range_histogram[i] * 20.0 * NPIXELS / peak; if (st->range_histogram[i] > 0 && heights[i] == 0) heights[i] = 1; } for (j = 0; j < 20; ++j) { for (i = 0; i < RANGE_BUCKET_COUNT; ++i) { int pheight = heights[i] - ((19 - j) * NPIXELS); if (pheight <= 0) printf(" "); else if (pheight >= NPIXELS) printf("%s", pixels[NPIXELS - 1]); else printf("%s", pixels[pheight]); } printf("\n"); } for (i = 0; i < RANGE_BUCKET_COUNT / 4; ++i) { printf("----"); } printf("\n"); for (i = 0; i < RANGE_BUCKET_COUNT / 4; ++i) { printf(" ' "); } printf("\n"); for (i = 0; i < RANGE_BUCKET_COUNT / 4; ++i) { int midpoint = round((i * 4 + 1.5) * Modes.maxRange / RANGE_BUCKET_COUNT / 1000); printf("%03d ", midpoint); } printf("km\n"); } void reset_stats(struct stats *st) { static struct stats st_zero; *st = st_zero; st->distance_min = 2E42; } void add_stats(const struct stats *st1, const struct stats *st2, struct stats *target) { int i; if (st1->start == 0) target->start = st2->start; else if (st2->start == 0) target->start = st1->start; else if (st1->start < st2->start) target->start = st1->start; else target->start = st2->start; target->end = st1->end > st2->end ? st1->end : st2->end; target->demod_preambles = st1->demod_preambles + st2->demod_preambles; target->demod_rejected_bad = st1->demod_rejected_bad + st2->demod_rejected_bad; target->demod_rejected_unknown_icao = st1->demod_rejected_unknown_icao + st2->demod_rejected_unknown_icao; for (i = 0; i < MODES_MAX_BITERRORS + 1; ++i) target->demod_accepted[i] = st1->demod_accepted[i] + st2->demod_accepted[i]; target->demod_modeac = st1->demod_modeac + st2->demod_modeac; for (int i = 0; i < 5; i++) { target->demod_preamblePhase[i] = st1->demod_preamblePhase[i] + st2->demod_preamblePhase[i]; target->demod_bestPhase[i] = st1->demod_bestPhase[i] + st2->demod_bestPhase[i]; } target->samples_processed = st1->samples_processed + st2->samples_processed; target->samples_dropped = st1->samples_dropped + st2->samples_dropped; target->samples_lost = st1->samples_lost + st2->samples_lost; add_timespecs(&st1->demod_cpu, &st2->demod_cpu, &target->demod_cpu); add_timespecs(&st1->reader_cpu, &st2->reader_cpu, &target->reader_cpu); add_timespecs(&st1->background_cpu, &st2->background_cpu, &target->background_cpu); add_timespecs(&st1->aircraft_json_cpu, &st2->aircraft_json_cpu, &target->aircraft_json_cpu); add_timespecs(&st1->globe_json_cpu, &st2->globe_json_cpu, &target->globe_json_cpu); add_timespecs(&st1->bin_cpu, &st2->bin_cpu, &target->bin_cpu); add_timespecs(&st1->heatmap_and_state_cpu, &st2->heatmap_and_state_cpu, &target->heatmap_and_state_cpu); add_timespecs(&st1->remove_stale_cpu, &st2->remove_stale_cpu, &target->remove_stale_cpu); add_timespecs(&st1->api_update_cpu, &st2->api_update_cpu, &target->api_update_cpu); add_timespecs(&st1->api_worker_cpu, &st2->api_worker_cpu, &target->api_worker_cpu); add_timespecs(&st1->trace_json_cpu, &st2->trace_json_cpu, &target->trace_json_cpu); target->pos_all = st1->pos_all + st2->pos_all; target->pos_duplicate = st1->pos_duplicate + st2->pos_duplicate; target->pos_garbage = st1->pos_garbage + st2->pos_garbage; for (i = 0; i < NUM_TYPES; i ++) { target->pos_by_type[i] = st1->pos_by_type[i] + st2->pos_by_type[i]; } target->api_request_count = st1->api_request_count + st2->api_request_count; target->recentTraceWrites = st1->recentTraceWrites + st2->recentTraceWrites; target->fullTraceWrites = st1->fullTraceWrites + st2->fullTraceWrites; target->permTraceWrites = st1->permTraceWrites + st2->permTraceWrites; // noise power: target->noise_power_sum = st1->noise_power_sum + st2->noise_power_sum; target->noise_power_count = st1->noise_power_count + st2->noise_power_count; // mean signal power: target->signal_power_sum = st1->signal_power_sum + st2->signal_power_sum; target->signal_power_count = st1->signal_power_count + st2->signal_power_count; // peak signal power seen if (st1->peak_signal_power > st2->peak_signal_power) target->peak_signal_power = st1->peak_signal_power; else target->peak_signal_power = st2->peak_signal_power; // strong signals target->strong_signal_count = st1->strong_signal_count + st2->strong_signal_count; // remote messages: target->remote_received_modeac = st1->remote_received_modeac + st2->remote_received_modeac; target->remote_received_modes = st1->remote_received_modes + st2->remote_received_modes; target->remote_received_basestation_valid = st1->remote_received_basestation_valid + st2->remote_received_basestation_valid; target->remote_received_basestation_invalid = st1->remote_received_basestation_invalid + st2->remote_received_basestation_invalid; target->remote_rejected_bad = st1->remote_rejected_bad + st2->remote_rejected_bad; target->remote_rejected_delayed = st1->remote_rejected_delayed + st2->remote_rejected_delayed; target->remote_malformed_beast = st1->remote_malformed_beast + st2->remote_malformed_beast; if (Modes.ping) { for (int i = 0; i < PING_BUCKETS; i++) { target->remote_ping_rtt[i] = st1->remote_ping_rtt[i] + st2->remote_ping_rtt[i]; } } target->network_bytes_in = st1->network_bytes_in + st2->network_bytes_in; target->network_bytes_out = st1->network_bytes_out + st2->network_bytes_out; target->remote_rejected_unknown_icao = st1->remote_rejected_unknown_icao + st2->remote_rejected_unknown_icao; for (i = 0; i < MODES_MAX_BITERRORS + 1; ++i) target->remote_accepted[i] = st1->remote_accepted[i] + st2->remote_accepted[i]; // total messages: target->messages_total = st1->messages_total + st2->messages_total; // CPR decoding: target->cpr_surface = st1->cpr_surface + st2->cpr_surface; target->cpr_airborne = st1->cpr_airborne + st2->cpr_airborne; target->cpr_global_ok = st1->cpr_global_ok + st2->cpr_global_ok; target->cpr_global_bad = st1->cpr_global_bad + st2->cpr_global_bad; target->cpr_global_skipped = st1->cpr_global_skipped + st2->cpr_global_skipped; target->cpr_global_range_checks = st1->cpr_global_range_checks + st2->cpr_global_range_checks; target->cpr_global_speed_checks = st1->cpr_global_speed_checks + st2->cpr_global_speed_checks; target->cpr_local_ok = st1->cpr_local_ok + st2->cpr_local_ok; target->cpr_local_aircraft_relative = st1->cpr_local_aircraft_relative + st2->cpr_local_aircraft_relative; target->cpr_local_receiver_relative = st1->cpr_local_receiver_relative + st2->cpr_local_receiver_relative; target->cpr_local_skipped = st1->cpr_local_skipped + st2->cpr_local_skipped; target->cpr_local_range_checks = st1->cpr_local_range_checks + st2->cpr_local_range_checks; target->cpr_local_speed_checks = st1->cpr_local_speed_checks + st2->cpr_local_speed_checks; target->cpr_filtered = st1->cpr_filtered + st2->cpr_filtered; target->suppressed_altitude_messages = st1->suppressed_altitude_messages + st2->suppressed_altitude_messages; // aircraft target->unique_aircraft = st1->unique_aircraft + st2->unique_aircraft; target->single_message_aircraft = st1->single_message_aircraft + st2->single_message_aircraft; // range histogram for (i = 0; i < RANGE_BUCKET_COUNT; ++i) target->range_histogram[i] = st1->range_histogram[i] + st2->range_histogram[i]; // Longest Distance observed if (st1->distance_max > st2->distance_max) target->distance_max = st1->distance_max; else target->distance_max = st2->distance_max; if (st1->distance_min < st2->distance_min) target->distance_min = st1->distance_min; else target->distance_min = st2->distance_min; } static void lockCurrent() { int micro = atomic_exchange(&Modes.apiWorkerCpuMicro, 0); Modes.stats_current.api_worker_cpu.tv_sec += micro / (1000LL * 1000LL); Modes.stats_current.api_worker_cpu.tv_nsec += 1000LL * (micro % (1000LL * 1000LL)); normalize_timespec(&Modes.stats_current.api_worker_cpu); Modes.stats_current.api_request_count += atomic_exchange(&Modes.apiRequestCounter, 0); Modes.stats_current.recentTraceWrites += atomic_exchange(&Modes.recentTraceWrites, 0); Modes.stats_current.fullTraceWrites += atomic_exchange(&Modes.fullTraceWrites, 0); Modes.stats_current.permTraceWrites += atomic_exchange(&Modes.permTraceWrites, 0); } static void unlockCurrent() { } void display_total_stats(void) { struct stats added; lockCurrent(); add_stats(&Modes.stats_alltime, &Modes.stats_current, &added); unlockCurrent(); display_stats(&added); } void display_total_short_range_stats() { struct stats added; lockCurrent(); add_stats(&Modes.stats_alltime, &Modes.stats_current, &added); unlockCurrent(); #define buckets 16 #define stride 50.0e3 // 100 km int counts[buckets]; memset(counts, 0, sizeof(counts)); int bucket = 0; for (int i = 0; i < RANGE_BUCKET_COUNT; i++) { double range = ((double) i + 0.5) * Modes.maxRange / (double) RANGE_BUCKET_COUNT; if (range > (bucket + 1) * stride && bucket < buckets - 1) { bucket++; } counts[bucket] += added.range_histogram[i]; } fprintf(stderr, "range buckets (%.0f km):", stride / 1000.0); for (int i = 0; i < buckets; i++) { fprintf(stderr, " %d", counts[i]); } fprintf(stderr, "\n"); #undef buckets #undef stride } void checkDisplayStats(int64_t now) { Modes.stats_current.end = now; //fprintf(stderr, "next_stats_display %.1f now %.1f", Modes.next_stats_display / 1000.0, now / 1000.0); if (Modes.stats_display_interval && now + 5 * SECONDS >= Modes.next_stats_display) { Modes.next_stats_display = now + Modes.stats_display_interval; display_stats(&Modes.stats_periodic); reset_stats(&Modes.stats_periodic); } } void statsUpdate(int64_t now) { Modes.stats_current.end = now; Modes.next_stats_update = roundSeconds(10, 5, now + 10 * SECONDS); lockCurrent(); Modes.stats_bucket = (Modes.stats_bucket + 1) % STAT_BUCKETS; Modes.stats_10[Modes.stats_bucket] = Modes.stats_current; add_stats(&Modes.stats_current, &Modes.stats_alltime, &Modes.stats_alltime); add_stats(&Modes.stats_current, &Modes.stats_periodic, &Modes.stats_periodic); reset_stats(&Modes.stats_current); Modes.stats_current.start = Modes.stats_current.end = now; unlockCurrent(); } static char * appendTypeCounts(char *p, char *end) { struct statsCount *sC = &(Modes.globalStatsCount); p = safe_snprintf(p, end, ",\n\"aircraft_with_pos\": %d", sC->readsb_aircraft_with_position); p = safe_snprintf(p, end, ", \"aircraft_without_pos\": %d", sC->readsb_aircraft_no_position); p = safe_snprintf(p, end, ", \"aircraft_count_by_type\": {"); for (int i = 0; i < NUM_TYPES; i++) { p = safe_snprintf(p, end, " \"%s\": %d,", addrtype_enum_string(i), sC->type_counts[i]); } p--; p = safe_snprintf(p, end, "}"); return p; } static char * appendStatsJson(char *p, char *end, struct stats *st, const char *key) { int i; p = safe_snprintf(p, end, ",\n\"%s\":{\"start\":%.1f,\"end\":%.1f", key, st->start / 1000.0, st->end / 1000.0); if (Modes.sdr_type != SDR_NONE) { p = safe_snprintf(p, end, ",\"local\":{\"samples_processed\":%llu" ",\"samples_dropped\":%llu" ",\"samples_lost\":%llu" ",\"modeac\":%u" ",\"modes\":%u" ",\"bad\":%u" ",\"unknown_icao\":%u", (unsigned long long) st->samples_processed, (unsigned long long) st->samples_dropped, (unsigned long long) st->samples_lost, st->demod_modeac, st->demod_preambles, st->demod_rejected_bad, st->demod_rejected_unknown_icao); for (i = 0; i <= Modes.nfix_crc; ++i) { if (i == 0) p = safe_snprintf(p, end, ",\"accepted\":[%u", st->demod_accepted[i]); else p = safe_snprintf(p, end, ",%u", st->demod_accepted[i]); } p = safe_snprintf(p, end, "]"); if (st->signal_power_sum > 0 && st->signal_power_count > 0) p = safe_snprintf(p, end, ",\"signal\":%.1f", 10 * log10(st->signal_power_sum / st->signal_power_count)); if (st->noise_power_sum > 0 && st->noise_power_count > 0) p = safe_snprintf(p, end, ",\"noise\":%.1f", 10 * log10(st->noise_power_sum / st->noise_power_count)); if (st->peak_signal_power > 0) p = safe_snprintf(p, end, ",\"peak_signal\":%.1f", 10 * log10(st->peak_signal_power)); p = safe_snprintf(p, end, ",\"strong_signals\":%d", st->strong_signal_count); p = safe_snprintf(p, end, ",\n\"pre_phase_1\":["); for (int i = 0; i < 5; i++) p = safe_snprintf(p, end, "%9u,", st->demod_preamblePhase[i]); p--; p = safe_snprintf(p, end, "],\"best_phase\" :["); for (int i = 0; i < 5; i++) p = safe_snprintf(p, end, "%9u,", st->demod_bestPhase[i]); p--; p = safe_snprintf(p, end, "]"); p = safe_snprintf(p, end, "}"); } p = safe_snprintf(p, end, ",\"messages_valid\": %u", st->messages_total); p = safe_snprintf(p, end, ",\"position_count_total\": %u", st->pos_all); p = safe_snprintf(p, end, ",\"position_count_by_type\": {"); for (int i = 0; i < NUM_TYPES; i++) { p = safe_snprintf(p, end, "\"%s\": %d,", addrtype_enum_string(i), st->pos_by_type[i]); } p--; p = safe_snprintf(p, end, "}"); if (Modes.net) { p = safe_snprintf(p, end, ",\"remote\":{\"modeac\":%u" ",\"modes\":%u" ",\"basestation\": %u" ",\"bad\":%u" ",\"unknown_icao\":%u", st->remote_received_modeac, st->remote_received_modes, st->remote_received_basestation_valid, st->remote_rejected_bad, st->remote_rejected_unknown_icao); for (i = 0; i <= Modes.nfix_crc; ++i) { if (i == 0) p = safe_snprintf(p, end, ",\"accepted\":[%u", st->remote_accepted[i]); else p = safe_snprintf(p, end, ",%u", st->remote_accepted[i]); } p = safe_snprintf(p, end, "]"); p = safe_snprintf(p, end, ",\"bytes_in\": %lu", (long) st->network_bytes_in); p = safe_snprintf(p, end, ",\"bytes_out\": %lu", (long) st->network_bytes_out); p = safe_snprintf(p, end, "}"); } { long long trace_json_cpu_millis_sum = 0; trace_json_cpu_millis_sum += (int64_t) st->trace_json_cpu.tv_sec * 1000UL + st->trace_json_cpu.tv_nsec / 1000000UL; p = safe_snprintf(p, end, ",\"cpr\":{\"surface\":%u" ",\"airborne\":%u" ",\"global_ok\":%u" ",\"global_bad\":%u" ",\"global_range\":%u" ",\"global_speed\":%u" ",\"global_skipped\":%u" ",\"local_ok\":%u" ",\"local_aircraft_relative\":%u" ",\"local_receiver_relative\":%u" ",\"local_skipped\":%u" ",\"local_range\":%u" ",\"local_speed\":%u" ",\"filtered\":%u}" ",\"altitude_suppressed\":%u" ",\"cpu\":{\"demod\":%lld,\"reader\":%lld,\"background\":%lld" ",\"aircraft_json\":%lld" ",\"globe_json\":%lld" ",\"binCraft\":%lld" ",\"trace_json\":%lld" ",\"heatmap_and_state\":%lld" ",\"api_workers\":%lld" ",\"api_update\":%lld" ",\"remove_stale\":%lld}" ",\"tracks\":{\"all\":%u" ",\"single_message\":%u}" ",\"messages\":%u" ",\"max_distance\":%ld" "}", st->cpr_surface, st->cpr_airborne, st->cpr_global_ok, st->cpr_global_bad, st->cpr_global_range_checks, st->cpr_global_speed_checks, st->cpr_global_skipped, st->cpr_local_ok, st->cpr_local_aircraft_relative, st->cpr_local_receiver_relative, st->cpr_local_skipped, st->cpr_local_range_checks, st->cpr_local_speed_checks, st->cpr_filtered, st->suppressed_altitude_messages, #define CPU_MILLIS(x) ((unsigned long long) st->x##_cpu.tv_sec * 1000UL + st->x##_cpu.tv_nsec / 1000000UL) CPU_MILLIS(demod), CPU_MILLIS(reader), CPU_MILLIS(background), CPU_MILLIS(aircraft_json), CPU_MILLIS(globe_json), CPU_MILLIS(bin), trace_json_cpu_millis_sum, CPU_MILLIS(heatmap_and_state), CPU_MILLIS(api_worker), CPU_MILLIS(api_update), CPU_MILLIS(remove_stale), #undef CPU_MILLIS st->unique_aircraft, st->single_message_aircraft, st->messages_total, (long) st->distance_max); } return p; } struct char_buffer generateStatusProm(int64_t now) { struct char_buffer cb; size_t buflen = 8192; char *buf = (char *) cmalloc(buflen), *p = buf, *end = buf + buflen; struct statsCount *sC = &(Modes.globalStatsCount); p = safe_snprintf(p, end, "readsb_now %"PRIu64"\n", now); p = safe_snprintf(p, end, "readsb_uptime %"PRIu64"\n", getUptime()); p = safe_snprintf(p, end, "readsb_aircraft_with_position %u\n", sC->readsb_aircraft_with_position); p = safe_snprintf(p, end, "readsb_aircraft_without_position %u\n", sC->readsb_aircraft_total - sC->readsb_aircraft_with_position); for (int i = 0; i < NUM_TYPES; i++) { p = safe_snprintf(p, end, "readsb_aircraft_%s %u\n", addrtype_enum_string(i), sC->type_counts[i]); } if (p >= end) fprintf(stderr, "buffer overrun status json\n"); cb.len = p - buf; cb.buffer = buf; return cb; } struct char_buffer generateStatusJson(int64_t now) { struct char_buffer cb; size_t buflen = 8192; char *buf = (char *) cmalloc(buflen), *p = buf, *end = buf + buflen; p = safe_snprintf(p, end, "{ \"now\": %.1f, \"uptime\": %.1f", now / 1000.0, getUptime() / 1000.0); p = appendTypeCounts(p, end); p = safe_snprintf(p, end, " }\n"); if (p >= end) fprintf(stderr, "buffer overrun status json\n"); cb.len = p - buf; cb.buffer = buf; return cb; } struct char_buffer generateStatsJson(int64_t now) { struct char_buffer cb; int bufsize = 64 * 1024; char *buf = (char *) cmalloc(bufsize), *p = buf, *end = buf + bufsize; p = safe_snprintf(p, end, "{ \"now\" : %.1f", now / 1000.0); if (!Modes.net_only) { p = safe_snprintf(p, end, ", \"gain_db\" : %.1f", Modes.gain / 10.0); p = safe_snprintf(p, end, ", \"estimated_ppm\" : %.1f", Modes.estimated_ppm); } p = appendTypeCounts(p, end); p = appendStatsJson(p, end, &Modes.stats_1min, "last1min"); p = appendStatsJson(p, end, &Modes.stats_5min, "last5min"); p = appendStatsJson(p, end, &Modes.stats_15min, "last15min"); p = appendStatsJson(p, end, &Modes.stats_alltime, "total"); p = safe_snprintf(p, end, "\n}\n"); if (p >= end) fprintf(stderr, "buffer overrun stats json\n"); cb.len = p - buf; cb.buffer = buf; return cb; } struct char_buffer generatePromFile(int64_t now) { struct char_buffer cb; int bufsize = 64 * 1024; char *buf = (char *) cmalloc(bufsize), *p = buf, *end = buf + bufsize; struct stats *st = &Modes.stats_1min; unsigned long long trace_json_cpu_millis_sum = 0; trace_json_cpu_millis_sum += (int64_t) st->trace_json_cpu.tv_sec * 1000UL + st->trace_json_cpu.tv_nsec / 1000000UL; struct statsCount *sC = &(Modes.globalStatsCount); p = safe_snprintf(p, end, "readsb_aircraft_adsb_version_zero %u\n", sC->readsb_aircraft_adsb_version_0); p = safe_snprintf(p, end, "readsb_aircraft_adsb_version_one %u\n", sC->readsb_aircraft_adsb_version_1); p = safe_snprintf(p, end, "readsb_aircraft_adsb_version_two %u\n", sC->readsb_aircraft_adsb_version_2); p = safe_snprintf(p, end, "readsb_aircraft_emergency %u\n", sC->readsb_aircraft_emergency); p = safe_snprintf(p, end, "readsb_aircraft_rssi_average %.1f\n", sC->readsb_aircraft_rssi_average); p = safe_snprintf(p, end, "readsb_aircraft_rssi_min %.1f\n", sC->readsb_aircraft_rssi_min); p = safe_snprintf(p, end, "readsb_aircraft_rssi_quart1 %.1f\n", sC->readsb_aircraft_rssi_quart1); p = safe_snprintf(p, end, "readsb_aircraft_rssi_median %.1f\n", sC->readsb_aircraft_rssi_median); p = safe_snprintf(p, end, "readsb_aircraft_rssi_quart3 %.1f\n", sC->readsb_aircraft_rssi_quart3); p = safe_snprintf(p, end, "readsb_aircraft_rssi_max %.1f\n", sC->readsb_aircraft_rssi_max); p = safe_snprintf(p, end, "readsb_aircraft_total %u\n", sC->readsb_aircraft_total); p = safe_snprintf(p, end, "readsb_aircraft_with_flight_number %u\n", sC->readsb_aircraft_with_flight_number); p = safe_snprintf(p, end, "readsb_aircraft_without_flight_number %u\n", sC->readsb_aircraft_without_flight_number); p = safe_snprintf(p, end, "readsb_aircraft_with_position %u\n", sC->readsb_aircraft_with_position); p = safe_snprintf(p, end, "readsb_aircraft_without_position %u\n", sC->readsb_aircraft_total - sC->readsb_aircraft_with_position); for (int i = 0; i < NUM_TYPES; i++) { p = safe_snprintf(p, end, "readsb_aircraft_%s %u\n", addrtype_enum_string(i), sC->type_counts[i]); } p = safe_snprintf(p, end, "readsb_cpr_airborne %u\n", st->cpr_airborne); p = safe_snprintf(p, end, "readsb_cpr_surface %u\n", st->cpr_surface); p = safe_snprintf(p, end, "readsb_cpr_global_ok %u\n", st->cpr_global_ok); p = safe_snprintf(p, end, "readsb_cpr_global_bad %u\n", st->cpr_global_bad); p = safe_snprintf(p, end, "readsb_cpr_global_bad_range %u\n", st->cpr_global_range_checks); p = safe_snprintf(p, end, "readsb_cpr_global_bad_speed %u\n", st->cpr_global_speed_checks); p = safe_snprintf(p, end, "readsb_cpr_global_skipped %u\n", st->cpr_global_skipped); p = safe_snprintf(p, end, "readsb_cpr_local_ok %u\n", st->cpr_local_ok); p = safe_snprintf(p, end, "readsb_cpr_local_aircraft_relative %u\n", st->cpr_local_aircraft_relative); p = safe_snprintf(p, end, "readsb_cpr_local_receiver_relative %u\n", st->cpr_local_receiver_relative); p = safe_snprintf(p, end, "readsb_cpr_local_bad_range %u\n", st->cpr_local_range_checks); p = safe_snprintf(p, end, "readsb_cpr_local_bad_speed %u\n", st->cpr_local_speed_checks); p = safe_snprintf(p, end, "readsb_cpr_local_skipped %u\n", st->cpr_local_skipped); p = safe_snprintf(p, end, "readsb_cpr_filtered %u\n", st->cpr_filtered); #define CPU_MILLIS(x) ((unsigned long long) st->x##_cpu.tv_sec * 1000UL + st->x##_cpu.tv_nsec / 1000000UL) p = safe_snprintf(p, end, "readsb_cpu_background %llu\n", CPU_MILLIS(background)); p = safe_snprintf(p, end, "readsb_cpu_demod %llu\n", CPU_MILLIS(demod)); p = safe_snprintf(p, end, "readsb_cpu_reader %llu\n", CPU_MILLIS(reader)); p = safe_snprintf(p, end, "readsb_cpu_aircraft_json %llu\n", CPU_MILLIS(aircraft_json)); p = safe_snprintf(p, end, "readsb_cpu_globe_json %llu\n", CPU_MILLIS(globe_json)); p = safe_snprintf(p, end, "readsb_cpu_binCraft %llu\n", CPU_MILLIS(bin)); p = safe_snprintf(p, end, "readsb_cpu_heatmap_and_state %llu\n", CPU_MILLIS(heatmap_and_state)); p = safe_snprintf(p, end, "readsb_cpu_remove_stale %llu\n", CPU_MILLIS(remove_stale)); p = safe_snprintf(p, end, "readsb_cpu_trace_json %llu\n", trace_json_cpu_millis_sum); p = safe_snprintf(p, end, "readsb_cpu_api_update %llu\n", CPU_MILLIS(api_update)); p = safe_snprintf(p, end, "readsb_cpu_api_workers %llu\n", CPU_MILLIS(api_worker)); #undef CPU_MILLIS p = safe_snprintf(p, end, "readsb_api_request_count %llu\n", (unsigned long long) st->api_request_count); p = safe_snprintf(p, end, "readsb_tracewrites_recent %u\n", st->recentTraceWrites); p = safe_snprintf(p, end, "readsb_tracewrites_full %u\n", st->fullTraceWrites); p = safe_snprintf(p, end, "readsb_tracewrites_perm %u\n", st->permTraceWrites); p = safe_snprintf(p, end, "readsb_tracewrites_cycle_duration %lld\n", (long long) Modes.writeTracesActualDuration); p = safe_snprintf(p, end, "readsb_distance_max %u\n", (uint32_t) st->distance_max); if (st->distance_min < 1E42) p = safe_snprintf(p, end, "readsb_distance_min %u\n", (uint32_t) st->distance_min); else p = safe_snprintf(p, end, "readsb_distance_min 0\n"); p = safe_snprintf(p, end, "readsb_messages_valid %u\n", st->messages_total); p = safe_snprintf(p, end, "readsb_messages_invalid %u\n", st->remote_received_basestation_invalid + st->remote_rejected_bad + st->demod_rejected_bad + st->remote_rejected_unknown_icao + st->demod_rejected_unknown_icao); p = safe_snprintf(p, end, "readsb_messages_modes_valid %u\n", st->remote_accepted[0] + st->demod_accepted[0]); p = safe_snprintf(p, end, "readsb_messages_modes_valid_fixed_bit %u\n", st->remote_accepted[1] + st->demod_accepted[1]); p = safe_snprintf(p, end, "readsb_messages_modes_invalid_bad %u\n", st->remote_rejected_bad + st->demod_rejected_bad); p = safe_snprintf(p, end, "readsb_messages_modes_invalid_unknown_icao %u\n", st->remote_rejected_unknown_icao + st->demod_rejected_unknown_icao); p = safe_snprintf(p, end, "readsb_messages_modes_rejected_delayed %u\n", st->remote_rejected_delayed); p = safe_snprintf(p, end, "readsb_messages_basestation_valid %u\n", st->remote_received_basestation_valid); p = safe_snprintf(p, end, "readsb_messages_basestation_invalid %u\n", st->remote_received_basestation_invalid); p = safe_snprintf(p, end, "readsb_messages_modeac_valid %u\n", st->remote_received_modeac + st->demod_modeac); p = safe_snprintf(p, end, "readsb_network_bytes_in %lu\n", (long) st->network_bytes_in); p = safe_snprintf(p, end, "readsb_network_bytes_out %lu\n", (long) st->network_bytes_out); p = safe_snprintf(p, end, "readsb_network_malformed_beast_bytes %u\n", st->remote_malformed_beast); if (Modes.ping) { float bucketsize = PING_BUCKETBASE; float bucketmax = 0; for (int i = 0; i < PING_BUCKETS; i++) { bucketmax += bucketsize; bucketmax = nearbyint(bucketmax / 10) * 10; bucketsize *= PING_BUCKETMULT; p = safe_snprintf(p, end, "readsb_network_packets_rtt_%d %u\n", (int) bucketmax, st->remote_ping_rtt[i]); } } p = safe_snprintf(p, end, "readsb_tracks_all %u\n", st->unique_aircraft); p = safe_snprintf(p, end, "readsb_tracks_single_message %u\n", st->single_message_aircraft); p = safe_snprintf(p, end, "readsb_position_count_total %u\n", st->pos_all); p = safe_snprintf(p, end, "readsb_position_count_duplicate %u\n", st->pos_duplicate); p = safe_snprintf(p, end, "readsb_position_count_garbage %u\n", st->pos_garbage); for (int i = 0; i < NUM_TYPES; i++) { p = safe_snprintf(p, end, "readsb_position_count_%s %u\n", addrtype_enum_string(i), st->pos_by_type[i]); } for (int i = 0; i < Modes.net_connectors_count; i++) { struct net_connector *con = &Modes.net_connectors[i]; int64_t value = 0; if (con->connected) { value = (now - con->lastConnect) / 1000.0; } p = safe_snprintf(p, end, "readsb_net_connector_status{host=\"%s\",port=\"%s\"} %"PRIu64"\n", con->address, con->port, value); } if (Modes.sdr_type != SDR_NONE) { if (!Modes.net_only) { p = safe_snprintf(p, end, "readsb_sdr_gain %.1f\n", Modes.gain / 10.0); } if (st->signal_power_sum > 0 && st->signal_power_count > 0) p = safe_snprintf(p, end, "readsb_signal_avg %.1f\n", 10 * log10(st->signal_power_sum / st->signal_power_count)); else p = safe_snprintf(p, end, "readsb_signal_avg -50.0\n"); if (st->noise_power_sum > 0 && st->noise_power_count > 0) p = safe_snprintf(p, end, "readsb_signal_noise %.1f\n", 10 * log10(st->noise_power_sum / st->noise_power_count)); else p = safe_snprintf(p, end, "readsb_signal_noise -50.0\n"); if (st->peak_signal_power > 0) p = safe_snprintf(p, end, "readsb_signal_peak %.1f\n", 10 * log10(st->peak_signal_power)); else p = safe_snprintf(p, end, "readsb_signal_peak -50.0\n"); p = safe_snprintf(p, end, "readsb_signal_strong %d\n", st->strong_signal_count); if (!Modes.net_only) { p = safe_snprintf(p, end, "readsb_demod_samples_processed %"PRIu64"\n", st->samples_processed); p = safe_snprintf(p, end, "readsb_demod_samples_dropped %"PRIu64"\n", st->samples_dropped); p = safe_snprintf(p, end, "readsb_demod_samples_lost %"PRIu64"\n", st->samples_lost); p = safe_snprintf(p, end, "readsb_demod_estimated_ppm %.1f\n", Modes.estimated_ppm); p = safe_snprintf(p, end, "readsb_demod_preambles %"PRIu32"\n", st->demod_preambles); } } p = safe_snprintf(p, end, "readsb_mem_aircraft_count %d\n", Modes.total_aircraft_count); p = safe_snprintf(p, end, "readsb_mem_aircraft_alloc %"PRIu64"\n", Modes.aircraft_data_size); if (Modes.keep_traces) { p = safe_snprintf(p, end, "readsb_trace_current_memory %"PRIu64"\n", Modes.trace_current_size); p = safe_snprintf(p, end, "readsb_trace_chunk_memory %"PRIu64"\n", Modes.trace_chunk_size); p = safe_snprintf(p, end, "readsb_trace_cache_memory %"PRIu64"\n", Modes.trace_cache_size); p = safe_snprintf(p, end, "readsb_trace_last_memory %"PRIu64"\n", Modes.trace_last_size); } p = safe_snprintf(p, end, "readsb_uptime %"PRIu64"\n", getUptime()); if (p >= end) fprintf(stderr, "buffer overrun stats prom\n"); cb.len = p - buf; cb.buffer = buf; return cb; } void statsResetCount() { struct statsCount *s = &(Modes.globalStatsCount); memset(&s->type_counts, 0, sizeof(s->type_counts)); s->rssi_table_len = 0; s->readsb_aircraft_with_position = 0; s->readsb_aircraft_no_position = 0; s->readsb_aircraft_adsb_version_0 = 0; s->readsb_aircraft_adsb_version_1 = 0; s->readsb_aircraft_adsb_version_2 = 0; s->readsb_aircraft_emergency = 0; s->readsb_aircraft_rssi_average = 0; s->readsb_aircraft_rssi_max = -50; s->readsb_aircraft_rssi_min = 42; s->readsb_aircraft_total = 0; s->readsb_aircraft_with_flight_number = 0; s->readsb_aircraft_without_flight_number = 0; } void statsCountAircraft(int64_t now) { struct statsCount *s = &(Modes.globalStatsCount); int32_t total_aircraft_count = 0; int32_t json_aircraft_count = 0; uint64_t trace_chunk_size = 0; uint64_t trace_cache_size = 0; uint64_t trace_current_size = 0; uint64_t trace_last_size = 0; for (int j = 0; j < Modes.acBuckets; j++) { for (struct aircraft *a = Modes.aircraft[j]; a; a = a->next) { total_aircraft_count++; if (Modes.keep_traces && a->trace_current_len > 0) { trace_current_size += stateBytes(a->trace_current_max); trace_chunk_size += a->trace_chunk_overall_bytes; trace_last_size += stateBytes(Modes.traceLastMax); struct traceCache *tCache = &a->traceCache; if (tCache->entries) { trace_cache_size += tCache->totalAlloc; } } if (!includeAircraftJson(now, a)) { continue; } json_aircraft_count++; if (trackDataValid(&a->position_valid)) s->readsb_aircraft_with_position++; else s->readsb_aircraft_no_position++; s->type_counts[a->addrtype]++; if (a->adsb_version == 0) s->readsb_aircraft_adsb_version_0++; else if (a->adsb_version == 1) s->readsb_aircraft_adsb_version_1++; else if (a->adsb_version == 2) s->readsb_aircraft_adsb_version_2++; if (trackDataValid(&a->emergency_valid) && a->emergency) s->readsb_aircraft_emergency++; double signal = 10 * log10((a->signalLevel[0] + a->signalLevel[1] + a->signalLevel[2] + a->signalLevel[3] + a->signalLevel[4] + a->signalLevel[5] + a->signalLevel[6] + a->signalLevel[7] + 1e-5) / 8); if (( a->addrtype == ADDR_MODE_S || a->addrtype == ADDR_ADSB_ICAO || a->addrtype == ADDR_ADSB_ICAO_NT || a->addrtype == ADDR_ADSR_ICAO || a->addrtype == ADDR_MLAT || a->addrtype == ADDR_MODE_S ) && signal > -49.4 && signal < 1) { if (s->rssi_table_alloc < s->rssi_table_len + 1) { s->rssi_table_alloc = 2 * s->rssi_table_len + 1024; s->rssi_table = realloc(s->rssi_table, sizeof(float) * s->rssi_table_alloc); } s->rssi_table[s->rssi_table_len] = signal; s->rssi_table_len++; } if (trackDataValid(&a->callsign_valid)) s->readsb_aircraft_with_flight_number++; else s->readsb_aircraft_without_flight_number++; } } Modes.total_aircraft_count = total_aircraft_count; Modes.json_aircraft_count = json_aircraft_count; Modes.trace_chunk_size = trace_chunk_size; Modes.trace_cache_size = trace_cache_size; Modes.trace_current_size = trace_current_size; Modes.trace_last_size = trace_last_size; static int64_t antiSpam2; if (total_aircraft_count > 2 * Modes.acBuckets && now > antiSpam2 + 12 * HOURS) { fprintf(stderr, "<3>increase --ac-hash-bits, aircraft hash table fill: %0.1f\n", total_aircraft_count / (double) Modes.acBuckets); antiSpam2 = now; } } static float percentile(float p, float *values, int len) { float x = p * (len - 1); float d = x - ((int) x); int index = (int) x; float res; if (index + 1 < len) res = values[index] + d * (values[index + 1] - values[index]); else res = values[index]; //printf("p: %.2f x: %.4f d: %.4f index: %d value: %.4f value_i1: %.4f\n", p, x, d, index, values[index], values[index+1]); return res; } static int compareFloat(const void *p1, const void *p2) { float a1 = *(const float *) p1; float a2 = *(const float *) p2; return (a1 > a2) - (a1 < a2); } static void statsCalc() { // add up the buckets for the 1 min, 5 min and 15 min stats reset_stats(&Modes.stats_1min); for (int i = 0; i < 6; ++i) { int index = (Modes.stats_bucket - i + STAT_BUCKETS) % STAT_BUCKETS; add_stats(&Modes.stats_10[index], &Modes.stats_1min, &Modes.stats_1min); } reset_stats(&Modes.stats_5min); for (int i = 0; i < 30; ++i) { int index = (Modes.stats_bucket - i + STAT_BUCKETS) % STAT_BUCKETS; add_stats(&Modes.stats_10[index], &Modes.stats_5min, &Modes.stats_5min); } reset_stats(&Modes.stats_15min); for (int i = 0; i < 90; ++i) { int index = (Modes.stats_bucket - i + STAT_BUCKETS) % STAT_BUCKETS; add_stats(&Modes.stats_10[index], &Modes.stats_15min, &Modes.stats_15min); } struct statsCount *s = &(Modes.globalStatsCount); s->readsb_aircraft_total = s->readsb_aircraft_with_position + s->readsb_aircraft_no_position; if (s->rssi_table_len > 0) { qsort(s->rssi_table, s->rssi_table_len, sizeof(float), compareFloat); //printf("after sort\n"); //for (int i = 0; i < s->rssi_table_len; i++) // printf("%.2f\n", s->rssi_table[i]); s->readsb_aircraft_rssi_min = s->rssi_table[0]; s->readsb_aircraft_rssi_quart1 = percentile(0.25, s->rssi_table, s->rssi_table_len); s->readsb_aircraft_rssi_median = percentile(0.5, s->rssi_table, s->rssi_table_len); s->readsb_aircraft_rssi_quart3 = percentile(0.75, s->rssi_table, s->rssi_table_len); s->readsb_aircraft_rssi_max = s->rssi_table[s->rssi_table_len - 1]; for (int i = 0; i < s->rssi_table_len; i++) { s->readsb_aircraft_rssi_average += s->rssi_table[i]; } s->readsb_aircraft_rssi_average /= s->rssi_table_len; } else { s->readsb_aircraft_rssi_average = -50; s->readsb_aircraft_rssi_max = -50; s->readsb_aircraft_rssi_min = -50; s->readsb_aircraft_rssi_quart1 = -50; s->readsb_aircraft_rssi_median = -50; s->readsb_aircraft_rssi_quart3 = -50; } if (s->readsb_aircraft_rssi_min == 42) s->readsb_aircraft_rssi_min = -50; } void statsProcess(int64_t now) { statsCalc(); // calculate statistics stuff checkDisplayStats(now); // display stats if requested struct char_buffer prom = { 0 }; if (Modes.json_dir || Modes.prom_file) { prom = generatePromFile(now); } if (Modes.json_dir) { free(writeJsonToFile(Modes.json_dir, "stats.json", generateStatsJson(now)).buffer); writeJsonToFile(Modes.json_dir, "stats.prom", prom); } if (Modes.prom_file) { writeJsonToFile(NULL, Modes.prom_file, prom); } sfree(prom.buffer); } readsb-3.16/stats.h000066400000000000000000000156221505057307600142300ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // stats.c: statistics structures and prototypes. // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef DUMP1090_STATS_H #define DUMP1090_STATS_H struct stats { int64_t start; int64_t end; // Mode S demodulator counts: uint32_t demod_preambles; uint32_t demod_rejected_bad; uint32_t demod_rejected_unknown_icao; uint32_t demod_accepted[MODES_MAX_BITERRORS + 1]; uint32_t demod_preamblePhase[5]; uint32_t demod_bestPhase[5]; uint64_t samples_processed; uint64_t samples_dropped; uint64_t samples_lost; // Mode A/C demodulator counts: uint32_t demod_modeac; // number of signals with power > -3dBFS uint32_t strong_signal_count; // noise floor: double noise_power_sum; uint64_t noise_power_count; // mean signal power: double signal_power_sum; uint64_t signal_power_count; // peak signal power seen double peak_signal_power; // timing: struct timespec demod_cpu; struct timespec reader_cpu; struct timespec background_cpu; struct timespec aircraft_json_cpu; struct timespec trace_json_cpu; struct timespec globe_json_cpu; struct timespec bin_cpu; struct timespec heatmap_and_state_cpu; struct timespec remove_stale_cpu; struct timespec api_worker_cpu; struct timespec api_update_cpu; uint64_t api_request_count; // remote messages: uint32_t remote_received_modeac; uint32_t remote_received_modes; uint32_t remote_received_basestation_valid; uint32_t remote_received_basestation_invalid; uint32_t remote_rejected_bad; uint32_t remote_rejected_unknown_icao; uint32_t remote_rejected_delayed; uint32_t remote_accepted[MODES_MAX_BITERRORS + 1]; uint32_t remote_malformed_beast; uint32_t remote_ping_rtt[PING_BUCKETS]; uint64_t network_bytes_in; uint64_t network_bytes_out; // total messages: uint32_t messages_total; // CPR decoding: uint32_t cpr_surface; uint32_t cpr_airborne; uint32_t cpr_global_ok; uint32_t cpr_global_bad; uint32_t cpr_global_skipped; uint32_t cpr_global_range_checks; uint32_t cpr_global_speed_checks; uint32_t cpr_local_ok; uint32_t cpr_local_skipped; uint32_t cpr_local_range_checks; uint32_t cpr_local_speed_checks; uint32_t cpr_local_aircraft_relative; uint32_t cpr_local_receiver_relative; uint32_t cpr_filtered; uint32_t pos_all; uint32_t pos_duplicate; uint32_t pos_garbage; uint32_t pos_by_type[NUM_TYPES]; uint32_t recentTraceWrites; uint32_t fullTraceWrites; uint32_t permTraceWrites; // number of altitude messages ignored because // we had a recent DF17/18 altitude uint32_t suppressed_altitude_messages; // aircraft: // total "new" aircraft (i.e. not seen in the last 30 or 300s) uint32_t unique_aircraft; // we saw only a single message uint32_t single_message_aircraft; // range histogram #define RANGE_BUCKET_COUNT 128 uint32_t range_histogram[RANGE_BUCKET_COUNT]; double distance_max; // Longest range decoded, in *metres* double distance_min; // Shortest range decoded, in *metres* }; struct statsCount { uint32_t readsb_aircraft_with_position; uint32_t readsb_aircraft_no_position; uint32_t readsb_aircraft_total; uint32_t readsb_aircraft_with_flight_number; uint32_t readsb_aircraft_without_flight_number; uint32_t type_counts[NUM_TYPES]; uint32_t readsb_aircraft_adsb_version_0; uint32_t readsb_aircraft_adsb_version_1; uint32_t readsb_aircraft_adsb_version_2; uint32_t readsb_aircraft_emergency; double readsb_aircraft_rssi_average; float readsb_aircraft_rssi_max; float readsb_aircraft_rssi_min; float readsb_aircraft_rssi_quart1; float readsb_aircraft_rssi_median; float readsb_aircraft_rssi_quart3; float *rssi_table; int rssi_table_len; int rssi_table_alloc; }; #define RANGEDIRS_BUCKETS 360 #define RANGEDIRS_IVALS 64 struct distCoords { float distance; float lat; float lon; int32_t alt; } __attribute__ ((__packed__)); #define RANGEDIRSSIZE (RANGEDIRS_IVALS * RANGEDIRS_BUCKETS * sizeof(struct distCoords)) void add_stats (const struct stats *st1, const struct stats *st2, struct stats *target); void display_stats (struct stats *st); void reset_stats (struct stats *st); void display_total_stats(void); void display_total_short_range_stats(); void add_timespecs (const struct timespec *x, const struct timespec *y, struct timespec *z); struct char_buffer generateStatusJson(int64_t now); struct char_buffer generateStatusProm(int64_t now); struct char_buffer generateStatsJson(int64_t now); struct char_buffer generatePromFile(int64_t now); void statsUpdate(int64_t now); void checkDisplayStats(int64_t now); void statsResetCount(); void statsCountAircraft(int64_t now); void statsProcess(int64_t now); #endif readsb-3.16/tag.sh000077500000000000000000000014711505057307600140300ustar00rootroot00000000000000#!/bin/bash set -e trap 'echo "[ERROR] Error in line $LINENO when executing: $BASH_COMMAND"' ERR VERSION="$(cat version | cut -d'.' -f1).$(cat version | cut -d'.' -f2).$(( $(cat version | cut -d'.' -f3) + 1 ))" echo "$VERSION" > version git add version # use this and the actual changelog from the main directory cat - changelog > debian/changelog < $(date -R) EOF git add debian/changelog git commit -m "incrementing version: $VERSION" git tag "v$VERSION" git push git push --tag # notes for a release (done manually due to changelog) # # put version in version file # modify changelog in main directory # copy changelog from main directory to debian # git tag -a v3.16 # git push # git push --tags readsb-3.16/threadpool.c000066400000000000000000000217271505057307600152310ustar00rootroot00000000000000/* Copyright (c) 2021, Youssef Touil Copyright (c) 2021, Matthias Wirth Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "threadpool.h" #include #include //#include //#include #include #include #define ATOMIC_WORKER_LOCK (-1) typedef struct { int index; threadpool_t* pool; pthread_t pthread; struct timespec thread_time; threadpool_threadbuffers_t user_buffers; } thread_t; struct threadpool_t { pthread_mutex_t worker_lock; pthread_cond_t notify_worker; pthread_mutex_t master_lock; pthread_cond_t notify_master; thread_t* threads; uint32_t thread_count; uint32_t terminate; atomic_intptr_t tasks; atomic_int task_count; atomic_int pending_count; }; static void *threadpool_threadproc(void *threadpool); static void normalize_timespec(struct timespec *ts) { if (ts->tv_nsec >= 1000000000) { ts->tv_sec += ts->tv_nsec / 1000000000; ts->tv_nsec = ts->tv_nsec % 1000000000; } else if (ts->tv_nsec < 0) { long adjust = ts->tv_nsec / 1000000000 + 1; ts->tv_sec -= adjust; ts->tv_nsec = (ts->tv_nsec + 1000000000 * adjust) % 1000000000; } } struct timespec threadpool_get_cumulative_thread_time(threadpool_t* pool) { struct timespec sum = { 0, 0 }; for (uint32_t i = 0; i < pool->thread_count; i++) { struct timespec ts = pool->threads[i].thread_time; sum.tv_sec += ts.tv_sec; sum.tv_nsec += ts.tv_nsec; } normalize_timespec(&sum); return sum; } threadpool_t *threadpool_create(uint32_t thread_count, uint32_t buffer_count) { threadpool_t *pool = (threadpool_t *) malloc(sizeof(threadpool_t)); pool->terminate = 0; atomic_store(&pool->task_count, 0); atomic_store(&pool->pending_count, 0); atomic_store(&pool->tasks, (intptr_t) NULL); pool->thread_count = thread_count; pool->threads = (thread_t *) malloc(sizeof(thread_t) * thread_count); pthread_mutex_init(&pool->worker_lock, NULL); pthread_cond_init(&pool->notify_worker, NULL); pthread_mutex_init(&pool->master_lock, NULL); pthread_cond_init(&pool->notify_master, NULL); for (uint32_t i = 0; i < thread_count; i++) { thread_t *thread = &pool->threads[i]; thread->index = i; thread->pool = pool; thread->thread_time.tv_sec = 0; thread->thread_time.tv_nsec = 0; thread->user_buffers.buffer_count = buffer_count; thread->user_buffers.buffers = malloc(buffer_count * sizeof(threadpool_buffer_t)); memset(thread->user_buffers.buffers, 0x0, buffer_count * sizeof(threadpool_buffer_t)); pthread_create(&thread->pthread, NULL, threadpool_threadproc, thread); } return pool; } void threadpool_reset_buffers(threadpool_t *pool) { for (uint32_t i = 0; i < pool->thread_count; i++) { thread_t *thread = &pool->threads[i]; for (uint32_t k = 0; k < thread->user_buffers.buffer_count; k++) { free_threadpool_buffer(&thread->user_buffers.buffers[k]); } } } void threadpool_destroy(threadpool_t *pool) { pool->terminate = 1; pthread_mutex_lock(&pool->worker_lock); atomic_store(&pool->task_count, 0); pthread_cond_broadcast(&pool->notify_worker); pthread_mutex_unlock(&pool->worker_lock); pthread_mutex_lock(&pool->master_lock); atomic_store(&pool->pending_count, 0); pthread_cond_broadcast(&pool->notify_master); pthread_mutex_unlock(&pool->master_lock); for (uint32_t i = 0; i < pool->thread_count; i++) { thread_t *thread = &pool->threads[i]; pthread_join(thread->pthread, NULL); for (uint32_t k = 0; k < thread->user_buffers.buffer_count; k++) { free_threadpool_buffer(&thread->user_buffers.buffers[k]); } free(thread->user_buffers.buffers); } pthread_mutex_destroy(&pool->worker_lock); pthread_cond_destroy(&pool->notify_worker); pthread_mutex_destroy(&pool->master_lock); pthread_cond_destroy(&pool->notify_master); free(pool->threads); free(pool); } void threadpool_run(threadpool_t *pool, threadpool_task_t* tasks, uint32_t count) { atomic_store(&pool->pending_count, count); atomic_store(&pool->tasks, (intptr_t) tasks); // incrementing task count means a thread could start doing work already // pending_count / tasks need to be in place, so this order is important atomic_store(&pool->task_count, count); pthread_mutex_lock(&pool->worker_lock); pthread_cond_broadcast(&pool->notify_worker); // wake up sleeping worker threads after task_count has been set pthread_mutex_unlock(&pool->worker_lock); pthread_mutex_lock(&pool->master_lock); while (atomic_load(&pool->pending_count) > 0 && !pool->terminate) { pthread_cond_wait(&pool->notify_master, &pool->master_lock); } pthread_mutex_unlock(&pool->master_lock); } static unsigned get_seed() { struct timespec time; clock_gettime(CLOCK_REALTIME, &time); return (time.tv_sec ^ time.tv_nsec ^ (getpid() << 16) ^ (uintptr_t) pthread_self()); } static void *threadpool_threadproc(void *arg) { srandom(get_seed()); thread_t *thread = (thread_t *) arg; threadpool_t *pool = thread->pool; int task_count; while (1) { task_count = atomic_load(&pool->task_count); //fprintf(stderr, "%d %4d\n", thread->index, task_count); if (task_count == 0) { pthread_mutex_lock(&pool->worker_lock); if (pool->terminate) { pthread_mutex_unlock(&pool->worker_lock); return NULL; } // re-check task_count inside worker_lock before sleeping // this makes lost wakeup impossible // (task count is incremented BEFORE taking worker_lock to wake the workers) if (atomic_load(&pool->task_count) == 0) { // update thread_time clock_gettime(CLOCK_THREAD_CPUTIME_ID, &thread->thread_time); // wait until we have more work pthread_cond_wait(&pool->notify_worker, &pool->worker_lock); } pthread_mutex_unlock(&pool->worker_lock); continue; } int expected = task_count; task_count--; if (!atomic_compare_exchange_weak(&pool->task_count, &expected, task_count)) { continue; } threadpool_task_t* task = (threadpool_task_t*) atomic_load(&pool->tasks) + task_count; task->function(task->argument, &thread->user_buffers); int pending_count = atomic_fetch_sub(&pool->pending_count, 1) - 1; if (pending_count == 0) { pthread_mutex_lock(&pool->master_lock); pthread_cond_broadcast(&pool->notify_master); pthread_mutex_unlock(&pool->master_lock); } } //pthread_exit(NULL); return NULL; } void free_threadpool_buffer(threadpool_buffer_t *buffer) { if (buffer->buf) { free(buffer->buf); buffer->buf = NULL; buffer->size = 0; } #ifdef _THREADPOOL_WITH_ZSTD if (buffer->cctx) { ZSTD_freeCCtx(buffer->cctx); buffer->cctx = NULL; } if (buffer->dctx) { ZSTD_freeDCtx(buffer->dctx); buffer->dctx = NULL; } #endif } readsb-3.16/threadpool.h000066400000000000000000000073221505057307600152310ustar00rootroot00000000000000/* Copyright (c) 2021, Youssef Touil Copyright (c) 2021, Matthias Wirth Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _THREADPOOL_H_ #define _THREADPOOL_H_ #include #include #include // Minimal thread pool implementation using pthread.h and stdatomic.h // with option per thread pointers (for readsb used for per thread buffers) #if 1 #define _THREADPOOL_WITH_ZSTD #include #endif typedef struct { void *buf; ssize_t size; #ifdef _THREADPOOL_WITH_ZSTD ZSTD_CCtx* cctx; ZSTD_DCtx* dctx; #endif } threadpool_buffer_t; // this function is called when destroying a threadpool with buffers // in case you're using the threadpool_buffer_t yourself, you can use this to free the buffers in it void free_threadpool_buffer(threadpool_buffer_t *buffer); typedef struct { uint32_t buffer_count; threadpool_buffer_t *buffers; } threadpool_threadbuffers_t; typedef struct threadpool_t threadpool_t; // create a thread pool (number of threads, number of usable buffer_t structs in threadpool_threadbuffers_t) threadpool_t *threadpool_create(uint32_t thread_count, uint32_t buffer_count); // destroy the thread pool void threadpool_destroy(threadpool_t *pool); typedef void (* threadpool_function_t)(void *, threadpool_threadbuffers_t *); typedef struct { threadpool_function_t function; void *argument; } threadpool_task_t; // run count tasks defined in tasks using a function pointer and and argument each // the count is not constrained by thread_count in any way // the user is responsible to not call threadpool_run until the previous run has completed // threadpool_run will block until all tasks have finished void threadpool_run(threadpool_t *pool, threadpool_task_t *tasks, uint32_t count); // get the cumulative thread time used by all threads in the threadpool // note that the underlying counters are updated only when a thread finishes a // task and around 1 second has elapsed since the last update // this time is best effort to achieve optimal performance struct timespec threadpool_get_cumulative_thread_time(threadpool_t* threadpool); // only use this after rading the code for it void threadpool_reset_buffers(threadpool_t *pool); #endif /* _THREADPOOL_H_ */ readsb-3.16/toString.h000066400000000000000000000102371505057307600147000ustar00rootroot00000000000000#ifndef DUMP1090_TOSTRING_H #define DUMP1090_TOSTRING_H static inline const char *cpr_type_string(cpr_type_t type) { switch (type) { case CPR_SURFACE: return "Surface"; case CPR_AIRBORNE: return "Airborne"; case CPR_COARSE: return "TIS-B Coarse"; default: return "unknown CPR type"; } } static inline const char *addrtype_enum_string(addrtype_t type) { switch (type) { case ADDR_ADSB_ICAO: return "adsb_icao"; case ADDR_ADSB_ICAO_NT: return "adsb_icao_nt"; case ADDR_ADSR_ICAO: return "adsr_icao"; case ADDR_TISB_ICAO: return "tisb_icao"; case ADDR_JAERO: return "adsc"; case ADDR_MLAT: return "mlat"; case ADDR_OTHER: return "other"; case ADDR_MODE_S: return "mode_s"; case ADDR_ADSB_OTHER: return "adsb_other"; case ADDR_ADSR_OTHER: return "adsr_other"; case ADDR_TISB_TRACKFILE: return "tisb_trackfile"; case ADDR_TISB_OTHER: return "tisb_other"; case ADDR_MODE_A: return "mode_ac"; default: return "unknown"; } } static inline const char *emergency_enum_string(emergency_t emergency) { switch (emergency) { case EMERGENCY_NONE: return "none"; case EMERGENCY_GENERAL: return "general"; case EMERGENCY_LIFEGUARD: return "lifeguard"; case EMERGENCY_MINFUEL: return "minfuel"; case EMERGENCY_NORDO: return "nordo"; case EMERGENCY_UNLAWFUL: return "unlawful"; case EMERGENCY_DOWNED: return "downed"; default: return "reserved"; } } static inline const char *sil_type_enum_string(sil_type_t type) { switch (type) { case SIL_UNKNOWN: return "unknown"; case SIL_PER_HOUR: return "perhour"; case SIL_PER_SAMPLE: return "persample"; default: return "invalid"; } } static inline const char *source_enum_string(datasource_t src) { switch (src) { case SOURCE_INVALID: return "INVALID"; case SOURCE_INDIRECT: return "INDIRECT"; case SOURCE_MODE_AC: return "MODE_AC"; case SOURCE_SBS: return "SBS"; case SOURCE_MLAT: return "MLAT"; case SOURCE_MODE_S: return "MODE_S"; case SOURCE_JAERO: return "JAERO"; case SOURCE_MODE_S_CHECKED: return "MODE_S_CHECKED"; case SOURCE_TISB: return "TISB"; case SOURCE_ADSR: return "ADSR"; case SOURCE_ADSB: return "ADSB"; case SOURCE_PRIO: return "PRIO"; default: return "WTF"; } } /* static inline const char *source_enum_string_long(datasource_t src) { switch (src) { case SOURCE_INVALID: return "SOURCE_INVALID"; case SOURCE_INDIRECT: return "SOURCE_INDIRECT"; case SOURCE_MODE_AC: return "SOURCE_MODE_AC"; case SOURCE_SBS: return "SOURCE_SBS"; case SOURCE_MLAT: return "SOURCE_MLAT"; case SOURCE_MODE_S: return "SOURCE_MODE_S"; case SOURCE_JAERO: return "SOURCE_JAERO"; case SOURCE_MODE_S_CHECKED: return "SOURCE_MODE_S_CHECKED"; case SOURCE_TISB: return "SOURCE_TISB"; case SOURCE_ADSR: return "SOURCE_ADSR"; case SOURCE_ADSB: return "SOURCE_ADSB"; case SOURCE_PRIO: return "SOURCE_PRIO"; default: return "SOURCE_WTF"; } } */ static inline const char *nav_altitude_source_enum_string(nav_altitude_source_t src) { switch (src) { case NAV_ALT_INVALID: return "invalid"; case NAV_ALT_UNKNOWN: return "unknown"; case NAV_ALT_AIRCRAFT: return "aircraft"; case NAV_ALT_MCP: return "mcp"; case NAV_ALT_FMS: return "fms"; default: return "invalid"; } } static inline const char *airground_to_string(airground_t airground) { switch (airground) { case AG_GROUND: return "ground"; case AG_AIRBORNE: return "airborne"; case AG_INVALID: return "invalid"; case AG_UNCERTAIN: return "airborne?"; default: return "(unknown airground state)"; } } #endif readsb-3.16/track.c000066400000000000000000004317351505057307600142000ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // track.c: aircraft state tracking // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014-2016 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "readsb.h" uint32_t modeAC_count[4096]; uint32_t modeAC_lastcount[4096]; uint32_t modeAC_match[4096]; uint32_t modeAC_age[4096]; #define PPforward if (0) {fprintf(stderr, "%s %d\n", __FILE__, __LINE__);} static void showPositionDebug(struct aircraft *a, struct modesMessage *mm, int64_t now, double bad_lat, double bad_lon); static void position_bad(struct modesMessage *mm, struct aircraft *a); static void calc_wind(struct aircraft *a, int64_t now); static void calc_temp(struct aircraft *a, int64_t now); static inline int declination(struct aircraft *a, double *dec, int64_t now); static const char *source_string(datasource_t source); static void incrementReliable(struct aircraft *a, struct modesMessage *mm, int64_t now, int odd); static uint16_t simpleHash(uint64_t receiverId) { uint16_t simpleHash = receiverId; simpleHash ^= (uint16_t) (receiverId >> 16); simpleHash ^= (uint16_t) (receiverId >> 32); simpleHash ^= (uint16_t) (receiverId >> 48); if (simpleHash == 0) return 1; return simpleHash; } static float knots_to_meterpersecond = (1852.0 / 3600.0); static void calculateMessageRateGlobal(int64_t now) { float sum = 0.0f; float mult = REMOVE_STALE_INTERVAL / 1000.0f; float multSum = 0.0f; for (int k = 0; k < MESSAGE_RATE_CALC_POINTS; k++) { sum += Modes.messageRateAcc[k] * mult; multSum += mult; mult *= 0.7f; } Modes.messageRate = sum / multSum * Modes.messageRateMult; Modes.nextMessageRateCalc = now + REMOVE_STALE_INTERVAL; memmove(&Modes.messageRateAcc[1], &Modes.messageRateAcc[0], sizeof(uint32_t) * (MESSAGE_RATE_CALC_POINTS - 1)); Modes.messageRateAcc[0] = 0; //fprintf(stderr, "%.3f\n", Modes.messageRate); } static void calculateMessageRate(struct aircraft *a, int64_t now) { float sum = 0.0f; float mult = REMOVE_STALE_INTERVAL / 1000.0f; float multSum = 0.0f; for (int k = 0; k < MESSAGE_RATE_CALC_POINTS; k++) { sum += a->messageRateAcc[k] * mult; multSum += mult; mult *= 0.7f; } a->messageRate = sum / multSum * Modes.messageRateMult; a->nextMessageRateCalc = now + REMOVE_STALE_INTERVAL; memmove(&a->messageRateAcc[1], &a->messageRateAcc[0], sizeof(uint16_t) * (MESSAGE_RATE_CALC_POINTS - 1)); a->messageRateAcc[0] = 0; } // Should we accept some new data from the given source? // If so, update the validity and return 1 static inline int32_t currentReduceInterval(int64_t now) { MODES_NOTUSED(now); return Modes.net_output_beast_reduce_interval; } static inline int will_accept_data(data_validity *d, datasource_t source, struct modesMessage *mm, struct aircraft *a) { int64_t now = mm->sysTimestamp; if (source == SOURCE_INVALID) { return 0; } if (now < d->updated) { return 0; } // increase timeout to use lower quality data by the time no CRC reliable (addressReliable()) has been seen: // if (source < d->source && (now < d->updated + TRACK_STALE + (now - a->seen))) { // this simplifies to: // basically an reliable CRC needs to have been received TRACK_STALE (15s) after the higher // quality info was received if (source < d->source && (a->seen < d->updated + TRACK_STALE)) { return 0; } // this is a position and will be wholly reverted if it's not accepted //int is_pos = (mm->sbs_pos_valid || mm->cpr_valid || d == &a->pos_reliable_valid || d == &a->position_valid || d == &a->cpr_odd_valid || d == &a->cpr_even_valid || d == &a->mlat_pos_valid); int is_pos = (mm->sbs_pos_valid || mm->cpr_valid); // if we have a jaero position, don't allow non-position data to have a lesser source if (!is_pos && a->pos_reliable_valid.source == SOURCE_JAERO && source < SOURCE_JAERO) { return 0; } // prevent JAERO from disrupting other data sources too quickly if (source == SOURCE_JAERO && a->pos_reliable_valid.last_source != SOURCE_JAERO) { if (Modes.trackExpireJaero >= 10 * MINUTES && now < d->updated + 5 * MINUTES) { return 0; } } // if we have recent data and a recent position, only accept data from the last couple receivers that contributed a position // this hopefully reduces data jitter introduced by differing receiver latencies // it's important to excluded data coming in alongside positions, that extra data if accepted is discarded via the scratch mechanism if (Modes.netReceiverId && !is_pos && a->pos_reliable_valid.source >= SOURCE_TISB && now - d->updated < 5 * SECONDS && now - a->seenPosReliable < 2200 * MS) { uint16_t hash = simpleHash(mm->receiverId); uint32_t found = 0; for (int i = 0; i < RECEIVERIDBUFFER; i++) { found += (a->receiverIds[i] == hash); } if (!found) { return 0; } if (Modes.netIngest) { // ingest: forward all nonposition messages from receivers on the receiverIds list // receivers on the receiverIds list are the receivers with the lowest latency mm->reduce_forward = 1; } } if (0 && is_pos && a->addr == Modes.cpr_focus) { //fprintf(stderr, "%d %p %p\n", mm->duplicate, d, &a->pos_reliable_valid); fprintf(stderr, "%d %s", mm->duplicate, source_string(mm->source)); } return 1; } static int accept_data(data_validity *d, datasource_t source, struct modesMessage *mm, struct aircraft *a, int reduce_often) { if (!will_accept_data(d, source, mm, a)) { return 0; } int64_t now = mm->sysTimestamp; d->source = source; if (unlikely(source == SOURCE_PRIO)) { d->source = SOURCE_ADSB; } d->last_source = d->source; d->updated = now; d->stale = 0; if (now > d->next_reduce_forward || mm->reduce_forward) { int32_t reduceInterval = currentReduceInterval(now); if (reduce_often == REDUCE_DOUBLE) { reduceInterval = reduceInterval * 3 / 8; } else if (reduce_often == REDUCE_OFTEN) { reduceInterval = reduceInterval * 7 / 8; } else if (reduce_often == REDUCE_RARE) { reduceInterval = reduceInterval * 4; } if (mm->cpr_valid && reduceInterval > 7000) { // make sure global CPR stays possible even at high interval: reduceInterval = 7000; } d->next_reduce_forward = now + reduceInterval; mm->reduce_forward = 1; PPforward; } return 1; } // Given two datasources, produce a third datasource for data combined from them. static void combine_validity(data_validity *to, const data_validity *from1, const data_validity *from2, int64_t now) { if (from1->source == SOURCE_INVALID) { *to = *from2; return; } if (from2->source == SOURCE_INVALID) { *to = *from1; return; } to->source = (from1->source < from2->source) ? from1->source : from2->source; // the worse of the two input sources to->last_source = to->source; to->updated = (from1->updated > from2->updated) ? from1->updated : from2->updated; // the *later* of the two update times to->stale = (now > to->updated + TRACK_STALE); } static int compare_validity(const data_validity *lhs, const data_validity *rhs) { if (!lhs->stale && lhs->source > rhs->source) return 1; else if (!rhs->stale && lhs->source < rhs->source) return -1; else if (lhs->updated >= rhs->updated) return 1; else if (lhs->updated < rhs->updated) return -1; else return 0; } static void update_range_histogram(struct aircraft *a, int64_t now) { if (!Modes.outline_json) { return; } int rangeDirIval = (now * (RANGEDIRS_IVALS - 1) / Modes.range_outline_duration) % RANGEDIRS_IVALS; if (rangeDirIval != Modes.lastRangeDirHour) { //log_with_timestamp("rangeDirIval: %d", rangeDirIval); // when the current interval changes, reset the data for it memset(Modes.rangeDirs[rangeDirIval], 0, RANGEDIRS_BUCKETS * sizeof(struct distCoords)); Modes.lastRangeDirHour = rangeDirIval; } if (!a) { return; } double lat = a->lat; double lon = a->lon; double range = a->receiver_distance; int rangeDirDirection = ((int) nearbyint(a->receiver_direction)) % RANGEDIRS_BUCKETS; struct distCoords *current = &(Modes.rangeDirs[rangeDirIval][rangeDirDirection]); // if the position isn't proper reliable, only allow it if the range in that direction is increased by less than 25 nmi compared to the maximum of the last 24h if (range > current->distance && (a->pos_reliable_odd < 2 || a->pos_reliable_even < 2)) { float directionMax = 0; for (int i = 0; i < RANGEDIRS_IVALS; i++) { if (Modes.rangeDirs[i][rangeDirDirection].distance > directionMax) directionMax = Modes.rangeDirs[i][rangeDirDirection].distance; } directionMax += 50.0f * 1852.0f; // allow 50 nmi more than recorded for that direction in the last 24h if (range > directionMax && !Modes.debug_bogus && Modes.json_reliable > 0) { return; } //fprintf(stderr, "actual %.1f max %.1f\n", range / 1852.0f, (directionMax / 1852.0f)); } if (range > current->distance) { current->distance = range; current->lat = lat; current->lon = lon; if (trackDataValid(&a->baro_alt_valid) || !trackDataValid(&a->geom_alt_valid)) { current->alt = a->baro_alt; } else { current->alt = a->geom_alt; } } if (range > Modes.stats_current.distance_max) Modes.stats_current.distance_max = range; if (range < Modes.stats_current.distance_min) Modes.stats_current.distance_min = range; int bucket = round(range / Modes.maxRange * RANGE_BUCKET_COUNT); if (bucket < 0) bucket = 0; else if (bucket >= RANGE_BUCKET_COUNT) bucket = RANGE_BUCKET_COUNT - 1; ++Modes.stats_current.range_histogram[bucket]; } static int cpr_duplicate_check(int64_t now, struct aircraft *a, struct modesMessage *mm) { struct cpr_cache *cpr; uint32_t inCache = 0; uint32_t cpr_lat = mm->cpr_lat; uint32_t cpr_lon = mm->cpr_lon; uint64_t receiverId = mm->receiverId; for (int i = 0; i < CPR_CACHE; i++) { cpr = &a->cpr_cache[i]; if ( ( now - cpr->ts < 2 * SECONDS && cpr->cpr_lat == cpr_lat && cpr->cpr_lon == cpr_lon && cpr->receiverId != receiverId ) ) { inCache += 1; } } if (inCache > 0) { mm->duplicate = 1; return 1; } else { // CPR not yet known to cpr cache a->cpr_cache_index = (a->cpr_cache_index + 1) % CPR_CACHE; cpr = &a->cpr_cache[a->cpr_cache_index]; cpr->ts = now; cpr->cpr_lat = cpr_lat; cpr->cpr_lon = cpr_lon; cpr->receiverId = receiverId; return 0; } } static int duplicate_check(int64_t now, struct aircraft *a, double new_lat, double new_lon, struct modesMessage *mm) { if (mm->duplicate_checked || mm->duplicate) { // already checked return mm->duplicate; } mm->duplicate_checked = 1; // if the last position is older than 2 seconds we don't consider it a duplicate if (now > a->seen_pos + 2 * SECONDS) { return 0; } // duplicate if (a->lat == new_lat && a->lon == new_lon) { mm->duplicate = 1; return 1; } // if the previous position is older than 2 seconds we don't consider it a duplicate if (now > a->prev_pos_time + 2 * SECONDS) { return 0; } // duplicate (this happens either with some transponder or delayed data arrival due to odd / even CPR, not certain) if (a->prev_lat == new_lat && a->prev_lon == new_lon) { mm->duplicate = 1; return 1; } return 0; } static int uat2esnt_duplicate(int64_t now, struct aircraft *a, struct modesMessage *mm) { return ( mm->cpr_valid && mm->cpr_odd && mm->msgtype == 18 && (mm->timestamp == MAGIC_UAT_TIMESTAMP || mm->timestamp == 0) && now - a->seenPosReliable < 2500 ); } static int inDiscCache(int64_t now, struct aircraft *a, struct modesMessage *mm) { struct cpr_cache *disc; uint32_t inCache = 0; uint32_t cpr_lat = mm->cpr_lat; uint32_t cpr_lon = mm->cpr_lon; uint64_t receiverId = mm->receiverId; for (int i = 0; i < DISCARD_CACHE; i++) { disc = &a->disc_cache[i]; // don't decrement pos_reliable if we already got the same bad position within the last second // rate limit reliable decrement per receiver if ( ( now - disc->ts < 4 * SECONDS && disc->cpr_lat == cpr_lat && disc->cpr_lon == cpr_lon ) || ( now - disc->ts < 300 && disc->receiverId == receiverId ) ) { inCache += 1; } } if (inCache > 0) { return 1; } else { return 0; } } // return true if it's OK for the aircraft to have travelled from its last known position // to a new position at (lat,lon,surface) at a time of now. static int speed_check(struct aircraft *a, datasource_t source, double lat, double lon, struct modesMessage *mm, cpr_local_t cpr_local) { int64_t now = mm->sysTimestamp; int64_t elapsed = trackDataAge(now, &a->position_valid); int receiverRangeExceeded = 0; if (0 && mm->sbs_in && a->addr == Modes.cpr_focus) { fprintf(stderr, "."); } if (duplicate_check(now, a, lat, lon, mm)) { // don't use duplicate positions mm->pos_ignore = 1; // but count it as a received position towards receiver heuristics if (!Modes.userLocationRef) { receiverPositionReceived(a, mm, lat, lon, now); } if (elapsed > 200 && a->receiverId == mm->receiverId && (Modes.debug_cpr || Modes.debug_speed_check || a->addr == Modes.cpr_focus)) { // let speed_check continue for displaying this duplicate (at least for non-aggregated receivers) } else { // omit rest of speed check to save on cycles return 1; } } if (0 && (a->prev_lat == lat && a->prev_lon == lon) && (Modes.debug_cpr || Modes.debug_speed_check || a->addr == Modes.cpr_focus)) { fprintf(stderr, "%06x now - seen_pos %6.3f now - prev_pos_time %6.3f\n", a->addr, (now - a->seen_pos) / 1000.0, (now - a->prev_pos_time) / 1000.0 ); } if (mm->cpr_valid && inDiscCache(now, a, mm)) { mm->in_disc_cache = 1; } float distance = -1; float range = -1; float speed = -1; float transmitted_speed = -1; float calc_track = -1; int inrange; int override = 0; double oldLat = a->lat; double oldLon = a->lon; int surface = trackDataValid(&a->airground_valid) && a->airground == AG_GROUND && a->pos_surface && (!mm->cpr_valid || mm->cpr_type == CPR_SURFACE); // json_reliable == -1 disables the speed check if (Modes.json_reliable == -1 || mm->source == SOURCE_PRIO) { override = 1; } else if (bogus_lat_lon(lat, lon) || (mm->cpr_valid && mm->cpr_lat == 0 && mm->cpr_lon == 0) || ( mm->cpr_valid && (mm->cpr_lat == 0 || mm->cpr_lon == 0) && (a->position_valid.source < SOURCE_TISB || !posReliable(a)) ) ) { mm->pos_ignore = 1; // don't decrement pos_reliable } else if (a->pos_reliable_odd < 0.01f || a->pos_reliable_even < 0.01f) { override = 1; } else if (now - a->position_valid.updated > POS_RELIABLE_TIMEOUT) { override = 1; // no reference or older than 60 minutes, assume OK } else if (source > a->position_valid.source && source > a->position_valid.last_source && source > a->pos_reliable_valid.source) { override = 1; // data is better quality, OVERRIDE } else if (source > a->position_valid.source && a->position_valid.source == SOURCE_INDIRECT) { override = 1; // data is better quality, OVERRIDE } else if (source <= SOURCE_MLAT && elapsed > 45 * SECONDS) { override = 1; } else if (a->addr == 0xa19b53) { // Virgin SS2 override = 1; } if (mm->in_disc_cache) { override = 0; // don't override if in discard cache } if (trackDataValid(&a->gs_valid)) { // use the larger of the current and earlier speed speed = (a->gs_last_pos > a->gs) ? a->gs_last_pos : a->gs; // add 3 knots for every second we haven't known the speed and the position speed = speed + (3 * trackDataAge(now, &a->gs_valid)/1000.0f) + (3 * trackDataAge(now, &a->position_valid)/1000.0f); } else if (trackDataValid(&a->tas_valid)) { speed = a->tas * 4 / 3; } else if (trackDataValid(&a->ias_valid)) { speed = a->ias * 2; } transmitted_speed = speed; // find actual distance distance = greatcircle(oldLat, oldLon, lat, lon, 0); mm->distance_traveled = distance; float track_diff = -1; float track_bonus = 0; int64_t track_max_age = 5 * SECONDS; int64_t track_age = -1; float track = -1; if (trackDataAge(now, &a->track_valid) < track_max_age) { track = a->track; track_age = trackDataAge(now, &a->track_valid); } else if (trackDataAge(now, &a->true_heading_valid) < track_max_age) { track = a->true_heading; track_age = trackDataAge(now, &a->true_heading_valid); } if (distance > 2.5f) { calc_track = bearing(oldLat, oldLon, lat, lon); mm->calculated_track = calc_track; if (source != SOURCE_MLAT && track > -1 && trackDataAge(now, &a->position_valid) < 7 * SECONDS ) { track_diff = fabs(norm_diff(track - calc_track, 180)); } } if (track_diff > 70.0f && speed > 10) { mm->trackUnreliable = +1; } else if (track_diff > -1) { mm->trackUnreliable = -1; } if (!posReliable(a)) { // don't use track_diff for further calculations unless position is already reliable track_diff = -1; } if (speed < 0 || a->speedUnreliable > 8) { speed = surface ? 120 : 900; // guess } if (speed > 10 && track_diff > -1 && a->trackUnreliable < 8) { track_bonus = speed * (90.0f - track_diff) / 90.0f; track_bonus *= (surface ? 0.9f : 1.0f) * (1.0f - track_age / track_max_age); if (a->gs < 10) { // don't allow negative "bonus" below 10 knots speed track_bonus = fmaxf(0.0f, track_bonus); speed += 2; } speed += track_bonus; if (track_diff > 160) { mm->pos_old = 1; // don't decrement pos_reliable } // allow relatively big forward jumps if (speed > 40 && track_diff < 10) { range += 2e3; } } else { // Work out a reasonable speed to use: // current speed + 1/3 speed = speed * 1.3f; } if (surface) { range += 10; } else { range += 30; } // same TCP packet (2 ms), two positions from same receiver id, allow plenty of extra range if (elapsed < 2 && a->receiverId == mm->receiverId && source > SOURCE_MLAT) { range += 500; // 500 m extra in this case } // cap speed at 2000 knots .. speed = fmin(speed, 2000); if (source == SOURCE_MLAT) { speed *= 1.4; speed += 50; range += 250; } if (transmitted_speed < 0) { mm->speedUnreliable = -1; } else if (distance > 2.5f && (track_diff < 70 || track_diff == -1)) { if (distance <= range + (((float) elapsed + 50.0f) * (1.0f / 1000.0f)) * (transmitted_speed * knots_to_meterpersecond)) { mm->speedUnreliable = -1; } else if (distance > range + (((float) elapsed + 400.0f) * (1.0f / 1000.0f)) * (transmitted_speed * knots_to_meterpersecond)) { mm->speedUnreliable = +1; } } // plus distance covered at the given speed for the elapsed time + 0.2 seconds. range += (((float) elapsed + 200.0f) * (1.0f / 1000.0f)) * (speed * (1852.0f / 3600.0f)); inrange = (distance <= range); // don't allow going backwards in the air contrary to good track information // when only a short time has elapsed // when the receiverId differs // this mostly happens due to static range allowed if (!surface && a->gs > 10 && track_diff > 135 && elapsed < 2 * SECONDS && trackDataAge(now, &a->track_valid) < 2 * SECONDS && a->receiverId != mm->receiverId) { inrange = 0; } float backInTimeSeconds = 0; if (!inrange && a->gs > 10 && track_diff > 135 && trackDataAge(now, &a->gs_valid) < 10 * SECONDS) { backInTimeSeconds = distance / (a->gs * (1852.0f / 3600.0f)); } if (!Modes.userLocationRef && (inrange || override)) { if (receiverPositionReceived(a, mm, lat, lon, now) == RECEIVER_RANGE_BAD) { // far outside receiver area receiverRangeExceeded = 1; } } if (!Modes.userLocationRef && !override && mm->source == SOURCE_ADSB) { if (!receiverRangeExceeded && !inrange && (distance - range > 800 || backInTimeSeconds > 3) && track_diff > 45 && a->pos_reliable_odd >= Modes.position_persistence * 3 / 4 && a->pos_reliable_even >= Modes.position_persistence * 3 / 4 && a->trackUnreliable < 3 ) { struct receiver *r = receiverBad(mm->receiverId, a->addr, now); if (r && Modes.debug_garbage && r->badCounter > 6) { fprintf(stderr, "hex: %06x id: %016"PRIx64" #good: %6d #bad: %3.0f trackDiff: %3.0f: %7.2fkm/%7.2fkm in %4.1f s, max %4.0f kt\n", a->addr, r->id, r->goodCounter, r->badCounter, track_diff, distance / 1000.0, range / 1000.0, elapsed / 1000.0, speed ); } } } if ( ( ((!inrange && track_diff < 160) || (!surface && (a->speedUnreliable > 8 || a->trackUnreliable > 8))) && source == a->position_valid.source && source > SOURCE_MLAT && (Modes.debug_cpr || Modes.debug_speed_check) ) || (a->addr == Modes.cpr_focus && source >= a->position_valid.source) || (Modes.debug_maxRange && track_diff > 90) || (receiverRangeExceeded && Modes.debug_receiverRangeLimit) ) { if (uat2esnt_duplicate(now, a, mm) || (!inrange && !override && mm->in_disc_cache) || mm->garbage) { // don't show debug } else { char *failMessage; if (inrange) { failMessage = "pass"; } else if (override) { failMessage = "ovrd"; } else if (receiverRangeExceeded) { failMessage = "RcvR"; } else { failMessage = "FAIL"; } char uuid[32]; // needs 18 chars and null byte sprint_uuid1(mm->receiverId, uuid); fprintTime(stderr, now); fprintf(stderr, " %06x R%3.1f|%3.1f %s %2.0f %s %s %s %4.0f%%%2ds%2dt %3.0f/%3.0f td %3.0f %8.3fkm in%4.1fs, %4.0fkt %11.6f,%11.6f->%11.6f,%11.6f biT %4.1f s %s rId %s\n", a->addr, a->pos_reliable_odd, a->pos_reliable_even, mm->cpr_odd ? "O" : "E", mm->cpr_odd ? fmin(99, (now - a->cpr_even_valid.updated) / 1000.0) : fmin(99, (now - a->cpr_odd_valid.updated) / 1000.0), cpr_local == CPR_LOCAL ? "L" : (cpr_local == CPR_GLOBAL ? "G" : "S"), (surface ? "S" : "A"), failMessage, fmin(9001.0, 100.0 * distance / range), a->speedUnreliable, a->trackUnreliable, track, calc_track, track_diff, fmin(9001.0, distance / 1000.0), elapsed / 1000.0, fmin(9001.0, distance / elapsed * 1000.0 / 1852.0 * 3600.0), oldLat, oldLon, lat, lon, backInTimeSeconds, source_string(mm->source), uuid); } } // override, this allows for printing stuff instead of returning if (override) { if (!inrange) { a->lastOverrideTs = now; } inrange = override; } if (receiverRangeExceeded && Modes.garbage_ports) { mm->pos_receiver_range_exceeded = 1; inrange = 0; // far outside receiver area mm->pos_ignore = 1; mm->pos_bad = 1; } return inrange; } /* debug code for surface CPR decoding ... might be useful and reduce typing at some point if (Modes.debug_receiver && Modes.debug_speed_check && receiver && a->seen_pos && *lat < 89 && *lat > -89 && (fabs(a->lat - *lat) > 35 || fabs(a->lon - *lon) > 35 || fabs(reflat - *lat) > 35 || fabs(reflon - *lon) > 35) && !bogus_lat_lon(*lat, *lon) ) { //struct receiver *r = receiver; //fprintf(stderr, "id: %016"PRIx64" #pos: %9"PRIu64" lat min:%4.0f max:%4.0f lon min:%4.0f max:%4.0f\n", // r->id, r->positionCounter, // r->latMin, r->latMax, // r->lonMin, r->lonMax); int sc = speed_check(a, mm->source, *lat, *lon, mm, CPR_GLOBAL); fprintf(stderr, "%s%06x surface CPR rec. ref.: %4.0f %4.0f sc: %d result: %7.2f %7.2f --> %7.2f %7.2f\n", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : " ", a->addr, reflat, reflon, sc, a->lat, a->lon, *lat, *lon); } if (Modes.debug_receiver && receiver && a->addr == Modes.cpr_focus) fprintf(stderr, "%06x using reference: %4.0f %4.0f result: %7.2f %7.2f\n", a->addr, reflat, reflon, *lat, *lon); */ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, double *lon, unsigned *nic, unsigned *rc) { int result; int fflag = mm->cpr_odd; int surface = (mm->cpr_type == CPR_SURFACE); struct receiver *receiver; double reflat, reflon; // derive NIC, Rc from the worse of the two position // smaller NIC is worse; larger Rc is worse *nic = (a->cpr_even_nic < a->cpr_odd_nic ? a->cpr_even_nic : a->cpr_odd_nic); *rc = (a->cpr_even_rc > a->cpr_odd_rc ? a->cpr_even_rc : a->cpr_odd_rc); if (surface) { // surface global CPR // find reference location int ref = 0; if (Modes.userLocationRef) { reflat = Modes.fUserLat; reflon = Modes.fUserLon; ref = 1; } else if ((receiver = receiverGetReference(mm->receiverId, &reflat, &reflon, a, 0))) { //function sets reflat and reflon on success, nothing to do here. ref = 2; } else if (a->seen_pos && a->surfaceCPR_allow_ac_rel) { reflat = a->latReliable; reflon = a->lonReliable; ref = 3; } else { // No local reference, give up return (-1); } result = decodeCPRsurface(reflat, reflon, a->cpr_even_lat, a->cpr_even_lon, a->cpr_odd_lat, a->cpr_odd_lon, fflag, lat, lon); double refDistance = greatcircle(reflat, reflon, *lat, *lon, 0); if (refDistance > 450e3) { if (0 && (a->addr == Modes.cpr_focus || Modes.debug_cpr)) { fprintf(stderr, "%06x CPRsurface ref %d refDistance: %4.0f km (%4.0f, %4.0f) allow_ac_rel %d\n", a->addr, ref, refDistance / 1000.0, reflat, reflon, a->surfaceCPR_allow_ac_rel); } // change to failure which doesn't decrement reliable result = -1; return result; } } else { // airborne global CPR result = decodeCPRairborne(a->cpr_even_lat, a->cpr_even_lon, a->cpr_odd_lat, a->cpr_odd_lon, fflag, lat, lon); } if (result < 0) { if (!mm->duplicate && (a->addr == Modes.cpr_focus || Modes.debug_cpr) && !inDiscCache(mm->sysTimestamp, a, mm)) { int64_t now = mstime(); fprintTime(stderr, now); fprintf(stderr, "CPR: decode failure for %06x (%d): even: %6d %6d %4.1fs odd: %6d %6d %4.1fs fflag: %s\n", a->addr, result, a->cpr_even_lat, a->cpr_even_lon, fmin(999, ((double) now - a->cpr_even_valid.updated) / 1000.0), a->cpr_odd_lat, a->cpr_odd_lon, fmin(999, ((double) now - a->cpr_odd_valid.updated) / 1000.0), fflag ? "odd" : "even" ); } return result; } // check max range if (Modes.maxRange > 0 && Modes.userLocationValid) { mm->receiver_distance = greatcircle(Modes.fUserLat, Modes.fUserLon, *lat, *lon, 0); if (mm->receiver_distance > Modes.maxRange) { if (a->addr == Modes.cpr_focus || Modes.debug_bogus) { fprintf(stdout, "%5llu %5.1f Global range check failed: %06x %.3f,%.3f, max range %.1fkm, actual %.1fkm\n", (long long) mm->timestamp % 65536, 10 * log10(mm->signalLevel), a->addr, *lat, *lon, Modes.maxRange / 1000.0, mm->receiver_distance / 1000.0); } if (mm->source != SOURCE_MLAT) { Modes.stats_current.cpr_global_range_checks++; if (Modes.debug_maxRange) { showPositionDebug(a, mm, mm->sysTimestamp, *lat, *lon); } } return (-2); // we consider an out-of-range value to be bad data } } // check speed limit if (!speed_check(a, mm->source, *lat, *lon, mm, CPR_GLOBAL)) { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_global_speed_checks++; return -2; } return result; } static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, double *lon, unsigned *nic, unsigned *rc) { // relative CPR // find reference location double reflat, reflon; double range_limit = 0; int result; int fflag = mm->cpr_odd; int surface = (mm->cpr_type == CPR_SURFACE); int relative_to = 0; // aircraft(1) or receiver(2) relative if (fflag) { *nic = a->cpr_odd_nic; *rc = a->cpr_odd_rc; } else { *nic = a->cpr_even_nic; *rc = a->cpr_even_rc; } int64_t now = mm->sysTimestamp; if (now - a->seenPosReliable < 10 * MINUTES && a->localCPR_allow_ac_rel) { reflat = a->lat; reflon = a->lon; if (a->pos_nic < *nic) *nic = a->pos_nic; if (a->pos_rc < *rc) *rc = a->pos_rc; range_limit = 1852*100; // 100NM // 100 NM in the 10 minutes of position validity means 600 knots which // is fast but happens even for commercial airliners. // It's not a problem if this limitation fails every now and then. // A wrong relative position decode would require the aircraft to // travel 360-100=260 NM in the 10 minutes of position validity. // This is impossible for planes slower than 1560 knots/Mach 2.3 over the ground. // Thus this range limit combined with the 10 minutes of position // validity should not provide bad positions (1 cell away). relative_to = 1; } else if (!surface && Modes.userLocationRef) { reflat = Modes.fUserLat; reflon = Modes.fUserLon; // The cell size is at least 360NM, giving a nominal // max range of 180NM (half a cell). // // If the receiver range is more than half a cell // then we must limit this range further to avoid // ambiguity. (e.g. if we receive a position report // at 200NM distance, this may resolve to a position // at (200-360) = 160NM in the wrong direction) if (Modes.maxRange == 0) { return (-1); // Can't do receiver-centered checks at all } else if (Modes.maxRange <= 1852 * 180) { range_limit = Modes.maxRange; } else if (Modes.maxRange < 1852 * 360) { range_limit = (1852 * 360) - Modes.maxRange; } else { return (-1); // Can't do receiver-centered checks at all } relative_to = 2; } else { // No local reference, give up return (-1); } result = decodeCPRrelative(reflat, reflon, mm->cpr_lat, mm->cpr_lon, fflag, surface, lat, lon); if (result < 0) { return result; } // check range limit if (range_limit > 0) { double range = greatcircle(reflat, reflon, *lat, *lon, 0); if (range > range_limit) { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_local_range_checks++; return (-1); } } // check max range if (Modes.maxRange > 0 && Modes.userLocationValid) { mm->receiver_distance = greatcircle(Modes.fUserLat, Modes.fUserLon, *lat, *lon, 0); if (mm->receiver_distance > Modes.maxRange) { if (a->addr == Modes.cpr_focus || Modes.debug_bogus) { fprintf(stdout, "%5llu %5.1f Global range check failed: %06x %.3f,%.3f, max range %.1fkm, actual %.1fkm\n", (long long) mm->timestamp % 65536, 10 * log10(mm->signalLevel), a->addr, *lat, *lon, Modes.maxRange / 1000.0, mm->receiver_distance / 1000.0); } if (mm->source != SOURCE_MLAT) { Modes.stats_current.cpr_local_range_checks++; if (Modes.debug_maxRange) { showPositionDebug(a, mm, mm->sysTimestamp, *lat, *lon); } } return (-2); // we consider an out-of-range value to be bad data } } // check speed limit if (!speed_check(a, mm->source, *lat, *lon, mm, CPR_LOCAL)) { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_local_speed_checks++; return -2; } return relative_to; } static int64_t time_between(int64_t t1, int64_t t2) { if (t1 >= t2) return t1 - t2; else return t2 - t1; } static void addReceiverId(struct aircraft *a, struct modesMessage *mm, int64_t elapsed) { a->receiverIdsNext = (a->receiverIdsNext + 1) % RECEIVERIDBUFFER; a->receiverIds[a->receiverIdsNext] = simpleHash(mm->receiverId); int64_t advance = imin(RECEIVERIDBUFFER * 500, elapsed); int iterations = 0; while (advance > 750) { iterations++; // ADS-B positions nominally come in every 500 ms receiver id // buffer has 12 positions, if the positions don't come in // often enough we zero them so it's only roughly the receiver // ids for the last 6 seconds advance -= 500; a->receiverIdsNext = (a->receiverIdsNext + 1) % RECEIVERIDBUFFER; a->receiverIds[a->receiverIdsNext] = 0; } if (iterations > 20) { fprintf(stderr, "%06x\n", a->addr); } if (0 && a->addr == Modes.cpr_focus) { fprintf(stderr, "%u\n", simpleHash(mm->receiverId)); } uint16_t *set1 = a->receiverIds; uint16_t set2[RECEIVERIDBUFFER] = { 0 }; int div = 0; for (int k = 0; k < RECEIVERIDBUFFER; k++) { int unequal = 0; for (int j = 0; j < div; j++) { unequal += (set1[k] != set2[j]); } if (unequal == div && set1[k]) { set2[div++] = set1[k]; } } a->receiverCount = div; } static void setPosition(struct aircraft *a, struct modesMessage *mm, int64_t now) { if (0 && a->addr == Modes.cpr_focus) { showPositionDebug(a, mm, now, 0, 0); } // if we get the same position again but from an inferior source, assume it's delayed and treat as duplicate if (now < a->seen_pos + 10 * MINUTES && mm->source < a->position_valid.last_source && mm->distance_traveled < 20) { if (a->addr == Modes.cpr_focus) { fprintf(stderr, "%06x less than 20 m\n", a->addr); } mm->duplicate = 1; mm->pos_ignore = 1; } if (duplicate_check(now, a, mm->decoded_lat, mm->decoded_lon, mm)) { // don't use duplicate positions mm->pos_ignore = 1; } if (bogus_lat_lon(mm->decoded_lat, mm->decoded_lon)) { if (0 && (fabs(mm->decoded_lat) >= 90.0 || fabs(mm->decoded_lon) >= 180.0)) { fprintf(stderr, "%06x lat,lon out of bounds: %.2f,%.2f source: %s\n", a->addr, mm->decoded_lat, mm->decoded_lon, source_enum_string(mm->source)); } return; } // for UAT messages converted by uat2esnt each position becomes a odd / even message pair // only update the position for the odd message if we've recently seen a reliable position if (uat2esnt_duplicate(now, a, mm)) { return; } Modes.stats_current.pos_by_type[mm->addrtype]++; Modes.stats_current.pos_all++; // mm->pos_bad should never arrive here, handle it just in case if (mm->cpr_valid && (mm->garbage || mm->pos_bad)) { Modes.stats_current.pos_garbage++; return; } if (mm->client) { mm->client->positionCounter++; } #if defined(PRINT_UUIDS) { int64_t oldestTime = now; int64_t overwriteOlder = now - 60 * SECONDS; idTime *overwrite = &a->recentReceiverIds[0]; for (int i = 0; i < RECENT_RECEIVER_IDS; i++) { idTime *entry = &a->recentReceiverIds[i]; if (entry->id == mm->receiverId) { overwrite = entry; break; } // if we already found an entry to overwrite (older than 60 seconds) // then look no further for an entry to overwrite if (oldestTime > overwriteOlder && entry->time < oldestTime) { oldestTime = entry->time; overwrite = entry; } } overwrite->id = mm->receiverId; overwrite->time = now; } #endif if (mm->duplicate) { Modes.stats_current.pos_duplicate++; return; } a->receiverId = mm->receiverId; if (mm->source != SOURCE_JAERO && mm->distance_traveled >= 100) { if (mm->calculated_track != -1) a->calc_track = mm->calculated_track; else a->calc_track = bearing(a->lat, a->lon, mm->decoded_lat, mm->decoded_lon); } if (mm->source == SOURCE_JAERO && a->seenPosReliable) { if ((a->position_valid.last_source == SOURCE_JAERO || now > a->seenPosReliable + 10 * MINUTES) && mm->distance_traveled > 2e3) { if (accept_data(&a->track_valid, SOURCE_JAERO, mm, a, REDUCE_OFTEN)) { a->calc_track = a->track = bearing(a->latReliable, a->lonReliable, mm->decoded_lat, mm->decoded_lon); //fprintf(stderr, "%06x track %0.1f\n", a->addr, a->track); } } else if (trackDataAge(now, &a->track_valid) < 10 * MINUTES) { accept_data(&a->track_valid, SOURCE_JAERO, mm, a, REDUCE_OFTEN); } } // Update aircraft state a->prev_lat = a->lat; a->prev_lon = a->lon; a->prev_pos_time = a->seen_pos; a->lat = mm->decoded_lat; a->lon = mm->decoded_lon; a->pos_nic = mm->decoded_nic; a->pos_rc = mm->decoded_rc; a->seen_pos = now; a->pos_surface = trackDataValid(&a->airground_valid) && a->airground == AG_GROUND; // due to the position deduplication logic we won't put receivers // into the receiver list which aren't the first ones to send us the position if (!Modes.netReceiverId) { a->receiverCount = 1; } else { if (mm->source == SOURCE_MLAT && mm->receiverCountMlat) { a->receiverCount = mm->receiverCountMlat; } else { addReceiverId(a, mm, now - a->prev_pos_time); } } if (Modes.netReceiverId && posReliable(a)) { int64_t valid_elapsed = now - a->pos_reliable_valid.updated; int64_t override_elapsed = now - a->lastOverrideTs; int64_t status_elapsed = now - a->lastStatusTs; if ( (valid_elapsed > 10 * MINUTES || override_elapsed < 10 * MINUTES) && (mm->msgtype == 17 || (mm->addrtype == ADDR_ADSB_ICAO_NT && mm->cpr_type != CPR_SURFACE && !a->is_df18_exception && ((a->addr >= 0xa00000 && a->addr <= 0xafffff) || (a->dbFlags & (1 << 0))) )) && mm->cpr_valid && status_elapsed > 5 * MINUTES ) { double dist = greatcircle(a->latReliable, a->lonReliable, mm->decoded_lat, mm->decoded_lon, 0); double estimate = a->gs_reliable * knots_to_meterpersecond * (valid_elapsed * 1e-3); if (dist > 200e3 && fabs(estimate - dist) > 100e3) { a->lastStatusDiscarded++; if (((Modes.debug_lastStatus & 1) && a->lastStatusDiscarded > 8) || (Modes.debug_lastStatus & 4)) { char uuid[32]; // needs 18 chars and null byte sprint_uuid1(mm->receiverId, uuid); fprintf(stderr, "%06x dist: %4d status_e: %4d valid_e: %4d over_e: %4d rCount: %d uuid: %s dbFlags: %u DF: %d\n", a->addr, (int) imin(9999, (dist / 1000)), (int) imin(9999, (status_elapsed / 1000)), (int) imin(9999, (valid_elapsed / 1000)), (int) imin(9999, (override_elapsed / 1000)), a->receiverCount, uuid, a->dbFlags, mm->msgtype); } if (!(Modes.debug_lastStatus & 2)) { return; } } } } if (posReliable(a) && accept_data(&a->pos_reliable_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->lastStatusDiscarded = 0; // when accepting crappy positions, invalidate the data indicating position accuracy if (mm->source < SOURCE_TISB) { // a->nic_baro_valid.source = SOURCE_INVALID; a->nac_p_valid.source = SOURCE_INVALID; a->nac_v_valid.source = SOURCE_INVALID; a->sil_valid.source = SOURCE_INVALID; a->gva_valid.source = SOURCE_INVALID; a->sda_valid.source = SOURCE_INVALID; a->sil_type = SIL_INVALID; } set_globe_index(a, globe_index(a->lat, a->lon)); if (0 && a->addr == Modes.trace_focus) { fprintf(stderr, "%5.1fs traceAdd: %06x\n", (now % (600 * SECONDS)) / 1000.0, a->addr); } a->lastPosReceiverId = mm->receiverId; // update addrtype, we use the type from the accepted position. a->addrtype = mm->addrtype; a->addrtype_updated = now; if (now < a->seenPosReliable) { fprintf(stderr, "%06x now < seenPosReliable ??? mstime: %.3f now: %.3f seenPosReliabe: %.3f\n", a->addr, mstime() / 1000.0, now / 1000.0, a->seenPosReliable / 1000.0); } int stale = (now > a->seenPosReliable + TRACE_STALE); a->seenPosReliable = now; a->latReliable = mm->decoded_lat; a->lonReliable = mm->decoded_lon; a->pos_nic_reliable = mm->decoded_nic; a->pos_rc_reliable = mm->decoded_rc; a->surfaceCPR_allow_ac_rel = 1; // allow ac relative CPR for ground positions if (trackDataValid(&a->gs_valid)) { a->gs_reliable = a->gs; } if (trackDataValid(&a->track_valid)) { a->track_reliable = a->track; } traceAdd(a, mm, now, stale); if ( mm->source == SOURCE_ADSB && trackDataValid(&a->nac_p_valid) && a->nac_p >= 4 // 1 nmi ) { a->seenAdsbReliable = now; a->seenAdsbLat = mm->decoded_lat; a->seenAdsbLon = mm->decoded_lon; } if (Modes.userLocationValid) { if (mm->receiver_distance == 0) { mm->receiver_distance = greatcircle(Modes.fUserLat, Modes.fUserLon, a->lat, a->lon, 0); } a->receiver_distance = mm->receiver_distance; a->receiver_direction = bearing(Modes.fUserLat, Modes.fUserLon, a->lat, a->lon); // nac_p >= 2 accuracy better than 4 nmi // decoded_rc less than 5 * nmi if (mm->source == SOURCE_ADSB && trackDataValid(&a->nac_p_valid) && a->nac_p >= 2 && mm->decoded_rc != 0 && mm->decoded_rc < 5 * 1852 ) { update_range_histogram(a, now); } else if (mm->source == SOURCE_ADSR) { update_range_histogram(a, now); } } if (now > a->nextJsonPortOutput) { a->nextJsonPortOutput = now + Modes.net_output_json_interval; mm->jsonPositionOutputEmit = 1; } } if (0 && a->addr == Modes.cpr_focus) { fprintf(stderr, "%06x: reliability odd: %3.1f even: %3.1f status: %d\n", a->addr, a->pos_reliable_odd, a->pos_reliable_even, posReliable(a)); } } static int64_t cpr_global_airborne_max_elapsed(int64_t now, struct aircraft *a) { if (!trackDataValid(&a->gs_valid) || trackDataAge(now, &a->gs_valid) > 20 * SECONDS) { return 10 * SECONDS; } int speed = imax((int64_t) a->gs, 1); // max time for 500 knots gs int64_t ref = 19 * SECONDS; // empirically tested int64_t ival = (ref * 500) / speed; // never return more than 30 seconds ival = imin(30 * SECONDS, ival); //fprintf(stderr, "%lld\n", (long long) ival); return ival; } static void updatePosition(struct aircraft *a, struct modesMessage *mm, int64_t now) { int location_result = -1; int globalCPR = 0; int64_t max_elapsed; double new_lat = 0, new_lon = 0; unsigned new_nic = 0; unsigned new_rc = 0; int surface; surface = (mm->cpr_type == CPR_SURFACE); a->pos_surface = trackDataValid(&a->airground_valid) && a->airground == AG_GROUND; if (surface) { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_surface++; // Surface: 25 seconds if >25kt or speed unknown, 50 seconds otherwise if (mm->gs_valid && mm->gs.selected <= 25) max_elapsed = 50000; else max_elapsed = 25000; } else { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_airborne++; // Airborne: determine depending on speed, fallback 10 seconds max_elapsed = cpr_global_airborne_max_elapsed(now, a); } // If we have enough recent data, try global CPR if (trackDataValid(&a->cpr_odd_valid) && trackDataValid(&a->cpr_even_valid) && a->cpr_odd_valid.source == a->cpr_even_valid.source && a->cpr_odd_type == a->cpr_even_type && time_between(a->cpr_odd_valid.updated, a->cpr_even_valid.updated) <= max_elapsed) { location_result = doGlobalCPR(a, mm, &new_lat, &new_lon, &new_nic, &new_rc); if (0 && Modes.debug_cpr && location_result == -2) { fprintTime(stderr, now); fprintf(stderr, " mm->cpr: (%6d) (%6d) %s %s, %s age: %0.1f sources o: %s %s e: %s %s lpos src: %s \n", mm->cpr_lat, mm->cpr_lon, mm->cpr_odd ? " odd" : "even", cpr_type_string(mm->cpr_type), mm->cpr_odd ? "even" : " odd", mm->cpr_odd ? fmin(999, ((double) now - a->cpr_even_valid.updated) / 1000.0) : fmin(999, ((double) now - a->cpr_odd_valid.updated) / 1000.0), source_enum_string(a->cpr_odd_valid.source), cpr_type_string(a->cpr_odd_type), source_enum_string(a->cpr_even_valid.source), cpr_type_string(a->cpr_even_type), source_enum_string(a->position_valid.last_source)); } //if (a->addr == Modes.cpr_focus) // fprintf(stderr, "%06x globalCPR result: %d\n", a->addr, location_result); if (location_result == -2) { // Global CPR failed because the position produced implausible results. // This is bad data. if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_global_bad++; mm->pos_bad = 1; // still note which position we decoded mm->decoded_lat = new_lat; mm->decoded_lon = new_lon; } else if (location_result == -1) { if (a->addr == Modes.cpr_focus || Modes.debug_cpr) { if (mm->source == SOURCE_MLAT) { fprintf(stderr, "CPR skipped from MLAT (%06x).\n", a->addr); } } // No local reference for surface position available, or the two messages crossed a zone. // Nonfatal, try again later. if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_global_skipped++; } else { if (accept_data(&a->position_valid, mm->source, mm, a, REDUCE_DOUBLE)) { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_global_ok++; globalCPR = 1; } else { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_global_skipped++; location_result = -2; } } } // Otherwise try relative CPR. if (location_result == -1) { location_result = doLocalCPR(a, mm, &new_lat, &new_lon, &new_nic, &new_rc); //if (a->addr == Modes.cpr_focus) // fprintf(stderr, "%06x: localCPR: %d\n", a->addr, location_result); if (location_result == -2) { // Local CPR failed because the position produced implausible results. // This is bad data. mm->pos_bad = 1; // still note which position we decoded mm->decoded_lat = new_lat; mm->decoded_lon = new_lon; } else if (location_result >= 0 && accept_data(&a->position_valid, mm->source, mm, a, REDUCE_DOUBLE)) { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_local_ok++; mm->cpr_relative = 1; if (location_result == 1) { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_local_aircraft_relative++; } if (location_result == 2) { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_local_receiver_relative++; } } else { if (mm->source != SOURCE_MLAT) Modes.stats_current.cpr_local_skipped++; location_result = -1; } } if (location_result >= 0) { // If we sucessfully decoded, back copy the results to mm mm->cpr_decoded = 1; mm->decoded_lat = new_lat; mm->decoded_lon = new_lon; mm->decoded_nic = new_nic; mm->decoded_rc = new_rc; if (trackDataValid(&a->gs_valid)) a->gs_last_pos = a->gs; if (globalCPR) incrementReliable(a, mm, now, mm->cpr_odd); setPosition(a, mm, now); } else if (location_result == -1 && a->addr == Modes.cpr_focus && !mm->duplicate) { fprintTime(stderr, now); fprintf(stderr, " mm->cpr: (%d) (%d) %s %s, %s age: %0.1f sources o: %s %s e: %s %s lpos src: %s \n", mm->cpr_lat, mm->cpr_lon, mm->cpr_odd ? " odd" : "even", cpr_type_string(mm->cpr_type), mm->cpr_odd ? "even" : " odd", mm->cpr_odd ? fmin(999, ((double) now - a->cpr_even_valid.updated) / 1000.0) : fmin(999, ((double) now - a->cpr_odd_valid.updated) / 1000.0), source_enum_string(a->cpr_odd_valid.source), cpr_type_string(a->cpr_odd_type), source_enum_string(a->cpr_even_valid.source), cpr_type_string(a->cpr_even_type), source_enum_string(a->position_valid.last_source)); } } static unsigned compute_nic(unsigned metype, unsigned version, unsigned nic_a, unsigned nic_b, unsigned nic_c) { switch (metype) { case 5: // surface case 9: // airborne case 20: // airborne, GNSS altitude return 11; case 6: // surface case 10: // airborne case 21: // airborne, GNSS altitude return 10; case 7: // surface if (version == 2) { if (nic_a && !nic_c) { return 9; } else { return 8; } } else if (version == 1) { if (nic_a) { return 9; } else { return 8; } } else { return 8; } case 8: // surface if (version == 2) { if (nic_a && nic_c) { return 7; } else if (nic_a && !nic_c) { return 6; } else if (!nic_a && nic_c) { return 6; } else { return 0; } } else { return 0; } case 11: // airborne if (version == 2) { if (nic_a && nic_b) { return 9; } else { return 8; } } else if (version == 1) { if (nic_a) { return 9; } else { return 8; } } else { return 8; } case 12: // airborne return 7; case 13: // airborne return 6; case 14: // airborne return 5; case 15: // airborne return 4; case 16: // airborne if (nic_a && nic_b) { return 3; } else { return 2; } case 17: // airborne return 1; default: return 0; } } static unsigned compute_rc(unsigned metype, unsigned version, unsigned nic_a, unsigned nic_b, unsigned nic_c) { switch (metype) { case 5: // surface case 9: // airborne case 20: // airborne, GNSS altitude return 8; // 7.5m case 6: // surface case 10: // airborne case 21: // airborne, GNSS altitude return 25; case 7: // surface if (version == 2) { if (nic_a && !nic_c) { return 75; } else { return 186; // 185.2m, 0.1NM } } else if (version == 1) { if (nic_a) { return 75; } else { return 186; // 185.2m, 0.1NM } } else { return 186; // 185.2m, 0.1NM } case 8: // surface if (version == 2) { if (nic_a && nic_c) { return 371; // 370.4m, 0.2NM } else if (nic_a && !nic_c) { return 556; // 555.6m, 0.3NM } else if (!nic_a && nic_c) { return 926; // 926m, 0.5NM } else { return RC_UNKNOWN; } } else { return RC_UNKNOWN; } case 11: // airborne if (version == 2) { if (nic_a && nic_b) { return 75; } else { return 186; // 370.4m, 0.2NM } } else if (version == 1) { if (nic_a) { return 75; } else { return 186; // 370.4m, 0.2NM } } else { return 186; // 370.4m, 0.2NM } case 12: // airborne return 371; // 370.4m, 0.2NM case 13: // airborne if (version == 2) { if (!nic_a && nic_b) { return 556; // 555.6m, 0.3NM } else if (!nic_a && !nic_b) { return 926; // 926m, 0.5NM } else if (nic_a && nic_b) { return 1112; // 1111.2m, 0.6NM } else { return RC_UNKNOWN; // bad combination, assume worst Rc } } else if (version == 1) { if (nic_a) { return 1112; // 1111.2m, 0.6NM } else { return 926; // 926m, 0.5NM } } else { return 926; // 926m, 0.5NM } case 14: // airborne return 1852; // 1.0NM case 15: // airborne return 3704; // 2NM case 16: // airborne if (version == 2) { if (nic_a && nic_b) { return 7408; // 4NM } else { return 14816; // 8NM } } else if (version == 1) { if (nic_a) { return 7408; // 4NM } else { return 14816; // 8NM } } else { return 18520; // 10NM } case 17: // airborne return 37040; // 20NM default: return RC_UNKNOWN; } } // Map ADS-B v0 position message type to NACp value // returned computed NACp, or -1 if not a suitable message type static int compute_v0_nacp(struct modesMessage *mm) { if (mm->msgtype != 17 && mm->msgtype != 18) { return -1; } // ED-102A Table N-7 switch (mm->metype) { case 0: return 0; case 5: return 11; case 6: return 10; case 7: return 8; case 8: return 0; case 9: return 11; case 10: return 10; case 11: return 8; case 12: return 7; case 13: return 6; case 14: return 5; case 15: return 4; case 16: return 1; case 17: return 1; case 18: return 0; case 20: return 11; case 21: return 10; case 22: return 0; default: return -1; } } // Map ADS-B v0 position message type to SIL value // returned computed SIL, or -1 if not a suitable message type static int compute_v0_sil(struct modesMessage *mm) { if (mm->msgtype != 17 && mm->msgtype != 18) { return -1; } // ED-102A Table N-8 switch (mm->metype) { case 0: return 0; case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: return 2; case 18: return 0; case 20: case 21: return 2; case 22: return 0; default: return -1; } } static void compute_nic_rc_from_message(struct modesMessage *mm, struct aircraft *a, unsigned *nic, unsigned *rc) { int nic_a = (trackDataValid(&a->nic_a_valid) && a->nic_a); int nic_b = (mm->accuracy.nic_b_valid && mm->accuracy.nic_b); int nic_c = (trackDataValid(&a->nic_c_valid) && a->nic_c); *nic = compute_nic(mm->metype, a->adsb_version, nic_a, nic_b, nic_c); *rc = compute_rc(mm->metype, a->adsb_version, nic_a, nic_b, nic_c); } static int altitude_to_feet(int raw, altitude_unit_t unit) { switch (unit) { case UNIT_METERS: return raw / 0.3048; case UNIT_FEET: return raw; default: return 0; } } // check if we trust that this message is actually from the aircraft with this address // similar reasoning to icaoFilterAdd in mode_s.c static int addressReliable(struct modesMessage *mm) { if (mm->msgtype == 17 || mm->msgtype == 18 || (mm->msgtype == 11 && mm->IID == 0) || mm->sbs_in) { return 1; } return 0; } static inline void focusGroundstateChange(struct aircraft *a, struct modesMessage *mm, int arg, int64_t now, airground_t airground) { if (a->airground != mm->airground) { a->lastAirGroundChange = now; } //if (a->airground != mm->airground) { if (a->addr == Modes.cpr_focus && a->airground != airground) { fprintf(stderr, "%02d:%04.1f %06x Ground state change %d: Source: %s, %s -> %s\n", (int) (now / (60 * SECONDS) % 60), (now % (60 * SECONDS)) / 1000.0, a->addr, arg, source_enum_string(mm->source), airground_to_string(a->airground), airground_to_string(airground)); displayModesMessage(mm); } } static void updateAltitude(int64_t now, struct aircraft *a, struct modesMessage *mm) { int alt = altitude_to_feet(mm->baro_alt, mm->baro_alt_unit); if (a->modeC_hit) { int new_modeC = (a->baro_alt + 49) / 100; int old_modeC = (alt + 49) / 100; if (new_modeC != old_modeC) { a->modeC_hit = 0; } } int delta = alt - a->baro_alt; int fpm = 0; int max_fpm = 12500; int min_fpm = -12500; int lowDelta = 300; int old_reliable = a->alt_reliable; int wasReliable = altBaroReliable(a); // int64_t baroAge = trackDataAge(now, &a->baro_alt_valid); datasource_t baroSource = a->baro_alt_valid.source; int debug = 0; if (0 && a->position_valid.source == SOURCE_ADSB && !wasReliable) { debug = 1; } if (abs(delta) >= lowDelta) { fpm = delta*60*10/(abs((int)baroAge/100)+10); if (trackDataValid(&a->geom_rate_valid) && trackDataAge(now, &a->geom_rate_valid) < trackDataAge(now, &a->baro_rate_valid)) { min_fpm = a->geom_rate - 1500 - imin(11000, ((int)trackDataAge(now, &a->geom_rate_valid)/2)); max_fpm = a->geom_rate + 1500 + imin(11000, ((int)trackDataAge(now, &a->geom_rate_valid)/2)); } else if (trackDataValid(&a->baro_rate_valid)) { min_fpm = a->baro_rate - 1500 - imin(11000, ((int)trackDataAge(now, &a->baro_rate_valid)/2)); max_fpm = a->baro_rate + 1500 + imin(11000, ((int)trackDataAge(now, &a->baro_rate_valid)/2)); } if (trackDataValid(&a->baro_alt_valid) && baroAge < 30 * SECONDS) { a->alt_reliable = imin( ALTITUDE_BARO_RELIABLE_MAX - (ALTITUDE_BARO_RELIABLE_MAX*baroAge/(30 * SECONDS)), a->alt_reliable); } else { a->alt_reliable = 0; } } int good_crc = 0; // just trust messages with this source implicitely and rate the altitude as max reliable // if we get the occasional altitude excursion that's acceptable and preferable to not capturing implausible altitude changes for example before a crash if (mm->crc == 0 && mm->source > SOURCE_MODE_S_CHECKED) { // only trust implicitely if we are getting quick updates if (baroAge < 2 * SECONDS) { good_crc = ALTITUDE_BARO_RELIABLE_MAX; } else { good_crc = ALTITUDE_BARO_RELIABLE_MAX/3; } } if (mm->source == SOURCE_JAERO || mm->source == SOURCE_SBS) { // trust those data sources they are often slow to update and we want to show an altitude // for those good_crc = ALTITUDE_BARO_RELIABLE_MAX; } if (mm->source == SOURCE_MLAT) { if (mm->receiverCountMlat > 2) { // this is an mlat result with altitude from a version of mlat-server that hopefully // only sets the altitude when it was received from the plane via multiple receivers good_crc = ALTITUDE_BARO_RELIABLE_MAX/2 - 1; } else { // this is typically mlat results from mlat-client // this is a terrible altitude source, ignore it completely // better to show "no altitude" // exception: anonymous FA mlat results look a bit stupid without altitude // exception: when trying to visualize local MLAT results, make that work by using the // MLAT sbs input instead of the MLAT results as beast if (mm->sbs_in || (a->addr & MODES_NON_ICAO_ADDRESS)) { good_crc = 0; } else { // ignore altitude from this message completely return; } } } if (a->baro_alt > 50175 && mm->alt_q_bit && a->alt_reliable > ALTITUDE_BARO_RELIABLE_MAX/4) { good_crc = 0; //fprintf(stderr, "q_bit == 1 && a->alt > 50175: %06x\n", a->addr); goto discard_alt; } int reason = 0; if (abs(delta) < lowDelta) { reason = 1; goto accept_alt; } if (fpm < max_fpm && fpm > min_fpm) { reason = 2; goto accept_alt; } // accept the message if the good_crc score is better than the current alt reliable score if (good_crc >= a->alt_reliable) { a->alt_reliable = 0; // reset alt_reliable reason = 3; goto accept_alt; } // accept the altitude if the source is better than the current one if (mm->source > a->baro_alt_valid.source) { a->alt_reliable = 0; // reset alt_reliable reason = 4; goto accept_alt; } goto discard_alt; int score_add; accept_alt: if (Modes.netReceiverId && mm->source == SOURCE_MODE_S && now - a->baro_alt_valid.updated > 10 * SECONDS) { score_add = 0; } else { score_add = good_crc + 1; } if (mm->source == SOURCE_MODE_S && a->position_valid.source == SOURCE_MLAT && baroAge > 5 * SECONDS && trackDataAge(now, &a->position_valid) < 15 * SECONDS && a->receiverCount > 1) { // when running with certain mlat-server versions, set altitude to inreliable when we get them too infrequently score_add = -1; } if (accept_data(&a->baro_alt_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->alt_reliable = imin(ALTITUDE_BARO_RELIABLE_MAX , a->alt_reliable + score_add); if (a->alt_reliable < 0) { a->alt_reliable = 0; } // || wasReliable != altBaroReliable(a) if ( debug || (0 && baroAge < 60 * SECONDS && (abs(delta) > 5000 || alt > 50000)) ) { fprintf(stderr, "Alt check S: %06x: %2d -> %2d %6d ->%6d, %s->%s, min %5.1f kfpm, max %5.1f kfpm, actual %7.1f kfpm, reason %1d, reliable %1d\n", a->addr, old_reliable, a->alt_reliable, a->baro_alt, alt, source_string(baroSource), source_string(mm->source), min_fpm/1000.0, max_fpm/1000.0, fpm/1000.0, reason, altBaroReliable(a)); } a->baro_alt = alt; } return; discard_alt: if (!will_accept_data(&a->baro_alt_valid, mm->source, mm, a)) { return; } a->alt_reliable = a->alt_reliable - (good_crc+1); if ( debug || (Modes.debug_bogus && trackDataAge(now, &a->baro_rate_valid) < 20 * SECONDS && trackDataAge(now, &a->baro_alt_valid) < 20 * SECONDS ) ) { fprintf(stdout, "%6llx %5.1f Alt check F: %06x %2d %6d ->%6d, %s->%s, min %.1f kfpm, max %.1f kfpm, actual %.1f kfpm\n", (long long) mm->timestamp % 0x1000000, 10 * log10(mm->signalLevel), a->addr, a->alt_reliable, a->baro_alt, alt, source_string(a->baro_alt_valid.source), source_string(mm->source), min_fpm/1000.0, max_fpm/1000.0, fpm/1000.0); } if (a->alt_reliable <= 0) { //fprintf(stdout, "Altitude INVALIDATED: %06x\n", a->addr); a->alt_reliable = 0; if (a->pos_reliable_valid.source != SOURCE_JAERO) { a->baro_alt_valid.source = SOURCE_INVALID; } } if (Modes.garbage_ports) mm->source = SOURCE_INVALID; return; } static int accept_cpr(struct aircraft *a, struct modesMessage *mm) { // CPR, even if (mm->cpr_valid && !mm->cpr_odd && accept_data(&a->cpr_even_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->cpr_even_type = mm->cpr_type; a->cpr_even_lat = mm->cpr_lat; a->cpr_even_lon = mm->cpr_lon; compute_nic_rc_from_message(mm, a, &a->cpr_even_nic, &a->cpr_even_rc); if (0 && a->addr == Modes.cpr_focus) fprintf(stderr, "E \n"); return 1; } // CPR, odd if (mm->cpr_valid && mm->cpr_odd && accept_data(&a->cpr_odd_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->cpr_odd_type = mm->cpr_type; a->cpr_odd_lat = mm->cpr_lat; a->cpr_odd_lon = mm->cpr_lon; compute_nic_rc_from_message(mm, a, &a->cpr_odd_nic, &a->cpr_odd_rc); if (0 && a->addr == Modes.cpr_focus) fprintf(stderr, "O \n"); return 1; } return 0; } // //========================================================================= // // Receive new messages and update tracked aircraft state // struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) { struct aircraft *res = NULL; int64_t now = mm->sysTimestamp; ++Modes.stats_current.messages_total; Modes.messageRateAcc[0]++; if (now > Modes.nextMessageRateCalc) { calculateMessageRateGlobal(now); } if (0) { static int64_t lastPrint; static int msgAcc; int64_t printIval = 50; msgAcc++; if (now > lastPrint + printIval) { int64_t elapsed = now - lastPrint; double rate = msgAcc / (elapsed * 0.001); int width = rate / 20; unsigned char bar[1024]; for (int i = 0; i < (int) sizeof(bar); i++) { bar[i] = 219; // ascii block } if (width >= (int) sizeof(bar)) { width = (int) sizeof(bar) - 1; } bar[width] = '\0'; fprintTimePrecise(stderr, now); fprintf(stderr, " %4d %s\n", (int) rate, bar); //fprintf(stderr, "%5.0f\n", rate / 100); lastPrint = now; msgAcc = 0; } } if (mm->msgtype == DFTYPE_MODEAC) { // Mode A/C, just count it (we ignore SPI) modeAC_count[modeAToIndex(mm->squawkHex)]++; res = NULL; goto exit; } if (mm->decodeResult < 0) { res = NULL; goto exit; } struct aircraft *a; unsigned int cpr_new = 0; mm->calculated_track = -1; if (CHECK_APPROXIMATIONS) { // great circle random testing stuff ... for (int i = 0; i < 100; i++) { double la1 = 2 * random() / (double) INT_MAX - 1; double la2 = 2 * random() / (double) INT_MAX - 1; double lo1 = 2 * random() / (double) INT_MAX - 1; double lo2 = 2 * random() / (double) INT_MAX - 1; la1 *= 90; lo1 *= 180; la2 = la1 + 90 * la2; lo2 = lo1 + 90 * lo2; if (greatcircle(la1, lo1, la2, lo2, 0)) { } if (bearing(la1, lo1, la2, lo2)) { } } } mm->address_reliable = addressReliable(mm); // Lookup our aircraft or create a new one a = aircraftGet(mm->addr); if (!a) { // If it's a currently unknown aircraft.... if (mm->address_reliable) { a = aircraftCreate(mm->addr); // ., create a new record for it, } else { //fprintf(stderr, "%06x: !a && !addressReliable(mm)\n", mm->addr); res = NULL; goto exit; } } struct aircraft scratch; bool haveScratch = false; if (mm->cpr_valid || mm->sbs_pos_valid) { memcpy(&scratch, a, offsetof(struct aircraft, traceCache)); haveScratch = true; // messages from receivers classified garbage with position get processed to see if they still send garbage } else if (mm->garbage) { res = NULL; goto exit; } // only count the aircraft as "seen" for reliable messages with CRC if (mm->address_reliable) { int64_t elapsed_seen = now - a->seen; if (elapsed_seen > 5 * MINUTES) { Modes.stats_current.unique_aircraft++; if ( (elapsed_seen > 15 * MINUTES && a->addrtype != ADDR_JAERO) || (elapsed_seen > Modes.trackExpireJaero && a->addrtype == ADDR_JAERO) ) { // if an aircraft hasn't been active in a bit, reset its message count a->messages = 0; } } a->seen = now; if (now - a->seen_pos > 500 * RECEIVERIDBUFFER) { addReceiverId(a, mm, elapsed_seen); } } // don't use messages with unreliable CRC too long after receiving a reliable address from an aircraft if (now - a->seen > TRACK_STALE) { res = NULL; goto exit; } a->last_message_crc_fixed = (mm->correctedbits > 0) ? 1 : 0; a->messageRateAcc[0]++; if (now > a->nextMessageRateCalc) { calculateMessageRate(a, now); } if (mm->signalLevel > 0) { a->signalLevel[a->signalNext % 8] = mm->signalLevel; a->signalNext++; //fprintf(stderr, "%0.4f\n",mm->signalLevel); a->lastSignalTimestamp = now; } else { //fprintf(stderr, "signal zero: %06x; %s\n", a->addr, source_string(mm->source)); // if we haven't received a message with signal level for a bit, set it to zero if (now - a->lastSignalTimestamp > 15 * SECONDS && a->signalNext > 0) { a->signalNext = 0; //fprintf(stderr, "no_sig_thresh: %06x; %d; %d\n", a->addr, (int) a->no_signal_count, (int) a->signalNext); } } // reset to 100000 on overflow ... avoid any low message count checks if (a->messages == UINT32_MAX) a->messages = UINT16_MAX; a->messages++; if (mm->client) { if (!mm->garbage) { mm->client->messageCounter++; } mm->client->recentMessages++; } // update addrtype float newType = mm->addrtype == ADDR_MODE_S ? 4.5 : mm->addrtype; float oldType = a->addrtype == ADDR_MODE_S ? 4.5 : a->addrtype; // change type ranking without messing with enum :/ if ( (newType <= oldType && now - a->addrtype_updated > TRACK_EXPIRE * 3 / 4) || (newType > oldType && now - a->addrtype_updated > TRACK_EXPIRE * 3 / 2) ) { if (mm->addrtype == ADDR_ADSB_ICAO && a->position_valid.source != SOURCE_ADSB) { // don't set to ADS-B without a position if (mm->msgtype == 17) { a->addrtype = ADDR_MODE_S; // set type ModeS for DF17 messages when not knowing position a->addrtype_updated = now; } } else { a->addrtype = mm->addrtype; a->addrtype_updated = now; } if (a->addrtype > ADDR_ADSB_ICAO_NT) { a->adsb_version = -1; // reset ADS-B version if a non ADS-B message type is received } } // decide on where to stash the version int dummy_version = -1; // used for non-adsb/adsr/tisb messages int *message_version; switch (mm->source) { case SOURCE_ADSB: message_version = &a->adsb_version; break; case SOURCE_TISB: message_version = &a->tisb_version; break; case SOURCE_ADSR: message_version = &a->adsr_version; break; default: message_version = &dummy_version; break; } // assume version 0 until we see something else if (*message_version < 0) { *message_version = 0; } if (mm->category_valid) { a->category = mm->category; a->category_updated = now; } // operational status message // done early to update version / HRD / TAH if (mm->opstatus.valid) { *message_version = mm->opstatus.version; if (mm->opstatus.hrd != HEADING_INVALID) { a->adsb_hrd = mm->opstatus.hrd; } if (mm->opstatus.tah != HEADING_INVALID) { a->adsb_tah = mm->opstatus.tah; } } // fill in ADS-B v0 NACp, SIL from position message type if (*message_version == 0 && !mm->accuracy.nac_p_valid) { int computed_nacp = compute_v0_nacp(mm); if (computed_nacp != -1) { mm->accuracy.nac_p_valid = 1; mm->accuracy.nac_p = computed_nacp; } } if (*message_version == 0 && mm->accuracy.sil_type == SIL_INVALID) { int computed_sil = compute_v0_sil(mm); if (computed_sil != -1) { mm->accuracy.sil_type = SIL_UNKNOWN; mm->accuracy.sil = computed_sil; } } if (mm->baro_alt_valid && (mm->source >= a->baro_alt_valid.source || Modes.debug_bogus || (trackDataAge(now, &a->baro_alt_valid) > 10 * SECONDS && a->baro_alt_valid.source != SOURCE_JAERO && a->baro_alt_valid.source != SOURCE_SBS) || trackDataAge(now, &a->baro_alt_valid) > 30 * SECONDS ) ) { updateAltitude(now, a, mm); } if (mm->squawk_valid) { uint32_t oldsquawk = a->squawk; int changeTentative = 0; int changeActual = 0; if (a->squawkTentative != mm->squawkHex && now - a->seen < 15 * SECONDS && will_accept_data(&a->squawk_valid, mm->source, mm, a)) { PPforward; changeTentative = 1; } if ( (mm->source == SOURCE_JAERO || (a->squawkTentative == mm->squawkHex && now - a->squawkTentativeChanged > 250)) && accept_data(&a->squawk_valid, mm->source, mm, a, REDUCE_RARE)) { changeActual = 1; if (mm->squawkHex != a->squawk) { a->modeA_hit = 0; } a->squawk = mm->squawkHex; } // try and forward as soon as possible when the squawk changes // but not if the squawk is flipflopping very quickly if ((changeTentative || changeActual) && now - a->squawkTentativeChanged > currentReduceInterval(now)) { a->squawk_valid.next_reduce_forward = now + currentReduceInterval(now); mm->reduce_forward = 1; } if (changeTentative) { a->squawkTentative = mm->squawkHex; a->squawkTentativeChanged = now; } if (Modes.debug_squawk && (a->squawk == 0x7500 || a->squawk == 0x7600 || a->squawk == 0x7700 || a->squawkTentative == 0x7500 || a->squawkTentative == 0x7600 || a->squawkTentative == 0x7700 || oldsquawk == 0x7500 || oldsquawk == 0x7600 || oldsquawk == 0x7700) ) { char uuid[32]; // needs 18 chars and null byte sprint_uuid1(mm->receiverId, uuid); if (changeTentative) { if (1) { fprintf(stderr, "%06x DF: %02d a->squawk: %04x tentative %04x (receiverId: %s)\n", a->addr, mm->msgtype, a->squawk, mm->squawkHex, uuid); } } else { static int64_t antiSpam; if (now > antiSpam + 15 * SECONDS || oldsquawk != a->squawk) { antiSpam = now; fprintf(stderr, "%06x DF: %02d a->squawk: %04x ---> %04x (receiverId: %s)\n", a->addr, mm->msgtype, oldsquawk, a->squawk, uuid); } } } } if (mm->emergency_valid && accept_data(&a->emergency_valid, mm->source, mm, a, REDUCE_RARE)) { if (a->emergency != mm->emergency) { PPforward; } a->emergency = mm->emergency; } if (mm->geom_alt_valid && accept_data(&a->geom_alt_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->geom_alt = altitude_to_feet(mm->geom_alt, mm->geom_alt_unit); } if (mm->geom_delta_valid && accept_data(&a->geom_delta_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->geom_delta = mm->geom_delta; } if (mm->heading_valid) { heading_type_t htype = mm->heading_type; if (htype == HEADING_MAGNETIC_OR_TRUE) { htype = a->adsb_hrd; } else if (htype == HEADING_TRACK_OR_HEADING) { htype = a->adsb_tah; } if (htype == HEADING_GROUND_TRACK && accept_data(&a->track_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->track = mm->heading; } else if (htype == HEADING_MAGNETIC) { double dec; int err = declination(a, &dec, now); if (accept_data(&a->mag_heading_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->mag_heading = mm->heading; // don't accept more than 45 degree crab when deriving the true heading if ( (!trackDataValid(&a->track_valid) || fabs(norm_diff(mm->heading + dec - a->track, 180)) < 45) && !err && accept_data(&a->true_heading_valid, SOURCE_INDIRECT, mm, a, REDUCE_OFTEN) ) { a->true_heading = norm_angle(mm->heading + dec, 180); calc_wind(a, now); } } } else if (htype == HEADING_TRUE && accept_data(&a->true_heading_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->true_heading = mm->heading; } } if (mm->track_rate_valid && accept_data(&a->track_rate_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->track_rate = mm->track_rate; } if (mm->roll_valid && accept_data(&a->roll_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->roll = mm->roll; } if (mm->gs_valid) { mm->gs.selected = (*message_version == 2 ? mm->gs.v2 : mm->gs.v0); if (accept_data(&a->gs_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->gs = mm->gs.selected; } } if (mm->ias_valid && accept_data(&a->ias_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->ias = mm->ias; } if (mm->tas_valid && !(trackDataValid(&a->ias_valid) && mm->tas < a->ias) && accept_data(&a->tas_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->tas = mm->tas; calc_temp(a, now); calc_wind(a, now); } if (mm->mach_valid && accept_data(&a->mach_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->mach = mm->mach; calc_temp(a, now); } if (mm->baro_rate_valid && accept_data(&a->baro_rate_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->baro_rate = mm->baro_rate; } if (mm->geom_rate_valid && accept_data(&a->geom_rate_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->geom_rate = mm->geom_rate; } if ( mm->airground != AG_INVALID // only consider changing ground state if the message has information about ground state && (mm->source >= a->airground_valid.source || mm->source >= SOURCE_MODE_S_CHECKED || trackDataAge(now, &a->airground_valid) > 2 * TRACK_EXPIRE) // don't accept lower quality data until our state is 2 minutes old // usually lower quality data is allowed after 15 seconds by accept_data, this doesn't make sense for ground state ) { // If our current state is UNCERTAIN or INVALID, accept new data // If our current state is certain but new data is not, don't accept the new data if (a->airground == AG_INVALID || a->airground == AG_UNCERTAIN || mm->airground != AG_UNCERTAIN // also accept ground to air transitions for an uncertain ground state message if the plane is moving fast enough // and the baro altitude is in a reliable state || (mm->airground == AG_UNCERTAIN && a->airground == AG_GROUND && altBaroReliable(a) && trackDataAge(now, &a->gs_valid) < 3 * SECONDS && a->gs > 80 ) ) { if ( (a->airground == AG_AIRBORNE || a->airground == AG_GROUND) && (a->last_cpr_type == CPR_SURFACE || a->last_cpr_type == CPR_AIRBORNE) && trackDataAge(now, &a->cpr_odd_valid) < 20 * SECONDS && trackDataAge(now, &a->cpr_even_valid) < 20 * SECONDS && now < a->seenPosReliable + 20 * SECONDS && trackDataAge(now, &a->airground_valid) < 20 * SECONDS ) { // if we have very recent CPR / position data ... // those are more reliable in an aggregation situation, // ignore other airground status indication } else if (accept_data(&a->airground_valid, mm->source, mm, a, REDUCE_RARE)) { focusGroundstateChange(a, mm, 1, now, mm->airground); if (mm->airground != a->airground) { mm->reduce_forward = 1; PPforward; } a->airground = mm->airground; } } } if (mm->callsign_valid && accept_data(&a->callsign_valid, mm->source, mm, a, REDUCE_RARE)) { memcpy(a->callsign, mm->callsign, sizeof (a->callsign)); } if (mm->nav.mcp_altitude_valid && accept_data(&a->nav_altitude_mcp_valid, mm->source, mm, a, REDUCE_RARE)) { a->nav_altitude_mcp = mm->nav.mcp_altitude; } if (mm->nav.fms_altitude_valid && accept_data(&a->nav_altitude_fms_valid, mm->source, mm, a, REDUCE_RARE)) { a->nav_altitude_fms = mm->nav.fms_altitude; } if (mm->nav.altitude_source != NAV_ALT_INVALID && accept_data(&a->nav_altitude_src_valid, mm->source, mm, a, REDUCE_RARE)) { a->nav_altitude_src = mm->nav.altitude_source; } if (mm->nav.heading_valid && accept_data(&a->nav_heading_valid, mm->source, mm, a, REDUCE_RARE)) { a->nav_heading = mm->nav.heading; } if (mm->nav.modes_valid && accept_data(&a->nav_modes_valid, mm->source, mm, a, REDUCE_RARE)) { a->nav_modes = mm->nav.modes; } if (mm->nav.qnh_valid && accept_data(&a->nav_qnh_valid, mm->source, mm, a, REDUCE_RARE)) { a->nav_qnh = mm->nav.qnh; } if (mm->alert_valid && accept_data(&a->alert_valid, mm->source, mm, a, REDUCE_RARE)) { a->alert = mm->alert; } if (mm->spi_valid && accept_data(&a->spi_valid, mm->source, mm, a, REDUCE_RARE)) { a->spi = mm->spi; } if (mm->wind_valid) { a->wind_speed = a->wind_speed; a->wind_direction = a->wind_direction; a->wind_updated = now; a->wind_altitude = a->baro_alt; } if (mm->oat_valid) { a->oat = mm->oat; a->oat_updated = now; } if (mm->cpr_valid && accept_cpr(a, mm)) { cpr_new = 1; } if (mm->cpr_valid) { cpr_duplicate_check(now, a, mm); } if (!mm->duplicate && !mm->garbage) { int setLastStatus = 0; if (mm->msgtype == 17) { if ((mm->metype >= 1 && mm->metype <= 4) || mm->metype >= 23) { setLastStatus = 1; } } if (mm->msgtype == 11) { setLastStatus = 1; } if (!mm->squawk_valid) { if ((mm->msgtype == 0 || mm->msgtype == 4 || mm->msgtype == 20) && a->airground == AG_GROUND) { setLastStatus = 1; } } if (setLastStatus) { a->lastStatusTs = now; if (now > a->next_reduce_forward_status) { a->next_reduce_forward_status = now + 4 * currentReduceInterval(now); mm->reduce_forward = 1; PPforward; } } } if (Modes.beast_reduce_optimize_mlat) { if (mm->cpr_valid || a->position_valid.source < SOURCE_ADSR) { mm->reduce_forward = 1; } } if (mm->acas_ra_valid) { unsigned char *bytes = NULL; if (mm->msgtype == 16) { bytes = mm->MV; } else if (mm->metype == 28 && mm->mesub == 2) { bytes = mm->ME; } else { bytes = mm->MB; } if (bytes && (checkAcasRaValid(bytes, mm, 0))) { if (accept_data(&a->acas_ra_valid, mm->source, mm, a, REDUCE_OFTEN)) { if (memcmp(a->acas_ra, bytes, sizeof(a->acas_ra)) != 0) { mm->reduce_forward = 1; PPforward; } memcpy(a->acas_ra, bytes, sizeof(a->acas_ra)); logACASInfoShort(mm->addr, bytes, a, mm, mm->sysTimestamp); } } else if (bytes && Modes.debug_ACAS && checkAcasRaValid(bytes, mm, 1) && (getbit(bytes, 9) || getbit(bytes, 27) || getbit(bytes, 28))) { // getbit checks for ARA/RAT/MTE, at least one must be set logACASInfoShort(mm->addr, bytes, a, mm, mm->sysTimestamp); } } if (mm->accuracy.sda_valid && accept_data(&a->sda_valid, mm->source, mm, a, REDUCE_RARE)) { a->sda = mm->accuracy.sda; } if (mm->accuracy.nic_a_valid && accept_data(&a->nic_a_valid, mm->source, mm, a, REDUCE_RARE)) { a->nic_a = mm->accuracy.nic_a; } if (mm->accuracy.nic_c_valid && accept_data(&a->nic_c_valid, mm->source, mm, a, REDUCE_RARE)) { a->nic_c = mm->accuracy.nic_c; } if (mm->accuracy.nic_baro_valid && accept_data(&a->nic_baro_valid, mm->source, mm, a, REDUCE_RARE)) { a->nic_baro = mm->accuracy.nic_baro; } if (mm->accuracy.nac_p_valid && accept_data(&a->nac_p_valid, mm->source, mm, a, REDUCE_RARE)) { a->nac_p = mm->accuracy.nac_p; } if (mm->accuracy.nac_v_valid && accept_data(&a->nac_v_valid, mm->source, mm, a, REDUCE_RARE)) { a->nac_v = mm->accuracy.nac_v; } if (mm->accuracy.sil_type != SIL_INVALID && accept_data(&a->sil_valid, mm->source, mm, a, REDUCE_RARE)) { a->sil = mm->accuracy.sil; if (a->sil_type == SIL_INVALID || mm->accuracy.sil_type != SIL_UNKNOWN) { a->sil_type = mm->accuracy.sil_type; } } if (mm->accuracy.gva_valid && accept_data(&a->gva_valid, mm->source, mm, a, REDUCE_RARE)) { a->gva = mm->accuracy.gva; } if (mm->accuracy.sda_valid && accept_data(&a->sda_valid, mm->source, mm, a, REDUCE_RARE)) { a->sda = mm->accuracy.sda; } // Now handle derived data // derive geometric altitude if we have baro + delta if ( (a->baro_alt_valid.updated > a->geom_alt_valid.updated || a->geom_delta_valid.updated > a->geom_alt_valid.updated) && altBaroReliable(a) && compare_validity(&a->baro_alt_valid, &a->geom_alt_valid) > 0 && trackDataValid(&a->geom_delta_valid) && a->geom_delta_valid.source >= a->geom_alt_valid.source) { // Baro is more recent than geometric, derive geometric from baro + delta mm->geom_alt = a->baro_alt + a->geom_delta; mm->geom_alt_unit = UNIT_FEET; mm->geom_alt_derived = 1; a->geom_alt = mm->geom_alt; combine_validity(&a->geom_alt_valid, &a->baro_alt_valid, &a->geom_delta_valid, now); } // to keep the barometric altitude consistent with geometric altitude, save a derived geom_delta if all data are current if (mm->geom_alt_valid && a->geom_alt_valid.updated > a->geom_delta_valid.updated && altBaroReliable(a) && trackDataAge(now, &a->baro_alt_valid) < 1 * SECONDS && accept_data(&a->geom_delta_valid, mm->source, mm, a, REDUCE_OFTEN) ) { combine_validity(&a->geom_delta_valid, &a->baro_alt_valid, &a->geom_alt_valid, now); a->geom_delta = a->geom_alt - a->baro_alt; } // If we've got a new cpr_odd or cpr_even if (cpr_new) { // this is in addition to the normal air / ground handling // making especially sure we catch surface -> airborne transitions if (mm->addrtype >= a->addrtype) { //if (a->last_cpr_type == CPR_SURFACE && mm->cpr_type == CPR_AIRBORNE if (mm->cpr_type == CPR_AIRBORNE && (a->last_cpr_type == CPR_SURFACE || mm->airground == AG_AIRBORNE) && accept_data(&a->airground_valid, mm->source, mm, a, REDUCE_RARE)) { focusGroundstateChange(a, mm, 2, now, AG_AIRBORNE); if (mm->airground != a->airground) { mm->reduce_forward = 1; PPforward; } a->airground = AG_AIRBORNE; } // old or shitty transponders can continue sending CPR_AIRBORNE while on the ground // but the risk to wrongly show good transponders on the ground is too damn great // thus set AG_UNCERTAIN if we get CPR_AIRBORNE and are currently AG_GROUND if (mm->cpr_type == CPR_AIRBORNE && mm->airground == AG_UNCERTAIN && a->airground == AG_GROUND && accept_data(&a->airground_valid, mm->source, mm, a, REDUCE_RARE)) { focusGroundstateChange(a, mm, 2, now, AG_UNCERTAIN); if (mm->airground != a->airground) { mm->reduce_forward = 1; PPforward; } a->airground = AG_UNCERTAIN; } if (mm->cpr_type == CPR_SURFACE && accept_data(&a->airground_valid, mm->source, mm, a, REDUCE_RARE)) { focusGroundstateChange(a, mm, 2, now, AG_GROUND); if (mm->airground != a->airground) { mm->reduce_forward = 1; PPforward; } a->airground = AG_GROUND; } } updatePosition(a, mm, now); if (0 && a->addr == Modes.cpr_focus) { fprintf(stderr, "%06x: age: odd %"PRIu64" even %"PRIu64"\n", a->addr, trackDataAge(mm->sysTimestamp, &a->cpr_odd_valid), trackDataAge(mm->sysTimestamp, &a->cpr_even_valid)); } } if (mm->sbs_in && mm->sbs_pos_valid) { int old_jaero = 0; if (mm->source == SOURCE_JAERO && a->trace_len > 0) { spinLock(&a->traceLock); for (int i = imax(0, a->trace_current_len - 10); i < a->trace_current_len; i++) { if ( (int32_t) (mm->decoded_lat * 1E6) == getState(a->trace_current, i)->lat && (int32_t) (mm->decoded_lon * 1E6) == getState(a->trace_current, i)->lon ) old_jaero = 1; } spinRelease(&a->traceLock); } if (Modes.maxRange > 0 && Modes.userLocationValid) { mm->receiver_distance = greatcircle(Modes.fUserLat, Modes.fUserLon, mm->decoded_lat, mm->decoded_lon, 0); } if (old_jaero) { // avoid using already received positions for JAERO input } else if (mm->receiver_distance > Modes.maxRange && mm->source != SOURCE_JAERO) { // ignore positions out of receiver range unless it's jaero } else if (mm->source == SOURCE_MLAT && mm->mlatEPU > 2 * a->mlatEPU && imin((int)(3000.0f * logf((float)mm->mlatEPU / (float)a->mlatEPU)), TRACE_STALE * 3 / 4) > (int64_t) trackDataAge(mm->sysTimestamp, &a->pos_reliable_valid) ) { // don't use less accurate MLAT positions unless some time has elapsed // only works with SBS input MLAT data coming from some versions of mlat-server //fprintf(stderr, "%06x: kasdhflkdshf\n", a->addr); } else { if (mm->source == SOURCE_MLAT && accept_data(&a->mlat_pos_valid, mm->source, mm, a, REDUCE_OFTEN)) { if (0 && greatcircle(a->mlat_lat, a->mlat_lon, mm->decoded_lat, mm->decoded_lon, 1) > 5000) { a->mlat_pos_valid.source = SOURCE_INVALID; } a->mlat_lat = mm->decoded_lat; a->mlat_lon = mm->decoded_lon; if (mm->mlatEPU) { a->mlatEPU += 0.5 * mm->mlatEPU - a->mlatEPU; } if (0 && a->pos_reliable_valid.source > SOURCE_MLAT) { fprintf(stderr, "%06x: %d\n", a->addr, mm->reduce_forward); } } int usePosition = 0; //fprintf(stderr, "%06x: mlat pos diff: %6.0f\n", a->addr, greatcircle(a->latReliable, a->lonReliable, mm->decoded_lat, mm->decoded_lon, 1)); // if (mm->source == SOURCE_MLAT && now - a->lastMlatForce > Modes.mlatForceInterval && a->pos_reliable_valid.source > SOURCE_MLAT && greatcircle(a->latReliable, a->lonReliable, mm->decoded_lat, mm->decoded_lon, 1) > Modes.mlatForceDistance ) { a->lastMlatForce = now; usePosition = 1; // force accept_data a->position_valid.source = SOURCE_INVALID; a->pos_reliable_valid.source = SOURCE_INVALID; int res = accept_data(&a->position_valid, mm->source, mm, a, REDUCE_OFTEN); incrementReliable(a, mm, now, 3); // force reliable if (0) { fprintTime(stderr, now); fprintf(stderr, " %06x: mlat force %d\n", a->addr, res); } } else if ( mm->source == SOURCE_MLAT && now - a->seenPosReliable > TRACK_STALE && accept_data(&a->position_valid, mm->source, mm, a, REDUCE_OFTEN) ) { // no speed check for MLAT data if position older than TRACK_STALE usePosition = 1; } else if (!speed_check(a, mm->source, mm->decoded_lat, mm->decoded_lon, mm, CPR_NONE)) { mm->pos_bad = 1; // speed check failed, do nothing } else if (accept_data(&a->position_valid, mm->source, mm, a, REDUCE_DOUBLE)) { usePosition = 1; } if (usePosition) { incrementReliable(a, mm, now, 2); setPosition(a, mm, now); } } } if (mm->msgtype == 11 && mm->IID == 0 && mm->correctedbits == 0) { double reflat; double reflon; struct receiver *r = receiverGetReference(mm->receiverId, &reflat, &reflon, a, 1); if (r) { if (now - a->rr_seen < 600 * SECONDS && fabs(a->lon - reflon) < 5 && fabs(a->lon - reflon) < 5) { a->rr_lat = 0.1 * reflat + 0.9 * a->rr_lat; a->rr_lon = 0.1 * reflon + 0.9 * a->rr_lon; } else { a->rr_lat = reflat; a->rr_lon = reflon; } a->rr_seen = now; if (Modes.debug_rough_receiver_location) { if ( (a->position_valid.last_source == SOURCE_INDIRECT && trackDataAge(now, &a->position_valid) > TRACK_EXPIRE_ROUGH - 30 * SECONDS) || (a->position_valid.last_source != SOURCE_INDIRECT && a->position_valid.source == SOURCE_INVALID) ) { if (accept_data(&a->position_valid, SOURCE_INDIRECT, mm, a, REDUCE_OFTEN)) { a->addrtype_updated = now; a->addrtype = ADDR_MODE_S; if (0 && a->position_valid.last_source > SOURCE_INDIRECT && a->position_valid.source == SOURCE_INVALID) { mm->decoded_lat = a->lat; mm->decoded_lon = a->lon; } else { mm->decoded_lat = a->rr_lat; mm->decoded_lon = a->rr_lon; } set_globe_index(a, globe_index(mm->decoded_lat, mm->decoded_lon)); setPosition(a, mm, now); } } } } } if (mm->msgtype == 17) { int oldNogpsCounter = a->nogpsCounter; if ( // no position info or uncertainty >= 4 nmi (mm->metype == 0 || (mm->accuracy.nac_p_valid && mm->accuracy.nac_p <= 2) ) && a->gs > 50 && a->airground != AG_GROUND && now < a->seenAdsbReliable + NOGPS_DWELL && now > a->seenAdsbReliable + 10 * SECONDS && a->nogpsCounter < NOGPS_MAX ) { a->nogpsCounter++; } if (a->nogpsCounter > 0) { if (now > a->seenAdsbReliable + NOGPS_DWELL) { a->nogpsCounter = 0; } // position info during last 10 seconds and uncertainty <= 1 nmi if (mm->source == SOURCE_ADSB && mm->accuracy.nac_p_valid && mm->accuracy.nac_p >= 4 && now < a->seenAdsbReliable + 10 * SECONDS) { a->nogpsCounter--; } } if (Modes.debug_nogps && oldNogpsCounter != a->nogpsCounter) { fprintf(stderr, "%06x nogps %d -> %d\n", a->addr, oldNogpsCounter, a->nogpsCounter); } } if (mm->msgtype == 11 && mm->IID == 0 && mm->correctedbits == 0) { if (Modes.net_output_json_include_nopos && now > a->nextJsonPortOutput && now - a->seenPosReliable > 10 * SECONDS && now - a->seenPosReliable > 2 * Modes.net_output_json_interval) { a->nextJsonPortOutput = now + Modes.net_output_json_interval; mm->jsonPositionOutputEmit = 1; } // forward DF0/DF11 every 2 * beast_reduce_interval for beast_reduce if (now > a->next_reduce_forward_DF11) { a->next_reduce_forward_DF11 = now + 4 * currentReduceInterval(now); mm->reduce_forward = 1; PPforward; } } if (0 && a->addr == Modes.cpr_focus && mm->cpr_valid) { displayModesMessage(mm); } if (cpr_new) { a->last_cpr_type = mm->cpr_type; } if (haveScratch && (mm->garbage || mm->pos_bad || mm->duplicate)) { memcpy(a, &scratch, offsetof(struct aircraft, traceCache)); } if (!(mm->source < a->position_valid.source || mm->in_disc_cache || mm->garbage || mm->pos_ignore || mm->pos_receiver_range_exceeded)) { if (mm->pos_bad) { position_bad(mm, a); } a->speedUnreliable += mm->speedUnreliable; a->trackUnreliable += mm->trackUnreliable; a->speedUnreliable = imax(0, imin(16, a->speedUnreliable)); a->trackUnreliable = imax(0, imin(16, a->trackUnreliable)); } if (!a->onActiveList && includeAircraftJson(now, a)) { updateValidities(a, now); ca_add(&Modes.aircraftActive, a); a->onActiveList = 1; //fprintf(stderr, "active len %d\n", Modes.aircraftActive.len); } if (mm->reduce_forward) { // don't reduce forward duplicate / garbage / bad positions when garbage ports is active if ((mm->duplicate || mm->garbage || mm->pos_bad) && Modes.garbage_ports) { mm->reduce_forward = 0; } if (Modes.beast_reduce_filter_distance != -1 && now < a->seenPosReliable + 1 * MINUTES && Modes.userLocationValid && greatcircle(Modes.fUserLat, Modes.fUserLon, a->latReliable, a->lonReliable, 0) > Modes.beast_reduce_filter_distance) { mm->reduce_forward = 0; //fprintf(stderr, "%.0f %0.f\n", greatcircle(Modes.fUserLat, Modes.fUserLon, a->latReliable, a->lonReliable, 0) / 1852.0, Modes.beast_reduce_filter_distance / 1852.0); } if (Modes.beast_reduce_filter_altitude != -1 && altBaroReliable(a) && a->airground != AG_GROUND && a->baro_alt > Modes.beast_reduce_filter_altitude) { mm->reduce_forward = 0; //fprintf(stderr, "%.0f %.0f\n", (double) a->baro_alt, Modes.beast_reduce_filter_altitude); } } // forward all CPRs to the apex for faster garbage detection and such // even the duplicates and the garbage // unless garbage ports are configured if (Modes.netIngest && mm->cpr_valid && !(Modes.garbage_ports && mm->garbage)) { mm->reduce_forward = 1; PPforward; } mm->aircraft = a; res = a; struct aircraft *ac; exit: ac = res; //fprintf(stderr, "epoch: %.6f\n", mm->sysTimestamp / 1000.0); // In non-interactive non-quiet mode, display messages on standard output if ( !Modes.quiet || (Modes.show_only != BADDR && (mm->addr == Modes.show_only || mm->maybe_addr == Modes.show_only)) || (Modes.debug_7700 && ac && ac->squawk == 0x7700 && trackDataValid(&ac->squawk_valid)) ) { // filter messages with unwanted DF types (sbs_in are unknown DF type, filter them all, this is arbitrary but no one cares anyway) if (!(Modes.filterDF && (mm->sbs_in || !(Modes.filterDFbitset & (1 << mm->msgtype))))) { displayModesMessage(mm); } } if (Modes.debug_bogus) { if (!Modes.synthetic_now) { Modes.startup_time = mstime() - mm->timestamp / 12000U; } Modes.synthetic_now = Modes.startup_time + mm->timestamp / 12000U; if (mm->addr != HEX_UNKNOWN && !(mm->addr & MODES_NON_ICAO_ADDRESS)) { ac = aircraftCreate(mm->addr); } if (ac && ac->messages == 1 && ac->registration[0] == 0) { fprintf(stdout, "%6llx %5.1f not in DB: %06x\n", (long long) mm->timestamp % 0x1000000, 10 * log10(mm->signalLevel), mm->addr); //displayModesMessage(mm); } else if (0 && !ac) { if (mm->addr != HEX_UNKNOWN && !dbGet(mm->addr, Modes.dbIndex)) displayModesMessage(mm); if (mm->addr == HEX_UNKNOWN && !dbGet(mm->maybe_addr, Modes.dbIndex)) displayModesMessage(mm); } } return res; } // // Periodic updates of tracking state // // Periodically match up mode A/C results with mode S results void trackMatchAC(int64_t now) { // clear match flags for (unsigned i = 0; i < 4096; ++i) { modeAC_match[i] = 0; } // scan aircraft list, look for matches struct craftArray *ca = &Modes.aircraftActive; struct aircraft *a; ca_lock_read(ca); for (int i = 0; i < ca->len; i++) { a = ca->list[i]; if (a == NULL) continue; if ((now - a->seen) > 5000) { continue; } // match on Mode A if (trackDataValid(&a->squawk_valid)) { unsigned i = modeAToIndex(a->squawk); if ((modeAC_count[i] - modeAC_lastcount[i]) >= TRACK_MODEAC_MIN_MESSAGES) { a->modeA_hit = 1; modeAC_match[i] = (modeAC_match[i] ? 0xFFFFFFFF : a->addr); } } // match on Mode C (+/- 100ft) if (trackDataValid(&a->baro_alt_valid)) { int modeC = (a->baro_alt + 49) / 100; unsigned modeA = modeCToModeA(modeC); unsigned i = modeAToIndex(modeA); if (modeA && (modeAC_count[i] - modeAC_lastcount[i]) >= TRACK_MODEAC_MIN_MESSAGES) { a->modeC_hit = 1; modeAC_match[i] = (modeAC_match[i] ? 0xFFFFFFFF : a->addr); } modeA = modeCToModeA(modeC + 1); i = modeAToIndex(modeA); if (modeA && (modeAC_count[i] - modeAC_lastcount[i]) >= TRACK_MODEAC_MIN_MESSAGES) { a->modeC_hit = 1; modeAC_match[i] = (modeAC_match[i] ? 0xFFFFFFFF : a->addr); } modeA = modeCToModeA(modeC - 1); i = modeAToIndex(modeA); if (modeA && (modeAC_count[i] - modeAC_lastcount[i]) >= TRACK_MODEAC_MIN_MESSAGES) { a->modeC_hit = 1; modeAC_match[i] = (modeAC_match[i] ? 0xFFFFFFFF : a->addr); } } } ca_unlock_read(ca); // reset counts for next time for (unsigned i = 0; i < 4096; ++i) { if (!modeAC_count[i]) continue; if ((modeAC_count[i] - modeAC_lastcount[i]) < TRACK_MODEAC_MIN_MESSAGES) { if (++modeAC_age[i] > 15) { // not heard from for a while, clear it out modeAC_lastcount[i] = modeAC_count[i] = modeAC_age[i] = 0; } } else { // this one is live // set a high initial age for matches, so they age out rapidly // and don't show up on the interactive display when the matching // mode S data goes away or changes if (modeAC_match[i]) { modeAC_age[i] = 10; } else { modeAC_age[i] = 0; } } modeAC_lastcount[i] = modeAC_count[i]; } } /* static void updateAircraft() { for (int j = 0; j < Modes.acBuckets; j++) { for (struct aircraft *a = Modes.aircraft[j]; a; a = a->next) { } } } */ // //========================================================================= // // If we don't receive new nessages within TRACK_AIRCRAFT_TTL // we remove the aircraft from the list. // static void removeStaleRange(void *arg, threadpool_threadbuffers_t * buffer_group) { readsb_task_t *info = (readsb_task_t *) arg; int64_t now = info->now; //fprintf(stderr, "%9d %9d %9d\n", info->from, info->to, Modes.acBuckets); // timeout for aircraft with position int64_t posTimeout = now - 1 * HOURS; // timeout for non-ICAO aircraft with position int64_t nonIcaoPosTimeout = now - 30 * MINUTES; if (Modes.writeTraces) { posTimeout = now - 26 * HOURS; nonIcaoPosTimeout = now - 26 * HOURS; } if (Modes.state_dir && !Modes.userLocationRef) { posTimeout = now - 6 * 28 * 24 * HOURS; // 6 months nonIcaoPosTimeout = now - 26 * HOURS; } if (Modes.debug_rough_receiver_location) { posTimeout = now - 2 * 24 * HOURS; nonIcaoPosTimeout = now - 26 * HOURS; } // aircraft with valid position are never pruned (fix for very long jaero-timeout settings) // timeout for aircraft without position int64_t noposTimeout = now - 5 * MINUTES; int64_t jaeroTimeout = now - Modes.trackExpireJaero; for (int j = info->from; j < info->to; j++) { struct aircraft **nextPointer = &(Modes.aircraft[j]); while (*nextPointer) { struct aircraft *a = *nextPointer; if ( ((!a->seenPosReliable && a->seen < noposTimeout) || ( a->seenPosReliable && (a->seenPosReliable < posTimeout || ((a->addr & MODES_NON_ICAO_ADDRESS) && a->seenPosReliable < nonIcaoPosTimeout)) ) ) && (a->addrtype != ADDR_JAERO || a->seen < jaeroTimeout) ) { // Count aircraft where we saw only one message before reaping them. // These are likely to be due to messages with bad addresses. if (a->messages == 1) Modes.stats_current.single_message_aircraft++; if (a->addr == Modes.cpr_focus) fprintf(stderr, "del: %06x seen: %.1f seen_pos: %.1f\n", a->addr, (now - a->seen) / 1000.0, (now - a->seen_pos) / 1000.0); // Remove the element from the linked list *nextPointer = a->next; freeAircraft(a); } else { if (Modes.keep_traces) { traceMaintenance(a, now, &buffer_group->buffers[0]); } nextPointer = &(a->next); } } } } static void activeUpdateRange(void *arg, threadpool_threadbuffers_t * buffer_group) { readsb_task_t *info = (readsb_task_t *) arg; int64_t now = info->now; struct craftArray *ca = &Modes.aircraftActive; for (int i = info->from; i < info->to; i++) { struct aircraft *a = ca->list[i]; if (!a) { continue; } updateValidities(a, now); if (Modes.keep_traces) { traceMaintenance(a, now, &buffer_group->buffers[0]); } } //fprintf(stderr, "%9d %9d %9d\n", info->from, info->to, ca->len); } // update active Aircraft static void activeUpdate(int64_t now) { struct craftArray *ca = &Modes.aircraftActive; pthread_mutex_lock(&ca->change_mutex); for (int i = 0; i < ca->len; i++) { struct aircraft *a = ca->list[i]; if (!a) { continue; } if (!includeAircraftJson(now, a) && now - a->seen > TRACK_EXPIRE_LONG + 1 * MINUTES) { a->onActiveList = 0; quickRemove(a); if (a->globe_index >= 0) { set_globe_index(a, -5); } // we have the lock and are already scannign the array, remove without ca_remove() // also keep this array compact if (i == ca->len - 1) { ca->list[i] = NULL; ca->len--; } else if (ca->list[ca->len - 1]) { ca->list[i] = ca->list[ca->len - 1]; ca->list[ca->len - 1] = NULL; ca->len--; i--; // take a step back continue; } } } quickInit(); pthread_mutex_unlock(&ca->change_mutex); } // run activeUpdate and remove stale aircraft for a fraction of the entire hashtable void trackRemoveStale(int64_t now) { struct timespec watch; startWatch(&watch); // update range histogram to progressively clear it if no positions are coming in update_range_histogram(NULL, now); if (now > Modes.nextMessageRateCalc) { calculateMessageRateGlobal(now); } // update the active aircraft list //fprintf(stderr, "activeUpdate\n"); activeUpdate(now); int64_t elapsed1 = lapWatch(&watch); int taskCount; threadpool_task_t *tasks; readsb_task_t *infos; taskCount = imin(Modes.allPoolSize, Modes.allTasks->task_count); tasks = Modes.allTasks->tasks; infos = Modes.allTasks->infos; // tasks to maintain validities / traces in active list struct craftArray *ca = &Modes.aircraftActive; int section_len = ca->len / taskCount; int extra = ca->len % taskCount; // assign tasks for (int i = 0; i < taskCount; i++) { threadpool_task_t *task = &tasks[i]; readsb_task_t *range = &infos[i]; range->now = now; range->from = i * section_len + imin(extra, i); range->to = range->from + section_len + (i < extra ? 1 : 0); if (range->to > ca->len || (i == taskCount - 1 && range->to != ca->len)) { range->to = ca->len; fprintf(stderr, "check trackRemoveStale distribution\n"); } task->function = activeUpdateRange; task->argument = range; //fprintf(stderr, "%d %d\n", range->from, range->to); } ca_lock_read(ca); threadpool_run(Modes.allPool, tasks, taskCount); ca_unlock_read(ca); int64_t elapsed2 = lapWatch(&watch); // don't mix tasks above and below // don't mix tasks above and below // don't mix tasks above and below // tasks to maintain aircraft in list of all aircraft static int part = 0; // iterate entire list of aircraft roughly every minute // (assuming this runs every second which it should) int n_parts = 64 * taskCount; section_len = Modes.acBuckets / n_parts; extra = Modes.acBuckets % n_parts; //fprintf(stderr, "part %d\n", part); // assign tasks for (int i = 0; i < taskCount; i++) { threadpool_task_t *task = &tasks[i]; readsb_task_t *range = &infos[i]; range->now = now; range->from = part * section_len + imin(extra, part); range->to = range->from + section_len + (part < extra ? 1 : 0); if (range->to > Modes.acBuckets || (part == n_parts - 1 && range->to != Modes.acBuckets)) { range->to = Modes.acBuckets; fprintf(stderr, "check trackRemoveStale distribution\n"); } task->function = removeStaleRange; task->argument = range; //fprintf(stderr, "%d %d\n", range->from, range->to); if (++part >= n_parts) { //fprintf(stderr, "removestaleRange finished\n"); part = 0; } } //fprintf(stderr, "removeStaleRange start\n"); // run tasks threadpool_run(Modes.allPool, tasks, taskCount); //fprintf(stderr, "removeStaleRange done\n"); int64_t elapsed3 = lapWatch(&watch); if (elapsed1 + elapsed2 + elapsed3 > 25) { fprintf(stderr, "trackRemoveStale elapsed: %4ld %4ld %4ld\n", (long) elapsed1, (long) elapsed2, (long) elapsed3); } } /* static void adjustExpire(struct aircraft *a, int64_t timeout) { #define F(f,s,e) do { a->f##_valid.stale_interval = (s) * 1000; a->f##_valid.expire_interval = (e) * 1000; } while (0) F(callsign, 60, timeout); // ADS-B or Comm-B F(baro_alt, 15, timeout); // ADS-B or Mode S F(altitude_geom, 30, timeout); // ADS-B only F(geom_delta, 30, timeout); // ADS-B only F(gs, 30, timeout); // ADS-B or Comm-B F(ias, 30, timeout); // ADS-B (rare) or Comm-B F(tas, 30, timeout); // ADS-B (rare) or Comm-B F(mach, 30, timeout); // Comm-B only F(track, 30, timeout); // ADS-B or Comm-B F(track_rate, 30, timeout); // Comm-B only F(roll, 30, timeout); // Comm-B only F(mag_heading, 30, timeout); // ADS-B (rare) or Comm-B F(true_heading, 30, timeout); // ADS-B only (rare) F(baro_rate, 30, timeout); // ADS-B or Comm-B F(geom_rate, 30, timeout); // ADS-B or Comm-B F(squawk, 15, timeout); // ADS-B or Mode S F(airground, 15, timeout); // ADS-B or Mode S F(nav_qnh, 30, timeout); // Comm-B only F(nav_altitude_mcp, 30, timeout); // ADS-B or Comm-B F(nav_altitude_fms, 30, timeout); // ADS-B or Comm-B F(nav_altitude_src, 30, timeout); // ADS-B or Comm-B F(nav_heading, 30, timeout); // ADS-B or Comm-B F(nav_modes, 30, timeout); // ADS-B or Comm-B F(cpr_odd, 10, timeout); // ADS-B only F(cpr_even, 10, timeout); // ADS-B only F(position, 10, timeout); // ADS-B only F(nic_a, 30, timeout); // ADS-B only F(nic_c, 30, timeout); // ADS-B only F(nic_baro, 30, timeout); // ADS-B only F(nac_p, 30, timeout); // ADS-B only F(nac_v, 30, timeout); // ADS-B only F(sil, 30, timeout); // ADS-B only F(gva, 30, timeout); // ADS-B only F(sda, 30, timeout); // ADS-B only #undef F } */ static void calc_wind(struct aircraft *a, int64_t now) { uint32_t focus = 0xc0ffeeba; if (a->addr == focus) fprintf(stderr, "%"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64"\n", trackDataAge(now, &a->tas_valid), trackDataAge(now, &a->true_heading_valid), trackDataAge(now, &a->gs_valid), trackDataAge(now, &a->track_valid)); if (!trackDataValid(&a->position_valid) || a->airground == AG_GROUND) return; if (now < a->wind_updated + 1 * SECONDS) { // don't do wind calculation more often than necessary, precision isn't THAT good anyhow return; } if (trackDataAge(now, &a->tas_valid) > TRACK_WT_TIMEOUT || trackDataAge(now, &a->gs_valid) > TRACK_WT_TIMEOUT || trackDataAge(now, &a->track_valid) > TRACK_WT_TIMEOUT / 2 || trackDataAge(now, &a->true_heading_valid) > TRACK_WT_TIMEOUT / 2 ) { return; } // don't use this code for now /* if (a->trace && a->trace_len >= 2) { struct state *last = &(a->trace[a->trace_len-1]); if (now + 1500 < last->timestamp) last = &(a->trace[a->trace_len-2]); float track_diff = fabs(a->track - last->track / 10.0); if (last->track_valid && track_diff > 0.5) return; } */ double trk = (M_PI / 180) * a->track; double hdg = (M_PI / 180) * a->true_heading; double tas = a->tas; double gs = a->gs; double crab = norm_diff(hdg - trk, M_PI); double hw = tas - cos(crab) * gs; double cw = sin(crab) * gs; double ws = sqrt(hw * hw + cw * cw); double wd = hdg + atan2(cw, hw); wd = norm_angle(wd, M_PI); wd *= (180 / M_PI); crab *= (180 / M_PI); //if (a->addr == focus) //fprintf(stderr, "%06x: %.1f %.1f %.1f %.1f %.1f\n", a->addr, ws, wd, gs, tas, crab); if (ws > 250) { // Filter out wildly unrealistic wind speeds return; } a->wind_speed = ws; a->wind_direction = wd; a->wind_updated = now; a->wind_altitude = a->baro_alt; } static void calc_temp(struct aircraft *a, int64_t now) { if (a->airground == AG_GROUND) return; if (trackDataAge(now, &a->tas_valid) > TRACK_WT_TIMEOUT || trackDataAge(now, &a->mach_valid) > TRACK_WT_TIMEOUT) return; if (a->mach < 0.395) return; double fraction = a->tas / 661.47 / a->mach; double oat = (fraction * fraction * 288.15) - 273.15; double tat = -273.15 + ((oat + 273.15) * (1 + 0.2 * a->mach * a->mach)); a->oat = oat; a->tat = tat; a->oat_updated = now; } static inline int declination(struct aircraft *a, double *dec, int64_t now) { // only update delination every 30 seconds (per plane) // it doesn't change that much assuming the plane doesn't move huge distances in that time if (now < a->updatedDeclination + 5 * SECONDS) { *dec = a->magneticDeclination; return 0; } double year; time_t now_t = now/1000; struct tm utc; gmtime_r(&now_t, &utc); year = 1900.0 + utc.tm_year + utc.tm_yday / 365.0; double dip; double ti; double gv; int res = geomag_calc(a->baro_alt * 0.0003048, a->lat, a->lon, year, dec, &dip, &ti, &gv); if (res) { *dec = 0.0; } else { a->updatedDeclination = now; a->magneticDeclination = *dec; } return res; } /* { int64_t timestamp:48; // 16 bits of flags int32_t lat; int32_t lon; uint16_t gs; uint16_t track; int16_t baro_alt; int16_t baro_rate; int16_t geom_alt; int16_t geom_rate; unsigned ias:12; int roll:12; addrtype_t addrtype:5; int padding:3; #if defined(TRACKS_UUID) uint64_t receiverId; */ void to_state(struct aircraft *a, struct state *new, int64_t now, int on_ground, float track, int stale) { memset(new, 0, sizeof(struct state)); new->timestamp = now; new->lat = (int32_t) nearbyint(a->latReliable * 1E6); new->lon = (int32_t) nearbyint(a->lonReliable * 1E6); new->stale = stale; if (on_ground) new->on_ground = 1; if (trackVState(now, &a->gs_valid, &a->pos_reliable_valid)) { new->gs_valid = 1; new->gs = (uint16_t) nearbyint(a->gs * _gs_factor); } if (track > -1) { new->track = (uint16_t) nearbyint(track * _track_factor); new->track_valid = 1; } if (altBaroReliableTrace(now, a)) { new->baro_alt_valid = 1; new->baro_alt = (int16_t) nearbyint(a->baro_alt * _alt_factor); } if (trackVState(now, &a->baro_rate_valid, &a->pos_reliable_valid)) { new->baro_rate_valid = 1; new->baro_rate = (int16_t) nearbyint(a->baro_rate * _rate_factor); } if (trackVState(now, &a->geom_alt_valid, &a->pos_reliable_valid)) { new->geom_alt_valid = 1; new->geom_alt = (int16_t) nearbyint(a->geom_alt * _alt_factor); } if (trackVState(now, &a->geom_rate_valid, &a->pos_reliable_valid)) { new->geom_rate_valid = 1; new->geom_rate = (int16_t) nearbyint(a->geom_rate * _rate_factor); } /* unsigned ias:12; int roll:12; addrtype_t addrtype:5; */ if (trackVState(now, &a->ias_valid, &a->pos_reliable_valid)) { new->ias = a->ias; new->ias_valid = 1; } if (trackVState(now, &a->roll_valid, &a->pos_reliable_valid)) { new->roll = (int16_t) nearbyint(a->roll * _roll_factor); new->roll_valid = 1; } new->addrtype = a->addrtype; #if defined(TRACKS_UUID) new->receiverId = (uint32_t) (a->lastPosReceiverId >> 32); #endif } void to_state_all(struct aircraft *a, struct state_all *new, int64_t now) { memset(new, 0, sizeof(struct state_all)); for (int i = 0; i < 8; i++) new->callsign[i] = a->callsign[i]; new->pos_nic = a->pos_nic_reliable; new->pos_rc = a->pos_rc_reliable; new->tas = a->tas; new->squawk = a->squawk; new->category = a->category; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset new->nav_altitude_mcp = (int16_t) nearbyint(a->nav_altitude_mcp / 4.0f); new->nav_altitude_fms = (int16_t) nearbyint(a->nav_altitude_fms / 4.0f); new->nav_qnh = (int16_t) nearbyint(a->nav_qnh * 10.0f); new->mach = (int16_t) nearbyint(a->mach * 1000.0f); new->track_rate = (int16_t) nearbyint(a->track_rate * 100.0f); new->mag_heading = (uint16_t) nearbyint(a->mag_heading * _track_factor); new->true_heading = (uint16_t) nearbyint(a->true_heading * _track_factor); new->nav_heading = (uint16_t) nearbyint(a->nav_heading * _track_factor); new->emergency = a->emergency; new->airground = a->airground; new->nav_modes = a->nav_modes; new->nav_altitude_src = a->nav_altitude_src; new->sil_type = a->sil_type; if (now < a->wind_updated + TRACK_EXPIRE && abs(a->wind_altitude - a->baro_alt) < 500) { new->wind_direction = (int) nearbyint(a->wind_direction); new->wind_speed = (int) nearbyint(a->wind_speed); new->wind_valid = 1; } if (now < a->oat_updated + TRACK_EXPIRE) { new->oat = (int) nearbyint(a->oat); if (now < a->tat_updated + TRACK_EXPIRE) { new->temp_valid = 1; new->tat = (int) nearbyint(a->tat); } } if (a->adsb_version < 0) new->adsb_version = 15; else new->adsb_version = a->adsb_version; if (a->adsr_version < 0) new->adsr_version = 15; else new->adsr_version = a->adsr_version; if (a->tisb_version < 0) new->tisb_version = 15; else new->tisb_version = a->tisb_version; new->nic_a = a->nic_a; new->nic_c = a->nic_c; new->nic_baro = a->nic_baro; new->nac_p = a->nac_p; new->nac_v = a->nac_v; new->sil = a->sil; new->gva = a->gva; new->sda = a->sda; new->alert = a->alert; new->spi = a->spi; new->position_valid = trackDataValid(&a->pos_reliable_valid); #define F(f) do { new->f = trackVState(now, &a->f, &a->pos_reliable_valid); } while (0) F(callsign_valid); F(tas_valid); F(mach_valid); F(track_rate_valid); F(mag_heading_valid); F(true_heading_valid); F(nic_a_valid); F(nic_c_valid); F(nic_baro_valid); F(nac_p_valid); F(nac_v_valid); F(sil_valid); F(gva_valid); F(sda_valid); F(squawk_valid); F(emergency_valid); F(airground_valid); F(nav_qnh_valid); F(nav_altitude_mcp_valid); F(nav_altitude_fms_valid); F(nav_altitude_src_valid); F(nav_heading_valid); F(nav_modes_valid); F(alert_valid); F(spi_valid); #undef F } void from_state_all(struct state_all *in, struct state *in2, struct aircraft *a , int64_t ts) { for (int i = 0; i < 8; i++) a->callsign[i] = in->callsign[i]; a->callsign[8] = '\0'; a->pos_nic = in->pos_nic; a->pos_rc = in->pos_rc; a->pos_nic_reliable = in->pos_nic; a->pos_rc_reliable = in->pos_rc; a->tas = in->tas; a->squawk = in->squawk; a->category = in->category; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset a->nav_altitude_mcp = in->nav_altitude_mcp * 4.0f; a->nav_altitude_fms = in->nav_altitude_fms * 4.0f; a->nav_qnh = in->nav_qnh / 10.0f; a->mach = in->mach / 1000.0f; a->track_rate = in->track_rate / 100.0f; a->mag_heading = in->mag_heading / _track_factor; a->true_heading = in->true_heading / _track_factor; a->nav_heading = in->nav_heading / _track_factor; a->emergency = in->emergency; a->airground = in->airground; a->nav_modes = in->nav_modes; a->nav_altitude_src = in->nav_altitude_src; a->sil_type = in->sil_type; a->geom_alt = in2->geom_alt / _alt_factor; a->baro_alt = in2->baro_alt / _alt_factor; if (in->wind_valid) { a->wind_direction = in->wind_direction; a->wind_speed = in->wind_speed; a->wind_updated = ts - 5000; a->wind_altitude = a->baro_alt; } if (in->temp_valid) { a->oat = in->oat; a->tat = in->tat; a->oat_updated = ts - 5000; } if (in->adsb_version == 15) a->adsb_version = -1; else a->adsb_version = in->adsb_version; if (in->adsr_version == 15) a->adsr_version = -1; else a->adsr_version = in->adsr_version; if (in->tisb_version == 15) a->tisb_version = -1; else a->tisb_version = in->tisb_version; a->nic_a = in->nic_a; a->nic_c = in->nic_c; a->nic_baro = in->nic_baro; a->nac_p = in->nac_p; a->nac_v = in->nac_v; a->sil = in->sil; a->gva = in->gva; a->sda = in->sda; a->alert = in->alert; a->spi = in->spi; a->addrtype = in2->addrtype; a->ias = in2->ias; a->baro_rate = in2->baro_rate / _rate_factor; a->geom_rate = in2->geom_rate / _rate_factor; a->gs = in2->gs / _gs_factor; a->roll = in2->roll / _roll_factor; a->track = in2->track / _track_factor; a->baro_alt_valid.source = (in2->baro_alt_valid ? SOURCE_INDIRECT : SOURCE_INVALID); a->baro_alt_valid.updated = ts - 5000; a->geom_alt_valid.source = (in2->geom_alt_valid ? SOURCE_INDIRECT : SOURCE_INVALID); a->geom_alt_valid.updated = ts - 5000; #define F(f) do { a->f.source = (in2->f ? SOURCE_INDIRECT : SOURCE_INVALID); a->f.updated = ts - 5000; } while (0) F(baro_rate_valid); F(geom_rate_valid); F(ias_valid); F(roll_valid); F(gs_valid); F(track_valid); #undef F a->pos_reliable_valid.source = a->position_valid.source = in->position_valid ? SOURCE_INDIRECT : SOURCE_INVALID; a->pos_reliable_valid.updated = a->position_valid.updated = ts - 5000; // giving this a timestamp is kinda hacky, do it anyway // we want to be able to reuse the sprintAircraft routine for printing aircraft details #define F(f) do { a->f.source = (in->f ? SOURCE_INDIRECT : SOURCE_INVALID); a->f.updated = ts - 5000; } while (0) F(callsign_valid); F(tas_valid); F(mach_valid); F(track_rate_valid); F(mag_heading_valid); F(true_heading_valid); F(nic_a_valid); F(nic_c_valid); F(nic_baro_valid); F(nac_p_valid); F(nac_v_valid); F(sil_valid); F(gva_valid); F(sda_valid); F(squawk_valid); F(emergency_valid); F(airground_valid); F(nav_qnh_valid); F(nav_altitude_mcp_valid); F(nav_altitude_fms_valid); F(nav_altitude_src_valid); F(nav_heading_valid); F(nav_modes_valid); F(alert_valid); F(spi_valid); #undef F } /* static const char *cpr_string(cpr_type_t type) { switch (type) { case CPR_INVALID: return "INVALID "; case CPR_SURFACE: return "SURFACE "; case CPR_AIRBORNE: return "AIRBORNE"; case CPR_COARSE: return "COARSE "; default: return "ERR "; } } */ static const char *source_string(datasource_t source) { switch (source) { case SOURCE_INVALID: return "INVALID "; case SOURCE_INDIRECT: return "INDIRECT"; case SOURCE_MODE_AC: return "MODE_AC "; case SOURCE_SBS: return "SBS "; case SOURCE_MLAT: return "MLAT "; case SOURCE_MODE_S: return "MODE_S "; case SOURCE_JAERO: return "JAERO "; case SOURCE_MODE_S_CHECKED: return "MODE_CH "; case SOURCE_TISB: return "TISB "; case SOURCE_ADSR: return "ADSR "; case SOURCE_ADSB: return "ADSB "; case SOURCE_PRIO: return "PRIO "; default: return "ERROR "; } } void updateValidities(struct aircraft *a, int64_t now) { int64_t elapsed_pos = now - a->seen_pos; if (now - a->seen > 500 * RECEIVERIDBUFFER && a->receiverCount > 0) { a->receiverCount = 0; for (int i = 0; i < RECEIVERIDBUFFER; i++) { a->receiverIds[i] = 0; } } if (a->globe_index >= 0 && elapsed_pos > Modes.trackExpireMax) { set_globe_index(a, -5); } if (a->category != 0 && now > a->category_updated + Modes.trackExpireMax) a->category = 0; // reset position reliability when no position was received for 60 minutes if (a->pos_reliable_odd != 0 && a->pos_reliable_even != 0 && elapsed_pos > POS_RELIABLE_TIMEOUT) { a->pos_reliable_odd = 0; a->pos_reliable_even = 0; } if (a->tracePosBuffered && now > a->seenPosReliable + TRACE_STALE) { traceUsePosBuffered(a); } updateValidity(&a->baro_alt_valid, now, TRACK_EXPIRE); if (a->alt_reliable != 0 && a->baro_alt_valid.source == SOURCE_INVALID) { a->alt_reliable = 0; } updateValidity(&a->callsign_valid, now, TRACK_EXPIRE_LONG); updateValidity(&a->geom_alt_valid, now, TRACK_EXPIRE); updateValidity(&a->geom_delta_valid, now, TRACK_EXPIRE); updateValidity(&a->gs_valid, now, TRACK_EXPIRE); updateValidity(&a->ias_valid, now, TRACK_EXPIRE); updateValidity(&a->tas_valid, now, TRACK_EXPIRE); updateValidity(&a->mach_valid, now, TRACK_EXPIRE); updateValidity(&a->track_valid, now, TRACK_EXPIRE); updateValidity(&a->track_rate_valid, now, TRACK_EXPIRE); updateValidity(&a->roll_valid, now, TRACK_EXPIRE); updateValidity(&a->mag_heading_valid, now, TRACK_EXPIRE); updateValidity(&a->true_heading_valid, now, TRACK_EXPIRE); updateValidity(&a->baro_rate_valid, now, TRACK_EXPIRE); updateValidity(&a->geom_rate_valid, now, TRACK_EXPIRE); updateValidity(&a->nic_a_valid, now, TRACK_EXPIRE); updateValidity(&a->nic_c_valid, now, TRACK_EXPIRE); updateValidity(&a->nic_baro_valid, now, TRACK_EXPIRE); updateValidity(&a->nac_p_valid, now, TRACK_EXPIRE); updateValidity(&a->nac_v_valid, now, TRACK_EXPIRE); updateValidity(&a->sil_valid, now, TRACK_EXPIRE); updateValidity(&a->gva_valid, now, TRACK_EXPIRE); updateValidity(&a->sda_valid, now, TRACK_EXPIRE); updateValidity(&a->squawk_valid, now, TRACK_EXPIRE); updateValidity(&a->emergency_valid, now, TRACK_EXPIRE); updateValidity(&a->airground_valid, now, TRACK_EXPIRE_LONG); updateValidity(&a->nav_qnh_valid, now, TRACK_EXPIRE); updateValidity(&a->nav_altitude_mcp_valid, now, TRACK_EXPIRE); updateValidity(&a->nav_altitude_fms_valid, now, TRACK_EXPIRE); updateValidity(&a->nav_altitude_src_valid, now, TRACK_EXPIRE); updateValidity(&a->nav_heading_valid, now, TRACK_EXPIRE); updateValidity(&a->nav_modes_valid, now, TRACK_EXPIRE); updateValidity(&a->cpr_odd_valid, now, TRACK_EXPIRE); updateValidity(&a->cpr_even_valid, now, TRACK_EXPIRE); updateValidity(&a->position_valid, now, TRACK_EXPIRE); updateValidity(&a->alert_valid, now, TRACK_EXPIRE); updateValidity(&a->spi_valid, now, TRACK_EXPIRE); updateValidity(&a->acas_ra_valid, now, TRACK_EXPIRE); updateValidity(&a->mlat_pos_valid, now, TRACK_EXPIRE); updateValidity(&a->pos_reliable_valid, now, TRACK_EXPIRE); if (now > a->nextMessageRateCalc) { calculateMessageRate(a, now); } } static void showPositionDebug(struct aircraft *a, struct modesMessage *mm, int64_t now, double bad_lat, double bad_lon) { fprintf(stderr, "%06x ", a->addr); fprintf(stderr, "elapsed %4.1f ", (now - a->seen_pos) / 1000.0); if (mm->receiver_distance > 0) { fprintf(stderr, "receiver_distance: %7.1f nmi ", mm->receiver_distance / 1852.0); } if (mm->sbs_in) { fprintf(stderr, "SBS, "); if (mm->source == SOURCE_JAERO) fprintf(stderr, "JAERO, "); if (mm->source == SOURCE_MLAT) fprintf(stderr, "MLAT, "); } else { fprintf(stderr, "%s%s", (mm->cpr_type == CPR_SURFACE) ? "surf, " : "air, ", mm->cpr_odd ? "odd, " : "even, "); } if (mm->sbs_in) { fprintf(stderr, "lat: %.6f," "lon: %.6f", mm->decoded_lat, mm->decoded_lon); } else if (mm->cpr_decoded) { fprintf(stderr,"lat: %11.6f (%u)," " lon: %11.6f (%u)," " relative: %d," " NIC: %u," " Rc: %.3f km", mm->decoded_lat, mm->cpr_lat, mm->decoded_lon, mm->cpr_lon, mm->cpr_relative, mm->decoded_nic, mm->decoded_rc / 1000.0); } else { fprintf(stderr,"lat: %11.6f (%u)," " lon: %11.6f (%u)," " CPR decoding: failed", bad_lat, mm->cpr_lat, bad_lon, mm->cpr_lon); } fprintf(stderr, "\n"); } static void incrementReliable(struct aircraft *a, struct modesMessage *mm, int64_t now, int odd) { a->localCPR_allow_ac_rel = 1; float threshold = Modes.json_reliable; if (a->seenPosReliable && now - a->seenPosReliable > POS_RELIABLE_TIMEOUT && mm->source > SOURCE_JAERO && a->pos_reliable_odd < threshold && a->pos_reliable_even < threshold ) { double distance = greatcircle(a->latReliable, a->lonReliable, mm->decoded_lat, mm->decoded_lon, 0); // if aircraft is within 50 km of last reliable position, treat new position as reliable immediately. // pos_reliable is mostly to filter out bad decodes which usually show a much larger offset than 50km // it's very unlikely to get a bad decode that's in a range of 50 km of the last known position // at this point the position has already passed the speed check if (distance < 50e3) { a->pos_reliable_odd = fmaxf(1, threshold); a->pos_reliable_even = fmaxf(1, threshold); if (a->addr == Modes.cpr_focus) fprintf(stderr, "%06x: fast track json_reliable\n", a->addr); } } float increment = 1.0f; if (mm->pos_receiver_range_exceeded) { increment = 0.25f; } if (odd == 3) { increment = Modes.json_reliable; } if (odd) a->pos_reliable_odd = fminf(a->pos_reliable_odd + increment, Modes.position_persistence); if (!odd || odd == 2 || odd == 3) a->pos_reliable_even = fminf(a->pos_reliable_even + increment, Modes.position_persistence); if (a->pos_reliable_odd < increment) { a->pos_reliable_odd = increment; } if (a->pos_reliable_even < increment) { a->pos_reliable_even = increment; } } static void position_bad(struct modesMessage *mm, struct aircraft *a) { int64_t now = mm->sysTimestamp; if (mm->cpr_valid) { struct cpr_cache *disc; a->disc_cache_index = (a->disc_cache_index + 1) % DISCARD_CACHE; // most recent discarded position which led to decrementing reliability and timestamp (speed_check) disc = &a->disc_cache[a->disc_cache_index]; disc->ts = now; disc->cpr_lat = mm->cpr_lat; disc->cpr_lon = mm->cpr_lon; disc->receiverId = mm->receiverId; } a->pos_reliable_odd -= 0.26f; a->pos_reliable_even -= 0.26f; if (a->pos_reliable_odd < 0.1f || a->pos_reliable_even < 0.1f) { // when we reach zero, reset reliable state a->pos_reliable_odd = 0; a->pos_reliable_even = 0; // invalidate CPRs to start fresh a->cpr_even_valid.source = SOURCE_INVALID; a->cpr_odd_valid.source = SOURCE_INVALID; // accept the CPR we just got to get going again a bit quicker if (mm->cpr_valid) { accept_cpr(a, mm); } } if (0 && a->addr == Modes.cpr_focus) fprintf(stderr, "%06x: position_bad %.1f %.1f %u %u\n", a->addr, a->pos_reliable_odd, a->pos_reliable_even, mm->cpr_lat, mm->cpr_lon); } int nogps(int64_t now, struct aircraft *a) { return (a->nogpsCounter >= NOGPS_SHOW && now < a->seenAdsbReliable + NOGPS_DWELL && now > a->seenAdsbReliable + 15 * SECONDS); } readsb-3.16/track.h000066400000000000000000000620311505057307600141720ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // track.h: aircraft state tracking prototypes // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2014-2016 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef DUMP1090_TRACK_H #define DUMP1090_TRACK_H /* Minimum number of repeated Mode A/C replies with a particular Mode A code needed in a * 1 second period before accepting that code. */ #define TRACK_MODEAC_MIN_MESSAGES 4 /* Special value for Rc unknown */ #define RC_UNKNOWN 0 #define ALTITUDE_BARO_RELIABLE_MAX 20 #define POS_RELIABLE_TIMEOUT (60 * MINUTES) #define TRACK_STALE (15*SECONDS) #define TRACK_EXPIRE (60*SECONDS) #define TRACK_EXPIRE_LONG (180*SECONDS) #define TRACK_EXPIRE_JAERO (33*MINUTES) #define TRACK_EXPIRE_ROUGH (2 * MINUTES) #define NOGPS_DWELL (15 * MINUTES) #define NOGPS_MAX (20) #define NOGPS_SHOW (17) int nogps(int64_t now, struct aircraft *a); // 2.5 seconds maximum between messages used for calculating wind / temperature #define TRACK_WT_TIMEOUT (2500) #define RECEIVERIDBUFFER (12) #ifndef RECENT_RECEIVER_IDS #define RECENT_RECEIVER_IDS (32) #endif typedef struct { uint64_t id; int64_t time; } idTime; #define DISCARD_CACHE (4) #define CPR_CACHE (4) typedef enum { REDUCE_RARE, REDUCE_OFTEN, REDUCE_DOUBLE, } reduce_freq_t; // data moves through three states: // fresh: data is valid. Updates from a less reliable source are not accepted. // stale: data is valid. Updates from a less reliable source are accepted. // expired: data is not valid. typedef struct { int64_t updated; /* when it arrived */ int64_t next_reduce_forward; /* when to next forward the data for reduced beast output */ datasource_t source:8; /* where the data came from */ datasource_t last_source:8; /* where the data came from */ int8_t stale; /* if it's stale 1 / 0 */ unsigned padding:8; int padding2; } data_validity; // size must be multiple of 64 bits so it can be aligned in struct aircraft. // uint16: 0 to 65535 #define _gs_factor (10.0f) // 6000 to 60000 #define _track_factor (100.0f) // 360 -> 36000 // int16: -32768 to 32767 #define _alt_factor (1/6.25f) // 200000 to 32000 #define _rate_factor (1/8.0f) // 262136 to 32767 #define _roll_factor (10.0f) // 180 to 1800 /* Structure representing one point in the aircraft trace */ struct state { int64_t timestamp:48; //struct state_flags flags; // 16 bits unsigned on_ground:1; unsigned stale:1; unsigned leg_marker:1; unsigned gs_valid:1; unsigned track_valid:1; unsigned baro_alt_valid:1; unsigned baro_rate_valid:1; unsigned geom_alt_valid:1; unsigned geom_rate_valid:1; unsigned roll_valid:1; unsigned ias_valid:1; unsigned padding:5; int32_t lat; int32_t lon; uint16_t gs; uint16_t track; int16_t baro_alt; int16_t baro_rate; int16_t geom_alt; int16_t geom_rate; unsigned ias:12; int roll:12; addrtype_t addrtype:5; int padding2:3; #if defined(TRACKS_UUID) uint32_t receiverId; #endif } __attribute__ ((__packed__)); struct state_all { char callsign[8]; // Flight number uint16_t squawk; // Squawk int16_t nav_altitude_mcp; // FCU/MCP selected altitude int16_t nav_altitude_fms; // FMS selected altitude int16_t nav_qnh; // Altimeter setting (QNH/QFE), millibars uint16_t nav_heading; // target heading, degrees (0-359) uint16_t mach; int16_t track_rate; // Rate of change of ground track, degrees/second uint16_t mag_heading; // Magnetic heading uint16_t true_heading; // True heading int wind_direction:10; int wind_speed:10; int oat:10; int tat:10; unsigned category:8; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset unsigned pos_nic:8; // NIC of last computed position unsigned pos_rc:16; // Rc of last computed position emergency_t emergency:3; // Emergency/priority status nav_modes_t nav_modes:7; // enabled modes (autopilot, vnav, etc) airground_t airground:2; // air/ground status nav_altitude_source_t nav_altitude_src:3; // source of altitude used by automation sil_type_t sil_type:3; // SIL supplement from TSS or opstatus unsigned tas:12; unsigned adsb_version:4; // ADS-B version (from ADS-B operational status); -1 means no ADS-B messages seen unsigned adsr_version:4; // As above, for ADS-R messages unsigned tisb_version:4; // As above, for TIS-B messages unsigned nic_a : 1; // NIC supplement A from opstatus unsigned nic_c : 1; // NIC supplement C from opstatus unsigned nic_baro : 1; // NIC baro supplement from TSS or opstatus unsigned nac_p : 4; // NACp from TSS or opstatus unsigned nac_v : 3; // NACv from airborne velocity or opstatus unsigned sil : 2; // SIL from TSS or opstatus unsigned gva : 2; // GVA from opstatus unsigned sda : 2; // SDA from opstatus unsigned alert : 1; // FS Flight status alert bit unsigned spi : 1; // FS Flight status SPI (Special Position Identification) bit unsigned callsign_valid:1; unsigned tas_valid:1; unsigned mach_valid:1; unsigned track_valid:1; unsigned track_rate_valid:1; unsigned mag_heading_valid:1; unsigned true_heading_valid:1; unsigned nic_a_valid:1; unsigned nic_c_valid:1; unsigned nic_baro_valid:1; unsigned nac_p_valid:1; unsigned nac_v_valid:1; unsigned sil_valid:1; unsigned gva_valid:1; unsigned sda_valid:1; unsigned squawk_valid:1; unsigned emergency_valid:1; unsigned airground_valid:1; unsigned nav_qnh_valid:1; unsigned nav_altitude_mcp_valid:1; unsigned nav_altitude_fms_valid:1; unsigned nav_altitude_src_valid:1; unsigned nav_heading_valid:1; unsigned nav_modes_valid:1; unsigned position_valid:1; // used for position accuracy stuff, position is in small state struct unsigned alert_valid:1; unsigned spi_valid:1; unsigned wind_valid:1; unsigned temp_valid:1; } __attribute__ ((__packed__)); #define SFOUR (4) typedef struct fourState { struct state no[SFOUR]; struct state_all zeroAll; } fourState; typedef struct traceBuffer { int len; fourState *trace; } traceBuffer; static inline int getFourStates(int points) { return ((points + SFOUR - 1) / SFOUR); } static inline int alignSFOUR(int value) { return ((value + SFOUR - 1) / SFOUR) * SFOUR; } static inline ssize_t stateBytes(int points) { return getFourStates(points) * sizeof(fourState); } static inline struct state *getState(fourState *buffer, int position) { return &buffer[position / SFOUR].no[position % SFOUR]; } static inline struct state_all *getStateAll(fourState *buffer, int position) { if (position % SFOUR == 0) return &buffer[position / SFOUR].zeroAll; else return NULL; } typedef struct stateChunk { unsigned char *compressed; int32_t compressed_size; int32_t numStates; int64_t firstTimestamp; int64_t lastTimestamp; } stateChunk; struct cpr_cache { uint32_t cpr_lat; uint32_t cpr_lon; int64_t ts; uint64_t receiverId; }; struct traceCacheEntry { int64_t ts; int32_t offset; int32_t len; int32_t leg_marker; }; struct traceCache { int32_t entriesLen; int32_t json_max; int32_t firstRecentCache; int32_t totalAlloc; int64_t referenceTs; struct traceCacheEntry *entries; char *json; }; /* Structure used to describe the state of one tracked aircraft */ struct aircraft { struct aircraft *next; // Next aircraft in our linked list uint32_t addr; // ICAO address addrtype_t addrtype; // highest priority address type seen for this aircraft int64_t seen; // Time (millis) at which the last packet with reliable address was received int64_t seen_pos; // Time (millis) at which the last position was received uint32_t messages; // Number of Mode S messages received int32_t onActiveList; uint32_t receiverCount; uint32_t category; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset // int64_t category_updated; // ---- int64_t trace_next_mw; // timestamp for next full trace write to /run (tmpfs) int64_t trace_next_perm; // timestamp for next trace write to history_dir (disk) int64_t lastSignalTimestamp; // timestamp the last message with RSSI was received int64_t trace_perm_last_timestamp; // timestamp for last trace point written to disk fourState *trace_current; // uncompressed most recent points in the trace stateChunk *trace_chunks; // compressed chunks of trace int32_t trace_current_max; int32_t trace_current_len; // number of points in our uncompressed most recent trace portion int32_t trace_len; // total number of points in the trace int32_t trace_chunk_len; // how many stateChunks are saved for this aircraft int32_t trace_write; // signal for writing the trace int32_t trace_writeCounter; // how many points where added since the complete trace was written to memory int32_t baro_alt; // Altitude (Baro) int32_t alt_reliable; int32_t geom_alt; // Altitude (Geometric) int32_t geom_delta; // Difference between Geometric and Baro altitudes uint32_t signalNext; // next index of signalLevel to use // ---- double signalLevel[8]; // Last 8 Signal Amplitudes // ---- float rr_lat; // very rough receiver latitude float rr_lon; // very rough receiver longitude int64_t rr_seen; // when we noted this rough position int64_t seenAdsbReliable; // last time we saw a reliable SOURCE_ADSB positions from this aircraft int64_t addrtype_updated; float tat; uint16_t nogpsCounter; uint16_t receiverIdsNext; int64_t seenPosReliable; // last time we saw a reliable position uint64_t lastPosReceiverId; // ---- the following section has 9 instead of 8 times 8 bytes. but that's not critical as long as the 8 byte alignment is ok uint32_t pos_nic; // NIC of last computed position uint32_t pos_rc; // Rc of last computed position double lat; // Coordinates obtained from CPR encoded data double lon; // Coordinates obtained from CPR encoded data float pos_reliable_odd; // Number of good global CPRs, indicates position reliability float pos_reliable_even; int16_t traceWrittenForYesterday; // the permanent trace has been written for the previous day uint16_t mlatEPU; float gs_last_pos; // Save a groundspeed associated with the last position float wind_speed; float wind_direction; int32_t wind_altitude; float oat; int64_t wind_updated; int64_t oat_updated; int64_t tat_updated; // ---- int32_t baro_rate; // Vertical rate (barometric) int32_t geom_rate; // Vertical rate (geometric) uint32_t ias; uint32_t tas; uint32_t squawk; // Squawk uint32_t squawkTentative; // require the same squawk code twice to accept it uint32_t nav_altitude_mcp; // FCU/MCP selected altitude uint32_t nav_altitude_fms; // FMS selected altitude uint32_t cpr_odd_lat; uint32_t cpr_odd_lon; uint32_t cpr_odd_nic; uint32_t cpr_odd_rc; uint32_t cpr_even_lat; uint32_t cpr_even_lon; uint32_t cpr_even_nic; uint32_t cpr_even_rc; // ---- float nav_qnh; // Altimeter setting (QNH/QFE), millibars float nav_heading; // target heading, degrees (0-359) float gs; float mach; float track; // Ground track float track_rate; // Rate of change of ground track, degrees/second float roll; // Roll angle, degrees right float mag_heading; // Magnetic heading float true_heading; // True heading float calc_track; // Calculated Ground track int64_t next_reduce_forward_DF11; char callsign[16]; // Flight number // ---- emergency_t emergency; // Emergency/priority status airground_t airground; // air/ground status nav_modes_t nav_modes; // enabled modes (autopilot, vnav, etc) cpr_type_t cpr_odd_type; cpr_type_t cpr_even_type; nav_altitude_source_t nav_altitude_src; // source of altitude used by automation int32_t modeA_hit; // did our squawk match a possible mode A reply in the last check period? int32_t modeC_hit; // did our altitude match a possible mode C reply in the last check period? // data extracted from opstatus etc int32_t adsb_version; // ADS-B version (from ADS-B operational status); -1 means no ADS-B messages seen int32_t adsr_version; // As above, for ADS-R messages int32_t tisb_version; // As above, for TIS-B messages heading_type_t adsb_hrd; // Heading Reference Direction setting (from ADS-B operational status) heading_type_t adsb_tah; // Track Angle / Heading setting (from ADS-B operational status) int32_t globe_index; // custom index of the planes area on the globe sil_type_t sil_type; // SIL supplement from TSS or opstatus uint32_t nic_a : 1; // nic supplement a from opstatus uint32_t nic_c : 1; // nic supplement c from opstatus uint32_t nic_baro : 1; // nic baro supplement from tss or opstatus uint32_t nac_p : 4; // nacp from tss or opstatus uint32_t nac_v : 3; // nacv from airborne velocity or opstatus uint32_t sil : 2; // sil from tss or opstatus uint32_t gva : 2; // gva from opstatus uint32_t sda : 2; // sda from opstatus // 16 bit uint32_t alert : 1; // fs flight status alert bit uint32_t spi : 1; // fs flight status spi (special position identification) bit uint32_t pos_surface : 1; // (a->airground == ag_ground) associated with current position uint32_t last_cpr_type : 2; // mm->cpr_type associated with current position uint32_t tracePosBuffered : 1; // denotes if a->trace[a->trace_len] has a valid state buffered in it uint32_t surfaceCPR_allow_ac_rel : 1; // allow surface cpr relative to last known aircraft location uint32_t localCPR_allow_ac_rel : 1; // allow local cpr relative to last known aircraft location // 24 bit uint32_t last_message_crc_fixed : 1; uint32_t is_df18_exception : 1; uint32_t chunkRecompressed : 1; uint32_t padding_b : 5; // 32 bit !! // ---- data_validity callsign_valid; data_validity baro_alt_valid; data_validity geom_alt_valid; data_validity geom_delta_valid; data_validity gs_valid; data_validity ias_valid; data_validity tas_valid; data_validity mach_valid; data_validity track_valid; data_validity track_rate_valid; data_validity roll_valid; data_validity mag_heading_valid; data_validity true_heading_valid; data_validity baro_rate_valid; data_validity geom_rate_valid; data_validity nic_a_valid; data_validity nic_c_valid; data_validity nic_baro_valid; data_validity nac_p_valid; data_validity nac_v_valid; data_validity sil_valid; data_validity gva_valid; data_validity sda_valid; data_validity squawk_valid; data_validity emergency_valid; data_validity airground_valid; data_validity nav_qnh_valid; data_validity nav_altitude_mcp_valid; data_validity nav_altitude_fms_valid; data_validity nav_altitude_src_valid; data_validity nav_heading_valid; data_validity nav_modes_valid; data_validity cpr_odd_valid; // Last seen even CPR message data_validity cpr_even_valid; // Last seen odd CPR message data_validity position_valid; data_validity alert_valid; data_validity spi_valid; int64_t lastAirGroundChange; double latReliable; // last reliable position based on json_reliable threshold double lonReliable; // last reliable position based on json_reliable threshold char typeCode[4]; char registration[12]; char typeLong[64]; uint16_t receiverIds[RECEIVERIDBUFFER]; // RECEIVERIDBUFFER = 12 int64_t next_reduce_forward_status; unsigned char acas_ra[7]; // mm->MV from last acas RA message unsigned char acas_flags; // maybe use for some flags, would be padding otherwise data_validity acas_ra_valid; float gs_reliable; float track_reliable; int64_t lastMlatForce; int64_t squawkTentativeChanged; double magneticDeclination; int64_t updatedDeclination; uint16_t pos_nic_reliable; uint16_t pos_rc_reliable; int32_t trackUnreliable; uint64_t receiverId; // previous position and timestamp double prev_lat; // previous latitude double prev_lon; // previous longitude int64_t prev_pos_time; // time the previous position was received int32_t speedUnreliable; uint32_t lastStatusDiscarded; int64_t nextJsonPortOutput; float receiver_distance; float receiver_direction; data_validity mlat_pos_valid; double mlat_lat; double mlat_lon; data_validity pos_reliable_valid; // last reliable SOURCE_ADSB positions from this aircraft double seenAdsbLat; double seenAdsbLon; int64_t lastStatusTs; int64_t lastOverrideTs; fourState *traceLast; int32_t traceLastNext; // DANGER, this section is zeroed when saving and loading data char zeroStart; uint32_t disc_cache_index; uint32_t cpr_cache_index; struct cpr_cache disc_cache[DISCARD_CACHE]; struct cpr_cache cpr_cache[CPR_CACHE]; // DANGER, everything above this section is possibly rolled back in trackUpdateFromMessage using // the scratch mechanism struct traceCache traceCache; char ownOp[64]; char year[4]; uint16_t dbFlags; int8_t initialTraceWriteDone; atomic_int traceLock; uint32_t trace_chunk_overall_bytes; float messageRate; uint16_t messageRateAcc[MESSAGE_RATE_CALC_POINTS]; int64_t nextMessageRateCalc; #if defined(PRINT_UUIDS) int recentReceiverIdsNext; idTime recentReceiverIds[RECENT_RECEIVER_IDS]; #endif char zeroEnd; }; #define aircraftBackAlloc (2 * 1024 * 1024) #define aircraftBackCap (int64_t) ((aircraftBackAlloc - 3 * sizeof(void*)) / sizeof(struct aircraft)) struct aircraftBack { struct aircraftBack *prev; struct aircraftBack *next; int64_t used; struct aircraft store[aircraftBackCap]; }; /* Mode A/C tracking is done separately, not via the aircraft list, * and via a flat array rather than a list since there are only 4k possible values * (nb: we ignore the ident/SPI bit when tracking) */ extern uint32_t modeAC_count[4096]; extern uint32_t modeAC_match[4096]; extern uint32_t modeAC_age[4096]; /* is this bit of data valid? */ static inline void updateValidity (data_validity *v, int64_t now, int64_t expiration_timeout) { if (v->source == SOURCE_INVALID) return; int stale = (now > v->updated + TRACK_STALE); if (stale != v->stale) v->stale = stale; if (v->source == SOURCE_JAERO) { if (now > v->updated + Modes.trackExpireJaero) v->source = SOURCE_INVALID; } else if (v->source == SOURCE_INDIRECT && Modes.debug_rough_receiver_location) { if (now > v->updated + TRACK_EXPIRE_ROUGH) v->source = SOURCE_INVALID; } else { if (now > v->updated + expiration_timeout) v->source = SOURCE_INVALID; } } /* is this bit of data valid? */ static inline int trackDataValid (const data_validity *v) { return (v->source != SOURCE_INVALID); } static inline int posReliable(struct aircraft *a) { if (!trackDataValid(&a->position_valid)) { return 0; } if (a->position_valid.source == SOURCE_JAERO || a->position_valid.source == SOURCE_MLAT || a->position_valid.source == SOURCE_INDIRECT) { return 1; } int reliable = Modes.json_reliable; // disable this extra requirement for the moment: if (0 && Modes.position_persistence > reliable && reliable > 1 && (a->addr & MODES_NON_ICAO_ADDRESS || a->addrtype == ADDR_TISB_ICAO || a->addrtype == ADDR_ADSR_ICAO)) { reliable += 1; // require additional reliability for non-icao hex addresses } if (a->pos_reliable_odd >= reliable && a->pos_reliable_even >= reliable) { return 1; } return 0; } static inline int altBaroReliable(struct aircraft *a) { if (!trackDataValid(&a->baro_alt_valid)) return 0; if (a->position_valid.source == SOURCE_JAERO) return 1; if (a->alt_reliable >= 2 * Modes.json_reliable) return 1; return 0; } static inline int trackVState (int64_t now, const data_validity *v, const data_validity *pos_valid) { if (pos_valid->source <= SOURCE_JAERO) { // allow normal expiration time for shitty position sources return (v->source != SOURCE_INVALID); } // reduced expiration time for good sources return (v->source != SOURCE_INVALID && now < v->updated + 35 * SECONDS); } static inline int altBaroReliableTrace(int64_t now, struct aircraft *a) { if (altBaroReliable(a) && trackVState(now, &a->baro_alt_valid, &a->pos_reliable_valid)) return 1; else return 0; } /* what's the age of this data, in milliseconds? */ static inline int64_t trackDataAge (int64_t now, const data_validity *v) { if (v->updated >= now) return 0; return (now - v->updated); } void to_state(struct aircraft *a, struct state *new, int64_t now, int on_ground, float track, int stale); void to_state_all(struct aircraft *a, struct state_all *new, int64_t now); void from_state_all(struct state_all *in, struct state *in2, struct aircraft *a , int64_t ts); /* Update aircraft state from data in the provided mesage. * Return the tracked aircraft. */ struct modesMessage; struct aircraft *trackUpdateFromMessage (struct modesMessage *mm); void trackMatchAC(int64_t now); void trackRemoveStale(int64_t now); void updateValidities(struct aircraft *a, int64_t now); struct aircraft *trackFindAircraft(uint32_t addr); /* Convert from a (hex) mode A value to a 0-4095 index */ static inline unsigned modeAToIndex (unsigned modeA) { return (modeA & 0x0007) | ((modeA & 0x0070) >> 1) | ((modeA & 0x0700) >> 2) | ((modeA & 0x7000) >> 3); } /* Convert from a 0-4095 index to a (hex) mode A value */ static inline unsigned indexToModeA (unsigned index) { return (index & 0007) | ((index & 0070) << 1) | ((index & 0700) << 2) | ((index & 07000) << 3); } /* convert from (hex) squawk to (dec) squawk */ static inline uint32_t squawkHex2Dec(uint32_t s) { return ( (s & 0xf000) / 0x1000 * 1000 + (s & 0x0f00) / 0x100 * 100 + (s & 0x00f0) / 0x10 * 10 + (s & 0x000f) / 0x1 * 1); } /* convert from (dec) squawk to (hex) squawk */ static inline uint32_t squawkDec2Hex(uint32_t s) { return ( (s / 1000 % 10) * 16*16*16 + (s / 100 % 10) * 16*16 + (s / 10 % 10) * 16 + (s % 10) ); } static inline int bogus_lat_lon(double lat, double lon) { if (fabs(lat) >= 90.0 || fabs(lon) >= 180.0) return 1; if (lat == 0 && (lon == -90 || lon == 90 || lon == 0)) return 1; if (fabs(lat) < 0.01 && fabs(lon) < 0.01) return 1; return 0; } static inline float signalRaw(struct aircraft *a) { float sum; if (a->signalNext >= 8) { sum = a->signalLevel[0] + a->signalLevel[1] + a->signalLevel[2] + a->signalLevel[3] + a->signalLevel[4] + a->signalLevel[5] + a->signalLevel[6] + a->signalLevel[7]; return sum / 8.0f; } else if (a->signalNext >= 4) { sum = 0; for (uint32_t i = 0; i < a->signalNext; i++) { sum += a->signalLevel[i]; } return sum / a->signalNext; } return 0; } static inline float getSignal(struct aircraft *a) { return 10.0f * log10f(signalRaw(a) + 1.125e-5f); } static inline int get8bitSignal(struct aircraft *a) { float signal = sqrtf(signalRaw(a)) * 255.0f; if (signal > 255.0f) signal = 255.0f; if (signal < 1.0f && signal > 0.0f) signal = 1.0f; return (int) nearbyintf(signal); } static inline const char *nonIcaoSpace(struct aircraft *a) { if (a->addr & MODES_NON_ICAO_ADDRESS) { return ""; } else { return " "; } } #endif readsb-3.16/uat2esnt/000077500000000000000000000000001505057307600144605ustar00rootroot00000000000000readsb-3.16/uat2esnt/uat.h000066400000000000000000000032271505057307600154260ustar00rootroot00000000000000// Part of dump978, a UAT decoder. // // Copyright 2015, Oliver Jowett // // This file is free software: you may copy, redistribute and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 2 of the License, or (at your // option) any later version. // // This file 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 . #ifndef UAT_H #define UAT_H // Frame size constants #define SHORT_FRAME_DATA_BITS (144) #define SHORT_FRAME_BITS (SHORT_FRAME_DATA_BITS+96) #define SHORT_FRAME_DATA_BYTES (SHORT_FRAME_DATA_BITS/8) #define SHORT_FRAME_BYTES (SHORT_FRAME_BITS/8) #define LONG_FRAME_DATA_BITS (272) #define LONG_FRAME_BITS (LONG_FRAME_DATA_BITS+112) #define LONG_FRAME_DATA_BYTES (LONG_FRAME_DATA_BITS/8) #define LONG_FRAME_BYTES (LONG_FRAME_BITS/8) #define UPLINK_BLOCK_DATA_BITS (576) #define UPLINK_BLOCK_BITS (UPLINK_BLOCK_DATA_BITS+160) #define UPLINK_BLOCK_DATA_BYTES (UPLINK_BLOCK_DATA_BITS/8) #define UPLINK_BLOCK_BYTES (UPLINK_BLOCK_BITS/8) #define UPLINK_FRAME_BLOCKS (6) #define UPLINK_FRAME_DATA_BITS (UPLINK_FRAME_BLOCKS * UPLINK_BLOCK_DATA_BITS) #define UPLINK_FRAME_BITS (UPLINK_FRAME_BLOCKS * UPLINK_BLOCK_BITS) #define UPLINK_FRAME_DATA_BYTES (UPLINK_FRAME_DATA_BITS/8) #define UPLINK_FRAME_BYTES (UPLINK_FRAME_BITS/8) #endif readsb-3.16/uat2esnt/uat2esnt.c000077500000000000000000000661071505057307600164060ustar00rootroot00000000000000// // Copyright 2015, Oliver Jowett // // This file is free software: you may copy, redistribute and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 2 of the License, or (at your // option) any later version. // // This file 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 . #include #include #include #include #include "uat.h" #include "uat_decode.h" #define _UNUSED(V) ((void) V) typedef enum { UAT_UPLINK, UAT_DOWNLINK } frame_type_t; // Function pointer type for a handler called by dump978_read_frames(). // It is called with arguments: // t: frame type (UAT_UPLINK or UAT_DOWNLINK) // f: pointer to frame data buffer // l: length of frame data // d1: value of handler_data passed to handler // d2: value of handler_data passed to handler // ss: measured signal strength in dBFS // The frame data buffer is a shared buffer owned by the caller // and may be reused after return; if the handler needs to // preserve the data after returning, it should take a copy. typedef void (*frame_handler_t)(frame_type_t t,uint8_t *f,int l,char *d1, char *d2, float ss); static char* checksum_and_print(uint8_t *frame, int len, uint32_t parity, char *p, char *end); // If you call this with constants for firstbit/lastbit // gcc will do a pretty good job of crunching it down // to just a couple of operations. Even more so if value // is also constant. static inline void setbits(uint8_t *frame, unsigned firstbit, unsigned lastbit, uint32_t value) { // convert to 0-based: unsigned lb = lastbit-1; // align value with byte layout: unsigned offset = 7 - (lb&7); unsigned nb = (lastbit - firstbit + 1 + offset); uint32_t mask = (1 << (lastbit-firstbit+1))-1; uint32_t imask = ~(mask << offset); uint32_t aligned = (value & mask) << offset; frame[lb >> 3] = (frame[lb >> 3] & imask) | aligned; if (nb > 8) frame[(lb >> 3) - 1] = (frame[(lb >> 3) - 1] & (imask >> 8)) | (aligned >> 8); if (nb > 16) frame[(lb >> 3) - 2] = (frame[(lb >> 3) - 2] & (imask >> 16)) | (aligned >> 16); if (nb > 24) frame[(lb >> 3) - 3] = (frame[(lb >> 3) - 3] & (imask >> 24)) | (aligned >> 24); } static int encode_altitude(int ft) { int i; i = (ft + 1000) / 25; if (i < 0) i = 0; if (i > 0x7FF) i = 0x7FF; return (i & 0x000F) | 0x0010 | ((i & 0x07F0) << 1); } static int encode_ground_speed(int kt) { if (kt > 175) return 124; if (kt > 100) return (kt - 100) / 5 + 108; if (kt > 70) return (kt - 70) / 2 + 93; if (kt > 15) return (kt - 15) + 38; if (kt > 2) return (kt - 2) * 2 + 11; if (kt == 2) return 12; if (kt == 1) return 8; return 1; } static int encode_air_speed(int kt, int supersonic) { int sign; if (kt < 0) { sign = 0x0400; kt = -kt; } else { sign = 0; } if (supersonic) kt = kt / 4; ++kt; if (kt > 1023) kt = 1023; return kt | sign; } static int encode_vert_rate(int rate) { int sign; if (rate < 0) { sign = 0x200; rate = -rate; } else { sign = 0; } rate = (rate / 64) + 1; if (rate > 511) rate = 511; return rate | sign; } static double cprMod(double a, double b) { double res = fmod(a, b); if (res < 0) res += b; return res; } static int cprNL(double lat) { if (lat < 0) lat = -lat; if (lat < 10.47047130) return 59; if (lat < 14.82817437) return 58; if (lat < 18.18626357) return 57; if (lat < 21.02939493) return 56; if (lat < 23.54504487) return 55; if (lat < 25.82924707) return 54; if (lat < 27.93898710) return 53; if (lat < 29.91135686) return 52; if (lat < 31.77209708) return 51; if (lat < 33.53993436) return 50; if (lat < 35.22899598) return 49; if (lat < 36.85025108) return 48; if (lat < 38.41241892) return 47; if (lat < 39.92256684) return 46; if (lat < 41.38651832) return 45; if (lat < 42.80914012) return 44; if (lat < 44.19454951) return 43; if (lat < 45.54626723) return 42; if (lat < 46.86733252) return 41; if (lat < 48.16039128) return 40; if (lat < 49.42776439) return 39; if (lat < 50.67150166) return 38; if (lat < 51.89342469) return 37; if (lat < 53.09516153) return 36; if (lat < 54.27817472) return 35; if (lat < 55.44378444) return 34; if (lat < 56.59318756) return 33; if (lat < 57.72747354) return 32; if (lat < 58.84763776) return 31; if (lat < 59.95459277) return 30; if (lat < 61.04917774) return 29; if (lat < 62.13216659) return 28; if (lat < 63.20427479) return 27; if (lat < 64.26616523) return 26; if (lat < 65.31845310) return 25; if (lat < 66.36171008) return 24; if (lat < 67.39646774) return 23; if (lat < 68.42322022) return 22; if (lat < 69.44242631) return 21; if (lat < 70.45451075) return 20; if (lat < 71.45986473) return 19; if (lat < 72.45884545) return 18; if (lat < 73.45177442) return 17; if (lat < 74.43893416) return 16; if (lat < 75.42056257) return 15; if (lat < 76.39684391) return 14; if (lat < 77.36789461) return 13; if (lat < 78.33374083) return 12; if (lat < 79.29428225) return 11; if (lat < 80.24923213) return 10; if (lat < 81.19801349) return 9; if (lat < 82.13956981) return 8; if (lat < 83.07199445) return 7; if (lat < 83.99173563) return 6; if (lat < 84.89166191) return 5; if (lat < 85.75541621) return 4; if (lat < 86.53536998) return 3; if (lat < 87.00000000) return 2; else return 1; } static int cprN(double lat, int odd) { int nl = cprNL(lat) - (odd ? 1 : 0); if (nl < 1) nl = 1; return nl; } static int encode_cpr_lat(double lat, double lon, int odd, int surface) { _UNUSED(lon); int NbPow = (surface ? 1<<19 : 1<<17); double Dlat = 360.0 / (odd ? 59 : 60); int YZ = floor(NbPow * cprMod(lat, Dlat) / Dlat + 0.5); return YZ & 0x1FFFF; // always a 17-bit field } static int encode_cpr_lon(double lat, double lon, int odd, int surface) { int NbPow = (surface ? 1<<19 : 1<<17); double Dlat = 360.0 / (odd ? 59 : 60); int YZ = floor(NbPow * cprMod(lat, Dlat) / Dlat + 0.5); double Rlat = Dlat * (1.0 * YZ / NbPow + floor(lat / Dlat)); double Dlon = (360.0 / cprN(Rlat, odd)); int XZ = floor(NbPow * cprMod(lon, Dlon) / Dlon + 0.5); return XZ & 0x1FFFF; // always a 17-bit field } static int encode_cf(struct uat_adsb_mdb *mdb) { // Encode the CF field for DF 18; this says whether this // is a TIS-B message, an ADS-B rebroadcast, etc switch (mdb->address_qualifier) { case AQ_ADSB_ICAO: return 6; // ADS-B rebroadcast, will use IMF=0 case AQ_TISB_ICAO: return 2; // Fine TIS-B message, will use IMF=0 case AQ_TISB_OTHER: return 2; // Fine TIS-B message, will use IMF=1 default: // national, fixed beacon, vehicle, reserved return 1; // ES/NT devices that use other addressing techniques } } static int encode_imf(struct uat_adsb_mdb *mdb) { // Encode the IMF bit for DF 18; this is 0 if the address // is a regular 24-bit ICAO address, or 1 if it uses a // different format. switch (mdb->address_qualifier) { case AQ_ADSB_ICAO: case AQ_TISB_ICAO: return 0; default: return 1; } } static char* send_altitude_only(struct uat_adsb_mdb *mdb, char *p, char *end) { uint8_t esnt_frame[14] = { 0 }; int raw_alt; // Need barometric altitude, see if we have it if (mdb->altitude_type == ALT_BARO) { raw_alt = encode_altitude(mdb->altitude); } else if (mdb->sec_altitude_type == ALT_BARO) { raw_alt = encode_altitude(mdb->sec_altitude); } else { raw_alt = 0; } setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT setbits(esnt_frame, 6, 8, encode_cf(mdb)); // CF setbits(esnt_frame, 9, 32, mdb->address); // AA // ES: setbits(esnt_frame+4, 1, 5, 0); // FORMAT TYPE CODE = 0, barometric altitude with no position setbits(esnt_frame+4, 6, 7, 0); // SURVEILLANCE STATUS normal setbits(esnt_frame+4, 8, 8, encode_imf(mdb)); // IMF setbits(esnt_frame+4, 9, 20, raw_alt); // ALTITUDE setbits(esnt_frame+4, 21, 21, 0); // TIME (T) setbits(esnt_frame+4, 22, 22, 0); // CPR FORMAT (F) setbits(esnt_frame+4, 23, 39, 0); // ENCODED LATITUDE setbits(esnt_frame+4, 40, 56, 0); // ENCODED LONGITUDE p = checksum_and_print(esnt_frame, 14, 0, p, end); return p; } static char* maybe_send_surface_position(struct uat_adsb_mdb *mdb, char *p, char *end) { uint8_t esnt_frame[14] = { 0 }; if (mdb->airground_state != AG_GROUND) return p; // nope! setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT setbits(esnt_frame, 6, 8, encode_cf(mdb)); // CF setbits(esnt_frame, 9, 32, mdb->address); // AA setbits(esnt_frame+4, 1, 5, 8); // FORMAT TYPE CODE = 8, surface position (NUCp=6) if (!mdb->speed_valid) { setbits(esnt_frame+4, 6, 12, 0); // MOVEMENT: invalid } else { setbits(esnt_frame+4, 6, 12, encode_ground_speed(mdb->speed)); // MOVEMENT } if (mdb->track_type != TT_TRACK) { setbits(esnt_frame+4, 13, 13, 0); // STATUS for ground track: invalid setbits(esnt_frame+4, 14, 20, 0); // GROUND TRACK (TRUE) } else { setbits(esnt_frame+4, 13, 13, 1); // STATUS for ground track: valid setbits(esnt_frame+4, 14, 20, mdb->track * 128 / 360); // GROUND TRACK (TRUE) } setbits(esnt_frame+4, 21, 21, encode_imf(mdb)); // IMF // even frame: setbits(esnt_frame+4, 22, 22, 0); // CPR FORMAT (F) = even setbits(esnt_frame+4, 23, 39, encode_cpr_lat(mdb->lat, mdb->lon, 0, 1)); // ENCODED LATITUDE setbits(esnt_frame+4, 40, 56, encode_cpr_lon(mdb->lat, mdb->lon, 0, 1)); // ENCODED LONGITUDE p = checksum_and_print(esnt_frame, 14, 0, p, end); // odd frame: setbits(esnt_frame+4, 22, 22, 1); // CPR FORMAT (F) = odd setbits(esnt_frame+4, 23, 39, encode_cpr_lat(mdb->lat, mdb->lon, 1, 1)); // ENCODED LATITUDE setbits(esnt_frame+4, 40, 56, encode_cpr_lon(mdb->lat, mdb->lon, 1, 1)); // ENCODED LONGITUDE p = checksum_and_print(esnt_frame, 14, 0, p, end); return p; } static char* maybe_send_air_position(struct uat_adsb_mdb *mdb, char *p, char *end) { uint8_t esnt_frame[14] = { 0 }; int raw_alt; if (mdb->airground_state != AG_SUPERSONIC && mdb->airground_state != AG_SUBSONIC) return p; // nope! if (!mdb->position_valid) { p = send_altitude_only(mdb, p, end); return p; } setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT setbits(esnt_frame, 6, 8, encode_cf(mdb));// CF setbits(esnt_frame, 9, 32, mdb->address); // AA // decide on a metype switch (mdb->altitude_type) { case ALT_BARO: setbits(esnt_frame+4, 1, 5, 18); // FORMAT TYPE CODE = 18, airborne position (baro alt) raw_alt = encode_altitude(mdb->altitude); break; case ALT_GEO: setbits(esnt_frame+4, 1, 5, 22); // FORMAT TYPE CODE = 22, airborne position (GNSS alt) raw_alt = encode_altitude(mdb->altitude); break; default: setbits(esnt_frame+4, 1, 5, 18); // FORMAT TYPE CODE = 18, airborne position (baro alt) raw_alt = 0; // unavailable break; } setbits(esnt_frame+4, 6, 7, 0); // SURVEILLANCE STATUS normal setbits(esnt_frame+4, 8, 8, encode_imf(mdb)); // IMF setbits(esnt_frame+4, 9, 20, raw_alt); // ALTITUDE setbits(esnt_frame+4, 21, 21, 0); // TIME (T) // even frame: setbits(esnt_frame+4, 22, 22, 0); // CPR FORMAT (F) - even setbits(esnt_frame+4, 23, 39, encode_cpr_lat(mdb->lat, mdb->lon, 0, 0)); // ENCODED LATITUDE setbits(esnt_frame+4, 40, 56, encode_cpr_lon(mdb->lat, mdb->lon, 0, 0)); // ENCODED LONGITUDE p = checksum_and_print(esnt_frame, 14, 0, p, end); // odd frame: setbits(esnt_frame+4, 22, 22, 1); // CPR FORMAT (F) - odd setbits(esnt_frame+4, 23, 39, encode_cpr_lat(mdb->lat, mdb->lon, 1, 0)); // ENCODED LATITUDE setbits(esnt_frame+4, 40, 56, encode_cpr_lon(mdb->lat, mdb->lon, 1, 0)); // ENCODED LONGITUDE p = checksum_and_print(esnt_frame, 14, 0, p, end); return p; } static char* maybe_send_air_velocity(struct uat_adsb_mdb *mdb, char *p, char *end) { uint8_t esnt_frame[14] = { 0 }; int supersonic; if (mdb->airground_state != AG_SUPERSONIC && mdb->airground_state != AG_SUBSONIC) return p; // nope! if (!mdb->ew_vel_valid && !mdb->ns_vel_valid && mdb->vert_rate_source == ALT_INVALID) { // not really any point sending this return p; } setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT setbits(esnt_frame, 6, 8, encode_cf(mdb));// CF setbits(esnt_frame, 9, 32, mdb->address); // AA supersonic = (mdb->airground_state == AG_SUPERSONIC); setbits(esnt_frame+4, 1, 5, 19); // FORMAT TYPE CODE = 19, airborne velocity if (supersonic) setbits(esnt_frame+4, 6, 8, 2); // SUBTYPE = 2, supersonic, speed over ground else setbits(esnt_frame+4, 6, 8, 1); // SUBTYPE = 1, subsonic, speed over ground setbits(esnt_frame+4, 9, 9, encode_imf(mdb)); // IMF setbits(esnt_frame+4, 10, 10, 0); // IFR setbits(esnt_frame+4, 11, 13, 0); // NAVIGATIONAL UNCERTAINTY CATEGORY FOR VELOCITY // EAST/WEST DIRECTION BIT + EAST/WEST VELOCITY if (!mdb->ew_vel_valid) setbits(esnt_frame+4, 14, 24, 0); else setbits(esnt_frame+4, 14, 24, encode_air_speed(mdb->ew_vel, supersonic)); // NORTH/SOUTH DIRECTION BIT + NORTH/SOUTH VELOCITY if (!mdb->ns_vel_valid) setbits(esnt_frame+4, 25, 35, 0); else setbits(esnt_frame+4, 25, 35, encode_air_speed(mdb->ns_vel, supersonic)); switch (mdb->vert_rate_source) { case ALT_BARO: setbits(esnt_frame+4, 36, 36, 0); // SOURCE = BARO setbits(esnt_frame+4, 37, 46, encode_vert_rate(mdb->vert_rate)); // SIGN BIT FOR VERTICAL RATE + VERTICAL RATE break; case ALT_GEO: setbits(esnt_frame+4, 36, 36, 1); // SOURCE = GNSS setbits(esnt_frame+4, 37, 46, encode_vert_rate(mdb->vert_rate)); // SIGN BIT FOR VERTICAL RATE + VERTICAL RATE break; default: setbits(esnt_frame+4, 36, 36, 0); // SOURCE = BARO setbits(esnt_frame+4, 37, 46, 0); // SIGN BIT FOR VERTICAL RATE + VERTICAL RATE = 0, no information break; } setbits(esnt_frame+4, 47, 48, 0); // RESERVED FOR TURN INDICATOR if (mdb->altitude_type != ALT_INVALID && mdb->sec_altitude_type != ALT_INVALID) { int delta, sign; if (mdb->altitude < mdb->sec_altitude) { // secondary above primary delta = mdb->sec_altitude - mdb->altitude; sign = mdb->altitude_type == ALT_BARO ? 0 : 1; } else { // primary above secondary delta = mdb->altitude - mdb->sec_altitude; sign = mdb->altitude_type == ALT_BARO ? 1 : 0; } delta = delta / 25 + 1; if (delta >= 127) delta = 127; setbits(esnt_frame+4, 49, 49, sign); // DIFFERENCE SIGN BIT setbits(esnt_frame+4, 50, 56, delta); // GNSS ALT DIFFERENCE FROM BARO ALT } else { setbits(esnt_frame+4, 49, 49, 0); // DIFFERENCE SIGN BIT setbits(esnt_frame+4, 50, 56, 0); // GNSS ALT DIFFERENCE FROM BARO ALT = 0, no information } p = checksum_and_print(esnt_frame, 14, 0, p, end); return p; } // yeah, this could be done with a lookup table, meh. static char ais_charset[65] = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"; static uint8_t char_to_ais(int ch) { char *match; if (!ch) return 32; match = strchr(ais_charset, ch); if (match) return (uint8_t)(match - ais_charset); else return 32; } static unsigned encodeSquawk(char *squawkStr) { unsigned squawk = strtoul(squawkStr, NULL, 16); unsigned encoded = 0; if (squawk & 0x1000) encoded |= 0x0800; // A1 if (squawk & 0x2000) encoded |= 0x0200; // A2 if (squawk & 0x4000) encoded |= 0x0080; // A4 if (squawk & 0x0100) encoded |= 0x0020; // B1 if (squawk & 0x0200) encoded |= 0x0008; // B2 if (squawk & 0x0400) encoded |= 0x0002; // B4 if (squawk & 0x0010) encoded |= 0x1000; // C1 if (squawk & 0x0020) encoded |= 0x0400; // C2 if (squawk & 0x0040) encoded |= 0x0100; // C4 if (squawk & 0x0001) encoded |= 0x0010; // D1 if (squawk & 0x0002) encoded |= 0x0004; // D2 if (squawk & 0x0004) encoded |= 0x0001; // D4 return encoded; } static int mapSquawkToEmergency(const char *squawkStr) { if (!strcmp(squawkStr, "7500")) return 5; // Unlawful Interference if (!strcmp(squawkStr, "7600")) return 4; // No Communications if (!strcmp(squawkStr, "7700")) return 1; // General Emergency return 0; // No Emergency } static char* maybe_send_callsign(struct uat_adsb_mdb *mdb, char *p, char *end) { uint8_t esnt_frame[14] = { 0 }; int imf = encode_imf(mdb); switch (mdb->callsign_type) { case CS_CALLSIGN: if (imf) { // Not sent with non-ICAO addresses return p; } setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT setbits(esnt_frame, 6, 8, encode_cf(mdb));// CF setbits(esnt_frame, 9, 32, mdb->address); // AA if (mdb->emitter_category <= 7) { setbits(esnt_frame+4, 1, 5, 4); // FORMAT TYPE CODE = 4, aircraft category A setbits(esnt_frame+4, 6, 8, mdb->emitter_category & 7); // AIRCRAFT CATEGORY (A0 - A7) } else if (mdb->emitter_category <= 15) { setbits(esnt_frame+4, 1, 5, 3); // FORMAT TYPE CODE = 3, aircraft category B setbits(esnt_frame+4, 6, 8, mdb->emitter_category & 7); // AIRCRAFT CATEGORY (B0 - B7) } else if (mdb->emitter_category <= 23) { setbits(esnt_frame+4, 1, 5, 2); // FORMAT TYPE CODE = 2, aircraft category C setbits(esnt_frame+4, 6, 8, mdb->emitter_category & 7); // AIRCRAFT CATEGORY (C0 - C7) } else if (mdb->emitter_category <= 31) { setbits(esnt_frame+4, 1, 5, 1); // FORMAT TYPE CODE = 1, aircraft category D setbits(esnt_frame+4, 6, 8, mdb->emitter_category & 7); // AIRCRAFT CATEGORY (D0 - D7) } else { // reserved, map to A0 setbits(esnt_frame+4, 1, 5, 4); // FORMAT TYPE CODE = 4, aircraft category A setbits(esnt_frame+4, 6, 8, 0); // AIRCRAFT CATEGORY A0 } // Map callsign setbits(esnt_frame+4, 9, 14, char_to_ais(mdb->callsign[0])); setbits(esnt_frame+4, 15, 20, char_to_ais(mdb->callsign[1])); setbits(esnt_frame+4, 21, 26, char_to_ais(mdb->callsign[2])); setbits(esnt_frame+4, 27, 32, char_to_ais(mdb->callsign[3])); setbits(esnt_frame+4, 33, 38, char_to_ais(mdb->callsign[4])); setbits(esnt_frame+4, 39, 44, char_to_ais(mdb->callsign[5])); setbits(esnt_frame+4, 45, 50, char_to_ais(mdb->callsign[6])); setbits(esnt_frame+4, 51, 56, char_to_ais(mdb->callsign[7])); p = checksum_and_print(esnt_frame, 14, 0, p, end); break; case CS_SQUAWK: setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT setbits(esnt_frame, 6, 8, encode_cf(mdb));// CF setbits(esnt_frame, 9, 32, mdb->address); // AA setbits(esnt_frame+4, 1, 5, 28); // FORMAT TYPE CODE = 28, Aircraft Status Message setbits(esnt_frame+4, 6, 8, 1); // subtype = 1, emergency/priority status setbits(esnt_frame+4, 9, 11, mapSquawkToEmergency(mdb->callsign)); setbits(esnt_frame+4, 12, 24, encodeSquawk(mdb->callsign)); // 25..55 reserved setbits(esnt_frame+4, 56, 56, imf); p = checksum_and_print(esnt_frame, 14, 0, p, end); break; default: break; } return p; } // Generator polynomial for the Mode S CRC: #define MODES_GENERATOR_POLY 0xfff409U // CRC values for all single-byte messages; // used to speed up CRC calculation. static uint32_t crc_table[256]; void uat2esnt_initCrcTables() { int i; for (i = 0; i < 256; ++i) { uint32_t c = i << 16; int j; for (j = 0; j < 8; ++j) { if (c & 0x800000) c = (c<<1) ^ MODES_GENERATOR_POLY; else c = (c<<1); } crc_table[i] = c & 0x00ffffff; } } static uint32_t checksum(uint8_t *message, int n) { uint32_t rem = 0; int i; for (i = 0; i < n; ++i) { rem = (rem << 8) ^ crc_table[message[i] ^ ((rem & 0xff0000) >> 16)]; rem = rem & 0xffffff; } return rem; } static uint8_t signal_strength = 0; static char* checksum_and_print(uint8_t *frame, int len, uint32_t parity, char *p, char *end) { // 2 characters for each byte, 16 characters for timestamp / signal / stuff, 8 characters safety buffer :) int char_len = 2 * len + 16 + 8; int max_len = end - p; if (char_len > max_len) { fprintf(stderr, "uat2esnt: checksum_and_print(): message too long! (%d / %d)\n", len, max_len); return p; } int j; uint32_t rem = checksum(frame, len-3) ^ parity; frame[len-3] = (rem & 0xFF0000) >> 16; frame[len-2] = (rem & 0x00FF00) >> 8; frame[len-1] = (rem & 0x0000FF); #define MAGIC_UAT_TIMESTAMP "FF004D4C4155" // using format <-AVR: <+beast_ts+sigL+raw+;\n // 15 characters // print start char, timestamp and signal level address) return; double ss_W = pow(10.0, ss / 10.0); int sig = round(sqrt(ss_W) * 255.0); if (ss_W > 0 && sig < 1) sig = 1; if (sig > 255) sig = 255; signal_strength = (uint8_t)sig; p = maybe_send_surface_position(mdb, p, end); p = maybe_send_air_position(mdb, p, end); p = maybe_send_air_velocity(mdb, p, end); p = maybe_send_callsign(mdb, p, end); } static int use_tisb = 1; static int should_send(struct uat_adsb_mdb *mdb) { switch (mdb->address_qualifier) { case AQ_ADSB_ICAO: return 1; // Real UAT case AQ_TISB_ICAO: case AQ_TISB_OTHER: return use_tisb; // Only if TIS-B is enabled default: return 1; } } static void frame_to_esnt(frame_type_t type, uint8_t *frame, int len, char *p, char *end, float ss) { _UNUSED(len); *p = '\0'; if (type == UAT_DOWNLINK) { struct uat_adsb_mdb mdb; uat_decode_adsb_mdb(frame, &mdb); if (should_send(&mdb)) { generate_esnt(&mdb, ss, p, end); } } } static int hexbyte(char *buf) { int i; char c; c = buf[0]; if (c >= '0' && c <= '9') i = (c - '0'); else if (c >= 'a' && c <= 'f') i = (c - 'a' + 10); else if (c >= 'A' && c <= 'F') i = (c - 'A' + 10); else return -1; i <<= 4; c = buf[1]; if (c >= '0' && c <= '9') return i | (c - '0'); else if (c >= 'a' && c <= 'f') return i | (c - 'a' + 10); else if (c >= 'A' && c <= 'F') return i | (c - 'A' + 10); else return -1; } static int process_line(frame_handler_t handler, void *handler_data1, void *handler_data2, char *p, char *end) { uint8_t frame_back[UPLINK_FRAME_DATA_BYTES]; uint8_t *frame = frame_back; int len = 0; frame_type_t frametype; if (*p == '-') frametype = UAT_DOWNLINK; else if (*p == '+') frametype = UAT_UPLINK; else return 0; ++p; while (p < end) { int byte; /* * If we hit a semicolon, the rest of the line is "extra info", like timestmap, RSSI, etc. * Try to parse some of it... */ if (p[0] == ';') { int done = 0; float signal_strength = 0; p++; while (*p && !done) { if (!strncmp(p, "ss=", 3)) sscanf(p, "ss=%f;", &signal_strength); if (!strncmp(p, "rssi=", 5)) sscanf(p, "rssi=%f;", &signal_strength); // if (!strncmp(p, "t=", 2)) // sscanf(p, "t=%f;", ×tamp); /* Advance until next semicolon (or end)... */ while (*p && *p != ';') { p++; } // Short-circuit and bail if we have SS - remove later if we want to parse other info if (!*p || (signal_strength != 0)) { done = 1; } else { p++; } } // ignore rest of line handler(frametype, frame_back, len, handler_data1, handler_data2, signal_strength); return 1; } if (len >= (int) sizeof(frame_back)) { return 0; // oversized frame } byte = hexbyte(p); if (byte < 0) { return 0; // badly formatted byte } ++len; *frame++ = byte; p += 2; } return 0; // ran off the end without seeing semicolon } void uat2esnt_convert_message(char *p, char *end, char *out, char *out_end) { out[0] = '\0'; process_line(frame_to_esnt, out, out_end, p, end); } readsb-3.16/uat2esnt/uat2esnt.h000066400000000000000000000026511505057307600164020ustar00rootroot00000000000000// // Copyright 2015, Oliver Jowett // // This file is free software: you may copy, redistribute and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 2 of the License, or (at your // option) any later version. // // This file 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 . #ifndef UAT2ESNT_H #define UAT2ESNT_H #include // call once before calling other functions void uat2esnt_initCrcTables(); // // p is the start of the UAT message starting with + or - // end points one byte after the last message byte // // allocate 1024 bytes of output buffer and pass as out variable // pass pointer past end of buffer as out_end // // 1 uat message can result in the output of multiple AVR type messages, each message is newline terminated // // using AVR / raw bytes as ascii as output, using this fromat with timestamp and signal: <+beast_ts+sigL+raw+;\n // // // This file is free software: you may copy, redistribute and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 2 of the License, or (at your // option) any later version. // // This file 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 . #include #include #include #include "uat.h" #include "uat_decode.h" static void uat_decode_hdr(uint8_t *frame, struct uat_adsb_mdb *mdb) { mdb->mdb_type = (frame[0] >> 3) & 0x1f; mdb->address_qualifier = (address_qualifier_t) (frame[0] & 0x07); mdb->address = (frame[1] << 16) | (frame[2] << 8) | frame[3]; } static const char *address_qualifier_names[8] = { "ICAO address via ADS-B", "reserved (national use)", "ICAO address via TIS-B", "TIS-B track file address", "Vehicle address", "Fixed ADS-B Beacon Address", "reserved (6)", "reserved (7)" }; static void uat_display_hdr(const struct uat_adsb_mdb *mdb, FILE *to) { fprintf(to, "HDR:\n" " MDB Type: %d\n" " Address: %06X (%s)\n", mdb->mdb_type, mdb->address, address_qualifier_names[mdb->address_qualifier]); } static double dimensions_widths[16] = { 11.5, 23, 28.5, 34, 33, 38, 39.5, 45, 45, 52, 59.5, 67, 72.5, 80, 80, 90 }; static void uat_decode_sv(uint8_t *frame, struct uat_adsb_mdb *mdb) { uint32_t raw_lat, raw_lon, raw_alt; mdb->has_sv = 1; mdb->nic = (frame[11] & 15); raw_lat = (frame[4] << 15) | (frame[5] << 7) | (frame[6] >> 1); raw_lon = ((frame[6] & 0x01) << 23) | (frame[7] << 15) | (frame[8] << 7) | (frame[9] >> 1); if (mdb->nic != 0 || raw_lat != 0 || raw_lon != 0) { mdb->position_valid = 1; mdb->lat = raw_lat * 360.0 / 16777216.0; if (mdb->lat > 90) mdb->lat -= 180; mdb->lon = raw_lon * 360.0 / 16777216.0; if (mdb->lon > 180) mdb->lon -= 360; } raw_alt = (frame[10] << 4) | ((frame[11] & 0xf0) >> 4); if (raw_alt != 0) { mdb->altitude_type = (frame[9] & 1) ? ALT_GEO : ALT_BARO; mdb->altitude = (raw_alt - 1) * 25 - 1000; } mdb->airground_state = (frame[12] >> 6) & 0x03; switch (mdb->airground_state) { case AG_SUBSONIC: case AG_SUPERSONIC: { int raw_ns, raw_ew, raw_vvel; raw_ns = ((frame[12] & 0x1f) << 6) | ((frame[13] & 0xfc) >> 2); if ((raw_ns & 0x3ff) != 0) { mdb->ns_vel_valid = 1; mdb->ns_vel = ((raw_ns & 0x3ff) - 1); if (raw_ns & 0x400) mdb->ns_vel = 0 - mdb->ns_vel; if (mdb->airground_state == AG_SUPERSONIC) mdb->ns_vel *= 4; } raw_ew = ((frame[13] & 0x03) << 9) | (frame[14] << 1) | ((frame[15] & 0x80) >> 7); if ((raw_ew & 0x3ff) != 0) { mdb->ew_vel_valid = 1; mdb->ew_vel = ((raw_ew & 0x3ff) - 1); if (raw_ew & 0x400) mdb->ew_vel = 0 - mdb->ew_vel; if (mdb->airground_state == AG_SUPERSONIC) mdb->ew_vel *= 4; } if (mdb->ns_vel_valid && mdb->ew_vel_valid) { if (mdb->ns_vel != 0 || mdb->ew_vel != 0) { mdb->track_type = TT_TRACK; mdb->track = (uint16_t)(360 + 90 - atan2(mdb->ns_vel, mdb->ew_vel) * 180 / M_PI) % 360; } mdb->speed_valid = 1; mdb->speed = (int) sqrt(mdb->ns_vel * mdb->ns_vel + mdb->ew_vel * mdb->ew_vel); } raw_vvel = ((frame[15] & 0x7f) << 4) | ((frame[16] & 0xf0) >> 4); if ((raw_vvel & 0x1ff) != 0) { mdb->vert_rate_source = (raw_vvel & 0x400) ? ALT_BARO : ALT_GEO; mdb->vert_rate = ((raw_vvel & 0x1ff) - 1) * 64; if (raw_vvel & 0x200) mdb->vert_rate = 0 - mdb->vert_rate; } } break; case AG_GROUND: { int raw_gs, raw_track; raw_gs = ((frame[12] & 0x1f) << 6) | ((frame[13] & 0xfc) >> 2); if (raw_gs != 0) { mdb->speed_valid = 1; mdb->speed = ((raw_gs & 0x3ff) - 1); } raw_track = ((frame[13] & 0x03) << 9) | (frame[14] << 1) | ((frame[15] & 0x80) >> 7); switch ((raw_track & 0x0600)>>9) { case 1: mdb->track_type = TT_TRACK; break; case 2: mdb->track_type = TT_MAG_HEADING; break; case 3: mdb->track_type = TT_TRUE_HEADING; break; } if (mdb->track_type != TT_INVALID) mdb->track = (raw_track & 0x1ff) * 360 / 512; mdb->dimensions_valid = 1; mdb->length = 15 + 10 * ((frame[15] & 0x38) >> 3); mdb->width = dimensions_widths[(frame[15] & 0x78) >> 3]; mdb->position_offset = (frame[15] & 0x04) ? 1 : 0; } break; case AG_RESERVED: // nothing break; } if ((frame[0] & 7) == 2 || (frame[0] & 7) == 3) { mdb->utc_coupled = 0; mdb->tisb_site_id = (frame[16] & 0x0f); } else { mdb->utc_coupled = (frame[16] & 0x08) ? 1 : 0; mdb->tisb_site_id = 0; } } static void uat_display_sv(const struct uat_adsb_mdb *mdb, FILE *to) { if (!mdb->has_sv) return; fprintf(to, "SV:\n" " NIC: %u\n", mdb->nic); if (mdb->position_valid) fprintf(to, " Latitude: %+.4f\n" " Longitude: %+.4f\n", mdb->lat, mdb->lon); switch (mdb->altitude_type) { case ALT_BARO: fprintf(to, " Altitude: %d ft (barometric)\n", mdb->altitude); break; case ALT_GEO: fprintf(to, " Altitude: %d ft (geometric)\n", mdb->altitude); break; default: break; } if (mdb->ns_vel_valid) fprintf(to, " N/S velocity: %d kt\n", mdb->ns_vel); if (mdb->ew_vel_valid) fprintf(to, " E/W velocity: %d kt\n", mdb->ew_vel); switch (mdb->track_type) { case TT_TRACK: fprintf(to, " Track: %u\n", mdb->track); break; case TT_MAG_HEADING: fprintf(to, " Heading: %u (magnetic)\n", mdb->track); break; case TT_TRUE_HEADING: fprintf(to, " Heading: %u (true)\n", mdb->track); break; default: break; } if (mdb->speed_valid) fprintf(to, " Speed: %u kt\n", mdb->speed); switch (mdb->vert_rate_source) { case ALT_BARO: fprintf(to, " Vertical rate: %d ft/min (from barometric altitude)\n", mdb->vert_rate); break; case ALT_GEO: fprintf(to, " Vertical rate: %d ft/min (from geometric altitude)\n", mdb->vert_rate); break; default: break; } if (mdb->dimensions_valid) fprintf(to, " Dimensions: %.1fm L x %.1fm W%s\n", mdb->length, mdb->width, mdb->position_offset ? " (position offset applied)" : ""); fprintf(to, " UTC coupling: %s\n" " TIS-B site ID: %u\n", mdb->utc_coupled ? "yes" : "no", mdb->tisb_site_id); } static char base40_alphabet[41] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ .."; static void uat_decode_ms(uint8_t *frame, struct uat_adsb_mdb *mdb) { uint16_t v; int i; mdb->has_ms = 1; v = (frame[17]<<8) | (frame[18]); mdb->emitter_category = (v/1600) % 40; mdb->callsign[0] = base40_alphabet[(v/40) % 40]; mdb->callsign[1] = base40_alphabet[v % 40]; v = (frame[19]<<8) | (frame[20]); mdb->callsign[2] = base40_alphabet[(v/1600) % 40]; mdb->callsign[3] = base40_alphabet[(v/40) % 40]; mdb->callsign[4] = base40_alphabet[v % 40]; v = (frame[21]<<8) | (frame[22]); mdb->callsign[5] = base40_alphabet[(v/1600) % 40]; mdb->callsign[6] = base40_alphabet[(v/40) % 40]; mdb->callsign[7] = base40_alphabet[v % 40]; mdb->callsign[8] = 0; // trim trailing spaces for (i = 7; i >= 0; --i) { if (mdb->callsign[i] == ' ') mdb->callsign[i] = 0; else break; } mdb->emergency_status = (frame[23] >> 5) & 7; mdb->uat_version = (frame[23] >> 2) & 7; mdb->sil = (frame[23] & 3); mdb->transmit_mso = (frame[24] >> 2) & 0x3f; mdb->nac_p = (frame[25] >> 4) & 15; mdb->nac_v = (frame[25] >> 1) & 7; mdb->nic_baro = (frame[25] & 1); mdb->has_cdti = (frame[26] & 0x80 ? 1 : 0); mdb->has_acas = (frame[26] & 0x40 ? 1 : 0); mdb->acas_ra_active = (frame[26] & 0x20 ? 1 : 0); mdb->ident_active = (frame[26] & 0x10 ? 1 : 0); mdb->atc_services = (frame[26] & 0x08 ? 1 : 0); mdb->heading_type = (frame[26] & 0x04 ? HT_MAGNETIC : HT_TRUE); if (mdb->callsign[0]) mdb->callsign_type = (frame[26] & 0x02 ? CS_CALLSIGN : CS_SQUAWK); } static const char *emitter_category_names[40] = { "No information", // A0 "Light <= 7000kg", "Medium Wake 7000-34000kg", "Medium Wake 34000-136000kg", "Medium Wake High Vortex 34000-136000kg", "Heavy >= 136000kg", "Highly Maneuverable", "Rotorcraft", // A7 "reserved (8)", // B0 "Glider/Sailplane", "Lighter than air", "Parachutist / sky diver", "Ultra light / hang glider / paraglider", "reserved (13)", "UAV", "Space / transatmospheric", // B7 "reserved (16)", // C0 "Emergency vehicle", "Service vehicle", "Point obstacle", "Cluster obstacle", "Line obstacle", "reserved (22)", "reserved (23)", // C7 "reserved (24)", "reserved (25)", "reserved (26)", "reserved (27)", "reserved (28)", "reserved (29)", "reserved (30)", "reserved (31)", "reserved (32)", "reserved (33)", "reserved (34)", "reserved (35)", "reserved (36)", "reserved (37)", "reserved (38)", "reserved (39)" }; static const char *emergency_status_names[8] = { "No emergency", "General emergency", "Lifeguard / Medical emergency", "Minimum fuel", "No communications", "Unlawful interference", "Downed aircraft", "reserved" }; static void uat_display_ms(const struct uat_adsb_mdb *mdb, FILE *to) { if (!mdb->has_ms) return; fprintf(to, "MS:\n" " Emitter category: %s\n" " Callsign: %s%s\n" " Emergency status: %s\n" " UAT version: %u\n" " SIL: %u\n" " Transmit MSO: %u\n" " NACp: %u\n" " NACv: %u\n" " NICbaro: %u\n" " Capabilities: %s%s\n" " Active modes: %s%s%s\n" " Target track type: %s\n", emitter_category_names[mdb->emitter_category], mdb->callsign_type == CS_SQUAWK ? "squawk " : "", mdb->callsign_type == CS_INVALID ? "unavailable" : mdb->callsign, emergency_status_names[mdb->emergency_status], mdb->uat_version, mdb->sil, mdb->transmit_mso, mdb->nac_p, mdb->nac_v, mdb->nic_baro, mdb->has_cdti ? "CDTI " : "", mdb->has_acas ? "ACAS " : "", mdb->acas_ra_active ? "ACASRA " : "", mdb->ident_active ? "IDENT " : "", mdb->atc_services ? "ATC " : "", mdb->heading_type == HT_MAGNETIC ? "magnetic heading" : "true heading"); } static void uat_decode_auxsv(uint8_t *frame, struct uat_adsb_mdb *mdb) { int raw_alt = (frame[29] << 4) | ((frame[30] & 0xf0) >> 4); if (raw_alt != 0) { mdb->sec_altitude = (raw_alt - 1) * 25 - 1000; mdb->sec_altitude_type = (frame[9] & 1) ? ALT_BARO : ALT_GEO; } else { mdb->sec_altitude_type = ALT_INVALID; } mdb->has_auxsv = 1; } static void uat_display_auxsv(const struct uat_adsb_mdb *mdb, FILE *to) { if (!mdb->has_auxsv) return; fprintf(to, "AUXSV:\n"); switch (mdb->sec_altitude_type) { case ALT_BARO: fprintf(to, " Sec. altitude: %d ft (barometric)\n", mdb->sec_altitude); break; case ALT_GEO: fprintf(to, " Sec. altitude: %d ft (geometric)\n", mdb->sec_altitude); break; default: fprintf(to, " Sec. altitude: unavailable\n"); break; } } void uat_decode_adsb_mdb(uint8_t *frame, struct uat_adsb_mdb *mdb) { static struct uat_adsb_mdb mdb_zero; *mdb = mdb_zero; uat_decode_hdr(frame, mdb); switch (mdb->mdb_type) { case 0: // HDR SV case 4: // HDR SV (TC+0) (TS) case 7: // HDR SV reserved case 8: // HDR SV reserved case 9: // HDR SV reserved case 10: // HDR SV reserved uat_decode_sv(frame, mdb); break; case 1: // HDR SV MS AUXSV uat_decode_sv(frame, mdb); uat_decode_ms(frame, mdb); uat_decode_auxsv(frame, mdb); break; case 2: // HDR SV AUXSV case 5: // HDR SV (TC+1) AUXSV case 6: // HDR SV (TS) AUXSV uat_decode_sv(frame, mdb); uat_decode_auxsv(frame, mdb); break; case 3: // HDR SV MS (TS) uat_decode_sv(frame, mdb); uat_decode_ms(frame, mdb); break; default: break; } } void uat_display_adsb_mdb(const struct uat_adsb_mdb *mdb, FILE *to) { uat_display_hdr(mdb, to); uat_display_sv(mdb, to); uat_display_ms(mdb, to); uat_display_auxsv(mdb, to); } static void uat_decode_info_frame(struct uat_uplink_info_frame *frame) { unsigned t_opt; frame->is_fisb = 0; if (frame->type != 0) return; // not FIS-B if (frame->length < 4) // too short for FIS-B return; t_opt = ((frame->data[1] & 0x01) << 1) | (frame->data[2] >> 7); switch (t_opt) { case 0: // Hours, Minutes frame->fisb.monthday_valid = 0; frame->fisb.seconds_valid = 0; frame->fisb.hours = (frame->data[2] & 0x7c) >> 2; frame->fisb.minutes = ((frame->data[2] & 0x03) << 4) | (frame->data[3] >> 4); frame->fisb.length = frame->length - 4; frame->fisb.data = frame->data + 4; break; case 1: // Hours, Minutes, Seconds if (frame->length < 5) return; frame->fisb.monthday_valid = 0; frame->fisb.seconds_valid = 1; frame->fisb.hours = (frame->data[2] & 0x7c) >> 2; frame->fisb.minutes = ((frame->data[2] & 0x03) << 4) | (frame->data[3] >> 4); frame->fisb.seconds = ((frame->data[3] & 0x0f) << 2) | (frame->data[4] >> 6); frame->fisb.length = frame->length - 5; frame->fisb.data = frame->data + 5; break; case 2: // Month, Day, Hours, Minutes if (frame->length < 5) return; frame->fisb.monthday_valid = 1; frame->fisb.seconds_valid = 0; frame->fisb.month = (frame->data[2] & 0x78) >> 3; frame->fisb.day = ((frame->data[2] & 0x07) << 2) | (frame->data[3] >> 6); frame->fisb.hours = (frame->data[3] & 0x3e) >> 1; frame->fisb.minutes = ((frame->data[3] & 0x01) << 5) | (frame->data[4] >> 3); frame->fisb.length = frame->length - 5; // ??? frame->fisb.data = frame->data + 5; break; case 3: // Month, Day, Hours, Minutes, Seconds if (frame->length < 6) return; frame->fisb.monthday_valid = 1; frame->fisb.seconds_valid = 1; frame->fisb.month = (frame->data[2] & 0x78) >> 3; frame->fisb.day = ((frame->data[2] & 0x07) << 2) | (frame->data[3] >> 6); frame->fisb.hours = (frame->data[3] & 0x3e) >> 1; frame->fisb.minutes = ((frame->data[3] & 0x01) << 5) | (frame->data[4] >> 3); frame->fisb.seconds = ((frame->data[4] & 0x03) << 3) | (frame->data[5] >> 5); frame->fisb.length = frame->length - 6; frame->fisb.data = frame->data + 6; break; } frame->fisb.a_flag = (frame->data[0] & 0x80) ? 1 : 0; frame->fisb.g_flag = (frame->data[0] & 0x40) ? 1 : 0; frame->fisb.p_flag = (frame->data[0] & 0x20) ? 1 : 0; frame->fisb.product_id = ((frame->data[0] & 0x1f) << 6) | (frame->data[1] >> 2); frame->fisb.s_flag = (frame->data[1] & 0x02) ? 1 : 0; frame->is_fisb = 1; } void uat_decode_uplink_mdb(uint8_t *frame, struct uat_uplink_mdb *mdb) { mdb->position_valid = (frame[5] & 0x01) ? 1 : 0; /* Even with position_valid = 0, there seems to be plausible data here. * Decode it always. */ /*if (mdb->position_valid)*/ { uint32_t raw_lat = (frame[0] << 15) | (frame[1] << 7) | (frame[2] >> 1); uint32_t raw_lon = ((frame[2] & 0x01) << 23) | (frame[3] << 15) | (frame[4] << 7) | (frame[5] >> 1); mdb->lat = raw_lat * 360.0 / 16777216.0; if (mdb->lat > 90) mdb->lat -= 180; mdb->lon = raw_lon * 360.0 / 16777216.0; if (mdb->lon > 180) mdb->lon -= 360; } mdb->utc_coupled = (frame[6] & 0x80) ? 1 : 0; mdb->app_data_valid = (frame[6] & 0x20) ? 1 : 0; mdb->slot_id = (frame[6] & 0x1f); mdb->tisb_site_id = (frame[7] >> 4); if (mdb->app_data_valid) { uint8_t *data, *end; memcpy(mdb->app_data, frame+8, 424); mdb->num_info_frames = 0; data = mdb->app_data; end = mdb->app_data + 424; while (mdb->num_info_frames < UPLINK_MAX_INFO_FRAMES && data+2 <= end) { struct uat_uplink_info_frame *frame = &mdb->info_frames[mdb->num_info_frames]; frame->length = (data[0] << 1) | (data[1] >> 7); frame->type = (data[1] & 0x0f); if (data + frame->length + 2 > end) { // overrun? break; } if (frame->length == 0 && frame->type == 0) { break; // no more frames } frame->data = data + 2; uat_decode_info_frame(frame); data += frame->length + 2; ++mdb->num_info_frames; } } } static void display_generic_data(uint8_t *data, uint16_t length, FILE *to) { unsigned i; fprintf(to, " Data: "); for (i = 0; i < length; i += 16) { unsigned j; if (i > 0) fprintf(to, " "); for (j = i; j < i+16; ++j) { if (j < length) fprintf(to, "%02X ", data[j]); else fprintf(to, " "); } for (j = i; j < i+16 && j < length; ++j) { fprintf(to, "%c", (data[j] >= 32 && data[j] < 127) ? data[j] : '.'); } fprintf(to, "\n"); } } // The odd two-string-literals here is to avoid \0x3ABCDEF being interpreted as a single (very large valued) character static const char *dlac_alphabet = "\x03" "ABCDEFGHIJKLMNOPQRSTUVWXYZ\x1A\t\x1E\n| !\"#$%&'()*+,-./0123456789:;<=>?"; static const char *decode_dlac(uint8_t *data, unsigned bytelen) { static char buf[1024]; uint8_t *end = data + bytelen; char *p = buf; int step = 0; int tab = 0; while (data < end) { int ch = 0; assert(step >= 0 && step <= 3); switch (step) { case 0: ch = data[0] >> 2; ++data; break; case 1: ch = ((data[-1] & 0x03) << 4) | (data[0] >> 4); ++data; break; case 2: ch = ((data[-1] & 0x0f) << 2) | (data[0] >> 6); break; case 3: ch = data[0] & 0x3f; ++data; break; } if (tab) { while (ch > 0) *p++ = ' ', ch--; tab = 0; } else if (ch == 28) { // tab tab = 1; } else { *p++ = dlac_alphabet[ch]; } step = (step+1)%4; } *p = 0; return buf; } static const char *get_fisb_product_name(uint16_t product_id) { switch (product_id) { case 0: case 20: return "METAR and SPECI"; case 1: case 21: return "TAF and Amended TAF"; case 2: case 22: return "SIGMET"; case 3: case 23: return "Convective SIGMET"; case 4: case 24: return "AIRMET"; case 5: case 25: return "PIREP"; case 6: case 26: return "AWW"; case 7: case 27: return "Winds and Temperatures Aloft"; case 8: return "NOTAM (Including TFRs) and Service Status"; case 9: return "Aerodrome and Airspace – D-ATIS"; case 10: return "Aerodrome and Airspace - TWIP"; case 11: return "Aerodrome and Airspace - AIRMET"; case 12: return "Aerodrome and Airspace - SIGMET/Convective SIGMET"; case 13: return "Aerodrome and Airspace - SUA Status"; case 51: return "National NEXRAD, Type 0 - 4 level"; case 52: return "National NEXRAD, Type 1 - 8 level (quasi 6-level VIP)"; case 53: return "National NEXRAD, Type 2 - 8 level"; case 54: return "National NEXRAD, Type 3 - 16 level"; case 55: return "Regional NEXRAD, Type 0 - low dynamic range"; case 56: return "Regional NEXRAD, Type 1 - 8 level (quasi 6-level VIP)"; case 57: return "Regional NEXRAD, Type 2 - 8 level"; case 58: return "Regional NEXRAD, Type 3 - 16 level"; case 59: return "Individual NEXRAD, Type 0 - low dynamic range"; case 60: return "Individual NEXRAD, Type 1 - 8 level (quasi 6-level VIP)"; case 61: return "Individual NEXRAD, Type 2 - 8 level"; case 62: return "Individual NEXRAD, Type 3 - 16 level"; case 63: return "Global Block Representation - Regional NEXRAD, Type 4 – 8 level"; case 64: return "Global Block Representation - CONUS NEXRAD, Type 4 - 8 level"; case 81: return "Radar echo tops graphic, scheme 1: 16-level"; case 82: return "Radar echo tops graphic, scheme 2: 8-level"; case 83: return "Storm tops and velocity"; case 101: return "Lightning strike type 1 (pixel level)"; case 102: return "Lightning strike type 2 (grid element level)"; case 151: return "Point phenomena, vector format"; case 201: return "Surface conditions/winter precipitation graphic"; case 202: return "Surface weather systems"; case 254: return "AIRMET, SIGMET: Bitmap encoding"; case 351: return "System Time"; case 352: return "Operational Status"; case 353: return "Ground Station Status"; case 401: return "Generic Raster Scan Data Product APDU Payload Format Type 1"; case 402: case 411: return "Generic Textual Data Product APDU Payload Format Type 1"; case 403: return "Generic Vector Data Product APDU Payload Format Type 1"; case 404: case 412: return "Generic Symbolic Product APDU Payload Format Type 1"; case 405: case 413: return "Generic Textual Data Product APDU Payload Format Type 2"; case 600: return "FISDL Products – Proprietary Encoding"; case 2000: return "FAA/FIS-B Product 1 – Developmental"; case 2001: return "FAA/FIS-B Product 2 – Developmental"; case 2002: return "FAA/FIS-B Product 3 – Developmental"; case 2003: return "FAA/FIS-B Product 4 – Developmental"; case 2004: return "WSI Products - Proprietary Encoding"; case 2005: return "WSI Developmental Products"; default: return "unknown"; } } static const char *get_fisb_product_format(uint16_t product_id) { switch (product_id) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 351: case 352: case 353: case 402: case 405: return "Text"; case 8: case 9: case 10: case 11: case 12: case 13: return "Text/Graphic"; case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 411: case 413: return "Text (DLAC)"; case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58: case 59: case 60: case 61: case 62: case 63: case 64: case 81: case 82: case 83: case 101: case 102: case 151: case 201: case 202: case 254: case 401: case 403: case 404: return "Graphic"; case 412: return "Graphic (DLAC)"; case 600: case 2004: return "Proprietary"; case 2000: case 2001: case 2002: case 2003: case 2005: return "Developmental"; default: return "unknown"; } } static void uat_display_fisb_frame(const struct fisb_apdu *apdu, FILE *to) { fprintf(to, "FIS-B:\n" " Flags: %s%s%s%s\n" " Product ID: %u (%s) - %s\n", apdu->a_flag ? "A" : "", apdu->g_flag ? "G" : "", apdu->p_flag ? "P" : "", apdu->s_flag ? "S" : "", apdu->product_id, get_fisb_product_name(apdu->product_id), get_fisb_product_format(apdu->product_id)); fprintf(to, " Product time: "); if (apdu->monthday_valid) fprintf(to, "%u/%u ", apdu->month, apdu->day); fprintf(to, "%02u:%02u", apdu->hours, apdu->minutes); if (apdu->seconds_valid) fprintf(to, ":%02u", apdu->seconds); fprintf(to, "\n"); switch (apdu->product_id) { case 413: { // Generic text, DLAC const char *text = decode_dlac(apdu->data, apdu->length); const char *report = text; while (report) { char report_buf[1024]; const char *next_report; char *p, *r; next_report = strchr(report, '\x1e'); // RS if (!next_report) next_report = strchr(report, '\x03'); // ETX if (next_report) { memcpy(report_buf, report, next_report - report); report_buf[next_report - report] = 0; report = next_report + 1; } else { strcpy(report_buf, report); report = NULL; } if (!report_buf[0]) continue; r = report_buf; p = strchr(r, ' '); if (p) { *p = 0; fprintf(to, " Report type: %s\n", r); r = p+1; } p = strchr(r, ' '); if (p) { *p = 0; fprintf(to, " Report location: %s\n", r); r = p+1; } p = strchr(r, ' '); if (p) { *p = 0; fprintf(to, " Report time: %s\n", r); r = p+1; } fprintf(to, " Text:\n%s\n", r); } } break; default: display_generic_data(apdu->data, apdu->length, to); break; } } static const char *info_frame_type_names[16] = { "FIS-B APDU", "Reserved for Developmental Use", "Reserved for Future Use (2)", "Reserved for Future Use (3)", "Reserved for Future Use (4)", "Reserved for Future Use (5)", "Reserved for Future Use (6)", "Reserved for Future Use (7)", "Reserved for Future Use (8)", "Reserved for Future Use (9)", "Reserved for Future Use (10)", "Reserved for Future Use (11)", "Reserved for Future Use (12)", "Reserved for Future Use (13)", "Reserved for Future Use (14)", "TIS-B/ADS-R Service Status" }; static void uat_display_uplink_info_frame(const struct uat_uplink_info_frame *frame, FILE *to) { fprintf(to, "INFORMATION FRAME:\n" " Length: %u bytes\n" " Type: %u (%s)\n", frame->length, frame->type, info_frame_type_names[frame->type]); if (frame->length > 0) { if (frame->is_fisb) uat_display_fisb_frame(&frame->fisb, to); else { display_generic_data(frame->data, frame->length, to); } } } void uat_display_uplink_mdb(const struct uat_uplink_mdb *mdb, FILE *to) { fprintf(to, "UPLINK:\n"); fprintf(to, " Site Latitude: %+.4f%s\n" " Site Longitude: %+.4f%s\n", mdb->lat, mdb->position_valid ? "" : " (possibly invalid)", mdb->lon, mdb->position_valid ? "" : " (possibly invalid)"); fprintf(to, " UTC coupled: %s\n" " Slot ID: %u\n" " TIS-B Site ID: %u\n", mdb->utc_coupled ? "yes" : "no", mdb->slot_id, mdb->tisb_site_id); if (mdb->app_data_valid) { unsigned i; for (i = 0; i < mdb->num_info_frames; ++i) uat_display_uplink_info_frame(&mdb->info_frames[i], to); } } readsb-3.16/uat2esnt/uat_decode.h000066400000000000000000000117271505057307600167350ustar00rootroot00000000000000// Part of dump978, a UAT decoder. // // Copyright 2015, Oliver Jowett // // This file is free software: you may copy, redistribute and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 2 of the License, or (at your // option) any later version. // // This file 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 . #ifndef UAT_DECODE_H #define UAT_DECODE_H #include #include #include #include #include "uat.h" // // Datatypes // typedef enum { AQ_ADSB_ICAO=0, AQ_NATIONAL=1, AQ_TISB_ICAO=2, AQ_TISB_OTHER=3, AQ_VEHICLE=4, AQ_FIXED_BEACON=5, AQ_RESERVED_6=6, AQ_RESERVED_7=7 } address_qualifier_t; typedef enum { ALT_INVALID=0, ALT_BARO, ALT_GEO } altitude_type_t; typedef enum { AG_SUBSONIC=0, AG_SUPERSONIC=1, AG_GROUND=2, AG_RESERVED=3 } airground_state_t; typedef enum { TT_INVALID=0, TT_TRACK, TT_MAG_HEADING, TT_TRUE_HEADING } track_type_t; typedef enum { HT_INVALID=0, HT_MAGNETIC, HT_TRUE } heading_type_t; typedef enum { CS_INVALID=0, CS_CALLSIGN, CS_SQUAWK } callsign_type_t; struct uat_adsb_mdb { // presence bits uint32_t has_sv : 1; uint32_t has_ms : 1; uint32_t has_auxsv : 1; uint32_t position_valid : 1; uint32_t ns_vel_valid : 1; uint32_t ew_vel_valid : 1; uint32_t speed_valid : 1; uint32_t dimensions_valid : 1; // // HDR // uint8_t mdb_type; address_qualifier_t address_qualifier; uint32_t address; // // SV // // if position_valid: double lat; double lon; altitude_type_t altitude_type; int32_t altitude; // in feet uint8_t nic; airground_state_t airground_state; // if ns_vel_valid: int16_t ns_vel; // in kts // if ew_vel_valid: int16_t ew_vel; // in kts track_type_t track_type; uint16_t track; // if speed_valid: uint16_t speed; // in kts altitude_type_t vert_rate_source; int16_t vert_rate; // in ft/min // if lengthwidth_valid: double length; // in meters (just to be different) double width; // in meters (just to be different) uint32_t position_offset : 1; // true if Position Offset Applied uint32_t utc_coupled : 1; // true if UTC Coupled flag is set (ADS-B) uint8_t tisb_site_id; // TIS-B site ID, or zero in ADS-B messages // // MS // uint8_t emitter_category; callsign_type_t callsign_type; char callsign[9]; uint8_t emergency_status; uint8_t uat_version; uint8_t sil; uint8_t transmit_mso; uint8_t nac_p; uint8_t nac_v; uint8_t nic_baro; // capabilities: uint32_t has_cdti : 1; uint32_t has_acas : 1; // operational modes: uint32_t acas_ra_active : 1; uint32_t ident_active : 1; uint32_t atc_services : 1; heading_type_t heading_type; // // AUXSV altitude_type_t sec_altitude_type; int32_t sec_altitude; // in feet }; static inline int64_t mstime(void) { struct timeval tv; int64_t mst; gettimeofday(&tv, NULL); mst = ((int64_t) tv.tv_sec)*1000; mst += tv.tv_usec / 1000; return mst; } // // Decode/display prototypes // void uat_decode_adsb_mdb(uint8_t *frame, struct uat_adsb_mdb *mdb); void uat_display_adsb_mdb(const struct uat_adsb_mdb *mdb, FILE *to); // // UPLINK // // assume 6 byte frames: 2 header bytes, 4 byte payload // (TIS-B heartbeat with one address, or empty FIS-B APDU) #define UPLINK_MAX_INFO_FRAMES (424/6) struct fisb_apdu { uint32_t a_flag : 1; uint32_t g_flag : 1; uint32_t p_flag : 1; uint32_t s_flag : 1; uint32_t monthday_valid : 1; uint32_t seconds_valid : 1; uint16_t product_id; uint8_t month; // if monthday_valid uint8_t day; // if monthday_valid uint8_t hours; uint8_t minutes; uint8_t seconds; // if seconds_valid uint16_t length; uint8_t *data; }; struct uat_uplink_info_frame { uint32_t is_fisb : 1; uint16_t length; uint8_t type; uint8_t *data; // points within the containing appdata // if is_fisb: struct fisb_apdu fisb; }; struct uat_uplink_mdb { uint32_t position_valid : 1; uint32_t utc_coupled : 1; uint32_t app_data_valid : 1; // if position_valid: double lat; double lon; uint8_t slot_id; uint8_t tisb_site_id; // if app_data_valid: uint8_t app_data[424]; unsigned num_info_frames; struct uat_uplink_info_frame info_frames[UPLINK_MAX_INFO_FRAMES]; }; void uat_decode_uplink_mdb(uint8_t *frame, struct uat_uplink_mdb *mdb); void uat_display_uplink_mdb(const struct uat_uplink_mdb *mdb, FILE *to); #endif readsb-3.16/util.c000066400000000000000000001105411505057307600140360ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // util.c: misc utilities // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . // // This file incorporates work covered by the following copyright and // license: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "readsb.h" #include int64_t mstime(void) { if (Modes.synthetic_now) return Modes.synthetic_now; struct timeval tv; int64_t mst; gettimeofday(&tv, NULL); mst = ((int64_t) tv.tv_sec)*1000; mst += tv.tv_usec / 1000; return mst; } int64_t microtime(void) { if (Modes.synthetic_now) return 1000 * Modes.synthetic_now; struct timeval tv; int64_t mst; gettimeofday(&tv, NULL); mst = ((int64_t) tv.tv_sec) * 1000LL * 1000LL; mst += tv.tv_usec; return mst; } void milli_micro_seconds(int64_t *milli, int64_t *micro) { if (Modes.synthetic_now) { *milli = Modes.synthetic_now; *micro = 1000 * Modes.synthetic_now; return; } struct timeval tv; gettimeofday(&tv, NULL); *milli = ((int64_t) tv.tv_sec) * 1000 + ((int64_t) tv.tv_usec) / 1000; *micro = ((int64_t) tv.tv_sec) * (1000 * 1000) + ((int64_t) tv.tv_usec); } int64_t mono_micro_seconds() { if (Modes.synthetic_now) { return 1000 * Modes.synthetic_now; } struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); int64_t micro = ((int64_t) ts.tv_sec) * (1000 * 1000) + ((int64_t) ts.tv_nsec) / 1000; return micro; } int64_t mono_milli_seconds() { if (Modes.synthetic_now) { return Modes.synthetic_now; } struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); int64_t milli = ((int64_t) ts.tv_sec) * 1000 + ((int64_t) ts.tv_nsec) / (1000 * 1000); return milli; } int64_t getUptime() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); int64_t milli = ((int64_t) ts.tv_sec) * 1000 + ((int64_t) ts.tv_nsec) / (1000 * 1000); return milli - Modes.startup_time_mono; } int snprintHMS(char *buf, size_t bufsize, int64_t now) { time_t nowTime = nearbyint(now / 1000.0); struct tm local; localtime_r(&nowTime, &local); char timebuf[128]; strftime(timebuf, 128, "%T", &local); return snprintf(buf, bufsize, "%s.%03d", timebuf, (int) (now % 1000)); } int64_t msThreadTime(void) { struct timespec ts; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts); return ((int64_t) ts.tv_sec * 1000 + ts.tv_nsec / (1000 * 1000)); } int64_t nsThreadTime(void) { struct timespec ts; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts); return ((int64_t) ts.tv_sec * (1000LL * 1000LL * 1000LL) + ts.tv_nsec); } int64_t receiveclock_ns_elapsed(int64_t t1, int64_t t2) { return (t2 - t1) * 1000U / 12U; } int64_t receiveclock_ms_elapsed(int64_t t1, int64_t t2) { return (t2 - t1) / 12000U; } /* record current CPU time in start_time */ void start_cpu_timing(struct timespec *start_time) { clock_gettime(CLOCK_THREAD_CPUTIME_ID, start_time); } /* add difference between start_time and the current CPU time to add_to */ void end_cpu_timing(const struct timespec *start_time, struct timespec *add_to) { struct timespec end_time; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end_time); add_to->tv_sec += end_time.tv_sec - start_time->tv_sec; add_to->tv_nsec += end_time.tv_nsec - start_time->tv_nsec; normalize_timespec(add_to); } void timespec_add_elapsed(const struct timespec *start_time, const struct timespec *end_time, struct timespec *add_to) { add_to->tv_sec += end_time->tv_sec - start_time->tv_sec; add_to->tv_nsec += end_time->tv_nsec - start_time->tv_nsec; normalize_timespec(add_to); } void start_monotonic_timing(struct timespec *start_time) { clock_gettime(CLOCK_MONOTONIC, start_time); } void end_monotonic_timing(const struct timespec *start_time, struct timespec *add_to) { struct timespec end_time; clock_gettime(CLOCK_MONOTONIC, &end_time); add_to->tv_sec += end_time.tv_sec - start_time->tv_sec; add_to->tv_nsec += end_time.tv_nsec - start_time->tv_nsec; normalize_timespec(add_to); } /* record current monotonic time in start_time */ void startWatch(struct timespec *start_time) { clock_gettime(CLOCK_MONOTONIC, start_time); } // return elapsed time int64_t stopWatch(struct timespec *start_time) { struct timespec end_time; clock_gettime(CLOCK_MONOTONIC, &end_time); int64_t res = ((int64_t) end_time.tv_sec * 1000UL + end_time.tv_nsec / 1000000UL) - ((int64_t) start_time->tv_sec * 1000UL + start_time->tv_nsec / 1000000UL); return res; } // return elapsed time and set start_time to current time int64_t lapWatch(struct timespec *start_time) { struct timespec end_time; clock_gettime(CLOCK_MONOTONIC, &end_time); int64_t res = ((int64_t) end_time.tv_sec * 1000UL + end_time.tv_nsec / 1000000UL) - ((int64_t) start_time->tv_sec * 1000UL + start_time->tv_nsec / 1000000UL); if (start_time->tv_sec == 0 && start_time->tv_nsec == 0) { res = 0; } *start_time = end_time; return res; } // this is not cryptographic but much better than mstime() as a seed unsigned int get_seed() { struct timespec time; clock_gettime(CLOCK_REALTIME, &time); unsigned int seed = (uint64_t) time.tv_sec ^ (uint64_t) time.tv_nsec ^ (((uint64_t) getpid()) << 16) ^ (((uint64_t) (uintptr_t) pthread_self()) << 10); return seed; } // increment target by increment in ms, if result is in the past, set target to now. // specialized function for scheduling threads using pthreadcondtimedwait static void incTimedwait(struct timespec *target, int64_t increment) { struct timespec inc = msToTimespec(increment); target->tv_sec += inc.tv_sec; target->tv_nsec += inc.tv_nsec; normalize_timespec(target); struct timespec now; clock_gettime(CLOCK_REALTIME, &now); int64_t min_sleep = 50 * 1000; // always wait a bit (50 us) to yield (i hope) if (target->tv_sec < now.tv_sec || (target->tv_sec == now.tv_sec && target->tv_nsec <= now.tv_nsec + min_sleep)) { target->tv_sec = now.tv_sec; target->tv_nsec = now.tv_nsec + min_sleep; normalize_timespec(target); } } #define uThreadMax (32) static threadT *uThreads[uThreadMax]; static int uThreadCount = 0; void threadInit(threadT *thread, char *name) { if (uThreadCount >= uThreadMax) { fprintf(stderr, "util.c: increase uThreadmax!\n"); exit(1); } if (uThreadCount == 0) { memset(uThreads, 0, sizeof (uThreads)); } memset(thread, 0, sizeof (threadT)); pthread_mutex_init(&thread->mutex, NULL); pthread_cond_init(&thread->cond, NULL); thread->name = strdup(name); uThreads[uThreadCount++] = thread; thread->joined = 1; } void threadCreate(threadT *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) { if (!thread->joined) { fprintf(stderr, "<3>FATAL: threadCreate() thread %s failed: already running?\n", thread->name); setExit(2); } int res = pthread_create(&thread->pthread, attr, start_routine, arg); if (res != 0) { fprintf(stderr, "<3>FATAL: threadCreate() pthread_create() failed: %s\n", strerror(res)); setExit(2); } thread->joined = 0; thread->joinFailed = 0; } static void threadDestroy(threadT *thread) { // if the join didn't work, don't clean up if (!thread->joined || thread->joinFailed) { fprintf(stderr, "<3>FATAL: thread %s could not be joined, calling abort()!\n", thread->name); abort(); } pthread_mutex_destroy(&thread->mutex); pthread_cond_destroy(&thread->cond); sfree(thread->name); } void threadDestroyAll() { for (int i = 0; i < uThreadCount; i++) { threadDestroy(uThreads[i]); } uThreadCount = 0; } void threadTimedWait(threadT *thread, struct timespec *ts, int64_t increment) { // don't wait when we want to exit if (Modes.exit) return; incTimedwait(ts, increment); int err = pthread_cond_timedwait(&thread->cond, &thread->mutex, ts); if (err && err != ETIMEDOUT) fprintf(stderr, "%s thread: pthread_cond_timedwait unexpected error: %s\n", thread->name, strerror(err)); } void threadSignalJoin(threadT *thread) { if (thread->joined) return; int err = 0; #ifdef __APPLE__ pthread_join(thread->pthread, NULL); #else int64_t timeout = Modes.joinTimeout; while ((err = pthread_tryjoin_np(thread->pthread, NULL)) && timeout-- > 0) { pthread_cond_signal(&thread->cond); msleep(1); } #endif if (err == 0) { thread->joined = 1; } else { thread->joinFailed = 1; fprintf(stderr, "%s thread: threadSignalJoin timed out after %.1f seconds, undefined behaviour may result!\n", thread->name, (float) Modes.joinTimeout / (float) SECONDS); Modes.joinTimeout /= 2; Modes.joinTimeout = imax(Modes.joinTimeout, 2 * SECONDS); } } int threadAffinity(int core_id) { int num_cores = Modes.num_procs; if (core_id < 0 || core_id >= num_cores) return EINVAL; cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(core_id, &cpuset); return sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); } struct char_buffer readWholeFile(int fd, char *errorContext) { struct char_buffer cb = {0}; struct stat fileinfo = {0}; if (fstat(fd, &fileinfo)) { fprintf(stderr, "%s: readWholeFile: fstat failed, wat?!\n", errorContext); return cb; } size_t fsize = fileinfo.st_size; int extra = 128 * 1024; cb.buffer = cmalloc(fsize + extra); memset(cb.buffer, 0x0, fsize + extra); // zero entire buffer if (!cb.buffer) { fprintf(stderr, "%s: readWholeFile couldn't allocate buffer!\n", errorContext); return cb; } int64_t res = 0; int toRead = fsize; cb.len = 0; while (toRead >= 0) { res = read(fd, cb.buffer + cb.len, toRead); if (res <= 0) { if (errno == EINTR) { continue; } break; } cb.len += res; toRead -= res; } if (fstat(fd, &fileinfo)) { fprintf(stderr, "%s: readWholeFile: fstat failed, wat?!\n", errorContext); sfree(cb.buffer); cb.len = 0; } if (toRead < 0 || res < 0 || cb.len != fsize || (size_t) fileinfo.st_size != fsize) { fprintf(stderr, "%s: readWholeFile size mismatch! toRead %ld res %ld %s cb.len %ld fsize %ld fileinfo.st_size %ld\n", errorContext, (long) toRead, (long) res, strerror(res), (long) cb.len, (long) fsize, (long) fileinfo.st_size); sfree(cb.buffer); cb.len = 0; } return cb; } struct char_buffer readWholeGz(gzFile gzfp, char *errorContext) { struct char_buffer cb = {0}; if (gzbuffer(gzfp, GZBUFFER_BIG) < 0) { fprintf(stderr, "reading %s: gzbuffer fail!\n", errorContext); } int alloc = 1 * 1024 * 1024; cb.buffer = cmalloc(alloc); if (!cb.buffer) { fprintf(stderr, "reading %s: readWholeGz alloc fail!\n", errorContext); return cb; } int res; int toRead = alloc; while (true) { res = gzread(gzfp, cb.buffer + cb.len, toRead); if (res <= 0) break; cb.len += res; toRead -= res; if (toRead == 0) { alloc *= 2; char *oldBuffer = cb.buffer; cb.buffer = realloc(cb.buffer, alloc); if (!cb.buffer) { sfree(oldBuffer); fprintf(stderr, "reading %s: readWholeGz alloc fail!\n", errorContext); return (struct char_buffer) {0}; } toRead = alloc - cb.len; } } if (res < 0) { sfree(cb.buffer); int error; fprintf(stderr, "readWholeGz: gzread failed: %s (res == %d)\n", gzerror(gzfp, &error), res); if (error == Z_ERRNO) perror(errorContext); return (struct char_buffer) {0}; } return cb; } // wrapper to write to an opened gzFile int writeGz(gzFile gzfp, void *source, int toWrite, char *errorContext) { int res, error; int nwritten = 0; char *p = source; if (!gzfp) { fprintf(stderr, "writeGz: gzfp was NULL .............\n"); return -1; } while (toWrite > 0) { int len = toWrite; //if (len > 8 * 1024 * 1024) // len = 8 * 1024 * 1024; res = gzwrite(gzfp, p, len); if (res <= 0) { fprintf(stderr, "gzwrite of length %d failed: %s (res == %d)\n", toWrite, gzerror(gzfp, &error), res); if (error == Z_ERRNO) perror(errorContext); return -1; } p += res; nwritten += res; toWrite -= res; } return nwritten; } void printTimestamp(FILE *stream, int64_t time_ms) { char timebuf[128]; char timebuf2[128]; time_t now; struct tm local; now = floor(time_ms / 1000.0); localtime_r(&now, &local); strftime(timebuf, 128, "%Y-%m-%d %T", &local); timebuf[127] = 0; strftime(timebuf2, 128, "%Z", &local); timebuf2[127] = 0; fprintf(stream, "[%s.%03d %s] ", timebuf, (int) (time_ms % 1000), timebuf2); } void log_with_timestamp(const char *format, ...) { char msg[1024]; va_list ap; va_start(ap, format); vsnprintf(msg, 1024, format, ap); va_end(ap); msg[1023] = 0; printTimestamp(stderr, mstime()); fprintf(stderr, "%s\n", msg); } int64_t roundSeconds(int interval, int offset, int64_t epoch_ms) { if (offset >= interval) fprintf(stderr, "roundSeconds was used wrong, interval must be greater than offset\n"); time_t epoch = epoch_ms / SECONDS + (epoch_ms % SECONDS >= SECONDS / 2); struct tm utc; gmtime_r(&epoch, &utc); int sec = utc.tm_sec; int step = nearbyint((sec - offset) / (float) interval); int calc = offset + step * interval; //fprintf(stderr, "%d %d\n", sec, calc); return (epoch + (calc - sec)) * SECONDS; } ssize_t check_write(int fd, const void *buf, size_t count, const char *error_context) { ssize_t res = write(fd, buf, count); if (res < 0) perror(error_context); else if (res != (ssize_t) count) fprintf(stderr, "%s: Only %zd of %zd bytes written!\n", error_context, res, count); return res; } int my_epoll_create(int *event_fd_ptr) { int fd = epoll_create(32); // argument positive, ignored if (fd == -1) { perror("FATAL: epoll_create() failed:"); exit(1); } // if an invalid event_fd is passed, ignore it (apple) if (*event_fd_ptr >= 0) { // add exit signaling eventfd, we want that for all our epoll fds struct epoll_event epollEvent = { .events = EPOLLIN, .data = { .ptr = event_fd_ptr }}; if (epoll_ctl(fd, EPOLL_CTL_ADD, *event_fd_ptr, &epollEvent)) { perror("epoll_ctl fail:"); exit(1); } } return fd; } void epollAllocEvents(struct epoll_event **events, int *maxEvents) { if (!*events) { *maxEvents = 32; } else if (*maxEvents > 9000) { return; } else { *maxEvents *= 2; } sfree(*events); *events = cmalloc(*maxEvents * sizeof(struct epoll_event)); if (!*events) { fprintf(stderr, "Fatal: epollAllocEvents malloc\n"); exit(1); } } char *sprint_uuid1_partial(uint64_t id1, char *p) { for (int i = 7; i >= 0; i--) { //int j = 7 - i; //if (j == 4) //*p++ = '-'; uint64_t val = (id1 >> (4 * i)) & 15; if (val > 9) *p++ = val - 10 + 'a'; else *p++ = val + '0'; } *p = '\0'; return p; } char *sprint_uuid1(uint64_t id1, char *p) { for (int i = 15; i >= 0; i--) { int j = 15 - i; if (j == 8 || j == 12) *p++ = '-'; uint64_t val = (id1 >> (4 * i)) & 15; if (val > 9) *p++ = val - 10 + 'a'; else *p++ = val + '0'; } *p = '\0'; return p; } char *sprint_uuid2(uint64_t id2, char *p) { for (int i = 15; i >= 0; i--) { int j = 15 - i; if (j == 0 || j == 4) *p++ = '-'; uint64_t val = (id2 >> (4 * i)) & 15; if (val > 9) *p++ = val - 10 + 'a'; else *p++ = val + '0'; } *p = '\0'; return p; } char *sprint_uuid(uint64_t id1, uint64_t id2, char *p) { p = sprint_uuid1(id1, p); p = sprint_uuid2(id2, p); *p = '\0'; return p; } int mkdir_error(const char *path, mode_t mode, FILE *err_stream) { int err = mkdir(path, mode); if (err != 0 && errno != EEXIST && err_stream) { fprintf(err_stream, "mkdir %s: %s\n", path, strerror(errno)); } return err; } // Distance between points on a spherical earth. // This has up to 0.5% error because the earth isn't actually spherical // (but we don't use it in situations where that matters) // define for testing some approximations: #define DEGR (0.017453292519943295) // 1 degree in radian double greatcircle(double lat0, double lon0, double lat1, double lon1, int approx) { if (lat0 == lat1 && lon0 == lon1) { return 0; } // toRad converts degrees to radians lat0 = toRad(lat0); lon0 = toRad(lon0); lat1 = toRad(lat1); lon1 = toRad(lon1); double dlat = fabs(lat1 - lat0); double dlon = fabs(lon1 - lon0); double hav = 0; if (CHECK_APPROXIMATIONS) { double a = sin(dlat / 2) * sin(dlat / 2) + cos(lat0) * cos(lat1) * sin(dlon / 2) * sin(dlon / 2); hav = 6371e3 * 2 * atan2(sqrt(a), sqrt(1.0 - a)); } // after checking this isn't necessary with doubles // anyhow for small distance we can do a much cheaper approximation: // anyhow, nice formular let's leave it in the code for reference // for small distances the earth is flat enough that we can use this approximation // don't use this approximation near the poles, would probably behave poorly // // in our particular case many calls of this function are by speed_check which usually is small distances // thus having less trigonometric functions used should be a performance gain // // difference to haversine is less than 0.04 percent for up to 3 degrees of lat/lon difference // this isn't an issue for us and due to the oblateness and this calculation taking it into account, this calculation might actually be more accurate for small distances but i can't be bothered to check. // if (approx || (dlat < 3 * DEGR && dlon < 3 * DEGR && fabs(lat1) < 80 * DEGR)) { // calculate the equivalent length of the latitude and longitude difference // use pythagoras to get the distance // Equatorial radius: e = (6378.1370 km) -> circumference: 2 * pi * e = 40 075.016 km // Polar radius: p = (6356.7523 km) -> quarter meridian from wiki: 10 001.965 km // float ec = 40075016; // equatorial circumerence // float mc = 4 * 10001965; // meridial circumference // to have consistency to other calculations, use a circular earth /* float ec = 2 * M_PI * 6371e3; // equatorial circumference float mc = 2 * M_PI * 6371e3; // meridial circumference float dmer = (float) dlat / (2 * M_PI) * mc; float dequ = (float) dlon / (2 * M_PI) * ec * cosf(avglat); */ // eliminate 2 * M_PI float avglat = (float) lat0 + ((float) lat1 - (float) lat0) * 0.5f; float dmer = (float) dlat * 6371e3f; float dequ = (float) dlon * 6371e3f * cosf(avglat); float pyth = sqrtf(dmer * dmer + dequ * dequ); if (!approx && CHECK_APPROXIMATIONS) { double errorPercent = fabs(hav - pyth) / hav * 100; if (errorPercent > 0.03) { fprintf(stderr, "pos: %.1f, %.1f dlat: %.5f dlon %.5f hav: %.1f errorPercent: %.3f\n", toDeg(lat0), toDeg(lon0), toDeg(dlat), toDeg(dlon), hav, errorPercent); } } return pyth; } // spherical law of cosines // use float calculations if latitudes differ sufficiently if (dlat > 1 * DEGR && dlon > 1 * DEGR) { // error double slocf = 6371e3f * acosf(sinf(lat0) * sinf(lat1) + cosf(lat0) * cosf(lat1) * cosf(dlon)); if (CHECK_APPROXIMATIONS) { double errorPercent = fabs(hav - slocf) / hav * 100; if (errorPercent > 0.025) { fprintf(stderr, "pos: %.1f, %.1f dlat: %.5f dlon %.5f hav: %.1f errorPercent: %.3f\n", toDeg(lat0), toDeg(lon0), toDeg(dlat), toDeg(dlon), hav, errorPercent); } } return slocf; } double sloc = 6371e3 * acos(sin(lat0) * sin(lat1) + cos(lat0) * cos(lat1) * cos(dlon)); if (CHECK_APPROXIMATIONS) { double errorPercent = fabs(hav - sloc) / hav * 100; if (errorPercent > 0.025) { fprintf(stderr, "pos: %.1f, %.1f dlat: %.5f dlon %.5f sloc: %.1f errorPercent: %.3f\n", toDeg(lat0), toDeg(lon0), toDeg(dlat), toDeg(dlon), sloc, errorPercent); } } return sloc; } double bearing(double lat0, double lon0, double lat1, double lon1) { lat0 = toRad(lat0); lon0 = toRad(lon0); lat1 = toRad(lat1); lon1 = toRad(lon1); float y = sinf(lon1-lon0)*cosf(lat1); float x = cosf(lat0)*sinf(lat1) - sinf(lat0)*cosf(lat1)*cosf(lon1-lon0); float res = toDegf(atan2f(y, x)) + 360.0f; if (CHECK_APPROXIMATIONS) { // check against using double trigonometric functions // errors greater than 0.5 are rare and only happen for small distances // bearings derived from small distances don't need to be accurate at all for our purposes double y = sin(lon1-lon0)*cos(lat1); double x = cos(lat0)*sin(lat1) - sin(lat0)*cos(lat1)*cos(lon1-lon0); double res2 = (atan2(y, x) * (180 / M_PI) + 360); double diff = fabs(res2 - res); double dist = greatcircle(toDeg(lat0), toDeg(lon0), toDeg(lat1), toDeg(lon1), 1); if ((diff > 0.2 && dist > 150) || diff > 2) { fprintf(stderr, "errorDeg: %.2f %.2f %.2f dist: %.2f km\n", diff, res, res2, dist / 1000.0); } } while (res > 360) res -= 360; return res; } #undef DEGR // allocate a group of task_info task_group_t *allocate_task_group(uint32_t count) { task_group_t *group = cmalloc(sizeof(task_group_t)); group->task_count = count; group->infos = cmalloc(count * sizeof(readsb_task_t)); memset(group->infos, 0x0, count * sizeof(readsb_task_t)); /* for (uint32_t k = 0; k < count; k++) { readsb_task_t *info = &group->infos[k]; info->buffer_count = buffer_count; info->buffers = cmalloc(buffer_count * sizeof(buffer_t)); memset(info->buffers, 0x0, buffer_count * sizeof(buffer_t)); } */ group->tasks = cmalloc(count * sizeof(threadpool_task_t)); memset(group->tasks, 0x0, count * sizeof(threadpool_task_t)); return group; } // destroy a group of task_info void destroy_task_group(task_group_t *group) { /* for (uint32_t k = 0; k < group->task_count; k++) { readsb_task_t *info = &group->infos[k]; for (uint32_t j = 0; j < info->buffer_count; j++) { free(info->buffers[j].buf); } free(info->buffers); } */ free(group->infos); free(group->tasks); memset(group, 0x0, sizeof(task_group_t)); free(group); } void threadpool_distribute_and_run(threadpool_t *pool, task_group_t *task_group, threadpool_function_t func, int totalRange, int taskCount, int64_t now) { if (taskCount == 0 || taskCount > (int) task_group->task_count) { taskCount = task_group->task_count; } threadpool_task_t *tasks = task_group->tasks; readsb_task_t *infos = task_group->infos; int section_len = totalRange / taskCount; int extra = totalRange % taskCount; int p = 0; int actualTaskCount = 0; // assign tasks for (int i = 0; i < taskCount; i++) { threadpool_task_t *task = &tasks[i]; readsb_task_t *range = &infos[i]; range->now = now; range->from = p; p += section_len; if (extra) { p++; extra--; } range->to = p; if (range->from == range->to) { break; } task->function = func; task->argument = range; actualTaskCount++; //fprintf(stderr, "%d %d\n", range->from, range->to); } if (p != totalRange) { fprintf(stderr, "threadpool_distribute_and_run: range distribution error: p: %d totalRange: %d\n", p, totalRange); } threadpool_run(pool, tasks, actualTaskCount); } void gzipFile(char *filename) { int fd; char fileGz[PATH_MAX]; gzFile gzfp; // read uncompressed file into buffer fd = open(filename, O_RDONLY); if (fd < 0) { return; } struct char_buffer cb = readWholeFile(fd, filename); close(fd); if (!cb.buffer) { fprintf(stderr, "gzipFile readWholeFile failed: %s\n", filename); return; } snprintf(fileGz, PATH_MAX, "%s.gz", filename); gzfp = gzopen(fileGz, "wb"); if (!gzfp) { fprintf(stderr, "gzopen failed:"); perror(fileGz); return; } gzbuffer(gzfp, GZBUFFER_BIG); int res = gzsetparams(gzfp, 9, Z_DEFAULT_STRATEGY); if (res < 0) { fprintf(stderr, "gzsetparams fail: %d", res); } if (cb.len > 0) { writeGz(gzfp, cb.buffer, cb.len, fileGz); } sfree(cb.buffer); cb.len = 0; if (gzclose(gzfp) != Z_OK) { fprintf(stderr, "compressACAS gzclose failed: %s\n", fileGz); unlink(fileGz); return; } } void check_grow_buffer_t(buffer_t *buffer, ssize_t newSize) { if (buffer->bufSize < newSize) { sfree(buffer->buf); buffer->buf = cmalloc(newSize); } } void *check_grow_threadpool_buffer_t(threadpool_buffer_t *buffer, ssize_t newSize) { if (buffer->size < newSize || !buffer->buf) { //fprintf(stderr, "check_grow_threadpool_buffer: buffer->size %ld requested size %ld\n", (long) buffer->size, (long) newSize); sfree(buffer->buf); buffer->buf = cmalloc(newSize); if (!buffer->buf) { fprintf(stderr, "<3>FATAL: check_grow_threadpool_buffer_t no enough memory allocating %ld bytes!\n", (long) newSize); abort(); } buffer->size = newSize; } return buffer->buf; } struct char_buffer generateZstd(ZSTD_CCtx* cctx, threadpool_buffer_t *pbuffer, struct char_buffer src, int level) { struct char_buffer cb; size_t dstCapacity = (ZSTD_compressBound(src.len) / 1024 + 1) * 1024; check_grow_threadpool_buffer_t(pbuffer, dstCapacity); if (Modes.debug_zstd) { fprintf(stderr, "calling ZSTD_compressCCtx() with cctx %p dstCapacity %6zd" " src.buffer %p src.len %6ld level %d src.buffer[0] 0x%02x cctx_first_byte 0x%02x\n" , cctx, dstCapacity, src.buffer, (long) src.len, level, (uint8_t) src.buffer[0], (uint8_t) ((uint8_t *) cctx)[0] ); } /* * size_t ZSTD_compressCCtx(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize, int compressionLevel); */ size_t compressedSize = ZSTD_compressCCtx(cctx, pbuffer->buf, dstCapacity, src.buffer, src.len, level); if (Modes.debug_zstd) { fprintf(stderr, "calling ZSTD_isError() with compressedSize: %zd\n", compressedSize); } if (ZSTD_isError(compressedSize)) { fprintf(stderr, "generateZstd() zstd error: %s\n", ZSTD_getErrorName(compressedSize)); cb.buffer = NULL; cb.len = 0; return cb; } cb.len = compressedSize; cb.buffer = pbuffer->buf; return cb; } struct char_buffer ident(struct char_buffer target) { return target; } void setLowestPriorityPthread() { #ifndef __linux__ return; #endif //fprintf(stderr, "priority before: %d\n", (int) getpriority(PRIO_PROCESS, 0)); setpriority(PRIO_PROCESS, 0, 10 + getpriority(PRIO_PROCESS, 0)); //fprintf(stderr, "priority after: %d\n", (int) getpriority(PRIO_PROCESS, 0)); return; int policy; struct sched_param param = { 0 }; pthread_getschedparam(pthread_self(), &policy, ¶m); fprintf(stderr, "priority before: %d\n", (int) param.sched_priority); policy=SCHED_FIFO; int priority_max = sched_get_priority_max(policy); int priority_min = sched_get_priority_min(policy); fprintf(stderr, "min prio: %d max prio: %d\n", priority_min, priority_max); param.sched_priority = priority_min; pthread_setschedparam(pthread_self(), policy, ¶m); pthread_getschedparam(pthread_self(), &policy, ¶m); fprintf(stderr, "priority after: %d\n", (int) param.sched_priority); } void setPriorityPthread() { #ifndef __linux__ return; #endif setpriority(PRIO_PROCESS, 0, -5 + getpriority(PRIO_PROCESS, 0)); int policy = SCHED_FIFO; struct sched_param param = { 0 }; param.sched_priority = sched_get_priority_min(policy); pthread_setschedparam(pthread_self(), policy, ¶m); } zstd_fw_t *createZstdFw(size_t inBufSize) { zstd_fw_t *fw = cmalloc(sizeof(zstd_fw_t)); memset(fw, 0x0, sizeof(zstd_fw_t)); fw->in.src = cmalloc(inBufSize); fw->inAlloc = inBufSize; fw->in.size = 0; fw->in.pos = 0; int outBufSize = ZSTD_compressBound(inBufSize); fw->out.dst = cmalloc(outBufSize); fw->out.size = outBufSize; fw->out.pos = 0; //fw->cctx = ZSTD_createCCtx(); fw->cstream = ZSTD_createCStream(); fw->fd = -1; return fw; } void destroyZstdFw(zstd_fw_t *fw) { //ZSTD_freeCCtx(fw->cctx); ZSTD_freeCStream(fw->cstream); free((void *) fw->in.src); free((void *) fw->out.dst); free(fw); } static size_t zstdFwAvailable(zstd_fw_t *fw) { return fw->inAlloc - fw->in.size; } static void zstdFwWrite(zstd_fw_t *fw) { if (fw->fd < 0) { return; } check_write(fw->fd, fw->out.dst, fw->out.pos, fw->outFile); fw->out.pos = 0; } static void zstdFwCompress(zstd_fw_t *fw) { if (fw->in.size == 0) { return; } if (fw->fd < 0) { return; } size_t res; // fw->in buffer is full, let's compress it //res = ZSTD_compressStream2(fw->cctx, &fw->out, &fw->in, ZSTD_e_flush); res = ZSTD_compressStream(fw->cstream, &fw->out, &fw->in); if (ZSTD_isError(res)) { fprintf(stderr, "ZSTD_compressStream failed: %ld %s\n", (long) res, ZSTD_getErrorName(res)); } /* res = ZSTD_flushStream(fw->cstream, &fw->out); if (ZSTD_isError(res)) { fprintf(stderr, "ZSTD_flushStream failed: %s\n", ZSTD_getErrorName(res)); } */ if (fw->in.size != fw->in.pos) { fprintf(stderr, "<3>BAD: ohB6ooVi %ld %ld %ld\n", (long) fw->in.size, (long) fw->in.pos, (long) res); } fw->in.size = 0; fw->in.pos = 0; zstdFwWrite(fw); } void zstdFwStartFile(zstd_fw_t *fw, const char *outFile, int compressionLvl) { fw->in.pos = 0; fw->in.size = 0; fw->out.pos = 0; size_t res; //ZSTD_CCtx_reset(fw->cctx, ZSTD_reset_session_and_parameters); //ZSTD_CCtx_setParameter(fw->cctx, ZSTD_c_compressionLevel, compressionLvl); res = ZSTD_initCStream(fw->cstream, compressionLvl); if (ZSTD_isError(res)) { fprintf(stderr, "ZSTD_initCStream failed: %s\n", ZSTD_getErrorName(res)); } fw->outFile = outFile; if (!fw->outFile) { fprintf(stderr, "zstdFwStartFile(): outFile null!\n"); } fw->fd = open(fw->outFile, O_WRONLY | O_CREAT | O_APPEND, 0644); if (fw->fd < 0) { fprintf(stderr, "zstdFwStartFile(): open failed: %s\n", strerror(errno)); } } void zstdFwFinishFile(zstd_fw_t *fw) { zstdFwCompress(fw); //size_t res = ZSTD_compressStream2(fw->cctx, &fw->out, &fw->in, ZSTD_e_end); size_t res = ZSTD_endStream(fw->cstream, &fw->out); if (res != 0) { fprintf(stderr, "ZSTD_endStream failed: %ld %s\n", (long) res, ZSTD_getErrorName(res)); } zstdFwWrite(fw); close(fw->fd); } void zstdFwPutData(zstd_fw_t *fw, const uint8_t *data, size_t len) { if (fw->fd < 0) { return; } size_t remaining = len; const uint8_t *p = data; while (remaining > 0) { if (zstdFwAvailable(fw) == 0) { zstdFwCompress(fw); } size_t bytes = imin(zstdFwAvailable(fw), remaining); memcpy((char *)(fw->in.src + fw->in.size), p, bytes); fw->in.size += bytes; remaining -= bytes; p += bytes; } } void dump_beast_check(int64_t now) { if (!Modes.dump_fw) { return; } int32_t index = now / (Modes.dump_interval * SECONDS); if (Modes.dump_beast_index == index) { return; } // finish old file zstd_fw_t *fw = Modes.dump_fw; if (fw->fd >= 0) { zstdFwFinishFile(fw); } int startup = (Modes.dump_beast_index < 0); Modes.dump_beast_index = index; time_t nowish = index * Modes.dump_interval; struct tm utc; gmtime_r(&nowish, &utc); char tstring[100]; strftime (tstring, 100, "%H%M%S", &utc); char pathbuf[PATH_MAX]; snprintf(pathbuf, PATH_MAX, "%s/%sZ.zst", Modes.dump_beast_dir, tstring); // unless we just restarted, delete the file if (!startup) { unlink(pathbuf); } // start new file zstdFwStartFile(fw, pathbuf, Modes.dump_compressionLevel); //fprintf(stderr, "dump_beast started file: %s\n", pathbuf); } // get the first tokens from a string separated by any bytes in and place them in the provided char pointer array tokens // the array of token pointers is set to NULL before populating it // stringp / delim work just like strsep(3), this is just a wrapper to easily extract multiple tokens from a string // the pointer pointed at by stringp WILL NOT be modified // the string pointed at by stringp WILL be modified int32_t tokenize(char **restrict stringp, char *restrict delim, char **restrict tokens, int maxTokens) { memset(tokens, 0x0, sizeof(char *) * maxTokens); int32_t k = 0; char *p = *stringp; while (k < maxTokens) { tokens[k] = strsep(&p, delim); if (!tokens[k]) { break; } k++; } return k; } void spinLock(volatile atomic_int *lock) { atomic_int expected; int calls = 0; do { expected = 0; calls++; } while (!atomic_compare_exchange_weak(lock, &expected, 1)); if (0 && calls > 1000) { fprintf(stderr, "cas_weak calls %5d %8ld\n", calls, (long) pthread_self()); } } void spinRelease(volatile atomic_int *lock) { atomic_store(lock, 0); } readsb-3.16/util.h000066400000000000000000000213771505057307600140530ustar00rootroot00000000000000// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // track.h: aircraft state tracking prototypes // // Copyright (c) 2019 Michael Wolf // // This code is based on a detached fork of dump1090-fa. // // Copyright (c) 2015 Oliver Jowett // // This file 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 // any later version. // // This file 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 . #ifndef DUMP1090_UTIL_H #define DUMP1090_UTIL_H #define CHECK_APPROXIMATIONS (0) #define GZBUFFER_BIG (512 * 1024) #include #include #define sfree(x) do { free(x); x = NULL; } while (0) #define HOURS (60*60*1000LL) #define MINUTES (60*1000LL) #define SECONDS (1000LL) #define MS (1LL) #define memberSize(type, member) (sizeof( ((type *)0)->member )) #define litLen(literal) (sizeof(literal) - 1) // return true for byte match between string and string literal. string IS allowed to be longer than literal #define byteMatchStart(s1, literal) (memcmp(s1, literal, litLen(literal)) == 0) // return true for byte match between string and string literal. string IS NOT allowed to be longer than literal #define byteMatchStrict(s1, literal) (memcmp(s1, literal, sizeof(literal)) == 0) int tryJoinThread(pthread_t *thread, int64_t timeout); typedef struct { pthread_t pthread; pthread_mutex_t mutex; pthread_cond_t cond; char *name; int8_t joined; int8_t joinFailed; } threadT; void threadDestroyAll(); void threadInit(threadT *thread, char *name); void threadCreate(threadT *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); void threadTimedWait(threadT *thread, struct timespec *ts, int64_t increment); void threadSignalJoin(threadT *thread); int threadAffinity(int core_id); struct char_buffer { char *buffer; size_t len; size_t alloc; }; struct char_buffer readWholeFile(int fd, char *errorContext); struct char_buffer readWholeGz(gzFile gzfp, char *errorContext); int writeGz(gzFile gzfp, void *source, int toWrite, char *errorContext); static inline void msleep(int64_t ms) { struct timespec slp = {ms / 1000, (ms % 1000) * 1000 * 1000}; nanosleep(&slp, NULL); } /* Returns system time in milliseconds */ int64_t mstime (void); // microseconds int64_t microtime(void); void milli_micro_seconds(int64_t *milli, int64_t *micro); int64_t mono_micro_seconds(); int64_t mono_milli_seconds(); int64_t getUptime(); int snprintHMS(char *buf, size_t bufsize, int64_t now); int64_t msThreadTime(void); int64_t nsThreadTime(void); /* Returns the time elapsed, in nanoseconds, from t1 to t2, * where t1 and t2 are 12MHz counters. */ int64_t receiveclock_ns_elapsed (int64_t t1, int64_t t2); /* Same, in milliseconds */ int64_t receiveclock_ms_elapsed (int64_t t1, int64_t t2); /* Normalize the value in ts so that ts->nsec lies in * [0,999999999] */ static inline void normalize_timespec(struct timespec *ts) { if (ts->tv_nsec >= 1000000000) { ts->tv_sec += ts->tv_nsec / 1000000000; ts->tv_nsec = ts->tv_nsec % 1000000000; } else if (ts->tv_nsec < 0) { long adjust = ts->tv_nsec / 1000000000 + 1; ts->tv_sec -= adjust; ts->tv_nsec = (ts->tv_nsec + 1000000000 * adjust) % 1000000000; } } // convert ms to timespec static inline struct timespec msToTimespec(int64_t ms) { struct timespec ts; ts.tv_sec = (ms / 1000); ts.tv_nsec = (ms % 1000) * 1000 * 1000; return ts; } /* record current CPU time in start_time */ void start_cpu_timing (struct timespec *start_time); /* add difference between start_time and the current CPU time to add_to */ void end_cpu_timing (const struct timespec *start_time, struct timespec *add_to); // given a start and end time, add the difference to the third timespec void timespec_add_elapsed(const struct timespec *start_time, const struct timespec *end_time, struct timespec *add_to); void start_monotonic_timing(struct timespec *start_time); void end_monotonic_timing (const struct timespec *start_time, struct timespec *add_to); // start watch for stopWatch void startWatch(struct timespec *start_time); // return elapsed time int64_t stopWatch(struct timespec *start_time); // return elapsed time and set start_time to current time int64_t lapWatch(struct timespec *start_time); // get nanoseconds and some other stuff for use with srand unsigned int get_seed(); void log_with_timestamp(const char *format, ...) __attribute__ ((format(printf, 1, 2))); // based on a give epoch time in ms, calculate the nearest offset interval step // offset must be smaller than interval, at offset seconds after the full minute // is the first possible value, all additional return values differ by a multiple // of interval int64_t roundSeconds(int interval, int offset, int64_t epoch_ms); ssize_t check_write(int fd, const void *buf, size_t count, const char *error_context); int my_epoll_create(int *event_fd_ptr); void epollAllocEvents(struct epoll_event **events, int *maxEvents); char *sprint_uuid(uint64_t id1, uint64_t id2, char *p); char *sprint_uuid1_partial(uint64_t id1, char *p); char *sprint_uuid1(uint64_t id1, char *p); char *sprint_uuid2(uint64_t id2, char *p); int mkdir_error(const char *path, mode_t mode, FILE *err_stream); static inline double toRad(double degrees) { return degrees * (M_PI / 180.0); } static inline double toDeg(double radians) { return radians * (180.0 / M_PI); } static inline float toRadf(float degrees) { return degrees * (float) (M_PI / 180.0f); } static inline float toDegf(float radians) { return radians * (float) (180.0f / M_PI); } double greatcircle(double lat0, double lon0, double lat1, double lon1, int approx); double bearing(double lat0, double lon0, double lat1, double lon1); static inline int64_t imin(int64_t a, int64_t b) { if (a < b) return a; else return b; } static inline int64_t imax(int64_t a, int64_t b) { if (a > b) return a; else return b; } static inline double norm_diff (double a, double pi) { if (a < -pi) a += 2 * pi; if (a > pi) a -= 2 * pi; return a; } static inline double norm_angle (double a, double pi) { if (a < 0) a += 2 * pi; if (a >= 2 * pi) a -= 2 * pi; return a; } static inline void fprintTimePrecise(FILE *stream, int64_t now) { fprintf(stream, "%02d:%02d:%06.3f", (int) ((now / (3600 * SECONDS)) % 24), (int) ((now / (60 * SECONDS)) % 60), (now % (60 * SECONDS)) / 1000.0); } static inline void fprintTime(FILE *stream, int64_t now) { fprintf(stream, "%02d:%02d:%04.1f", (int) ((now / (3600 * SECONDS)) % 24), (int) ((now / (60 * SECONDS)) % 60), (now % (60 * SECONDS)) / 1000.0); } typedef struct { void *buf; ssize_t bufSize; } buffer_t; typedef struct { int64_t now; int32_t from; int32_t to; } readsb_task_t; typedef struct { uint32_t task_count; readsb_task_t *infos; threadpool_task_t *tasks; } task_group_t; // allocate a group of tasks task_group_t *allocate_task_group(uint32_t count); // destroy a group of tasks void destroy_task_group(task_group_t *group); void threadpool_distribute_and_run(threadpool_t *pool, task_group_t *task_group, threadpool_function_t func, int totalRange, int taskCount, int64_t now); void check_grow_buffer_t(buffer_t *buffer, ssize_t newSize); void *check_grow_threadpool_buffer_t(threadpool_buffer_t *buffer, ssize_t newSize); void gzipFile(char *file); struct char_buffer generateZstd(ZSTD_CCtx* cctx, threadpool_buffer_t *pbuffer, struct char_buffer src, int level); struct char_buffer ident(struct char_buffer target); void setLowestPriorityPthread(); void setPriorityPthread(); typedef struct { //ZSTD_CCtx *cctx; ZSTD_CStream *cstream; ZSTD_inBuffer in; size_t inAlloc; ZSTD_outBuffer out; const char *outFile; int fd; } zstd_fw_t; zstd_fw_t *createZstdFw(size_t inBufSize); void destroyZstdFw(zstd_fw_t *fw); void zstdFwStartFile(zstd_fw_t *fw, const char *outFile, int compressionLvl); void zstdFwFinishFile(zstd_fw_t *fw); void zstdFwPutData(zstd_fw_t *fw, const uint8_t *data, size_t len); void dump_beast_check(int64_t now); int32_t tokenize(char **restrict stringp, char *restrict delim, char **restrict tokens, int maxTokens); void spinLock(volatile atomic_int *lock); void spinRelease(volatile atomic_int *lock); #endif readsb-3.16/valgrind.sh000077500000000000000000000007451505057307600150660ustar00rootroot00000000000000#!/bin/bash systemctl stop test rm -rf /run/test mkdir -p /run/test chown readsb /run/test source /etc/default/test cp -f readsb /tmp/test123 FIRST="--error-exitcode=3 --exit-on-first-error=yes" FIRST="-s" MEM="--track-origins=yes" MEM="--show-leak-kinds=all --leak-check=full" MEM="--show-leak-kinds=all --track-origins=yes --leak-check=full" MEM="" valgrind $MASSIF $FIRST $MEM /tmp/test123 $RECEIVER_OPTIONS $DECODER_OPTIONS $NET_OPTIONS $JSON_OPTIONS --quiet --db-file=none $@ readsb-3.16/version000066400000000000000000000000051505057307600143160ustar00rootroot000000000000003.16