diff --git a/LICENSE b/LICENSE
index d6a9326..437605d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,340 +1,24 @@
-GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) 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
-this service 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 make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. 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.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-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
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the 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 a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE 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.
-
- 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
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- {description}
- Copyright (C) {year} {fullname}
-
- 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.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision 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, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- {signature of Ty Coon}, 1 April 1989
- Ty Coon, President of Vice
-
-This 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.
-
+Copyright 2015, Oliver Jowett
+Copyright (c) 2019, FlightAware LLC.
+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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..00be569
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+CC=gcc
+CFLAGS+=-Wall -Werror -O2 -g -Ilibs
+
+CXX=g++
+CXXFLAGS+=-std=c++11 -Wall -Werror -O2 -g -Ilibs
+
+LIBS=-lboost_system -lboost_program_options -lboost_regex -lSoapySDR -lpthread
+
+all: dump978 faup978
+
+dump978: dump978_main.o socket_output.o message_dispatch.o fec.o libs/fec/init_rs_char.o libs/fec/decode_rs_char.o sample_source.o soapy_source.o convert.o demodulator.o uat_message.o
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS)
+
+faup978: faup978_main.o socket_input.o uat_message.o track.o
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS)
+
+clean:
+ rm -f *.o fec/*.o dump978 faup978
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..f12d8b8
--- /dev/null
+++ b/common.h
@@ -0,0 +1,24 @@
+// -*- c++ -*-
+
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#ifndef UAT_COMMON_H
+#define UAT_COMMON_H
+
+#include
+#include
+#include
+
+namespace uat {
+ typedef std::vector Bytes;
+ typedef std::vector PhaseBuffer;
+
+ inline static double RoundN(double value, unsigned dp) {
+ const double scale = std::pow(10, dp);
+ return std::round(value * scale) / scale;
+ }
+};
+
+#endif
diff --git a/convert.cc b/convert.cc
new file mode 100644
index 0000000..aa2118d
--- /dev/null
+++ b/convert.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#include "convert.h"
+
+#include
+#include
+
+// TODO: This probably needs PhaseBuffer to use a POD-vector-like thing (not
+// std::vector) to run at an acceptable speed. Profile it and check.
+
+namespace dump978 {
+ static inline std::uint16_t scaled_atan2(double y, double x) {
+ double ang = std::atan2(y, x) + M_PI; // atan2 returns [-pi..pi], normalize to [0..2*pi]
+ double scaled_ang = std::round(32768 * ang / M_PI);
+ return scaled_ang < 0 ? 0 : scaled_ang > 65535 ? 65535 : (std::uint16_t)scaled_ang;
+ }
+
+ SampleConverter::Pointer SampleConverter::Create(SampleFormat format) {
+ switch (format) {
+ case SampleFormat::CU8:
+ return Pointer(new CU8Converter());
+ case SampleFormat::CS8:
+ return Pointer(new CS8Converter());
+ case SampleFormat::CS16H:
+ return Pointer(new CS16HConverter());
+ case SampleFormat::CF32H:
+ return Pointer(new CF32HConverter());
+ default:
+ throw std::runtime_error("format not implemented yet");
+ }
+ }
+
+ CU8Converter::CU8Converter() {
+ cu8_alias u;
+
+ unsigned i,q;
+ for (i = 0; i < 256; ++i) {
+ double d_i = (i - 127.5);
+ for (q = 0; q < 256; ++q) {
+ double d_q = (q - 127.5);
+ u.iq[0] = i;
+ u.iq[1] = q;
+ lookup_[u.iq16] = scaled_atan2(d_q, d_i);
+ }
+ }
+ }
+
+ void CU8Converter::Convert(const uat::Bytes &in, uat::PhaseBuffer &out) {
+ const cu8_alias *in_iq = reinterpret_cast(in.data());
+
+ // unroll the loop
+ const auto n = in.size() / 2;
+ const auto n8 = n / 8;
+ const auto n7 = n & 7;
+
+ out.reserve(out.size() + n);
+ for (unsigned i = 0; i < n8; ++i, in_iq += 8) {
+ out.push_back(lookup_[in_iq[0].iq16]);
+ out.push_back(lookup_[in_iq[1].iq16]);
+ out.push_back(lookup_[in_iq[2].iq16]);
+ out.push_back(lookup_[in_iq[3].iq16]);
+ out.push_back(lookup_[in_iq[4].iq16]);
+ out.push_back(lookup_[in_iq[5].iq16]);
+ out.push_back(lookup_[in_iq[6].iq16]);
+ out.push_back(lookup_[in_iq[7].iq16]);
+ }
+ for (unsigned i = 0; i < n7; ++i, ++in_iq) {
+ out.push_back(lookup_[in_iq[0].iq16]);
+ }
+ }
+
+ CS8Converter::CS8Converter() {
+ cs8_alias u;
+
+ int i,q;
+ for (i = -128; i <= 127; ++i) {
+ for (q = -128; q <= 127; ++q) {
+ u.iq[0] = i;
+ u.iq[1] = q;
+ lookup_[u.iq16] = scaled_atan2(q, i);
+ }
+ }
+ }
+
+ void CS8Converter::Convert(const uat::Bytes &in, uat::PhaseBuffer &out) {
+ const cs8_alias *in_iq = reinterpret_cast(in.data());
+
+ // unroll the loop
+ const auto n = in.size() / 2;
+ const auto n8 = n / 8;
+ const auto n7 = n & 7;
+
+ out.reserve(out.size() + n);
+ for (unsigned i = 0; i < n8; ++i, in_iq += 8) {
+ out.push_back(lookup_[in_iq[0].iq16]);
+ out.push_back(lookup_[in_iq[1].iq16]);
+ out.push_back(lookup_[in_iq[2].iq16]);
+ out.push_back(lookup_[in_iq[3].iq16]);
+ out.push_back(lookup_[in_iq[4].iq16]);
+ out.push_back(lookup_[in_iq[5].iq16]);
+ out.push_back(lookup_[in_iq[6].iq16]);
+ out.push_back(lookup_[in_iq[7].iq16]);
+ }
+ for (unsigned i = 0; i < n7; ++i, ++in_iq) {
+ out.push_back(lookup_[in_iq[0].iq16]);
+ }
+ }
+
+ void CS16HConverter::Convert(const uat::Bytes &in, uat::PhaseBuffer &out) {
+ const std::int16_t *in_iq = reinterpret_cast(in.data());
+
+ // unroll the loop
+ const auto n = in.size() / 4;
+ const auto n8 = n / 8;
+ const auto n7 = n & 7;
+
+ out.reserve(out.size() + n);
+ for (unsigned i = 0; i < n8; ++i, in_iq += 8) {
+ out.push_back(scaled_atan2(in_iq[1], in_iq[0]));
+ out.push_back(scaled_atan2(in_iq[3], in_iq[2]));
+ out.push_back(scaled_atan2(in_iq[5], in_iq[4]));
+ out.push_back(scaled_atan2(in_iq[7], in_iq[6]));
+ out.push_back(scaled_atan2(in_iq[9], in_iq[8]));
+ out.push_back(scaled_atan2(in_iq[11], in_iq[10]));
+ out.push_back(scaled_atan2(in_iq[13], in_iq[12]));
+ out.push_back(scaled_atan2(in_iq[15], in_iq[14]));
+ }
+ for (unsigned i = 0; i < n7; ++i, ++in_iq) {
+ out.push_back(scaled_atan2(in_iq[1], in_iq[0]));
+ }
+ }
+
+ void CF32HConverter::Convert(const uat::Bytes &in, uat::PhaseBuffer &out) {
+ const double *in_iq = reinterpret_cast(in.data());
+
+ // unroll the loop
+ const auto n = in.size() / 8;
+ const auto n8 = n / 8;
+ const auto n7 = n & 7;
+
+ out.reserve(out.size() + n);
+ for (unsigned i = 0; i < n8; ++i, in_iq += 8) {
+ out.push_back(scaled_atan2(in_iq[1], in_iq[0]));
+ out.push_back(scaled_atan2(in_iq[3], in_iq[2]));
+ out.push_back(scaled_atan2(in_iq[5], in_iq[4]));
+ out.push_back(scaled_atan2(in_iq[7], in_iq[6]));
+ out.push_back(scaled_atan2(in_iq[9], in_iq[8]));
+ out.push_back(scaled_atan2(in_iq[11], in_iq[10]));
+ out.push_back(scaled_atan2(in_iq[13], in_iq[12]));
+ out.push_back(scaled_atan2(in_iq[15], in_iq[14]));
+ }
+ for (unsigned i = 0; i < n7; ++i, ++in_iq) {
+ out.push_back(scaled_atan2(in_iq[15], in_iq[14]));
+ }
+ }
+}
diff --git a/convert.h b/convert.h
new file mode 100644
index 0000000..5714563
--- /dev/null
+++ b/convert.h
@@ -0,0 +1,91 @@
+// -*- c++ -*-
+
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#ifndef DUMP978_CONVERT_H
+#define DUMP978_CONVERT_H
+
+#include
+
+#include "common.h"
+
+namespace dump978 {
+ // Describes a sample data layout:
+ // CU8 - interleaved I/Q data, 8 bit unsigned integers
+ // CS8 - interleaved I/Q data, 8 bit signed integers
+ // CS16H - interleaved I/Q data, 16 bit signed integers, host byte order
+ // CF32H - interleaved I/Q data, 32 bit signed floats, host byte order
+ enum class SampleFormat { CU8, CS8, CS16H, CF32H, UNKNOWN };
+
+ // Return the number of bytes for 1 sample in the given format
+ inline static unsigned BytesPerSample(SampleFormat f) {
+ switch (f) {
+ case SampleFormat::CU8: return 2;
+ case SampleFormat::CS8: return 2;
+ case SampleFormat::CS16H: return 4;
+ case SampleFormat::CF32H: return 8;
+ default: return 0;
+ }
+ }
+
+ // Base class for all sample converters.
+ // Use SampleConverter::Create to build converters.
+ class SampleConverter {
+ public:
+ typedef std::shared_ptr Pointer;
+
+ virtual ~SampleConverter() {};
+
+ // Read samples from `in` and append one phase value per sample to `out`.
+ // The input buffer should contain an integral number of samples (trailing
+ // partial samples are ignored, not buffered)
+ virtual void Convert(const uat::Bytes &in, uat::PhaseBuffer &out) = 0;
+
+ // Return a new SampleConverter that converts from the given format
+ static Pointer Create(SampleFormat format);
+ };
+
+ class CU8Converter : public SampleConverter {
+ public:
+ CU8Converter();
+
+ void Convert(const uat::Bytes &in, uat::PhaseBuffer &out) override;
+
+ private:
+ union cu8_alias {
+ std::uint8_t iq[2];
+ std::uint16_t iq16;
+ };
+
+ std::array lookup_;
+ };
+
+ class CS8Converter : public SampleConverter {
+ public:
+ CS8Converter();
+
+ void Convert(const uat::Bytes &in, uat::PhaseBuffer &out) override;
+
+ private:
+ union cs8_alias {
+ std::int8_t iq[2];
+ std::uint16_t iq16;
+ };
+
+ std::array lookup_;
+ };
+
+ class CS16HConverter : public SampleConverter {
+ public:
+ void Convert(const uat::Bytes &in, uat::PhaseBuffer &out) override;
+ };
+
+ class CF32HConverter : public SampleConverter {
+ public:
+ void Convert(const uat::Bytes &in, uat::PhaseBuffer &out) override;
+ };
+};
+
+#endif
diff --git a/demodulator.cc b/demodulator.cc
new file mode 100644
index 0000000..57abe32
--- /dev/null
+++ b/demodulator.cc
@@ -0,0 +1,344 @@
+// Copyright 2015, Oliver Jowett
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#include "demodulator.h"
+
+#include
+#include
+#include
+
+using namespace uat;
+
+namespace dump978 {
+ SingleThreadReceiver::SingleThreadReceiver(SampleFormat format)
+ : converter_(SampleConverter::Create(format)),
+ demodulator_(new TwoMegDemodulator())
+ {}
+
+ // Handle samples in 'buffer' by:
+ // converting them to a phase buffer
+ // demodulating the phase buffer
+ // dispatching any demodulated messages
+ // preserving the end of the phase buffer for reuse in the next call
+ void SingleThreadReceiver::HandleSamples(std::uint64_t timestamp, const Bytes &buffer) {
+ assert(converter_);
+ converter_->Convert(buffer, phase_); // appends to phase_
+
+ auto messages = demodulator_->Demodulate(timestamp, phase_); // FIXME: not correct because of the preamble
+ if (messages && !messages->empty()) {
+ DispatchMessages(messages);
+ }
+
+ // preserve the tail of the phase buffer for next time
+ const auto tail_size = demodulator_->NumTrailingSamples();
+ if (phase_.size() > tail_size) {
+ std::copy(phase_.end() - tail_size, phase_.end(), phase_.begin());
+ phase_.resize(tail_size);
+ }
+ }
+
+ static inline std::int16_t PhaseDifference(std::uint16_t from, std::uint16_t to) {
+ int32_t difference = to - from; // lies in the range -65535 .. +65535
+ if (difference >= 32768) // +32768..+65535
+ return difference - 65536; // -> -32768..-1: always in range
+ else if (difference < -32768) // -65535..-32769
+ return difference + 65536; // -> +1..32767: always in range
+ else
+ return difference;
+ }
+
+ static inline bool SyncWordMatch(std::uint64_t word, std::uint64_t expected) {
+ std::uint64_t diff;
+
+ if (word == expected)
+ return 1;
+
+ diff = word ^ expected; // guaranteed nonzero
+
+ // This is a bit-twiddling popcount
+ // hack, tweaked as we only care about
+ // "=N" set bits for fixed N -
+ // so we can bail out early after seeing N
+ // set bits.
+ //
+ // It relies on starting with a nonzero value
+ // with zero or more trailing clear bits
+ // after the last set bit:
+ //
+ // 010101010101010000
+ // ^
+ // Subtracting one, will flip the
+ // bits starting at the last set bit:
+ //
+ // 010101010101001111
+ // ^
+ // then we can use that as a bitwise-and
+ // mask to clear the lowest set bit:
+ //
+ // 010101010101000000
+ // ^
+ // And repeat until the value is zero
+ // or we have seen too many set bits.
+
+ // >= 1 bit
+ diff &= (diff-1); // clear lowest set bit
+ if (!diff)
+ return 1; // 1 bit error
+
+ // >= 2 bits
+ diff &= (diff-1); // clear lowest set bit
+ if (!diff)
+ return 1; // 2 bits error
+
+ // >= 3 bits
+ diff &= (diff-1); // clear lowest set bit
+ if (!diff)
+ return 1; // 3 bits error
+
+ // >= 4 bits
+ diff &= (diff-1); // clear lowest set bit
+ if (!diff)
+ return 1; // 4 bits error
+
+ // > 4 bits in error, give up
+ return 0;
+ }
+
+ // check that there is a valid sync word starting at 'start'
+ // that matches the sync word 'pattern'. Return a pair:
+ // first element is true if the sync word looks OK; second
+ // element has the dphi threshold to use for bit slicing
+ static inline std::pair CheckSyncWord(const PhaseBuffer &buffer, unsigned start, std::uint64_t pattern) {
+ const unsigned MAX_SYNC_ERRORS = 4;
+
+ std::int32_t dphi_zero_total = 0;
+ int zero_bits = 0;
+ std::int32_t dphi_one_total = 0;
+ int one_bits = 0;
+
+ // find mean dphi for zero and one bits;
+ // take the mean of the two as our central value
+
+ for (unsigned i = 0; i < SYNC_BITS; ++i) {
+ auto dphi = PhaseDifference(buffer[start + i * 2], buffer[start + i * 2 + 1]);
+ if (pattern & (1UL << (35-i))) {
+ ++one_bits;
+ dphi_one_total += dphi;
+ } else {
+ ++zero_bits;
+ dphi_zero_total += dphi;
+ }
+ }
+
+ dphi_zero_total /= zero_bits;
+ dphi_one_total /= one_bits;
+
+ std::int16_t center = (dphi_one_total + dphi_zero_total) / 2;
+
+ // recheck sync word using our center value
+ unsigned error_bits = 0;
+ for (unsigned i = 0; i < SYNC_BITS; ++i) {
+ auto dphi = PhaseDifference(buffer[start + i * 2], buffer[start + i * 2 + 1]);
+
+ if (pattern & (1UL << (35-i))) {
+ if (dphi < center)
+ ++error_bits;
+ } else {
+ if (dphi > center)
+ ++error_bits;
+ }
+ }
+
+ return std::make_pair<>((error_bits <= MAX_SYNC_ERRORS), center);
+ }
+
+ // demodulate 'bytes' bytes from samples at 'start' using 'center' as the bit slicing threshold
+ static inline Bytes DemodBits(const PhaseBuffer &buffer, unsigned start, unsigned bytes, std::int16_t center)
+ {
+ Bytes result;
+ result.reserve(bytes);
+
+ if (start + bytes * 8 * 2 >= buffer.size()) {
+ throw std::runtime_error("would overrun source buffer");
+ }
+
+ auto *phase = buffer.data() + start;
+ for (unsigned i = 0; i < bytes; ++i) {
+ std::uint8_t b = 0;
+ if (PhaseDifference(phase[0], phase[1]) > center) b |= 0x80;
+ if (PhaseDifference(phase[2], phase[3]) > center) b |= 0x40;
+ if (PhaseDifference(phase[4], phase[5]) > center) b |= 0x20;
+ if (PhaseDifference(phase[6], phase[7]) > center) b |= 0x10;
+ if (PhaseDifference(phase[8], phase[9]) > center) b |= 0x08;
+ if (PhaseDifference(phase[10], phase[11]) > center) b |= 0x04;
+ if (PhaseDifference(phase[12], phase[13]) > center) b |= 0x02;
+ if (PhaseDifference(phase[14], phase[15]) > center) b |= 0x01;
+ result.push_back(b);
+ phase += 16;
+ }
+
+ return result;
+ }
+
+ unsigned TwoMegDemodulator::NumTrailingSamples() {
+ return (SYNC_BITS + UPLINK_BITS) * 2;
+ }
+
+ // Try to demodulate messages from `buffer` and return a list of messages.
+ // Messages that start near the end of `buffer` may not be demodulated
+ // (less than (SYNC_BITS + UPLINK_BITS)*2 before the end of the buffer)
+ SharedMessageVector TwoMegDemodulator::Demodulate(std::uint64_t timestamp, const PhaseBuffer &buffer) {
+ // We expect samples at twice the UAT bitrate.
+ // We look at phase difference between pairs of adjacent samples, i.e.
+ // sample 1 - sample 0 -> sync0
+ // sample 2 - sample 1 -> sync1
+ // sample 3 - sample 2 -> sync0
+ // sample 4 - sample 3 -> sync1
+ // ...
+ //
+ // We accumulate bits into two buffers, sync0 and sync1.
+ // Then we compare those buffers to the expected 36-bit sync word that
+ // should be at the start of each UAT frame. When (if) we find it,
+ // that tells us which sample to start decoding from.
+
+ // Stop when we run out of remaining samples for a max-sized frame.
+ // Arrange for our caller to pass the trailing data back to us next time;
+ // ensure we don't consume any partial sync word we might be part-way
+ // through. This means we don't need to maintain state between calls.
+
+ SharedMessageVector messages = std::make_shared();
+
+ const auto trailing_samples = (SYNC_BITS + UPLINK_BITS) * 2 - 2;
+ if (buffer.size() <= trailing_samples) {
+ return messages;
+ }
+ const auto limit = buffer.size() - trailing_samples;
+
+ unsigned sync_bits = 0;
+ std::uint64_t sync0 = 0, sync1 = 0;
+ const std::uint64_t SYNC_MASK = ((((std::uint64_t)1)< 0 ? 1 : 0)) & SYNC_MASK;
+ sync1 = ((sync1 << 1) | (d1 > 0 ? 1 : 0)) & SYNC_MASK;
+
+ if (++sync_bits < SYNC_BITS)
+ continue; // haven't fully populated sync0/1 yet
+
+ // see if we have (the start of) a valid sync word
+ // when we find a match, try to demodulate both with that match
+ // and with the next position, and pick the one with fewer
+ // errors.
+ if (SyncWordMatch(sync0, DOWNLINK_SYNC_WORD)) {
+ auto start = i - SYNC_BITS * 2 + 2;
+ auto start_timestamp = timestamp + start * 1000 / 2083333;
+ auto message = DemodBest(buffer, start, true /* downlink */, start_timestamp);
+ if (message) {
+ i = start + message.BitLength() * 2;
+ sync_bits = 0;
+ messages->emplace_back(std::move(message));
+ continue;
+ }
+ }
+
+ if (SyncWordMatch(sync1, DOWNLINK_SYNC_WORD)) {
+ auto start = i - SYNC_BITS * 2 + 3;
+ auto start_timestamp = timestamp + start * 1000 / 2083333;
+ auto message = DemodBest(buffer, start, true /* downlink */, start_timestamp);
+ if (message) {
+ i = start + message.BitLength() * 2;
+ sync_bits = 0;
+ messages->emplace_back(std::move(message));
+ continue;
+ }
+ }
+
+ if (SyncWordMatch(sync0, UPLINK_SYNC_WORD)) {
+ auto start = i - SYNC_BITS * 2 + 2;
+ auto start_timestamp = timestamp + start * 1000 / 2083333;
+ auto message = DemodBest(buffer, start, false /* !downlink */, start_timestamp);
+ if (message) {
+ i = start + message.BitLength() * 2;
+ sync_bits = 0;
+ messages->emplace_back(std::move(message));
+ continue;
+ }
+ }
+
+ if (SyncWordMatch(sync1, UPLINK_SYNC_WORD)) {
+ auto start = i - SYNC_BITS * 2 + 3;
+ auto start_timestamp = timestamp + start * 1000 / 2083333;
+ auto message = DemodBest(buffer, start, false /* !downlink */, start_timestamp);
+ if (message) {
+ i = start + message.BitLength() * 2;
+ sync_bits = 0;
+ messages->emplace_back(std::move(message));
+ continue;
+ }
+ }
+ }
+
+ return messages;
+ }
+
+ RawMessage TwoMegDemodulator::DemodBest(const PhaseBuffer &buffer, unsigned start, bool downlink, std::uint64_t timestamp) {
+ auto message0 = downlink ? DemodOneDownlink(buffer, start, timestamp) : DemodOneUplink(buffer, start, timestamp);
+ auto message1 = downlink ? DemodOneDownlink(buffer, start + 1, timestamp) : DemodOneUplink(buffer, start + 1, timestamp);
+
+ unsigned errors0 = (message0 ? message0.Errors() : 9999);
+ unsigned errors1 = (message1 ? message1.Errors() : 9999);
+
+ if (errors0 <= errors1)
+ return message0; // should be move-eligible
+ else
+ return message1; // should be move-eligible
+ }
+
+ RawMessage TwoMegDemodulator::DemodOneDownlink(const PhaseBuffer &buffer, unsigned start, std::uint64_t timestamp) {
+ auto sync = CheckSyncWord(buffer, start, DOWNLINK_SYNC_WORD);
+ if (!sync.first) {
+ // Sync word had errors
+ return RawMessage();
+ }
+
+ Bytes raw = DemodBits(buffer, start + SYNC_BITS*2, DOWNLINK_LONG_BYTES, sync.second);
+
+ bool success;
+ uat::Bytes corrected;
+ unsigned errors;
+ std::tie(success, corrected, errors) = fec_.CorrectDownlink(raw);
+ if (!success) {
+ // Error correction failed
+ return RawMessage();
+ }
+
+ return RawMessage(std::move(corrected), timestamp, errors, 0);
+ }
+
+ RawMessage TwoMegDemodulator::DemodOneUplink(const PhaseBuffer &buffer, unsigned start, std::uint64_t timestamp) {
+ auto sync = CheckSyncWord(buffer, start, UPLINK_SYNC_WORD);
+ if (!sync.first) {
+ // Sync word had errors
+ return RawMessage();
+ }
+
+ Bytes raw = DemodBits(buffer, start + SYNC_BITS*2, UPLINK_BYTES, sync.second);
+
+ bool success;
+ uat::Bytes corrected;
+ unsigned errors;
+ std::tie(success, corrected, errors) = fec_.CorrectUplink(raw);
+
+ if (!success) {
+ // Error correction failed
+ return RawMessage();
+ }
+
+ return RawMessage(std::move(corrected), timestamp, errors, 0);
+ }
+}
diff --git a/demodulator.h b/demodulator.h
new file mode 100644
index 0000000..418fd01
--- /dev/null
+++ b/demodulator.h
@@ -0,0 +1,62 @@
+// -*- c++ -*-
+
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#ifndef DUMP978_DEMODULATOR_H
+#define DUMP978_DEMODULATOR_H
+
+#include
+#include
+
+#include "fec.h"
+#include "common.h"
+#include "uat_message.h"
+#include "convert.h"
+#include "message_source.h"
+
+namespace dump978 {
+ class Demodulator {
+ public:
+ virtual ~Demodulator() {}
+ virtual uat::SharedMessageVector Demodulate(std::uint64_t timestamp, const uat::PhaseBuffer &buffer) = 0;
+
+ virtual unsigned NumTrailingSamples() = 0;
+
+ protected:
+ uat::FEC fec_;
+ };
+
+ class TwoMegDemodulator : public Demodulator {
+ public:
+ uat::SharedMessageVector Demodulate(std::uint64_t timestamp, const uat::PhaseBuffer &buffer) override;
+ unsigned NumTrailingSamples() override;
+
+ private:
+ uat::RawMessage DemodBest(const uat::PhaseBuffer &buffer, unsigned start, bool downlink, std::uint64_t timestamp);
+ uat::RawMessage DemodOneDownlink(const uat::PhaseBuffer &buffer, unsigned start, std::uint64_t timestamp);
+ uat::RawMessage DemodOneUplink(const uat::PhaseBuffer &buffer, unsigned start, std::uint64_t timestamp);
+ };
+
+ class Receiver : public uat::MessageSource {
+ public:
+ virtual void HandleSamples(std::uint64_t timestamp, const uat::Bytes &buffer) = 0;
+ };
+
+ class SingleThreadReceiver : public Receiver {
+ public:
+ SingleThreadReceiver(SampleFormat format);
+
+ void HandleSamples(std::uint64_t timestamp, const uat::Bytes &buffer) override;
+
+ private:
+ SampleConverter::Pointer converter_;
+ uat::PhaseBuffer phase_;
+ std::unique_ptr demodulator_;
+ };
+
+};
+
+#endif
+
diff --git a/dump978_main.cc b/dump978_main.cc
new file mode 100644
index 0000000..a410f1e
--- /dev/null
+++ b/dump978_main.cc
@@ -0,0 +1,224 @@
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "socket_output.h"
+#include "message_dispatch.h"
+#include "sample_source.h"
+#include "soapy_source.h"
+#include "convert.h"
+#include "demodulator.h"
+
+using namespace uat;
+using namespace dump978;
+
+namespace po = boost::program_options;
+using boost::asio::ip::tcp;
+
+struct listen_option {
+ std::string host;
+ std::string port;
+};
+
+struct format_option {
+ SampleFormat format;
+};
+
+// Specializations of validate for --xxx-port
+void validate(boost::any& v,
+ const std::vector& values,
+ listen_option* target_type, int)
+{
+ po::validators::check_first_occurrence(v);
+ const std::string &s = po::validators::get_single_string(values);
+
+ static const boost::regex r("(?:([^:]+):)?(\\d+)");
+ boost::smatch match;
+ if (boost::regex_match(s, match, r)) {
+ listen_option o;
+ o.host = match[1];
+ o.port = match[2];
+ v = boost::any(o);
+ } else {
+ throw po::validation_error(po::validation_error::invalid_option_value);
+ }
+}
+
+// Specializations of validate for --format
+void validate(boost::any& v,
+ const std::vector& values,
+ format_option* target_type, int)
+{
+ po::validators::check_first_occurrence(v);
+ const std::string &s = po::validators::get_single_string(values);
+
+ static std::map formats {
+ { "CU8", SampleFormat::CU8 },
+ { "CS8", SampleFormat::CS8 },
+ { "CS16H", SampleFormat::CS16H },
+ { "CF32H", SampleFormat::CF32H }
+ };
+
+ auto entry = formats.find(s);
+ if (entry == formats.end())
+ throw po::validation_error(po::validation_error::invalid_option_value);
+
+ v = boost::any(format_option { entry->second });
+}
+
+#define EXIT_NO_RESTART (64)
+
+static int realmain(int argc, char **argv)
+{
+ boost::asio::io_service io_service;
+
+ po::options_description desc("Allowed options");
+ desc.add_options()
+ ("help", "produce help message")
+ ("raw-stdout", "write raw messages to stdout")
+ ("json-stdout", "write decoded json to stdout")
+ ("format", po::value()->default_value({ SampleFormat::CU8 }, "CU8"), "set sample format")
+ ("stdin", "read sample data from stdin")
+ ("file", po::value(), "read sample data from a file")
+ ("file-throttle", "throttle file input to realtime")
+ ("sdr", po::value(), "read sample data from named SDR device")
+ ("raw-port", po::value< std::vector >(), "listen for connections on [host:]port and provide raw messages")
+ ("json-port", po::value< std::vector >(), "listen for connections on [host:]port and provide decoded json");
+
+ po::variables_map opts;
+
+ try {
+ po::store(po::parse_command_line(argc, argv, desc), opts);
+ po::notify(opts);
+ } catch (boost::program_options::error &err) {
+ std::cerr << err.what() << std::endl;
+ std::cerr << desc << std::endl;
+ return EXIT_NO_RESTART;
+ }
+
+ if (opts.count("help")) {
+ std::cerr << desc << std::endl;
+ return EXIT_NO_RESTART;
+ }
+
+ MessageDispatch dispatch;
+ SampleSource::Pointer source;
+
+ tcp::resolver resolver(io_service);
+
+ auto format = opts["format"].as().format;
+
+ if (opts.count("stdin") + opts.count("file") + opts.count("sdr") != 1) {
+ std::cerr << "Exactly one of --stdin, --file, or --sdr must be used" << std::endl;
+ return EXIT_NO_RESTART;
+ }
+
+ if (opts.count("stdin")) {
+ source = StdinSampleSource::Create(io_service, format);
+ } else if (opts.count("file")) {
+ boost::filesystem::path path(opts["file"].as());
+ source = FileSampleSource::Create(io_service, path, format, opts.count("file-throttle") > 0);
+ } else if (opts.count("sdr")) {
+ auto device = opts["sdr"].as();
+ source = SoapySampleSource::Create(format, device);
+ } else {
+ assert("impossible case" && false);
+ }
+
+ auto create_output_port = [&](std::string option, SocketListener::ConnectionFactory factory) -> bool {
+ if (!opts.count(option)) {
+ return true;
+ }
+
+ bool ok = true;
+ for (auto l : opts[option].as< std::vector >()) {
+ tcp::resolver::query query(l.host, l.port, tcp::resolver::query::passive);
+ boost::system::error_code ec;
+
+ bool success = false;
+ tcp::resolver::iterator end;
+ for (auto i = resolver.resolve(query, ec); i != end; ++i) {
+ const auto &endpoint = i->endpoint();
+
+ try {
+ auto listener = SocketListener::Create(io_service, endpoint, dispatch, factory);
+ listener->Start();
+ std::cerr << option << ": listening for connections on " << endpoint << std::endl;
+ success = true;
+ } catch (boost::system::system_error &err) {
+ std::cerr << option << ": could not listen on " << endpoint << ": " << err.what() << std::endl;
+ ec = err.code();
+ }
+ }
+
+ if (!success) {
+ std::cerr << option << ": no available listening addresses" << std::endl;
+ ok = false;
+ }
+ }
+
+ return ok;
+ };
+
+ auto raw_ok = create_output_port("raw-port", &RawOutput::Create);
+ auto json_ok = create_output_port("json-port", &JsonOutput::Create);
+ if (!raw_ok || !json_ok) {
+ return 1;
+ }
+
+ if (opts.count("raw-stdout")) {
+ dispatch.AddClient([](SharedMessageVector messages) {
+ for (const auto &message : *messages) {
+ std::cout << message << std::endl;
+ }
+ });
+ }
+
+ if (opts.count("json-stdout")) {
+ dispatch.AddClient([](SharedMessageVector messages) {
+ for (const auto &message : *messages) {
+ if (message.Type() == MessageType::DOWNLINK_SHORT || message.Type() == MessageType::DOWNLINK_LONG) {
+ std::cout << AdsbMessage(message).ToJson() << std::endl;
+ }
+ }
+ });
+ }
+
+ auto receiver = std::make_shared(format);
+ receiver->SetConsumer(std::bind(&MessageDispatch::Dispatch, &dispatch, std::placeholders::_1));
+
+ source->SetConsumer([&io_service,receiver](std::uint64_t timestamp, const Bytes &buffer, const boost::system::error_code &ec) {
+ if (ec) {
+ std::cerr << "sample source reports error: " << ec.message() << std::endl;
+ io_service.stop();
+ } else {
+ receiver->HandleSamples(timestamp, buffer);
+ }
+ });
+
+ source->Start();
+
+ io_service.run();
+
+ source->Stop();
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ try {
+ return realmain(argc, argv);
+ } catch (...) {
+ std::cerr << "Uncaught exception: " << boost::current_exception_diagnostic_information() << std::endl;
+ return 2;
+ }
+}
diff --git a/faup978_main.cc b/faup978_main.cc
new file mode 100644
index 0000000..99fb6f4
--- /dev/null
+++ b/faup978_main.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "socket_input.h"
+#include "message_source.h"
+#include "uat_message.h"
+#include "track.h"
+
+using namespace uat;
+
+namespace po = boost::program_options;
+using boost::asio::ip::tcp;
+
+struct connect_option {
+ std::string host;
+ std::string port;
+};
+
+// Specializations of validate for --connect
+void validate(boost::any& v,
+ const std::vector& values,
+ connect_option* target_type, int)
+{
+ po::validators::check_first_occurrence(v);
+ const std::string &s = po::validators::get_single_string(values);
+
+ static const boost::regex r("(?:([^:]+):)?(\\d+)");
+ boost::smatch match;
+ if (boost::regex_match(s, match, r)) {
+ v = boost::any(connect_option { match[1], match[2] });
+ } else {
+ throw po::validation_error(po::validation_error::invalid_option_value);
+ }
+}
+
+#define EXIT_NO_RESTART (64)
+
+static int realmain(int argc, char **argv)
+{
+ boost::asio::io_service io_service;
+
+ po::options_description desc("Allowed options");
+ desc.add_options()
+ ("help", "produce help message")
+ ("connect", po::value(), "connect to host:port for raw UAT data");
+
+ po::variables_map opts;
+
+ try {
+ po::store(po::parse_command_line(argc, argv, desc), opts);
+ po::notify(opts);
+ } catch (boost::program_options::error &err) {
+ std::cerr << err.what() << std::endl;
+ std::cerr << desc << std::endl;
+ return EXIT_NO_RESTART;
+ }
+
+ if (opts.count("help")) {
+ std::cerr << desc << std::endl;
+ return EXIT_NO_RESTART;
+ }
+
+ if (!opts.count("connect")) {
+ std::cerr << "--connect option is required" << std::endl;
+ return EXIT_NO_RESTART;
+ }
+
+ auto connect = opts["connect"].as();
+ auto input = RawInput::Create(io_service, connect.host, connect.port);
+ auto tracker = Tracker::Create(io_service);
+
+ input->SetConsumer([tracker](SharedMessageVector messages) {
+ /*
+ for (const auto &message : *messages) {
+ std::cout << message << std::endl;
+ if (message.Type() == MessageType::DOWNLINK_SHORT || message.Type() == MessageType::DOWNLINK_LONG) {
+ std::cout << AdsbMessage(message).ToJson() << std::endl;
+ }
+ }
+ */
+ tracker->HandleMessages(messages);
+ std::cout << "tracking " << tracker->Aircraft().size() << " aircraft" << std::endl;
+ });
+
+ tracker->Start();
+ input->Start();
+
+ io_service.run();
+
+ input->Stop();
+ tracker->Stop();
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+#if 0
+ try {
+ return realmain(argc, argv);
+ } catch (...) {
+ std::cerr << "Uncaught exception: " << boost::current_exception_diagnostic_information() << std::endl;
+ return 2;
+ }
+#else
+ return realmain(argc, argv);
+#endif
+}
diff --git a/fec.cc b/fec.cc
new file mode 100644
index 0000000..5e1f70c
--- /dev/null
+++ b/fec.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#include "fec.h"
+#include "uat_protocol.h"
+
+extern "C" {
+#include "fec/rs.h"
+}
+
+uat::FEC::FEC(void)
+{
+ rs_downlink_short_ = ::init_rs_char(8, /* gfpoly */ DOWNLINK_POLY, /* fcr */ 120, /* prim */ 1, /* nroots */ 12, /* pad */ 225);
+ rs_downlink_long_ = ::init_rs_char(8, /* gfpoly */ DOWNLINK_POLY, /* fcr */ 120, /* prim */ 1, /* nroots */ 14, /* pad */ 207);
+ rs_uplink_ = ::init_rs_char(8, /* gfpoly */ UPLINK_POLY, /* fcr */ 120, /* prim */ 1, /* nroots */ 20, /* pad */ 163);
+}
+
+uat::FEC::~FEC(void)
+{
+ ::free_rs_char(rs_downlink_short_);
+ ::free_rs_char(rs_downlink_long_);
+ ::free_rs_char(rs_uplink_);
+}
+
+std::tuple uat::FEC::CorrectDownlink(const Bytes &raw)
+{
+ using R = std::tuple;
+
+ if (raw.size() != DOWNLINK_LONG_BYTES) {
+ return R { false, {}, 0 };
+ }
+
+ // Try decoding as a Long UAT.
+ Bytes corrected;
+ std::copy(raw.begin(), raw.end(), std::back_inserter(corrected));
+
+ int n_corrected = ::decode_rs_char(rs_downlink_long_, corrected.data(), NULL, 0);
+ if (n_corrected >= 0 && n_corrected <= 7 && (corrected[0]>>3) != 0) {
+ // Valid long frame.
+ corrected.resize(DOWNLINK_LONG_DATA_BYTES);
+ return R { true, std::move(corrected), n_corrected };
+ }
+
+ // Retry as Basic UAT
+ // We rely on decode_rs_char not modifying the data if there were
+ // uncorrectable errors in the previous step.
+ n_corrected = ::decode_rs_char(rs_downlink_short_, corrected.data(), NULL, 0);
+ if (n_corrected >= 0 && n_corrected <= 6 && (corrected[0]>>3) == 0) {
+ // Valid short frame
+ corrected.resize(DOWNLINK_SHORT_DATA_BYTES);
+ return R { true, std::move(corrected), n_corrected };
+ }
+
+ // Failed.
+ return R { false, {}, 0 };
+}
+
+std::tuple uat::FEC::CorrectUplink(const Bytes &raw)
+{
+ using R = std::tuple;
+
+ if (raw.size() != UPLINK_BYTES) {
+ return R { false, {}, 0 };
+ }
+
+ // uplink messages consist of 6 blocks, interleaved; each block consists of a data section
+ // then an ECC section; we need to deinterleave, check/correct the data, then join the blocks
+ // removing the ECC sections.
+ unsigned total_errors = 0;
+ Bytes corrected;
+ Bytes blockdata;
+
+ corrected.reserve(UPLINK_DATA_BYTES);
+ blockdata.resize(UPLINK_BLOCK_BYTES);
+
+ for (unsigned block = 0; block < UPLINK_BLOCKS_PER_FRAME; ++block) {
+ // deinterleave
+ for (unsigned i = 0; i < UPLINK_BLOCK_BYTES; ++i) {
+ blockdata[i] = raw[i * UPLINK_BLOCKS_PER_FRAME + block];
+ }
+
+ // error-correct
+ int n_corrected = ::decode_rs_char(rs_uplink_, blockdata.data(), NULL, 0);
+ if (n_corrected < 0 || n_corrected > 10) {
+ // Failed
+ return R { false, {}, 0 };
+ }
+
+ total_errors += n_corrected;
+
+ // copy the data into the right place
+ std::copy(blockdata.begin(), blockdata.begin() + UPLINK_BLOCK_DATA_BYTES, std::back_inserter(corrected));
+ }
+
+ return R { true, std::move(corrected), total_errors };
+}
diff --git a/fec.h b/fec.h
new file mode 100644
index 0000000..2279739
--- /dev/null
+++ b/fec.h
@@ -0,0 +1,48 @@
+// -*- c++ -*-
+
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#ifndef UAT_FEC_H
+#define UAT_FEC_H
+
+#include
+
+#include "common.h"
+
+namespace uat {
+ // Deinterleaving and error-correction of UAT messages.
+ // This delegates to the "fec" library (in fec/) for the actual Reed-Solomon
+ // error-correction work.
+ class FEC {
+ public:
+ FEC();
+ ~FEC();
+
+ // Given DOWNLINK_LONG_BYTES of demodulated data, returns a tuple of:
+ // bool - true if the message is good, false if it was uncorrectable.
+ // Bytes - a buffer containing the corrected data with FEC bits removed;
+ // this will be either DOWNLINK_SHORT_DATA_BYTES or
+ // DOWNLINK_LONG_DATA_BYTES in size depending on the detected
+ // message type. Empty if the message was uncorrectable.
+ // unsigned - the number of errors corrected. 0 if the message was uncorrectable
+ std::tuple CorrectDownlink(const Bytes &raw);
+
+ // Given UPLINK_BYTES of demodulated data, returns a tuple of:
+ // bool - true if the message is good, false if it was uncorrectable.
+ // Bytes - a buffer containing the deinterleaved, corrected data with
+ // FEC bits removed; this will be exactly UPLINK_DATA_BYTES
+ // in size. Empty if the message was uncorrectable.
+ // unsigned - the number of errors corrected. 0 if the message was uncorrectable
+ std::tuple CorrectUplink(const Bytes &raw);
+
+ private:
+ void *rs_uplink_;
+ void *rs_downlink_short_;
+ void *rs_downlink_long_;
+ };
+};
+
+#endif
+
diff --git a/legacy/LICENSE b/legacy/LICENSE
new file mode 100644
index 0000000..d6a9326
--- /dev/null
+++ b/legacy/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) 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
+this service 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 make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. 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.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+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
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the 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 a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE 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.
+
+ 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
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ 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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision 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, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This 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.
+
diff --git a/message_dispatch.cc b/message_dispatch.cc
new file mode 100644
index 0000000..b6f72cd
--- /dev/null
+++ b/message_dispatch.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#include "message_dispatch.h"
+
+#include
+#include
+
+namespace uat {
+ MessageDispatch::MessageDispatch()
+ : next_handle_(0),
+ busy_(0)
+ {}
+
+ MessageDispatch::Handle MessageDispatch::AddClient(MessageHandler handler) {
+ std::unique_lock lock(mutex_);
+
+ Handle h = next_handle_++;
+ clients_[h] = { handler, false };
+ return h;
+ }
+
+ void MessageDispatch::RemoveClient(Handle h) {
+ std::unique_lock lock(mutex_);
+
+ auto i = clients_.find(h);
+ if (i == clients_.end())
+ return;
+
+ i->second.deleted = true;
+ PurgeDeadClients();
+ }
+
+ template
+ class BusyCounter {
+ public:
+ BusyCounter(T& var)
+ : var_(var), owned_(true)
+ {
+ ++var_;
+ }
+
+ BusyCounter(const T&) = delete;
+ BusyCounter& operator=(const T&) = delete;
+
+ ~BusyCounter() {
+ release();
+ }
+
+ void release() {
+ if (owned_) {
+ --var_;
+ owned_ = false;
+ }
+ }
+ private:
+ T& var_;
+ bool owned_;
+ };
+
+ void MessageDispatch::Dispatch(SharedMessageVector messages) {
+ std::unique_lock lock(mutex_);
+
+ BusyCounter counter(busy_);
+ for (auto i = clients_.begin(); i != clients_.end(); ++i) {
+ Client &c = i->second;
+ if (!c.deleted)
+ c.handler(messages);
+ }
+ counter.release();
+
+ PurgeDeadClients();
+ }
+
+ void MessageDispatch::PurgeDeadClients() {
+ // caller must lock!
+ if (busy_)
+ return;
+
+ for (auto i = clients_.begin(); i != clients_.end(); ) {
+ Client &c = i->second;
+ if (c.deleted)
+ clients_.erase(i++);
+ else
+ ++i;
+ }
+ }
+}
diff --git a/message_dispatch.h b/message_dispatch.h
new file mode 100644
index 0000000..a66e150
--- /dev/null
+++ b/message_dispatch.h
@@ -0,0 +1,49 @@
+// -*- c++ -*-
+
+// Copyright (c) 2019, FlightAware LLC.
+// All rights reserved.
+// Licensed under the 2-clause BSD license; see the LICENSE file
+
+#ifndef MESSAGE_DISPATCH_H
+#define MESSAGE_DISPATCH_H
+
+#include
+#include