[hamradio-commits] [dump1090-mutability] 02/02: New upstream version 1.14

Matteo F. Vescovi mfv at moszumanska.debian.org
Sun Jan 28 13:46:05 UTC 2018


This is an automated email from the git hooks/post-receive script.

mfv pushed a commit to branch upstream
in repository dump1090-mutability.

commit 27ad657b79d84efb955fd949126c051a1e03d231
Author: Matteo F. Vescovi <mfv at debian.org>
Date:   Fri Jan 26 23:56:16 2018 +0100

    New upstream version 1.14
---
 .gitignore                         |   11 +
 COPYING                            |  339 ++++++++
 LICENSE                            |   41 +
 Makefile                           |   44 +
 README-dump1090.md                 |  287 +++++++
 README-json.md                     |  126 +++
 README.md                          |  154 ++++
 anet.c                             |  345 ++++++++
 anet.h                             |   59 ++
 cpr.c                              |  349 ++++++++
 cpr.h                              |   39 +
 cprtests.c                         |  303 +++++++
 crc.c                              |  555 ++++++++++++
 crc.h                              |   39 +
 demod_2000.c                       |  607 +++++++++++++
 demod_2000.h                       |   27 +
 demod_2400.c                       |  494 +++++++++++
 demod_2400.h                       |   27 +
 dump1090.c                         | 1122 ++++++++++++++++++++++++
 dump1090.h                         |  454 ++++++++++
 icao_filter.c                      |  140 +++
 icao_filter.h                      |   41 +
 interactive.c                      |  177 ++++
 mode_ac.c                          |  383 +++++++++
 mode_s.c                           | 1259 +++++++++++++++++++++++++++
 net_io.c                           | 1639 ++++++++++++++++++++++++++++++++++++
 public_html/config.js              |   47 ++
 public_html/coolclock/coolclock.js |  318 +++++++
 public_html/coolclock/excanvas.js  |  785 +++++++++++++++++
 public_html/coolclock/moreskins.js |  212 +++++
 public_html/gmap.html              |  192 +++++
 public_html/planeObject.js         |  359 ++++++++
 public_html/script.js              |  914 ++++++++++++++++++++
 public_html/spinny.gif             |  Bin 0 -> 80649 bytes
 public_html/style.css              |   41 +
 stats.c                            |  263 ++++++
 stats.h                            |  130 +++
 testfiles/modes1.bin               |   13 +
 tools/debug.html                   |  193 +++++
 track.c                            |  659 +++++++++++++++
 track.h                            |  115 +++
 util.c                             |   81 ++
 util.h                             |   39 +
 view1090.c                         |  275 ++++++
 view1090.h                         |   84 ++
 45 files changed, 13781 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..35355ea
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+*.o
+dump1090
+testfiles/*.bin
+misc
+frames.js
+.*.swp
+*~
+*.rej
+*.orig
+untrackedDeveloperSettings.js
+view1090
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+		    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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 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/LICENSE b/LICENSE
new file mode 100644
index 0000000..c05596a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,41 @@
+This version of dump1090 is licensed under the GPL, v2 or later.
+
+Please see the individual source files and the file COPYING
+for full copyright and license details.
+
+If you need to use dump1090 in a way that is incompatible with
+the GPL, please contact Oliver Jowett <oliver at mutability.co.uk>
+to discuss your requirements.
+
+The source code 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 <antirez at gmail.com>
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//  *  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..6505939
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,44 @@
+#
+# When building a package or installing otherwise in the system, make
+# sure that the variable PREFIX is defined, e.g. make PREFIX=/usr/local
+#
+PROGNAME=dump1090
+
+ifndef DUMP1090_VERSION
+DUMP1090_VERSION=$(shell git describe --tags)
+endif
+
+ifdef PREFIX
+BINDIR=$(PREFIX)/bin
+SHAREDIR=$(PREFIX)/share/$(PROGNAME)
+EXTRACFLAGS=-DHTMLPATH=\"$(SHAREDIR)\"
+endif
+
+CPPFLAGS+=-DMODES_DUMP1090_VERSION=\"$(DUMP1090_VERSION)\"
+CFLAGS+=-O2 -g -Wall -Werror -W `pkg-config --cflags librtlsdr`
+LIBS=-lpthread -lm -lrt
+LIBS_RTL=`pkg-config --libs librtlsdr`
+CC=gcc
+
+all: dump1090 view1090
+
+%.o: %.c *.h
+	$(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRACFLAGS) -c $< -o $@
+
+dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o demod_2000.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o
+	$(CC) -g -o $@ $^ $(LIBS) $(LIBS_RTL) $(LDFLAGS)
+
+view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o
+	$(CC) -g -o $@ $^ $(LIBS) $(LDFLAGS)
+
+clean:
+	rm -f *.o dump1090 view1090 cprtests crctests
+
+test: cprtests
+	./cprtests
+
+cprtests: cpr.o cprtests.o
+	$(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRACFLAGS) -g -o $@ $^ -lm
+
+crctests: crc.c crc.h
+	$(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRACFLAGS) -g -DCRCDEBUG -o $@ $<
diff --git a/README-dump1090.md b/README-dump1090.md
new file mode 100644
index 0000000..5b05472
--- /dev/null
+++ b/README-dump1090.md
@@ -0,0 +1,287 @@
+Dump1090 README
+===
+
+Dump 1090 is a Mode S decoder specifically designed for RTLSDR devices.
+
+The main features are:
+
+* Robust decoding of weak messages, with mode1090 many users observed
+  improved range compared to other popular decoders.
+* Network support: TCP30003 stream (MSG5...), Raw packets, HTTP.
+* Embedded HTTP server that displays the currently detected aircrafts on
+  Google Map.
+* Single bit errors correction using the 24 bit CRC.
+* Ability to decode DF11, DF17 messages.
+* Ability to decode DF formats like DF0, DF4, DF5, DF16, DF20 and DF21
+  where the checksum is xored with the ICAO address by brute forcing the
+  checksum field using recently seen ICAO addresses.
+* Decode raw IQ samples from file (using --ifile command line switch).
+* Interactive command-line-interfae mode where aircrafts currently detected
+  are shown as a list refreshing as more data arrives.
+* CPR coordinates decoding and track calculation from velocity.
+* TCP server streaming and recceiving raw data to/from connected clients
+  (using --net).
+
+Installation
+---
+
+Type "make".
+
+Normal usage
+---
+
+To capture traffic directly from your RTL device and show the captured traffic
+on standard output, just run the program without options at all:
+
+    ./dump1090
+
+To just output hexadecimal messages:
+
+    ./dump1090 --raw
+
+To run the program in interactive mode:
+
+    ./dump1090 --interactive
+
+To run the program in interactive mode, with networking support, and connect
+with your browser to http://localhost:8080 to see live traffic:
+
+    ./dump1090 --interactive --net
+
+In iteractive mode it is possible to have a less information dense but more
+"arcade style" output, where the screen is refreshed every second displaying
+all the recently seen aircrafts with some additional information such as
+altitude and flight number, extracted from the received Mode S packets.
+
+Using files as source of data
+---
+
+To decode data from file, use:
+
+    ./dump1090 --ifile /path/to/binfile
+
+The binary file should be created using `rtl_sdr` like this (or with any other
+program that is able to output 8-bit unsigned IQ samples at 2Mhz sample rate).
+
+    rtl_sdr -f 1090000000 -s 2000000 -g 50 output.bin
+
+In the example `rtl_sdr` a gain of 50 is used, simply you should use the highest
+gain availabe for your tuner. This is not needed when calling Dump1090 itself
+as it is able to select the highest gain supported automatically.
+
+It is possible to feed the program with data via standard input using
+the --ifile option with "-" as argument.
+
+Additional options
+---
+
+Dump1090 can be called with other command line options to set a different
+gain, frequency, and so forth. For a list of options use:
+
+    ./dump1090 --help
+
+Everything is not documented here should be obvious, and for most users calling
+it without arguments at all is the best thing to do.
+
+Reliability
+---
+
+By default Dump1090 checks for decoding errors using the 24-bit CRC checksum,
+where available. Messages with errors are discarded.
+
+The --fix command line switch enables fixing single bit error correction
+based on the CRC checksum. Technically, it uses a table of precomputed
+checksum differences resulting from single bit errors to look up the
+wrong bit position.
+
+This is indeed able to fix errors and works reliably in my experience,
+however if you are interested in very reliable data I suggest to use
+the --no-fix command line switch in order to disable error fixing.
+
+Performances and sensibility of detection
+---
+
+In my limited experience Dump1090 was able to decode a big number of messages
+even in conditions where I encountered problems using other programs, however
+no formal test was performed so I can't really claim that this program is
+better or worse compared to other similar programs.
+
+If you can capture traffic that Dump1090 is not able to decode properly, drop
+me an email with a download link. I may try to improve the detection during
+my free time (this is just an hobby project).
+
+Network server features
+---
+
+By enabling the networking support with --net Dump1090 starts listening
+for clients connections on port 30002 and 30001 (you can change both the
+ports if you want, see --help output).
+
+Port 30002
+---
+
+Connected clients are served with data ASAP as they arrive from the device
+(or from file if --ifile is used) in the raw format similar to the following:
+
+    *8D451E8B99019699C00B0A81F36E;
+
+Every entry is separated by a simple newline (LF character, hex 0x0A).
+
+Port 30001
+---
+
+Port 30001 is the raw input port, and can be used to feed Dump1090 with
+data in the same format as specified above, with hex messages starting with
+a `*` and ending with a `;` character.
+
+So for instance if there is another remote Dump1090 instance collecting data
+it is possible to sum the output to a local Dump1090 instance doing something
+like this:
+
+    nc remote-dump1090.example.net 30002 | nc localhost 30001
+
+It is important to note that what is received via port 30001 is also
+broadcasted to clients listening to port 30002.
+
+In general everything received from port 30001 is handled exactly like the
+normal traffic from RTL devices or from file when --ifile is used.
+
+It is possible to use Dump1090 just as an hub using --ifile with /dev/zero
+as argument as in the following example:
+
+    ./dump1090 --net-only
+
+Or alternatively to see what's happening on the screen:
+
+    ./dump1090 --net-only --interactive
+
+Then you can feed it from different data sources from the internet.
+
+Port 30003
+---
+
+Connected clients are served with messages in SBS1 (BaseStation) format,
+similar to:
+
+    MSG,4,,,738065,,,,,,,,420,179,,,0,,0,0,0,0
+    MSG,3,,,738065,,,,,,,35000,,,34.81609,34.07810,,,0,0,0,0
+
+This can be used to feed data to various sharing sites without the need to use another decoder.
+
+Antenna
+---
+
+Mode S messages are transmitted in the 1090 Mhz frequency. If you have a decent
+antenna you'll be able to pick up signals from aircrafts pretty far from your
+position, especially if you are outdoor and in a position with a good sky view.
+
+You can easily build a very cheap antenna following the istructions at:
+
+    http://antirez.com/news/46
+
+With this trivial antenna I was able to pick up signals of aircrafts 200+ Km
+away from me.
+
+If you are interested in a more serious antenna check the following
+resources:
+
+* http://gnuradio.org/redmine/attachments/download/246/06-foster-adsb.pdf
+* http://www.lll.lu/~edward/edward/adsb/antenna/ADSBantenna.html
+* http://modesbeast.com/pix/adsb-ant-drawing.gif
+
+Aggressive mode
+---
+
+With --aggressive it is possible to activate the *aggressive mode* that is a
+modified version of the Mode S packet detection and decoding.
+The aggresive mode uses more CPU usually (especially if there are many planes
+sending DF17 packets), but can detect a few more messages.
+
+The algorithm in aggressive mode is modified in the following ways:
+
+* Up to two demodulation errors are tolerated (adjacent entires in the
+  magnitude vector with the same eight). Normally only messages without
+  errors are checked.
+* It tries to fix DF17 messages with CRC errors resulting from any two bit
+  errors.
+
+The use of aggressive mdoe is only advised in places where there is
+low traffic in order to have a chance to capture some more messages.
+
+Debug mode
+---
+
+The Debug mode is a visual help to improve the detection algorithm or to
+understand why the program is not working for a given input.
+
+In this mode messages are displayed in an ASCII-art style graphical
+representation, where the individial magnitude bars sampled at 2Mhz are
+displayed.
+
+An index shows the sample number, where 0 is the sample where the first
+Mode S peak was found. Some additional background noise is also added
+before the first peak to provide some context.
+
+To enable debug mode and check what combinations of packets you can
+log, use `mode1090 --help` to obtain a list of available debug flags.
+
+Debug mode includes an optional javascript output that is used to visualize
+packets using a web browser, you can use the file debug.html under the
+'tools' directory to load the generated frames.js file.
+
+How this program works?
+---
+
+The code is very documented and written in order to be easy to understand.
+For the diligent programmer with a Mode S specification on his hands it
+should be trivial to understand how it works.
+
+The algorithms I used were obtained basically looking at many messages
+as displayed using a trow-away SDL program, and trying to model the algorithm
+based on how the messages look graphically.
+
+How to test the program?
+---
+
+If you have an RTLSDR device and you happen to be in an area where there
+are aircrafts flying over your head, just run the program and check for signals.
+
+However if you don't have an RTLSDR device, or if in your area the presence
+of aircrafts is very limited, you may want to try the sample file distributed
+with the Dump1090 distribution under the "testfiles" directory.
+
+Just run it like this:
+
+    ./dump1090 --ifile testfiles/modes1.bin
+
+What is --strip mode?
+---
+
+It is just a simple filter that will get raw IQ 8 bit samples in input
+and will output a file missing all the parts of the file where I and Q
+are lower than the specified <level> for more than 32 samples.
+
+Use it like this:
+
+    cat big.bin | ./dump1090 --snip 25 > small.bin
+
+I used it in order to create a small test file to include inside this
+program source code distribution.
+
+Contributing
+---
+
+Dump1090 was written during some free time during xmas 2012, it is an hobby
+project so I'll be able to address issues and improve it only during
+free time, however you are incouraged to send pull requests in order to
+improve the program. A good starting point can be the TODO list included in
+the source distribution.
+
+Credits
+---
+
+The original version of dump1090 was written by Salvatore Sanfilippo
+<antirez at gmail.com> and was released under the BSD three clause license.
+
+This modified version of dump1090 is maintained by Oliver Jowett
+<oliver at mutability.co.uk> and is released under the GPL (v2 or later).
diff --git a/README-json.md b/README-json.md
new file mode 100644
index 0000000..b6599aa
--- /dev/null
+++ b/README-json.md
@@ -0,0 +1,126 @@
+# JSON output formats
+
+dump1090 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
+e.g. [this collectd plugin](https://github.com/mutability/dump1090-tools/tree/master/collectd) feeds stats
+about dump1090's operation to collectd for later graphing.
+
+## Reading the json files
+
+There are two ways to obtain the json files:
+
+ * By HTTP from dump1090's internal webserver, which defaults to running on port 8080. The json is served from the data/ path, e.g. http://somehost:8080/data/aircraft.json
+ * As a file in the directory specified by --write-json on dump1090's command line. These can be exposed via a
+   separate webserver.
+
+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 dump1090. It does not change often and you probably just want to read it once at startup.
+The keys are:
+
+ * version: the version of dump1090 in use
+ * refresh: how often aircraft.json is updated (for the file version), in milliseconds. the webmap uses this to control its refresh interval.
+ * history: the current number of valid history files (see below)
+ * 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
+
+This file contains dump1090's list of 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 dump1090 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).
+   * squawk: the 4-digit squawk (octal representation)
+   * flight: the flight name / callsign
+   * lat, lon: the aircraft position in decimal degrees
+   * nucp: the NUCp (navigational uncertainty category) reported for the position
+   * seen_pos: how long ago (in seconds before "now") the position was last updated
+   * altitude: the aircraft altitude in feet, or "ground" if it is reporting it is on the ground
+   * vert_rate: vertical rate in feet/minute
+   * track: true track over ground in degrees (0-359)
+   * speed: reported speed in kt. This is usually speed over ground, but might be IAS - you can't tell the difference here, sorry!
+   * 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.
+   
+## history_0.json, history_1.json, ..., history_119.json
+
+These files are historical copies of aircraft.json at (by default) 30 second intervals. They follow exactly the
+same format as aircraft.json. To know how many are valid, see receiver.json ("history" value). They are written in
+a cycle, with history_0 being overwritten after history_119 is generated, so history_0.json is not necessarily the
+oldest history entry. To load history, you should:
+
+ * read "history" from receiver.json.
+ * load that many history_N.json files
+ * sort the resulting files by their "now" values
+ * process the files in order
+ 
+## stats.json
+
+This file contains statistics about dump1090's 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 dump1090 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 dump1090 from any source
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..64cf19b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,154 @@
+# dump1090-mutability Debian/Raspbian packages
+
+This is a fork of MalcolmRobb's version of dump1090
+that adds new functionality and is designed to be built as
+a Debian/Raspbian package.
+
+This version is licensed under the GPL (v2 or later).
+See the file COPYING for details.
+
+# Features
+
+* 2.4MHz "oversampling" support
+* doesn't run as root
+* supports FlightAware-TSV-format connections directly (same as the FlightAware version - no faup1090 needed)
+* can start from init.d, with detailed config via debconf or `/etc/default/dump1090-mutability`
+* can serve the virtual radar map via an external webserver (lighttpd integration included by default)
+* map view uses receiver lat/long given to dump1090 automatically
+* somewhat cleaned-up network code
+* tries to do things "the debian way" when it comes to config, package structure, etc
+* probably a bunch of other things I've forgotten..
+
+# Simple install via apt-get
+
+There is a repository that contains the current releases. To set up the repository:
+
+````
+$ wget https://github.com/mutability/mutability-repo/releases/download/v0.1.0/mutability-repo_0.1.0_armhf.deb
+$ sudo dpkg -i mutability-repo_0.1.0_armhf.deb
+````
+
+Then you can install and upgrade packages via apt-get as needed:
+
+````
+$ sudo apt-get update && sudo apt-get install dump1090-mutability
+$ sudo dpkg-reconfigure dump1090-mutability                           # for detailed configuration
+$ sudo apt-get install lighttpd && sudo lighty-enable-mod dump1090    # if you want to use the external webserver integration
+````
+
+Installing the mutability-repo package also installs the public key used to sign the packages; the signatures will be verified automatically by apt-get.
+
+# Manual repository setup
+
+Add a suitable entry to sources.list:
+
+````
+# echo "deb http://repo.mutability.co.uk/raspbian wheezy rpi" >/etc/apt/sources.list.d/mutabiltiy.list
+````
+
+Obtain the public key used to sign the repository release by a method of your choice. This is the signing key:
+
+````
+pub   2048R/4D731812 2014-12-28 [expires: 2015-12-28]
+      Key fingerprint = 2098 7C8D D31A 6107 E033  7CC3 80D5 57AA 4D73 1812
+uid                  Oliver Jowett (repo.mutability.co.uk archive signing key) <oliver at mutability.co.uk>
+````
+
+which is available from:
+
+ * [GitHub](https://github.com/mutability/mutability-repo/raw/master/mutability.gpg)
+ * [repo.mutability.co.uk](http://repo.mutability.co.uk/mutability.gpg) (caution - not HTTPS!)
+ * keys.gnupg.net (`gpg --keyserver keys.gnupg.net --recv-keys 4D731812`)
+
+Install the key with `apt-key add` or by placing the keyring in `/etc/apt/trusted.gpg.d/`
+
+# Manual installation
+
+To install from packages directly:
+
+You will need a librtlsdr0 package for Raspbian.
+There is no standard build of this.
+I have built suitable packages that are available from 
+[this release page](https://github.com/mutability/librtlsdr/releases)
+
+Then you will need the dump1090-mutability package itself from
+[this release page](https://github.com/mutability/dump1090/releases)
+
+Install the packages with dpkg.
+
+# Configuration
+
+By default it'll only ask you whether to start automatically and assume sensible defaults for everything else.
+Notable defaults that are perhaps not what you'd first expect:
+
+* All network ports are bound to the localhost interface only.
+  If you need remote access to the ADS-B data ports, you will want to change this to bind to the wildcard address.
+* The internal HTTP server is disabled. I recommend using an external webserver (see below).
+  You can reconfigure to enable the internal one if you don't want to use an external one.
+
+To reconfigure, either use `dpkg-reconfigure dump1090-mutability` or edit `/etc/default/dump1090-mutability`. Both should be self-explanatory.
+
+## External webserver configuration
+
+This is the recommended configuration; a dedicated webserver is almost always going to be better and more secure than the collection of hacks that is the dump1090 webserver.
+It works by having dump1090 write json files to a path under `/run` once a second (this is on tmpfs and will not write to the sdcard).
+Then an external webserver is used to serve both the static html/javascript files making up the map view, and the json files that provide the dynamic data.
+
+The package includes a config file for lighttpd (which is what I happen to use on my system).
+To use this:
+
+````
+# apt-get install lighttpd         # if you don't have it already
+# lighty-enable-mod dump1090
+# service lighttpd force-reload
+````
+
+This uses a configuration file installed by the package at `/etc/lighttpd/conf-available/89-dump1090.conf`.
+It makes the map view available at http://<pi address>/dump1090/
+
+This should also work fine with other webservers, you will need to write a similar config to the lighttpd one (it's basically just a couple of aliases).
+If you do set up a config for something else, please send me a copy so I can integrate it into the package!
+
+## Logging
+
+The default configuration logs to `/var/log/dump1090-mutability.log` (this can be reconfigured).
+The only real logging other than any startup problems is hourly stats.
+There is a logrotate configuration installed by the package at `/etc/logrotate.d/dump1090-mutability` that will rotate that logfile weekly.
+
+# Bug reports, feedback etc
+
+Please use the [github issues page](https://github.com/mutability/dump1090/issues) to report any problems.
+Or you can [email me](mailto:oliver at mutability.co.uk).
+
+# Future plans
+
+Packages following the same model for MalcolmRobb & FlightAware's forks of dump1090 are in the pipeline.
+So is a repackaged version of piaware.
+
+# Building from source
+
+While there is a Makefile that you can use, the preferred way to build is via the Debian package building system:
+
+````
+$ sudo apt-get install librtlsdr-dev libusb-1.0-0-dev pkg-config debhelper
+$ dpkg-buildpackage -b
+````
+
+Or you can use debuild/pdebuild. I find building via qemubuilder quite effective for building images for Raspbian (it's actually faster to build on an emulated ARM running on my PC than to build directly on real hardware).
+
+Here's the pbuilder config I use to build the Raspbian packages:
+
+````
+MIRRORSITE=http://mirrordirector.raspbian.org/raspbian/
+PDEBUILD_PBUILDER=cowbuilder
+BASEPATH=/var/cache/pbuilder/armhf-raspbian-wheezy-base.cow
+DISTRIBUTION=wheezy
+OTHERMIRROR="deb http://repo.mutability.co.uk/raspbian wheezy rpi"
+ARCHITECTURE=armhf
+DEBOOTSTRAP=qemu-debootstrap
+DEBOOTSTRAPOPTS="--variant=buildd --keyring=/usr/share/keyrings/raspbian-archive-keyring.gpg"
+COMPONENTS="main contrib non-free rpi"
+EXTRAPACKAGES="eatmydata debhelper fakeroot"
+ALLOWUNTRUSTED="no"
+APTKEYRINGS=("/home/oliver/ppa/mutability.gpg")
+````
diff --git a/anet.c b/anet.c
new file mode 100644
index 0000000..859c98c
--- /dev/null
+++ b/anet.c
@@ -0,0 +1,345 @@
+/* anet.c -- Basic TCP socket stuff made a bit less boring
+ *
+ * Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * 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 _WIN32
+  #include <sys/types.h>
+  #include <sys/socket.h>
+  #include <sys/stat.h>
+  #include <sys/un.h>
+  #include <netinet/in.h>
+  #include <netinet/tcp.h>
+  #include <arpa/inet.h>
+  #include <unistd.h>
+  #include <fcntl.h>
+  #include <string.h>
+  #include <netdb.h>
+  #include <errno.h>
+  #include <stdarg.h>
+  #include <stdio.h>
+#else
+  #include "winstubs.h" //Put everything Windows specific in here
+  #include "dump1090.h"
+#endif
+
+#include "anet.h"
+
+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;
+#ifndef _WIN32
+    /* 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;
+    }
+#else
+    flags = 1;
+    if (ioctlsocket(fd, FIONBIO, &flags)) {
+        errno = WSAGetLastError();
+        anetSetError(err, "ioctlsocket(FIONBIO): %s", strerror(errno));
+        return ANET_ERR;
+    }
+#endif
+    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;
+    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&yes, sizeof(yes)) == -1) {
+        anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno));
+        return ANET_ERR;
+    }
+    return ANET_OK;
+}
+
+int anetResolve(char *err, char *host, char *ipbuf)
+{
+    struct sockaddr_in sa;
+
+    sa.sin_family = AF_INET;
+    if (inet_aton(host, (void*)&sa.sin_addr) == 0) {
+        struct hostent *he;
+
+        he = gethostbyname(host);
+        if (he == NULL) {
+            anetSetError(err, "can't resolve: %s", host);
+            return ANET_ERR;
+        }
+        memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
+    }
+    strcpy(ipbuf,inet_ntoa(sa.sin_addr));
+    return ANET_OK;
+}
+
+static int anetCreateSocket(char *err, int domain) {
+    int s, on = 1;
+    if ((s = socket(domain, SOCK_STREAM, 0)) == -1) {
+#ifdef _WIN32
+        errno = WSAGetLastError();
+#endif
+        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));
+        return ANET_ERR;
+    }
+    return s;
+}
+
+#define ANET_CONNECT_NONE 0
+#define ANET_CONNECT_NONBLOCK 1
+static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
+{
+    int s;
+    struct sockaddr_in sa;
+
+    if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
+        return ANET_ERR;
+
+    memset(&sa,0,sizeof(sa));
+    sa.sin_family = AF_INET;
+    sa.sin_port = htons((uint16_t)port);
+    if (inet_aton(addr, (void*)&sa.sin_addr) == 0) {
+        struct hostent *he;
+
+        he = gethostbyname(addr);
+        if (he == NULL) {
+            anetSetError(err, "can't resolve: %s", addr);
+            close(s);
+            return ANET_ERR;
+        }
+        memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
+    }
+    if (flags & ANET_CONNECT_NONBLOCK) {
+        if (anetNonBlock(err,s) != ANET_OK)
+            return ANET_ERR;
+    }
+    if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+        if (errno == EINPROGRESS &&
+            flags & ANET_CONNECT_NONBLOCK)
+            return s;
+
+        anetSetError(err, "connect: %s", strerror(errno));
+        close(s);
+        return ANET_ERR;
+    }
+    return s;
+}
+
+int anetTcpConnect(char *err, char *addr, int port)
+{
+    return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE);
+}
+
+int anetTcpNonBlockConnect(char *err, char *addr, int port)
+{
+    return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
+}
+
+/* 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,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,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 (bind(s,sa,len) == -1) {
+#ifdef _WIN32
+        errno = WSAGetLastError();
+#endif
+        anetSetError(err, "bind: %s", strerror(errno));
+        close(s);
+        return ANET_ERR;
+    }
+
+    /* Use a backlog of 512 entries. We pass 511 to the listen() call because
+     * the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1);
+     * which will thus give us a backlog of 512 entries */
+    if (listen(s, 511) == -1) {
+#ifdef _WIN32
+        errno = WSAGetLastError();
+#endif
+        anetSetError(err, "listen: %s", strerror(errno));
+        close(s);
+        return ANET_ERR;
+    }
+    return ANET_OK;
+}
+
+int anetTcpServer(char *err, int port, char *bindaddr)
+{
+    int s;
+    struct sockaddr_in sa;
+
+    if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
+        return ANET_ERR;
+
+    memset(&sa,0,sizeof(sa));
+    sa.sin_family = AF_INET;
+    sa.sin_port = htons((uint16_t)port);
+    sa.sin_addr.s_addr = htonl(INADDR_ANY);
+    if (bindaddr && inet_aton(bindaddr, (void*)&sa.sin_addr) == 0) {
+        anetSetError(err, "invalid bind address");
+        close(s);
+        return ANET_ERR;
+    }
+    if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
+        return ANET_ERR;
+    return s;
+}
+
+static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) {
+    int fd;
+    while(1) {
+        fd = accept(s,sa,len);
+        if (fd == -1) {
+#ifndef _WIN32
+            if (errno == EINTR) {
+                continue;
+#else
+            errno = WSAGetLastError();
+            if (errno == WSAEWOULDBLOCK) {
+#endif
+            } else {
+                anetSetError(err, "accept: %s", strerror(errno));
+            }
+        }
+        break;
+    }
+    return fd;
+}
+
+int anetTcpAccept(char *err, int s, char *ip, int *port) {
+    int fd;
+    struct sockaddr_in sa;
+    socklen_t salen = sizeof(sa);
+    if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
+        return ANET_ERR;
+
+    if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
+    if (port) *port = ntohs(sa.sin_port);
+    return fd;
+}
+
+int anetPeerToString(int fd, char *ip, int *port) {
+    struct sockaddr_in sa;
+    socklen_t salen = sizeof(sa);
+
+    if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) {
+        *port = 0;
+        ip[0] = '?';
+        ip[1] = '\0';
+        return -1;
+    }
+    if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
+    if (port) *port = ntohs(sa.sin_port);
+    return 0;
+}
+
+int anetSockName(int fd, char *ip, int *port) {
+    struct sockaddr_in sa;
+    socklen_t salen = sizeof(sa);
+
+    if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
+        *port = 0;
+        ip[0] = '?';
+        ip[1] = '\0';
+        return -1;
+    }
+    if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
+    if (port) *port = ntohs(sa.sin_port);
+    return 0;
+}
diff --git a/anet.h b/anet.h
new file mode 100644
index 0000000..6d74af5
--- /dev/null
+++ b/anet.h
@@ -0,0 +1,59 @@
+/* anet.c -- Basic TCP socket stuff made a bit less boring
+ *
+ * Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * 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
+
+int anetTcpConnect(char *err, char *addr, int port);
+int anetTcpNonBlockConnect(char *err, char *addr, int port);
+int anetUnixConnect(char *err, char *path);
+int anetUnixNonBlockConnect(char *err, char *path);
+int anetRead(int fd, char *buf, int count);
+int anetResolve(char *err, char *host, char *ipbuf);
+int anetTcpServer(char *err, int port, char *bindaddr);
+int anetUnixServer(char *err, char *path, mode_t perm);
+int anetTcpAccept(char *err, int serversock, char *ip, int *port);
+int anetUnixAccept(char *err, int serversock);
+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 anetPeerToString(int fd, char *ip, int *port);
+int anetSetSendBuffer(char *err, int fd, int buffsize);
+
+#endif
diff --git a/cpr.c b/cpr.c
new file mode 100644
index 0000000..d641670
--- /dev/null
+++ b/cpr.c
@@ -0,0 +1,349 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// cpr.c - Compact Position Reporting decoder and tests
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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 <math.h>
+#include <stdio.h>
+
+//
+//=========================================================================
+//
+// 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 < 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 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
+
+    if ( (rlat0 - reflat) > 45 ) rlat0 -= 90;
+    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);
+}
diff --git a/cpr.h b/cpr.h
new file mode 100644
index 0000000..1cb413b
--- /dev/null
+++ b/cpr.h
@@ -0,0 +1,39 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// cpr.h - Compact Position Reporting prototypes
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#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
diff --git a/cprtests.c b/cprtests.c
new file mode 100644
index 0000000..182aa19
--- /dev/null
+++ b/cprtests.c
@@ -0,0 +1,303 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// cprtests.c - tests for CPR decoder
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+
+#include <math.h>
+#include <stdio.h>
+
+#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 },
+};
+
+// 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[%d,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[%d,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[%d,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[%d,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[%d]:  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[%d,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[%d,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[%d,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[%d]:  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[%d]:  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;
+}
diff --git a/crc.c b/crc.c
new file mode 100644
index 0000000..ad7d020
--- /dev/null
+++ b/crc.c
@@ -0,0 +1,555 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// crc.h: Mode S CRC calculation and error correction.
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#include "dump1090.h"
+
+#include <assert.h>
+
+// 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.
+static uint32_t crc_table[256];
+
+// Syndrome values for all single-bit errors;
+// used to speed up construction of error-
+// correction tables.
+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 = malloc(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 = malloc(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));
+}
+
+#ifdef CRCDEBUG
+int main(int argc, char **argv)
+{
+    int shortlen, longlen;
+    int i;
+    struct errorinfo *shorttable, *longtable;
+
+    if (argc < 3) {
+        fprintf(stderr, "syntax: crctests <ncorrect> <ndetect>\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
diff --git a/crc.h b/crc.h
new file mode 100644
index 0000000..96abe35
--- /dev/null
+++ b/crc.h
@@ -0,0 +1,39 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// crc.h: Mode S checksum prototypes.
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#ifndef DUMP1090_CRC_H
+#define DUMP1090_CRC_H
+
+#include <stdint.h>
+
+// 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)
+};
+
+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);
+
+#endif
diff --git a/demod_2000.c b/demod_2000.c
new file mode 100644
index 0000000..4fef969
--- /dev/null
+++ b/demod_2000.c
@@ -0,0 +1,607 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// demod_2000.c: 2MHz Mode S demodulator.
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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 "dump1090.h"
+
+// Mode S 2.0MHz demodulator
+
+// ============================== Debugging =================================
+//
+// Helper function for dumpMagnitudeVector().
+// It prints a single bar used to display raw signals.
+//
+// Since every magnitude sample is between 0-255, the function uses
+// up to 63 characters for every bar. Every character represents
+// a length of 4, 3, 2, 1, specifically:
+//
+// "O" is 4
+// "o" is 3
+// "-" is 2
+// "." is 1
+//
+static void dumpMagnitudeBar(int index, int magnitude) {
+    char *set = " .-o";
+    char buf[256];
+    int div = magnitude / 256 / 4;
+    int rem = magnitude / 256 % 4;
+
+    memset(buf,'O',div);
+    buf[div] = set[rem];
+    buf[div+1] = '\0';
+
+    if (index >= 0)
+        printf("[%.3d] |%-66s 0x%04X\n", index, buf, magnitude);
+    else
+        printf("[%.2d] |%-66s 0x%04X\n", index, buf, magnitude);
+}
+//
+//=========================================================================
+//
+// Display an ASCII-art alike graphical representation of the undecoded
+// message as a magnitude signal.
+//
+// The message starts at the specified offset in the "m" buffer.
+// The function will display enough data to cover a short 56 bit message.
+//
+// If possible a few samples before the start of the messsage are included
+// for context.
+//
+static void dumpMagnitudeVector(uint16_t *m, uint32_t offset) {
+    uint32_t padding = 5; // Show a few samples before the actual start.
+    uint32_t start = (offset < padding) ? 0 : offset-padding;
+    uint32_t end = offset + (MODES_PREAMBLE_SAMPLES)+(MODES_SHORT_MSG_SAMPLES) - 1;
+    uint32_t j;
+
+    for (j = start; j <= end; j++) {
+        dumpMagnitudeBar(j-offset, m[j]);
+    }
+}
+//
+//=========================================================================
+//
+// Produce a raw representation of the message as a Javascript file
+// loadable by debug.html.
+//
+static void dumpRawMessageJS(char *descr, unsigned char *msg,
+                             uint16_t *m, uint32_t offset, struct errorinfo *ei)
+{
+    int padding = 5; // Show a few samples before the actual start.
+    int start = offset - padding;
+    int end = offset + (MODES_PREAMBLE_SAMPLES)+(MODES_LONG_MSG_SAMPLES) - 1;
+    FILE *fp;
+    int j;
+
+    if ((fp = fopen("frames.js","a")) == NULL) {
+        fprintf(stderr, "Error opening frames.js: %s\n", strerror(errno));
+        exit(1);
+    }
+
+    fprintf(fp,"frames.push({\"descr\": \"%s\", \"mag\": [", descr);
+    for (j = start; j <= end; j++) {
+        fprintf(fp,"%d", j < 0 ? 0 : m[j]);
+        if (j != end) fprintf(fp,",");
+    }
+    fprintf(fp, "], ");
+    for (j = 0; j < MODES_MAX_BITERRORS; ++j)
+        fprintf(fp,"\"fix%d\": %d, ", j, ei->bit[j]);
+    fprintf(fp, "\"bits\": %d, \"hex\": \"", modesMessageLenByType(msg[0]>>3));
+    for (j = 0; j < MODES_LONG_MSG_BYTES; j++)
+        fprintf(fp,"\\x%02x",msg[j]);
+    fprintf(fp,"\"});\n");
+    fclose(fp);
+}
+//
+//=========================================================================
+//
+// This is a wrapper for dumpMagnitudeVector() that also show the message
+// in hex format with an additional description.
+//
+// descr  is the additional message to show to describe the dump.
+// msg    points to the decoded message
+// m      is the original magnitude vector
+// offset is the offset where the message starts
+//
+// The function also produces the Javascript file used by debug.html to
+// display packets in a graphical format if the Javascript output was
+// enabled.
+//
+static void dumpRawMessage(char *descr, unsigned char *msg, uint16_t *m, uint32_t offset) {
+    int  j;
+    int  msgtype = msg[0] >> 3;
+    struct errorinfo *ei = NULL;
+
+    if (msgtype == 17) {
+        int len = modesMessageLenByType(msgtype);
+        uint32_t csum = modesChecksum(msg, len);
+        ei = modesChecksumDiagnose(csum, len);
+    }
+
+    if (Modes.debug & MODES_DEBUG_JS) {
+        dumpRawMessageJS(descr, msg, m, offset, ei);
+        return;
+    }
+
+    printf("\n--- %s\n    ", descr);
+    for (j = 0; j < MODES_LONG_MSG_BYTES; j++) {
+        printf("%02x",msg[j]);
+        if (j == MODES_SHORT_MSG_BYTES-1) printf(" ... ");
+    }
+    printf(" (DF %d, Fixable: %d)\n", msgtype, ei ? ei->errors : 0);
+    dumpMagnitudeVector(m,offset);
+    printf("---\n\n");
+}
+
+//
+//=========================================================================
+//
+// Return -1 if the message is out of fase left-side
+// Return  1 if the message is out of fase right-size
+// Return  0 if the message is not particularly out of phase.
+//
+// Note: this function will access pPreamble[-1], so the caller should make sure to
+// call it only if we are not at the start of the current buffer
+//
+static int detectOutOfPhase(uint16_t *pPreamble) {
+    if (pPreamble[ 3] > pPreamble[2]/3) return  1;
+    if (pPreamble[10] > pPreamble[9]/3) return  1;
+    if (pPreamble[ 6] > pPreamble[7]/3) return -1;
+    if (pPreamble[-1] > pPreamble[1]/3) return -1;
+    return 0;
+}
+
+
+static uint16_t clamped_scale(uint16_t v, uint16_t scale) {
+    uint32_t scaled = (uint32_t)v * scale / 16384;
+    if (scaled > 65535) return 65535;
+    return (uint16_t) scaled;
+}
+// This function decides whether we are sampling early or late,
+// and by approximately how much, by looking at the energy in
+// preamble bits before and after the expected pulse locations.
+//
+// It then deals with one sample pair at a time, comparing samples
+// to make a decision about the bit value. Based on this decision it
+// modifies the sample value of the *adjacent* sample which will
+// contain some of the energy from the bit we just inspected.
+//
+// pPayload[0] should be the start of the preamble,
+// pPayload[-1 .. MODES_PREAMBLE_SAMPLES + MODES_LONG_MSG_SAMPLES - 1] should be accessible.
+// pPayload[MODES_PREAMBLE_SAMPLES .. MODES_PREAMBLE_SAMPLES + MODES_LONG_MSG_SAMPLES - 1] will be updated.
+static void applyPhaseCorrection(uint16_t *pPayload) {
+    int j;
+
+    // we expect 1 bits at 0, 2, 7, 9
+    // and 0 bits at -1, 1, 3, 4, 5, 6, 8, 10, 11, 12, 13, 14
+    // use bits -1,6 for early detection (bit 0/7 arrived a little early, our sample period starts after the bit phase so we include some of the next bit)
+    // use bits 3,10 for late detection (bit 2/9 arrived a little late, our sample period starts before the bit phase so we include some of the last bit)
+
+    uint32_t onTime = (pPayload[0] + pPayload[2] + pPayload[7] + pPayload[9]);
+    uint32_t early = (pPayload[-1] + pPayload[6]) << 1;
+    uint32_t late = (pPayload[3] + pPayload[10]) << 1;
+
+    if (onTime == 0 && early == 0 && late == 0) {
+        // Blah, can't do anything with this, avoid a divide-by-zero
+        return;
+    }
+
+    if (early > late) {
+        // Our sample period starts late and so includes some of the next bit.
+
+        uint16_t scaleUp = 16384 + 16384 * early / (early + onTime);   // 1 + early / (early+onTime)
+        uint16_t scaleDown = 16384 - 16384 * early / (early + onTime); // 1 - early / (early+onTime)
+
+        // trailing bits are 0; final data sample will be a bit low.
+        pPayload[MODES_PREAMBLE_SAMPLES + MODES_LONG_MSG_SAMPLES - 1] =
+            clamped_scale(pPayload[MODES_PREAMBLE_SAMPLES + MODES_LONG_MSG_SAMPLES - 1],  scaleUp);
+        for (j = MODES_PREAMBLE_SAMPLES + MODES_LONG_MSG_SAMPLES - 2; j > MODES_PREAMBLE_SAMPLES; j -= 2) {
+            if (pPayload[j] > pPayload[j+1]) {
+                // x [1 0] y
+                // x overlapped with the "1" bit and is slightly high
+                pPayload[j-1] = clamped_scale(pPayload[j-1], scaleDown);
+            } else {
+                // x [0 1] y
+                // x overlapped with the "0" bit and is slightly low
+                pPayload[j-1] = clamped_scale(pPayload[j-1], scaleUp);
+            }
+        }
+    } else {
+        // Our sample period starts early and so includes some of the previous bit.
+
+        uint16_t scaleUp = 16384 + 16384 * late / (late + onTime);   // 1 + late / (late+onTime)
+        uint16_t scaleDown = 16384 - 16384 * late / (late + onTime); // 1 - late / (late+onTime)
+
+        // leading bits are 0; first data sample will be a bit low.
+        pPayload[MODES_PREAMBLE_SAMPLES] = clamped_scale(pPayload[MODES_PREAMBLE_SAMPLES], scaleUp);
+        for (j = MODES_PREAMBLE_SAMPLES; j < MODES_PREAMBLE_SAMPLES + MODES_LONG_MSG_SAMPLES - 2; j += 2) {
+            if (pPayload[j] > pPayload[j+1]) {
+                // x [1 0] y
+                // y overlapped with the "0" bit and is slightly low
+                pPayload[j+2] = clamped_scale(pPayload[j+2], scaleUp);
+            } else {
+                // x [0 1] y
+                // y overlapped with the "1" bit and is slightly high
+                pPayload[j+2] = clamped_scale(pPayload[j+2], scaleDown);
+            }
+        }
+    }
+}
+//
+//=========================================================================
+//
+// Detect a Mode S messages inside the magnitude buffer pointed by 'm' and of
+// size 'mlen' bytes. Every detected Mode S message is convert it into a
+// stream of bits and passed to the function to display it.
+//
+void demodulate2000(uint16_t *m, uint32_t mlen) {
+    struct modesMessage mm;
+    unsigned char msg[MODES_LONG_MSG_BYTES], *pMsg;
+    uint16_t aux[MODES_PREAMBLE_SAMPLES+MODES_LONG_MSG_SAMPLES+1];
+    uint32_t j;
+    int use_correction = 0;
+
+    memset(&mm, 0, sizeof(mm));
+
+    // The Mode S preamble is made of impulses of 0.5 microseconds at
+    // the following time offsets:
+    //
+    // 0   - 0.5 usec: first impulse.
+    // 1.0 - 1.5 usec: second impulse.
+    // 3.5 - 4   usec: third impulse.
+    // 4.5 - 5   usec: last impulse.
+    // 
+    // Since we are sampling at 2 Mhz every sample in our magnitude vector
+    // is 0.5 usec, so the preamble will look like this, assuming there is
+    // an impulse at offset 0 in the array:
+    //
+    // 0   -----------------
+    // 1   -
+    // 2   ------------------
+    // 3   --
+    // 4   -
+    // 5   --
+    // 6   -
+    // 7   ------------------
+    // 8   --
+    // 9   -------------------
+    //
+    for (j = 0; j < mlen; j++) {
+        int high, i, errors, errors56, errorsTy; 
+        uint16_t *pPreamble, *pPayload, *pPtr;
+        uint8_t  theByte, theErrs;
+        int msglen, scanlen;
+        uint32_t sigLevel, noiseLevel;
+        uint16_t snr;
+        int message_ok;
+
+        pPreamble = &m[j];
+        pPayload  = &m[j+MODES_PREAMBLE_SAMPLES];
+
+        // Rather than clear the whole mm structure, just clear the parts which are required. The clear
+        // is required for every bit of the input stream, and we don't want to be memset-ing the whole
+        // modesMessage structure two million times per second if we don't have to..
+        mm.bFlags          =
+        mm.correctedbits   = 0;
+
+        if (!use_correction)  // This is not a re-try with phase correction
+            {                 // so try to find a new preamble
+
+            if (Modes.mode_ac) 
+                {
+                int ModeA = detectModeA(pPreamble, &mm);
+
+                if (ModeA) // We have found a valid ModeA/C in the data                    
+                    {
+                    mm.timestampMsg = Modes.timestampBlk + ((j+1) * 6);
+
+                    // compute message receive time as block-start-time + difference in the 12MHz clock
+                    mm.sysTimestampMsg = Modes.stSystemTimeBlk; // end of block time
+                    mm.sysTimestampMsg.tv_nsec -= receiveclock_ns_elapsed(mm.timestampMsg, Modes.timestampBlk + MODES_ASYNC_BUF_SAMPLES * 6); // time until end of block
+                    normalize_timespec(&mm.sysTimestampMsg);
+
+                    // Decode the received message
+                    decodeModeAMessage(&mm, ModeA);
+
+                    // Pass data to the next layer
+                    useModesMessage(&mm);
+
+                    j += MODEAC_MSG_SAMPLES;
+                    Modes.stats_current.demod_modeac++;
+                    continue;
+                    }
+                }
+
+            // First check of relations between the first 10 samples
+            // representing a valid preamble. We don't even investigate further
+            // if this simple test is not passed
+            if (!(pPreamble[0] > pPreamble[1] &&
+                  pPreamble[1] < pPreamble[2] &&
+                  pPreamble[2] > pPreamble[3] &&
+                  pPreamble[3] < pPreamble[0] &&
+                  pPreamble[4] < pPreamble[0] &&
+                  pPreamble[5] < pPreamble[0] &&
+                  pPreamble[6] < pPreamble[0] &&
+                  pPreamble[7] > pPreamble[8] &&
+                  pPreamble[8] < pPreamble[9] &&
+                  pPreamble[9] > pPreamble[6]))
+            {
+                if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
+                    *pPreamble  > MODES_DEBUG_NOPREAMBLE_LEVEL)
+                    dumpRawMessage("Unexpected ratio among first 10 samples", msg, m, j);
+                continue;
+            }
+
+            // The samples between the two spikes must be < than the average
+            // of the high spikes level. We don't test bits too near to
+            // the high levels as signals can be out of phase so part of the
+            // energy can be in the near samples
+            high = (pPreamble[0] + pPreamble[2] + pPreamble[7] + pPreamble[9]) / 6;
+            if (pPreamble[4] >= high ||
+                pPreamble[5] >= high)
+            {
+                if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
+                    *pPreamble  > MODES_DEBUG_NOPREAMBLE_LEVEL)
+                    dumpRawMessage("Too high level in samples between 3 and 6", msg, m, j);
+                continue;
+            }
+
+            // Similarly samples in the range 11-14 must be low, as it is the
+            // space between the preamble and real data. Again we don't test
+            // bits too near to high levels, see above
+            if (pPreamble[11] >= high ||
+                pPreamble[12] >= high ||
+                pPreamble[13] >= high ||
+                pPreamble[14] >= high)
+            {
+                if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
+                    *pPreamble  > MODES_DEBUG_NOPREAMBLE_LEVEL)
+                    dumpRawMessage("Too high level in samples between 10 and 15", msg, m, j);
+                continue;
+            }
+            Modes.stats_current.demod_preambles++;
+        } 
+
+        else {
+            // If the previous attempt with this message failed, retry using
+            // magnitude correction
+            // Make a copy of the Payload, and phase correct the copy
+            memcpy(aux, &pPreamble[-1], sizeof(aux));
+            applyPhaseCorrection(&aux[1]);
+            pPayload = &aux[1 + MODES_PREAMBLE_SAMPLES];
+            // TODO ... apply other kind of corrections
+            }
+
+        // Decode all the next 112 bits, regardless of the actual message
+        // size. We'll check the actual message type later
+        pMsg    = &msg[0];
+        pPtr    = pPayload;
+        theByte = 0;
+        theErrs = 0; errorsTy = 0;
+        errors  = 0; errors56 = 0;
+
+        // We should have 4 'bits' of 0/1 and 1/0 samples in the preamble, 
+        // so include these in the signal strength 
+        sigLevel = pPreamble[0] + pPreamble[2] + pPreamble[7] + pPreamble[9];
+        noiseLevel = pPreamble[1] + pPreamble[3] + pPreamble[4] + pPreamble[6] + pPreamble[8];
+
+        msglen = scanlen = MODES_LONG_MSG_BITS;
+        for (i = 0; i < scanlen; i++) {
+            uint32_t a = *pPtr++;
+            uint32_t b = *pPtr++;
+
+            if      (a > b) 
+                {theByte |= 1; if (i < 56) { sigLevel += a; noiseLevel += b; }}
+            else if (a < b) 
+                {/*theByte |= 0;*/ if (i < 56) { sigLevel += b; noiseLevel += a; }}
+            else {
+                if (i < 56) { sigLevel += a; noiseLevel += a; }
+                if (i >= MODES_SHORT_MSG_BITS) //(a == b), and we're in the long part of a frame
+                    {errors++;  /*theByte |= 0;*/}
+                else if (i >= 5)                    //(a == b), and we're in the short part of a frame
+                    {scanlen = MODES_LONG_MSG_BITS; errors56 = ++errors;/*theByte |= 0;*/}
+                else if (i)                         //(a == b), and we're in the message type part of a frame
+                    {errorsTy = errors56 = ++errors; theErrs |= 1; /*theByte |= 0;*/}
+                else                                //(a == b), and we're in the first bit of the message type part of a frame
+                    {errorsTy = errors56 = ++errors; theErrs |= 1; theByte |= 1;}
+            }
+
+            if ((i & 7) == 7) 
+              {*pMsg++ = theByte;}
+            else if (i == 4) {
+              msglen  = modesMessageLenByType(theByte);
+              if (errors == 0)
+                  {scanlen = msglen;}
+            }
+
+            theByte = theByte << 1;
+            if (i < 7)
+              {theErrs = theErrs << 1;}
+
+            // If we've exceeded the permissible number of encoding errors, abandon ship now
+            if (errors > MODES_MSG_ENCODER_ERRS) {
+
+                if        (i < MODES_SHORT_MSG_BITS) {
+                    msglen = 0;
+
+                } else if ((errorsTy == 1) && (theErrs == 0x80)) {
+                    // If we only saw one error in the first bit of the byte of the frame, then it's possible 
+                    // we guessed wrongly about the value of the bit. We may be able to correct it by guessing
+                    // the other way.
+                    //
+                    // We guessed a '1' at bit 7, which is the DF length bit == 112 Bits.
+                    // Inverting bit 7 will change the message type from a long to a short. 
+                    // Invert the bit, cross your fingers and carry on.
+                    msglen  = MODES_SHORT_MSG_BITS;
+                    msg[0] ^= theErrs; errorsTy = 0;
+                    errors  = errors56; // revert to the number of errors prior to bit 56
+
+                } else if (i < MODES_LONG_MSG_BITS) {
+                    msglen = MODES_SHORT_MSG_BITS;
+                    errors = errors56;
+
+                } else {
+                    msglen = MODES_LONG_MSG_BITS;
+                }
+
+            break;
+            }
+        }
+
+        // Ensure msglen is consistent with the DF type
+        if (msglen > 0) {
+            i = modesMessageLenByType(msg[0] >> 3);
+            if      (msglen > i) {msglen = i;}
+            else if (msglen < i) {msglen = 0;}
+        }
+
+        //
+        // If we guessed at any of the bits in the DF type field, then look to see if our guess was sensible.
+        // Do this by looking to see if the original guess results in the DF type being one of the ICAO defined
+        // message types. If it isn't then toggle the guessed bit and see if this new value is ICAO defined.
+        // if the new value is ICAO defined, then update it in our message.
+        if ((msglen) && (errorsTy == 1) && (theErrs & 0x78)) {
+            // We guessed at one (and only one) of the message type bits. See if our guess is "likely" 
+            // to be correct by comparing the DF against a list of known good DF's
+            int      thisDF      = ((theByte = msg[0]) >> 3) & 0x1f;
+            uint32_t validDFbits = 0x017F0831;   // One bit per 32 possible DF's. Set bits 0,4,5,11,16.17.18.19,20,21,22,24
+            uint32_t thisDFbit   = (1 << thisDF);
+            if (0 == (validDFbits & thisDFbit)) {
+                // The current DF is not ICAO defined, so is probably an errors. 
+                // Toggle the bit we guessed at and see if the resultant DF is more likely
+                theByte  ^= theErrs;
+                thisDF    = (theByte >> 3) & 0x1f;
+                thisDFbit = (1 << thisDF);
+                // if this DF any more likely?
+                if (validDFbits & thisDFbit) {
+                    // Yep, more likely, so update the main message 
+                    msg[0] = theByte;
+                    errors--; // decrease the error count so we attempt to use the modified DF.
+                }
+            }
+        }
+
+        // snr = 5 * 20log10(sigLevel / noiseLevel)         (in units of 0.2dB)
+        //     = 100log10(sigLevel) - 100log10(noiseLevel)
+
+        while (sigLevel > 65535 || noiseLevel > 65535) {
+            sigLevel >>= 1;
+            noiseLevel >>= 1;
+        }
+        snr = Modes.log10lut[sigLevel] - Modes.log10lut[noiseLevel];
+
+        // When we reach this point, if error is small, and the signal strength is large enough
+        // we may have a Mode S message on our hands. It may still be broken and the CRC may not 
+        // be correct, but this can be handled by the next layer.
+        if ( (msglen) 
+          && ((2 * snr) > (int) (MODES_MSG_SQUELCH_DB * 10))
+          && (errors      <= MODES_MSG_ENCODER_ERRS) ) {
+            int result;
+
+            // Set initial mm structure details
+            mm.timestampMsg = Modes.timestampBlk + (j*6);
+
+            // compute message receive time as block-start-time + difference in the 12MHz clock
+            mm.sysTimestampMsg = Modes.stSystemTimeBlk; // end of block time
+            mm.sysTimestampMsg.tv_nsec -= receiveclock_ns_elapsed(mm.timestampMsg, Modes.timestampBlk + MODES_ASYNC_BUF_SAMPLES * 6); // time until end of block
+            normalize_timespec(&mm.sysTimestampMsg);
+
+            mm.signalLevel = (365.0*60 + sigLevel + noiseLevel) * (365.0*60 + sigLevel + noiseLevel) / MAX_POWER / 60 / 60;
+
+            // Decode the received message
+            result = decodeModesMessage(&mm, msg);
+            if (result < 0) {
+                message_ok = 0;
+                if (result == -1)
+                    Modes.stats_current.demod_rejected_unknown_icao++;
+                else
+                    Modes.stats_current.demod_rejected_bad++;
+            } else {
+                message_ok = 1;
+                Modes.stats_current.demod_accepted[mm.correctedbits]++;
+            }
+
+            // Update statistics
+
+            // Output debug mode info if needed
+            if (use_correction) {
+                if (Modes.debug & MODES_DEBUG_DEMOD)
+                    dumpRawMessage("Demodulated with 0 errors", msg, m, j);
+                else if (Modes.debug & MODES_DEBUG_BADCRC &&
+                         mm.msgtype == 17 &&
+                         (!message_ok || mm.correctedbits > 0))
+                    dumpRawMessage("Decoded with bad CRC", msg, m, j);
+                else if (Modes.debug & MODES_DEBUG_GOODCRC &&
+                         message_ok && 
+                         mm.correctedbits == 0)
+                    dumpRawMessage("Decoded with good CRC", msg, m, j);
+            }
+
+            // Skip this message if we are sure it's fine
+            if (message_ok) {
+                j += (MODES_PREAMBLE_US+msglen)*2 - 1;
+
+                // Pass data to the next layer
+                useModesMessage(&mm);
+            }
+        } else {
+            message_ok = 0;
+            if (Modes.debug & MODES_DEBUG_DEMODERR && use_correction) {
+                printf("The following message has %d demod errors\n", errors);
+                dumpRawMessage("Demodulated with errors", msg, m, j);
+            }
+        }
+
+        // Retry with phase correction if enabled, necessary and possible.
+        if (Modes.phase_enhance && (!message_ok || mm.correctedbits > 0) && !use_correction && j && detectOutOfPhase(pPreamble)) {
+            use_correction = 1; j--;
+        } else {
+            use_correction = 0; 
+        }
+    }
+}
+
diff --git a/demod_2000.h b/demod_2000.h
new file mode 100644
index 0000000..b2029a5
--- /dev/null
+++ b/demod_2000.h
@@ -0,0 +1,27 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// demod_2000.h: 2MHz Mode S demodulator prototypes.
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#ifndef DUMP1090_DEMOD_2000_H
+#define DUMP1090_DEMOD_2000_H
+
+#include <stdint.h>
+
+void demodulate2000(uint16_t *m, uint32_t mlen);
+
+#endif
diff --git a/demod_2400.c b/demod_2400.c
new file mode 100644
index 0000000..0cd844a
--- /dev/null
+++ b/demod_2400.c
@@ -0,0 +1,494 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// demod_2400.c: 2.4MHz Mode S demodulator.
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#include "dump1090.h"
+
+//
+// Measuring the noise power is actually surprisingly expensive on an ARM -
+// it increases the CPU use of the demodulator by 1/3. So it's off by default.
+// You can turn it back on here:
+#undef MEASURE_NOISE
+
+// 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)
+
+static inline int slice_phase0(uint16_t *m) {
+    return 5 * m[0] - 3 * m[1] - 2 * m[2];
+}
+static inline int slice_phase1(uint16_t *m) {
+    return 4 * m[0] - m[1] - 3 * m[2];
+}
+static inline int slice_phase2(uint16_t *m) {
+    return 3 * m[0] + m[1] - 4 * m[2];
+}
+static inline int slice_phase3(uint16_t *m) {
+    return 2 * m[0] + 3 * m[1] - 5 * m[2];
+}
+static inline int slice_phase4(uint16_t *m) {
+    return m[0] + 5 * m[1] - 5 * m[2] - m[3];
+}
+
+static inline int correlate_phase0(uint16_t *m) {
+    return slice_phase0(m) * 26;
+}
+static inline int correlate_phase1(uint16_t *m) {
+    return slice_phase1(m) * 38;
+}
+static inline int correlate_phase2(uint16_t *m) {
+    return slice_phase2(m) * 38;
+}
+static inline int correlate_phase3(uint16_t *m) {
+    return slice_phase3(m) * 26;
+}
+static inline int correlate_phase4(uint16_t *m) {
+    return slice_phase4(m) * 19;
+}
+
+//
+// These functions work out the correlation quality for the 10 symbols (5 bits) starting at m[0] + given phase offset.
+// This is used to find the right phase offset to use for decoding.
+//
+
+static inline int correlate_check_0(uint16_t *m) {
+    return
+        abs(correlate_phase0(&m[0])) +
+        abs(correlate_phase2(&m[2])) +
+        abs(correlate_phase4(&m[4])) +
+        abs(correlate_phase1(&m[7])) +
+        abs(correlate_phase3(&m[9]));
+}
+
+static inline int correlate_check_1(uint16_t *m) {
+    return
+        abs(correlate_phase1(&m[0])) +
+        abs(correlate_phase3(&m[2])) +
+        abs(correlate_phase0(&m[5])) +
+        abs(correlate_phase2(&m[7])) +
+        abs(correlate_phase4(&m[9]));
+}
+
+static inline int correlate_check_2(uint16_t *m) {
+    return
+        abs(correlate_phase2(&m[0])) +
+        abs(correlate_phase4(&m[2])) +
+        abs(correlate_phase1(&m[5])) +
+        abs(correlate_phase3(&m[7])) +
+        abs(correlate_phase0(&m[10]));
+}
+
+static inline int correlate_check_3(uint16_t *m) {
+    return
+        abs(correlate_phase3(&m[0])) +
+        abs(correlate_phase0(&m[3])) +
+        abs(correlate_phase2(&m[5])) +
+        abs(correlate_phase4(&m[7])) +
+        abs(correlate_phase1(&m[10]));
+}
+
+static inline int correlate_check_4(uint16_t *m) {
+    return
+        abs(correlate_phase4(&m[0])) +
+        abs(correlate_phase1(&m[3])) +
+        abs(correlate_phase3(&m[5])) +
+        abs(correlate_phase0(&m[8])) +
+        abs(correlate_phase2(&m[10]));
+}
+
+// Work out the best phase offset to use for the given message.
+static int best_phase(uint16_t *m) {
+    int test;
+    int best = -1;
+    int bestval = (m[0] + m[1] + m[2] + m[3] + m[4] + m[5]); // minimum correlation quality we will accept
+
+    // empirical testing suggests that 4..8 is the best range to test for here
+    // (testing a wider range runs the danger of picking the wrong phase for
+    // a message that would otherwise be successfully decoded - the correlation
+    // functions can match well with a one symbol / half bit offset)
+
+    // this is consistent with the peak detection which should produce
+    // the first data symbol with phase offset 4..8
+
+    test = correlate_check_4(&m[0]);
+    if (test > bestval) { bestval = test; best = 4; }
+    test = correlate_check_0(&m[1]);
+    if (test > bestval) { bestval = test; best = 5; }
+    test = correlate_check_1(&m[1]);
+    if (test > bestval) { bestval = test; best = 6; }
+    test = correlate_check_2(&m[1]);
+    if (test > bestval) { bestval = test; best = 7; }
+    test = correlate_check_3(&m[1]);
+    if (test > bestval) { bestval = test; best = 8; }
+    return best;
+}
+
+//
+// Given 'mlen' magnitude samples in 'm', sampled at 2.4MHz,
+// try to demodulate some Mode S messages.
+//
+void demodulate2400(uint16_t *m, uint32_t mlen) {
+    struct modesMessage mm;
+    unsigned char msg1[MODES_LONG_MSG_BYTES], msg2[MODES_LONG_MSG_BYTES], *msg;
+    uint32_t j;
+#ifdef MEASURE_NOISE
+    uint32_t last_message_end = 0;
+#endif
+
+    unsigned char *bestmsg;
+    int bestscore, bestphase;
+
+#ifdef MEASURE_NOISE
+    // noise floor:
+    uint32_t noise_power_count = 0;
+    uint64_t noise_power_sum = 0;
+#endif
+
+    memset(&mm, 0, sizeof(mm));
+    msg = msg1;
+
+    for (j = 0; j < mlen; j++) {
+        uint16_t *preamble = &m[j];
+        int high;
+        uint32_t base_signal, base_noise;
+        int initial_phase, first_phase, last_phase, try_phase;
+        int msglen;
+
+#ifdef MEASURE_NOISE
+        // update noise for all samples that aren't part of a message
+        // (we don't know if m[j] is or not, yet, so work one sample
+        // in arrears)
+        if (j > last_message_end+1) {
+            // There seems to be a weird compiler bug I'm hitting here..
+            // if you compute the square directly, it occasionally gets mangled.
+            uint64_t s = TRUE_AMPLITUDE(m[j-1]);
+            noise_power_sum += s * s;
+            noise_power_count++;
+        }
+#endif
+
+        // 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
+        //
+        
+        // quick check: we must have a rising edge 0->1 and a falling edge 12->13
+        if (! (preamble[0] < preamble[1] && preamble[12] > preamble[13]) )
+           continue;
+
+        if (preamble[1] > preamble[2] &&                                       // 1
+            preamble[2] < preamble[3] && preamble[3] > preamble[4] &&          // 3
+            preamble[8] < preamble[9] && preamble[9] > preamble[10] &&         // 9
+            preamble[10] < preamble[11]) {                                     // 11-12
+            // peaks at 1,3,9,11-12: phase 3
+            high = (preamble[1] + preamble[3] + preamble[9] + preamble[11] + preamble[12]) / 4;
+            base_signal = preamble[1] + preamble[3] + preamble[9];
+            base_noise = preamble[5] + preamble[6] + preamble[7];
+        } else if (preamble[1] > preamble[2] &&                                // 1
+                   preamble[2] < preamble[3] && preamble[3] > preamble[4] &&   // 3
+                   preamble[8] < preamble[9] && preamble[9] > preamble[10] &&  // 9
+                   preamble[11] < preamble[12]) {                              // 12
+            // peaks at 1,3,9,12: phase 4
+            high = (preamble[1] + preamble[3] + preamble[9] + preamble[12]) / 4;
+            base_signal = preamble[1] + preamble[3] + preamble[9] + preamble[12];
+            base_noise = preamble[5] + preamble[6] + preamble[7] + preamble[8];
+        } else if (preamble[1] > preamble[2] &&                                // 1
+                   preamble[2] < preamble[3] && preamble[4] > preamble[5] &&   // 3-4
+                   preamble[8] < preamble[9] && preamble[10] > preamble[11] && // 9-10
+                   preamble[11] < preamble[12]) {                              // 12
+            // peaks at 1,3-4,9-10,12: phase 5
+            high = (preamble[1] + preamble[3] + preamble[4] + preamble[9] + preamble[10] + preamble[12]) / 4;
+            base_signal = preamble[1] + preamble[12];
+            base_noise = preamble[6] + preamble[7];
+        } else if (preamble[1] > preamble[2] &&                                 // 1
+                   preamble[3] < preamble[4] && preamble[4] > preamble[5] &&    // 4
+                   preamble[9] < preamble[10] && preamble[10] > preamble[11] && // 10
+                   preamble[11] < preamble[12]) {                               // 12
+            // peaks at 1,4,10,12: phase 6
+            high = (preamble[1] + preamble[4] + preamble[10] + preamble[12]) / 4;
+            base_signal = preamble[1] + preamble[4] + preamble[10] + preamble[12];
+            base_noise = preamble[5] + preamble[6] + preamble[7] + preamble[8];
+        } else if (preamble[2] > preamble[3] &&                                 // 1-2
+                   preamble[3] < preamble[4] && preamble[4] > preamble[5] &&    // 4
+                   preamble[9] < preamble[10] && preamble[10] > preamble[11] && // 10
+                   preamble[11] < preamble[12]) {                               // 12
+            // peaks at 1-2,4,10,12: phase 7
+            high = (preamble[1] + preamble[2] + preamble[4] + preamble[10] + preamble[12]) / 4;
+            base_signal = preamble[4] + preamble[10] + preamble[12];
+            base_noise = preamble[6] + preamble[7] + preamble[8];
+        } else {
+            // no suitable peaks
+            continue;
+        }
+
+        // Check for enough signal
+        if (base_signal * 2 < 3 * base_noise) // about 3.5dB SNR
+            continue;
+
+        // Check that the "quiet" bits 6,7,15,16,17 are actually quiet
+        if (preamble[5] >= high ||
+            preamble[6] >= high ||
+            preamble[7] >= high ||
+            preamble[8] >= high ||
+            preamble[14] >= high ||
+            preamble[15] >= high ||
+            preamble[16] >= high ||
+            preamble[17] >= high ||
+            preamble[18] >= high) {
+            continue;
+        }
+
+        if (Modes.phase_enhance) {
+            first_phase = 4;
+            last_phase = 8;           // try all phases
+        } else {
+            // Crosscorrelate against the first few bits to find a likely phase offset
+            initial_phase = best_phase(&preamble[19]);
+            if (initial_phase < 0) {
+                continue; // nothing satisfactory
+            }
+            
+            first_phase = last_phase = initial_phase;  // try only the phase we think it is
+        }
+
+        Modes.stats_current.demod_preambles++;
+        bestmsg = NULL; bestscore = -2; bestphase = -1;
+        for (try_phase = first_phase; try_phase <= last_phase; ++try_phase) {
+            uint16_t *pPtr;
+            int phase, i, score, bytelen;
+
+            // Decode all the next 112 bits, regardless of the actual message
+            // size. We'll check the actual message type later
+            
+            pPtr = &m[j+19] + (try_phase/5);
+            phase = try_phase % 5;
+
+            bytelen = MODES_LONG_MSG_BYTES;
+            for (i = 0; i < bytelen; ++i) {
+                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;
+                }
+
+                msg[i] = theByte;
+                if (i == 0) {
+                    switch (msg[0] >> 3) {
+                    case 0: case 4: case 5: case 11:
+                        bytelen = MODES_SHORT_MSG_BYTES; break;
+                        
+                    case 16: case 17: case 18: case 20: case 21: case 24:
+                        break;
+
+                    default:
+                        bytelen = 1; // unknown DF, give up immediately
+                        break;
+                    }
+                }
+            }
+
+            // Score the mode S message and see if it's any good.
+            score = scoreModesMessage(msg, i*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;
+            }
+        }
+
+        // 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(bestmsg[0] >> 3);
+
+        // Set initial mm structure details
+        mm.timestampMsg = Modes.timestampBlk + (j*5) + bestphase;
+
+        // compute message receive time as block-start-time + difference in the 12MHz clock
+        mm.sysTimestampMsg = Modes.stSystemTimeBlk; // end of block time
+        mm.sysTimestampMsg.tv_nsec -= receiveclock_ns_elapsed(mm.timestampMsg, Modes.timestampBlk + MODES_ASYNC_BUF_SAMPLES * 5); // time until end of block
+        normalize_timespec(&mm.sysTimestampMsg);
+
+        mm.score = bestscore;
+        mm.bFlags = mm.correctedbits   = 0;
+
+        // measure signal power
+        {
+            uint64_t signal_power_sum = 0;
+            double signal_power;
+            int signal_len = msglen*12/5 + 1;
+            int k;
+
+            for (k = 0; k < signal_len; ++k) {
+                uint64_t s = TRUE_AMPLITUDE(m[j+19+k]);
+                signal_power_sum += s * s;
+            }
+
+            mm.signalLevel = signal_power = signal_power_sum / MAX_POWER / signal_len;
+            Modes.stats_current.signal_power_sum += signal_power;
+            Modes.stats_current.signal_power_count ++;
+
+            if (signal_power > Modes.stats_current.peak_signal_power)
+                Modes.stats_current.peak_signal_power = signal_power;
+            if (signal_power > 0.50119)
+                Modes.stats_current.strong_signal_count++; // signal power above -3dBFS
+        }
+
+        // Decode the received message
+        {
+            int result = decodeModesMessage(&mm, bestmsg);
+            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]++;
+            }
+        }
+
+
+        // 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)
+#ifdef MEASURE_NOISE
+        last_message_end = j + (8 + msglen)*12/5;
+#endif
+        j += (8 + msglen - 8)*12/5 - 1;
+            
+        // Pass data to the next layer
+        useModesMessage(&mm);
+    }
+
+#ifdef MEASURE_NOISE
+    Modes.stats_current.noise_power_sum += (noise_power_sum / MAX_POWER / noise_power_count);
+    Modes.stats_current.noise_power_count ++;
+#endif
+}
+
diff --git a/demod_2400.h b/demod_2400.h
new file mode 100644
index 0000000..26cc8aa
--- /dev/null
+++ b/demod_2400.h
@@ -0,0 +1,27 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// demod_2400.h: 2.4MHz Mode S demodulator prototypes.
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#ifndef DUMP1090_DEMOD_2400_H
+#define DUMP1090_DEMOD_2400_H
+
+#include <stdint.h>
+
+void demodulate2400(uint16_t *m, uint32_t mlen);
+
+#endif
diff --git a/dump1090.c b/dump1090.c
new file mode 100644
index 0000000..1f5c324
--- /dev/null
+++ b/dump1090.c
@@ -0,0 +1,1122 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// dump1090.c: main program & miscellany
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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 "dump1090.h"
+
+#include <stdarg.h>
+
+static int verbose_device_search(char *s);
+
+//
+// ============================= Utility functions ==========================
+//
+
+static void log_with_timestamp(const char *format, ...) __attribute__((format (printf, 1, 2) ));
+
+static void log_with_timestamp(const char *format, ...)
+{
+    char timebuf[128];
+    char msg[1024];
+    time_t now;
+    struct tm local;
+    va_list ap;
+
+    now = time(NULL);
+    localtime_r(&now, &local);
+    strftime(timebuf, 128, "%c %Z", &local);
+    timebuf[127] = 0;
+
+    va_start(ap, format);
+    vsnprintf(msg, 1024, format, ap);
+    va_end(ap);
+    msg[1023] = 0;
+
+    fprintf(stderr, "%s  %s\n", timebuf, msg);
+}
+
+static void sigintHandler(int dummy) {
+    MODES_NOTUSED(dummy);
+    signal(SIGINT, SIG_DFL);  // reset signal handler - bit extra safety
+    Modes.exit = 1;           // Signal to threads that we are done
+    log_with_timestamp("Caught SIGINT, shutting down..\n");
+}
+
+static void sigtermHandler(int dummy) {
+    MODES_NOTUSED(dummy);
+    signal(SIGTERM, SIG_DFL); // reset signal handler - bit extra safety
+    Modes.exit = 1;           // Signal to threads that we are done
+    log_with_timestamp("Caught SIGTERM, shutting down..\n");
+}
+//
+// =============================== Terminal handling ========================
+//
+#ifndef _WIN32
+// Get the number of rows after the terminal changes size.
+int getTermRows() { 
+    struct winsize w; 
+    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); 
+    return (w.ws_row); 
+} 
+
+// Handle resizing terminal
+void sigWinchCallback() {
+    signal(SIGWINCH, SIG_IGN);
+    Modes.interactive_rows = getTermRows();
+    interactiveShowData();
+    signal(SIGWINCH, sigWinchCallback); 
+}
+#else 
+int getTermRows() { return MODES_INTERACTIVE_ROWS;}
+#endif
+
+static void start_cpu_timing(struct timespec *start_time)
+{
+    clock_gettime(CLOCK_THREAD_CPUTIME_ID, start_time);
+}
+
+static 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 - 1);
+    add_to->tv_nsec += (1000000000L + end_time.tv_nsec - start_time->tv_nsec);
+    add_to->tv_sec += add_to->tv_nsec / 1000000000L;
+    add_to->tv_nsec = add_to->tv_nsec % 1000000000L;
+}
+
+//
+// =============================== Initialization ===========================
+//
+void modesInitConfig(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.freq                    = MODES_DEFAULT_FREQ;
+    Modes.ppm_error               = MODES_DEFAULT_PPM;
+    Modes.check_crc               = 1;
+    Modes.net_heartbeat_interval  = MODES_NET_HEARTBEAT_INTERVAL;
+    Modes.net_output_sbs_port     = MODES_NET_OUTPUT_SBS_PORT;
+    Modes.net_output_raw_port     = MODES_NET_OUTPUT_RAW_PORT;
+    Modes.net_input_raw_port      = MODES_NET_INPUT_RAW_PORT;
+    Modes.net_output_beast_port   = MODES_NET_OUTPUT_BEAST_PORT;
+    Modes.net_input_beast_port    = MODES_NET_INPUT_BEAST_PORT;
+    Modes.net_http_port           = MODES_NET_HTTP_PORT;
+    Modes.net_fatsv_port          = MODES_NET_OUTPUT_FA_TSV_PORT;
+    Modes.interactive_rows        = getTermRows();
+    Modes.interactive_display_ttl = MODES_INTERACTIVE_DISPLAY_TTL;
+    Modes.json_interval           = 1000;
+    Modes.json_location_accuracy  = 1;
+    Modes.maxRange                = 1852 * 300; // 300NM default max range
+}
+//
+//=========================================================================
+//
+void modesInit(void) {
+    int i, q;
+
+    pthread_mutex_init(&Modes.data_mutex,NULL);
+    pthread_cond_init(&Modes.data_cond,NULL);
+
+    // Allocate the various buffers used by Modes
+    Modes.trailing_samples = (Modes.oversample ? (MODES_OS_PREAMBLE_SAMPLES + MODES_OS_LONG_MSG_SAMPLES) : (MODES_PREAMBLE_SAMPLES + MODES_LONG_MSG_SAMPLES)) + 16;
+    if ( ((Modes.pFileData  = (uint16_t *) malloc(MODES_ASYNC_BUF_SIZE)                                         ) == NULL) ||
+         ((Modes.magnitude  = (uint16_t *) calloc(MODES_ASYNC_BUF_SAMPLES+Modes.trailing_samples, 2)            ) == NULL) ||
+         ((Modes.maglut     = (uint16_t *) malloc(sizeof(uint16_t) * 256 * 256)                                 ) == NULL) ||
+         ((Modes.log10lut   = (uint16_t *) malloc(sizeof(uint16_t) * 256 * 256)                                 ) == NULL) )
+    {
+        fprintf(stderr, "Out of memory allocating data buffer.\n");
+        exit(1);
+    }
+
+    // Clear the buffers that have just been allocated, just in-case
+    memset(Modes.pFileData,127,   MODES_ASYNC_BUF_SIZE);
+
+    // 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) ) {
+        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 
+    // is at 0.0 Lon,so we must check for either fLat or fLon being non zero not both. 
+    // Testing the flag at runtime will be much quicker than ((fLon != 0.0) || (fLat != 0.0))
+    Modes.bUserFlags &= ~MODES_USER_LATLON_VALID;
+    if ((Modes.fUserLat != 0.0) || (Modes.fUserLon != 0.0)) {
+        Modes.bUserFlags |= MODES_USER_LATLON_VALID;
+    }
+
+    // Limit the maximum requested raw output size to less than one Ethernet Block 
+    if (Modes.net_output_flush_size > (MODES_OUT_FLUSH_SIZE))
+      {Modes.net_output_flush_size = MODES_OUT_FLUSH_SIZE;}
+    if (Modes.net_output_flush_interval > (MODES_OUT_FLUSH_INTERVAL))
+      {Modes.net_output_flush_interval = MODES_OUT_FLUSH_INTERVAL;}
+    if (Modes.net_sndbuf_size > (MODES_NET_SNDBUF_MAX))
+      {Modes.net_sndbuf_size = MODES_NET_SNDBUF_MAX;}
+
+    // Initialise the Block Timers to something half sensible
+    clock_gettime(CLOCK_REALTIME, &Modes.stSystemTimeBlk);
+    for (i = 0; i < MODES_ASYNC_BUF_NUMBER; i++)
+      {Modes.stSystemTimeRTL[i] = Modes.stSystemTimeBlk;}
+
+    // Each I and Q value varies from 0 to 255, which represents a range from -1 to +1. To get from the 
+    // unsigned (0-255) range you therefore subtract 127 (or 128 or 127.5) from each I and Q, giving you 
+    // a range from -127 to +128 (or -128 to +127, or -127.5 to +127.5)..
+    //
+    // To decode the AM signal, you need the magnitude of the waveform, which is given by sqrt((I^2)+(Q^2))
+    // The most this could be is if I&Q are both 128 (or 127 or 127.5), so you could end up with a magnitude 
+    // of 181.019 (or 179.605, or 180.312)
+    //
+    // However, in reality the magnitude of the signal should never exceed the range -1 to +1, because the 
+    // values are I = rCos(w) and Q = rSin(w). Therefore the integer computed magnitude should (can?) never 
+    // exceed 128 (or 127, or 127.5 or whatever)
+    //
+    // If we scale up the results so that they range from 0 to 65535 (16 bits) then we need to multiply 
+    // by 511.99, (or 516.02 or 514). antirez's original code multiplies by 360, presumably because he's 
+    // assuming the maximim calculated amplitude is 181.019, and (181.019 * 360) = 65166.
+    //
+    // So lets see if we can improve things by subtracting 127.5, Well in integer arithmatic we can't
+    // subtract half, so, we'll double everything up and subtract one, and then compensate for the doubling 
+    // in the multiplier at the end.
+    //
+    // If we do this we can never have I or Q equal to 0 - they can only be as small as +/- 1.
+    // This gives us a minimum magnitude of root 2 (0.707), so the dynamic range becomes (1.414-255). This 
+    // also affects our scaling value, which is now 65535/(255 - 1.414), or 258.433254
+    //
+    // The sums then become mag = 258.433254 * (sqrt((I*2-255)^2 + (Q*2-255)^2) - 1.414)
+    //                   or mag = (258.433254 * sqrt((I*2-255)^2 + (Q*2-255)^2)) - 365.4798
+    //
+    // We also need to clip mag just incaes any rogue I/Q values somehow do have a magnitude greater than 255.
+    //
+
+    for (i = 0; i <= 255; i++) {
+        for (q = 0; q <= 255; q++) {
+            int mag, mag_i, mag_q;
+
+            mag_i = (i * 2) - 255;
+            mag_q = (q * 2) - 255;
+
+            mag = (int) round((sqrt((mag_i*mag_i)+(mag_q*mag_q)) * 258.433254) - 365.4798);
+
+            Modes.maglut[(i*256)+q] = (uint16_t) ((mag < 65535) ? mag : 65535);
+        }
+    }
+
+    // Prepare the log10 lookup table.
+    // This maps from a magnitude value x (scaled as above) to 100log10(x)
+    for (i = 0; i <= 65535; i++) {
+        int l10 = (int) round(100 * log10( (i + 365.4798) / 258.433254) );
+        Modes.log10lut[i] = (uint16_t) ((l10 < 65535 ? l10 : 65535));
+    }
+
+    // Prepare error correction tables
+    modesChecksumInit(Modes.nfix_crc);
+    icaoFilterInit();
+}
+//
+// =============================== RTLSDR handling ==========================
+//
+int modesInitRTLSDR(void) {
+    int j;
+    int device_count, dev_index = 0;
+    char vendor[256], product[256], serial[256];
+
+    if (Modes.dev_name) {
+        if ( (dev_index = verbose_device_search(Modes.dev_name)) < 0 )
+            return -1;
+    }
+
+    device_count = rtlsdr_get_device_count();
+    if (!device_count) {
+        fprintf(stderr, "No supported RTLSDR devices found.\n");
+        return -1;
+    }
+
+    fprintf(stderr, "Found %d device(s):\n", device_count);
+    for (j = 0; j < device_count; j++) {
+        rtlsdr_get_device_usb_strings(j, vendor, product, serial);
+        fprintf(stderr, "%d: %s, %s, SN: %s %s\n", j, vendor, product, serial,
+            (j == dev_index) ? "(currently selected)" : "");
+    }
+
+    if (rtlsdr_open(&Modes.dev, dev_index) < 0) {
+        fprintf(stderr, "Error opening the RTLSDR device: %s\n",
+            strerror(errno));
+        return -1;
+    }
+
+    // Set gain, frequency, sample rate, and reset the device
+    rtlsdr_set_tuner_gain_mode(Modes.dev,
+        (Modes.gain == MODES_AUTO_GAIN) ? 0 : 1);
+    if (Modes.gain != MODES_AUTO_GAIN) {
+        int *gains;
+        int numgains;
+
+        numgains = rtlsdr_get_tuner_gains(Modes.dev, NULL);
+        if (numgains <= 0) {
+            fprintf(stderr, "Error getting tuner gains\n");
+            return -1;
+        }
+
+        gains = malloc(numgains * sizeof(int));
+        if (rtlsdr_get_tuner_gains(Modes.dev, gains) != numgains) {
+            fprintf(stderr, "Error getting tuner gains\n");
+            free(gains);
+            return -1;
+        }
+        
+        if (Modes.gain == MODES_MAX_GAIN) {
+            int highest = -1;
+            int i;
+
+            for (i = 0; i < numgains; ++i) {
+                if (gains[i] > highest)
+                    highest = gains[i];
+            }
+
+            Modes.gain = highest;
+            fprintf(stderr, "Max available gain is: %.2f dB\n", Modes.gain/10.0);
+        } else {
+            int closest = -1;
+            int i;
+
+            for (i = 0; i < numgains; ++i) {
+                if (closest == -1 || abs(gains[i] - Modes.gain) < abs(closest - Modes.gain))
+                    closest = gains[i];
+            }
+
+            if (closest != Modes.gain) {
+                Modes.gain = closest;
+                fprintf(stderr, "Closest available gain: %.2f dB\n", Modes.gain/10.0);
+            }
+        }
+
+        free(gains);
+
+        fprintf(stderr, "Setting gain to: %.2f dB\n", Modes.gain/10.0);
+        if (rtlsdr_set_tuner_gain(Modes.dev, Modes.gain) < 0) {
+            fprintf(stderr, "Error setting tuner gains\n");
+            return -1;
+        }
+    } else {
+        fprintf(stderr, "Using automatic gain control.\n");
+    }
+    rtlsdr_set_freq_correction(Modes.dev, Modes.ppm_error);
+    if (Modes.enable_agc) rtlsdr_set_agc_mode(Modes.dev, 1);
+    rtlsdr_set_center_freq(Modes.dev, Modes.freq);
+    rtlsdr_set_sample_rate(Modes.dev, Modes.oversample ? MODES_OVERSAMPLE_RATE : MODES_DEFAULT_RATE);
+
+    rtlsdr_reset_buffer(Modes.dev);
+    fprintf(stderr, "Gain reported by device: %.2f dB\n",
+        rtlsdr_get_tuner_gain(Modes.dev)/10.0);
+
+    return 0;
+}
+//
+//=========================================================================
+//
+// We use a thread reading data in background, while the main thread
+// handles decoding and visualization of data to the user.
+//
+// The reading thread calls the RTLSDR API to read data asynchronously, and
+// uses a callback to populate the data buffer.
+//
+// A Mutex is used to avoid races with the decoding thread.
+//
+
+static struct timespec reader_thread_start;
+
+void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) {
+    MODES_NOTUSED(ctx);
+
+    // Lock the data buffer variables before accessing them
+    pthread_mutex_lock(&Modes.data_mutex);
+
+    if (Modes.exit) {
+        rtlsdr_cancel_async(Modes.dev); // ask our caller to exit
+    }
+
+    Modes.iDataIn &= (MODES_ASYNC_BUF_NUMBER-1); // Just incase!!!
+
+    // Get the system time for this block
+    clock_gettime(CLOCK_REALTIME, &Modes.stSystemTimeRTL[Modes.iDataIn]);
+
+    if (len > MODES_ASYNC_BUF_SIZE) {len = MODES_ASYNC_BUF_SIZE;}
+
+    // Queue the new data
+    Modes.pData[Modes.iDataIn] = (uint16_t *) buf;
+    Modes.iDataIn    = (MODES_ASYNC_BUF_NUMBER-1) & (Modes.iDataIn + 1);
+    Modes.iDataReady = (MODES_ASYNC_BUF_NUMBER-1) & (Modes.iDataIn - Modes.iDataOut);   
+
+    if (Modes.iDataReady == 0) {
+      // Ooooops. We've just received the MODES_ASYNC_BUF_NUMBER'th outstanding buffer
+      // This means that RTLSDR is currently overwriting the MODES_ASYNC_BUF_NUMBER+1
+      // buffer, but we havent yet processed it, so we're going to lose it. There
+      // isn't much we can do to recover the lost data, but we can correct things to
+      // avoid any additional problems.
+      Modes.iDataOut   = (MODES_ASYNC_BUF_NUMBER-1) & (Modes.iDataOut+1);
+      Modes.iDataReady = (MODES_ASYNC_BUF_NUMBER-1);   
+      Modes.iDataLost++;
+    }
+
+    // accumulate CPU while holding the mutex, and restart measurement
+    end_cpu_timing(&reader_thread_start, &Modes.reader_cpu_accumulator);
+    start_cpu_timing(&reader_thread_start);
+ 
+    // Signal to the other thread that new data is ready, and unlock
+    pthread_cond_signal(&Modes.data_cond);
+    pthread_mutex_unlock(&Modes.data_mutex);
+}
+//
+//=========================================================================
+//
+// This is used when --ifile is specified in order to read data from file
+// instead of using an RTLSDR device
+//
+void readDataFromFile(void) {
+    pthread_mutex_lock(&Modes.data_mutex);
+    while(Modes.exit == 0) {
+        ssize_t nread, toread;
+        unsigned char *p;
+
+        if (Modes.iDataReady) {
+            pthread_cond_wait(&Modes.data_cond, &Modes.data_mutex);
+            continue;
+        }
+
+        if (Modes.interactive) {
+            // When --ifile and --interactive are used together, slow down
+            // playing at the natural rate of the RTLSDR received.
+            pthread_mutex_unlock(&Modes.data_mutex);
+            usleep(64000);
+            pthread_mutex_lock(&Modes.data_mutex);
+        }
+
+        toread = MODES_ASYNC_BUF_SIZE;
+        p = (unsigned char *) Modes.pFileData;
+        while(toread) {
+            nread = read(Modes.fd, p, toread);
+            if (nread <= 0) {
+                // Done.
+                Modes.exit = 1; // Signal the other threads to exit.
+                goto OUT;
+            }
+            p += nread;
+            toread -= nread;
+        }
+        if (toread) {
+            // Not enough data on file to fill the buffer? Pad with no signal.
+            memset(p,127,toread);
+        }
+
+        Modes.iDataIn &= (MODES_ASYNC_BUF_NUMBER-1); // Just incase!!!
+
+        // Get the system time for this block
+        clock_gettime(CLOCK_REALTIME, &Modes.stSystemTimeRTL[Modes.iDataIn]);
+
+        // Queue the new data
+        Modes.pData[Modes.iDataIn] = Modes.pFileData;
+        Modes.iDataIn    = (MODES_ASYNC_BUF_NUMBER-1) & (Modes.iDataIn + 1);
+        Modes.iDataReady = (MODES_ASYNC_BUF_NUMBER-1) & (Modes.iDataIn - Modes.iDataOut);   
+
+        // accumulate CPU while holding the mutex, and restart measurement
+        end_cpu_timing(&reader_thread_start, &Modes.reader_cpu_accumulator);
+        start_cpu_timing(&reader_thread_start);
+
+        // Signal to the other thread that new data is ready
+        pthread_cond_signal(&Modes.data_cond);
+    }
+
+ OUT: 
+    pthread_mutex_unlock(&Modes.data_mutex);
+}
+//
+//=========================================================================
+//
+// We read data using a thread, so the main thread only handles decoding
+// without caring about data acquisition
+//
+
+void *readerThreadEntryPoint(void *arg) {
+    MODES_NOTUSED(arg);
+
+    start_cpu_timing(&reader_thread_start); // we accumulate in rtlsdrCallback() or readDataFromFile()
+
+    if (Modes.filename == NULL) {
+        while (!Modes.exit) {
+            rtlsdr_read_async(Modes.dev, rtlsdrCallback, NULL,
+                              MODES_ASYNC_BUF_NUMBER,
+                              MODES_ASYNC_BUF_SIZE);
+
+            if (!Modes.exit) {
+                log_with_timestamp("Warning: lost the connection to the RTLSDR device.");
+                rtlsdr_close(Modes.dev);
+                Modes.dev = NULL;
+
+                do {
+                    sleep(5);
+                    log_with_timestamp("Trying to reconnect to the RTLSDR device..");
+                } while (!Modes.exit && modesInitRTLSDR() < 0);
+            }
+        }
+
+        if (Modes.dev != NULL) {
+            rtlsdr_close(Modes.dev);
+            Modes.dev = NULL;
+        }
+    } else {
+        readDataFromFile();
+    }
+
+    // Wake the main thread (if it's still waiting)
+    pthread_mutex_lock(&Modes.data_mutex);
+    Modes.exit = 1; // just in case
+    pthread_cond_signal(&Modes.data_cond);
+    pthread_mutex_unlock(&Modes.data_mutex);
+
+#ifndef _WIN32
+    pthread_exit(NULL);
+#else
+    return NULL;
+#endif
+}
+//
+// ============================== 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
+//
+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);
+    }
+}
+//
+// ================================ Main ====================================
+//
+void showHelp(void) {
+    printf(
+"-----------------------------------------------------------------------------\n"
+"| dump1090 ModeS Receiver     %45s |\n"
+"-----------------------------------------------------------------------------\n"
+"--device-index <index>   Select RTL device (default: 0)\n"
+"--gain <db>              Set gain (default: max gain. Use -10 for auto-gain)\n"
+"--enable-agc             Enable the Automatic Gain Control (default: off)\n"
+"--freq <hz>              Set frequency (default: 1090 Mhz)\n"
+"--ifile <filename>       Read data from file (use '-' for stdin)\n"
+"--interactive            Interactive mode refreshing data on screen\n"
+"--interactive-rows <num> Max number of rows in interactive mode (default: 15)\n"
+"--interactive-ttl <sec>  Remove from list if idle for <sec> (default: 60)\n"
+"--interactive-rtl1090    Display flight table in RTL1090 format\n"
+"--raw                    Show only messages hex values\n"
+"--net                    Enable networking\n"
+"--modeac                 Enable decoding of SSR Modes 3/A & 3/C\n"
+"--net-only               Enable just networking, no RTL device or file used\n"
+"--net-bind-address <ip>  IP address to bind to (default: Any; Use 127.0.0.1 for private)\n"
+"--net-http-port <port>   HTTP server port (default: 8080)\n"
+"--net-ri-port <port>     TCP raw input listen port  (default: 30001)\n"
+"--net-ro-port <port>     TCP raw output listen port (default: 30002)\n"
+"--net-sbs-port <port>    TCP BaseStation output listen port (default: 30003)\n"
+"--net-bi-port <port>     TCP Beast input listen port  (default: 30004)\n"
+"--net-bo-port <port>     TCP Beast output listen port (default: 30005)\n"
+"--net-fatsv-port <port>  FlightAware TSV output port (default: 10001)\n"
+"--net-ro-size <size>     TCP output minimum size (default: 0)\n"
+"--net-ro-interval <rate> TCP output memory flush rate in seconds (default: 0)\n"
+"--net-heartbeat <rate>   TCP heartbeat rate in seconds (default: 60 sec; 0 to disable)\n"
+"--net-buffer <n>         TCP buffer size 64Kb * (2^n) (default: n=0, 64Kb)\n"
+"--net-verbatim           Do not apply CRC corrections to messages we forward; send unchanged\n"
+"--lat <latitude>         Reference/receiver latitude for surface posn (opt)\n"
+"--lon <longitude>        Reference/receiver longitude for surface posn (opt)\n"
+"--max-range <distance>   Absolute maximum range for position decoding (in nm, default: 300)\n"
+"--fix                    Enable single-bits error correction using CRC\n"
+"--no-fix                 Disable single-bits error correction using CRC\n"
+"--no-crc-check           Disable messages with broken CRC (discouraged)\n"
+"--phase-enhance          Enable phase enhancement\n"
+"--aggressive             More CPU for more messages (two bits fixes, ...)\n"
+"--mlat                   display raw messages in Beast ascii mode\n"
+"--stats                  With --ifile print stats at exit. No other output\n"
+"--stats-every <seconds>  Show and reset stats every <seconds> seconds\n"
+"--onlyaddr               Show only ICAO addresses (testing purposes)\n"
+"--metric                 Use metric units (meters, km/h, ...)\n"
+"--snip <level>           Strip IQ file removing samples < level\n"
+"--debug <flags>          Debug mode (verbose), see README for details\n"
+"--quiet                  Disable output to stdout. Use for daemon applications\n"
+"--ppm <error>            Set receiver error in parts per million (default 0)\n"
+"--no-decode              Don't decode the message contents beyond the minimum necessary\n"
+"--write-json <dir>       Periodically write json output to <dir> (for serving by a separate webserver)\n"
+"--write-json-every <t>   Write json output every t seconds (default 1)\n"
+"--json-location-accuracy <n>  Accuracy of receiver location in json metadata: 0=no location, 1=approximate, 2=exact\n"
+"--oversample             Enable oversampling at 2.4MHz\n"
+"--help                   Show this help\n"
+"\n"
+"Debug mode flags: d = Log frames decoded with errors\n"
+"                  D = Log frames decoded with zero errors\n"
+"                  c = Log frames with bad CRC\n"
+"                  C = Log frames with good CRC\n"
+"                  p = Log frames with bad preamble\n"
+"                  n = Log network debugging info\n"
+"                  j = Log frames to frames.js, loadable by debug.html\n",
+MODES_DUMP1090_VARIANT " " MODES_DUMP1090_VERSION
+    );
+}
+
+static void display_total_stats(void)
+{
+    struct stats added;
+    add_stats(&Modes.stats_alltime, &Modes.stats_current, &added);
+    display_stats(&added);
+}
+
+//
+//=========================================================================
+//
+// 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
+//
+void backgroundTasks(void) {
+    static uint64_t next_stats_display;
+    static uint64_t next_stats_update;
+    static uint64_t next_json, next_history;
+
+    uint64_t now = mstime();
+
+    icaoFilterExpire();
+    trackPeriodicUpdate();
+
+    if (Modes.net) {
+	modesNetPeriodicWork();
+    }    
+
+
+    // Refresh screen when in interactive mode
+    if (Modes.interactive) {
+        interactiveShowData();
+    }
+
+    // always update end time so it is current when requests arrive
+    Modes.stats_current.end = now;
+
+    if (now >= next_stats_update) {
+        int i;
+
+        if (next_stats_update == 0) {
+            next_stats_update = now + 60000;
+        } else {
+            Modes.stats_latest_1min = (Modes.stats_latest_1min + 1) % 15;
+            Modes.stats_1min[Modes.stats_latest_1min] = 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_5min);
+            for (i = 0; i < 5; ++i)
+                add_stats(&Modes.stats_1min[(Modes.stats_latest_1min - i + 15) % 15], &Modes.stats_5min, &Modes.stats_5min);
+            
+            reset_stats(&Modes.stats_15min);
+            for (i = 0; i < 15; ++i)
+                add_stats(&Modes.stats_1min[i], &Modes.stats_15min, &Modes.stats_15min);
+            
+            reset_stats(&Modes.stats_current);
+            Modes.stats_current.start = Modes.stats_current.end = now;
+            
+            if (Modes.json_dir)
+                writeJsonToFile("stats.json", generateStatsJson);
+
+            next_stats_update += 60000;
+        }
+    }
+
+    if (Modes.stats && now >= next_stats_display) {
+        if (next_stats_display == 0) {
+            next_stats_display = now + Modes.stats;
+        } else {
+            add_stats(&Modes.stats_periodic, &Modes.stats_current, &Modes.stats_periodic);
+            display_stats(&Modes.stats_periodic);
+            reset_stats(&Modes.stats_periodic);
+
+            next_stats_display += Modes.stats;
+        }
+    }
+
+    if (Modes.json_dir && now >= next_json) {
+        writeJsonToFile("aircraft.json", generateAircraftJson);
+        next_json = now + Modes.json_interval;
+    }
+
+    if ((Modes.json_dir || Modes.net_http_port) && now >= next_history) {
+        int rewrite_receiver_json = (Modes.json_aircraft_history[HISTORY_SIZE-1].content == NULL);
+
+        free(Modes.json_aircraft_history[Modes.json_aircraft_history_next].content); // might be NULL, that's OK.
+        Modes.json_aircraft_history[Modes.json_aircraft_history_next].content =
+            generateAircraftJson("/data/aircraft.json", &Modes.json_aircraft_history[Modes.json_aircraft_history_next].clen);
+
+        if (Modes.json_dir) {
+            char filebuf[PATH_MAX];
+            snprintf(filebuf, PATH_MAX, "history_%d.json", Modes.json_aircraft_history_next);
+            writeJsonToFile(filebuf, generateHistoryJson);
+        }
+
+        Modes.json_aircraft_history_next = (Modes.json_aircraft_history_next+1) % HISTORY_SIZE;
+
+        if (rewrite_receiver_json)
+            writeJsonToFile("receiver.json", generateReceiverJson); // number of history entries changed
+
+        next_history = now + HISTORY_INTERVAL;
+    }
+}
+
+//
+//=========================================================================
+//
+int verbose_device_search(char *s)
+{
+	int i, device_count, device, offset;
+	char *s2;
+	char vendor[256], product[256], serial[256];
+	device_count = rtlsdr_get_device_count();
+	if (!device_count) {
+		fprintf(stderr, "No supported devices found.\n");
+		return -1;
+	}
+	fprintf(stderr, "Found %d device(s):\n", device_count);
+	for (i = 0; i < device_count; i++) {
+		rtlsdr_get_device_usb_strings(i, vendor, product, serial);
+		fprintf(stderr, "  %d:  %s, %s, SN: %s\n", i, vendor, product, serial);
+	}
+	fprintf(stderr, "\n");
+	/* does string look like raw id number */
+	device = (int)strtol(s, &s2, 0);
+	if (s2[0] == '\0' && device >= 0 && device < device_count) {
+		fprintf(stderr, "Using device %d: %s\n",
+			device, rtlsdr_get_device_name((uint32_t)device));
+		return device;
+	}
+	/* does string exact match a serial */
+	for (i = 0; i < device_count; i++) {
+		rtlsdr_get_device_usb_strings(i, vendor, product, serial);
+		if (strcmp(s, serial) != 0) {
+			continue;}
+		device = i;
+		fprintf(stderr, "Using device %d: %s\n",
+			device, rtlsdr_get_device_name((uint32_t)device));
+		return device;
+	}
+	/* does string prefix match a serial */
+	for (i = 0; i < device_count; i++) {
+		rtlsdr_get_device_usb_strings(i, vendor, product, serial);
+		if (strncmp(s, serial, strlen(s)) != 0) {
+			continue;}
+		device = i;
+		fprintf(stderr, "Using device %d: %s\n",
+			device, rtlsdr_get_device_name((uint32_t)device));
+		return device;
+	}
+	/* does string suffix match a serial */
+	for (i = 0; i < device_count; i++) {
+		rtlsdr_get_device_usb_strings(i, vendor, product, serial);
+		offset = strlen(serial) - strlen(s);
+		if (offset < 0) {
+			continue;}
+		if (strncmp(s, serial+offset, strlen(s)) != 0) {
+			continue;}
+		device = i;
+		fprintf(stderr, "Using device %d: %s\n",
+			device, rtlsdr_get_device_name((uint32_t)device));
+		return device;
+	}
+	fprintf(stderr, "No matching devices found.\n");
+	return -1;
+}
+//
+//=========================================================================
+//
+int main(int argc, char **argv) {
+    int j;
+
+    // Set sane defaults
+    modesInitConfig();
+
+    // signal handlers:
+    signal(SIGINT, sigintHandler);
+    signal(SIGTERM, sigtermHandler);
+
+    // Parse the command line options
+    for (j = 1; j < argc; j++) {
+        int more = j+1 < argc; // There are more arguments
+
+        if (!strcmp(argv[j],"--device-index") && more) {
+            Modes.dev_name = strdup(argv[++j]);
+        } else if (!strcmp(argv[j],"--gain") && more) {
+            Modes.gain = (int) (atof(argv[++j])*10); // Gain is in tens of DBs
+        } else if (!strcmp(argv[j],"--enable-agc")) {
+            Modes.enable_agc++;
+        } else if (!strcmp(argv[j],"--freq") && more) {
+            Modes.freq = (int) strtoll(argv[++j],NULL,10);
+        } else if (!strcmp(argv[j],"--ifile") && more) {
+            Modes.filename = strdup(argv[++j]);
+        } else if (!strcmp(argv[j],"--fix")) {
+            Modes.nfix_crc = 1;
+        } else if (!strcmp(argv[j],"--no-fix")) {
+            Modes.nfix_crc = 0;
+        } else if (!strcmp(argv[j],"--no-crc-check")) {
+            Modes.check_crc = 0;
+        } else if (!strcmp(argv[j],"--phase-enhance")) {
+            Modes.phase_enhance = 1;
+        } else if (!strcmp(argv[j],"--raw")) {
+            Modes.raw = 1;
+        } else if (!strcmp(argv[j],"--net")) {
+            Modes.net = 1;
+        } else if (!strcmp(argv[j],"--modeac")) {
+            Modes.mode_ac = 1;
+        } else if (!strcmp(argv[j],"--net-beast")) {
+            Modes.beast = 1;
+        } else if (!strcmp(argv[j],"--net-only")) {
+            Modes.net = 1;
+            Modes.net_only = 1;
+       } else if (!strcmp(argv[j],"--net-heartbeat") && more) {
+            Modes.net_heartbeat_interval = (uint64_t)(1000 * atof(argv[++j]));
+       } else if (!strcmp(argv[j],"--net-ro-size") && more) {
+            Modes.net_output_flush_size = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--net-ro-rate") && more) {
+            Modes.net_output_flush_interval = 1000 * atoi(argv[++j]) / 15; // backwards compatibility
+        } else if (!strcmp(argv[j],"--net-ro-interval") && more) {
+            Modes.net_output_flush_interval = (uint64_t)(1000 * atof(argv[++j]));
+        } else if (!strcmp(argv[j],"--net-ro-port") && more) {
+            if (Modes.beast) // Required for legacy backward compatibility
+                {Modes.net_output_beast_port = atoi(argv[++j]);;}
+            else
+                {Modes.net_output_raw_port = atoi(argv[++j]);}
+        } else if (!strcmp(argv[j],"--net-ri-port") && more) {
+            Modes.net_input_raw_port = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--net-bo-port") && more) {
+            Modes.net_output_beast_port = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--net-bi-port") && more) {
+            Modes.net_input_beast_port = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--net-bind-address") && more) {
+            Modes.net_bind_address = strdup(argv[++j]);
+        } else if (!strcmp(argv[j],"--net-http-port") && more) {
+            Modes.net_http_port = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--net-fatsv-port") && more) {
+            Modes.net_fatsv_port = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--net-sbs-port") && more) {
+            Modes.net_output_sbs_port = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--net-buffer") && more) {
+            Modes.net_sndbuf_size = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--net-verbatim")) {
+            Modes.net_verbatim = 1;
+        } else if (!strcmp(argv[j],"--onlyaddr")) {
+            Modes.onlyaddr = 1;
+        } else if (!strcmp(argv[j],"--metric")) {
+            Modes.metric = 1;
+        } else if (!strcmp(argv[j],"--aggressive")) {
+            Modes.nfix_crc = MODES_MAX_BITERRORS;
+        } else if (!strcmp(argv[j],"--interactive")) {
+            Modes.interactive = 1;
+        } else if (!strcmp(argv[j],"--interactive-rows") && more) {
+            Modes.interactive_rows = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--interactive-ttl") && more) {
+            Modes.interactive_display_ttl = (uint64_t)(1000 * atof(argv[++j]));
+        } else if (!strcmp(argv[j],"--lat") && more) {
+            Modes.fUserLat = atof(argv[++j]);
+        } else if (!strcmp(argv[j],"--lon") && more) {
+            Modes.fUserLon = atof(argv[++j]);
+        } else if (!strcmp(argv[j],"--max-range") && more) {
+            Modes.maxRange = atof(argv[++j]) * 1852.0; // convert to metres
+        } else if (!strcmp(argv[j],"--debug") && more) {
+            char *f = argv[++j];
+            while(*f) {
+                switch(*f) {
+                case 'D': Modes.debug |= MODES_DEBUG_DEMOD; break;
+                case 'd': Modes.debug |= MODES_DEBUG_DEMODERR; break;
+                case 'C': Modes.debug |= MODES_DEBUG_GOODCRC; break;
+                case 'c': Modes.debug |= MODES_DEBUG_BADCRC; break;
+                case 'p': Modes.debug |= MODES_DEBUG_NOPREAMBLE; break;
+                case 'n': Modes.debug |= MODES_DEBUG_NET; break;
+                case 'j': Modes.debug |= MODES_DEBUG_JS; break;
+                default:
+                    fprintf(stderr, "Unknown debugging flag: %c\n", *f);
+                    exit(1);
+                    break;
+                }
+                f++;
+            }
+        } else if (!strcmp(argv[j],"--stats")) {
+            if (!Modes.stats)
+                Modes.stats = (uint64_t)1 << 60; // "never"
+        } else if (!strcmp(argv[j],"--stats-every") && more) {
+            Modes.stats = (uint64_t) (1000 * atof(argv[++j]));
+        } else if (!strcmp(argv[j],"--snip") && more) {
+            snipMode(atoi(argv[++j]));
+            exit(0);
+        } else if (!strcmp(argv[j],"--help")) {
+            showHelp();
+            exit(0);
+        } else if (!strcmp(argv[j],"--ppm") && more) {
+            Modes.ppm_error = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--quiet")) {
+            Modes.quiet = 1;
+        } else if (!strcmp(argv[j],"--mlat")) {
+            Modes.mlat = 1;
+        } else if (!strcmp(argv[j],"--interactive-rtl1090")) {
+            Modes.interactive = 1;
+            Modes.interactive_rtl1090 = 1;
+        } else if (!strcmp(argv[j],"--oversample")) {
+            Modes.oversample = 1;
+#ifndef _WIN32
+        } else if (!strcmp(argv[j], "--write-json") && more) {
+            Modes.json_dir = strdup(argv[++j]);
+        } else if (!strcmp(argv[j], "--write-json-every") && more) {
+            Modes.json_interval = (uint64_t)(1000 * atof(argv[++j]));
+            if (Modes.json_interval < 100) // 0.1s
+                Modes.json_interval = 100;
+        } else if (!strcmp(argv[j], "--json-location-accuracy") && more) {
+            Modes.json_location_accuracy = atoi(argv[++j]);
+#endif
+        } else {
+            fprintf(stderr,
+                "Unknown or not enough arguments for option '%s'.\n\n",
+                argv[j]);
+            showHelp();
+            exit(1);
+        }
+    }
+
+#ifdef _WIN32
+    // Try to comply with the Copyright license conditions for binary distribution
+    if (!Modes.quiet) {showCopyright();}
+#endif
+
+#ifndef _WIN32
+    // Setup for SIGWINCH for handling lines
+    if (Modes.interactive) {signal(SIGWINCH, sigWinchCallback);}
+#endif
+
+    if (Modes.mode_ac && Modes.oversample) {
+        fprintf(stderr,
+                "Warning: --modeac is currently ignored when --oversample is used;\n"
+                "         no ModeA/C messages will be decoded.\n");
+    }
+
+    // Initialization
+    log_with_timestamp("%s %s starting up.", MODES_DUMP1090_VARIANT, MODES_DUMP1090_VERSION);
+    modesInit();
+
+    if (Modes.net_only) {
+        fprintf(stderr,"Net-only mode, no RTL device or file open.\n");
+    } else if (Modes.filename == NULL) {
+        if (modesInitRTLSDR() < 0) {
+            exit(1);
+        }
+    } else {
+        if (Modes.filename[0] == '-' && Modes.filename[1] == '\0') {
+            Modes.fd = STDIN_FILENO;
+        } else if ((Modes.fd = open(Modes.filename,
+#ifdef _WIN32
+                                    (O_RDONLY | O_BINARY)
+#else
+                                    (O_RDONLY)
+#endif
+                                    )) == -1) {
+            perror("Opening data file");
+            exit(1);
+        }
+    }
+    if (Modes.net) modesInitNet();
+
+    // 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_5min.start = Modes.stats_5min.end =
+        Modes.stats_15min.start = Modes.stats_15min.end = mstime();
+
+    for (j = 0; j < 15; ++j)
+        Modes.stats_1min[j].start = Modes.stats_1min[j].end = Modes.stats_current.start;
+
+    // write initial json files so they're not missing
+    writeJsonToFile("receiver.json", generateReceiverJson);
+    writeJsonToFile("stats.json", generateStatsJson);
+    writeJsonToFile("aircraft.json", generateAircraftJson);
+
+    // If the user specifies --net-only, just run in order to serve network
+    // clients without reading data from the RTL device
+    if (Modes.net_only) {
+        while (!Modes.exit) {
+            struct timespec start_time;
+
+            start_cpu_timing(&start_time);
+            backgroundTasks();
+            end_cpu_timing(&start_time, &Modes.stats_current.background_cpu);
+
+            usleep(100000);
+        }
+    } else {
+        // Create the thread that will read the data from the device.
+        pthread_mutex_lock(&Modes.data_mutex);
+        pthread_create(&Modes.reader_thread, NULL, readerThreadEntryPoint, NULL);
+
+        while (Modes.exit == 0) {
+            struct timespec start_time;
+
+            if (Modes.iDataReady == 0) {
+                /* wait for more data.
+                 * we should be getting data every 50-60ms. wait for max 100ms 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!
+                 */
+
+                struct timespec ts;
+                clock_gettime(CLOCK_REALTIME, &ts);
+                ts.tv_nsec += 100000000;
+                normalize_timespec(&ts);
+
+                pthread_cond_timedwait(&Modes.data_cond, &Modes.data_mutex, &ts); // This unlocks Modes.data_mutex, and waits for Modes.data_cond
+                // Once (Modes.data_cond) occurs, it locks Modes.data_mutex
+            }
+
+            // Modes.data_mutex is Locked, and possibly (Modes.iDataReady != 0)
+
+            // 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;
+
+            if (Modes.iDataReady) { // Check we have new data, just in case!!
+                start_cpu_timing(&start_time);
+
+                Modes.iDataOut &= (MODES_ASYNC_BUF_NUMBER-1); // Just incase
+
+                // Translate the next lot of I/Q samples into Modes.magnitude
+                computeMagnitudeVector(Modes.pData[Modes.iDataOut]);
+
+                Modes.stSystemTimeBlk = Modes.stSystemTimeRTL[Modes.iDataOut];
+
+                // Update the input buffer pointer queue
+                Modes.iDataOut   = (MODES_ASYNC_BUF_NUMBER-1) & (Modes.iDataOut + 1); 
+                Modes.iDataReady = (MODES_ASYNC_BUF_NUMBER-1) & (Modes.iDataIn - Modes.iDataOut);   
+
+                // If we lost some blocks, correct the timestamp
+                if (Modes.iDataLost) {
+                    Modes.timestampBlk += (MODES_ASYNC_BUF_SAMPLES * 6 * Modes.iDataLost);
+                    Modes.stats_current.blocks_dropped += Modes.iDataLost;
+                    Modes.iDataLost = 0;
+                }
+
+                // It's safe to release the lock now
+                pthread_cond_signal (&Modes.data_cond);
+                pthread_mutex_unlock(&Modes.data_mutex);
+
+                // Process data after releasing the lock, so that the capturing
+                // thread can read data while we perform computationally expensive
+                // stuff at the same time.
+
+                if (Modes.oversample)
+                    demodulate2400(Modes.magnitude, MODES_ASYNC_BUF_SAMPLES);
+                else
+                    demodulate2000(Modes.magnitude, MODES_ASYNC_BUF_SAMPLES);
+
+                // Update the timestamp ready for the next block
+                if (Modes.oversample)
+                    Modes.timestampBlk += (MODES_ASYNC_BUF_SAMPLES*5);
+                else
+                    Modes.timestampBlk += (MODES_ASYNC_BUF_SAMPLES*6);
+                Modes.stats_current.blocks_processed++;
+
+                end_cpu_timing(&start_time, &Modes.stats_current.demod_cpu);
+            } else {
+                pthread_cond_signal (&Modes.data_cond);
+                pthread_mutex_unlock(&Modes.data_mutex);
+            }
+
+            start_cpu_timing(&start_time);
+            backgroundTasks();
+            end_cpu_timing(&start_time, &Modes.stats_current.background_cpu);
+
+            pthread_mutex_lock(&Modes.data_mutex);
+        }
+
+        pthread_mutex_unlock(&Modes.data_mutex);
+
+        pthread_join(Modes.reader_thread,NULL);     // Wait on reader thread exit
+        pthread_cond_destroy(&Modes.data_cond);     // Thread cleanup - only after the reader thread is dead!
+        pthread_mutex_destroy(&Modes.data_mutex);
+    }
+
+    // If --stats were given, print statistics
+    if (Modes.stats) {
+        display_total_stats();
+    }
+
+    log_with_timestamp("Normal exit.");
+
+#ifndef _WIN32
+    pthread_exit(0);
+#else
+    return (0);
+#endif
+}
+//
+//=========================================================================
+//
diff --git a/dump1090.h b/dump1090.h
new file mode 100644
index 0000000..a34e569
--- /dev/null
+++ b/dump1090.h
@@ -0,0 +1,454 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// dump1090.h: main program header
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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
+
+// Default version number, if not overriden by the Makefile
+#ifndef MODES_DUMP1090_VERSION
+# define MODES_DUMP1090_VERSION     "v1.13-custom"
+#endif
+
+#ifndef MODES_DUMP1090_VARIANT
+# define MODES_DUMP1090_VARIANT     "dump1090-mutability"
+#endif
+
+// ============================= Include files ==========================
+
+#ifndef _WIN32
+    #include <stdio.h>
+    #include <string.h>
+    #include <stdlib.h>
+    #include <pthread.h>
+    #include <stdint.h>
+    #include <errno.h>
+    #include <unistd.h>
+    #include <math.h>
+    #include <sys/time.h>
+    #include <signal.h>
+    #include <fcntl.h>
+    #include <ctype.h>
+    #include <sys/stat.h>
+    #include <sys/ioctl.h>
+    #include <time.h>
+    #include <limits.h>
+#else
+    #include "winstubs.h" //Put everything Windows specific in here
+#endif
+
+#include <rtl-sdr.h>
+
+// ============================= #defines ===============================
+
+#define MODES_DEFAULT_PPM          52
+#define MODES_DEFAULT_RATE         2000000
+#define MODES_OVERSAMPLE_RATE      2400000
+#define MODES_DEFAULT_FREQ         1090000000
+#define MODES_DEFAULT_WIDTH        1000
+#define MODES_DEFAULT_HEIGHT       700
+#define MODES_ASYNC_BUF_NUMBER     16
+#define MODES_ASYNC_BUF_SIZE       (16*16384)                 // 256k
+#define MODES_ASYNC_BUF_SAMPLES    (MODES_ASYNC_BUF_SIZE / 2) // Each sample is 2 bytes
+#define MODES_AUTO_GAIN            -100                       // Use automatic gain
+#define MODES_MAX_GAIN             999999                     // Use max available gain
+#define MODES_MSG_SQUELCH_DB       4.0                        // Minimum SNR, in dB
+#define MODES_MSG_ENCODER_ERRS     3                          // Maximum number of encoding errors
+
+#define MODES_MAX_PHASE_STATS      10
+
+#define MODEAC_MSG_SAMPLES       (25 * 2)                     // include up to the SPI bit
+#define MODEAC_MSG_BYTES          2
+#define MODEAC_MSG_SQUELCH_LEVEL  0x07FF                      // Average signal strength limit
+#define MODEAC_MSG_FLAG          (1<<0)
+#define MODEAC_MSG_MODES_HIT     (1<<1)
+#define MODEAC_MSG_MODEA_HIT     (1<<2)
+#define MODEAC_MSG_MODEC_HIT     (1<<3)
+#define MODEAC_MSG_MODEA_ONLY    (1<<4)
+#define MODEAC_MSG_MODEC_OLD     (1<<5)
+
+#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_BUF_SIZE         (1500)
+#define MODES_OUT_FLUSH_SIZE       (MODES_OUT_BUF_SIZE - 256)
+#define MODES_OUT_FLUSH_INTERVAL   (60000)
+
+#define MODES_UNIT_FEET 0
+#define MODES_UNIT_METERS 1
+
+#define MODES_USER_LATLON_VALID (1<<0)
+
+#define MODES_ACFLAGS_LATLON_VALID   (1<<0)  // Aircraft Lat/Lon is decoded
+#define MODES_ACFLAGS_ALTITUDE_VALID (1<<1)  // Aircraft altitude is known
+#define MODES_ACFLAGS_HEADING_VALID  (1<<2)  // Aircraft heading is known
+#define MODES_ACFLAGS_SPEED_VALID    (1<<3)  // Aircraft speed is known
+#define MODES_ACFLAGS_VERTRATE_VALID (1<<4)  // Aircraft vertical rate is known
+#define MODES_ACFLAGS_SQUAWK_VALID   (1<<5)  // Aircraft Mode A Squawk is known
+#define MODES_ACFLAGS_CALLSIGN_VALID (1<<6)  // Aircraft Callsign Identity
+#define MODES_ACFLAGS_EWSPEED_VALID  (1<<7)  // Aircraft East West Speed is known
+#define MODES_ACFLAGS_NSSPEED_VALID  (1<<8)  // Aircraft North South Speed is known
+#define MODES_ACFLAGS_AOG            (1<<9)  // Aircraft is On the Ground
+#define MODES_ACFLAGS_LLEVEN_VALID   (1<<10) // Aircraft Even Lot/Lon is known
+#define MODES_ACFLAGS_LLODD_VALID    (1<<11) // Aircraft Odd Lot/Lon is known
+#define MODES_ACFLAGS_AOG_VALID      (1<<12) // MODES_ACFLAGS_AOG is valid
+#define MODES_ACFLAGS_FS_VALID       (1<<13) // Aircraft Flight Status is known
+#define MODES_ACFLAGS_NSEWSPD_VALID  (1<<14) // Aircraft EW and NS Speed is known
+#define MODES_ACFLAGS_LATLON_REL_OK  (1<<15) // Indicates it's OK to do a relative CPR
+#define MODES_ACFLAGS_REL_CPR_USED   (1<<16) // Lat/lon derived from relative CPR
+
+#define MODES_ACFLAGS_LLEITHER_VALID (MODES_ACFLAGS_LLEVEN_VALID | MODES_ACFLAGS_LLODD_VALID)
+#define MODES_ACFLAGS_LLBOTH_VALID   (MODES_ACFLAGS_LLEVEN_VALID | MODES_ACFLAGS_LLODD_VALID)
+#define MODES_ACFLAGS_AOG_GROUND     (MODES_ACFLAGS_AOG_VALID    | MODES_ACFLAGS_AOG)
+
+#define MODES_NON_ICAO_ADDRESS       (1<<24) // Set on addresses to indicate they are not ICAO addresses
+
+#define MODES_DEBUG_DEMOD (1<<0)
+#define MODES_DEBUG_DEMODERR (1<<1)
+#define MODES_DEBUG_BADCRC (1<<2)
+#define MODES_DEBUG_GOODCRC (1<<3)
+#define MODES_DEBUG_NOPREAMBLE (1<<4)
+#define MODES_DEBUG_NET (1<<5)
+#define MODES_DEBUG_JS (1<<6)
+
+// When debug is set to MODES_DEBUG_NOPREAMBLE, the first sample must be
+// at least greater than a given level for us to dump the signal.
+#define MODES_DEBUG_NOPREAMBLE_LEVEL 25
+
+#define MODES_INTERACTIVE_REFRESH_TIME 250      // Milliseconds
+#define MODES_INTERACTIVE_ROWS          22      // Rows on screen
+#define MODES_INTERACTIVE_DISPLAY_TTL 60000     // Delete from display after 60 seconds
+
+#define MODES_NET_HEARTBEAT_INTERVAL 60000      // milliseconds
+
+#define MODES_NET_SERVICES_NUM          7
+#define MODES_NET_INPUT_RAW_PORT    30001
+#define MODES_NET_OUTPUT_RAW_PORT   30002
+#define MODES_NET_OUTPUT_SBS_PORT   30003
+#define MODES_NET_INPUT_BEAST_PORT  30004
+#define MODES_NET_OUTPUT_BEAST_PORT 30005
+#define MODES_NET_HTTP_PORT          8080
+#define MODES_NET_OUTPUT_FA_TSV_PORT 10001
+#define MODES_CLIENT_BUF_SIZE  1024
+#define MODES_NET_SNDBUF_SIZE (1024*64)
+#define MODES_NET_SNDBUF_MAX  (7)
+
+#ifndef HTMLPATH
+#define HTMLPATH   "./public_html"      // default path for gmap.html etc
+#endif
+
+#define HISTORY_SIZE 120
+#define HISTORY_INTERVAL 30000
+
+#define MODES_NOTUSED(V) ((void) V)
+
+// adjust for zero offset of amplitude values
+#define TRUE_AMPLITUDE(x) ((x) + 365)
+#define MAX_AMPLITUDE TRUE_AMPLITUDE(65535)
+#define MAX_POWER (1.0 * MAX_AMPLITUDE * MAX_AMPLITUDE)
+
+// Include subheaders after all the #defines are in place
+
+#include "util.h"
+#include "anet.h"
+#include "crc.h"
+#include "demod_2000.h"
+#include "demod_2400.h"
+#include "stats.h"
+#include "cpr.h"
+#include "icao_filter.h"
+
+//======================== structure declarations =========================
+
+// Structure used to describe a networking client
+struct client {
+    struct client*  next;                // Pointer to next client
+    int    fd;                           // File descriptor
+    int    service;                      // TCP port the client is connected to
+    int    buflen;                       // Amount of data on buffer
+    char   buf[MODES_CLIENT_BUF_SIZE+1]; // Read buffer
+};
+
+// Common writer state for all output sockets of one type
+struct net_writer {
+    int socket;          // listening socket FD, used to identify the owning service
+    int connections;     // number of active clients
+    void *data;          // shared write buffer, sized MODES_OUT_BUF_SIZE
+    int dataUsed;        // number of bytes of write buffer currently used
+    uint64_t lastWrite;  // time of last write to clients
+};
+
+// Program global state
+struct {                             // Internal state
+    pthread_t       reader_thread;
+
+    pthread_mutex_t data_mutex;      // Mutex to synchronize buffer access
+    pthread_cond_t  data_cond;       // Conditional variable associated
+    uint16_t       *pData          [MODES_ASYNC_BUF_NUMBER]; // Raw IQ sample buffers from RTL
+    struct timespec stSystemTimeRTL[MODES_ASYNC_BUF_NUMBER]; // System time when RTL passed us this block
+    int             iDataIn;         // Fifo input pointer
+    int             iDataOut;        // Fifo output pointer
+    int             iDataReady;      // Fifo content count
+    int             iDataLost;       // Count of missed buffers
+    struct timespec reader_cpu_accumulator; // CPU time used by the reader thread, copied out and reset by the main thread under the mutex
+
+    int             trailing_samples;// extra trailing samples in magnitude buffer
+
+    uint16_t       *pFileData;       // Raw IQ samples buffer (from a File)
+    uint16_t       *magnitude;       // Magnitude vector
+    uint64_t        timestampBlk;    // Timestamp of the start of the current block
+    struct timespec stSystemTimeBlk; // System time when RTL passed us currently processing this block
+    int             fd;              // --ifile option file descriptor
+    uint16_t       *maglut;          // I/Q -> Magnitude lookup table
+    uint16_t       *log10lut;        // Magnitude -> log10 lookup table
+    int             exit;            // Exit from the main loop when true
+
+    // RTLSDR
+    char *        dev_name;
+    int           gain;
+    int           enable_agc;
+    rtlsdr_dev_t *dev;
+    int           freq;
+    int           ppm_error;
+
+    // Networking
+    char           aneterr[ANET_ERR_LEN];
+    struct client *clients;          // Our clients
+    int            ris;              // Raw input listening socket
+    int            bis;              // Beast input listening socket
+    int            https;            // HTTP listening socket
+
+    struct net_writer raw_out;       // Raw output
+    struct net_writer beast_out;     // Beast-format output
+    struct net_writer sbs_out;       // SBS-format output
+    struct net_writer fatsv_out;     // FATSV-format output
+
+#ifdef _WIN32
+    WSADATA        wsaData;          // Windows socket initialisation
+#endif
+
+    // Configuration
+    char *filename;                  // Input form file, --ifile option
+    int   oversample;
+    int   phase_enhance;             // Enable phase enhancement if true
+    int   nfix_crc;                  // Number of crc bit error(s) to correct
+    int   check_crc;                 // Only display messages with good CRC
+    int   raw;                       // Raw output format
+    int   beast;                     // Beast binary format output
+    int   mode_ac;                   // Enable decoding of SSR Modes A & C
+    int   debug;                     // Debugging mode
+    int   net;                       // Enable networking
+    int   net_only;                  // Enable just networking
+    uint64_t net_heartbeat_interval; // TCP heartbeat interval (milliseconds)
+    int   net_output_sbs_port;       // SBS output TCP port
+    int   net_output_flush_size;     // Minimum Size of output data
+    uint64_t net_output_flush_interval; // Maximum interval (in milliseconds) between outputwrites
+    int   net_output_raw_port;       // Raw output TCP port
+    int   net_input_raw_port;        // Raw input TCP port
+    int   net_output_beast_port;     // Beast output TCP port
+    int   net_input_beast_port;      // Beast input TCP port
+    char  *net_bind_address;         // Bind address
+    int   net_http_port;             // HTTP port
+    int   net_fatsv_port;            // FlightAware TSV port
+    int   net_sndbuf_size;           // TCP output buffer size (64Kb * 2^n)
+    int   net_verbatim;              // if true, send the original message, not the CRC-corrected one
+    int   quiet;                     // Suppress stdout
+    int   interactive;               // Interactive mode
+    int   interactive_rows;          // Interactive mode: max number of rows
+    uint64_t interactive_display_ttl;// Interactive mode: TTL display
+    uint64_t stats;                  // Interval (millis) between stats dumps,
+    int   onlyaddr;                  // Print only ICAO addresses
+    int   metric;                    // Use metric units
+    int   mlat;                      // Use Beast ascii format for raw data output, i.e. @...; iso *...;
+    int   interactive_rtl1090;       // flight table in interactive mode is formatted like RTL1090
+    char *json_dir;                  // Path to json base directory, or NULL not to write json.
+    uint64_t json_interval;          // Interval between rewriting the json aircraft file, in milliseconds; also the advertised map refresh interval
+    int   json_location_accuracy;    // Accuracy of location metadata: 0=none, 1=approx, 2=exact
+
+    int   json_aircraft_history_next;
+    struct {
+        char *content;
+        int clen;
+    } json_aircraft_history[HISTORY_SIZE];
+
+    // User details
+    double fUserLat;                // Users receiver/antenna lat/lon needed for initial surface location
+    double fUserLon;                // Users receiver/antenna lat/lon needed for initial surface location
+    int    bUserFlags;              // Flags relating to the user details
+    double maxRange;                // Absolute maximum decoding range, in *metres*
+
+    // State tracking
+    struct aircraft *aircrafts;
+
+    // Statistics
+    struct stats stats_current;
+    struct stats stats_alltime;
+    struct stats stats_periodic;
+    struct stats stats_1min[15];
+    int stats_latest_1min;
+    struct stats stats_5min;
+    struct stats stats_15min;
+} 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
+    int           msgbits;                        // Number of bits in message 
+    int           msgtype;                        // Downlink format #
+    uint32_t      crc;                            // Message CRC
+    int           correctedbits;                  // No. of bits corrected 
+    uint32_t      addr;                           // Address Announced
+    uint64_t      timestampMsg;                   // Timestamp of the message (12MHz clock)
+    struct timespec sysTimestampMsg;              // Timestamp of the message (system time)
+    int           remote;                         // If set this message is from a remote station
+    double        signalLevel;                    // RSSI, in the range [0..1], as a fraction of full-scale power
+    int           score;                          // Scoring from scoreModesMessage, if used
+
+    // DF 11, DF 17
+    int  ca;                    // Responder capabilities
+    int  iid;
+
+    // DF 17, DF 18
+    int    metype;              // Extended squitter message type.
+    int    mesub;               // Extended squitter message subtype.
+    int    heading;             // Reported by aircraft, or computed from from EW and NS velocity
+    int    raw_latitude;        // Non decoded latitude.
+    int    raw_longitude;       // Non decoded longitude.
+    unsigned nuc_p;             // NUCp value implied by message type
+    double fLat;                // Coordinates obtained from CPR encoded data if/when decoded
+    double fLon;                // Coordinates obtained from CPR encoded data if/when decoded
+    char   flight[16];          // 8 chars flight number.
+    int    ew_velocity;         // E/W velocity.
+    int    ns_velocity;         // N/S velocity.
+    int    vert_rate;           // Vertical rate.
+    int    velocity;            // Reported by aircraft, or computed from from EW and NS velocity
+
+    // DF 18
+    int    cf;                  // Control Field
+
+    // DF4, DF5, DF20, DF21
+    int  fs;                    // Flight status for DF4,5,20,21
+    int  modeA;                 // 13 bits identity (Squawk).
+
+    // DF20, DF21
+    int  bds;                   // BDS value implied if overlay control was used
+
+    // Fields used by multiple message types.
+    int  altitude;
+    int  unit; 
+    int  bFlags;                // Flags related to fields in this structure
+};
+
+// This one needs modesMessage:
+#include "track.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);
+int  ModeAToModeC      (unsigned int ModeA);
+
+//
+// Functions exported from mode_s.c
+//
+int modesMessageLenByType(int type);
+int scoreModesMessage(unsigned char *msg, int validbits);
+int decodeModesMessage (struct modesMessage *mm, unsigned char *msg);
+void displayModesMessage(struct modesMessage *mm);
+void useModesMessage    (struct modesMessage *mm);
+void computeMagnitudeVector(uint16_t *pData);
+//
+// Functions exported from interactive.c
+//
+void  interactiveShowData(void);
+
+//
+// Functions exported from net_io.c
+//
+void modesInitNet         (void);
+void modesQueueOutput     (struct modesMessage *mm);
+void modesReadFromClient(struct client *c, char *sep, int(*handler)(struct client *, char *));
+void modesNetPeriodicWork (void);
+int   decodeBinMessage   (struct client *c, char *p);
+
+void writeJsonToFile(const char *file, char * (*generator) (const char*,int*));
+char *generateAircraftJson(const char *url_path, int *len);
+char *generateReceiverJson(const char *url_path, int *len);
+char *generateStatsJson(const char *url_path, int *len);
+char *generateHistoryJson(const char *url_path, int *len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __DUMP1090_H
diff --git a/icao_filter.c b/icao_filter.c
new file mode 100644
index 0000000..70f32b4
--- /dev/null
+++ b/icao_filter.c
@@ -0,0 +1,140 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// icao_filter.c: hashtable for ICAO addresses
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#include "dump1090.h"
+
+// hash table size, must be a power of two:
+#define ICAO_FILTER_SIZE 4096
+
+// Millis between filter expiry flips:
+#define MODES_ICAO_FILTER_TTL 60000
+
+// Open-addressed hash table with linear probing.
+// We store each address twice to handle Data/Parity
+// which need to match on a partial address (top 16 bits only).
+
+// Maintain two tables and switch between them to age out entries.
+
+static uint32_t icao_filter_a[ICAO_FILTER_SIZE];
+static uint32_t icao_filter_b[ICAO_FILTER_SIZE];
+static uint32_t *icao_filter_active;
+
+static uint32_t icaoHash(uint32_t a)
+{
+    // Jenkins one-at-a-time hash, unrolled for 3 bytes
+    uint32_t hash = 0;
+
+    hash += a & 0xff;
+    hash += hash << 10;
+    hash ^= hash >> 6;
+
+    hash += (a >> 8) & 0xff;
+    hash += (hash << 10);
+    hash ^= (hash >> 6);
+
+    hash += (a >> 16) & 0xff;
+    hash += (hash << 10);
+    hash ^= (hash >> 6);
+             
+    hash += (hash << 3);
+    hash ^= (hash >> 11);
+    hash += (hash << 15);
+
+    return hash & (ICAO_FILTER_SIZE-1);
+}
+
+void icaoFilterInit()
+{
+    memset(icao_filter_a, 0, sizeof(icao_filter_a));
+    memset(icao_filter_b, 0, sizeof(icao_filter_b));
+    icao_filter_active = icao_filter_a;
+}
+
+void icaoFilterAdd(uint32_t addr)
+{
+    uint32_t h = icaoHash(addr);
+    while (icao_filter_active[h] && icao_filter_active[h] != addr)
+        h = (h+1) & (ICAO_FILTER_SIZE-1);
+    if (!icao_filter_active[h])
+        icao_filter_active[h] = addr;
+
+    // also add with a zeroed top byte, for handling DF20/21 with Data Parity
+    h = icaoHash(addr & 0x00ffff);
+    while (icao_filter_active[h] && (icao_filter_active[h] & 0x00ffff) != (addr & 0x00ffff))
+        h = (h+1) & (ICAO_FILTER_SIZE-1);
+    if (!icao_filter_active[h])
+        icao_filter_active[h] = addr;
+}
+
+int icaoFilterTest(uint32_t addr)
+{
+    uint32_t h, h0;
+
+    h0 = h = icaoHash(addr);
+    while (icao_filter_a[h] && icao_filter_a[h] != addr)
+        h = (h+1) & (ICAO_FILTER_SIZE-1);
+    if (icao_filter_a[h])
+        return 1;
+
+    h = h0;
+    while (icao_filter_b[h] && icao_filter_b[h] != addr)
+        h = (h+1) & (ICAO_FILTER_SIZE-1);
+    if (icao_filter_b[h])
+        return 1;
+
+    return 0;
+}
+
+uint32_t icaoFilterTestFuzzy(uint32_t partial)
+{
+    uint32_t h, h0;
+
+    partial &= 0x00ffff;
+    h0 = h = icaoHash(partial);
+    while (icao_filter_a[h] && (icao_filter_a[h] & 0x00ffff) != partial)
+        h = (h+1) & (ICAO_FILTER_SIZE-1);
+    if (icao_filter_a[h])
+        return icao_filter_a[h];
+
+    h = h0;
+    while (icao_filter_b[h] && (icao_filter_b[h] & 0x00ffff) != partial)
+        h = (h+1) & (ICAO_FILTER_SIZE-1);
+    if (icao_filter_b[h])
+        return icao_filter_b[h];
+
+    return 0;
+}
+
+// call this periodically:
+void icaoFilterExpire()
+{
+    static uint64_t next_flip = 0;
+    uint64_t now = mstime();
+
+    if (now >= next_flip) {
+        if (icao_filter_active == icao_filter_a) {
+            memset(icao_filter_b, 0, sizeof(icao_filter_b));
+            icao_filter_active = icao_filter_b;
+        } else {
+            memset(icao_filter_a, 0, sizeof(icao_filter_a));
+            icao_filter_active = icao_filter_a;
+        }
+        next_flip = now + MODES_ICAO_FILTER_TTL;
+    }
+}
diff --git a/icao_filter.h b/icao_filter.h
new file mode 100644
index 0000000..2c30f8d
--- /dev/null
+++ b/icao_filter.h
@@ -0,0 +1,41 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// icao_filter.c: prototypes for ICAO address hashtable
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#ifndef DUMP1090_ICAO_FILTER_H
+#define DUMP1090_ICAO_FILTER_H
+
+// Call once:
+void icaoFilterInit();
+
+// 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
diff --git a/interactive.c b/interactive.c
new file mode 100644
index 0000000..22221a7
--- /dev/null
+++ b/interactive.c
@@ -0,0 +1,177 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// interactive.c: aircraft tracking and interactive display
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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 "dump1090.h"
+
+//
+//========================= Interactive mode ===============================
+
+
+//
+//=========================================================================
+//
+// Show the currently captured interactive data on screen.
+//
+void interactiveShowData(void) {
+    struct aircraft *a = Modes.aircrafts;
+    static uint64_t next_update;
+    uint64_t now = mstime();
+    int count = 0;
+    char progress;
+    char spinner[4] = "|/-\\";
+
+    // Refresh screen every (MODES_INTERACTIVE_REFRESH_TIME) miliseconde
+    if (now < next_update)
+        return;
+
+    next_update = now + MODES_INTERACTIVE_REFRESH_TIME;
+
+    progress = spinner[(now/1000)%4];
+
+#ifndef _WIN32
+    printf("\x1b[H\x1b[2J");    // Clear the screen
+#else
+    cls();
+#endif
+
+    if (Modes.interactive_rtl1090 == 0) {
+        printf (
+" Hex    Mode  Sqwk  Flight   Alt    Spd  Hdg    Lat      Long   RSSI  Msgs  Ti%c\n", progress);
+    } else {
+        printf (
+" Hex   Flight   Alt      V/S GS  TT  SSR  G*456^ Msgs    Seen %c\n", progress);
+    }
+    printf(
+"-------------------------------------------------------------------------------\n");
+
+    while(a && (count < Modes.interactive_rows)) {
+
+        if ((now - a->seen) < Modes.interactive_display_ttl)
+            {
+            int msgs  = a->messages;
+            int flags = a->modeACflags;
+
+            if ( (((flags & (MODEAC_MSG_FLAG                             )) == 0                    ) && (msgs > 1  ) )
+              || (((flags & (MODEAC_MSG_MODES_HIT | MODEAC_MSG_MODEA_ONLY)) == MODEAC_MSG_MODEA_ONLY) && (msgs > 4  ) ) 
+              || (((flags & (MODEAC_MSG_MODES_HIT | MODEAC_MSG_MODEC_OLD )) == 0                    ) && (msgs > 127) ) 
+              ) {
+                int altitude = a->altitude, speed = a->speed;
+                char strSquawk[5] = " ";
+                char strFl[6]     = " ";
+                char strTt[5]     = " ";
+                char strGs[5]     = " ";
+
+                // Convert units to metric if --metric was specified
+                if (Modes.metric) {
+                    altitude = (int) (altitude / 3.2828);
+                    speed    = (int) (speed    * 1.852);
+                }
+
+                if (a->bFlags & MODES_ACFLAGS_SQUAWK_VALID) {
+                    snprintf(strSquawk,5,"%04x", a->modeA);}
+
+                if (a->bFlags & MODES_ACFLAGS_SPEED_VALID) {
+                    snprintf (strGs, 5,"%3d", speed);}
+
+                if (a->bFlags & MODES_ACFLAGS_HEADING_VALID) {
+                    snprintf (strTt, 5,"%03d", a->track);}
+
+                if (msgs > 99999) {
+                    msgs = 99999;}
+
+                if (Modes.interactive_rtl1090) { // RTL1090 display mode
+
+                    if (a->bFlags & MODES_ACFLAGS_ALTITUDE_VALID) {
+                        snprintf(strFl,6,"F%03d",(altitude/100));
+                    }
+                    printf("%06x %-8s %-4s         %-3s %-3s %4s        %-6d  %-2.0f\n", 
+                           a->addr, a->flight, strFl, strGs, strTt, strSquawk, msgs, (now - a->seen)/1000.0);
+
+                } else {                         // Dump1090 display mode
+                    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; 
+
+                    if ((flags & MODEAC_MSG_FLAG) == 0) {
+                        strMode[0] = 'S';
+                    } else if (flags & MODEAC_MSG_MODEA_ONLY) {
+                        strMode[0] = 'A';
+                    }
+                    if (flags & MODEAC_MSG_MODEA_HIT) {strMode[2] = 'a';}
+                    if (flags & MODEAC_MSG_MODEC_HIT) {strMode[3] = 'c';}
+
+                    if (a->bFlags & MODES_ACFLAGS_LATLON_VALID) {
+                        snprintf(strLat, 8,"%7.03f", a->lat);
+                        snprintf(strLon, 9,"%8.03f", a->lon);
+                    }
+
+                    if (a->bFlags & MODES_ACFLAGS_AOG) {
+                        snprintf(strFl, 6," grnd");
+                    } else if (a->bFlags & MODES_ACFLAGS_ALTITUDE_VALID) {
+                        snprintf(strFl, 6, "%5d", altitude);
+                    }
+
+                    printf("%s%06X %-4s  %-4s  %-8s %5s  %3s  %3s  %7s %8s %5.1f %5d %2.0f\n",
+                           (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : " ", (a->addr & 0xffffff),
+                           strMode, strSquawk, a->flight, strFl, strGs, strTt,
+                           strLat, strLon, 10 * log10(signalAverage), msgs, (now - a->seen)/1000.0);
+                }
+                count++;
+            }
+        }
+        a = a->next;
+    }
+}
+
+//
+//=========================================================================
+//
diff --git a/mode_ac.c b/mode_ac.c
new file mode 100644
index 0000000..b4ecd11
--- /dev/null
+++ b/mode_ac.c
@@ -0,0 +1,383 @@
+// dump1090, a Mode S messages decoder for RTLSDR devices.
+//
+// Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//  *  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 "dump1090.h"
+//
+// ===================== Mode A/C detection and decoding  ===================
+//
+//
+// This table is used to build the Mode A/C variable called ModeABits.Each 
+// bit period is inspected, and if it's value exceeds the threshold limit, 
+// then the value in this table is or-ed into ModeABits.
+//
+// At the end of message processing, ModeABits will be the decoded ModeA value.
+//
+// We can also flag noise in bits that should be zeros - the xx bits. Noise in
+// these bits cause bits (31-16) in ModeABits to be set. Then at the end of message
+// processing we can test for errors by looking at these bits.
+//
+uint32_t ModeABitTable[24] = {
+0x00000000, // F1 = 1
+0x00000010, // C1
+0x00001000, // A1
+0x00000020, // C2 
+0x00002000, // A2
+0x00000040, // C4
+0x00004000, // A4
+0x40000000, // xx = 0  Set bit 30 if we see this high
+0x00000100, // B1 
+0x00000001, // D1
+0x00000200, // B2
+0x00000002, // D2
+0x00000400, // B4
+0x00000004, // D4
+0x00000000, // F2 = 1
+0x08000000, // xx = 0  Set bit 27 if we see this high
+0x04000000, // xx = 0  Set bit 26 if we see this high
+0x00000080, // SPI
+0x02000000, // xx = 0  Set bit 25 if we see this high
+0x01000000, // xx = 0  Set bit 24 if we see this high
+0x00800000, // xx = 0  Set bit 23 if we see this high
+0x00400000, // xx = 0  Set bit 22 if we see this high
+0x00200000, // xx = 0  Set bit 21 if we see this high
+0x00100000, // xx = 0  Set bit 20 if we see this high
+};
+//
+// This table is used to produce an error variable called ModeAErrs.Each 
+// inter-bit period is inspected, and if it's value falls outside of the 
+// expected range, then the value in this table is or-ed into ModeAErrs.
+//
+// At the end of message processing, ModeAErrs will indicate if we saw 
+// any inter-bit anomolies, and the bits that are set will show which 
+// bits had them.
+//
+uint32_t ModeAMidTable[24] = {
+0x80000000, // F1 = 1  Set bit 31 if we see F1_C1  error
+0x00000010, // C1      Set bit  4 if we see C1_A1  error
+0x00001000, // A1      Set bit 12 if we see A1_C2  error
+0x00000020, // C2      Set bit  5 if we see C2_A2  error
+0x00002000, // A2      Set bit 13 if we see A2_C4  error
+0x00000040, // C4      Set bit  6 if we see C3_A4  error
+0x00004000, // A4      Set bit 14 if we see A4_xx  error
+0x40000000, // xx = 0  Set bit 30 if we see xx_B1  error
+0x00000100, // B1      Set bit  8 if we see B1_D1  error
+0x00000001, // D1      Set bit  0 if we see D1_B2  error
+0x00000200, // B2      Set bit  9 if we see B2_D2  error
+0x00000002, // D2      Set bit  1 if we see D2_B4  error
+0x00000400, // B4      Set bit 10 if we see B4_D4  error
+0x00000004, // D4      Set bit  2 if we see D4_F2  error
+0x20000000, // F2 = 1  Set bit 29 if we see F2_xx  error
+0x08000000, // xx = 0  Set bit 27 if we see xx_xx  error
+0x04000000, // xx = 0  Set bit 26 if we see xx_SPI error
+0x00000080, // SPI     Set bit 15 if we see SPI_xx error
+0x02000000, // xx = 0  Set bit 25 if we see xx_xx  error
+0x01000000, // xx = 0  Set bit 24 if we see xx_xx  error
+0x00800000, // xx = 0  Set bit 23 if we see xx_xx  error
+0x00400000, // xx = 0  Set bit 22 if we see xx_xx  error
+0x00200000, // xx = 0  Set bit 21 if we see xx_xx  error
+0x00100000, // xx = 0  Set bit 20 if we see xx_xx  error
+};
+//
+// The "off air" format is,,
+// _F1_C1_A1_C2_A2_C4_A4_xx_B1_D1_B2_D2_B4_D4_F2_xx_xx_SPI_
+//
+// Bit spacing is 1.45uS, with 0.45uS high, and 1.00us low. This is a problem
+// because we ase sampling at 2Mhz (500nS) so we are below Nyquist. 
+//
+// The bit spacings are..
+// F1 :  0.00,   
+//       1.45,  2.90,  4.35,  5.80,  7.25,  8.70, 
+// X  : 10.15, 
+//    : 11.60, 13.05, 14.50, 15.95, 17.40, 18.85, 
+// F2 : 20.30, 
+// X  : 21.75, 23.20, 24.65 
+//
+// This equates to the following sample point centers at 2Mhz.
+// [ 0.0], 
+// [ 2.9], [ 5.8], [ 8.7], [11.6], [14.5], [17.4], 
+// [20.3], 
+// [23.2], [26.1], [29.0], [31.9], [34.8], [37.7]
+// [40.6]
+// [43.5], [46.4], [49.3]
+//
+// We know that this is a supposed to be a binary stream, so the signal
+// should either be a 1 or a 0. Therefore, any energy above the noise level 
+// in two adjacent samples must be from the same pulse, so we can simply 
+// add the values together.. 
+// 
+int detectModeA(uint16_t *m, struct modesMessage *mm)
+  {
+  int j, lastBitWasOne;
+  int ModeABits = 0;
+  int ModeAErrs = 0;
+  int byte, bit;
+  int thisSample, lastBit, lastSpace = 0; 
+  int m0, m1, m2, m3, mPhase;
+  int n0, n1, n2 ,n3;
+  int F1_sig, F1_noise;
+  int F2_sig, F2_noise;
+  int fSig, fNoise, fLevel, fLoLo;
+
+  // m[0] contains the energy from    0 ->  499 nS
+  // m[1] contains the energy from  500 ->  999 nS
+  // m[2] contains the energy from 1000 -> 1499 nS
+  // m[3] contains the energy from 1500 -> 1999 nS
+  //
+  // We are looking for a Frame bit (F1) whose width is 450nS, followed by
+  // 1000nS of quiet.
+  //
+  // The width of the frame bit is 450nS, which is 90% of our sample rate.
+  // Therefore, in an ideal world, all the energy for the frame bit will be
+  // in a single sample, preceeded by (at least) one zero, and followed by 
+  // two zeros, Best case we can look for ...
+  //
+  // 0 - 1 - 0 - 0
+  //
+  // However, our samples are not phase aligned, so some of the energy from 
+  // each bit could be spread over two consecutive samples. Worst case is
+  // that we sample half in one bit, and half in the next. In that case, 
+  // we're looking for 
+  //
+  // 0 - 0.5 - 0.5 - 0.
+
+  m0 = m[0]; m1 = m[1];
+
+  if (m0 >= m1)   // m1 *must* be bigger than m0 for this to be F1
+    {return (0);}
+
+  m2 = m[2]; m3 = m[3];
+
+  // 
+  // if (m2 <= m0), then assume the sample bob on (Phase == 0), so don't look at m3 
+  if ((m2 <= m0) || (m2 < m3))
+    {m3 = m2; m2 = m0;}
+
+  if (  (m3 >= m1)   // m1 must be bigger than m3
+     || (m0 >  m2)   // m2 can be equal to m0 if ( 0,1,0,0 )
+     || (m3 >  m2) ) // m2 can be equal to m3 if ( 0,1,0,0 )
+    {return (0);}
+
+  // m0 = noise
+  // m1 = noise + (signal *    X))
+  // m2 = noise + (signal * (1-X))
+  // m3 = noise
+  //
+  // Hence, assuming all 4 samples have similar amounts of noise in them 
+  //      signal = (m1 + m2) - ((m0 + m3) * 2)
+  //      noise  = (m0 + m3) / 2
+  //
+  F1_sig   = (m1 + m2) - ((m0 + m3) << 1);
+  F1_noise = (m0 + m3) >> 1;
+
+  if ( (F1_sig < MODEAC_MSG_SQUELCH_LEVEL) // minimum required  F1 signal amplitude
+    || (F1_sig < (F1_noise << 2)) )        // minimum allowable Sig/Noise ratio 4:1
+    {return (0);}
+
+  // If we get here then we have a potential F1, so look for an equally valid F2 20.3uS later
+  //
+  // Our F1 is centered somewhere between samples m[1] and m[2]. We can guestimate where F2 is 
+  // by comparing the ratio of m1 and m2, and adding on 20.3 uS (40.6 samples)
+  //
+  mPhase = ((m2 * 20) / (m1 + m2));
+  byte   = (mPhase + 812) / 20; 
+  n0     = m[byte++]; n1 = m[byte++]; 
+
+  if (n0 >= n1)   // n1 *must* be bigger than n0 for this to be F2
+    {return (0);}
+
+  n2 = m[byte++];
+  // 
+  // if the sample bob on (Phase == 0), don't look at n3 
+  //
+  if ((mPhase + 812) % 20)
+    {n3 = m[byte++];}
+  else
+    {n3 = n2; n2 = n0;}
+
+  if (  (n3 >= n1)   // n1 must be bigger than n3
+     || (n0 >  n2)   // n2 can be equal to n0 ( 0,1,0,0 )
+     || (n3 >  n2) ) // n2 can be equal to n3 ( 0,1,0,0 )
+    {return (0);}
+
+  F2_sig   = (n1 + n2) - ((n0 + n3) << 1);
+  F2_noise = (n0 + n3) >> 1;
+
+  if ( (F2_sig < MODEAC_MSG_SQUELCH_LEVEL) // minimum required  F2 signal amplitude
+    || (F2_sig < (F2_noise << 2)) )       // maximum allowable Sig/Noise ratio 4:1
+    {return (0);}
+
+  fSig          = (F1_sig   + F2_sig)   >> 1;
+  fNoise        = (F1_noise + F2_noise) >> 1;
+  fLoLo         = fNoise    + (fSig >> 2);       // 1/2
+  fLevel        = fNoise    + (fSig >> 1);
+  lastBitWasOne = 1;
+  lastBit       = F1_sig;
+  //
+  // Now step by a half ModeA bit, 0.725nS, which is 1.45 samples, which is 29/20
+  // No need to do bit 0 because we've already selected it as a valid F1
+  // Do several bits past the SPI to increase error rejection
+  //
+  for (j = 1, mPhase += 29; j < 48; mPhase += 29, j ++)
+    {
+    byte  = 1 + (mPhase / 20);
+    
+    thisSample = m[byte] - fNoise;
+    if (mPhase % 20)                     // If the bit is split over two samples...
+      {thisSample += (m[byte+1] - fNoise);}  //    add in the second sample's energy
+
+     // If we're calculating a space value
+    if (j & 1)               
+      {lastSpace = thisSample;}
+
+    else 
+      {// We're calculating a new bit value
+      bit = j >> 1;
+      if (thisSample >= fLevel)
+        {// We're calculating a new bit value, and its a one
+        ModeABits |= ModeABitTable[bit--];  // or in the correct bit
+
+        if (lastBitWasOne)
+          { // This bit is one, last bit was one, so check the last space is somewhere less than one
+          if ( (lastSpace >= (thisSample>>1)) || (lastSpace >= lastBit) )
+            {ModeAErrs |= ModeAMidTable[bit];}
+          }
+
+        else              
+          {// This bit,is one, last bit was zero, so check the last space is somewhere less than one
+          if (lastSpace >= (thisSample >> 1))
+            {ModeAErrs |= ModeAMidTable[bit];}
+          }
+
+        lastBitWasOne = 1;
+        }
+
+      
+      else 
+        {// We're calculating a new bit value, and its a zero
+        if (lastBitWasOne)
+          { // This bit is zero, last bit was one, so check the last space is somewhere in between
+          if (lastSpace >= lastBit)
+            {ModeAErrs |= ModeAMidTable[bit];}
+          }
+
+        else              
+          {// This bit,is zero, last bit was zero, so check the last space is zero too
+          if (lastSpace >= fLoLo)
+            {ModeAErrs |= ModeAMidTable[bit];}
+          }
+
+        lastBitWasOne = 0;   
+        }
+
+      lastBit = (thisSample >> 1); 
+      }
+    }
+
+  //
+  // Output format is : 00:A4:A2:A1:00:B4:B2:B1:00:C4:C2:C1:00:D4:D2:D1
+  //
+  if ((ModeABits < 3) || (ModeABits & 0xFFFF8808) || (ModeAErrs) )
+    {return (ModeABits = 0);}
+
+  mm->signalLevel = 1.0 * TRUE_AMPLITUDE(fSig + fNoise) * TRUE_AMPLITUDE(fSig + fNoise) / MAX_POWER;
+
+  return ModeABits;
+  }
+//
+//=========================================================================
+//
+// Input format is : 00:A4:A2:A1:00:B4:B2:B1:00:C4:C2:C1:00:D4:D2:D1
+//
+int ModeAToModeC(unsigned int ModeA) 
+  { 
+  unsigned int FiveHundreds = 0;
+  unsigned int OneHundreds  = 0;
+
+  if (  (ModeA & 0xFFFF888B)         // D1 set is illegal. D2 set is > 62700ft which is unlikely
+    || ((ModeA & 0x000000F0) == 0) ) // C1,,C4 cannot be Zero
+    {return -9999;}
+
+  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 -9999;} 
+
+//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->msgtype = 32; // Valid Mode S DF's are DF-00 to DF-31.
+                    // so use 32 to indicate Mode A/C
+
+  mm->msgbits = 16; // Fudge up a Mode S style data stream
+  mm->msg[0] = (ModeA >> 8);
+  mm->msg[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->modeA   = ModeA & 0x7777;
+  mm->bFlags |= MODES_ACFLAGS_SQUAWK_VALID;
+
+  // Flag ident in flight status
+  mm->fs = ModeA & 0x0080;
+
+  // 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  ===================
+//
diff --git a/mode_s.c b/mode_s.c
new file mode 100644
index 0000000..5894fad
--- /dev/null
+++ b/mode_s.c
@@ -0,0 +1,1259 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// mode_s.c: Mode S message decoding.
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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 "dump1090.h"
+//
+// ===================== Mode S detection and decoding  ===================
+//
+//
+//
+
+//=========================================================================
+//
+// 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.
+//
+int modesMessageLenByType(int type) {
+    return (type & 0x10) ? MODES_LONG_MSG_BITS : MODES_SHORT_MSG_BITS ;
+}
+
+//
+//=========================================================================
+//
+// 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);
+}
+
+#define INVALID_ALTITUDE (-9999)
+
+//
+//=========================================================================
+//
+// Decode the 13 bit AC altitude field (in DF 20 and others).
+// Returns the altitude, and set 'unit' to either MODES_UNIT_METERS or MDOES_UNIT_FEETS.
+//
+static int decodeAC13Field(int AC13Field, int *unit) {
+    int m_bit  = AC13Field & 0x0040; // set = meters, clear = feet
+    int q_bit  = AC13Field & 0x0010; // set = 25 ft encoding, clear = Gillham Mode C encoding
+
+    if (!m_bit) {
+        *unit = MODES_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 = MODES_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, int *unit) {
+    int q_bit  = AC12Field & 0x10; // Bit 48 = Q
+
+    *unit = MODES_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
+//
+static int decodeMovementField(int movement) {
+    int gspeed;
+
+    // Note : movement codes 0,125,126,127 are all invalid, but they are 
+    //        trapped for before this function is called.
+
+    if      (movement  > 123) gspeed = 199; // > 175kt
+    else if (movement  > 108) gspeed = ((movement - 108)  * 5) + 100;
+    else if (movement  >  93) gspeed = ((movement -  93)  * 2) +  70;
+    else if (movement  >  38) gspeed = ((movement -  38)     ) +  15;
+    else if (movement  >  12) gspeed = ((movement -  11) >> 1) +   2;
+    else if (movement  >   8) gspeed = ((movement -   6) >> 2) +   1;
+    else                      gspeed = 0;
+
+    return (gspeed);
+}
+//
+//=========================================================================
+//
+// Capability table
+static const char *ca_str[8] = {
+    /* 0 */ "Level 1",
+    /* 1 */ "reserved",
+    /* 2 */ "reserved",
+    /* 3 */ "reserved",
+    /* 4 */ "Level 2+, ground",
+    /* 5 */ "Level 2+, airborne",
+    /* 6 */ "Level 2+",
+    /* 7 */ "DR/Alert/SPI active"
+};
+
+// DF 18 Control field table.
+static const char *cf_str[8] = {
+    /* 0 */ "ADS-B ES/NT device with ICAO 24-bit address",
+    /* 1 */ "ADS-B ES/NT device with other address",
+    /* 2 */ "Fine format TIS-B",
+    /* 3 */ "Coarse format TIS-B",
+    /* 4 */ "TIS-B management message",
+    /* 5 */ "TIS-B relay of ADS-B message with other address",
+    /* 6 */ "ADS-B rebroadcast using DF-17 message format",
+    /* 7 */ "Reserved"
+};
+
+// Flight status table
+static const char *fs_str[8] = {
+    /* 0 */ "Normal, Airborne",
+    /* 1 */ "Normal, On the ground",
+    /* 2 */ "ALERT,  Airborne",
+    /* 3 */ "ALERT,  On the ground",
+    /* 4 */ "ALERT & Special Position Identification. Airborne or Ground",
+    /* 5 */ "Special Position Identification. Airborne or Ground",
+    /* 6 */ "Reserved",
+    /* 7 */ "Not assigned"
+};
+
+// Emergency state table
+// from https://www.ll.mit.edu/mission/aviation/publications/publication-files/atc-reports/Grappel_2007_ATC-334_WW-15318.pdf
+// and 1090-DO-260B_FRAC
+char *es_str[8] = {
+    /* 0 */ "No emergency",
+    /* 1 */ "General emergency (squawk 7700)",
+    /* 2 */ "Lifeguard/Medical",
+    /* 3 */ "Minimum fuel",
+    /* 4 */ "No communications (squawk 7600)",
+    /* 5 */ "Unlawful interference (squawk 7500)",
+    /* 6 */ "Reserved",
+    /* 7 */ "Reserved"
+};
+//
+//=========================================================================
+//
+static char *getMEDescription(int metype, int mesub) {
+    char *mename = "Unknown";
+
+    if (metype >= 1 && metype <= 4)
+        mename = "Aircraft Identification and Category";
+    else if (metype >= 5 && metype <= 8)
+        mename = "Surface Position";
+    else if (metype >= 9 && metype <= 18)
+        mename = "Airborne Position (Baro Altitude)";
+    else if (metype == 19 && mesub >=1 && mesub <= 4)
+        mename = "Airborne Velocity";
+    else if (metype >= 20 && metype <= 22)
+        mename = "Airborne Position (GNSS Height)";
+    else if (metype == 23 && mesub == 0)
+        mename = "Test Message";
+    else if (metype == 23 && mesub == 7)
+        mename = "Test Message -- Squawk";
+    else if (metype == 24 && mesub == 1)
+        mename = "Surface System Status";
+    else if (metype == 28 && mesub == 1)
+        mename = "Extended Squitter Aircraft Status (Emergency)";
+    else if (metype == 28 && mesub == 2)
+        mename = "Extended Squitter Aircraft Status (1090ES TCAS RA)";
+    else if (metype == 29 && (mesub == 0 || mesub == 1))
+        mename = "Target State and Status Message";
+    else if (metype == 31 && (mesub == 0 || mesub == 1))
+        mename = "Aircraft Operational Status Message";
+    return mename;
+}
+
+// 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
+//  800: DF11 with IID==0, 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
+//  375: DF11 with IID==0, 1-bit error and an address not matching a known aircraft
+
+// 1000: DF11 with IID!=0, good CRC and an address matching a known aircraft
+//  500: DF11 with IID!=0, 1-bit error and an address 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
+
+int scoreModesMessage(unsigned char *msg, int validbits)
+{
+    int msgtype, msgbits, crc, iid;
+    uint32_t addr;
+    struct errorinfo *ei;
+
+    if (validbits < 56)
+        return -2;
+
+    msgtype = msg[0] >> 3; // Downlink Format
+    msgbits = modesMessageLenByType(msgtype);
+
+    if (validbits < msgbits)
+        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 24: // Comm-D (ELM)
+        return icaoFilterTest(crc) ? 1000 : -1;
+
+    case 11: // All-call reply
+        iid = crc & 0x7f;
+        crc = crc & 0xffff80;
+        addr = (msg[1] << 16) | (msg[2] << 8) | (msg[3]);
+
+        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);
+
+        // validate address
+        if (iid == 0) {
+            if (icaoFilterTest(addr))
+                return 1600 / (ei->errors + 1);
+            else
+                return 750 / (ei->errors + 1);
+        } else {
+            if (icaoFilterTest(addr))
+                return 1000 / (ei->errors + 1);
+            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 = (msg[1] << 16) | (msg[2] << 8) | (msg[3]);
+        correct_aa_field(&addr, ei);        
+
+        if (icaoFilterTest(addr))
+            return 1800 / (ei->errors+1);
+        else
+            return 1400 / (ei->errors+1);
+
+    case 20:   // Comm-B, altitude reply
+    case 21:   // Comm-B, identity reply
+        if (icaoFilterTest(crc))
+            return 1000; // Address/Parity
+
+#if 0
+        // This doesn't seem useful, as we mistake a lot of CRC errors
+        // for overlay control
+        if (icaoFilterTestFuzzy(crc))
+            return 500;  // Data/Parity
+#endif
+
+        return -2;
+
+    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);
+static void decodeCommB(struct modesMessage *mm);
+static char *ais_charset = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?";
+
+// 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
+
+int decodeModesMessage(struct modesMessage *mm, unsigned char *msg)
+{
+    // Work on our local copy.
+    memcpy(mm->msg, msg, MODES_LONG_MSG_BYTES);
+    if (Modes.net_verbatim) {
+        // Preserve the original uncorrected copy for later forwarding
+        memcpy(mm->verbatim, msg, MODES_LONG_MSG_BYTES);
+    }
+    msg = mm->msg;
+
+    // Get the message type ASAP as other operations depend on this
+    mm->msgtype         = msg[0] >> 3; // Downlink Format
+    mm->msgbits         = modesMessageLenByType(mm->msgtype);
+    mm->crc             = modesChecksum(msg, mm->msgbits);
+    mm->correctedbits   = 0;
+    mm->addr            = 0;
+
+    // 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)
+        // 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
+        if (!icaoFilterTest(mm->crc)) {
+           return -1;
+        }
+        mm->addr = mm->crc;
+        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;
+        if (mm->crc & 0xffff80) {
+            int addr;
+            struct errorinfo *ei = modesChecksumDiagnose(mm->crc & 0xffff80, mm->msgbits);
+            if (!ei) {
+                return -2; // couldn't fix it
+            }
+
+            // 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
+
+            mm->correctedbits = ei->errors;
+            modesChecksumFix(msg, ei);
+
+            // check whether the corrected message looks sensible
+            // we are conservative here: only accept corrected messages that
+            // match an existing aircraft.
+            addr = (msg[1] << 16) | (msg[2] << 8) | (msg[3]); 
+            if (!icaoFilterTest(addr)) {
+                return -1;
+            }
+        }
+        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)
+            break;  // all good
+
+        ei = modesChecksumDiagnose(mm->crc, mm->msgbits);
+        if (!ei) {
+            return -2; // couldn't fix it
+        }
+
+        addr1 = (msg[1] << 16) | (msg[2] << 8) | (msg[3]); 
+        mm->correctedbits = ei->errors;
+        modesChecksumFix(msg, ei);
+        addr2 = (msg[1] << 16) | (msg[2] << 8) | (msg[3]); 
+        
+        // we are conservative here: only accept corrected messages that
+        // match an existing aircraft.
+        if (addr1 != addr2 && !icaoFilterTest(addr2)) {
+            return -1;
+        }
+
+        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.
+
+        // Try an exact match
+        if (icaoFilterTest(mm->crc)) {
+            // OK.
+            mm->addr = mm->crc;
+            mm->bds = 0; // unknown
+            break;
+        }
+
+#if 0
+        // This doesn't seem useful, as we mistake a lot of CRC errors
+        // for overlay control
+
+        // Try a fuzzy match
+        if ( (mm->addr = icaoFilterTestFuzzy(mm->crc)) != 0) {
+            // We have an address that would match, assume it's correct
+            mm->bds = (mm->crc ^ mm->addr) >> 16; // derive the BDS value based on what we think the address is
+            break;
+        }
+#endif
+
+        return -1; // no good
+
+    default:
+        // All other message types, we don't know how to handle their CRCs, give up
+        return -2;
+    }      
+
+    // decode the bulk of the message
+
+    mm->bFlags = 0;
+
+    // AA (Address announced)
+    if (mm->msgtype == 11 || mm->msgtype == 17 || mm->msgtype == 18) {
+        mm->addr  = (msg[1] << 16) | (msg[2] << 8) | (msg[3]);
+    }
+
+    // AC (Altitude Code)
+    if (mm->msgtype == 0 || mm->msgtype == 4 || mm->msgtype == 16 || mm->msgtype == 20) {
+        int AC13Field = ((msg[2] << 8) | msg[3]) & 0x1FFF; 
+        if (AC13Field) { // Only attempt to decode if a valid (non zero) altitude is present
+            mm->altitude = decodeAC13Field(AC13Field, &mm->unit);
+            if (mm->altitude != INVALID_ALTITUDE)
+                mm->bFlags  |= MODES_ACFLAGS_ALTITUDE_VALID;
+        }
+    }
+
+    // AF (DF19 Application Field) not decoded
+
+    // CA (Capability)
+    if (mm->msgtype == 11 || mm->msgtype == 17) {
+        mm->ca    = (msg[0] & 0x07);
+        if (mm->ca == 4) {
+            mm->bFlags |= MODES_ACFLAGS_AOG_VALID | MODES_ACFLAGS_AOG;
+        } else if (mm->ca == 5) {
+            mm->bFlags |= MODES_ACFLAGS_AOG_VALID;
+        }
+    }
+
+    // CC (Cross-link capability) not decoded
+
+    // CF (Control field)
+    if (mm->msgtype == 18) {
+        mm->cf = msg[0] & 7;
+    }
+
+    // DR (Downlink Request) not decoded
+
+    // FS (Flight Status)
+    if (mm->msgtype == 4 || mm->msgtype == 5 || mm->msgtype == 20 || mm->msgtype == 21) {
+        mm->bFlags  |= MODES_ACFLAGS_FS_VALID;
+        mm->fs = msg[0] & 7;
+        if (mm->fs <= 3) {
+            mm->bFlags |= MODES_ACFLAGS_AOG_VALID;
+            if (mm->fs & 1)
+                mm->bFlags |= MODES_ACFLAGS_AOG;
+        }
+    }
+
+    // ID (Identity)
+    if (mm->msgtype == 5  || mm->msgtype == 21) {
+        // Gillham encoded Squawk
+        int ID13Field = ((msg[2] << 8) | msg[3]) & 0x1FFF; 
+        if (ID13Field) {
+            mm->bFlags |= MODES_ACFLAGS_SQUAWK_VALID;
+            mm->modeA   = decodeID13Field(ID13Field);
+        }
+    }
+
+    // KE (Control, ELM) not decoded
+
+    // MB (messsage, Comm-B)
+    if (mm->msgtype == 20 || mm->msgtype == 21) {
+        decodeCommB(mm);
+    }
+
+    // MD (message, Comm-D) not decoded
+
+    // ME (message, extended squitter)
+    if (mm->msgtype == 17 ||   //  Extended squitter
+        mm->msgtype == 18) {   //  Extended squitter/non-transponder:
+        decodeExtendedSquitter(mm);
+    }
+
+    // MV (message, ACAS) not decoded
+    // ND (number of D-segment) not decoded
+    // RI (Reply information) not decoded
+    // SL (Sensitivity level, ACAS) not decoded
+    // UM (Utility Message) not decoded
+
+    // VS (Vertical Status)
+    if (mm->msgtype == 0 || mm->msgtype == 16) {
+        mm->bFlags |= MODES_ACFLAGS_AOG_VALID;        
+        if (msg[0] & 0x04)
+            mm->bFlags |= MODES_ACFLAGS_AOG;
+    }
+
+    if (!mm->correctedbits && (mm->msgtype == 17 || mm->msgtype == 18 || (mm->msgtype != 11 || mm->iid == 0))) {
+        // No CRC errors seen, and either it was an DF17/18 extended squitter
+        // or a DF11 acquisition squitter with II = 0. We probably have the right address.
+
+        // We wait until here to do this as we may have needed to decode an ES to note
+        // the type of address in DF18 messages.
+
+        // NB this is the only place that adds addresses!
+        icaoFilterAdd(mm->addr);
+    }
+
+    // all done
+    return 0;
+}
+
+// Decode BDS2,0 carried in Comm-B or ES
+static void decodeBDS20(struct modesMessage *mm)
+{
+    uint32_t chars1, chars2;
+    unsigned char *msg = mm->msg;
+    
+    chars1 = (msg[5] << 16) | (msg[6] << 8) | (msg[7]);
+    chars2 = (msg[8] << 16) | (msg[9] << 8) | (msg[10]);
+    
+    // A common failure mode seems to be to intermittently send
+    // all zeros. Catch that here.
+    if (chars1 == 0 && chars2 == 0)
+        return;
+
+    mm->bFlags |= MODES_ACFLAGS_CALLSIGN_VALID;
+    
+    mm->flight[3] = ais_charset[chars1 & 0x3F]; chars1 = chars1 >> 6;
+    mm->flight[2] = ais_charset[chars1 & 0x3F]; chars1 = chars1 >> 6;
+    mm->flight[1] = ais_charset[chars1 & 0x3F]; chars1 = chars1 >> 6;
+    mm->flight[0] = ais_charset[chars1 & 0x3F];
+    
+    mm->flight[7] = ais_charset[chars2 & 0x3F]; chars2 = chars2 >> 6;
+    mm->flight[6] = ais_charset[chars2 & 0x3F]; chars2 = chars2 >> 6;
+    mm->flight[5] = ais_charset[chars2 & 0x3F]; chars2 = chars2 >> 6;
+    mm->flight[4] = ais_charset[chars2 & 0x3F];
+    
+    mm->flight[8] = '\0';
+}
+
+static void decodeExtendedSquitter(struct modesMessage *mm)
+{    
+    unsigned char *msg = mm->msg;
+    int metype = mm->metype = msg[4] >> 3;   // Extended squitter message type
+    int mesub  = mm->mesub  = (metype == 29 ? ((msg[4]&6)>>1) : (msg[4]  & 7));   // Extended squitter message subtype
+
+    int 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 ES/NT devices that report the ICAO 24-bit address in the AA field
+            break;
+
+        case 1: //   Reserved for ADS-B for ES/NT devices that use other addressing techniques in the AA field
+        case 5: //   TIS-B messages that relay ADS-B Messages using anonymous 24-bit addresses (format not explicitly defined, but it seems to follow DF17)
+            mm->addr |= MODES_NON_ICAO_ADDRESS;
+            break;
+
+        case 2: //   Fine TIS-B message (formats are close enough to DF17 for our purposes)
+        case 6: //   ADS-B rebroadcast using the same type codes and message formats as defined for DF = 17 ADS-B messages
+            check_imf = 1;
+            break;
+
+        case 3: //   Coarse TIS-B airborne position and velocity.
+            // TODO: decode me.
+            // For now we only look at the IMF bit.
+            if (msg[4] & 0x80)
+                mm->addr |= MODES_NON_ICAO_ADDRESS;
+            return;
+
+        default:    // All others, we don't know the format.
+            mm->addr |= MODES_NON_ICAO_ADDRESS; // assume non-ICAO
+            return;
+        }
+    }
+
+
+
+    switch (metype) {
+    case 1: case 2: case 3: case 4: {
+        // Aircraft Identification and Category
+        uint32_t chars1, chars2;
+
+        chars1 = (msg[5] << 16) | (msg[6] << 8) | (msg[7]);
+        chars2 = (msg[8] << 16) | (msg[9] << 8) | (msg[10]);
+
+        // A common failure mode seems to be to intermittently send
+        // all zeros. Catch that here.
+        if (chars1 != 0 || chars2 != 0) {
+            mm->bFlags |= MODES_ACFLAGS_CALLSIGN_VALID;
+
+            mm->flight[3] = ais_charset[chars1 & 0x3F]; chars1 = chars1 >> 6;
+            mm->flight[2] = ais_charset[chars1 & 0x3F]; chars1 = chars1 >> 6;
+            mm->flight[1] = ais_charset[chars1 & 0x3F]; chars1 = chars1 >> 6;
+            mm->flight[0] = ais_charset[chars1 & 0x3F];
+        
+            mm->flight[7] = ais_charset[chars2 & 0x3F]; chars2 = chars2 >> 6;
+            mm->flight[6] = ais_charset[chars2 & 0x3F]; chars2 = chars2 >> 6;
+            mm->flight[5] = ais_charset[chars2 & 0x3F]; chars2 = chars2 >> 6;
+            mm->flight[4] = ais_charset[chars2 & 0x3F];
+        
+            mm->flight[8] = '\0';
+        }
+
+        break;
+    }
+
+    case 19: { // Airborne Velocity Message        
+        if (check_imf && (msg[5] & 0x80))
+            mm->addr |= MODES_NON_ICAO_ADDRESS;
+
+        // Presumably airborne if we get an Airborne Velocity Message
+        mm->bFlags |= MODES_ACFLAGS_AOG_VALID; 
+        
+        if ( (mesub >= 1) && (mesub <= 4) ) {
+            int vert_rate = ((msg[8] & 0x07) << 6) | (msg[9] >> 2);
+            if (vert_rate) {
+                --vert_rate;
+                if (msg[8] & 0x08) 
+                    {vert_rate = 0 - vert_rate;}
+                mm->vert_rate =  vert_rate * 64;
+                mm->bFlags   |= MODES_ACFLAGS_VERTRATE_VALID;
+            }
+        }
+
+        if ((mesub == 1) || (mesub == 2)) {
+            int ew_raw = ((msg[5] & 0x03) << 8) |  msg[6];
+            int ew_vel = ew_raw - 1;
+            int ns_raw = ((msg[7] & 0x7F) << 3) | (msg[8] >> 5);
+            int ns_vel = ns_raw - 1;
+            
+            if (mesub == 2) { // If (supersonic) unit is 4 kts
+                ns_vel = ns_vel << 2;
+                ew_vel = ew_vel << 2;
+            }
+            
+            if (ew_raw) { // Do East/West  
+                mm->bFlags |= MODES_ACFLAGS_EWSPEED_VALID;
+                if (msg[5] & 0x04)
+                    {ew_vel = 0 - ew_vel;}                   
+                mm->ew_velocity = ew_vel;
+            }
+            
+            if (ns_raw) { // Do North/South
+                mm->bFlags |= MODES_ACFLAGS_NSSPEED_VALID;
+                if (msg[7] & 0x80)
+                    {ns_vel = 0 - ns_vel;}                   
+                mm->ns_velocity = ns_vel;
+            }
+            
+            if (ew_raw && ns_raw) {
+                // Compute velocity and angle from the two speed components
+                mm->bFlags |= (MODES_ACFLAGS_SPEED_VALID | MODES_ACFLAGS_HEADING_VALID | MODES_ACFLAGS_NSEWSPD_VALID);
+                mm->velocity = (int) sqrt((ns_vel * ns_vel) + (ew_vel * ew_vel));
+                
+                if (mm->velocity) {
+                    mm->heading = (int) (atan2(ew_vel, ns_vel) * 180.0 / M_PI);
+                    // We don't want negative values but a 0-360 scale
+                    if (mm->heading < 0) mm->heading += 360;
+                }
+            }
+            
+        } else if (mesub == 3 || mesub == 4) {
+            int airspeed = ((msg[7] & 0x7f) << 3) | (msg[8] >> 5);
+            if (airspeed) {
+                mm->bFlags |= MODES_ACFLAGS_SPEED_VALID;
+                --airspeed;
+                if (mesub == 4)  // If (supersonic) unit is 4 kts
+                    {airspeed = airspeed << 2;}
+                mm->velocity =  airspeed;
+            }
+            
+            if (msg[5] & 0x04) {
+                mm->bFlags |= MODES_ACFLAGS_HEADING_VALID;
+                mm->heading = ((((msg[5] & 0x03) << 8) | msg[6]) * 45) >> 7;
+            }
+        }
+
+        break;
+    }
+        
+    case 5: case 6: case 7: case 8: {
+        // Ground position
+        int movement;
+
+        if (check_imf && (msg[6] & 0x08))
+            mm->addr |= MODES_NON_ICAO_ADDRESS;
+
+        mm->bFlags |= MODES_ACFLAGS_AOG_VALID | MODES_ACFLAGS_AOG;
+        mm->raw_latitude  = ((msg[6] & 3) << 15) | (msg[7] << 7) | (msg[8] >> 1);
+        mm->raw_longitude = ((msg[8] & 1) << 16) | (msg[9] << 8) | (msg[10]);
+        mm->bFlags       |= (mm->msg[6] & 0x04) ? MODES_ACFLAGS_LLODD_VALID 
+            : MODES_ACFLAGS_LLEVEN_VALID;
+
+        movement = ((msg[4] << 4) | (msg[5] >> 4)) & 0x007F;
+        if ((movement) && (movement < 125)) {
+            mm->bFlags |= MODES_ACFLAGS_SPEED_VALID;
+            mm->velocity = decodeMovementField(movement);
+        }
+
+        if (msg[5] & 0x08) {
+            mm->bFlags |= MODES_ACFLAGS_HEADING_VALID;
+            mm->heading = ((((msg[5] << 4) | (msg[6] >> 4)) & 0x007F) * 45) >> 4;
+        }
+
+        mm->nuc_p = (14 - metype);
+        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, GNSS HAE       
+        int AC12Field = ((msg[5] << 4) | (msg[6] >> 4)) & 0x0FFF;
+
+        if (check_imf && (msg[4] & 0x01))
+            mm->addr |= MODES_NON_ICAO_ADDRESS;
+
+        mm->bFlags |= MODES_ACFLAGS_AOG_VALID;
+
+        if (metype != 0) {
+            // Catch some common failure modes and don't mark them as valid
+            // (so they won't be used for positioning)
+
+            mm->raw_latitude  = ((msg[6] & 3) << 15) | (msg[7] << 7) | (msg[8] >> 1);
+            mm->raw_longitude = ((msg[8] & 1) << 16) | (msg[9] << 8) | (msg[10]);
+
+            if (AC12Field == 0 && mm->raw_longitude == 0 && (mm->raw_latitude & 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->bFlags       |= (mm->msg[6] & 0x04) ? MODES_ACFLAGS_LLODD_VALID 
+                    : MODES_ACFLAGS_LLEVEN_VALID;
+            }
+        }
+
+        if (AC12Field) {// Only attempt to decode if a valid (non zero) altitude is present
+            mm->altitude = decodeAC12Field(AC12Field, &mm->unit);
+            if (mm->altitude != INVALID_ALTITUDE)
+                mm->bFlags  |= MODES_ACFLAGS_ALTITUDE_VALID;
+        }
+
+        if (metype == 0 || metype == 18 || metype == 22)
+            mm->nuc_p = 0;
+        else if (metype < 18)
+            mm->nuc_p = (18 - metype);
+        else
+            mm->nuc_p = (29 - metype);
+        
+        break;
+    }
+
+    case 23: { // Test message
+        if (mesub == 7) {               // (see 1090-WP-15-20)
+            int ID13Field = (((msg[5] << 8) | msg[6]) & 0xFFF1)>>3;
+            if (ID13Field) {
+                mm->bFlags |= MODES_ACFLAGS_SQUAWK_VALID;
+                mm->modeA   = decodeID13Field(ID13Field);
+            }
+        }
+        break;
+    }
+
+    case 24: // Reserved for Surface System Status
+        break;
+
+    case 28: { // Extended Squitter Aircraft Status
+        if (mesub == 1) {      // Emergency status squawk field
+            int ID13Field = (((msg[5] << 8) | msg[6]) & 0x1FFF);
+            if (ID13Field) {
+                mm->bFlags |= MODES_ACFLAGS_SQUAWK_VALID;
+                mm->modeA   = decodeID13Field(ID13Field);
+            }
+
+            if (check_imf && (msg[10] & 0x01))
+                mm->addr |= MODES_NON_ICAO_ADDRESS;
+        }
+        break;
+    }
+
+    case 29: // Aircraft Trajectory Intent
+        break;
+
+    case 30: // Aircraft Operational Coordination
+        break;
+
+    case 31: // Aircraft Operational Status
+        if (check_imf && (msg[10] & 0x01))
+            mm->addr |= MODES_NON_ICAO_ADDRESS;
+        break;
+
+    default: 
+        break;
+    }
+}
+
+static void decodeCommB(struct modesMessage *mm)
+{    
+    unsigned char *msg = mm->msg;
+
+    // This is a bit hairy as we don't know what the requested register was
+    if (msg[4] == 0x20) { // BDS 2,0 Aircraft Identification
+        decodeBDS20(mm);
+    }
+}
+
+//
+//=========================================================================
+//
+// These functions gets a decoded Mode S Message and prints it on the screen
+// in a human readable format.
+//
+static void displayExtendedSquitter(struct modesMessage *mm) {
+    printf("  Extended Squitter  Type: %d\n", mm->metype);
+    printf("  Extended Squitter  Sub : %d\n", mm->mesub);
+    printf("  Extended Squitter  Name: %s\n", getMEDescription(mm->metype, mm->mesub));
+
+    // Decode the extended squitter message
+    if (mm->metype >= 1 && mm->metype <= 4) { // Aircraft identification
+        printf("    Aircraft Type  : %c%d\n", ('A' + 4 - mm->metype), mm->mesub);
+        printf("    Identification : %s\n", mm->flight);
+    } else if (mm->metype == 19) { // Airborne Velocity
+        if (mm->mesub == 1 || mm->mesub == 2) {
+            printf("    EW status         : %s\n", (mm->bFlags & MODES_ACFLAGS_EWSPEED_VALID)  ? "Valid" : "Unavailable");
+            printf("    EW velocity       : %d\n", mm->ew_velocity);
+            printf("    NS status         : %s\n", (mm->bFlags & MODES_ACFLAGS_NSSPEED_VALID)  ? "Valid" : "Unavailable");
+            printf("    NS velocity       : %d\n", mm->ns_velocity);
+            printf("    Vertical status   : %s\n", (mm->bFlags & MODES_ACFLAGS_VERTRATE_VALID) ? "Valid" : "Unavailable");
+            printf("    Vertical rate src : %d\n", ((mm->msg[8] >> 4) & 1));
+            printf("    Vertical rate     : %d\n", mm->vert_rate);
+        } else if (mm->mesub == 3 || mm->mesub == 4) {
+            printf("    Heading status    : %s\n", (mm->bFlags & MODES_ACFLAGS_HEADING_VALID)  ? "Valid" : "Unavailable");
+            printf("    Heading           : %d\n", mm->heading);
+            printf("    Airspeed status   : %s\n", (mm->bFlags & MODES_ACFLAGS_SPEED_VALID)    ? "Valid" : "Unavailable");
+            printf("    Airspeed          : %d\n", mm->velocity);
+            printf("    Vertical status   : %s\n", (mm->bFlags & MODES_ACFLAGS_VERTRATE_VALID) ? "Valid" : "Unavailable");
+            printf("    Vertical rate src : %d\n", ((mm->msg[8] >> 4) & 1));
+            printf("    Vertical rate     : %d\n", mm->vert_rate);
+            
+        } else {
+            printf("    Unrecognized ME subtype: %d subtype: %d\n", mm->metype, mm->mesub);
+        }
+    } else if (mm->metype >= 5 && mm->metype <= 22) { // Airborne position Baro
+        printf("    F flag   : %s\n", (mm->msg[6] & 0x04) ? "odd" : "even");
+        printf("    T flag   : %s\n", (mm->msg[6] & 0x08) ? "UTC" : "non-UTC");
+        if (mm->bFlags & MODES_ACFLAGS_ALTITUDE_VALID)
+            printf("    Altitude : %d feet\n", mm->altitude);
+        else
+            printf("    Altitude : not valid\n");
+        if (mm->bFlags & MODES_ACFLAGS_LATLON_VALID) {
+            if (mm->bFlags & MODES_ACFLAGS_REL_CPR_USED)
+                printf("    Local CPR decoding used.\n");
+            else
+                printf("    Global CPR decoding used.\n");
+            printf("    Latitude : %f (%d)\n", mm->fLat, mm->raw_latitude);
+            printf("    Longitude: %f (%d)\n", mm->fLon, mm->raw_longitude);
+            printf("    NUCp:      %u\n", mm->nuc_p);
+        } else {
+            if (!(mm->bFlags & MODES_ACFLAGS_LLEITHER_VALID))
+                printf("    Bad position data, not decoded.\n");
+            printf("    Latitude : %d (not decoded)\n", mm->raw_latitude);
+            printf("    Longitude: %d (not decoded)\n", mm->raw_longitude);
+            printf("    NUCp:      %u\n", mm->nuc_p);
+        }
+    } else if (mm->metype == 28) { // Extended Squitter Aircraft Status
+        if (mm->mesub == 1) {
+            printf("    Emergency State: %s\n", es_str[(mm->msg[5] & 0xE0) >> 5]);
+            printf("    Squawk: %04x\n", mm->modeA);
+        } else {
+            printf("    Unrecognized ME subtype: %d subtype: %d\n", mm->metype, mm->mesub);
+        }
+    } else if (mm->metype == 23) { // Test Message
+        if (mm->mesub == 7) {
+            printf("    Squawk: %04x\n", mm->modeA);
+        } else {
+            printf("    Unrecognized ME subtype: %d subtype: %d\n", mm->metype, mm->mesub);
+        }
+    } else {
+        printf("    Unrecognized ME type: %d subtype: %d\n", mm->metype, mm->mesub);
+    }
+}
+
+static void displayCommB(struct modesMessage *mm)
+{
+    if (mm->bds != 0)
+        printf("  Comm-B BDS     : %02x (maybe)\n", mm->bds);
+
+    // Decode the Comm-B message
+    if (mm->msg[4] == 0x20 && (mm->bds == 0 || mm->bds == 0x20)) { // BDS 2,0 Aircraft identification
+        printf("    BDS 2,0 Aircraft Identification : %s\n", mm->flight);
+    } else {
+        int i;
+        printf("  Comm-B MB      : ");
+        for (i = 4; i < 11; ++i)
+            printf("%02x", mm->msg[i]);
+        printf("\n");
+    }        
+}
+
+void displayModesMessage(struct modesMessage *mm) {
+    int j;
+    unsigned char * pTimeStamp;
+
+    // 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->timestampMsg) {
+        printf("@");
+        pTimeStamp = (unsigned char *) &mm->timestampMsg;
+        for (j=5; j>=0;j--) {
+            printf("%02X",pTimeStamp[j]);
+        } 
+    } 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
+    }
+
+    if (mm->msgtype < 32)
+        printf("CRC: %06x\n", mm->crc);
+
+    if (mm->correctedbits != 0)
+        printf("No. of bit errors fixed: %d\n", mm->correctedbits);
+
+    if (mm->signalLevel > 0)
+        printf("RSSI: %.1f dBFS\n", 10 * log10(mm->signalLevel));
+
+    if (mm->score)
+        printf("Score: %d\n", mm->score);
+
+    if (mm->timestampMsg)
+        printf("Time: %.2fus (phase: %d)\n", mm->timestampMsg / 12.0, (unsigned int) (360 * (mm->timestampMsg % 6) / 6));
+
+    if (mm->msgtype == 0) { // DF 0
+        printf("DF 0: Short Air-Air Surveillance.\n");
+        printf("  VS             : %s\n",  (mm->msg[0] & 0x04) ? "Ground" : "Airborne");
+        printf("  CC             : %d\n", ((mm->msg[0] & 0x02) >> 1));
+        printf("  SL             : %d\n", ((mm->msg[1] & 0xE0) >> 5));
+        printf("  Altitude       : %d %s\n", mm->altitude,
+            (mm->unit == MODES_UNIT_METERS) ? "meters" : "feet");
+        printf("  ICAO Address   : %06x\n", mm->addr);
+
+    } else if (mm->msgtype == 4 || mm->msgtype == 20) {
+        printf("DF %d: %s, Altitude Reply.\n", mm->msgtype,
+            (mm->msgtype == 4) ? "Surveillance" : "Comm-B");
+        printf("  Flight Status  : %s\n", fs_str[mm->fs]);
+        printf("  DR             : %d\n", ((mm->msg[1] >> 3) & 0x1F));
+        printf("  UM             : %d\n", (((mm->msg[1]  & 7) << 3) | (mm->msg[2] >> 5)));
+        printf("  Altitude       : %d %s\n", mm->altitude,
+            (mm->unit == MODES_UNIT_METERS) ? "meters" : "feet");
+        printf("  ICAO Address   : %06x\n", mm->addr);
+
+        if (mm->msgtype == 20) {
+            displayCommB(mm);
+        }
+    } else if (mm->msgtype == 5 || mm->msgtype == 21) {
+        printf("DF %d: %s, Identity Reply.\n", mm->msgtype,
+            (mm->msgtype == 5) ? "Surveillance" : "Comm-B");
+        printf("  Flight Status  : %s\n", fs_str[mm->fs]);
+        printf("  DR             : %d\n", ((mm->msg[1] >> 3) & 0x1F));
+        printf("  UM             : %d\n", (((mm->msg[1]  & 7) << 3) | (mm->msg[2] >> 5)));
+        printf("  Squawk         : %04x\n", mm->modeA);
+        printf("  ICAO Address   : %06x\n", mm->addr);
+
+        if (mm->msgtype == 21) {
+            displayCommB(mm);
+        }
+    } else if (mm->msgtype == 11) { // DF 11
+        printf("DF 11: All Call Reply.\n");
+        printf("  Capability  : %d (%s)\n", mm->ca, ca_str[mm->ca]);
+        printf("  ICAO Address: %06x\n", mm->addr);
+        if (mm->iid > 16)
+            {printf("  IID         : SI-%02d\n", mm->iid-16);}
+        else
+            {printf("  IID         : II-%02d\n", mm->iid);}
+
+    } else if (mm->msgtype == 16) { // DF 16
+        printf("DF 16: Long Air to Air ACAS\n");
+        printf("  VS             : %s\n",  (mm->msg[0] & 0x04) ? "Ground" : "Airborne");
+        printf("  CC             : %d\n", ((mm->msg[0] & 0x02) >> 1));
+        printf("  SL             : %d\n", ((mm->msg[1] & 0xE0) >> 5));
+        printf("  Altitude       : %d %s\n", mm->altitude,
+            (mm->unit == MODES_UNIT_METERS) ? "meters" : "feet");
+        printf("  ICAO Address   : %06x\n", mm->addr);
+
+    } else if (mm->msgtype == 17) { // DF 17
+        printf("DF 17: ADS-B message.\n");
+        printf("  Capability     : %d (%s)\n", mm->ca, ca_str[mm->ca]);
+        printf("  ICAO Address   : %06x\n", mm->addr);
+        displayExtendedSquitter(mm);
+    } else if (mm->msgtype == 18) { // DF 18 
+        printf("DF 18: Extended Squitter.\n");
+        printf("  Control Field : %d (%s)\n", mm->cf, cf_str[mm->cf]);
+        if ((mm->cf == 0) || (mm->cf == 1) || (mm->cf == 5) || (mm->cf == 6)) {
+            if (mm->cf == 1 || mm->cf == 5) {
+                printf("  Other Address : %06x\n", mm->addr);
+            } else {
+                printf("  ICAO Address  : %06x\n", mm->addr);
+            }
+            displayExtendedSquitter(mm);
+        }             
+
+    } else if (mm->msgtype == 19) { // DF 19
+        printf("DF 19: Military Extended Squitter.\n");
+
+    } else if (mm->msgtype == 22) { // DF 22
+        printf("DF 22: Military Use.\n");
+
+    } else if (mm->msgtype == 24) { // DF 24
+        printf("DF 24: Comm D Extended Length Message.\n");
+
+    } else if (mm->msgtype == 32) { // DF 32 is special code we use for Mode A/C
+        printf("SSR : Mode A/C Reply.\n");
+        if (mm->fs & 0x0080) {
+            printf("  Mode A : %04x IDENT\n", mm->modeA);
+        } else {
+            printf("  Mode A : %04x\n", mm->modeA);
+            if (mm->bFlags & MODES_ACFLAGS_ALTITUDE_VALID)
+                {printf("  Mode C : %d feet\n", mm->altitude);}
+        }
+
+    } else {
+        printf("DF %d: Unknown DF Format.\n", mm->msgtype);
+    }
+
+    printf("\n");
+}
+//
+//=========================================================================
+//
+// Turn I/Q samples pointed by Modes.data into the magnitude vector
+// pointed by Modes.magnitude.
+//
+void computeMagnitudeVector(uint16_t *p) {
+    uint16_t *m = &Modes.magnitude[Modes.trailing_samples];
+    uint32_t j;
+
+    memcpy(Modes.magnitude,&Modes.magnitude[MODES_ASYNC_BUF_SAMPLES], Modes.trailing_samples * 2);
+
+    // Compute the magnitudo vector. It's just SQRT(I^2 + Q^2), but
+    // we rescale to the 0-255 range to exploit the full resolution.
+    for (j = 0; j < MODES_ASYNC_BUF_SAMPLES; j ++) {
+        *m++ = Modes.maglut[*p++];
+    }
+}
+
+//
+//=========================================================================
+//
+// 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 useModesMessage(struct modesMessage *mm) {
+    struct aircraft *a;
+
+    ++Modes.stats_current.messages_total;
+
+    // Track aircraft state
+    a = trackUpdateFromMessage(mm);
+
+    // In non-interactive non-quiet mode, display messages on standard output
+    if (!Modes.interactive && !Modes.quiet) {
+        displayModesMessage(mm);
+    }
+
+    // Feed output clients.
+    // If in --net-verbatim mode, do this for all messages.
+    // Otherwise, apply a sanity-check filter and only
+    // forward messages when we have seen two of them.
+
+    // TODO: buffer the original message and forward it when we
+    // see a second message?
+
+    if (Modes.net) {
+        if (Modes.net_verbatim || a->messages > 1) {
+            // If this is the second message, and we
+            // squelched the first message, then re-emit the
+            // first message now.
+            if (!Modes.net_verbatim && a->messages == 2) {
+                modesQueueOutput(&a->first_message);
+            }
+
+            modesQueueOutput(mm);
+        }
+    }
+}
+
+//
+// ===================== Mode S detection and decoding  ===================
+//
diff --git a/net_io.c b/net_io.c
new file mode 100644
index 0000000..3c971e3
--- /dev/null
+++ b/net_io.c
@@ -0,0 +1,1639 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// net_io.c: network handling.
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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 "dump1090.h"
+
+#include <assert.h>
+
+//
+// ============================= Networking =============================
+//
+// Note: here we disregard any kind of good coding practice in favor of
+// extreme simplicity, that is:
+//
+// 1) We only rely on the kernel buffers for our I/O without any kind of
+//    user space buffering.
+// 2) We don't register any kind of event handler, from time to time a
+//    function gets called and we accept new connections. All the rest is
+//    handled via non-blocking I/O and manually polling clients to see if
+//    they have something new to share with us when reading is needed.
+//
+//=========================================================================
+//
+// Networking "stack" initialization
+//
+struct service {
+	char *descr;
+	int *socket;
+	struct net_writer *writer;
+	int port;
+	int enabled;
+};
+
+struct service services[MODES_NET_SERVICES_NUM];
+
+void modesInitNet(void) {
+    int j;
+
+    struct service svc[MODES_NET_SERVICES_NUM] = {
+	    {"Raw TCP output", &Modes.raw_out.socket, &Modes.raw_out, Modes.net_output_raw_port, 1},
+	    {"Raw TCP input", &Modes.ris, NULL, Modes.net_input_raw_port, 1},
+	    {"Beast TCP output", &Modes.beast_out.socket, &Modes.beast_out, Modes.net_output_beast_port, 1},
+	    {"Beast TCP input", &Modes.bis, NULL, Modes.net_input_beast_port, 1},
+	    {"HTTP server", &Modes.https, NULL, Modes.net_http_port, 1},
+	    {"Basestation TCP output", &Modes.sbs_out.socket, &Modes.sbs_out, Modes.net_output_sbs_port, 1},
+	    {"FlightAware TSV output", &Modes.fatsv_out.socket, &Modes.fatsv_out, Modes.net_fatsv_port, 1}
+    };
+
+	memcpy(&services, &svc, sizeof(svc));//services = svc;
+
+    Modes.clients = NULL;
+
+#ifdef _WIN32
+    if ( (!Modes.wsaData.wVersion) 
+      && (!Modes.wsaData.wHighVersion) ) {
+      // Try to start the windows socket support
+      if (WSAStartup(MAKEWORD(2,1),&Modes.wsaData) != 0) 
+        {
+        fprintf(stderr, "WSAStartup returned Error\n");
+        }
+      }
+#endif
+
+    for (j = 0; j < MODES_NET_SERVICES_NUM; j++) {
+		services[j].enabled = (services[j].port != 0);
+		if (services[j].enabled) {
+			int s = anetTcpServer(Modes.aneterr, services[j].port, Modes.net_bind_address);
+			if (s == -1) {
+				fprintf(stderr, "Error opening the listening port %d (%s): %s\n",
+					services[j].port, services[j].descr, Modes.aneterr);
+				exit(1);
+			}
+			anetNonBlock(Modes.aneterr, s);
+			*services[j].socket = s;
+
+                        if (services[j].writer) {
+                            if (! (services[j].writer->data = malloc(MODES_OUT_BUF_SIZE)) ) {
+                                fprintf(stderr, "Out of memory allocating output buffer for service %s\n", services[j].descr);
+                                exit(1);
+                            }
+
+                            services[j].writer->socket = s;
+                            services[j].writer->connections = 0;
+                            services[j].writer->dataUsed = 0;
+                            services[j].writer->lastWrite = mstime();
+                        }
+		} else {
+			if (Modes.debug & MODES_DEBUG_NET) printf("%s port is disabled\n", services[j].descr);
+		}
+    }
+
+#ifndef _WIN32
+    signal(SIGPIPE, SIG_IGN);
+#endif
+}
+//
+//=========================================================================
+//
+// This function gets called from time to time when the decoding thread is
+// awakened by new data arriving. This usually happens a few times every second
+//
+struct client * modesAcceptClients(void) {
+    int fd, port;
+    unsigned int j;
+    struct client *c;
+
+    for (j = 0; j < MODES_NET_SERVICES_NUM; j++) {
+		if (services[j].enabled) {
+			fd = anetTcpAccept(Modes.aneterr, *services[j].socket, NULL, &port);
+			if (fd == -1) continue;
+
+			anetNonBlock(Modes.aneterr, fd);
+			c = (struct client *) malloc(sizeof(*c));
+			c->service    = *services[j].socket;
+			c->next       = Modes.clients;
+			c->fd         = fd;
+			c->buflen     = 0;
+			Modes.clients = c;
+			anetSetSendBuffer(Modes.aneterr,fd, (MODES_NET_SNDBUF_SIZE << Modes.net_sndbuf_size));
+
+			if (services[j].writer) {
+				if (++ services[j].writer->connections == 1) {
+                                    services[j].writer->lastWrite = mstime(); // suppress heartbeat initially
+				}
+			}
+
+			j--; // Try again with the same listening port
+
+			if (Modes.debug & MODES_DEBUG_NET)
+				printf("Created new client %d\n", fd);
+		}
+    }
+    return Modes.clients;
+}
+//
+//=========================================================================
+//
+// On error free the client, collect the structure, adjust maxfd if needed.
+//
+void modesCloseClient(struct client *c) {
+    int j;
+
+    // Clean up, but defer removing from the list until modesNetCleanup().
+    // This is because there may be stackframes still pointing at this
+    // client (unpredictably: reading from client A may cause client B to
+    // be freed)
+
+    close(c->fd);
+
+    for (j = 0; j < MODES_NET_SERVICES_NUM; j++) {
+        if (c->service == *services[j].socket) {
+            if (services[j].writer)
+                services[j].writer->connections--;
+	    break;
+        }
+    }
+
+    if (Modes.debug & MODES_DEBUG_NET)
+        printf("Closing client %d\n", c->fd);
+
+    // mark it as inactive and ready to be freed
+    c->fd = -1;
+    c->service = -1;
+}
+//
+//=========================================================================
+//
+// Send the write buffer for the specified writer to all connected clients
+//
+static void flushWrites(struct net_writer *writer) {
+    struct client *c;
+
+    for (c = Modes.clients; c; c = c->next) {
+        if (c->service == writer->socket) {
+#ifndef _WIN32
+            int nwritten = write(c->fd, writer->data, writer->dataUsed);
+#else
+            int nwritten = send(c->fd, writer->data, writer->dataUsed, 0 );
+#endif
+            if (nwritten != writer->dataUsed) {
+                modesCloseClient(c);
+            }
+        }
+    }
+
+    writer->dataUsed = 0;
+    writer->lastWrite = mstime();
+}
+
+// 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 ||
+	    !writer->connections ||
+	    !writer->data)
+		return NULL;
+
+	if (len > MODES_OUT_BUF_SIZE)
+		return NULL;
+
+	if (writer->dataUsed + len >= MODES_OUT_BUF_SIZE) {
+		// Flush now to free some space
+		flushWrites(writer);
+	}
+
+	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) {
+	writer->dataUsed = endptr - writer->data;
+
+	if (writer->dataUsed >= Modes.net_output_flush_size) {
+		flushWrites(writer);
+	}
+}
+
+//
+//=========================================================================
+//
+// Write raw output in Beast Binary format with Timestamp to TCP clients
+//
+void modesSendBeastOutput(struct modesMessage *mm) {
+    int  msgLen = mm->msgbits / 8;
+    char *p = prepareWrite(&Modes.beast_out, 2 + 2 * (7 + msgLen));
+    char * pTimeStamp;
+    char ch;
+    int  j;
+    unsigned char *msg = (Modes.net_verbatim ? mm->verbatim : mm->msg);
+
+    if (!p)
+        return;
+
+    *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;}
+
+    pTimeStamp = (char *) &mm->timestampMsg;
+    for (j = 5; j >= 0; j--) {
+        *p++ = (ch = pTimeStamp[j]);
+        if (0x1A == ch) {*p++ = ch; }
+    }
+
+    *p++ = ch = (char) round(sqrt(mm->signalLevel) * 255);
+    if (0x1A == ch) {*p++ = ch; }
+
+    for (j = 0; j < msgLen; j++) {
+        *p++ = (ch = msg[j]);
+        if (0x1A == ch) {*p++ = ch; }
+    }
+
+    completeWrite(&Modes.beast_out, p);
+}
+
+//
+//=========================================================================
+//
+// Write raw output to TCP clients
+//
+void modesSendRawOutput(struct modesMessage *mm) {
+    int  msgLen = mm->msgbits / 8;
+    char *p = prepareWrite(&Modes.raw_out, msgLen*2 + 15);
+    int j;
+    unsigned char * pTimeStamp;
+    unsigned char *msg = (Modes.net_verbatim ? mm->verbatim : mm->msg);
+
+    if (!p)
+        return;
+
+    if (Modes.mlat && mm->timestampMsg) {
+        *p++ = '@';
+        pTimeStamp = (unsigned char *) &mm->timestampMsg;
+        for (j = 5; j >= 0; j--) {
+            sprintf(p, "%02X", pTimeStamp[j]);
+            p += 2;
+        }
+    } else
+        *p++ = '*';
+
+    for (j = 0; j < msgLen; j++) {
+        sprintf(p, "%02X", msg[j]);
+        p += 2;
+    }
+
+    *p++ = ';';
+    *p++ = '\n';
+
+    completeWrite(&Modes.raw_out, p);
+}
+//
+//=========================================================================
+//
+// Write SBS output to TCP clients
+// The message structure mm->bFlags tells us what has been updated by this message
+//
+void modesSendSBSOutput(struct modesMessage *mm) {
+    char *p;
+    struct timespec now;
+    struct tm    stTime_receive, stTime_now;
+    int          msgType;
+
+    // For now, suppress non-ICAO addresses
+    if (mm->addr & MODES_NON_ICAO_ADDRESS)
+        return;
+
+    p = prepareWrite(&Modes.sbs_out, 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
+    //
+
+    if (mm->msgtype == -1) {
+        // heartbeat
+        p += sprintf(p, "\r\n");
+        completeWrite(&Modes.sbs_out, p);
+        return;
+    }
+
+    // Decide on the basic SBS Message Type
+    if        ((mm->msgtype ==  4) || (mm->msgtype == 20)) {
+        msgType = 5;
+    } else if ((mm->msgtype ==  5) || (mm->msgtype == 21)) {
+        msgType = 6;
+    } else if ((mm->msgtype ==  0) || (mm->msgtype == 16)) {
+        msgType = 7;
+    } else if  (mm->msgtype == 11) {
+        msgType = 8;
+    } else if ((mm->msgtype != 17) && (mm->msgtype != 18)) {
+        return;
+    } else if ((mm->metype >= 1) && (mm->metype <=  4)) {
+        msgType = 1;
+    } else if ((mm->metype >= 5) && (mm->metype <=  8)) {
+        if (mm->bFlags & MODES_ACFLAGS_LATLON_VALID)
+            {msgType = 2;}
+        else
+            {msgType = 7;}
+    } else if ((mm->metype >= 9) && (mm->metype <= 18)) {
+        if (mm->bFlags & MODES_ACFLAGS_LATLON_VALID)
+            {msgType = 3;}
+        else
+            {msgType = 7;}
+    } else if (mm->metype !=  19) {
+        return;
+    } else if ((mm->mesub == 1) || (mm->mesub == 2)) {
+        msgType = 4;
+    } else {
+        return;
+    }
+
+    // Fields 1 to 6 : SBS message type and ICAO address of the aircraft and some other stuff
+    p += sprintf(p, "MSG,%d,111,11111,%06X,111111,", msgType, mm->addr); 
+
+    // Find current system time
+    clock_gettime(CLOCK_REALTIME, &now);
+    localtime_r(&now.tv_sec, &stTime_now);
+
+    // Find message reception time
+    localtime_r(&mm->sysTimestampMsg.tv_sec, &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->sysTimestampMsg.tv_nsec / 1000000U));
+
+    // 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->bFlags & MODES_ACFLAGS_CALLSIGN_VALID) {p += sprintf(p, ",%s", mm->flight);}
+    else                                           {p += sprintf(p, ",");}
+
+    // Field 12 is the altitude (if we have it) - force to zero if we're on the ground
+    if ((mm->bFlags & MODES_ACFLAGS_AOG_GROUND) == MODES_ACFLAGS_AOG_GROUND) {
+        p += sprintf(p, ",0");
+    } else if (mm->bFlags & MODES_ACFLAGS_ALTITUDE_VALID) {
+        p += sprintf(p, ",%d", mm->altitude);
+    } else {
+        p += sprintf(p, ",");
+    }
+
+    // Field 13 is the ground Speed (if we have it)
+    if (mm->bFlags & MODES_ACFLAGS_SPEED_VALID) {
+        p += sprintf(p, ",%d", mm->velocity);
+    } else {
+        p += sprintf(p, ","); 
+    }
+
+    // Field 14 is the ground Heading (if we have it)       
+    if (mm->bFlags & MODES_ACFLAGS_HEADING_VALID) {
+        p += sprintf(p, ",%d", mm->heading);
+    } else {
+        p += sprintf(p, ",");
+    }
+
+    // Fields 15 and 16 are the Lat/Lon (if we have it)
+    if (mm->bFlags & MODES_ACFLAGS_LATLON_VALID) {p += sprintf(p, ",%1.5f,%1.5f", mm->fLat, mm->fLon);}
+    else                                         {p += sprintf(p, ",,");}
+
+    // Field 17 is the VerticalRate (if we have it)
+    if (mm->bFlags & MODES_ACFLAGS_VERTRATE_VALID) {p += sprintf(p, ",%d", mm->vert_rate);}
+    else                                           {p += sprintf(p, ",");}
+
+    // Field 18 is  the Squawk (if we have it)
+    if (mm->bFlags & MODES_ACFLAGS_SQUAWK_VALID) {p += sprintf(p, ",%x", mm->modeA);}
+    else                                         {p += sprintf(p, ",");}
+
+    // Field 19 is the Squawk Changing Alert flag (if we have it)
+    if (mm->bFlags & MODES_ACFLAGS_FS_VALID) {
+        if ((mm->fs >= 2) && (mm->fs <= 4)) {
+            p += sprintf(p, ",-1");
+        } else {
+            p += sprintf(p, ",0");
+        }
+    } else {
+        p += sprintf(p, ",");
+    }
+
+    // Field 20 is the Squawk Emergency flag (if we have it)
+    if (mm->bFlags & MODES_ACFLAGS_SQUAWK_VALID) {
+        if ((mm->modeA == 0x7500) || (mm->modeA == 0x7600) || (mm->modeA == 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->bFlags & MODES_ACFLAGS_FS_VALID) {
+        if ((mm->fs >= 4) && (mm->fs <= 5)) {
+            p += sprintf(p, ",-1");
+        } else {
+            p += sprintf(p, ",0");
+        }
+    } else {
+        p += sprintf(p, ",");
+    }
+
+    // Field 22 is the OnTheGround flag (if we have it)
+    if (mm->bFlags & MODES_ACFLAGS_AOG_VALID) {
+        if (mm->bFlags & MODES_ACFLAGS_AOG) {
+            p += sprintf(p, ",-1");
+        } else {
+            p += sprintf(p, ",0");
+        }
+    } else {
+        p += sprintf(p, ",");
+    }
+
+    p += sprintf(p, "\r\n");
+
+    completeWrite(&Modes.sbs_out, p);
+}
+//
+//=========================================================================
+//
+void modesQueueOutput(struct modesMessage *mm) {
+    modesSendSBSOutput(mm);
+    modesSendBeastOutput(mm);
+    modesSendRawOutput(mm);
+}
+//
+//=========================================================================
+//
+// 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.
+//
+int decodeBinMessage(struct client *c, char *p) {
+    int msgLen = 0;
+    int  j;
+    char ch;
+    char * ptr;
+    unsigned char msg[MODES_LONG_MSG_BYTES];
+    struct modesMessage mm;
+    MODES_NOTUSED(c);
+    memset(&mm, 0, sizeof(mm));
+
+    ch = *p++; /// Get the message type
+    if (0x1A == ch) {p++;} 
+
+    if       ((ch == '1') && (Modes.mode_ac)) { // skip ModeA/C unless user enables --modes-ac
+        msgLen = MODEAC_MSG_BYTES;
+    } else if (ch == '2') {
+        msgLen = MODES_SHORT_MSG_BYTES;
+    } else if (ch == '3') {
+        msgLen = MODES_LONG_MSG_BYTES;
+    }
+
+    if (msgLen) {
+        // 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;
+
+        ptr = (char*) &mm.timestampMsg;
+        for (j = 0; j < 6; j++) { // Grab the timestamp (big endian format)
+            ptr[5-j] = ch = *p++; 
+            if (0x1A == ch) {p++;}
+        }
+
+        // record reception time as the time we read it.
+        clock_gettime(CLOCK_REALTIME, &mm.sysTimestampMsg);
+
+        ch = *p++;  // Grab the signal level
+        mm.signalLevel = ((unsigned char)ch / 256.0);
+        mm.signalLevel = mm.signalLevel * mm.signalLevel + 1e-5;
+        if (0x1A == ch) {p++;}
+
+        for (j = 0; j < msgLen; j++) { // and the data
+            msg[j] = ch = *p++;
+            if (0x1A == ch) {p++;}
+        }
+
+        if (msgLen == MODEAC_MSG_BYTES) { // ModeA or ModeC
+            Modes.stats_current.remote_received_modeac++;
+            decodeModeAMessage(&mm, ((msg[0] << 8) | msg[1]));
+        } else {
+            int result;
+
+            Modes.stats_current.remote_received_modes++;
+            result = decodeModesMessage(&mm, msg);
+            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]++;
+            }
+        }
+
+        useModesMessage(&mm);
+    }
+    return (0);
+}
+//
+//=========================================================================
+//
+// Turn an hex digit into its 4 bit decimal value.
+// Returns -1 if the digit is not in the 0-F range.
+//
+int hexDigitVal(int c) {
+    c = tolower(c);
+    if (c >= '0' && c <= '9') return c-'0';
+    else if (c >= 'a' && c <= 'f') return c-'a'+10;
+    else 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.
+//
+int decodeHexMessage(struct client *c, char *hex) {
+    int l = strlen(hex), j;
+    unsigned char msg[MODES_LONG_MSG_BYTES];
+    struct modesMessage mm;
+    MODES_NOTUSED(c);
+    memset(&mm, 0, sizeof(mm));
+
+    // 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 =    1e-5;
+
+    // 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--;
+    }
+
+    // Turn the message into binary.
+    // Accept *-AVR raw @-AVR/BEAST timeS+raw %-AVR timeS+raw (CRC good) <-BEAST timeS+sigL+raw
+    // and some AVR records that we can understand
+    if (hex[l-1] != ';') {return (0);} // not complete - abort
+
+    switch(hex[0]) {
+        case '<': {
+            mm.signalLevel = ((hexDigitVal(hex[13])<<4) | hexDigitVal(hex[14])) / 256.0;
+            mm.signalLevel = mm.signalLevel * mm.signalLevel + 1e-5;
+            hex += 15; l -= 16; // Skip <, timestamp and siglevel, and ;
+            break;}
+
+        case '@':     // No CRC check
+        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.
+    clock_gettime(CLOCK_REALTIME, &mm.sysTimestampMsg);
+
+    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, msg);
+        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]++;
+        }
+    }
+
+    useModesMessage(&mm);
+    return (0);
+}
+//
+//=========================================================================
+//
+// Return a description of planes in json. No metric conversion
+//
+
+// usual caveats about function-returning-pointer-to-static-buffer apply
+static const char *jsonEscapeString(const char *str) {
+    static char buf[1024];
+    const char *in = str;
+    char *out = buf, *end = buf + sizeof(buf) - 10;
+
+    for (; *in && out < end; ++in) {
+        unsigned char ch = *in;
+        if (ch == '"' || ch == '\\') {
+            *out++ = '\\';
+            *out++ = ch;
+        } else if (ch < 32 || ch > 127) {
+            out += snprintf(out, end - out, "\\u%04x", ch);
+        } else {
+            *out++ = ch;
+        }
+    }
+
+    *out++ = 0;
+    return buf;
+}
+
+char *generateAircraftJson(const char *url_path, int *len) {
+    uint64_t now = mstime();
+    struct aircraft *a;
+    int buflen = 1024; // The initial buffer is incremented as needed
+    char *buf = (char *) malloc(buflen), *p = buf, *end = buf+buflen;
+    int first = 1;
+
+    MODES_NOTUSED(url_path);
+
+    p += snprintf(p, end-p,
+                  "{ \"now\" : %.1f,\n"
+                  "  \"messages\" : %u,\n"
+                  "  \"aircraft\" : [",
+                  now / 1000.0,
+                  Modes.stats_current.messages_total + Modes.stats_alltime.messages_total);
+
+    for (a = Modes.aircrafts; a; a = a->next) {
+        if (a->modeACflags & MODEAC_MSG_FLAG) { // skip any fudged ICAO records Mode A/C
+            continue;
+        }
+
+        if (a->messages < 2) { // basic filter for bad decodes
+            continue;
+        }
+
+        if (first)            
+            first = 0;
+        else
+            *p++ = ',';
+            
+        p += snprintf(p, end-p, "\n    {\"hex\":\"%s%06x\"", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF);
+        if (a->bFlags & MODES_ACFLAGS_SQUAWK_VALID)
+            p += snprintf(p, end-p, ",\"squawk\":\"%04x\"", a->modeA);
+        if (a->bFlags & MODES_ACFLAGS_CALLSIGN_VALID)
+            p += snprintf(p, end-p, ",\"flight\":\"%s\"", jsonEscapeString(a->flight));
+        if (a->bFlags & MODES_ACFLAGS_LATLON_VALID)
+            p += snprintf(p, end-p, ",\"lat\":%f,\"lon\":%f,\"nucp\":%u,\"seen_pos\":%.1f", a->lat, a->lon, a->pos_nuc, (now - a->seenLatLon)/1000.0);
+        if ((a->bFlags & MODES_ACFLAGS_AOG_VALID) && (a->bFlags & MODES_ACFLAGS_AOG))
+            p += snprintf(p, end-p, ",\"altitude\":\"ground\"");
+        else if (a->bFlags & MODES_ACFLAGS_ALTITUDE_VALID)
+            p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude);
+        if (a->bFlags & MODES_ACFLAGS_VERTRATE_VALID)
+            p += snprintf(p, end-p, ",\"vert_rate\":%d", a->vert_rate);
+        if (a->bFlags & MODES_ACFLAGS_HEADING_VALID)
+            p += snprintf(p, end-p, ",\"track\":%d", a->track);
+        if (a->bFlags & MODES_ACFLAGS_SPEED_VALID)
+            p += snprintf(p, end-p, ",\"speed\":%d", a->speed);
+
+        p += snprintf(p, end-p, ",\"messages\":%ld,\"seen\":%.1f,\"rssi\":%.1f}",
+                      a->messages, (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] + 1e-5) / 8));
+        
+        // If we're getting near the end of the buffer, expand it.
+        if ((end - p) < 256) {
+            int used = p - buf;
+            buflen *= 2;
+            buf = (char *) realloc(buf, buflen);
+            p = buf+used;
+            end = buf + buflen;
+        }
+    }
+
+    p += snprintf(p, end-p, "\n  ]\n}\n");
+    *len = p-buf;
+    return buf;
+}
+
+static char * appendStatsJson(char *p,
+                              char *end,
+                              struct stats *st,
+                              const char *key)
+{
+    int i;
+
+    p += snprintf(p, end-p,
+                  "\"%s\":{\"start\":%.1f,\"end\":%.1f",
+                  key,
+                  st->start / 1000.0,
+                  st->end / 1000.0);
+
+    if (!Modes.net_only) {
+        p += snprintf(p, end-p,
+                      ",\"local\":{\"blocks_processed\":%u"
+                      ",\"blocks_dropped\":%u"
+                      ",\"modeac\":%u"
+                      ",\"modes\":%u"
+                      ",\"bad\":%u"
+                      ",\"unknown_icao\":%u",
+                      st->blocks_processed,
+                      st->blocks_dropped,
+                      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 += snprintf(p, end-p, ",\"accepted\":[%u", st->demod_accepted[i]);
+            else p += snprintf(p, end-p, ",%u", st->demod_accepted[i]);
+        }
+
+        p += snprintf(p, end-p, "]");
+
+        if (st->signal_power_count > 0)
+            p += snprintf(p, end-p,",\"signal\":%.1f", 10 * log10(st->signal_power_sum / st->signal_power_count));
+        if (st->noise_power_count > 0)
+            p += snprintf(p, end-p,",\"noise\":%.1f", 10 * log10(st->noise_power_sum / st->noise_power_count));
+        if (st->peak_signal_power > 0)
+            p += snprintf(p, end-p,",\"peak_signal\":%.1f", 10 * log10(st->peak_signal_power));
+
+        p += snprintf(p, end-p,",\"strong_signals\":%d}", st->strong_signal_count);
+    }
+
+    if (Modes.net) {
+        p += snprintf(p, end-p,
+                      ",\"remote\":{\"modeac\":%u"
+                      ",\"modes\":%u"
+                      ",\"bad\":%u"
+                      ",\"unknown_icao\":%u",
+                      st->remote_received_modeac,
+                      st->remote_received_modes,
+                      st->remote_rejected_bad,
+                      st->remote_rejected_unknown_icao);
+
+        for (i=0; i <= Modes.nfix_crc; ++i) {
+            if (i == 0) p += snprintf(p, end-p, ",\"accepted\":[%u", st->remote_accepted[i]);
+            else p += snprintf(p, end-p, ",%u", st->remote_accepted[i]);
+        }
+
+        p += snprintf(p, end-p, "]");
+
+        p += snprintf(p, end-p, "},\"http_requests\":%u", st->http_requests);
+    }
+
+    {
+        uint64_t demod_cpu_millis = (uint64_t)st->demod_cpu.tv_sec*1000UL + st->demod_cpu.tv_nsec/1000000UL;
+        uint64_t reader_cpu_millis = (uint64_t)st->reader_cpu.tv_sec*1000UL + st->reader_cpu.tv_nsec/1000000UL;
+        uint64_t background_cpu_millis = (uint64_t)st->background_cpu.tv_sec*1000UL + st->background_cpu.tv_nsec/1000000UL;
+
+        p += snprintf(p, end-p,
+                      ",\"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}"
+                      ",\"cpu\":{\"demod\":%llu,\"reader\":%llu,\"background\":%llu}"
+                      ",\"tracks\":{\"all\":%u"
+                      ",\"single_message\":%u}"
+                      ",\"messages\":%u}",
+                      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,
+                      (unsigned long long)demod_cpu_millis,
+                      (unsigned long long)reader_cpu_millis,
+                      (unsigned long long)background_cpu_millis,
+                      st->unique_aircraft,
+                      st->single_message_aircraft,
+                      st->messages_total);
+    }
+
+    return p;
+}
+    
+char *generateStatsJson(const char *url_path, int *len) {
+    struct stats add;
+    char *buf = (char *) malloc(4096), *p = buf, *end = buf + 4096;
+
+    MODES_NOTUSED(url_path);
+
+    p += snprintf(p, end-p, "{\n");
+    p = appendStatsJson(p, end, &Modes.stats_current, "latest");
+    p += snprintf(p, end-p, ",\n");
+
+    p = appendStatsJson(p, end, &Modes.stats_1min[Modes.stats_latest_1min], "last1min");
+    p += snprintf(p, end-p, ",\n");
+
+    p = appendStatsJson(p, end, &Modes.stats_5min, "last5min");
+    p += snprintf(p, end-p, ",\n");
+
+    p = appendStatsJson(p, end, &Modes.stats_15min, "last15min");
+    p += snprintf(p, end-p, ",\n");
+
+    add_stats(&Modes.stats_alltime, &Modes.stats_current, &add);
+    p = appendStatsJson(p, end, &add, "total");
+    p += snprintf(p, end-p, "\n}\n");    
+
+    assert(p <= end);
+
+    *len = p-buf;
+    return buf;
+}
+
+//
+// Return a description of the receiver in json.
+//
+char *generateReceiverJson(const char *url_path, int *len)
+{
+    char *buf = (char *) malloc(1024), *p = buf;
+    int history_size;
+
+    MODES_NOTUSED(url_path);
+
+    // work out number of valid history entries
+    if (Modes.json_aircraft_history[HISTORY_SIZE-1].content == NULL)
+        history_size = Modes.json_aircraft_history_next;
+    else
+        history_size = HISTORY_SIZE;
+
+    p += sprintf(p, "{ " \
+                 "\"version\" : \"%s\", "
+                 "\"refresh\" : %.0f, "
+                 "\"history\" : %d",
+                 MODES_DUMP1090_VERSION, 1.0*Modes.json_interval, history_size);
+
+    if (Modes.json_location_accuracy && (Modes.fUserLat != 0.0 || Modes.fUserLon != 0.0)) {
+        if (Modes.json_location_accuracy == 1) {
+            p += sprintf(p, ", "                \
+                         "\"lat\" : %.2f, "
+                         "\"lon\" : %.2f",
+                         Modes.fUserLat, Modes.fUserLon);  // round to 2dp - about 0.5-1km accuracy - for privacy reasons
+        } else {
+            p += sprintf(p, ", "                \
+                         "\"lat\" : %.6f, "
+                         "\"lon\" : %.6f",
+                         Modes.fUserLat, Modes.fUserLon);  // exact location
+        }
+    }
+
+    p += sprintf(p, " }\n");
+
+    *len = (p - buf);
+    return buf;
+}
+
+char *generateHistoryJson(const char *url_path, int *len)
+{
+    int history_index = -1;
+
+    if (sscanf(url_path, "/data/history_%d.json", &history_index) != 1)
+        return NULL;
+
+    if (history_index < 0 || history_index >= HISTORY_SIZE)
+        return NULL;
+
+    if (!Modes.json_aircraft_history[history_index].content)
+        return NULL;
+
+    *len = Modes.json_aircraft_history[history_index].clen;
+    return strdup(Modes.json_aircraft_history[history_index].content);
+}
+
+// Write JSON to file
+void writeJsonToFile(const char *file, char * (*generator) (const char *,int*))
+{
+#ifndef _WIN32
+    char pathbuf[PATH_MAX];
+    char tmppath[PATH_MAX];
+    int fd;
+    int len = 0;
+    mode_t mask;
+    char *content;
+
+    if (!Modes.json_dir)
+        return;
+
+    snprintf(tmppath, PATH_MAX, "%s/%s.XXXXXX", Modes.json_dir, file);
+    tmppath[PATH_MAX-1] = 0;
+    fd = mkstemp(tmppath);
+    if (fd < 0)
+        return;
+    
+    mask = umask(0);
+    umask(mask);
+    fchmod(fd, 0644 & ~mask);
+
+    snprintf(pathbuf, PATH_MAX, "/data/%s", file);
+    pathbuf[PATH_MAX-1] = 0;
+    content = generator(pathbuf, &len);
+
+    if (write(fd, content, len) != len)
+        goto error_1;
+
+    if (close(fd) < 0)
+        goto error_2;
+
+    snprintf(pathbuf, PATH_MAX, "%s/%s", Modes.json_dir, file);
+    pathbuf[PATH_MAX-1] = 0;
+    rename(tmppath, pathbuf);
+    free(content);
+    return;
+
+ error_1:
+    close(fd);
+ error_2:
+    unlink(tmppath);
+    free(content);
+    return;
+#endif
+}
+
+
+
+//
+//=========================================================================
+//
+#define MODES_CONTENT_TYPE_HTML "text/html;charset=utf-8"
+#define MODES_CONTENT_TYPE_CSS  "text/css;charset=utf-8"
+#define MODES_CONTENT_TYPE_JSON "application/json;charset=utf-8"
+#define MODES_CONTENT_TYPE_JS   "application/javascript;charset=utf-8"
+#define MODES_CONTENT_TYPE_GIF  "image/gif"
+
+static struct {
+    char *path;
+    char * (*handler)(const char*,int*);
+    char *content_type;
+    int prefix;
+} url_handlers[] = {
+    { "/data/aircraft.json", generateAircraftJson, MODES_CONTENT_TYPE_JSON, 0 },
+    { "/data/receiver.json", generateReceiverJson, MODES_CONTENT_TYPE_JSON, 0 },
+    { "/data/stats.json", generateStatsJson, MODES_CONTENT_TYPE_JSON, 0 },
+    { "/data/history_", generateHistoryJson, MODES_CONTENT_TYPE_JSON, 1 },
+    { NULL, NULL, NULL, 0 }
+};
+
+//
+// Get an HTTP request header and write the response to the client.
+// gain here we assume that the socket buffer is enough without doing
+// any kind of userspace buffering.
+//
+// Returns 1 on error to signal the caller the client connection should
+// be closed.
+//
+int handleHTTPRequest(struct client *c, char *p) {
+    char hdr[512];
+    int clen, hdrlen;
+    int httpver, keepalive;
+    int statuscode = 500;
+    const char *statusmsg = "Internal Server Error";
+    char *url, *content = NULL;
+    char *ext;
+    char *content_type;
+    int i;
+
+    if (Modes.debug & MODES_DEBUG_NET)
+        printf("\nHTTP request: %s\n", c->buf);
+
+    // Minimally parse the request.
+    httpver = (strstr(p, "HTTP/1.1") != NULL) ? 11 : 10;
+    if (httpver == 10) {
+        // HTTP 1.0 defaults to close, unless otherwise specified.
+        //keepalive = strstr(p, "Connection: keep-alive") != NULL;
+    } else if (httpver == 11) {
+        // HTTP 1.1 defaults to keep-alive, unless close is specified.
+        //keepalive = strstr(p, "Connection: close") == NULL;
+    }
+    keepalive = 0;
+
+    // Identify he URL.
+    p = strchr(p,' ');
+    if (!p) return 1; // There should be the method and a space
+    url = ++p;        // Now this should point to the requested URL
+    p = strchr(p, ' ');
+    if (!p) return 1; // There should be a space before HTTP/
+    *p = '\0';
+
+    if (Modes.debug & MODES_DEBUG_NET) {
+        printf("\nHTTP keep alive: %d\n", keepalive);
+        printf("HTTP requested URL: %s\n\n", url);
+    }
+    
+    // Ditch any trailing query part (AJAX might add one to avoid caching)
+    p = strchr(url, '?');
+    if (p) *p = 0;
+
+    statuscode = 404;
+    statusmsg = "Not Found";
+    for (i = 0; url_handlers[i].path; ++i) {
+        if ((url_handlers[i].prefix && !strncmp(url, url_handlers[i].path, strlen(url_handlers[i].path))) ||
+            (!url_handlers[i].prefix && !strcmp(url, url_handlers[i].path))) {
+            content_type = url_handlers[i].content_type;
+            content = url_handlers[i].handler(url, &clen);
+            if (!content)
+                continue;
+
+            statuscode = 200;
+            statusmsg = "OK";
+            if (Modes.debug & MODES_DEBUG_NET) {
+                printf("HTTP: 200: %s -> internal (%d bytes, %s)\n", url, clen, content_type);
+            }
+            break;
+        }
+    }
+            
+    if (!content) {
+        struct stat sbuf;
+        int fd = -1;
+        char rp[PATH_MAX], hrp[PATH_MAX];
+        char getFile[1024];
+
+        if (strlen(url) < 2) {
+            snprintf(getFile, sizeof getFile, "%s/gmap.html", HTMLPATH); // Default file
+        } else {
+            snprintf(getFile, sizeof getFile, "%s/%s", HTMLPATH, url);
+        }
+
+        if (!realpath(getFile, rp))
+            rp[0] = 0;
+        if (!realpath(HTMLPATH, hrp))
+            strcpy(hrp, HTMLPATH);
+
+        clen = -1;
+        content = strdup("Server error occured");
+        if (!strncmp(hrp, rp, strlen(hrp))) {
+            if (stat(getFile, &sbuf) != -1 && (fd = open(getFile, O_RDONLY)) != -1) {
+                content = (char *) realloc(content, sbuf.st_size);
+                if (read(fd, content, sbuf.st_size) != -1) {
+                    clen = sbuf.st_size;
+                    statuscode = 200;
+                    statusmsg = "OK";
+                }
+            }
+        } else {
+            errno = ENOENT;
+        }
+
+        if (clen < 0) {
+            content = realloc(content, 128);
+            clen = snprintf(content, 128,"Error opening HTML file: %s", strerror(errno));
+            statuscode = 404;
+            statusmsg = "Not Found";
+        }
+        
+        if (fd != -1) {
+            close(fd);
+        }
+
+        // Get file extension and content type
+        content_type = MODES_CONTENT_TYPE_HTML; // Default content type
+        ext = strrchr(getFile, '.');
+        
+        if (strlen(ext) > 0) {
+            if (strstr(ext, ".json")) {
+                content_type = MODES_CONTENT_TYPE_JSON;
+            } else if (strstr(ext, ".css")) {
+                content_type = MODES_CONTENT_TYPE_CSS;
+            } else if (strstr(ext, ".js")) {
+                content_type = MODES_CONTENT_TYPE_JS;
+            } else if (strstr(ext, ".gif")) {
+                content_type = MODES_CONTENT_TYPE_GIF;
+            }
+        }
+
+        if (Modes.debug & MODES_DEBUG_NET) {
+            printf("HTTP: %d %s: %s -> %s (%d bytes, %s)\n", statuscode, statusmsg, url, rp, clen, content_type);
+        }
+    }
+
+
+    // Create the header and send the reply
+    hdrlen = snprintf(hdr, sizeof(hdr),
+        "HTTP/1.1 %d %s\r\n"
+        "Server: Dump1090\r\n"
+        "Content-Type: %s\r\n"
+        "Connection: %s\r\n"
+        "Content-Length: %d\r\n"
+        "Cache-Control: no-cache, must-revalidate\r\n"
+        "Expires: Sat, 26 Jul 1997 05:00:00 GMT\r\n"
+        "\r\n",
+        statuscode, statusmsg,
+        content_type,
+        keepalive ? "keep-alive" : "close",
+        clen);
+
+    if (Modes.debug & MODES_DEBUG_NET) {
+        printf("HTTP Reply header:\n%s", hdr);
+    }
+
+    // Send header and content.
+#ifndef _WIN32
+    if ( (write(c->fd, hdr, hdrlen) != hdrlen) 
+      || (write(c->fd, content, clen) != clen) ) {
+#else
+    if ( (send(c->fd, hdr, hdrlen, 0) != hdrlen) 
+      || (send(c->fd, content, clen, 0) != clen) ) {
+#endif
+        free(content);
+        return 1;
+    }
+    free(content);
+    Modes.stats_current.http_requests++;
+    return !keepalive;
+}
+//
+//=========================================================================
+//
+// This function polls the clients using read() in order to receive new
+// messages from the net.
+//
+// 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.
+//
+void modesReadFromClient(struct client *c, char *sep,
+                         int(*handler)(struct client *, char *)) {
+    int left;
+    int nread;
+    int fullmsg;
+    int bContinue = 1;
+    char *s, *e, *p;
+
+    while(bContinue) {
+
+        fullmsg = 0;
+        left = MODES_CLIENT_BUF_SIZE - c->buflen;
+        // If our buffer is full discard it, this is some badly formatted shit
+        if (left <= 0) {
+            c->buflen = 0;
+            left = MODES_CLIENT_BUF_SIZE;
+            // If there is garbage, read more to discard it ASAP
+        }
+#ifndef _WIN32
+        nread = read(c->fd, c->buf+c->buflen, left);
+#else
+        nread = recv(c->fd, c->buf+c->buflen, left, 0);
+        if (nread < 0) {errno = WSAGetLastError();}
+#endif
+
+        // If we didn't get all the data we asked for, then return once we've processed what we did get.
+        if (nread != left) {
+            bContinue = 0;
+        }
+
+        if (nread == 0) { // End of file
+            modesCloseClient(c);
+            return;
+        }
+
+#ifndef _WIN32
+        if (nread < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { // No data available (not really an error)
+#else
+        if (nread < 0 && errno == EWOULDBLOCK) { // No data available (not really an error)
+#endif
+            return;
+        }
+
+        if (nread < 0) { // Other errors
+            modesCloseClient(c);
+            return;
+        }
+
+        c->buflen += nread;
+
+        // Always null-term so we are free to use strstr() (it won't affect binary case)
+        c->buf[c->buflen] = '\0';
+
+        e = s = c->buf;                                // Start with the start of buffer, first message
+
+        if (c->service == Modes.bis) {
+            // 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.
+
+            left = c->buflen;                                  // Length of valid search for memchr()
+            while (left && ((s = memchr(e, (char) 0x1a, left)) != NULL)) { // The first byte of buffer 'should' be 0x1a
+                s++;                                           // skip the 0x1a
+                if        (*s == '1') {
+                    e = s + MODEAC_MSG_BYTES      + 8;         // point past remainder of message
+                } else if (*s == '2') {
+                    e = s + MODES_SHORT_MSG_BYTES + 8;
+                } else if (*s == '3') {
+                    e = s + MODES_LONG_MSG_BYTES  + 8;
+                } else {
+                    e = s;                                     // Not a valid beast message, skip
+                    left = &(c->buf[c->buflen]) - e;
+                    continue;
+                }
+                // we need to be careful of double escape characters in the message body
+                for (p = s; p < e; p++) {
+                    if (0x1A == *p) {
+                        p++; e++;
+                        if (e > &(c->buf[c->buflen])) {
+                            break;
+                        }
+                    }
+                }
+                left = &(c->buf[c->buflen]) - e;
+                if (left < 0) {                                // Incomplete message in buffer
+                    e = s - 1;                                 // point back at last found 0x1a.
+                    break;
+                }
+                // Have a 0x1a followed by 1, 2 or 3 - pass message less 0x1a to handler.
+                if (handler(c, s)) {
+                    modesCloseClient(c);
+                    return;
+                }
+                fullmsg = 1;
+            }
+            s = e;     // For the buffer remainder below
+
+        } else {
+            //
+            // 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.
+            //
+            while ((e = strstr(s, sep)) != NULL) { // end of first message if found
+                *e = '\0';                         // The handler expects null terminated strings
+                if (handler(c, s)) {               // Pass message to handler.
+                    modesCloseClient(c);           // Handler returns 1 on error to signal we .
+                    return;                        // should close the client connection
+                }
+                s = e + strlen(sep);               // Move to start of next message
+                fullmsg = 1;
+            }
+        }
+
+        if (fullmsg) {                             // We processed something - so
+            c->buflen = &(c->buf[c->buflen]) - s;  //     Update the unprocessed buffer length
+            memmove(c->buf, s, c->buflen);         //     Move what's remaining to the start of the buffer
+        } else {                                   // If no message was decoded process the next client
+            return;
+        }
+    }
+}
+
+#define TSV_MAX_PACKET_SIZE 160
+
+static void writeFATSV() {
+    struct aircraft *a;
+    uint64_t now;
+    static uint64_t next_update;
+
+    if (!Modes.fatsv_out.connections) {
+        return; // no active connections
+    }
+
+    now = mstime();
+    if (now < next_update) {
+        return;
+    }
+
+    // scan once a second at most
+    next_update = now + 1000;
+
+    for (a = Modes.aircrafts; a; a = a->next) {
+        int altValid = 0;
+        int alt = 0;
+        int groundValid = 0;
+        int ground = 0;
+        int latlonValid = 0;
+        int useful = 0;
+        uint64_t emittedMillisAgo;
+        char *p, *end;
+
+        // skip non-ICAO
+        if (a->addr & MODES_NON_ICAO_ADDRESS)
+            continue;
+
+        if (a->messages < 2)  // basic filter for bad decodes
+            continue;
+
+        // don't emit if it hasn't updated since last time
+        if (a->seen < a->fatsv_last_emitted) {
+            continue;
+        }
+
+        emittedMillisAgo = (now - a->fatsv_last_emitted);
+
+        // don't emit more than once every five seconds
+        if (emittedMillisAgo < 5000) {
+            continue;
+        }
+
+        if (a->bFlags & MODES_ACFLAGS_ALTITUDE_VALID) {
+            altValid = 1;            
+            alt = a->altitude;
+        }
+        
+        if (a->bFlags & MODES_ACFLAGS_AOG_VALID) {
+            groundValid = 1;
+
+            if (a->bFlags & MODES_ACFLAGS_AOG) {
+                alt = 0;
+                ground = 1;
+            }
+        }
+
+        if (a->bFlags & MODES_ACFLAGS_LATLON_VALID) {
+            latlonValid = 1;
+        }
+
+        // if it's over 10,000 feet, don't emit more than once every 10 seconds
+        if (alt > 10000 && emittedMillisAgo < 10000) {
+            continue;
+        }
+
+        // disable if you want only ads-b
+        // also don't send mode S very often
+        if (!latlonValid) {
+            if (emittedMillisAgo < 30000) {
+                continue;
+            }
+        } else {
+            // if it hasn't changed altitude very much and it hasn't changed 
+            // heading very much, don't update real often
+            if (abs(a->track - a->fatsv_emitted_track) < 2 && abs(alt - a->fatsv_emitted_altitude) < 50) {
+                if (alt < 10000) {
+                    // it hasn't changed much but we're below 10,000 feet 
+                    // so update more frequently
+                    if (emittedMillisAgo < 10000) {
+                        continue;
+                    }
+                } else {
+                    // above 10,000 feet, don't update so often when it 
+                    // hasn't changed much
+                    if (emittedMillisAgo < 30000) {
+                        continue;
+                    }
+                }
+            }
+        }
+
+        p = prepareWrite(&Modes.fatsv_out, TSV_MAX_PACKET_SIZE);
+        if (!p)
+            return;
+
+        end = p + TSV_MAX_PACKET_SIZE;
+#       define bufsize(_p,_e) ((_p) >= (_e) ? (size_t)0 : (size_t)((_e) - (_p)))
+        p += snprintf(p, bufsize(p,end), "clock\t%ld\thexid\t%06X", (long)(a->seen / 1000), a->addr);
+
+        if (*a->flight != '\0') {
+            p += snprintf(p, bufsize(p,end), "\tident\t%s", a->flight);
+        }
+
+        if (a->bFlags & MODES_ACFLAGS_SQUAWK_VALID) {
+            p += snprintf(p, bufsize(p,end), "\tsquawk\t%04x", a->modeA);
+        }
+
+        if (altValid) {
+            p += snprintf(p, bufsize(p,end), "\talt\t%d", alt);
+            useful = 1;
+        }
+
+        if (a->bFlags & MODES_ACFLAGS_SPEED_VALID) {
+            p += snprintf(p, bufsize(p,end), "\tspeed\t%d", a->speed);
+            useful = 1;
+        }
+
+        if (groundValid) {
+            if (ground) {
+                p += snprintf(p, bufsize(p,end), "\tairGround\tG");
+            } else {
+                p += snprintf(p, bufsize(p,end), "\tairGround\tA");
+            }
+        }
+
+        if (latlonValid) {
+            p += snprintf(p, bufsize(p,end), "\tlat\t%.5f\tlon\t%.5f", a->lat, a->lon);
+            useful = 1;
+        }
+
+        if (a->bFlags & MODES_ACFLAGS_HEADING_VALID) {
+            p += snprintf(p, bufsize(p,end), "\theading\t%d", a->track);
+            useful = 1;
+        }
+
+        // if we didn't get at least an alt or a speed or a latlon or
+        // a heading, bail out. We don't need to do anything special
+        // to unwind prepareWrite().
+        if (!useful) {
+            continue;
+        }
+
+        p += snprintf(p, bufsize(p,end), "\n");
+
+        if (p <= end)
+            completeWrite(&Modes.fatsv_out, p);
+        else
+            fprintf(stderr, "fatsv: output too large (max %d, overran by %d)\n", TSV_MAX_PACKET_SIZE, (int) (p - end));
+#       undef bufsize
+
+        a->fatsv_last_emitted = now;
+        a->fatsv_emitted_altitude = alt;
+        a->fatsv_emitted_track = a->track;
+    }
+}
+
+//
+// Perform periodic network work
+//
+void modesNetPeriodicWork(void) {
+	struct client *c, **prev;
+        uint64_t now = mstime();
+	int j;
+	int need_heartbeat = 0, need_flush = 0;
+
+	// Accept new connetions
+	modesAcceptClients();
+
+	// Read from clients
+	for (c = Modes.clients; c; c = c->next) {
+		if (c->service == Modes.ris) {
+			modesReadFromClient(c,"\n",decodeHexMessage);
+		} else if (c->service == Modes.bis) {
+			modesReadFromClient(c,"",decodeBinMessage);
+		} else if (c->service == Modes.https) {
+			modesReadFromClient(c,"\r\n\r\n",handleHTTPRequest);
+		}
+	}
+
+        // Generate FATSV output
+        writeFATSV();
+
+	// If we have generated no messages for a while, generate
+	// a dummy heartbeat message.
+	if (Modes.net_heartbeat_interval) {
+		for (j = 0; j < MODES_NET_SERVICES_NUM; j++) {
+			if (services[j].writer &&
+			    services[j].writer->connections &&
+			    (services[j].writer->lastWrite + Modes.net_heartbeat_interval) <= now) {
+				need_flush = 1;
+				if (services[j].writer->dataUsed == 0) {
+					need_heartbeat = 1;
+					break;
+				}
+			}
+		}
+        }
+
+        if (need_heartbeat) {
+		//
+		// We haven't sent any traffic for some time. To try and keep any TCP
+		// links alive, send a null frame. This will help stop any routers discarding our TCP
+		// link which will cause an un-recoverable link error if/when a real frame arrives.
+		//
+		// Fudge up a null message
+		struct modesMessage mm;
+
+		memset(&mm, 0, sizeof(mm));
+		mm.msgbits      = MODES_SHORT_MSG_BITS;
+		mm.timestampMsg = 0;
+		mm.msgtype      = -1;
+
+		// Feed output clients
+		modesQueueOutput(&mm);
+        }
+
+	// If we have data that has been waiting to be written for a while,
+	// write it now.
+	for (j = 0; j < MODES_NET_SERVICES_NUM; j++) {
+		if (services[j].writer &&
+		    services[j].writer->dataUsed &&
+		    (need_flush || (services[j].writer->lastWrite + Modes.net_output_flush_interval) <= now)) {
+			flushWrites(services[j].writer);
+		}
+	}
+
+	// Unlink and free closed clients
+	for (prev = &Modes.clients, c = *prev; c; c = *prev) {
+		if (c->fd == -1) {
+			// Recently closed, prune from list
+			*prev = c->next;
+			free(c);
+		} else {
+			prev = &c->next;
+		}
+	}
+}
+
+//
+// =============================== Network IO ===========================
+//
diff --git a/public_html/config.js b/public_html/config.js
new file mode 100644
index 0000000..81909e4
--- /dev/null
+++ b/public_html/config.js
@@ -0,0 +1,47 @@
+// --------------------------------------------------------
+//
+// This file is to configure the configurable settings.
+// Load this file before script.js file at gmap.html.
+//
+// --------------------------------------------------------
+
+// -- Output Settings -------------------------------------
+// Show metric values
+// This controls the units used in the plane table,
+// and whether metric or imperial units are shown first
+// in the detailed plane info.
+Metric = false; // true or false
+
+// -- Map settings ----------------------------------------
+// These settings are overridden by any position information
+// provided by dump1090 itself. All positions are in decimal
+// degrees.
+
+// Default center of the map.
+DefaultCenterLat = 45.0;
+DefaultCenterLon = 9.0;
+// The google maps zoom level, 0 - 16, lower is further out
+DefaultZoomLvl   = 7;
+
+SiteShow    = false;           // true to show a center marker
+SiteLat     = 45.0;            // position of the marker
+SiteLon     = 9.0;
+SiteName    = "My Radar Site"; // tooltip of the marker
+
+
+// -- Marker settings -------------------------------------
+// The default marker color
+MarkerColor	  = "rgb(127, 127, 127)";
+SelectedColor = "rgb(225, 225, 225)";
+StaleColor = "rgb(190, 190, 190)";
+
+
+SiteCircles = true; // true to show circles (only shown if the center marker is shown)
+// In nautical miles or km (depending settings value 'Metric')
+SiteCirclesDistances = new Array(100,150,200);
+
+// Show the clocks at the top of the righthand pane? You can disable the clocks if you want here
+ShowClocks = true;
+
+// Controls page title, righthand pane when nothing is selected
+PageName = "DUMP1090";
diff --git a/public_html/coolclock/coolclock.js b/public_html/coolclock/coolclock.js
new file mode 100644
index 0000000..4411974
--- /dev/null
+++ b/public_html/coolclock/coolclock.js
@@ -0,0 +1,318 @@
+/**
+ * CoolClock 2.1.4
+ * Copyright 2010, Simon Baird
+ * Released under the BSD License.
+ *
+ * Display an analog clock using canvas.
+ * http://randomibis.com/coolclock/
+ *
+ */
+
+// Constructor for CoolClock objects
+window.CoolClock = function(options) {
+	return this.init(options);
+}
+
+// Config contains some defaults, and clock skins
+CoolClock.config = {
+	tickDelay: 1000,
+	longTickDelay: 15000,
+	defaultRadius: 85,
+	renderRadius: 100,
+	defaultSkin: "chunkySwiss",
+	// Should be in skin probably...
+	// (TODO: allow skinning of digital display)
+	showSecs: true,
+	showAmPm: true,
+
+	skins:	{
+		// There are more skins in moreskins.js
+		// Try making your own skin by copy/pasting one of these and tweaking it
+		swissRail: {
+			outerBorder: { lineWidth: 2, radius:95, color: "black", alpha: 1 },
+			smallIndicator: { lineWidth: 2, startAt: 88, endAt: 92, color: "black", alpha: 1 },
+			largeIndicator: { lineWidth: 4, startAt: 79, endAt: 92, color: "black", alpha: 1 },
+			hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "black", alpha: 1 },
+			minuteHand: { lineWidth: 7, startAt: -15, endAt: 75, color: "black", alpha: 1 },
+			secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: 1 },
+			secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "red", color: "red", alpha: 1 }
+		},
+		chunkySwiss: {
+			outerBorder: { lineWidth: 4, radius:97, color: "black", alpha: 1 },
+			smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "black", alpha: 1 },
+			largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "black", alpha: 1 },
+			hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "black", alpha: 1 },
+			minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "black", alpha: 1 },
+			secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 },
+			secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 }
+		},
+		chunkySwissOnBlack: {
+			outerBorder: { lineWidth: 4, radius:97, color: "white", alpha: 1 },
+			smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "white", alpha: 1 },
+			largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "white", alpha: 1 },
+			hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "white", alpha: 1 },
+			minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "white", alpha: 1 },
+			secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 },
+			secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 }
+		}
+
+	},
+
+	// Test for IE so we can nurse excanvas in a couple of places
+	isIE: !!document.all,
+
+	// Will store (a reference to) each clock here, indexed by the id of the canvas element
+	clockTracker: {},
+
+	// For giving a unique id to coolclock canvases with no id
+	noIdCount: 0
+};
+
+// Define the CoolClock object's methods
+CoolClock.prototype = {
+
+	// Initialise using the parameters parsed from the colon delimited class
+	init: function(options) {
+		// Parse and store the options
+		this.canvasId       = options.canvasId;
+		this.skinId         = options.skinId || CoolClock.config.defaultSkin;
+		this.displayRadius  = options.displayRadius || CoolClock.config.defaultRadius;
+		this.showSecondHand = typeof options.showSecondHand == "boolean" ? options.showSecondHand : true;
+		this.gmtOffset      = (options.gmtOffset != null && options.gmtOffset != '') ? parseFloat(options.gmtOffset) : null;
+		this.showDigital    = typeof options.showDigital == "boolean" ? options.showDigital : false;
+		this.logClock       = typeof options.logClock == "boolean" ? options.logClock : false;
+		this.logClockRev    = typeof options.logClock == "boolean" ? options.logClockRev : false;
+
+		this.tickDelay      = CoolClock.config[ this.showSecondHand ? "tickDelay" : "longTickDelay" ];
+
+		// Get the canvas element
+		this.canvas = document.getElementById(this.canvasId);
+
+		// Make the canvas the requested size. It's always square.
+		this.canvas.setAttribute("width",this.displayRadius*2);
+		this.canvas.setAttribute("height",this.displayRadius*2);
+		this.canvas.style.width = this.displayRadius*2 + "px";
+		this.canvas.style.height = this.displayRadius*2 + "px";
+
+		// Explain me please...?
+		this.renderRadius = CoolClock.config.renderRadius;
+		this.scale = this.displayRadius / this.renderRadius;
+
+		// Initialise canvas context
+		this.ctx = this.canvas.getContext("2d");
+		this.ctx.scale(this.scale,this.scale);
+
+		// Keep track of this object
+		CoolClock.config.clockTracker[this.canvasId] = this;
+
+		// Start the clock going
+		this.tick();
+
+		return this;
+	},
+
+	// Draw a circle at point x,y with params as defined in skin
+	fullCircleAt: function(x,y,skin) {
+		this.ctx.save();
+		this.ctx.globalAlpha = skin.alpha;
+		this.ctx.lineWidth = skin.lineWidth;
+
+		if (!CoolClock.config.isIE) {
+			this.ctx.beginPath();
+		}
+
+		if (CoolClock.config.isIE) {
+			// excanvas doesn't scale line width so we will do it here
+			this.ctx.lineWidth = this.ctx.lineWidth * this.scale;
+		}
+
+		this.ctx.arc(x, y, skin.radius, 0, 2*Math.PI, false);
+
+		if (CoolClock.config.isIE) {
+			// excanvas doesn't close the circle so let's fill in the tiny gap
+			this.ctx.arc(x, y, skin.radius, -0.1, 0.1, false);
+		}
+
+		if (skin.fillColor) {
+			this.ctx.fillStyle = skin.fillColor
+			this.ctx.fill();
+		}
+		else {
+			// XXX why not stroke and fill
+			this.ctx.strokeStyle = skin.color;
+			this.ctx.stroke();
+		}
+		this.ctx.restore();
+	},
+
+	// Draw some text centered vertically and horizontally
+	drawTextAt: function(theText,x,y) {
+		this.ctx.save();
+		this.ctx.font = '15px sans-serif';
+		var tSize = this.ctx.measureText(theText);
+		if (!tSize.height) tSize.height = 15; // no height in firefox.. :(
+		this.ctx.fillText(theText,x - tSize.width/2,y - tSize.height/2);
+		this.ctx.restore();
+	},
+
+	lpad2: function(num) {
+		return (num < 10 ? '0' : '') + num;
+	},
+
+	tickAngle: function(second) {
+		// Log algorithm by David Bradshaw
+		var tweak = 3; // If it's lower the one second mark looks wrong (?)
+		if (this.logClock) {
+			return second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak));
+		}
+		else if (this.logClockRev) {
+			// Flip the seconds then flip the angle (trickiness)
+			second = (60 - second) % 60;
+			return 1.0 - (second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak)));
+		}
+		else {
+			return second/60.0;
+		}
+	},
+
+	timeText: function(hour,min,sec) {
+		var c = CoolClock.config;
+		return '' +
+			(c.showAmPm ? ((hour%12)==0 ? 12 : (hour%12)) : hour) + ':' +
+			this.lpad2(min) +
+			(c.showSecs ? ':' + this.lpad2(sec) : '') +
+			(c.showAmPm ? (hour < 12 ? ' am' : ' pm') : '')
+		;
+	},
+
+	// Draw a radial line by rotating then drawing a straight line
+	// Ha ha, I think I've accidentally used Taus, (see http://tauday.com/)
+	radialLineAtAngle: function(angleFraction,skin) {
+		this.ctx.save();
+		this.ctx.translate(this.renderRadius,this.renderRadius);
+		this.ctx.rotate(Math.PI * (2.0 * angleFraction - 0.5));
+		this.ctx.globalAlpha = skin.alpha;
+		this.ctx.strokeStyle = skin.color;
+		this.ctx.lineWidth = skin.lineWidth;
+
+		if (CoolClock.config.isIE)
+			// excanvas doesn't scale line width so we will do it here
+			this.ctx.lineWidth = this.ctx.lineWidth * this.scale;
+
+		if (skin.radius) {
+			this.fullCircleAt(skin.startAt,0,skin)
+		}
+		else {
+			this.ctx.beginPath();
+			this.ctx.moveTo(skin.startAt,0)
+			this.ctx.lineTo(skin.endAt,0);
+			this.ctx.stroke();
+		}
+		this.ctx.restore();
+	},
+
+	render: function(hour,min,sec) {
+		// Get the skin
+		var skin = CoolClock.config.skins[this.skinId];
+		if (!skin) skin = CoolClock.config.skins[CoolClock.config.defaultSkin];
+
+		// Clear
+		this.ctx.clearRect(0,0,this.renderRadius*2,this.renderRadius*2);
+
+		// Draw the outer edge of the clock
+		if (skin.outerBorder)
+			this.fullCircleAt(this.renderRadius,this.renderRadius,skin.outerBorder);
+
+		// Draw the tick marks. Every 5th one is a big one
+		for (var i=0;i<60;i++) {
+			(i%5)  && skin.smallIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.smallIndicator);
+			!(i%5) && skin.largeIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.largeIndicator);
+		}
+
+		// Write the time
+		if (this.showDigital) {
+			this.drawTextAt(
+				this.timeText(hour,min,sec),
+				this.renderRadius,
+				this.renderRadius+this.renderRadius/2
+			);
+		}
+
+		// Draw the hands
+		if (skin.hourHand)
+			this.radialLineAtAngle(this.tickAngle(((hour%12)*5 + min/12.0)),skin.hourHand);
+
+		if (skin.minuteHand)
+			this.radialLineAtAngle(this.tickAngle((min + sec/60.0)),skin.minuteHand);
+
+		if (this.showSecondHand && skin.secondHand)
+			this.radialLineAtAngle(this.tickAngle(sec),skin.secondHand);
+
+		// Second hand decoration doesn't render right in IE so lets turn it off
+		if (!CoolClock.config.isIE && this.showSecondHand && skin.secondDecoration)
+			this.radialLineAtAngle(this.tickAngle(sec),skin.secondDecoration);
+	},
+
+	// Check the time and display the clock
+	refreshDisplay: function() {
+		var now = new Date();
+		if (this.gmtOffset != null) {
+			// Use GMT + gmtOffset
+			var offsetNow = new Date(now.valueOf() + (this.gmtOffset * 1000 * 60 * 60));
+			this.render(offsetNow.getUTCHours(),offsetNow.getUTCMinutes(),offsetNow.getUTCSeconds());
+		}
+		else {
+			// Use local time
+			this.render(now.getHours(),now.getMinutes(),now.getSeconds());
+		}
+	},
+
+	// Set timeout to trigger a tick in the future
+	nextTick: function() {
+		setTimeout("CoolClock.config.clockTracker['"+this.canvasId+"'].tick()",this.tickDelay);
+	},
+
+	// Check the canvas element hasn't been removed
+	stillHere: function() {
+		return document.getElementById(this.canvasId) != null;
+	},
+
+	// Main tick handler. Refresh the clock then setup the next tick
+	tick: function() {
+		if (this.stillHere()) {
+			this.refreshDisplay()
+			this.nextTick();
+		}
+	}
+};
+
+// Find all canvas elements that have the CoolClock class and turns them into clocks
+CoolClock.findAndCreateClocks = function() {
+	// (Let's not use a jQuery selector here so it's easier to use frameworks other than jQuery)
+	var canvases = document.getElementsByTagName("canvas");
+	for (var i=0;i<canvases.length;i++) {
+		// Pull out the fields from the class. Example "CoolClock:chunkySwissOnBlack:1000"
+		var fields = canvases[i].className.split(" ")[0].split(":");
+		if (fields[0] == "CoolClock") {
+			if (!canvases[i].id) {
+				// If there's no id on this canvas element then give it one
+				canvases[i].id = '_coolclock_auto_id_' + CoolClock.config.noIdCount++;
+			}
+			// Create a clock object for this element
+			new CoolClock({
+				canvasId:       canvases[i].id,
+				skinId:         fields[1],
+				displayRadius:  fields[2],
+				showSecondHand: fields[3]!='noSeconds',
+				gmtOffset:      fields[4],
+				showDigital:    fields[5]=='showDigital',
+				logClock:       fields[6]=='logClock',
+				logClockRev:    fields[6]=='logClockRev'
+			});
+		}
+	}
+};
+
+// If you don't have jQuery then you need a body onload like this: <body onload="CoolClock.findAndCreateClocks()">
+// If you do have jQuery and it's loaded already then we can do it right now
+if (window.jQuery) jQuery(document).ready(CoolClock.findAndCreateClocks);
diff --git a/public_html/coolclock/excanvas.js b/public_html/coolclock/excanvas.js
new file mode 100644
index 0000000..3e1aedf
--- /dev/null
+++ b/public_html/coolclock/excanvas.js
@@ -0,0 +1,785 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed 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.
+
+
+// Known Issues:
+//
+// * Patterns are not implemented.
+// * Radial gradient are not implemented. The VML version of these look very
+//   different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+//   width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+//   Quirks mode will draw the canvas using border-box. Either change your
+//   doctype to HTML5
+//   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+//   or use Box Sizing Behavior from WebFX
+//   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Optimize. There is always room for speed improvements.
+
+// only add this code if we do not already have a canvas implementation
+if (!window.CanvasRenderingContext2D) {
+
+(function () {
+
+  // alias some functions to make (compiled) code shorter
+  var m = Math;
+  var mr = m.round;
+  var ms = m.sin;
+  var mc = m.cos;
+
+  // this is used for sub pixel precision
+  var Z = 10;
+  var Z2 = Z / 2;
+
+  var G_vmlCanvasManager_ = {
+    init: function (opt_doc) {
+      var doc = opt_doc || document;
+      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+        var self = this;
+        doc.attachEvent("onreadystatechange", function () {
+          self.init_(doc);
+        });
+      }
+    },
+
+    init_: function (doc) {
+      if (doc.readyState == "complete") {
+        // create xmlns
+        if (!doc.namespaces["g_vml_"]) {
+          doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
+        }
+
+        // setup default css
+        var ss = doc.createStyleSheet();
+        ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
+            // default size is 300x150 in Gecko and Opera
+            "text-align:left;width:300px;height:150px}" +
+            "g_vml_\\:*{behavior:url(#default#VML)}";
+
+        // find all canvas elements
+        var els = doc.getElementsByTagName("canvas");
+        for (var i = 0; i < els.length; i++) {
+          if (!els[i].getContext) {
+            this.initElement(els[i]);
+          }
+        }
+      }
+    },
+
+    fixElement_: function (el) {
+      // in IE before version 5.5 we would need to add HTML: to the tag name
+      // but we do not care about IE before version 6
+      var outerHTML = el.outerHTML;
+
+      var newEl = el.ownerDocument.createElement(outerHTML);
+      // if the tag is still open IE has created the children as siblings and
+      // it has also created a tag with the name "/FOO"
+      if (outerHTML.slice(-2) != "/>") {
+        var tagName = "/" + el.tagName;
+        var ns;
+        // remove content
+        while ((ns = el.nextSibling) && ns.tagName != tagName) {
+          ns.removeNode();
+        }
+        // remove the incorrect closing tag
+        if (ns) {
+          ns.removeNode();
+        }
+      }
+      el.parentNode.replaceChild(newEl, el);
+      return newEl;
+    },
+
+    /**
+     * Public initializes a canvas element so that it can be used as canvas
+     * element from now on. This is called automatically before the page is
+     * loaded but if you are creating elements using createElement you need to
+     * make sure this is called on the element.
+     * @param {HTMLElement} el The canvas element to initialize.
+     * @return {HTMLElement} the element that was created.
+     */
+    initElement: function (el) {
+      el = this.fixElement_(el);
+      el.getContext = function () {
+        if (this.context_) {
+          return this.context_;
+        }
+        return this.context_ = new CanvasRenderingContext2D_(this);
+      };
+
+      // do not use inline function because that will leak memory
+      el.attachEvent('onpropertychange', onPropertyChange);
+      el.attachEvent('onresize', onResize);
+
+      var attrs = el.attributes;
+      if (attrs.width && attrs.width.specified) {
+        // TODO: use runtimeStyle and coordsize
+        // el.getContext().setWidth_(attrs.width.nodeValue);
+        el.style.width = attrs.width.nodeValue + "px";
+      } else {
+        el.width = el.clientWidth;
+      }
+      if (attrs.height && attrs.height.specified) {
+        // TODO: use runtimeStyle and coordsize
+        // el.getContext().setHeight_(attrs.height.nodeValue);
+        el.style.height = attrs.height.nodeValue + "px";
+      } else {
+        el.height = el.clientHeight;
+      }
+      //el.getContext().setCoordsize_()
+      return el;
+    }
+  };
+
+  function onPropertyChange(e) {
+    var el = e.srcElement;
+
+    switch (e.propertyName) {
+      case 'width':
+        el.style.width = el.attributes.width.nodeValue + "px";
+        el.getContext().clearRect();
+        break;
+      case 'height':
+        el.style.height = el.attributes.height.nodeValue + "px";
+        el.getContext().clearRect();
+        break;
+    }
+  }
+
+  function onResize(e) {
+    var el = e.srcElement;
+    if (el.firstChild) {
+      el.firstChild.style.width =  el.clientWidth + 'px';
+      el.firstChild.style.height = el.clientHeight + 'px';
+    }
+  }
+
+  G_vmlCanvasManager_.init();
+
+  // precompute "00" to "FF"
+  var dec2hex = [];
+  for (var i = 0; i < 16; i++) {
+    for (var j = 0; j < 16; j++) {
+      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+    }
+  }
+
+  function createMatrixIdentity() {
+    return [
+      [1, 0, 0],
+      [0, 1, 0],
+      [0, 0, 1]
+    ];
+  }
+
+  function matrixMultiply(m1, m2) {
+    var result = createMatrixIdentity();
+
+    for (var x = 0; x < 3; x++) {
+      for (var y = 0; y < 3; y++) {
+        var sum = 0;
+
+        for (var z = 0; z < 3; z++) {
+          sum += m1[x][z] * m2[z][y];
+        }
+
+        result[x][y] = sum;
+      }
+    }
+    return result;
+  }
+
+  function copyState(o1, o2) {
+    o2.fillStyle     = o1.fillStyle;
+    o2.lineCap       = o1.lineCap;
+    o2.lineJoin      = o1.lineJoin;
+    o2.lineWidth     = o1.lineWidth;
+    o2.miterLimit    = o1.miterLimit;
+    o2.shadowBlur    = o1.shadowBlur;
+    o2.shadowColor   = o1.shadowColor;
+    o2.shadowOffsetX = o1.shadowOffsetX;
+    o2.shadowOffsetY = o1.shadowOffsetY;
+    o2.strokeStyle   = o1.strokeStyle;
+    o2.arcScaleX_    = o1.arcScaleX_;
+    o2.arcScaleY_    = o1.arcScaleY_;
+  }
+
+  function processStyle(styleString) {
+    var str, alpha = 1;
+
+    styleString = String(styleString);
+    if (styleString.substring(0, 3) == "rgb") {
+      var start = styleString.indexOf("(", 3);
+      var end = styleString.indexOf(")", start + 1);
+      var guts = styleString.substring(start + 1, end).split(",");
+
+      str = "#";
+      for (var i = 0; i < 3; i++) {
+        str += dec2hex[Number(guts[i])];
+      }
+
+      if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
+        alpha = guts[3];
+      }
+    } else {
+      str = styleString;
+    }
+
+    return [str, alpha];
+  }
+
+  function processLineCap(lineCap) {
+    switch (lineCap) {
+      case "butt":
+        return "flat";
+      case "round":
+        return "round";
+      case "square":
+      default:
+        return "square";
+    }
+  }
+
+  /**
+   * This class implements CanvasRenderingContext2D interface as described by
+   * the WHATWG.
+   * @param {HTMLElement} surfaceElement The element that the 2D context should
+   * be associated with
+   */
+   function CanvasRenderingContext2D_(surfaceElement) {
+    this.m_ = createMatrixIdentity();
+
+    this.mStack_ = [];
+    this.aStack_ = [];
+    this.currentPath_ = [];
+
+    // Canvas context properties
+    this.strokeStyle = "#000";
+    this.fillStyle = "#000";
+
+    this.lineWidth = 1;
+    this.lineJoin = "miter";
+    this.lineCap = "butt";
+    this.miterLimit = Z * 1;
+    this.globalAlpha = 1;
+    this.canvas = surfaceElement;
+
+    var el = surfaceElement.ownerDocument.createElement('div');
+    el.style.width =  surfaceElement.clientWidth + 'px';
+    el.style.height = surfaceElement.clientHeight + 'px';
+    el.style.overflow = 'hidden';
+    el.style.position = 'absolute';
+    surfaceElement.appendChild(el);
+
+    this.element_ = el;
+    this.arcScaleX_ = 1;
+    this.arcScaleY_ = 1;
+  };
+
+  var contextPrototype = CanvasRenderingContext2D_.prototype;
+  contextPrototype.clearRect = function() {
+    this.element_.innerHTML = "";
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.beginPath = function() {
+    // TODO: Branch current matrix so that save/restore has no effect
+    //       as per safari docs.
+
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.moveTo = function(aX, aY) {
+    this.currentPath_.push({type: "moveTo", x: aX, y: aY});
+    this.currentX_ = aX;
+    this.currentY_ = aY;
+  };
+
+  contextPrototype.lineTo = function(aX, aY) {
+    this.currentPath_.push({type: "lineTo", x: aX, y: aY});
+    this.currentX_ = aX;
+    this.currentY_ = aY;
+  };
+
+  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+                                            aCP2x, aCP2y,
+                                            aX, aY) {
+    this.currentPath_.push({type: "bezierCurveTo",
+                           cp1x: aCP1x,
+                           cp1y: aCP1y,
+                           cp2x: aCP2x,
+                           cp2y: aCP2y,
+                           x: aX,
+                           y: aY});
+    this.currentX_ = aX;
+    this.currentY_ = aY;
+  };
+
+  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+    // the following is lifted almost directly from
+    // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+    var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_);
+    var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_);
+    var cp2x = cp1x + (aX - this.currentX_) / 3.0;
+    var cp2y = cp1y + (aY - this.currentY_) / 3.0;
+    this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY);
+  };
+
+  contextPrototype.arc = function(aX, aY, aRadius,
+                                  aStartAngle, aEndAngle, aClockwise) {
+    aRadius *= Z;
+    var arcType = aClockwise ? "at" : "wa";
+
+    var xStart = aX + (mc(aStartAngle) * aRadius) - Z2;
+    var yStart = aY + (ms(aStartAngle) * aRadius) - Z2;
+
+    var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2;
+    var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2;
+
+    // IE won't render arches drawn counter clockwise if xStart == xEnd.
+    if (xStart == xEnd && !aClockwise) {
+      xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+                       // that can be represented in binary
+    }
+
+    this.currentPath_.push({type: arcType,
+                           x: aX,
+                           y: aY,
+                           radius: aRadius,
+                           xStart: xStart,
+                           yStart: yStart,
+                           xEnd: xEnd,
+                           yEnd: yEnd});
+
+  };
+
+  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+  };
+
+  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+    // Will destroy any existing path (same as FF behaviour)
+    this.beginPath();
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.stroke();
+  };
+
+  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+    // Will destroy any existing path (same as FF behaviour)
+    this.beginPath();
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.fill();
+  };
+
+  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+    var gradient = new CanvasGradient_("gradient");
+    return gradient;
+  };
+
+  contextPrototype.createRadialGradient = function(aX0, aY0,
+                                                   aR0, aX1,
+                                                   aY1, aR1) {
+    var gradient = new CanvasGradient_("gradientradial");
+    gradient.radius1_ = aR0;
+    gradient.radius2_ = aR1;
+    gradient.focus_.x = aX0;
+    gradient.focus_.y = aY0;
+    return gradient;
+  };
+
+  contextPrototype.drawImage = function (image, var_args) {
+    var dx, dy, dw, dh, sx, sy, sw, sh;
+
+    // to find the original width we overide the width and height
+    var oldRuntimeWidth = image.runtimeStyle.width;
+    var oldRuntimeHeight = image.runtimeStyle.height;
+    image.runtimeStyle.width = 'auto';
+    image.runtimeStyle.height = 'auto';
+
+    // get the original size
+    var w = image.width;
+    var h = image.height;
+
+    // and remove overides
+    image.runtimeStyle.width = oldRuntimeWidth;
+    image.runtimeStyle.height = oldRuntimeHeight;
+
+    if (arguments.length == 3) {
+      dx = arguments[1];
+      dy = arguments[2];
+      sx = sy = 0;
+      sw = dw = w;
+      sh = dh = h;
+    } else if (arguments.length == 5) {
+      dx = arguments[1];
+      dy = arguments[2];
+      dw = arguments[3];
+      dh = arguments[4];
+      sx = sy = 0;
+      sw = w;
+      sh = h;
+    } else if (arguments.length == 9) {
+      sx = arguments[1];
+      sy = arguments[2];
+      sw = arguments[3];
+      sh = arguments[4];
+      dx = arguments[5];
+      dy = arguments[6];
+      dw = arguments[7];
+      dh = arguments[8];
+    } else {
+      throw "Invalid number of arguments";
+    }
+
+    var d = this.getCoords_(dx, dy);
+
+    var w2 = sw / 2;
+    var h2 = sh / 2;
+
+    var vmlStr = [];
+
+    var W = 10;
+    var H = 10;
+
+    // For some reason that I've now forgotten, using divs didn't work
+    vmlStr.push(' <g_vml_:group',
+                ' coordsize="', Z * W, ',', Z * H, '"',
+                ' coordorigin="0,0"' ,
+                ' style="width:', W, ';height:', H, ';position:absolute;');
+
+    // If filters are necessary (rotation exists), create them
+    // filters are bog-slow, so only create them if abbsolutely necessary
+    // The following check doesn't account for skews (which don't exist
+    // in the canvas spec (yet) anyway.
+
+    if (this.m_[0][0] != 1 || this.m_[0][1]) {
+      var filter = [];
+
+      // Note the 12/21 reversal
+      filter.push("M11='", this.m_[0][0], "',",
+                  "M12='", this.m_[1][0], "',",
+                  "M21='", this.m_[0][1], "',",
+                  "M22='", this.m_[1][1], "',",
+                  "Dx='", mr(d.x / Z), "',",
+                  "Dy='", mr(d.y / Z), "'");
+
+      // Bounding box calculation (need to minimize displayed area so that
+      // filters don't waste time on unused pixels.
+      var max = d;
+      var c2 = this.getCoords_(dx + dw, dy);
+      var c3 = this.getCoords_(dx, dy + dh);
+      var c4 = this.getCoords_(dx + dw, dy + dh);
+
+      max.x = Math.max(max.x, c2.x, c3.x, c4.x);
+      max.y = Math.max(max.y, c2.y, c3.y, c4.y);
+
+      vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z),
+                  "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
+                  filter.join(""), ", sizingmethod='clip');")
+    } else {
+      vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;")
+    }
+
+    vmlStr.push(' ">' ,
+                '<g_vml_:image src="', image.src, '"',
+                ' style="width:', Z * dw, ';',
+                ' height:', Z * dh, ';"',
+                ' cropleft="', sx / w, '"',
+                ' croptop="', sy / h, '"',
+                ' cropright="', (w - sx - sw) / w, '"',
+                ' cropbottom="', (h - sy - sh) / h, '"',
+                ' />',
+                '</g_vml_:group>');
+
+    this.element_.insertAdjacentHTML("BeforeEnd",
+                                    vmlStr.join(""));
+  };
+
+  contextPrototype.stroke = function(aFill) {
+    var lineStr = [];
+    var lineOpen = false;
+    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
+    var color = a[0];
+    var opacity = a[1] * this.globalAlpha;
+
+    var W = 10;
+    var H = 10;
+
+    lineStr.push('<g_vml_:shape',
+                 ' fillcolor="', color, '"',
+                 ' filled="', Boolean(aFill), '"',
+                 ' style="position:absolute;width:', W, ';height:', H, ';"',
+                 ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
+                 ' stroked="', !aFill, '"',
+                 ' strokeweight="', this.lineWidth, '"',
+                 ' strokecolor="', color, '"',
+                 ' path="');
+
+    var newSeq = false;
+    var min = {x: null, y: null};
+    var max = {x: null, y: null};
+
+    for (var i = 0; i < this.currentPath_.length; i++) {
+      var p = this.currentPath_[i];
+
+      if (p.type == "moveTo") {
+        lineStr.push(" m ");
+        var c = this.getCoords_(p.x, p.y);
+        lineStr.push(mr(c.x), ",", mr(c.y));
+      } else if (p.type == "lineTo") {
+        lineStr.push(" l ");
+        var c = this.getCoords_(p.x, p.y);
+        lineStr.push(mr(c.x), ",", mr(c.y));
+      } else if (p.type == "close") {
+        lineStr.push(" x ");
+      } else if (p.type == "bezierCurveTo") {
+        lineStr.push(" c ");
+        var c = this.getCoords_(p.x, p.y);
+        var c1 = this.getCoords_(p.cp1x, p.cp1y);
+        var c2 = this.getCoords_(p.cp2x, p.cp2y);
+        lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
+                     mr(c2.x), ",", mr(c2.y), ",",
+                     mr(c.x), ",", mr(c.y));
+      } else if (p.type == "at" || p.type == "wa") {
+        lineStr.push(" ", p.type, " ");
+        var c  = this.getCoords_(p.x, p.y);
+        var cStart = this.getCoords_(p.xStart, p.yStart);
+        var cEnd = this.getCoords_(p.xEnd, p.yEnd);
+
+        lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
+                     mr(c.y - this.arcScaleY_ * p.radius), " ",
+                     mr(c.x + this.arcScaleX_ * p.radius), ",",
+                     mr(c.y + this.arcScaleY_ * p.radius), " ",
+                     mr(cStart.x), ",", mr(cStart.y), " ",
+                     mr(cEnd.x), ",", mr(cEnd.y));
+      }
+
+
+      // TODO: Following is broken for curves due to
+      //       move to proper paths.
+
+      // Figure out dimensions so we can do gradient fills
+      // properly
+      if(c) {
+        if (min.x == null || c.x < min.x) {
+          min.x = c.x;
+        }
+        if (max.x == null || c.x > max.x) {
+          max.x = c.x;
+        }
+        if (min.y == null || c.y < min.y) {
+          min.y = c.y;
+        }
+        if (max.y == null || c.y > max.y) {
+          max.y = c.y;
+        }
+      }
+    }
+    lineStr.push(' ">');
+
+    if (typeof this.fillStyle == "object") {
+      var focus = {x: "50%", y: "50%"};
+      var width = (max.x - min.x);
+      var height = (max.y - min.y);
+      var dimension = (width > height) ? width : height;
+
+      focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
+      focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
+
+      var colors = [];
+
+      // inside radius (%)
+      if (this.fillStyle.type_ == "gradientradial") {
+        var inside = (this.fillStyle.radius1_ / dimension * 100);
+
+        // percentage that outside radius exceeds inside radius
+        var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
+      } else {
+        var inside = 0;
+        var expansion = 100;
+      }
+
+      var insidecolor = {offset: null, color: null};
+      var outsidecolor = {offset: null, color: null};
+
+      // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
+      // won't interpret it correctly
+      this.fillStyle.colors_.sort(function (cs1, cs2) {
+        return cs1.offset - cs2.offset;
+      });
+
+      for (var i = 0; i < this.fillStyle.colors_.length; i++) {
+        var fs = this.fillStyle.colors_[i];
+
+        colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
+
+        if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
+          insidecolor.offset = fs.offset;
+          insidecolor.color = fs.color;
+        }
+
+        if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
+          outsidecolor.offset = fs.offset;
+          outsidecolor.color = fs.color;
+        }
+      }
+      colors.pop();
+
+      lineStr.push('<g_vml_:fill',
+                   ' color="', outsidecolor.color, '"',
+                   ' color2="', insidecolor.color, '"',
+                   ' type="', this.fillStyle.type_, '"',
+                   ' focusposition="', focus.x, ', ', focus.y, '"',
+                   ' colors="', colors.join(""), '"',
+                   ' opacity="', opacity, '" />');
+    } else if (aFill) {
+      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
+    } else {
+      lineStr.push(
+        '<g_vml_:stroke',
+        ' opacity="', opacity,'"',
+        ' joinstyle="', this.lineJoin, '"',
+        ' miterlimit="', this.miterLimit, '"',
+        ' endcap="', processLineCap(this.lineCap) ,'"',
+        ' weight="', this.lineWidth, 'px"',
+        ' color="', color,'" />'
+      );
+    }
+
+    lineStr.push("</g_vml_:shape>");
+
+    this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
+
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.fill = function() {
+    this.stroke(true);
+  }
+
+  contextPrototype.closePath = function() {
+    this.currentPath_.push({type: "close"});
+  };
+
+  /**
+   * @private
+   */
+  contextPrototype.getCoords_ = function(aX, aY) {
+    return {
+      x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2,
+      y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2
+    }
+  };
+
+  contextPrototype.save = function() {
+    var o = {};
+    copyState(this, o);
+    this.aStack_.push(o);
+    this.mStack_.push(this.m_);
+    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+  };
+
+  contextPrototype.restore = function() {
+    copyState(this.aStack_.pop(), this);
+    this.m_ = this.mStack_.pop();
+  };
+
+  contextPrototype.translate = function(aX, aY) {
+    var m1 = [
+      [1,  0,  0],
+      [0,  1,  0],
+      [aX, aY, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.rotate = function(aRot) {
+    var c = mc(aRot);
+    var s = ms(aRot);
+
+    var m1 = [
+      [c,  s, 0],
+      [-s, c, 0],
+      [0,  0, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.scale = function(aX, aY) {
+    this.arcScaleX_ *= aX;
+    this.arcScaleY_ *= aY;
+    var m1 = [
+      [aX, 0,  0],
+      [0,  aY, 0],
+      [0,  0,  1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  /******** STUBS ********/
+  contextPrototype.clip = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.arcTo = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.createPattern = function() {
+    return new CanvasPattern_;
+  };
+
+  // Gradient / Pattern Stubs
+  function CanvasGradient_(aType) {
+    this.type_ = aType;
+    this.radius1_ = 0;
+    this.radius2_ = 0;
+    this.colors_ = [];
+    this.focus_ = {x: 0, y: 0};
+  }
+
+  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+    aColor = processStyle(aColor);
+    this.colors_.push({offset: 1-aOffset, color: aColor});
+  };
+
+  function CanvasPattern_() {}
+
+  // set up externs
+  G_vmlCanvasManager = G_vmlCanvasManager_;
+  CanvasRenderingContext2D = CanvasRenderingContext2D_;
+  CanvasGradient = CanvasGradient_;
+  CanvasPattern = CanvasPattern_;
+
+})();
+
+} // if
diff --git a/public_html/coolclock/moreskins.js b/public_html/coolclock/moreskins.js
new file mode 100644
index 0000000..e316181
--- /dev/null
+++ b/public_html/coolclock/moreskins.js
@@ -0,0 +1,212 @@
+CoolClock.config.skins = {
+
+	swissRail: {
+		outerBorder:      { lineWidth: 2, radius: 95, color: "black", alpha: 1 },
+		smallIndicator:   { lineWidth: 2, startAt: 88, endAt: 92, color: "black", alpha: 1 },
+		largeIndicator:   { lineWidth: 4, startAt: 79, endAt: 92, color: "black", alpha: 1 },
+		hourHand:         { lineWidth: 8, startAt: -15, endAt: 50, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 7, startAt: -15, endAt: 75, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: 1 },
+		secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "red", color: "red", alpha: 1 }
+	},
+
+	chunkySwiss: {
+		outerBorder:      { lineWidth: 4, radius: 97, color: "black", alpha: 1 },
+		smallIndicator:   { lineWidth: 4, startAt: 89, endAt: 93, color: "black", alpha: 1 },
+		largeIndicator:   { lineWidth: 8, startAt: 80, endAt: 93, color: "black", alpha: 1 },
+		hourHand:         { lineWidth: 12, startAt: -15, endAt: 60, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 10, startAt: -15, endAt: 85, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 },
+		secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 }
+	},
+
+	chunkySwissOnBlack: {
+		outerBorder:      { lineWidth: 4, radius: 97, color: "white", alpha: 1 },
+		smallIndicator:   { lineWidth: 4, startAt: 89, endAt: 93, color: "white", alpha: 1 },
+		largeIndicator:   { lineWidth: 8, startAt: 80, endAt: 93, color: "white", alpha: 1 },
+		hourHand:         { lineWidth: 12, startAt: -15, endAt: 60, color: "white", alpha: 1 },
+		minuteHand:       { lineWidth: 10, startAt: -15, endAt: 85, color: "white", alpha: 1 },
+		secondHand:       { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 },
+		secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 }
+	},
+
+	fancy: {
+		outerBorder:      { lineWidth: 5, radius: 95, color: "green", alpha: 0.7 },
+		smallIndicator:   { lineWidth: 1, startAt: 80, endAt: 93, color: "black", alpha: 0.4 },
+		largeIndicator:   { lineWidth: 1, startAt: 30, endAt: 93, color: "black", alpha: 0.5 },
+		hourHand:         { lineWidth: 8, startAt: -15, endAt: 50, color: "blue", alpha: 0.7 },
+		minuteHand:       { lineWidth: 7, startAt: -15, endAt: 92, color: "red", alpha: 0.7 },
+		secondHand:       { lineWidth: 10, startAt: 80, endAt: 85, color: "blue", alpha: 0.3 },
+		secondDecoration: { lineWidth: 1, startAt: 30, radius: 50, fillColor: "blue", color: "red", alpha: 0.15 }
+	},
+
+	machine: {
+		outerBorder:      { lineWidth: 60, radius: 55, color: "#dd6655", alpha: 1 },
+		smallIndicator:   { lineWidth: 4, startAt: 80, endAt: 95, color: "white", alpha: 1 },
+		largeIndicator:   { lineWidth: 14, startAt: 77, endAt: 92, color: "#dd6655", alpha: 1 },
+		hourHand:         { lineWidth: 18, startAt: -15, endAt: 40, color: "white", alpha: 1 },
+		minuteHand:       { lineWidth: 14, startAt: 24, endAt: 100, color: "#771100", alpha: 0.5 },
+		secondHand:       { lineWidth: 3, startAt: 22, endAt: 83, color: "green", alpha: 0 },
+		secondDecoration: { lineWidth: 1, startAt: 52, radius: 26, fillColor: "#ffcccc", color: "red", alpha: 0.5 }
+	},
+
+	simonbaird_com: {
+		hourHand:         { lineWidth: 80, startAt: -15, endAt: 35,  color: 'magenta', alpha: 0.5 },
+		minuteHand:       { lineWidth: 80, startAt: -15, endAt: 65,  color: 'cyan', alpha: 0.5 },
+		secondDecoration: { lineWidth: 1,  startAt: 40,  radius: 40, color: "#fff", fillColor: 'yellow', alpha: 0.5 }
+	},
+
+	// by bonstio, http://bonstio.net
+	classic/*was gIG*/: {
+		outerBorder:      { lineWidth: 185, radius: 1, color: "#E5ECF9", alpha: 1 },
+		smallIndicator:   { lineWidth: 2, startAt: 89, endAt: 94, color: "#3366CC", alpha: 1 },
+		largeIndicator:   { lineWidth: 4, startAt: 83, endAt: 94, color: "#3366CC", alpha: 1 },
+		hourHand:         { lineWidth: 5, startAt: 0, endAt: 60, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 4, startAt: 0, endAt: 80, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: .85 },
+		secondDecoration: { lineWidth: 3, startAt: 0, radius: 2, fillColor: "black", color: "black", alpha: 1 }
+	},
+
+	modern/*was gIG2*/: {
+		outerBorder:      { lineWidth: 185, radius: 1, color: "#E5ECF9", alpha: 1 },
+		smallIndicator:   { lineWidth: 5, startAt: 88, endAt: 94, color: "#3366CC", alpha: 1 },
+		largeIndicator:   { lineWidth: 5, startAt: 88, endAt: 94, color: "#3366CC", alpha: 1 },
+		hourHand:         { lineWidth: 8, startAt: 0, endAt: 60, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 8, startAt: 0, endAt: 80, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 5, startAt: 80, endAt: 85, color: "red", alpha: .85 },
+		secondDecoration: { lineWidth: 3, startAt: 0, radius: 4, fillColor: "black", color: "black", alpha: 1 }
+	},
+
+	simple/*was gIG3*/: {
+		outerBorder:      { lineWidth: 185, radius: 1, color: "#E5ECF9", alpha: 1 },
+		smallIndicator:   { lineWidth: 10, startAt: 90, endAt: 94, color: "#3366CC", alpha: 1 },
+		largeIndicator:   { lineWidth: 10, startAt: 90, endAt: 94, color: "#3366CC", alpha: 1 },
+		hourHand:         { lineWidth: 8, startAt: 0, endAt: 60, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 8, startAt: 0, endAt: 80, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 5, startAt: 80, endAt: 85, color: "red", alpha: .85 },
+		secondDecoration: { lineWidth: 3, startAt: 0, radius: 4, fillColor: "black", color: "black", alpha: 1 }
+	},
+
+	// by securephp
+	securephp: {
+		outerBorder:      { lineWidth: 100, radius: 0.45, color: "#669900", alpha: 0.3 },
+		smallIndicator:   { lineWidth: 2, startAt: 80, endAt: 90 , color: "green", alpha: 1 },
+		largeIndicator:   { lineWidth: 8.5, startAt: 20, endAt: 40 , color: "green", alpha: 0.4 },
+		hourHand:         { lineWidth: 3, startAt: 0, endAt: 60, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 2, startAt: 0, endAt: 75, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 1, startAt: -10, endAt: 80, color: "blue", alpha: 0.8 },
+		secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "blue", color: "red", alpha: 1 }
+	},
+
+	Tes2: {
+		outerBorder:      { lineWidth: 4, radius: 95, color: "black", alpha: 0.5 },
+		smallIndicator:   { lineWidth: 1, startAt: 10, endAt: 50 , color: "#66CCFF", alpha: 1 },
+		largeIndicator:   { lineWidth: 8.5, startAt: 60, endAt: 70, color: "#6699FF", alpha: 1 },
+		hourHand:         { lineWidth: 5, startAt: -15, endAt: 60, color: "black", alpha: 0.7 },
+		minuteHand:       { lineWidth: 3, startAt: -25, endAt: 75, color: "black", alpha: 0.7 },
+		secondHand:       { lineWidth: 1.5, startAt: -20, endAt: 88, color: "red", alpha: 1 },
+		secondDecoration: { lineWidth: 1, startAt: 20, radius: 4, fillColor: "blue", color: "red", alpha: 1 }
+	},
+
+
+	Lev: {
+		outerBorder:      { lineWidth: 10, radius: 95, color: "#CCFF33", alpha: 0.65 },
+		smallIndicator:   { lineWidth: 5, startAt: 84, endAt: 90, color: "#996600", alpha: 1 },
+		largeIndicator:   { lineWidth: 40, startAt: 25, endAt: 95, color: "#336600", alpha: 0.55 },
+		hourHand:         { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 0.9 },
+		minuteHand:       { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 0.85 },
+		secondHand:       { lineWidth: 1, startAt: 0, endAt: 85, color: "black", alpha: 1 },
+		secondDecoration: { lineWidth: 2, startAt: 5, radius: 10, fillColor: "black", color: "black", alpha: 1 }
+	},
+
+	Sand: {
+		outerBorder:      { lineWidth: 1, radius: 70, color: "black", alpha: 0.5 },
+		smallIndicator:   { lineWidth: 3, startAt: 50, endAt: 70, color: "#0066FF", alpha: 0.5 },
+		largeIndicator:   { lineWidth: 200, startAt: 80, endAt: 95, color: "#996600", alpha: 0.75 },
+		hourHand:         { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 0.9 },
+		minuteHand:       { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 0.85 },
+		secondHand:       { lineWidth: 1, startAt: 0, endAt: 85, color: "black", alpha: 1 },
+		secondDecoration: { lineWidth: 2, startAt: 5, radius: 10, fillColor: "black", color: "black", alpha: 1 }
+	},
+
+	Sun: {
+		outerBorder:      { lineWidth: 100, radius: 140, color: "#99FFFF", alpha: 0.2 },
+		smallIndicator:   { lineWidth: 300, startAt: 50, endAt: 70, color: "black", alpha: 0.1 },
+		largeIndicator:   { lineWidth: 5, startAt: 80, endAt: 95, color: "black", alpha: 0.65 },
+		hourHand:         { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 0.9 },
+		minuteHand:       { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 0.85 },
+		secondHand:       { lineWidth: 1, startAt: 0, endAt: 90, color: "black", alpha: 1 },
+		secondDecoration: { lineWidth: 2, startAt: 5, radius: 10, fillColor: "black", color: "black", alpha: 1 }
+	},
+
+	Tor: {
+		outerBorder:      { lineWidth: 10, radius: 88, color: "#996600", alpha: 0.9 },
+		smallIndicator:   { lineWidth: 6, startAt: -10, endAt: 73, color: "green", alpha: 0.3 },
+		largeIndicator:   { lineWidth: 6, startAt: 73, endAt: 100, color: "black", alpha: 0.65 },
+		hourHand:         { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 1, startAt: -73, endAt: 73, color: "black", alpha: 0.8 },
+		secondDecoration: { lineWidth: 2, startAt: 5, radius: 10, fillColor: "black", color: "black", alpha: 1 }
+	},
+
+	Cold: {
+		outerBorder:      { lineWidth: 15, radius: 90, color: "black", alpha: 0.3 },
+		smallIndicator:   { lineWidth: 15, startAt: -10, endAt: 95, color: "blue", alpha: 0.1 },
+		largeIndicator:   { lineWidth: 3, startAt: 80, endAt: 95, color: "blue", alpha: 0.65 },
+		hourHand:         { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 1, startAt: 0, endAt: 85, color: "black", alpha: 0.8 },
+		secondDecoration: { lineWidth: 5, startAt: 30, radius: 10, fillColor: "black", color: "black", alpha: 1 }
+	},
+
+	Babosa: {
+		outerBorder:      { lineWidth: 100, radius: 25, color: "blue", alpha: 0.25 },
+		smallIndicator:   { lineWidth: 3, startAt: 90, endAt: 95, color: "#3366CC", alpha: 1 },
+		largeIndicator:   { lineWidth: 4, startAt: 75, endAt: 95, color: "#3366CC", alpha: 1 },
+		hourHand:         { lineWidth: 4, startAt: 0, endAt: 60, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 3, startAt: 0, endAt: 85, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 12, startAt: 75, endAt: 90, color: "red", alpha: 0.8 },
+		secondDecoration: { lineWidth: 3, startAt: 0, radius: 4, fillColor: "black", color: "black", alpha: 1 }
+	},
+
+	Tumb: {
+		outerBorder:      { lineWidth: 105, radius: 5, color: "green", alpha: 0.4 },
+		smallIndicator:   { lineWidth: 1, startAt: 93, endAt: 98, color: "green", alpha: 1 },
+		largeIndicator:   { lineWidth: 50, startAt: 0, endAt: 89, color: "red", alpha: 0.14 },
+		hourHand:         { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 1, startAt: 0, endAt: 85, color: "black", alpha: 0.8 },
+		secondDecoration: { lineWidth: 5, startAt: 50, radius: 90, fillColor: "black", color: "black", alpha: 0.05 }
+	},
+
+	Stone: {
+		outerBorder:      { lineWidth: 15, radius: 80, color: "#339933", alpha: 0.5 },
+		smallIndicator:   { lineWidth: 2, startAt: 70, endAt: 90, color: "#FF3300", alpha: 0.7 },
+		largeIndicator:   { lineWidth: 15, startAt: 0, endAt: 29, color: "#FF6600", alpha: 0.3 },
+		hourHand:         { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 3, startAt: 0, endAt: 75, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 1, startAt: 0, endAt: 85, color: "black", alpha: 0.8 },
+		secondDecoration: { lineWidth: 5, startAt: 50, radius: 90, fillColor: "black", color: "black", alpha: 0.05 }
+	},
+
+	Disc: {
+		outerBorder:      { lineWidth: 105, radius: 1, color: "#666600", alpha: 0.2 },
+		smallIndicator:   { lineWidth: 1, startAt: 58, endAt: 95, color: "#669900", alpha: 0.8 },
+		largeIndicator:   { lineWidth: 6, startAt: 25, endAt: 35, color: "#666600", alpha: 1 },
+		hourHand:         { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 1 },
+		minuteHand:       { lineWidth: 3, startAt: 0, endAt: 75, color: "black", alpha: 1 },
+		secondHand:       { lineWidth: 1, startAt: -75, endAt: 75, color: "#99CC00", alpha: 0.8 },
+		secondDecoration: { lineWidth: 5, startAt: 50, radius: 90, fillColor: "#00FF00", color: "green", alpha: 0.05 }
+	},
+
+	// By Yoo Nhe
+	watermelon: {
+		outerBorder:      { lineWidth: 100, radius: 1.7, color: "#d93d04", alpha: 5 },
+		smallIndicator:   { lineWidth: 2, startAt: 50, endAt: 70, color: "#d93d04", alpha: 5 },
+		largeIndicator:   { lineWidth: 2, startAt: 45, endAt: 94, color: "#a9bf04", alpha: 1 },
+		hourHand:         { lineWidth: 5, startAt: -20, endAt: 80, color: "#8c0d17", alpha: 1 },
+		minuteHand:       { lineWidth: 2, startAt: -20, endAt: 80, color: "#7c8c03", alpha: .9 },
+		secondHand:       { lineWidth: 2, startAt: 70, endAt: 94, color: "#d93d04", alpha: .85 },
+		secondDecoration: { lineWidth: 1, startAt: 70, radius: 3, fillColor: "red", color: "black", alpha: .7 }
+	}
+};
diff --git a/public_html/gmap.html b/public_html/gmap.html
new file mode 100644
index 0000000..a2f0ff2
--- /dev/null
+++ b/public_html/gmap.html
@@ -0,0 +1,192 @@
+<html>
+	<head>
+		<meta charset="utf-8"/>
+		<link rel="stylesheet" type="text/css" href="style.css" />
+		<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />
+		<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
+		<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
+		<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?sensor=false&libraries=geometry"></script>
+		<script type="text/javascript" src="config.js"></script>
+		<script type="text/javascript" src="planeObject.js"></script>
+		<script type="text/javascript" src="script.js"></script>
+		<script type="text/javascript" src="coolclock/excanvas.js"></script>
+		<script type="text/javascript" src="coolclock/coolclock.js"></script>
+		<script type="text/javascript" src="coolclock/moreskins.js"></script>
+        <title>DUMP1090</title>
+	</head>
+	<body onload="initialize()">
+          <div id="loader" class="hidden">
+            <img src="spinny.gif" id="spinny">
+            <progress id="loader_progress"></progress>
+          </div>
+
+          <!--
+              This is hideous. airframes.org insists on getting a POST with a "submit" value specified,
+              but if we have an input control with that name then it shadows the submit() function that
+              we need. So steal the submit function off a different form. Surely there is a better way?!
+            -->
+          <form id="horrible_hack" class="hidden"></form>
+          <form id="airframes_post" method="POST" action="http://www.airframes.org/" target="_blank" class="hidden">
+            <input type="hidden" name="reg1" value="">
+            <input type="hidden" name="selcal" value="">
+            <input id="airframes_post_icao" type="hidden" name="ica024" value="">
+            <input type="hidden" name="submit" value="submit">
+          </form>
+
+		<div id="map_container">
+			<div id="map_canvas"></div>
+		</div>
+		<div id="sidebar_container">
+			<div id="sidebar_canvas">
+				<div id="timestamps" style="align: center">
+					<table width="100%">
+                                          <tr>
+					    <td align="center"> <canvas id="utcclock"></canvas> </td>
+					    <td align="center"> <canvas id="receiverclock"></canvas> </td>
+					  </tr>
+
+                                          <tr>
+					    <td align="center">UTC</td>
+					    <td align="center">Last Update</td>
+					  </tr>
+                                        </table>
+				</div>
+				<div id="sudo_buttons">
+					<table width="100%">
+                                          <tr>
+					    <td width="150" style="text-align: center;" class="pointer">
+					      [ <span onclick="resetMap();">Reset Map</span> ]
+					    </td>
+					  </tr>
+                                        </table>
+				</div>
+
+				<div id="dump1090_infoblock">
+                                  <table width="100%">
+                                    <tr class="infoblock_heading">
+                                      <td>
+                                        <b id="infoblock_name">DUMP1090</b>
+                                      </td>
+                                      <td align="right">
+                                        <a href="https://github.com/mutability/dump1090" id="dump1090_version" target="_blank"></span>
+                                      </td>
+                                    </tr>
+
+                                    <tr class="infoblock_body">
+                                      <td> </td>
+	                              <td> </td>
+                                    </tr>
+
+                                    <tr class="infoblock_body dim">
+                                      <td>(no aircraft selected)</td>
+                                      <td> </td>
+                                    </tr>
+
+                                    <tr class="infoblock_body">
+                                      <td> </td>
+	                              <td> </td>
+                                    </tr>
+
+                                    <tr class="infoblock_body">
+                                      <td>Aircraft (total): <span id="dump1090_total_ac">n/a</span></td>
+	                              <td>Messages: <span id="dump1090_message_rate">n/a</span>/sec</td>
+                                    </tr>
+
+                                    <tr class="infoblock_body">
+                                      <td>(with positions): <span id="dump1090_total_ac_positions">n/a</span></td>
+                                      <td>History: <span id="dump1090_total_history">n/a</span> positions</td>
+                                    </tr>
+                                  </table>
+                                </div>
+
+				<div id="selected_infoblock" class="hidden">
+                                  <table width="100%">
+                                    <tr class="infoblock_heading">
+                                      <td colspan="2">
+                                        <b>
+                                          <span id="selected_callsign" onclick="toggleFollowSelected();" class="pointer">n/a</span>
+                                        </b>
+                                        <span id="selected_follow" onclick="toggleFollowSelected();" class="pointer">&#x21D2</span>
+
+                                        <a href="http://www.airframes.org/" onclick="document.getElementById('horrible_hack').submit.call(document.getElementById('airframes_post')); return false;">
+                                          <span id="selected_icao"></span>
+                                        </a>
+
+                                        <span id="selected_emergency"></span>
+                                        <span id="selected_links">
+                                          <a id="selected_fr24_link" href="" target="_blank">[FR24]</a>
+                                          <a id="selected_flightstats_link" href="" target="_blank">[FlightStats]</a>
+                                          <a id="selected_flightaware_link" href="" target="_blank">[FlightAware]</a>
+                                        </span>
+                                      </td>
+                                    </tr>
+
+                                    <tr class="infoblock_body">
+                                      <td width="55%">Altitude: <span id="selected_altitude"></span></td>
+                                      <td width="45%">Squawk: <span id="selected_squawk"></span></td>
+                                    </tr>
+
+                                    <tr class="infoblock_body">
+                                      <td>Speed: <span id="selected_speed">n/a</span></td>
+                                      <td>RSSI: <span id="selected_rssi">n/a</span></td>
+                                    </tr>
+
+                                    <tr class="infoblock_body">
+                                      <td>Track: <span id="selected_track">n/a</span></td>
+	                              <td>Last seen: <span id="selected_seen">n/a</span</td>
+                                    </tr>
+
+                                    <tr class="infoblock_body">
+                                      <td colspan=2>Position: <span id="selected_position">n/a</span></td>
+                                    </tr>
+
+                                    <tr class="infoblock_body">
+                                      <td colspan="2">Distance from Site: <span id="selected_sitedist">n/a</span></td>
+                                    </tr>
+                                  </table>
+                                </div>
+				<div id="planes_table">
+	                          <table id="tableinfo" width="100%">
+	                            <thead style="background-color: #BBBBBB; cursor: pointer;">
+	                              <td id="icao" onclick="sortByICAO();">ICAO</td>
+	                              <td id="flight" onclick="sortByFlight();">Flight</td>
+	                              <td id="squawk" onclick="sortBySquawk();" align="right">Squawk</td>
+                                      <td id="altitude" onclick="sortByAltitude();" align="right">Altitude</td>
+	                              <td id="speed" onclick="sortBySpeed();" align="right">Speed</td>
+                                      <td id="distance" onclick="sortByDistance();" align="right">Distance</td>
+                                      <td id="track" onclick="sortByTrack();" align="right">Track</td>
+	                              <td id="msgs" onclick="sortByMsgs();" align="right">Msgs</td>
+	                              <td id="seen" onclick="sortBySeen();" align="right">Age</td>
+                                    </thead>
+                                    <tbody>
+                                      <tr id="plane_row_template" class="plane_table_row hidden">
+                                        <td>ICAO</td>
+                                        <td>FLIGHT</td>
+                                        <td align="right">SQUAWK</td>
+                                        <td align="right">ALTITUDE</td>
+                                        <td align="right">SPEED</td>
+                                        <td align="right">DISTANCE</td>
+                                        <td align="right">TRACK</td>
+                                        <td align="right">MSGS</td>
+                                        <td align="right">SEEN</td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </div>
+			</div>
+		</div>
+		<div id="SpecialSquawkWarning" class="hidden">
+		    <b>Squawk 7x00 is reported and shown.</b><br>
+            This is most likely an error in receiving or decoding.<br>
+            Please do not call the local authorities, they already know about it if it is a valid squawk.
+		</div>
+
+		<div id="update_error" class="hidden">
+                  <b>Problem fetching data from dump1090.</b><br>
+                  <span id="update_error_detail"></span><br>
+                  The displayed map data will be out of date.
+                </div>
+
+		<div id="container_splitter"></div>
+	</body>
+</html>
diff --git a/public_html/planeObject.js b/public_html/planeObject.js
new file mode 100644
index 0000000..92ede4b
--- /dev/null
+++ b/public_html/planeObject.js
@@ -0,0 +1,359 @@
+"use strict";
+
+var PlaneSvg = "M 0,0 " +
+        "M 1.9565564,41.694305 C 1.7174505,40.497708 1.6419973,38.448747 " +
+        "1.8096508,37.70494 1.8936398,37.332056 2.0796653,36.88191 2.222907,36.70461 " +
+        "2.4497603,36.423844 4.087816,35.47248 14.917931,29.331528 l 12.434577," +
+        "-7.050718 -0.04295,-7.613412 c -0.03657,-6.4844888 -0.01164,-7.7625804 " +
+        "0.168134,-8.6194061 0.276129,-1.3160905 0.762276,-2.5869575 1.347875," +
+        "-3.5235502 l 0.472298,-0.7553719 1.083746,-0.6085497 c 1.194146,-0.67053522 " +
+        "1.399524,-0.71738842 2.146113,-0.48960552 1.077005,0.3285939 2.06344," +
+        "1.41299352 2.797602,3.07543322 0.462378,1.0469993 0.978731,2.7738408 " +
+        "1.047635,3.5036272 0.02421,0.2570284 0.06357,3.78334 0.08732,7.836246 0.02375," +
+        "4.052905 0.0658,7.409251 0.09345,7.458546 0.02764,0.04929 5.600384,3.561772 " +
+        "12.38386,7.805502 l 12.333598,7.715871 0.537584,0.959688 c 0.626485,1.118378 " +
+        "0.651686,1.311286 0.459287,3.516442 -0.175469,2.011604 -0.608966,2.863924 " +
+        "-1.590344,3.127136 -0.748529,0.200763 -1.293144,0.03637 -10.184829,-3.07436 " +
+        "C 48.007733,41.72562 44.793806,40.60197 43.35084,40.098045 l -2.623567," +
+        "-0.916227 -1.981212,-0.06614 c -1.089663,-0.03638 -1.985079,-0.05089 -1.989804," +
+        "-0.03225 -0.0052,0.01863 -0.02396,2.421278 -0.04267,5.339183 -0.0395,6.147742 " +
+        "-0.143635,7.215456 -0.862956,8.845475 l -0.300457,0.680872 2.91906,1.361455 " +
+        "c 2.929379,1.366269 3.714195,1.835385 4.04589,2.41841 0.368292,0.647353 " +
+        "0.594634,2.901439 0.395779,3.941627 -0.0705,0.368571 -0.106308,0.404853 " +
+        "-0.765159,0.773916 L 41.4545,62.83158 39.259237,62.80426 c -6.030106,-0.07507 " +
+        "-16.19508,-0.495041 -16.870991,-0.697033 -0.359409,-0.107405 -0.523792," +
+        "-0.227482 -0.741884,-0.541926 -0.250591,-0.361297 -0.28386,-0.522402 -0.315075," +
+        "-1.52589 -0.06327,-2.03378 0.23288,-3.033615 1.077963,-3.639283 0.307525," +
+        "-0.2204 4.818478,-2.133627 6.017853,-2.552345 0.247872,-0.08654 0.247455," +
+        "-0.102501 -0.01855,-0.711959 -0.330395,-0.756986 -0.708622,-2.221756 -0.832676," +
+        "-3.224748 -0.05031,-0.406952 -0.133825,-3.078805 -0.185533,-5.937448 -0.0517," +
+        "-2.858644 -0.145909,-5.208974 -0.209316,-5.222958 -0.06341,-0.01399 -0.974464," +
+        "-0.0493 -2.024551,-0.07845 L 23.247235,38.61921 18.831373,39.8906 C 4.9432155," +
+        "43.88916 4.2929558,44.057819 3.4954426,43.86823 2.7487826,43.690732 2.2007966," +
+        "42.916622 1.9565564,41.694305 z";
+
+function PlaneObject(icao) {
+	// Info about the plane
+        this.icao      = icao;
+        this.flight    = null;
+	this.squawk    = null;
+	this.selected  = false;
+
+	// Basic location information
+        this.altitude  = null;
+        this.speed     = null;
+        this.track     = null;
+        this.position  = null;
+        this.sitedist  = null;
+
+	// Data packet numbers
+	this.messages  = null;
+        this.rssi      = null;
+
+        // Track history as a series of line segments
+        this.track_linesegs = [];
+        this.history_size = 0;
+
+	// When was this last updated (receiver timestamp)
+        this.last_message_time = null;
+        this.last_position_time = null;
+
+        // When was this last updated (seconds before last update)
+        this.seen = null;
+        this.seen_pos = null;
+
+        // Display info
+        this.visible = true;
+        this.marker = null;
+        this.icon = { strokeWeight: 1,
+                      path: PlaneSvg,
+                      scale: 0.4,
+                      fillColor: MarkerColor,
+                      fillOpacity: 0.9,
+                      anchor: new google.maps.Point(32, 32), // Set anchor to middle of plane.
+                      rotation: 0 };
+}
+
+// Appends data to the running track so we can get a visual tail on the plane
+// Only useful for a long running browser session.
+PlaneObject.prototype.updateTrack = function(estimate_time) {
+        var here = this.position;
+        if (!here)
+                return;
+
+        if (this.track_linesegs.length == 0) {
+                // Brand new track
+                //console.log(this.icao + " new track");
+                var newseg = { track : new google.maps.MVCArray([here,here]),
+                               line : null,
+                               head_update : this.last_position_time,
+                               tail_update : this.last_position_time,
+                               estimated : false,
+                               ground : (this.altitude === "ground")
+                             };
+                this.track_linesegs.push(newseg);
+                this.history_size += 2;
+                return;
+        }
+        
+        var lastseg = this.track_linesegs[this.track_linesegs.length - 1];
+        var lastpos = lastseg.track.getAt(lastseg.track.getLength() - 1);
+        var elapsed = (this.last_position_time - lastseg.head_update);
+        
+        var new_data = (here !== lastpos);
+        var est_track = (elapsed > estimate_time);
+        var ground_track = (this.altitude === "ground");
+        
+        if (!new_data)
+                return false;
+        
+        if (est_track) {
+                if (!lastseg.estimated) {
+                        // >5s gap in data, create a new estimated segment
+                        //console.log(this.icao + " switching to estimated");
+                        this.track_linesegs.push({ track : new google.maps.MVCArray([lastpos, here]),
+                                                   line : null,
+                                                   head_update : this.last_position_time,
+                                                   estimated : true });
+                        this.history_size += 2;
+                        return true;
+                }
+                
+                // Append to ongoing estimated line
+                //console.log(this.icao + " extending estimated (" + lastseg.track.getLength() + ")");
+                lastseg.track.push(here);
+                lastseg.head_update = this.last_position_time;
+                this.history_size++;
+                return true;
+        }
+        
+        if (lastseg.estimated) {
+                // We are back to good data.
+                //console.log(this.icao + " switching to good track");
+                this.track_linesegs.push({ track : new google.maps.MVCArray([lastpos, here]),
+                                           line : null,
+                                           head_update : this.last_position_time,
+                                           tail_update : this.last_position_time,
+                                           estimated : false,
+                                           ground : (this.altitude === "ground") });
+                this.history_size += 2;
+                return true;
+        }
+        
+        if ( (lastseg.ground && this.altitude !== "ground") ||
+             (!lastseg.ground && this.altitude === "ground") ) {
+                //console.log(this.icao + " ground state changed");
+                // Create a new segment as the ground state changed.
+                // assume the state changed halfway between the two points
+                var midpoint = google.maps.geometry.spherical.interpolate(lastpos,here,0.5);
+                lastseg.track.push(midpoint);
+                this.track_linesegs.push({ track : new google.maps.MVCArray([midpoint,here,here]),
+                                           line : null,
+                                           head_update : this.last_position_time,
+                                           tail_update : this.last_position_time,
+                                           estimated : false,
+                                           ground : (this.altitude === "ground") });
+                this.history_size += 4;
+                return true;
+        }
+        
+        // Add more data to the existing track.
+        // We only retain some historical points, at 5+ second intervals,
+        // plus the most recent point
+        if (this.last_position_time - lastseg.tail_update >= 5) {
+                // enough time has elapsed; retain the last point and add a new one
+                //console.log(this.icao + " retain last point");
+                lastseg.track.push(here);
+                lastseg.tail_update = lastseg.head_update;
+                this.history_size ++;
+        } else {
+                // replace the last point with the current position
+                lastseg.track.setAt(lastseg.track.getLength()-1, here);
+        }
+        lastseg.head_update = this.last_position_time;
+        return true;
+};
+
+// This is to remove the line from the screen if we deselect the plane
+PlaneObject.prototype.clearLines = function() {
+        for (var i = 0; i < this.track_linesegs.length; ++i) {
+                var seg = this.track_linesegs[i];
+                if (seg.line !== null) {
+                        seg.line.setMap(null);
+                        seg.line = null;
+                }
+        }
+};
+
+PlaneObject.prototype.updateIcon = function() {
+        var col = MarkerColor;
+        
+	// If this marker is selected we should make it lighter than the rest.
+	if (this.selected)
+		col = SelectedColor;
+        
+	// If we have not seen a recent position update, change color
+	if (this.seen_pos > 15)
+		col = StaleColor;
+	
+	// If the squawk code is one of the international emergency codes,
+	// match the info window alert color.
+        if (this.squawk in SpecialSquawks)
+                col = SpecialSquawks[this.squawk].markerColor;
+        
+        var weight = this.selected ? 2 : 1;
+        var rotation = (this.track === null ? 0 : this.track);
+        
+        if (col === this.icon.fillColor && weight === this.icon.strokeWeight && rotation === this.icon.rotation)
+                return false;  // no changes
+        
+        this.icon.fillColor = col;                
+        this.icon.strokeWeight = weight;
+        this.icon.rotation = rotation;
+        if (this.marker)
+                this.marker.setIcon(this.icon);
+        
+        return true;
+};
+
+// Update our data
+PlaneObject.prototype.updateData = function(receiver_timestamp, data) {
+	// Update all of our data
+	this.messages	= data.messages;
+        this.rssi       = data.rssi;
+	this.last_message_time = receiver_timestamp - data.seen;
+        
+        if (typeof data.altitude !== "undefined")
+		this.altitude	= data.altitude;
+        if (typeof data.vert_rate !== "undefined")
+		this.vert_rate	= data.vert_rate;
+        if (typeof data.speed !== "undefined")
+		this.speed	= data.speed;
+        if (typeof data.track !== "undefined")
+                this.track	= data.track;
+        if (typeof data.lat !== "undefined") {
+                this.position   = new google.maps.LatLng(data.lat, data.lon);
+                this.last_position_time = receiver_timestamp - data.seen_pos;
+
+                if (SitePosition !== null) {
+                        this.sitedist = google.maps.geometry.spherical.computeDistanceBetween (SitePosition, this.position);
+                }
+        }
+        if (typeof data.flight !== "undefined")
+		this.flight	= data.flight;
+        if (typeof data.squawk !== "undefined")
+		this.squawk	= data.squawk;
+};
+
+PlaneObject.prototype.updateTick = function(receiver_timestamp, last_timestamp) {
+        // recompute seen and seen_pos
+        this.seen = receiver_timestamp - this.last_message_time;
+        this.seen_pos = (this.last_position_time === null ? null : receiver_timestamp - this.last_position_time);
+        
+	// If no packet in over 58 seconds, clear the plane.
+	if (this.seen > 58) {
+                if (this.visible) {
+                        //console.log("hiding " + this.icao);
+                        this.clearMarker();
+                        this.visible = false;
+			if (SelectedPlane == this.icao)
+                                selectPlaneByHex(null,false);
+                }
+	} else {
+                this.visible = true;
+                if (this.position !== null) {
+			if (this.updateTrack(receiver_timestamp - last_timestamp + 5)) {
+                                this.updateLines();
+                                this.updateMarker(true);
+                        } else { 
+                                this.updateMarker(false); // didn't move
+                        }
+                }
+	}
+};
+
+PlaneObject.prototype.clearMarker = function() {
+	if (this.marker) {
+		this.marker.setMap(null);
+                google.maps.event.clearListeners(this.marker, 'click');
+		this.marker = null;
+	}
+};
+
+// Update our marker on the map
+PlaneObject.prototype.updateMarker = function(moved) {
+        if (!this.visible) {
+                this.clearMarker();
+                return;
+        }
+        
+	if (this.marker) {
+                if (moved)
+			this.marker.setPosition(this.position);
+                this.updateIcon();
+	} else {
+                this.updateIcon();
+		this.marker = new google.maps.Marker({
+			position: this.position,
+			map: GoogleMap,
+			icon: this.icon,
+			visible: true
+		});
+                
+		// Trap clicks for this marker.
+		google.maps.event.addListener(this.marker, 'click', selectPlaneByHex.bind(undefined,this.icao,false));
+		google.maps.event.addListener(this.marker, 'dblclick', selectPlaneByHex.bind(undefined,this.icao,true));
+	}
+        
+	// Setting the marker title
+        var title = (this.flight === null || this.flight.length == 0) ? this.icao : (this.flight+' ('+this.icao+')');
+        if (title !== this.marker.title)
+	        this.marker.setTitle(title);
+};
+
+// Update our planes tail line,
+PlaneObject.prototype.updateLines = function() {
+        if (!this.selected)
+                return;
+        
+        for (var i = 0; i < this.track_linesegs.length; ++i) {
+                var seg = this.track_linesegs[i];
+                if (seg.line === null) {
+                        // console.log("create line for seg " + i + " with " + seg.track.getLength() + " points" + (seg.estimated ? " (estimated)" : ""));
+                        // for (var j = 0; j < seg.track.getLength(); j++) {
+                        //         console.log("  point " + j + " at " + seg.track.getAt(j).lat() + "," + seg.track.getAt(j).lng());
+                        // }
+                        
+                        if (seg.estimated) {
+                                var lineSymbol = {
+                                        path: 'M 0,-1 0,1',
+                                        strokeOpacity : 1,
+                                        strokeColor : '#804040',
+                                        strokeWeight : 2,
+                                        scale: 2
+                                };
+                                
+                                seg.line = new google.maps.Polyline({
+                                        path: seg.track,
+					strokeOpacity: 0,
+                                        icons: [{
+                                                icon: lineSymbol,
+                                                offset: '0',
+                                                repeat: '10px' }],
+                                        map : GoogleMap });
+                        } else {
+                                seg.line = new google.maps.Polyline({
+                                        path: seg.track,
+					strokeOpacity: 1.0,
+					strokeColor: (seg.ground ? '#408040' : '#000000'),
+					strokeWeight: 3,
+					map: GoogleMap });
+                        }
+                }
+        }
+};
+
+PlaneObject.prototype.destroy = function() {
+        this.clearLines();
+        this.clearMarker();
+};
diff --git a/public_html/script.js b/public_html/script.js
new file mode 100644
index 0000000..7d6e160
--- /dev/null
+++ b/public_html/script.js
@@ -0,0 +1,914 @@
+"use strict";
+
+// Define our global variables
+var GoogleMap     = null;
+var Planes        = {};
+var PlanesOrdered = [];
+var SelectedPlane = null;
+var FollowSelected = false;
+
+var SpecialSquawks = {
+        '7500' : { cssClass: 'squawk7500', markerColor: 'rgb(255, 85, 85)', text: 'Aircraft Hijacking' },
+        '7600' : { cssClass: 'squawk7600', markerColor: 'rgb(0, 255, 255)', text: 'Radio Failure' },
+        '7700' : { cssClass: 'squawk7700', markerColor: 'rgb(255, 255, 0)', text: 'General Emergency' }
+};
+
+// Get current map settings
+var CenterLat, CenterLon, ZoomLvl;
+
+var Dump1090Version = "unknown version";
+var RefreshInterval = 1000;
+
+var PlaneRowTemplate = null;
+
+var TrackedAircraft = 0;
+var TrackedAircraftPositions = 0;
+var TrackedHistorySize = 0;
+
+var SitePosition = null;
+
+var ReceiverClock = null;
+
+var LastReceiverTimestamp = 0;
+var StaleReceiverCount = 0;
+var FetchPending = null;
+
+var MessageCountHistory = [];
+
+var NBSP='\u00a0';
+var DEGREES='\u00b0'
+var UP_TRIANGLE='\u25b2'; // U+25B2 BLACK UP-POINTING TRIANGLE
+var DOWN_TRIANGLE='\u25bc'; // U+25BC BLACK DOWN-POINTING TRIANGLE
+
+function processReceiverUpdate(data) {
+	// Loop through all the planes in the data packet
+        var now = data.now;
+        var acs = data.aircraft;
+
+        // Detect stats reset
+        if (MessageCountHistory.length > 0 && MessageCountHistory[MessageCountHistory.length-1].messages > data.messages) {
+                MessageCountHistory = [{'time' : MessageCountHistory[MessageCountHistory.length-1].time,
+                                        'messages' : 0}];
+        }
+
+        // Note the message count in the history
+        MessageCountHistory.push({ 'time' : now, 'messages' : data.messages});
+        // .. and clean up any old values
+        if ((now - MessageCountHistory[0].time) > 30)
+                MessageCountHistory.shift();
+
+	for (var j=0; j < acs.length; j++) {
+                var ac = acs[j];
+                var hex = ac.hex;
+                var plane = null;
+
+		// Do we already have this plane object in Planes?
+		// If not make it.
+
+		if (Planes[hex]) {
+			plane = Planes[hex];
+		} else {
+			plane = new PlaneObject(hex);
+                        plane.tr = PlaneRowTemplate.cloneNode(true);
+                        if (hex[0] === '~') {
+                                // Non-ICAO address
+                                plane.tr.cells[0].textContent = hex.substring(1);
+                                $(plane.tr).css('font-style', 'italic');
+                        } else {
+                                plane.tr.cells[0].textContent = hex;
+                        }
+
+                        plane.tr.addEventListener('click', selectPlaneByHex.bind(undefined,hex,false));
+                        plane.tr.addEventListener('dblclick', selectPlaneByHex.bind(undefined,hex,true));
+                        
+                        Planes[hex] = plane;
+                        PlanesOrdered.push(plane);
+		}
+
+		// Call the function update
+		plane.updateData(now, ac);
+	}
+}
+
+function fetchData() {
+        if (FetchPending !== null && FetchPending.state() == 'pending') {
+                // don't double up on fetches, let the last one resolve
+                return;
+        }
+
+	FetchPending = $.ajax({ url: 'data/aircraft.json',
+                                timeout: 5000,
+                                cache: false,
+                                dataType: 'json' });
+        FetchPending.done(function(data) {
+                var now = data.now;
+
+                processReceiverUpdate(data);
+
+                // update timestamps, visibility, history track for all planes - not only those updated
+                for (var i = 0; i < PlanesOrdered.length; ++i) {
+                        var plane = PlanesOrdered[i];
+                        plane.updateTick(now, LastReceiverTimestamp);
+                }
+                
+		refreshTableInfo();
+		refreshSelected();
+                
+                if (ReceiverClock) {
+                        var rcv = new Date(now * 1000);
+                        ReceiverClock.render(rcv.getUTCHours(),rcv.getUTCMinutes(),rcv.getUTCSeconds());
+                }
+
+                // Check for stale receiver data
+                if (LastReceiverTimestamp === now) {
+                        StaleReceiverCount++;
+                        if (StaleReceiverCount > 5) {
+                                $("#update_error_detail").text("The data from dump1090 hasn't been updated in a while. Maybe dump1090 is no longer running?");
+                                $("#update_error").css('display','block');
+                        }
+                } else { 
+                        StaleReceiverCount = 0;
+                        LastReceiverTimestamp = now;
+                        $("#update_error").css('display','none');
+                }
+	});
+
+        FetchPending.fail(function(jqxhr, status, error) {
+                $("#update_error_detail").text("AJAX call failed (" + status + (error ? (": " + error) : "") + "). Maybe dump1090 is no longer running?");
+                $("#update_error").css('display','block');
+        });
+}
+
+var PositionHistorySize = 0;
+function initialize() {
+        // Set page basics
+        $("head title").text(PageName);
+        $("#infoblock_name").text(PageName);
+
+        PlaneRowTemplate = document.getElementById("plane_row_template");
+
+        if (!ShowClocks) {
+                $('#timestamps').css('display','none');
+        } else {
+                // Create the clocks.
+		new CoolClock({
+			canvasId:       "utcclock",
+			skinId:         "classic",
+			displayRadius:  40,
+			showSecondHand: true,
+			gmtOffset:      "0", // this has to be a string!
+			showDigital:    false,
+			logClock:       false,
+			logClockRev:    false
+		});
+
+		ReceiverClock = new CoolClock({
+			canvasId:       "receiverclock",
+			skinId:         "classic",
+			displayRadius:  40,
+			showSecondHand: true,
+			gmtOffset:      null,
+			showDigital:    false,
+			logClock:       false,
+			logClockRev:    false
+		});
+
+                // disable ticking on the receiver clock, we will update it ourselves
+                ReceiverClock.tick = (function(){})
+        }
+
+        $("#loader").removeClass("hidden");
+        
+        // Get receiver metadata, reconfigure using it, then continue
+        // with initialization
+        $.ajax({ url: 'data/receiver.json',
+                 timeout: 5000,
+                 cache: false,
+                 dataType: 'json' })
+
+                .done(function(data) {
+                        if (typeof data.lat !== "undefined") {
+                                SiteShow = true;
+                                SiteLat = data.lat;
+                                SiteLon = data.lon;
+                                DefaultCenterLat = data.lat;
+                                DefaultCenterLon = data.lon;
+                        }
+                        
+                        Dump1090Version = data.version;
+                        RefreshInterval = data.refresh;
+                        PositionHistorySize = data.history;
+                })
+
+                .always(function() {
+                        initialize_map();
+                        start_load_history();
+                });
+}
+
+var CurrentHistoryFetch = null;
+var PositionHistoryBuffer = []
+function start_load_history() {
+        if (PositionHistorySize > 0) {
+                $("#loader_progress").attr('max',PositionHistorySize);
+                console.log("Starting to load history (" + PositionHistorySize + " items)");
+                load_history_item(0);
+        } else {
+                end_load_history();
+        }
+}
+
+function load_history_item(i) {
+        if (i >= PositionHistorySize) {
+                end_load_history();
+                return;
+        }
+
+        console.log("Loading history #" + i);
+        $("#loader_progress").attr('value',i);
+
+        $.ajax({ url: 'data/history_' + i + '.json',
+                 timeout: 5000,
+                 cache: false,
+                 dataType: 'json' })
+
+                .done(function(data) {
+                        PositionHistoryBuffer.push(data);
+                        load_history_item(i+1);
+                })
+
+                .fail(function(jqxhr, status, error) {
+                        // No more history
+                        end_load_history();
+                });
+}
+
+function end_load_history() {
+        $("#loader").addClass("hidden");
+
+        console.log("Done loading history");
+
+        if (PositionHistoryBuffer.length > 0) {
+                var now, last=0;
+
+                // Sort history by timestamp
+                console.log("Sorting history");
+                PositionHistoryBuffer.sort(function(x,y) { return (x.now - y.now); });
+
+                // Process history
+                for (var h = 0; h < PositionHistoryBuffer.length; ++h) {
+                        now = PositionHistoryBuffer[h].now;
+                        console.log("Applying history " + h + "/" + PositionHistoryBuffer.length + " at: " + now);
+                        processReceiverUpdate(PositionHistoryBuffer[h]);
+
+                        // update track
+                        console.log("Updating tracks at: " + now);
+                        for (var i = 0; i < PlanesOrdered.length; ++i) {
+                                var plane = PlanesOrdered[i];
+                                plane.updateTrack((now - last) + 1);
+                        }
+
+                        last = now;
+                }
+
+                // Final pass to update all planes to their latest state
+                console.log("Final history cleanup pass");
+                for (var i = 0; i < PlanesOrdered.length; ++i) {
+                        var plane = PlanesOrdered[i];
+                        plane.updateTick(now);
+                }
+
+                LastReceiverTimestamp = last;
+        }
+
+        PositionHistoryBuffer = null;
+
+        console.log("Completing init");
+
+        refreshTableInfo();
+        refreshSelected();
+        reaper();
+
+        // Setup our timer to poll from the server.
+        window.setInterval(fetchData, RefreshInterval);
+        window.setInterval(reaper, 60000);
+
+        // And kick off one refresh immediately.
+        fetchData();
+
+}
+
+// Initalizes the map and starts up our timers to call various functions
+function initialize_map() {
+        // Load stored map settings if present
+        CenterLat = Number(localStorage['CenterLat']) || DefaultCenterLat;
+        CenterLon = Number(localStorage['CenterLon']) || DefaultCenterLon;
+        ZoomLvl = Number(localStorage['ZoomLvl']) || DefaultZoomLvl;
+
+        // Set SitePosition, initialize sorting
+        if (SiteShow && (typeof SiteLat !==  'undefined') && (typeof SiteLon !==  'undefined')) {
+	        SitePosition = new google.maps.LatLng(SiteLat, SiteLon);
+                sortByDistance();
+        } else {
+	        SitePosition = null;
+                PlaneRowTemplate.cells[5].style.display = 'none'; // hide distance column
+                document.getElementById("distance").style.display = 'none'; // hide distance header
+                sortByAltitude();
+        }
+
+	// Make a list of all the available map IDs
+	var mapTypeIds = [];
+	for(var type in google.maps.MapTypeId) {
+		mapTypeIds.push(google.maps.MapTypeId[type]);
+	}
+	// Push OSM on to the end
+	mapTypeIds.push("OSM");
+	mapTypeIds.push("dark_map");
+
+	// Styled Map to outline airports and highways
+	var styles = [
+		{
+			"featureType": "administrative",
+			"stylers": [
+				{ "visibility": "off" }
+			]
+		},{
+			"featureType": "landscape",
+			"stylers": [
+				{ "visibility": "off" }
+			]
+		},{
+			"featureType": "poi",
+			"stylers": [
+				{ "visibility": "off" }
+			]
+		},{
+			"featureType": "road",
+			"stylers": [
+				{ "visibility": "off" }
+			]
+		},{
+			"featureType": "transit",
+			"stylers": [
+				{ "visibility": "off" }
+			]
+		},{
+			"featureType": "landscape",
+			"stylers": [
+				{ "visibility": "on" },
+				{ "weight": 8 },
+				{ "color": "#000000" }
+			]
+		},{
+			"featureType": "water",
+			"stylers": [
+			{ "lightness": -74 }
+			]
+		},{
+			"featureType": "transit.station.airport",
+			"stylers": [
+				{ "visibility": "on" },
+				{ "weight": 8 },
+				{ "invert_lightness": true },
+				{ "lightness": 27 }
+			]
+		},{
+			"featureType": "road.highway",
+			"stylers": [
+				{ "visibility": "simplified" },
+				{ "invert_lightness": true },
+				{ "gamma": 0.3 }
+			]
+		},{
+			"featureType": "road",
+			"elementType": "labels",
+			"stylers": [
+				{ "visibility": "off" }
+			]
+		}
+	]
+
+	// Add our styled map
+	var styledMap = new google.maps.StyledMapType(styles, {name: "Dark Map"});
+
+	// Define the Google Map
+	var mapOptions = {
+		center: new google.maps.LatLng(CenterLat, CenterLon),
+		zoom: ZoomLvl,
+		mapTypeId: google.maps.MapTypeId.ROADMAP,
+		mapTypeControl: true,
+		streetViewControl: false,
+		mapTypeControlOptions: {
+			mapTypeIds: mapTypeIds,
+			position: google.maps.ControlPosition.TOP_LEFT,
+			style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
+		}
+	};
+
+	GoogleMap = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
+
+	//Define OSM map type pointing at the OpenStreetMap tile server
+	GoogleMap.mapTypes.set("OSM", new google.maps.ImageMapType({
+		getTileUrl: function(coord, zoom) {
+			return "http://tile.openstreetmap.org/" + zoom + "/" + coord.x + "/" + coord.y + ".png";
+		},
+		tileSize: new google.maps.Size(256, 256),
+		name: "OpenStreetMap",
+		maxZoom: 18
+	}));
+
+	GoogleMap.mapTypes.set("dark_map", styledMap);
+	
+	// Listeners for newly created Map
+        google.maps.event.addListener(GoogleMap, 'center_changed', function() {
+                localStorage['CenterLat'] = GoogleMap.getCenter().lat();
+                localStorage['CenterLon'] = GoogleMap.getCenter().lng();
+                if (FollowSelected) {
+                        // On manual navigation, disable follow
+                        var selected = Planes[SelectedPlane];
+                        if (Math.abs(GoogleMap.getCenter().lat() - selected.position.lat()) > 0.0001 &&
+                            Math.abs(GoogleMap.getCenter().lng() - selected.position.lng()) > 0.0001) {
+                                FollowSelected = false;
+                                refreshSelected();
+                        }
+                }
+        });
+    
+        google.maps.event.addListener(GoogleMap, 'zoom_changed', function() {
+                localStorage['ZoomLvl']  = GoogleMap.getZoom();
+        });
+	
+	// Add home marker if requested
+	if (SitePosition) {
+	    var markerImage = new google.maps.MarkerImage(
+	        'http://maps.google.com/mapfiles/kml/pal4/icon57.png',
+            new google.maps.Size(32, 32),   // Image size
+            new google.maps.Point(0, 0),    // Origin point of image
+            new google.maps.Point(16, 16)); // Position where marker should point 
+	    var marker = new google.maps.Marker({
+          position: SitePosition,
+          map: GoogleMap,
+          icon: markerImage,
+          title: SiteName,
+          zIndex: -99999
+        });
+        
+        if (SiteCircles) {
+            for (var i=0;i<SiteCirclesDistances.length;i++) {
+              drawCircle(marker, SiteCirclesDistances[i]); // in meters
+            }
+        }
+	}
+}
+
+// This looks for planes to reap out of the master Planes variable
+function reaper() {
+        //console.log("Reaping started..");
+
+	// Look for planes where we have seen no messages for >300 seconds
+        var newPlanes = [];
+        for (var i = 0; i < PlanesOrdered.length; ++i) {
+                var plane = PlanesOrdered[i];
+                if (plane.seen > 300) {
+			// Reap it.                                
+                        //console.log("Reaping " + plane.icao);
+                        //console.log("parent " + plane.tr.parentNode);
+                        plane.tr.parentNode.removeChild(plane.tr);
+                        plane.tr = null;
+			delete Planes[plane.icao];
+                        plane.destroy();
+		} else {
+                        // Keep it.
+                        newPlanes.push(plane);
+		}
+	};
+
+        PlanesOrdered = newPlanes;
+        refreshTableInfo();
+        refreshSelected();
+} 
+
+//
+// formatting helpers
+//
+
+var TrackDirections = ["North","Northeast","East","Southeast","South","Southwest","West","Northwest"];
+
+// track in degrees (0..359)
+function format_track_brief(track) {
+        if (track === null) return "";
+        return Math.round(track);
+}
+
+// track in degrees (0..359)
+function format_track_long(track) {
+        if (track === null) return "n/a";
+        var trackDir = Math.floor((360 + track % 360 + 22.5) / 45) % 8;
+        return Math.round(track) + DEGREES + NBSP + "(" + TrackDirections[trackDir] + ")";
+}
+
+// alt in ft
+function format_altitude_brief(alt, vr) {
+        var alt_text;
+
+        if (alt === null)
+                return "";
+        if (alt === "ground")
+                return "ground";
+
+        if (Metric)
+                alt_text = Math.round(alt / 3.2828) + NBSP;
+        else
+                alt_text = Math.round(alt) + NBSP;
+
+        if (vr > 128)
+                return alt_text + UP_TRIANGLE;
+        else if (vr < -128)
+                return alt_text + DOWN_TRIANGLE;
+        else
+                return alt_text + NBSP;
+}
+
+// alt in ft
+function format_altitude_long(alt, vr) {
+        var alt_text;
+
+        if (alt === null)
+                return "n/a";
+        if (alt === "ground")
+                return "on ground";
+        
+        if (Metric)
+                alt_text = Math.round(alt / 3.2828) + NBSP + "m / " + Math.round(alt) + NBSP + "ft";
+        else
+                alt_text = Math.round(alt) + NBSP + "ft / " + Math.round(alt / 3.2828) + NBSP + "m";
+
+        if (vr > 128)
+                return UP_TRIANGLE + NBSP + alt_text;
+        else if (vr < -128)
+                return DOWN_TRIANGLE + NBSP + alt_text;
+        else
+                return alt_text;
+}
+
+// speed in kts
+function format_speed_brief(speed) {
+        if (speed === null)
+                return "";
+
+        if (Metric)
+    		return Math.round(speed * 1.852);
+        else
+                return Math.round(speed);
+}
+
+// speed in kts
+function format_speed_long(speed) {
+        if (speed === null)
+                return "n/a";
+
+        if (Metric)
+    		return Math.round(speed * 1.852) + NBSP + "km/h / " + Math.round(speed) + NBSP + "kt";
+        else
+                return Math.round(speed) + NBSP + "kt / " + Math.round(speed * 1.852) + NBSP + "km/h";
+}
+
+// dist in metres
+function format_distance_brief(dist) {
+        if (dist === null)
+                return "";
+
+        if (Metric)
+                return (dist/1000).toFixed(1);
+        else
+                return (dist/1852).toFixed(1);
+}
+
+// dist in metres
+function format_distance_long(dist) {
+        if (dist === null)
+                return "n/a";
+
+        if (Metric)
+                return (dist/1000).toFixed(1) + " km / " + (dist/1852).toFixed(1) + " NM";
+        else
+                return (dist/1852).toFixed(1) + " NM / " + (dist/1000).toFixed(1) + " km";
+}
+
+// p as a LatLng
+function format_latlng(p) {
+        return p.lat().toFixed(3) + DEGREES + "," + NBSP + p.lng().toFixed(3) + DEGREES;
+}
+
+// Refresh the detail window about the plane
+function refreshSelected() {
+        var selected = false;
+	if (typeof SelectedPlane !== 'undefined' && SelectedPlane != "ICAO" && SelectedPlane != null) {
+    	        selected = Planes[SelectedPlane];
+        }
+        
+        if (!selected) {
+                $('#selected_infoblock').css('display','none');
+                $('#dump1090_infoblock').css('display','block');
+                $('#dump1090_version').text(Dump1090Version);
+                $('#dump1090_total_ac').text(TrackedAircraft);
+                $('#dump1090_total_ac_positions').text(TrackedAircraftPositions);
+                $('#dump1090_total_history').text(TrackedHistorySize);
+
+                var message_rate = null;
+                if (MessageCountHistory.length > 1) {
+                        var message_time_delta = MessageCountHistory[MessageCountHistory.length-1].time - MessageCountHistory[0].time;
+                        var message_count_delta = MessageCountHistory[MessageCountHistory.length-1].messages - MessageCountHistory[0].messages;
+                        if (message_time_delta > 0)
+                                message_rate = message_count_delta / message_time_delta;
+                }
+                
+                if (message_rate !== null)
+                        $('#dump1090_message_rate').text(message_rate.toFixed(1));
+                else
+                        $('#dump1090_message_rate').text("n/a");
+                        
+                return;
+        }
+        
+        $('#dump1090_infoblock').css('display','none');
+        $('#selected_infoblock').css('display','block');
+        
+        if (selected.flight !== null && selected.flight !== "") {
+                $('#selected_callsign').text(selected.flight);
+                $('#selected_links').css('display','inline');
+                $('#selected_fr24_link').attr('href','http://fr24.com/'+selected.flight);
+                $('#selected_flightstats_link').attr('href','http://www.flightstats.com/go/FlightStatus/flightStatusByFlight.do?flightNumber='+selected.flight);
+                $('#selected_flightaware_link').attr('href','http://flightaware.com/live/flight/'+selected.flight);
+        } else {
+                $('#selected_callsign').text('n/a');
+                $('#selected_links').css('display','none');
+        }
+                
+        var emerg = document.getElementById('selected_emergency');
+        if (selected.squawk in SpecialSquawks) {
+                emerg.className = SpecialSquawks[selected.squawk].cssClass;
+                emerg.textContent = NBSP + 'Squawking: ' + SpecialSquawks[selected.squawk].text + NBSP ;
+        } else {
+                emerg.className = 'hidden';
+        }
+
+        $("#selected_altitude").text(format_altitude_long(selected.altitude, selected.vert_rate));
+
+        if (selected.squawk === null || selected.squawk === '0000') {
+                $('#selected_squawk').text('n/a');
+        } else {
+                $('#selected_squawk').text(selected.squawk);
+        }
+	
+        $('#selected_speed').text(format_speed_long(selected.speed));
+        $('#selected_icao').text(selected.icao.toUpperCase());
+        $('#airframes_post_icao').attr('value',selected.icao);
+	$('#selected_track').text(format_track_long(selected.track));
+
+        if (selected.seen <= 1) {
+                $('#selected_seen').text('now');
+        } else {
+                $('#selected_seen').text(selected.seen.toFixed(1) + 's');
+        }
+
+	if (selected.position === null) {
+                $('#selected_position').text('n/a');
+                $('#selected_follow').addClass('hidden');
+        } else {
+                if (selected.seen_pos > 1) {
+                        $('#selected_position').text(format_latlng(selected.position) + " (" + selected.seen_pos.toFixed(1) + "s)");
+                } else {
+                        $('#selected_position').text(format_latlng(selected.position));
+                }
+                $('#selected_follow').removeClass('hidden');
+                if (FollowSelected) {
+                        $('#selected_follow').css('font-weight', 'bold');
+                        GoogleMap.panTo(selected.position);
+                } else {
+                        $('#selected_follow').css('font-weight', 'normal');
+                }
+	}
+        
+        $('#selected_sitedist').text(format_distance_long(selected.sitedist));
+        $('#selected_rssi').text(selected.rssi.toFixed(1) + ' dBFS');
+}
+
+// Refreshes the larger table of all the planes
+function refreshTableInfo() {
+        var show_squawk_warning = false;
+
+        TrackedAircraft = 0
+        TrackedAircraftPositions = 0
+        TrackedHistorySize = 0
+
+        for (var i = 0; i < PlanesOrdered.length; ++i) {
+		var tableplane = PlanesOrdered[i];
+                TrackedHistorySize += tableplane.history_size;
+		if (!tableplane.visible) {
+                        tableplane.tr.className = "plane_table_row hidden";
+                } else {
+                        TrackedAircraft++;
+                        var classes = "plane_table_row";
+                        
+			if (tableplane.position !== null)
+                                classes += " vPosition";
+			if (tableplane.icao == SelectedPlane)
+                                classes += " selected";
+                        
+                        if (tableplane.squawk in SpecialSquawks) {
+                                classes = classes + " " + SpecialSquawks[tableplane.squawk].cssClass;
+                                show_squawk_warning = true;
+			}			                
+
+                        // ICAO doesn't change
+                        tableplane.tr.cells[1].textContent = (tableplane.flight !== null ? tableplane.flight : "");
+                        tableplane.tr.cells[2].textContent = (tableplane.squawk !== null ? tableplane.squawk : "");    	                
+                        tableplane.tr.cells[3].textContent = format_altitude_brief(tableplane.altitude, tableplane.vert_rate);
+                        tableplane.tr.cells[4].textContent = format_speed_brief(tableplane.speed);
+
+                        if (tableplane.position !== null)
+                                ++TrackedAircraftPositions;
+                        
+                        tableplane.tr.cells[5].textContent = format_distance_brief(tableplane.sitedist);			
+                        tableplane.tr.cells[6].textContent = format_track_brief(tableplane.track);
+                        tableplane.tr.cells[7].textContent = tableplane.messages;
+                        tableplane.tr.cells[8].textContent = tableplane.seen.toFixed(0);
+                
+                        tableplane.tr.className = classes;
+
+		}
+	}
+
+	if (show_squawk_warning) {
+                $("#SpecialSquawkWarning").css('display','block');
+        } else {
+                $("#SpecialSquawkWarning").css('display','none');
+        }
+
+        resortTable();
+}
+
+//
+// ---- table sorting ----
+//
+
+function compareAlpha(xa,ya) {
+        if (xa === ya)
+                return 0;
+        if (xa < ya)
+                return -1;
+        return 1;
+}
+
+function compareNumeric(xf,yf) {
+        if (Math.abs(xf - yf) < 1e-9)
+                return 0;
+
+        return xf - yf;
+}
+
+function sortByICAO()     { sortBy('icao',    compareAlpha,   function(x) { return x.icao; }); }
+function sortByFlight()   { sortBy('flight',  compareAlpha,   function(x) { return x.flight; }); }
+function sortBySquawk()   { sortBy('squawk',  compareAlpha,   function(x) { return x.squawk; }); }
+function sortByAltitude() { sortBy('altitude',compareNumeric, function(x) { return (x.altitude == "ground" ? -1e9 : x.altitude); }); }
+function sortBySpeed()    { sortBy('speed',   compareNumeric, function(x) { return x.speed; }); }
+function sortByDistance() { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); }
+function sortByTrack()    { sortBy('track',   compareNumeric, function(x) { return x.track; }); }
+function sortByMsgs()     { sortBy('msgs',    compareNumeric, function(x) { return x.messages; }); }
+function sortBySeen()     { sortBy('seen',    compareNumeric, function(x) { return x.seen; }); }
+
+var sortId = '';
+var sortCompare = null;
+var sortExtract = null;
+var sortAscending = true;
+
+function sortFunction(x,y) {
+        var xv = x._sort_value;
+        var yv = y._sort_value;
+
+        // always sort missing values at the end, regardless of
+        // ascending/descending sort
+        if (xv == null && yv == null) return x._sort_pos - y._sort_pos;
+        if (xv == null) return 1;
+        if (yv == null) return -1;
+
+        var c = sortAscending ? sortCompare(xv,yv) : sortCompare(yv,xv);
+        if (c !== 0) return c;
+
+        return x._sort_pos - y._sort_pos;
+}
+
+function resortTable() {
+        // number the existing rows so we can do a stable sort
+        // regardless of whether sort() is stable or not.
+        // Also extract the sort comparison value.
+        for (var i = 0; i < PlanesOrdered.length; ++i) {
+                PlanesOrdered[i]._sort_pos = i;
+                PlanesOrdered[i]._sort_value = sortExtract(PlanesOrdered[i]);
+        }
+
+        PlanesOrdered.sort(sortFunction);
+        
+        var tbody = document.getElementById('tableinfo').tBodies[0];
+        for (var i = 0; i < PlanesOrdered.length; ++i) {
+                tbody.appendChild(PlanesOrdered[i].tr);
+        }
+}
+
+function sortBy(id,sc,se) {
+        if (id === sortId) {
+                sortAscending = !sortAscending;
+                PlanesOrdered.reverse(); // this correctly flips the order of rows that compare equal
+        } else {
+                sortAscending = true;
+        }
+
+        sortId = id;
+        sortCompare = sc;
+        sortExtract = se;
+
+        resortTable();
+}
+
+function selectPlaneByHex(hex,autofollow) {
+        //console.log("select: " + hex);
+	// If SelectedPlane has something in it, clear out the selected
+	if (SelectedPlane != null) {
+		Planes[SelectedPlane].selected = false;
+		Planes[SelectedPlane].clearLines();
+		Planes[SelectedPlane].updateMarker();
+                $(Planes[SelectedPlane].tr).removeClass("selected");
+	}
+
+	// If we are clicking the same plane, we are deselected it.
+	if (SelectedPlane === hex) {
+                hex = null;
+        }
+
+        if (hex !== null) {
+		// Assign the new selected
+		SelectedPlane = hex;
+		Planes[SelectedPlane].selected = true;
+		Planes[SelectedPlane].updateLines();
+		Planes[SelectedPlane].updateMarker();
+                $(Planes[SelectedPlane].tr).addClass("selected");
+	} else { 
+		SelectedPlane = null;
+	}
+
+        if (SelectedPlane !== null && autofollow) {
+                FollowSelected = true;
+                if (GoogleMap.getZoom() < 8)
+                        GoogleMap.setZoom(8);
+        } else {
+                FollowSelected = false;
+        } 
+
+        refreshSelected();
+}
+
+function toggleFollowSelected() {
+        FollowSelected = !FollowSelected;
+        if (FollowSelected && GoogleMap.getZoom() < 8)
+                GoogleMap.setZoom(8);
+        refreshSelected();
+}
+
+function resetMap() {
+        // Reset localStorage values and map settings
+        localStorage['CenterLat'] = CenterLat = DefaultCenterLat;
+        localStorage['CenterLon'] = CenterLon = DefaultCenterLon;
+        localStorage['ZoomLvl']   = ZoomLvl = DefaultZoomLvl;
+
+        // Set and refresh
+	GoogleMap.setZoom(ZoomLvl);
+	GoogleMap.setCenter(new google.maps.LatLng(CenterLat, CenterLon));
+	
+	selectPlaneByHex(null,false);
+}
+
+function drawCircle(marker, distance) {
+    if (typeof distance === 'undefined') {
+        return false;
+        
+        if (!(!isNaN(parseFloat(distance)) && isFinite(distance)) || distance < 0) {
+            return false;
+        }
+    }
+    
+    distance *= 1000.0;
+    if (!Metric) {
+        distance *= 1.852;
+    }
+    
+    // Add circle overlay and bind to marker
+    var circle = new google.maps.Circle({
+      map: GoogleMap,
+      radius: distance, // In meters
+      fillOpacity: 0.0,
+      strokeWeight: 1,
+      strokeOpacity: 0.3
+    });
+    circle.bindTo('center', marker, 'position');
+}
diff --git a/public_html/spinny.gif b/public_html/spinny.gif
new file mode 100644
index 0000000..7da19c0
Binary files /dev/null and b/public_html/spinny.gif differ
diff --git a/public_html/style.css b/public_html/style.css
new file mode 100644
index 0000000..7234d12
--- /dev/null
+++ b/public_html/style.css
@@ -0,0 +1,41 @@
+html, body {
+    margin: 0; padding: 0; background-color: #ffffff; font-family: Tahoma, Sans-Serif;
+    font-size: 10pt; overflow: auto; height: 100%;
+}
+div#map_container     { float: left; width: 100%; height: 100%; }
+div#map_canvas        { height: 100%; margin-right: 420px; }
+
+div#sidebar_container { float: left; width: 410px; margin-left: -410px; height: 100%; overflow: auto; }
+
+div#SpecialSquawkWarning { position: absolute; bottom: 25px; right: 430px; border: 2px solid red;
+    background-color: #FFFFA3; opacity: 0.75; filter:alpha(opacity=75); padding: 5px;
+    text-align: center; }
+
+div#update_error { position: absolute; bottom: 25px; left: 25px; border: 2px solid red;
+    background-color: #FFFFA3; opacity: 0.75; filter:alpha(opacity=75); padding: 5px;
+    text-align: center; }
+
+div#loader { z-index: 99; position: absolute; left: 0; top: 0; bottom: 0; right: 0; background: #000; opacity: 0.8; filter: alpha(opacity=80); }
+#spinny { width: 128px; height: 128px; position: absolute; top: 50%; left: 50%; margin: -64px 0 0 -64px; }
+#loader_progress { width: 250px; height: 20px; position: absolute; top: 50%; left: 50%; margin: 128px 0 0 -125px; }
+
+#tableinfo, #sudo_buttons { font-size: x-small; font-family: monospace; }
+
+.vPosition  { font-weight: bold; background-color: #d5ffd5; }
+.squawk7500 { font-weight: bold; background-color: #ff5555; }
+.squawk7600 { font-weight: bold; background-color: #00ffff; }
+.squawk7700 { font-weight: bold; background-color: #ffff00; }
+.selected   { background-color: #dddddd; }
+.plane_table_row { cursor: pointer; }
+.hidden { display: none; }
+
+.infoblock_heading    { font-size: larger; }
+.infoblock_heading a  { text-decoration: none; color: blue; font-size: x-small;}
+.infoblock_body       { font-size: small; }
+
+#selected_icao { font-size: x-small; }
+
+.dim    { opacity: 0.3; filter:alpha(opacity=30); /* For IE8 and earlier */ }
+
+.pointer { cursor: pointer; }
+
diff --git a/stats.c b/stats.c
new file mode 100644
index 0000000..13e1b0c
--- /dev/null
+++ b/stats.c
@@ -0,0 +1,263 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// stats.c: statistics helpers.
+//
+// Copyright (c) 2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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 "dump1090.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;
+}
+
+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");
+    if (Modes.interactive)
+        interactiveShowData();
+
+    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.net_only) {
+        printf("Local receiver:\n");
+        printf("  %u sample blocks processed\n",                    st->blocks_processed);
+        printf("  %u sample blocks dropped\n",                      st->blocks_dropped);
+
+        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_count) {
+            printf("  %.1f dBFS noise floor\n",
+                   10 * log10(st->noise_power_sum / st->noise_power_count));
+        }
+
+        if (st->signal_power_count) {
+            printf("  %.1f dBFS mean signal power\n",
+                   10 * log10(st->signal_power_sum / st->signal_power_count));
+        }
+
+        if (st->peak_signal_power) {
+            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);
+    }
+
+    if (Modes.net) {
+        printf("Messages from network clients:\n");
+        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 unique aircraft tracks\n", st->unique_aircraft);
+    printf("%u aircraft tracks where only one message was seen\n", st->single_message_aircraft);
+
+    if (Modes.net && Modes.net_http_port)
+        printf("%d HTTP requests\n", st->http_requests);
+
+    {
+        uint64_t demod_cpu_millis = (uint64_t)st->demod_cpu.tv_sec*1000UL + st->demod_cpu.tv_nsec/1000000UL;
+        uint64_t reader_cpu_millis = (uint64_t)st->reader_cpu.tv_sec*1000UL + st->reader_cpu.tv_nsec/1000000UL;
+        uint64_t background_cpu_millis = (uint64_t)st->background_cpu.tv_sec*1000UL + st->background_cpu.tv_nsec/1000000UL;
+
+        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);
+    }
+
+
+    fflush(stdout);
+}
+
+void reset_stats(struct stats *st) {
+    static struct stats st_zero;
+    *st = st_zero;
+}
+
+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;
+
+    target->blocks_processed = st1->blocks_processed + st2->blocks_processed;
+    target->blocks_dropped = st1->blocks_dropped + st2->blocks_dropped;
+
+    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);
+    
+    // noise floor:
+    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_rejected_bad = st1->remote_rejected_bad + st2->remote_rejected_bad;
+    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;
+
+    // network:
+    target->http_requests = st1->http_requests + st2->http_requests;
+
+    // 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;
+
+    // aircraft
+    target->unique_aircraft = st1->unique_aircraft + st2->unique_aircraft;
+    target->single_message_aircraft = st1->single_message_aircraft + st2->single_message_aircraft;
+}
+
diff --git a/stats.h b/stats.h
new file mode 100644
index 0000000..7c9cd7d
--- /dev/null
+++ b/stats.h
@@ -0,0 +1,130 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// stats.c: statistics structures and prototypes.
+//
+// Copyright (c) 2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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 {
+    uint64_t start;
+    uint64_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];
+
+    // Mode A/C demodulator counts:
+    uint32_t demod_modeac;
+
+    uint32_t blocks_processed;
+    uint32_t blocks_dropped;
+
+    // timing:
+    struct timespec demod_cpu;
+    struct timespec reader_cpu;
+    struct timespec background_cpu;
+
+    // noise floor:
+    double noise_power_sum;
+    uint32_t noise_power_count;
+
+    // mean signal power:
+    double signal_power_sum;
+    uint32_t signal_power_count;
+
+    // peak signal power seen
+    double peak_signal_power;
+
+    // number of signals with power > -3dBFS
+    uint32_t strong_signal_count;
+
+    // remote messages:
+    uint32_t remote_received_modeac;
+    uint32_t remote_received_modes;
+    uint32_t remote_rejected_bad;
+    uint32_t remote_rejected_unknown_icao;
+    uint32_t remote_accepted[MODES_MAX_BITERRORS+1];
+
+    // total messages:
+    uint32_t messages_total;
+
+    // network:
+    uint32_t http_requests;
+
+    // CPR decoding:
+    unsigned int cpr_surface;
+    unsigned int cpr_airborne;
+    unsigned int cpr_global_ok;
+    unsigned int cpr_global_bad;
+    unsigned int cpr_global_skipped;
+    unsigned int cpr_global_range_checks;
+    unsigned int cpr_global_speed_checks;
+    unsigned int cpr_local_ok;
+    unsigned int cpr_local_skipped;
+    unsigned int cpr_local_range_checks;
+    unsigned int cpr_local_speed_checks;
+    unsigned int cpr_local_aircraft_relative;
+    unsigned int cpr_local_receiver_relative;
+    unsigned int cpr_filtered;
+
+    // aircraft:
+    // total "new" aircraft (i.e. not seen in the last 30 or 300s)
+    unsigned int unique_aircraft;
+    // we saw only a single message
+    unsigned int single_message_aircraft;
+};    
+
+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 add_timespecs(const struct timespec *x, const struct timespec *y, struct timespec *z);
+
+#endif
diff --git a/testfiles/modes1.bin b/testfiles/modes1.bin
new file mode 100644
index 0000000..62f7f97
--- /dev/null
+++ b/testfiles/modes1.bin
@@ -0,0 +1,13 @@
+�~}}�~�~����~�~�������~~~�~~~���~�~����~�~}�~�}��x�n{�qzw{t|syvu|��u|v�x�p�|�x�{��������������������~�|�{~�f�t�x�v�~}�����}���������~}��{�w�z�t�v�r}y{rzyvuy{qzy~f�p�}x�y�~������������|����|�~�t�s��y�g~s}xwr��{ygvr~x�o��y�q�|�~~�������������������}��~���}��}pxp��}~�����v���}}�������}������~~~���������}�l�s��~�|���}}�����~��xf~}~�hp�{e~~���~�~���~|����||��~�~��~~~~~�wh}|���~���~�}�~�c��y�l��� [...]
�X�q�m�h���ɦ�����ט�}�e�Qu��_� �8zK{(dWY4We*H��Ws �8�[�.Îvi�d��ʔ��ӧ�����͞�x�x�h�Ep��J�.�`n g7^VK:Pl5WM�*{Y�#��s]�W�|�}���vn������̞�y�x�s�"�I�Q�0{N�+h[M&��R^*Y��|}}~~~���~���������������������~�~����$�b�
}_�~{~}%Mfs
y\���{����~�m聜y�䐛̿������{�_�p�;�e�"�\~�l? ms�Lex{ �l�)�d��yR�o���}~�䐗������|��i�t�B�g�*�^�
�Z[$q]6;fi!Y hj��|
�f�F�l�g�y����ݑ�˾��y}�}�_�4�o�)�`�xzbQ'ju�~c`|
�]�+��x�|t끡�׎��Ę�ݨ���y��L�n�/� i��jucB.is
c]v
�]��|+�5�}�{������̟�t|��y�s�� [...]
+�h�.�i�K�{�}���Ȕ�֨�����o�N�n�;�f�#~l}a>u{xu'Vaz!�e���y�z�\�������ϔ�ѫ����q�x�P�n�}��wr�yR0%p{|y^	�h�*�f�B�w�|�tᄜ�ӎ�����ߨ���v��N�3�j�*�`�&|a[1kd>Een%dby$�o�x�9�u�V�p������َ����|���m�v�@�$�h�&�bp"wn|y9=gp&df|�
+�u�z7�W�}����~����Ȍ���ሞ|�X�t�3��k�|l~yP*ot��}}�~����}���������������~��~~���~�~�~~|~}�~��>�JmKd>��wy��=sKwJ�;��}z����~t����um�����������d�m�V�X�I�H~Kw3��gI"Ec��6sBU� �[�\�ZŇpt�����sr���������so��\�\�K�J�E�BkGkDUWVQHjAgS| �H��tK�]�s�r�������������ˑi|�o�f�e��R�G�/��e;^Ibc
+,?n��5�F�R�J�f�9�w�o�ȑ������������q�_r��L�L�U� s?��^?XL\f @Ov?�?�Q�?��u^�U���u��������ɝox���X�Xt��D�L�F�@kLg at e]Be��7{E�M�F�^�V�q�j����������������x�mk��R�W�H�E|G{=bN\E`b2=q��9�C�^��f�m�p�k�œ����ٯ���Ѓj��b�b�O�L�H�AoJk>i]ReHo/r�;�I�^�U�p�n����Ó�������Ζoy�|�J�Ov��B�H{H{:sZ6OU��||}����������������������~~}~�~~~}~~~�}|��~����������yy��z��x�x�j�ex��}�~|~��jWee��RgdvU�]�_�5�q�j�r�}�}�����zv��̣�~t��v�t�m�=�k�X�X�xWT6fo��Pjb{^*�f��v_�s�y�z�����������������u�V�g}��R� [...]
��o�0�r���^�r�}������Ԏ�Ӣ���~��R��u�,�p~rynN2ol3Sju%{qw{�.�n�M�s��||�����~~����~~~�����~�~~����}��~~~������~{�{��z��|~��Ι��~�|{��e�@��P�5��bNeJ��{~��~~���}�������}~���~����~�{x��k�Q{��}�~�}�����~��~��~���~����������~�~��Kz��W�9��|��~�������~��{|��ů��}}���~~�|}��[�=~��|���{���<yTv��y~��~~�~��~��~~��~�}����}~��}~�~�qbe5jn>C�~y��{`�2�l�?��{~�~��||����ѩy��|�d�j�g�G�b�2|b~-shR-��ms
I&j�{\�/�r�+�O�z�u�{w�����u��Փ�~�_�Y{��_�;�`�+tbe-jjG at cv4]`�(�n��7��tx�c͆��ׅ��緾��ܞs��s�h�v�&�4~��hv t/ngD3��hwa.sf�&��zq�=�`�~|�����ʗ�����Ж�~�jz��e�@�o��-saj.lhJ?js&\�z^�-�l�8�|}~��~~�����������~���~~�~��~��}}�~~��x~��n�J~��}���~�}����~��~~�����~�͂�~z��~{��r�U��������|{��Y]VS��z|�~�~~�~~�������������~~�����~��~�����t|���}y��F�]y��z~�M<vh��+_vuy�)�y}�}�����~~����������~��~������a5zh��{�~~�~� [...]
+iq}(�e��|@�H�z���t���ҍ�{~��w�{�M�q�6�k�)�g}�T(yk5H@ry(|e}4�f�H�j��zs�{��Є�|w�~~~~��������~�~��~~~��{������~��������|u���~}��|��z�|��v�)��wmg*��flCM��~�~�~��~��~�~�~~����}~��u�7�}}�~����~�~��~~��~��~�~~~����~�}��]{��f�9��~����~������~���~��~�~~�����ņ�{{�������{x��k�B}��}����z~��IhLf��z|�}}~�������~�~��������~�~~~~�~���~��3�i� �}~|�~ms7Jkz!o{{}|~~��}|�y���~���������ړ���l�t [...]
+S]y� �g�%�d��uX�����x~�嗗׼���}~��`�r�7�a�
�W�~UT mY.9^gW kd��x�b�E�j�f�����旡t|鹟���e�p|��/� �h�}R��P
mmNVu�{� �k��x:�N�|��셪�ٓ�׺��w��}�|�P�j�,�[��WmwVD*g\%J[hoW~�}x}~}~�}~~~~~}�~�~���������������~���~}���Ex/{Q�&��{x��qv�yЎ���|t�����~s��z�bj��T�E�N�)�]f d:\TI=Nl2ZQ��~S��M΂ko�؉�������شqt���o�Vp��T�6�`� t!��cES;Rc8ONy+oN�+�W�;�i�X�{����vn������֚���g�`�c�5q��E�)z^Y J.��NX6WM}(vO�,�[�?�m�`Є��֘��Ϧ�ö��ܓ�~�[�Mt��O�0�b� n1kTF'��^b M �zB�4�^�F�r�iځ����Ʀ�����ў�{� [...]
+e6��xV�D�j�W�z�u�������wy���ȉz��|�I�\�`�7���Q};kabAblETiwp?�\�=�k�J��wv�����������ȭs���{�w�?�J��S�@x\s:hcZE_m at Zjzy5��|b�0�[�y�oą���y{����Ɩx��z�v�j�W�`�B�i�o4��oa(0LYWx=q_�9�g�B��ss�mǁ���������q}��dž�|�J�]�`�F�a�-��xbD
JK�VsAk\�9�e�F�m�\�x�{���ux�����������y�hz��m��E�^z/��ua.)OW]s<ib�.��x]�M�u�^�x�����sw������œ�w�p�v�.�N�\�;~ct1���zz�~}|~~~~~~��~~�~����������~�~��<�\fLmQ��zw�}9uXyL�O��{x�|~�xx����xp���������{�f�u�R�e�E�U}F}G��`G.'\h�~7uTyJ�
�f�X�]��pw� [...]
?dq>|QyN�I��wY�W�|�|y���됝����n~ˀ��j�5�l�D�XG~D��_E++[h�8wVwF�Q�Y�Z�k�`�y���������������~q��W�i�J�Y}E�QgHrQ^\
3Vn�|5�T�R��h�c�j�}n�ˉ����ږ������r�{�X�k�N�L~�x3�V_PlPNbZ`Rvy\~H�P�_�Z��rx���zz���Ƭ�x����e�l{��R��Q��p;yX\U[S��{y~~~}~}�~}~��~�������~�����������~~����~�;��yoa>��}~�����������~��~���}�Ā�z��~~~��}mm<�|~}~}�{��{Z�O��|{��~������~�����~���~~��|��~����}�|��~�����~Cqfy��{��|�^�{�|~�~|����u|��Ďr��v�jx���~��������� [...]
�\��nk�{�������os���������i�h�U�R�H�EzGw:n[/HW��:gFtH�;�]�
�d�l�i�����ui�����������t�ch��J�I�\� r=��^>\NKeB^Zx }>��uC�T�m�e����ƌ�����luŚ���{�\�Wq��E�F�Y� sOcK`FWd7U��It �C��zQ�/�p�x���zg�����������{�{�g�On��T� {<��h<dIT_NTJq;o[� �T�V�O�p�h���������po�����|Á�g�d�T�I�^� s<��`AXKbj :MsI~-��yE�Q�m�O�{����¢����������}g��_�]�Q�H�K�9y[F `TQ\IWPw.w��M��T��sc�j���{s���꼡sx���}�fn��T�T�R�6y��z~������������~�~�~�~~�~~~~��~�������bd��_t]}��}������~~��~~��}|��v [...]
+��n�?�y�{�{䂒���၀��ۤ���u�{�E�-�~�n�!�fk&rdC1qoF
`}~n� �o�+�?��}���~����֓�ԫ��ဉ�~�:�$�z�v�swmH'~xty"Xnz��l�-�w�x�a�|���񀁋�и��ߊ���]�B�}�q�'�g~$wbY+nh8Cfq%ec}%�c�0�i�K�r�s�}���ꄁ���󖁁�|�U�v�&��|�qr|o@ &*x}zwm�k�*�g�H�s�l�~��ڇ��χ������{�`�q�@�j�"�t�{_Isp.Cxxv{|m��$�s�M�y�|��ㄌ}}����}����������~�}���}�������������~�~��A�n~�
�n~��{�~�������~���������uz����vx��~��}��|�&�o}�{}~{��^w(u��{~��~~�~�~����~�}��������������� [...]
|ps>l[��GKesIl
se��z@�l�_�r�v�~�zw���������}�}�l�C�g{��7�`}F`lk��ASetGp
+~k�D�b�\�m��rtč퐛|x��ܼ�����{p��^�l�R��l�>vW��V at jnPaYa��}7�g�U�j�i�g�}x�Ï����������o�w�V�g�I�`�C�\hErbQTciLkh`��~@�+�s��uo�}����閑yy�������u�px��U�#�j�>�_kGs^TRggGiatE�V��}D�5�t�l�y���������wt��}�p�kr��F�d�L�
ylfDg]��CX_u��{~��������������~�����~~�~���~�}}|}~���~~����M^��~|��|^�Z��qk�y�n����}w���~~����}z����������~~~�~����~~�����~w���~x����~���������~~���������~������������\��rx����x���~�~��~~����������wyCb|�~�������xu�t��x~������x���|v��~�~~~~��~��}~���~���}�~��~�~����������Z��z~���{x����st����o}�y�vv��}����?~p���}~����������}����������~~�~��~x���{x���~��������~���~�~ [...]
gl)?bomm�y��z�x�T�z����눌��⾗����t�w�H�u�u��e� ] xyzp-3aq_]{�e�
�
�p�U�z��鉡�ܓ��‹����y�V�l�2�a��ctQ vv~x. Qv�r|�h��+�}�x�z�ᑝ�ȕ������|�}�C�q�� |s�{b @pi(7swr|w^�
�h�%�8���z�������~��������~�|�z�9�l�� ~r���|�~��~}~�~��~���~�}�������~������~�~�d�C�l��x��}��䑟�ğ�vz���~�}���&�`�
�Wm
vWD+g_'K_nn �g��{�#�s�h�y��接zy�М�����|�a�ly��"�_�
�Wj
tV?-hb%G Ne|�Y�)�]��vI�b���{{�ꓛ���Ζ�눫xs��H��f����\l> km��I\s~U�"�Y�:�g�`�s��慨������߲��r|�t�E�j��� �^��Wf
4 hm��H `c��Y�0�`��tS�p���yz�䗗wr��}�{�}~���~�~���}��~�|��~����� [...]
�c��od�{�����������ns�����lq��N�Z�R� |I��d.kTMRU[AjLo;�H�D�O�X�a�p�n���wo�ȟ����������s�`�Sn��4�K�Kn bR��UG1[t<s8��=��i�a�pĄj�ŵ���uu�������v�t�V�`�L�>{��)tLlP&^eBYOh=�GF�B��qP�i�y�~��r��~�����~~�����}�~~���~�~~�~�����}~�~�~��O�E��pU�i��t}��~��~����~~~����~��}{��saQ>��|~����{��xP�^��v|����~���|�Ȫ��x}��~��������������������~����~~~�������\�{�~�݈���ɺ�}��ڂx��x�Rz���~{���Np>��}y����~����~~����~�����~��~x��~�hy~��~��~}~ [...]
�v�}{G�b�}��܍�y{��뿒�ی�z{��W�,�n����zj\-nd>Gil(ed��} �i�6�8�w�l�|��ԉ��ƕ�y�ܝ���t�vy�����~���~����������~��~�~~~���������~[Ccd��/`b�}(�p�}Eɀ�z�{؃�}~��x|��ɘzy��~�~������~�������}��{w�hт����~�~�~����~�������~~���������tՁ���ʁ|~���~����~��~~����~���~-�h��}z����z��Ѓ~}�~��~��}u��k�Xu��~���}��~�~���~�����~�����~~~}��~~��;Mhw%l~�|��{l�F�s�dځ�~~�~���}�����x~z��9�4�d�,av(s`S3mm/ [...]
+AW��2r9yS� �F��qZ�_���zĝ��ű�����›�x�|�l�*�Fv��3�AoEr5g[ UYGl)f��C� �H��v]�N�u�����ui���������u�w�l�#�X�C�?|H|'��dJ
AW��Fr �8��tG�L�l�R�w�����tj������˜�r�t�j�$�By��H� |FgJP4��O] NHuD�*��yP�#�d�y�rǑ���wm���̵��|Ƅ�a�e�O�L�K�+��j6g<c`N`Es't��H� �L��mi�pń�����������Ԛf~�l�h�f��>|��3�6oY5 JJ��Lb ]4��|K��O��si�r���uo���������}ͅ�s�9�_�I�E�F�7oMb,��XT 18l�}6�=�R�A�f�Y�{�~���tp����vu��}�}�~~~~~}~�~���~�~~}~}�}~��~|~~z|��~�~���E}7yT�-��zy�rw�rȏ���~u���~u��x�ii��R�M�L�1^k l [...]
+?<��Wo,ii� �-��wd�R�y�nҋ��ҙ��ġ�ȫ��؆�w�P�Hy��Y�3|[y'vk=J at an'U��a} �+��|j�8�a�}���|r���¡�ǫ��؇�x�L�S�^�:�_�{�xeB
?8��er ]
�|[�5�o�7�c�t���ї��Ý�ǫ��ن�z�N�H{��h� �.y^b"��nh26]^y~��d��EɅvy�vՂ����ŗ�зzy���y�l�j�P�d�({�}Vt+ul7
ED`n$\��c�.��~o�E�l�u���͖�����̠�~��o�S�p��2�_z w�vg)=K]r0cb����h�#�OӁt|��ׇ�������ɨ��ԉ�p�c�f�E�e�#}�wXk0gfL=as0Xh} ~"��i�&�U�|�tӈ��ڂ}���}�}}�}�~}��}~������~��~�������~�������DkHjM�5��v��vf�e����ˀq���~�r}��̀i��a�c�Q�J�\� �MnL_5��W[?<q��J� [...]
gfD�S��vN�h�q�q��������������s|�}�Y�u�U�Yw��B�o[��UHlcDc[eNy
+�Y��|R�:�p��s|Ń�����������𢚅�r�z�[�k�M�_�D�YmFxX]S]Y��EdlfE�R��vQ�i�t�s�����������pyȎ���n�v�b�0�a��7�[tOOqlQYVe��8y[J�Y�[�8�q��vx��xv��ݿ��t~���e�u�[�]v��=�^rG{X]Pm[J`]fNtxf�H�Q��wX�W�y�����������������p~�p�K�fy��J�}[��c<n^]]<_r��{}�~��~�������~������~�}�}��~~~}��~���������GSjp?q_y��z��}J�l�k�p�~~��|x��{~����t~Ȃ���g�u�Q�'�l�;�^��c:?(ip��7euj��z;�h�Y�N�y�ā�~t���ϗ�x~Α���t�Z�p{��>�h�=�`k at y_TLpcDabp<}`|C��m��x^�u�����������������u��_�t�O��n�9�\��[:qiNX
Jg}�2�d� [...]
kL��g](:Ik��U{�K��{`�?�o�|�~�q���خ�rz�����z�]�m�b�Mw��N�E}�{~~~�}~�~~��~~������~~���}}�~~�~��������w|�}x��[�d�V�Mz�z��|}��U^Nd��Nw�S��vS�`�q�j������������������y�~�f�k�V�]�O�RwP}LdViTUdZ_NwRsP�O�X�S�i�H�s����}p���՜�����n�s�r�i�0�V|��C�TkRmQhe'0`lOsBv�~E�R�f�4�l�x�t�����zq��ھ��u}���k�gx��]� �P��yDzSaXfWUfXbQ{PrP�O�Z�V�i�f�|�v�����������������l�n�i�%�a�P�B��kFpV[aY\_r	e\~O�K�\�X�n�k��q�������ੜr������m�~�g�g�V�W�O�NpQuN`\cXSlUdNMwS�Q�_�Y�o�i���|��������� [...]
+�]�Y�S��||��~���~����~�~������}~~�~�~~~~���������~����}�w}���}w~z���|y�{�|}���{~���~��~�d�|��||�����y}����|~�~�����|��M�Q~�{~~���~��~�}�~���}��~�~~��~~����~��}��e�|��ˁ��������Ɉ�}��~|��t�2}v�yd%R2{}zy,R'k}�r�7�o�M�q�d�{��dž���������Ɨ���x�y�]�o�H�j�:�fy9xhZ>ur9G)Wz�o|3�l�C�o�W�w�q���ƅ������~��͊�}�h�S�~�v�9�n�'u"�}ztPEmt>ahx8}j�<�k�K�q�c�z��ɂ��ѷ˄���נۈ�}��V�H�p�?�j�3�xyuZ<ppCVjv9qg~7�n�6�=��|�pڈ܀�������ϩښ�~���c�Q�q�D�i�:�iq3~w [...]
\ No newline at end of file
diff --git a/tools/debug.html b/tools/debug.html
new file mode 100644
index 0000000..4d56d34
--- /dev/null
+++ b/tools/debug.html
@@ -0,0 +1,193 @@
+<!DOCTYPE html>
+<html>
+<body>
+<head>
+<script>
+var frames = [];
+var currentFrame = 0;
+
+var modes_checksum_table = [
+0x3935ea, 0x1c9af5, 0xf1b77e, 0x78dbbf, 0xc397db, 0x9e31e9, 0xb0e2f0, 0x587178,
+0x2c38bc, 0x161c5e, 0x0b0e2f, 0xfa7d13, 0x82c48d, 0xbe9842, 0x5f4c21, 0xd05c14,
+0x682e0a, 0x341705, 0xe5f186, 0x72f8c3, 0xc68665, 0x9cb936, 0x4e5c9b, 0xd8d449,
+0x939020, 0x49c810, 0x24e408, 0x127204, 0x093902, 0x049c81, 0xfdb444, 0x7eda22,
+0x3f6d11, 0xe04c8c, 0x702646, 0x381323, 0xe3f395, 0x8e03ce, 0x4701e7, 0xdc7af7,
+0x91c77f, 0xb719bb, 0xa476d9, 0xadc168, 0x56e0b4, 0x2b705a, 0x15b82d, 0xf52612,
+0x7a9309, 0xc2b380, 0x6159c0, 0x30ace0, 0x185670, 0x0c2b38, 0x06159c, 0x030ace,
+0x018567, 0xff38b7, 0x80665f, 0xbfc92b, 0xa01e91, 0xaff54c, 0x57faa6, 0x2bfd53,
+0xea04ad, 0x8af852, 0x457c29, 0xdd4410, 0x6ea208, 0x375104, 0x1ba882, 0x0dd441,
+0xf91024, 0x7c8812, 0x3e4409, 0xe0d800, 0x706c00, 0x383600, 0x1c1b00, 0x0e0d80,
+0x0706c0, 0x038360, 0x01c1b0, 0x00e0d8, 0x00706c, 0x003836, 0x001c1b, 0xfff409,
+0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
+0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
+0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000
+];
+
+function modesChecksum(frame) {
+    var crc = 0;
+    var bits = frame.bits;
+    var offset = (bits == 112) ? 0 : (112-56);
+
+    for(var j = 0; j < bits; j++) {
+        var byte = j/8;
+        var bit = j%8;
+        var bitmask = 1 << (7-bit);
+
+        /* If bit is set, xor with corresponding table entry. */
+        if (frame.hex.charCodeAt(byte) & bitmask)
+            crc ^= modes_checksum_table[j+offset];
+    }
+    return crc; /* 24 bit checksum. */
+}
+
+function getFrameChecksum(frame) {
+    var res = "";
+    for (j = 0; j < frame.hex.length; j++) {
+        var val = frame.hex.charCodeAt(j);
+        var h = val.toString(16);
+        if (h.length == 1) h = "0"+h;
+        res += h;
+    }
+    return res;
+}
+
+function displayFrame(i) {
+    var div = document.getElementById("frame");
+    var msgbits = 8+112;
+    var frame = frames[i];
+    var padding = frame.mag.length - msgbits*2;
+
+    /* Remove the old representation. */
+    var nodes = div.childNodes.length;
+    for(var j = 0; j < nodes; j++) {
+        div.removeChild(div.firstChild);
+    }
+
+    /* Display the new one. */
+    for (var j = -padding; j < msgbits*2+padding; j++) {
+        var m = frame.mag[j+padding];
+        var type;
+
+        if (j < 0) type = "noise";
+        if (j >= 0 && j < 16) type = "pre";
+        if (j >= 16) {
+            if (!(j % 2)) {
+                var next = frame.mag[j+padding+1];
+                if (m > next)
+                    type = "one";
+                else
+                    type = "zero";
+            }
+            var bit = (j-16)/2;
+            if (bit == frame.fix1 ||
+                bit == frame.fix2)
+                type = "err";
+        }
+        var sample = document.createElement("div");
+        sample.setAttribute("class","sample "+type);
+        sample.setAttribute("title","sample "+j+" ("+m+")");
+        sample.style.left = ""+((j+padding)*4)+"px";
+        sample.style.height = ""+(m/256)+"px";
+        div.appendChild(sample);
+    }
+    document.getElementById("info").innerHTML =
+        "#"+currentFrame+" "+frame.descr+"<br>"+
+        "Bits:"+frame.bits+"<br>"+
+        "DF  : "+(frame.hex.charCodeAt(0) >> 3)+"<br>"+
+        "fix1: "+frame.fix1+"<br>"+
+        "fix2: "+frame.fix2+"<br>"+
+        "hex : "+getFrameChecksum(frame)+"<br>"+
+        "crc (computed): "+modesChecksum(frame).toString(16)+"<br>";
+}
+
+function recomputeHex(frame) {
+    var padding = frame.mag.length - (112+8)*2;
+    var b = [];
+    var hex = "";
+
+    /* Get bits */
+    for (var j = 0; j < frame.bits*2; j += 2) {
+        var bit;
+        var l = frame.mag[padding+j+16];
+        var r = frame.mag[padding+j+1+16];
+        if (l > r)
+            bit = 1;
+        else
+            bit = 0;
+        b.push(bit);
+    }
+    /* Pack into bytes */
+    for (j = 0; j < frame.bits; j+= 8) {
+        hex += String.fromCharCode(
+            b[j]<<7 |
+            b[j+1]<<6 |
+            b[j+2]<<5 |
+            b[j+3]<<4 |
+            b[j+4]<<3 |
+            b[j+5]<<2 |
+            b[j+6]<<1 |
+            b[j+7]);
+    }
+    frame.hex = hex;
+}
+
+window.onload = function() {
+    document.getElementById("next").onclick = function() {
+        if (currentFrame != frames.length-1) currentFrame++;
+        displayFrame(currentFrame);
+    }
+    document.getElementById("prev").onclick = function() {
+        if (currentFrame != 0) currentFrame--;
+        displayFrame(currentFrame);
+    }
+    document.getElementById("re").onclick = function() {
+        recomputeHex(frames[currentFrame]);
+        displayFrame(currentFrame);
+    }
+    displayFrame(currentFrame);
+}
+</script>
+<script src="frames.js"></script>
+<style>
+#frame {
+    width: 1024px;
+    height: 255px;
+    border: 1px #aaa solid;
+    position: relative;
+}
+.sample {
+    position: absolute;
+    bottom: 0px;
+}
+.pre {
+    width:4px;
+    background-color: orange;
+}
+.one {
+    width:4px;
+    background-color: #0000cc;
+}
+.zero {
+    width:4px;
+    background-color: #aaaaaa;
+}
+.err {
+    width:4px;
+    background-color: #cc6666;
+}
+.noise {
+    width:2px;
+    background-color: #ffffff;
+    border: 1px #aaa dotted;
+}
+</style>
+</head>
+<div id="frame">
+</div>
+<pre id="info">
+</pre>
+<input type="button" id="prev" value="Prev frame">
+<input type="button" id="next" value="Next frame">
+<input type="button" id="re" value="Recompute Hex">
+</body>
+</html>
diff --git a/track.c b/track.c
new file mode 100644
index 0000000..8a8b849
--- /dev/null
+++ b/track.c
@@ -0,0 +1,659 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// track.c: aircraft state tracking
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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 "dump1090.h"
+
+/* #define DEBUG_CPR_CHECKS */
+
+//
+// Return a new aircraft structure for the linked list of tracked
+// aircraft
+//
+struct aircraft *trackCreateAircraft(struct modesMessage *mm) {
+    static struct aircraft zeroAircraft;
+    struct aircraft *a = (struct aircraft *) malloc(sizeof(*a));
+    int i;
+
+    // Default everything to zero/NULL
+    *a = zeroAircraft;
+
+    // Now initialise things that should not be 0/NULL to their defaults
+    a->addr = mm->addr;
+    for (i = 0; i < 8; ++i)
+        a->signalLevel[i] = mm->signalLevel;  // First time, initialise everything
+                                              // to the first signal strength
+
+    // mm->msgtype 32 is used to represent Mode A/C. These values can never change, so 
+    // set them once here during initialisation, and don't bother to set them every 
+    // time this ModeA/C is received again in the future
+    if (mm->msgtype == 32) {
+        int modeC      = ModeAToModeC(mm->modeA | mm->fs);
+        a->modeACflags = MODEAC_MSG_FLAG;
+        if (modeC < -12) {
+            a->modeACflags |= MODEAC_MSG_MODEA_ONLY;
+        } else {
+            mm->altitude = modeC * 100;
+            mm->bFlags  |= MODES_ACFLAGS_ALTITUDE_VALID;
+        }
+    }
+
+    // Copy the first message so we can emit it later when a second message arrives.
+    a->first_message = *mm;
+
+    Modes.stats_current.unique_aircraft++;
+
+    return (a);
+}
+
+//
+//=========================================================================
+//
+// Return the aircraft with the specified address, or NULL if no aircraft
+// exists with this address.
+//
+struct aircraft *trackFindAircraft(uint32_t addr) {
+    struct aircraft *a = Modes.aircrafts;
+
+    while(a) {
+        if (a->addr == addr) return (a);
+        a = a->next;
+    }
+    return (NULL);
+}
+
+//
+// CPR position updating
+//
+
+// 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)
+static double greatcircle(double lat0, double lon0, double lat1, double lon1)
+{
+    lat0 = lat0 * M_PI / 180.0;
+    lon0 = lon0 * M_PI / 180.0;
+    lat1 = lat1 * M_PI / 180.0;
+    lon1 = lon1 * M_PI / 180.0;
+
+    // avoid NaN
+    if (fabs(lat0 - lat1) < 0.0001 && fabs(lon0 - lon1) < 0.0001)
+        return 0.0;
+
+    return 6371e3 * acos(sin(lat0) * sin(lat1) + cos(lat0) * cos(lat1) * cos(fabs(lon0 - lon1)));
+}
+
+// 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, struct modesMessage *mm, double lat, double lon, uint64_t now, int surface)
+{
+    uint64_t elapsed;
+    double distance;
+    double range;
+    int speed;
+    int inrange;
+
+    if (!(a->bFlags & MODES_ACFLAGS_LATLON_VALID))
+        return 1; // no reference, assume OK
+
+    elapsed = now - a->seenLatLon;
+
+    if ((mm->bFlags & MODES_ACFLAGS_SPEED_VALID) && (a->bFlags & MODES_ACFLAGS_SPEED_VALID))
+        speed = (mm->velocity + a->speed) / 2;
+    else if (mm->bFlags & MODES_ACFLAGS_SPEED_VALID)
+        speed = mm->velocity;
+    else if (a->bFlags & MODES_ACFLAGS_SPEED_VALID)
+        speed = a->speed;
+    else
+        speed = surface ? 100 : 600; // guess
+
+    // Work out a reasonable speed to use:
+    //  current speed + 1/3
+    //  surface speed min 20kt, max 150kt
+    //  airborne speed min 200kt, no max
+    speed = speed * 4 / 3;
+    if (surface) {
+        if (speed < 20)
+            speed = 20;
+        if (speed > 150)
+            speed = 150;
+    } else {
+        if (speed < 200)
+            speed = 200;
+    }
+
+    // 100m (surface) or 500m (airborne) base distance to allow for minor errors,
+    // plus distance covered at the given speed for the elapsed time + 1 second.
+    range = (surface ? 0.1e3 : 0.5e3) + ((elapsed + 1000.0) / 1000.0) * (speed * 1852.0 / 3600.0);
+
+    // find actual distance
+    distance = greatcircle(a->lat, a->lon, lat, lon);
+
+    inrange = (distance <= range);
+#ifdef DEBUG_CPR_CHECKS
+    if (!inrange) {
+        fprintf(stderr, "Speed check failed: %06x: %.3f,%.3f -> %.3f,%.3f in %.1f seconds, max speed %d kt, range %.1fkm, actual %.1fkm\n",
+                a->addr, a->lat, a->lon, lat, lon, elapsed/1000.0, speed, range/1000.0, distance/1000.0);
+    }
+#endif
+
+    return inrange;
+}
+
+static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now, double *lat, double *lon, unsigned *nuc)
+{
+    int result;
+    int fflag = (mm->bFlags & MODES_ACFLAGS_LLODD_VALID) != 0;
+    int surface = (mm->bFlags & MODES_ACFLAGS_AOG) != 0;
+
+    *nuc = (a->even_cprnuc < a->odd_cprnuc ? a->even_cprnuc : a->odd_cprnuc); // worst of the two positions
+
+    if (surface) {
+        // surface global CPR
+        // find reference location
+        double reflat, reflon;
+
+        if (a->bFlags & MODES_ACFLAGS_LATLON_REL_OK) { // Ok to try aircraft relative first
+            reflat = a->lat;
+            reflon = a->lon;
+            if (a->pos_nuc < *nuc)
+                *nuc = a->pos_nuc;
+        } else if (Modes.bUserFlags & MODES_USER_LATLON_VALID) {
+            reflat = Modes.fUserLat;
+            reflon = Modes.fUserLon;
+        } else {
+            // No local reference, give up
+            return (-1);
+        }
+
+        result = decodeCPRsurface(reflat, reflon,
+                                  a->even_cprlat, a->even_cprlon,
+                                  a->odd_cprlat, a->odd_cprlon,
+                                  fflag,
+                                  lat, lon);
+    } else {
+        // airborne global CPR
+        result = decodeCPRairborne(a->even_cprlat, a->even_cprlon,
+                                   a->odd_cprlat, a->odd_cprlon,
+                                   fflag,
+                                   lat, lon);
+    }
+
+    if (result < 0)
+        return result;
+
+    // check max range
+    if (Modes.maxRange > 0 && (Modes.bUserFlags & MODES_USER_LATLON_VALID)) {
+        double range = greatcircle(Modes.fUserLat, Modes.fUserLon, *lat, *lon);
+        if (range > Modes.maxRange) {
+#ifdef DEBUG_CPR_CHECKS
+            fprintf(stderr, "Global range check failed: %06x: %.3f,%.3f, max range %.1fkm, actual %.1fkm\n",
+                    a->addr, *lat, *lon, Modes.maxRange/1000.0, range/1000.0);
+#endif
+
+            Modes.stats_current.cpr_global_range_checks++;
+            return (-2); // we consider an out-of-range value to be bad data
+        }
+    }
+
+    // check speed limit
+    if ((a->bFlags & MODES_ACFLAGS_LATLON_VALID) && a->pos_nuc >= *nuc && !speed_check(a, mm, *lat, *lon, now, surface)) {
+        Modes.stats_current.cpr_global_speed_checks++;
+        return -2;
+    }
+
+    return result;
+}
+
+static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now, double *lat, double *lon, unsigned *nuc)
+{
+    // relative CPR
+    // find reference location
+    double reflat, reflon;
+    double range_limit = 0;
+    int result;
+    int fflag = (mm->bFlags & MODES_ACFLAGS_LLODD_VALID) != 0;
+    int surface = (mm->bFlags & MODES_ACFLAGS_AOG) != 0;
+
+    *nuc = mm->nuc_p;
+
+    if (a->bFlags & MODES_ACFLAGS_LATLON_REL_OK) {
+        reflat = a->lat;
+        reflon = a->lon;
+
+        if (a->pos_nuc < *nuc)
+            *nuc = a->pos_nuc;
+    } else if (!surface && (Modes.bUserFlags & MODES_USER_LATLON_VALID)) {
+        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 <= 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
+        }
+    } else {
+        // No local reference, give up
+        return (-1);
+    }
+
+    result = decodeCPRrelative(reflat, reflon,
+                               mm->raw_latitude,
+                               mm->raw_longitude,
+                               fflag, surface,
+                               lat, lon);
+    if (result < 0)
+        return result;
+
+    // check range limit
+    if (range_limit > 0) {
+        double range = greatcircle(reflat, reflon, *lat, *lon);
+        if (range > range_limit) {
+            Modes.stats_current.cpr_local_range_checks++;
+            return (-1);
+        }
+    }
+
+    // check speed limit
+    if ((a->bFlags & MODES_ACFLAGS_LATLON_VALID) && a->pos_nuc >= *nuc && !speed_check(a, mm, *lat, *lon, now, surface)) {
+        Modes.stats_current.cpr_local_speed_checks++;
+        return -1;
+    }
+
+    return 0;
+}
+
+static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t now)
+{
+    int location_result = -1;
+    int max_elapsed;
+    double new_lat = 0, new_lon = 0;
+    unsigned new_nuc = 0;
+
+    if (mm->bFlags & MODES_ACFLAGS_AOG)
+        ++Modes.stats_current.cpr_surface;
+    else
+        ++Modes.stats_current.cpr_airborne;
+
+    if (mm->bFlags & MODES_ACFLAGS_AOG) {
+        // Surface: 25 seconds if >25kt or speed unknown, 50 seconds otherwise
+
+        if ((mm->bFlags & MODES_ACFLAGS_SPEED_VALID) && mm->velocity <= 25)
+            max_elapsed = 50000;
+        else
+            max_elapsed = 25000;
+    } else {
+        // Airborne: 10 seconds
+        max_elapsed = 10000;
+    }
+
+    if (mm->bFlags & MODES_ACFLAGS_LLODD_VALID) {
+        a->odd_cprnuc  = mm->nuc_p;
+        a->odd_cprlat  = mm->raw_latitude;
+        a->odd_cprlon  = mm->raw_longitude;
+        a->odd_cprtime = now;
+    } else {
+        a->even_cprnuc  = mm->nuc_p;
+        a->even_cprlat  = mm->raw_latitude;
+        a->even_cprlon  = mm->raw_longitude;
+        a->even_cprtime = now;
+    }
+
+    // If we have enough recent data, try global CPR
+    if (((mm->bFlags | a->bFlags) & MODES_ACFLAGS_LLEITHER_VALID) == MODES_ACFLAGS_LLBOTH_VALID && abs((int)(a->even_cprtime - a->odd_cprtime)) <= max_elapsed) {
+        location_result = doGlobalCPR(a, mm, now, &new_lat, &new_lon, &new_nuc);
+
+        if (location_result == -2) {
+            // Global CPR failed because the position produced implausible results.
+            // This is bad data. Discard both odd and even messages and wait for a fresh pair.
+            // Also disable aircraft-relative positions until we have a new good position (but don't discard the
+            // recorded position itself)
+            Modes.stats_current.cpr_global_bad++;
+            a->bFlags &= ~(MODES_ACFLAGS_LATLON_REL_OK | MODES_ACFLAGS_LLODD_VALID | MODES_ACFLAGS_LLEVEN_VALID);
+
+            // Also discard the current message's data as it is suspect - we don't want
+            // to update any of the aircraft state from this.
+            mm->bFlags &= ~(MODES_ACFLAGS_LATLON_VALID | MODES_ACFLAGS_LLODD_VALID | MODES_ACFLAGS_LLEVEN_VALID |
+                            MODES_ACFLAGS_ALTITUDE_VALID |
+                            MODES_ACFLAGS_SPEED_VALID |
+                            MODES_ACFLAGS_HEADING_VALID |
+                            MODES_ACFLAGS_NSEWSPD_VALID |
+                            MODES_ACFLAGS_VERTRATE_VALID |
+                            MODES_ACFLAGS_AOG_VALID |
+                            MODES_ACFLAGS_AOG);
+            return;
+        } else if (location_result == -1) {
+            // No local reference for surface position available, or the two messages crossed a zone.
+            // Nonfatal, try again later.
+            Modes.stats_current.cpr_global_skipped++;
+        } else {
+            Modes.stats_current.cpr_global_ok++;
+        }
+    }
+
+    // Otherwise try relative CPR.
+    if (location_result == -1) {
+        location_result = doLocalCPR(a, mm, now, &new_lat, &new_lon, &new_nuc);
+
+        if (location_result == -1) {
+            Modes.stats_current.cpr_local_skipped++;
+        } else {
+            Modes.stats_current.cpr_local_ok++;
+            if (a->bFlags & MODES_ACFLAGS_LATLON_REL_OK)
+                Modes.stats_current.cpr_local_aircraft_relative++;
+            else
+                Modes.stats_current.cpr_local_receiver_relative++;
+            mm->bFlags |= MODES_ACFLAGS_REL_CPR_USED;
+        }
+    }
+
+    if (location_result == 0) {
+        // If we sucessfully decoded, back copy the results to mm so that we can print them in list output
+        mm->bFlags |= MODES_ACFLAGS_LATLON_VALID;
+        mm->fLat    = new_lat;
+        mm->fLon    = new_lon;
+
+        // Update aircraft state
+        a->bFlags |= (MODES_ACFLAGS_LATLON_VALID | MODES_ACFLAGS_LATLON_REL_OK);
+        a->lat = new_lat;
+        a->lon = new_lon;
+        a->pos_nuc = new_nuc;
+        a->seenLatLon      = a->seen;
+
+    }
+}
+
+//
+//=========================================================================
+//
+// Receive new messages and update tracked aircraft state
+//
+
+struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
+{
+    struct aircraft *a;
+    uint64_t now = mstime();
+
+    // Lookup our aircraft or create a new one
+    a = trackFindAircraft(mm->addr);
+    if (!a) {                              // If it's a currently unknown aircraft....
+        a = trackCreateAircraft(mm);       // ., create a new record for it,
+        a->next = Modes.aircrafts;         // .. and put it at the head of the list
+        Modes.aircrafts = a;
+    }
+
+    a->signalLevel[a->messages & 7] = mm->signalLevel;// replace the 8th oldest signal strength
+    a->seen      = now;
+    a->messages++;
+
+    // if the Aircraft has landed or taken off since the last message, clear the even/odd CPR flags
+    if ((mm->bFlags & MODES_ACFLAGS_AOG_VALID) && ((a->bFlags ^ mm->bFlags) & MODES_ACFLAGS_AOG)) {
+        a->bFlags &= ~(MODES_ACFLAGS_LLBOTH_VALID | MODES_ACFLAGS_AOG);
+    }
+
+    // If we've got a new cprlat or cprlon
+    if (mm->bFlags & MODES_ACFLAGS_LLEITHER_VALID) {
+        updatePosition(a, mm, now);
+    }
+
+    // If a (new) CALLSIGN has been received, copy it to the aircraft structure
+    if (mm->bFlags & MODES_ACFLAGS_CALLSIGN_VALID) {
+        memcpy(a->flight, mm->flight, sizeof(a->flight));
+    }
+
+    // If a (new) ALTITUDE has been received, copy it to the aircraft structure
+    if (mm->bFlags & MODES_ACFLAGS_ALTITUDE_VALID) {
+        if ( (a->modeCcount)                   // if we've a modeCcount already
+          && (a->altitude  != mm->altitude ) ) // and Altitude has changed
+//        && (a->modeC     != mm->modeC + 1)   // and Altitude not changed by +100 feet
+//        && (a->modeC + 1 != mm->modeC    ) ) // and Altitude not changes by -100 feet
+            {
+            a->modeCcount   = 0;               //....zero the hit count
+            a->modeACflags &= ~MODEAC_MSG_MODEC_HIT;
+            }
+        a->altitude = mm->altitude;
+        a->modeC    = (mm->altitude + 49) / 100;
+    }
+
+    // If a (new) SQUAWK has been received, copy it to the aircraft structure
+    if (mm->bFlags & MODES_ACFLAGS_SQUAWK_VALID) {
+        if (a->modeA != mm->modeA) {
+            a->modeAcount   = 0; // Squawk has changed, so zero the hit count
+            a->modeACflags &= ~MODEAC_MSG_MODEA_HIT;
+        }
+        a->modeA = mm->modeA;
+    }
+
+    // If a (new) HEADING has been received, copy it to the aircraft structure
+    if (mm->bFlags & MODES_ACFLAGS_HEADING_VALID) {
+        a->track = mm->heading;
+    }
+
+    // If a (new) SPEED has been received, copy it to the aircraft structure
+    if (mm->bFlags & MODES_ACFLAGS_SPEED_VALID) {
+        a->speed = mm->velocity;
+    }
+
+    // If a (new) Vertical Descent rate has been received, copy it to the aircraft structure
+    if (mm->bFlags & MODES_ACFLAGS_VERTRATE_VALID) {
+        a->vert_rate = mm->vert_rate;
+    }
+
+    // Update the aircrafts a->bFlags to reflect the newly received mm->bFlags;
+    a->bFlags |= mm->bFlags;
+
+    if (mm->msgtype == 32) {
+        int flags = a->modeACflags;
+        if ((flags & (MODEAC_MSG_MODEC_HIT | MODEAC_MSG_MODEC_OLD)) == MODEAC_MSG_MODEC_OLD) {
+            //
+            // This Mode-C doesn't currently hit any known Mode-S, but it used to because MODEAC_MSG_MODEC_OLD is
+            // set  So the aircraft it used to match has either changed altitude, or gone out of our receiver range
+            //
+            // We've now received this Mode-A/C again, so it must be a new aircraft. It could be another aircraft
+            // at the same Mode-C altitude, or it could be a new airctraft with a new Mods-A squawk.
+            //
+            // To avoid masking this aircraft from the interactive display, clear the MODEAC_MSG_MODES_OLD flag
+            // and set messages to 1;
+            //
+            a->modeACflags = flags & ~MODEAC_MSG_MODEC_OLD;
+            a->messages    = 1;
+        }
+    }
+
+    return (a);
+}
+
+//
+// Periodic updates of tracking state
+//
+
+//
+//=========================================================================
+//
+// Periodically search through the list of known Mode-S aircraft and tag them if this
+// Mode A/C  matches their known Mode S Squawks or Altitudes(+/- 50feet).
+//
+// A Mode S equipped aircraft may also respond to Mode A and Mode C SSR interrogations.
+// We can't tell if this is a Mode A or C, so scan through the entire aircraft list
+// looking for matches on Mode A (squawk) and Mode C (altitude). Flag in the Mode S
+// records that we have had a potential Mode A or Mode C response from this aircraft. 
+//
+// If an aircraft responds to Mode A then it's highly likely to be responding to mode C 
+// too, and vice verca. Therefore, once the mode S record is tagged with both a Mode A
+// and a Mode C flag, we can be fairly confident that this Mode A/C frame relates to that
+// Mode S aircraft.
+//
+// Mode C's are more likely to clash than Mode A's; There could be several aircraft 
+// cruising at FL370, but it's less likely (though not impossible) that there are two 
+// aircraft on the same squawk. Therefore, give precidence to Mode A record matches
+//
+// Note : It's theoretically possible for an aircraft to have the same value for Mode A 
+// and Mode C. Therefore we have to check BOTH A AND C for EVERY S.
+//
+static void trackUpdateAircraftModeA(struct aircraft *a)
+{
+    struct aircraft *b = Modes.aircrafts;
+
+    while(b) {
+        if ((b->modeACflags & MODEAC_MSG_FLAG) == 0) {  // skip any fudged ICAO records 
+
+            // If both (a) and (b) have valid squawks...
+            if ((a->bFlags & b->bFlags) & MODES_ACFLAGS_SQUAWK_VALID) {
+                // ...check for Mode-A == Mode-S Squawk matches
+                if (a->modeA == b->modeA) { // If a 'real' Mode-S ICAO exists using this Mode-A Squawk
+                    b->modeAcount   = a->messages;
+                    b->modeACflags |= MODEAC_MSG_MODEA_HIT;
+                    a->modeACflags |= MODEAC_MSG_MODEA_HIT;
+                    if ( (b->modeAcount > 0) &&
+                       ( (b->modeCcount > 1)
+                      || (a->modeACflags & MODEAC_MSG_MODEA_ONLY)) ) // Allow Mode-A only matches if this Mode-A is invalid Mode-C
+                        {a->modeACflags |= MODEAC_MSG_MODES_HIT;}    // flag this ModeA/C probably belongs to a known Mode S                    
+                }
+            }
+
+            // If both (a) and (b) have valid altitudes...
+            if ((a->bFlags & b->bFlags) & MODES_ACFLAGS_ALTITUDE_VALID) {
+                // ... check for Mode-C == Mode-S Altitude matches
+                if (  (a->modeC     == b->modeC    )     // If a 'real' Mode-S ICAO exists at this Mode-C Altitude
+                   || (a->modeC     == b->modeC + 1)     //          or this Mode-C - 100 ft
+                   || (a->modeC + 1 == b->modeC    ) ) { //          or this Mode-C + 100 ft
+                    b->modeCcount   = a->messages;
+                    b->modeACflags |= MODEAC_MSG_MODEC_HIT;
+                    a->modeACflags |= MODEAC_MSG_MODEC_HIT;
+                    if ( (b->modeAcount > 0) &&
+                         (b->modeCcount > 1) )
+                        {a->modeACflags |= (MODEAC_MSG_MODES_HIT | MODEAC_MSG_MODEC_OLD);} // flag this ModeA/C probably belongs to a known Mode S                    
+                }
+            }
+        }
+        b = b->next;
+    }
+}
+//
+//=========================================================================
+//
+static void trackUpdateAircraftModeS()
+{
+    struct aircraft *a = Modes.aircrafts;
+
+    while(a) {
+        int flags = a->modeACflags;
+        if (flags & MODEAC_MSG_FLAG) { // find any fudged ICAO records
+
+            // clear the current A,C and S hit bits ready for this attempt
+            a->modeACflags = flags & ~(MODEAC_MSG_MODEA_HIT | MODEAC_MSG_MODEC_HIT | MODEAC_MSG_MODES_HIT);
+
+            trackUpdateAircraftModeA(a);  // and attempt to match them with Mode-S
+        }
+        a = a->next;
+    }
+}
+
+//
+//=========================================================================
+//
+// If we don't receive new nessages within TRACK_AIRCRAFT_TTL
+// we remove the aircraft from the list.
+//
+static void trackRemoveStaleAircraft(uint64_t now)
+{
+    struct aircraft *a = Modes.aircrafts;
+    struct aircraft *prev = NULL;
+    
+    while(a) {
+        if ((now - a->seen) > TRACK_AIRCRAFT_TTL ||
+            (a->messages == 1 && (now - a->seen) > TRACK_AIRCRAFT_ONEHIT_TTL)) {
+            // 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++;
+
+            // Remove the element from the linked list, with care
+            // if we are removing the first element
+            if (!prev) {
+                Modes.aircrafts = a->next; free(a); a = Modes.aircrafts;
+            } else {
+                prev->next = a->next; free(a); a = prev->next;
+            }
+        } else if ((a->bFlags & MODES_ACFLAGS_LATLON_VALID) && (now - a->seenLatLon) > TRACK_AIRCRAFT_POSITION_TTL) {
+            /* Position is too old and no longer valid */
+            a->bFlags &= ~(MODES_ACFLAGS_LATLON_VALID | MODES_ACFLAGS_LATLON_REL_OK);
+        } else {
+            prev = a; a = a->next;
+        }
+    }
+}
+
+
+//
+// Entry point for periodic updates
+//
+
+void trackPeriodicUpdate()
+{
+    static uint64_t next_update;
+    uint64_t now = mstime();
+
+    // Only do updates once per second
+    if (now >= next_update) {
+        next_update = now + 1000;
+        trackRemoveStaleAircraft(now);
+        trackUpdateAircraftModeS();
+    }
+}
diff --git a/track.h b/track.h
new file mode 100644
index 0000000..3cdef55
--- /dev/null
+++ b/track.h
@@ -0,0 +1,115 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// track.h: aircraft state tracking prototypes
+//
+// Copyright (c) 2014,2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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
+
+/* Maximum age of tracked aircraft in milliseconds */
+#define TRACK_AIRCRAFT_TTL 300000
+
+/* Maximum age of a tracked aircraft with only 1 message received, in milliseconds */
+#define TRACK_AIRCRAFT_ONEHIT_TTL 60000
+
+/* Maximum validity of an aircraft position */
+#define TRACK_AIRCRAFT_POSITION_TTL 60000
+
+/* Structure used to describe the state of one tracked aircraft */
+struct aircraft {
+    uint32_t      addr;           // ICAO address
+    char          flight[16];     // Flight number
+    double        signalLevel[8]; // Last 8 Signal Amplitudes
+    int           altitude;       // Altitude
+    int           speed;          // Velocity
+    int           track;          // Angle of flight
+    int           vert_rate;      // Vertical rate.
+    uint64_t      seen;           // Time (millis) at which the last packet was received
+    uint64_t      seenLatLon;     // Time (millis) at which the last lat long was calculated
+    long          messages;       // Number of Mode S messages received
+    int           modeA;          // Squawk
+    int           modeC;          // Altitude
+    long          modeAcount;     // Mode A Squawk hit Count
+    long          modeCcount;     // Mode C Altitude hit Count
+    int           modeACflags;    // Flags for mode A/C recognition
+
+    int           fatsv_emitted_altitude;  // last FA emitted altitude
+    int           fatsv_emitted_track;     // last FA emitted angle of flight
+    uint64_t      fatsv_last_emitted;      // time (millis) aircraft was last FA emitted
+
+    // Encoded latitude and longitude as extracted by odd and even CPR encoded messages
+    uint64_t      odd_cprtime;
+    int           odd_cprlat;
+    int           odd_cprlon;
+    unsigned      odd_cprnuc;
+
+    uint64_t      even_cprtime;
+    int           even_cprlat;
+    int           even_cprlon;
+    unsigned      even_cprnuc;
+
+    double        lat, lon;       // Coordinated obtained from CPR encoded data
+    unsigned      pos_nuc;        // NUCp of last computed position
+
+    int           bFlags;         // Flags related to valid fields in this structure
+    struct aircraft *next;        // Next aircraft in our linked list
+
+    struct modesMessage first_message;  // A copy of the first message we received for this aircraft.
+};
+
+
+
+/* Update aircraft state from data in the provided mesage.
+ * Return the tracked aircraft.
+ */
+struct modesMessage;
+struct aircraft *trackUpdateFromMessage(struct modesMessage *mm);
+
+/* Call periodically */
+void trackPeriodicUpdate();
+
+#endif
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..b66f7e1
--- /dev/null
+++ b/util.c
@@ -0,0 +1,81 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// util.c: misc utilities
+//
+// Copyright (c) 2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+// This file incorporates work covered by the following copyright and  
+// permission notice:
+//
+//   Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+//
+//   All rights reserved.
+//
+//   Redistribution and use in source and binary forms, with or without
+//   modification, are permitted provided that the following conditions are
+//   met:
+//
+//    *  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 "util.h"
+
+#include <stdlib.h>
+#include <sys/time.h>
+
+uint64_t mstime(void)
+{
+    struct timeval tv;
+    uint64_t mst;
+
+    gettimeofday(&tv, NULL);
+    mst = ((uint64_t)tv.tv_sec)*1000;
+    mst += tv.tv_usec/1000;
+    return mst;
+}
+
+int64_t receiveclock_ns_elapsed(uint64_t t1, uint64_t t2)
+{
+    return (t2 - t1) * 1000U / 12U;
+}
+
+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;
+    }
+}
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..41ccbb1
--- /dev/null
+++ b/util.h
@@ -0,0 +1,39 @@
+// Part of dump1090, a Mode S message decoder for RTLSDR devices.
+//
+// track.h: aircraft state tracking prototypes
+//
+// Copyright (c) 2015 Oliver Jowett <oliver at mutability.co.uk>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#ifndef DUMP1090_UTIL_H
+#define DUMP1090_UTIL_H
+
+#include <stdint.h>
+
+/* Returns system time in milliseconds */
+uint64_t mstime(void);
+
+/* Returns the time elapsed, in nanoseconds, from t1 to t2,
+ * where t1 and t2 are 12MHz counters.
+ */
+int64_t receiveclock_ns_elapsed(uint64_t t1, uint64_t t2);
+
+/* Normalize the value in ts so that ts->nsec lies in
+ * [0,999999999]
+ */
+struct timespec;
+void normalize_timespec(struct timespec *ts);
+
+#endif
diff --git a/view1090.c b/view1090.c
new file mode 100644
index 0000000..119ad49
--- /dev/null
+++ b/view1090.c
@@ -0,0 +1,275 @@
+// view1090, a Mode S messages viewer for dump1090 devices.
+//
+// Copyright (C) 2014 by Malcolm Robb <Support at ATTAvionics.com>
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//  *  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 "view1090.h"
+//
+// ============================= Utility functions ==========================
+//
+void sigintHandler(int dummy) {
+    NOTUSED(dummy);
+    signal(SIGINT, SIG_DFL);  // reset signal handler - bit extra safety
+    Modes.exit = 1;           // Signal to threads that we are done
+}
+//
+// =============================== Terminal handling ========================
+//
+#ifndef _WIN32
+// Get the number of rows after the terminal changes size.
+int getTermRows() { 
+    struct winsize w; 
+    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); 
+    return (w.ws_row); 
+} 
+
+// Handle resizing terminal
+void sigWinchCallback() {
+    signal(SIGWINCH, SIG_IGN);
+    Modes.interactive_rows = getTermRows();
+    interactiveShowData();
+    signal(SIGWINCH, sigWinchCallback); 
+}
+#else 
+int getTermRows() { return MODES_INTERACTIVE_ROWS;}
+#endif
+//
+// =============================== Initialization ===========================
+//
+void view1090InitConfig(void) {
+    // Default everything to zero/NULL
+    memset(&Modes,    0, sizeof(Modes));
+    memset(&View1090, 0, sizeof(View1090));
+
+    // Now initialise things that should not be 0/NULL to their defaults
+    Modes.check_crc               = 1;
+    strcpy(View1090.net_input_beast_ipaddr,VIEW1090_NET_OUTPUT_IP_ADDRESS); 
+    Modes.net_input_beast_port    = MODES_NET_OUTPUT_BEAST_PORT;
+    Modes.interactive_rows        = getTermRows();
+    Modes.interactive_display_ttl = MODES_INTERACTIVE_DISPLAY_TTL;
+
+    Modes.interactive             = 1;
+}
+//
+//=========================================================================
+//
+void view1090Init(void) {
+
+    pthread_mutex_init(&Modes.data_mutex,NULL);
+    pthread_cond_init(&Modes.data_cond,NULL);
+
+#ifdef _WIN32
+    if ( (!Modes.wsaData.wVersion) 
+      && (!Modes.wsaData.wHighVersion) ) {
+      // Try to start the windows socket support
+      if (WSAStartup(MAKEWORD(2,1),&Modes.wsaData) != 0) 
+        {
+        fprintf(stderr, "WSAStartup returned Error\n");
+        }
+      }
+#endif
+
+    // 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) ) {
+        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 
+    // is at 0.0 Lon,so we must check for either fLat or fLon being non zero not both. 
+    // Testing the flag at runtime will be much quicker than ((fLon != 0.0) || (fLat != 0.0))
+    Modes.bUserFlags &= ~MODES_USER_LATLON_VALID;
+    if ((Modes.fUserLat != 0.0) || (Modes.fUserLon != 0.0)) {
+        Modes.bUserFlags |= MODES_USER_LATLON_VALID;
+    }
+
+    // Prepare error correction tables
+    modesChecksumInit(Modes.nfix_crc);
+    icaoFilterInit();
+}
+
+// Set up data connection
+int setupConnection(struct client *c) {
+    int fd;
+
+    // Try to connect to the selected ip address and port. We only support *ONE* input connection which we initiate.here.
+    if ((fd = anetTcpConnect(Modes.aneterr, View1090.net_input_beast_ipaddr, Modes.net_input_beast_port)) != ANET_ERR) {
+		anetNonBlock(Modes.aneterr, fd);
+		//
+		// Setup a service callback client structure for a beast binary input (from dump1090)
+		// This is a bit dodgy under Windows. The fd parameter is a handle to the internet
+		// socket on which we are receiving data. Under Linux, these seem to start at 0 and 
+		// count upwards. However, Windows uses "HANDLES" and these don't nececeriy start at 0.
+		// dump1090 limits fd to values less than 1024, and then uses the fd parameter to 
+		// index into an array of clients. This is ok-ish if handles are allocated up from 0.
+		// However, there is no gaurantee that Windows will behave like this, and if Windows 
+		// allocates a handle greater than 1024, then dump1090 won't like it. On my test machine, 
+		// the first Windows handle is usually in the 0x54 (84 decimal) region.
+
+		c->next    = NULL;
+		c->buflen  = 0;
+		c->fd      = 
+		c->service =
+		Modes.bis  = fd;
+		Modes.clients = c;
+    }
+    return fd;
+}
+//
+// ================================ Main ====================================
+//
+void showHelp(void) {
+    printf(
+"-----------------------------------------------------------------------------\n"
+"| view1090 ModeS Viewer       %45s |\n"
+"-----------------------------------------------------------------------------\n"
+  "--interactive            Interactive mode refreshing data on screen\n"
+  "--interactive-rows <num> Max number of rows in interactive mode (default: 15)\n"
+  "--interactive-ttl <sec>  Remove from list if idle for <sec> (default: 60)\n"
+  "--interactive-rtl1090    Display flight table in RTL1090 format\n"
+  "--modeac                 Enable decoding of SSR modes 3/A & 3/C\n"
+  "--net-bo-ipaddr <IPv4>   TCP Beast output listen IPv4 (default: 127.0.0.1)\n"
+  "--net-bo-port <port>     TCP Beast output listen port (default: 30005)\n"
+  "--lat <latitude>         Reference/receiver latitide for surface posn (opt)\n"
+  "--lon <longitude>        Reference/receiver longitude for surface posn (opt)\n"
+  "--no-crc-check           Disable messages with broken CRC (discouraged)\n"
+  "--no-fix                 Disable single-bits error correction using CRC\n"
+  "--fix                    Enable single-bits error correction using CRC\n"
+  "--aggressive             More CPU for more messages (two bits fixes, ...)\n"
+  "--metric                 Use metric units (meters, km/h, ...)\n"
+  "--help                   Show this help\n",
+  MODES_DUMP1090_VARIANT " " MODES_DUMP1090_VERSION
+    );
+}
+
+//
+//=========================================================================
+//
+int main(int argc, char **argv) {
+    int j, fd;
+    struct client *c;
+    char pk_buf[8];
+
+    // Set sane defaults
+
+    view1090InitConfig();
+    signal(SIGINT, sigintHandler); // Define Ctrl/C handler (exit program)
+
+    // Parse the command line options
+    for (j = 1; j < argc; j++) {
+        int more = ((j + 1) < argc); // There are more arguments
+
+        if        (!strcmp(argv[j],"--net-bo-port") && more) {
+            Modes.net_input_beast_port = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--net-bo-ipaddr") && more) {
+            strcpy(View1090.net_input_beast_ipaddr, argv[++j]);
+        } else if (!strcmp(argv[j],"--modeac")) {
+            Modes.mode_ac = 1;
+        } else if (!strcmp(argv[j],"--interactive-rows") && more) {
+            Modes.interactive_rows = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--interactive")) {
+            Modes.interactive = 1;
+        } else if (!strcmp(argv[j],"--interactive-ttl") && more) {
+            Modes.interactive_display_ttl = (uint64_t)(1000 * atof(argv[++j]));
+        } else if (!strcmp(argv[j],"--interactive-rtl1090")) {
+            Modes.interactive = 1;
+            Modes.interactive_rtl1090 = 1;
+        } else if (!strcmp(argv[j],"--lat") && more) {
+            Modes.fUserLat = atof(argv[++j]);
+        } else if (!strcmp(argv[j],"--lon") && more) {
+            Modes.fUserLon = atof(argv[++j]);
+        } else if (!strcmp(argv[j],"--metric")) {
+            Modes.metric = 1;
+        } else if (!strcmp(argv[j],"--no-crc-check")) {
+            Modes.check_crc = 0;
+        } else if (!strcmp(argv[j],"--fix")) {
+            Modes.nfix_crc = 1;
+        } else if (!strcmp(argv[j],"--no-fix")) {
+            Modes.nfix_crc = 0;
+        } else if (!strcmp(argv[j],"--aggressive")) {
+            Modes.nfix_crc = MODES_MAX_BITERRORS;
+        } else if (!strcmp(argv[j],"--help")) {
+            showHelp();
+            exit(0);
+        } else {
+            fprintf(stderr, "Unknown or not enough arguments for option '%s'.\n\n", argv[j]);
+            showHelp();
+            exit(1);
+        }
+    }
+
+#ifdef _WIN32
+    // Try to comply with the Copyright license conditions for binary distribution
+    if (!Modes.quiet) {showCopyright();}
+#define MSG_DONTWAIT 0
+#endif
+
+#ifndef _WIN32
+    // Setup for SIGWINCH for handling lines
+    if (Modes.interactive) {signal(SIGWINCH, sigWinchCallback);}
+#endif
+
+    // Initialization
+    view1090Init();
+
+    // Try to connect to the selected ip address and port. We only support *ONE* input connection which we initiate.here.
+    c = (struct client *) malloc(sizeof(*c));
+    if ((fd = setupConnection(c)) == ANET_ERR) {
+        fprintf(stderr, "Failed to connect to %s:%d\n", View1090.net_input_beast_ipaddr, Modes.net_input_beast_port);
+        exit(1);
+    }
+
+    // Keep going till the user does something that stops us
+    while (!Modes.exit) {
+        icaoFilterExpire();
+        trackPeriodicUpdate();
+        interactiveShowData();
+        if ((fd == ANET_ERR) || (recv(c->fd, pk_buf, sizeof(pk_buf), MSG_PEEK | MSG_DONTWAIT) == 0)) {
+			free(c);
+			usleep(1000000);
+			c = (struct client *) malloc(sizeof(*c));
+			fd = setupConnection(c);
+			continue;
+        }
+        modesReadFromClient(c,"",decodeBinMessage);
+		usleep(100000);
+    }
+
+    // The user has stopped us, so close any socket we opened
+    if (fd != ANET_ERR) 
+      {close(fd);}
+
+    return (0);
+}
+//
+//=========================================================================
+//
diff --git a/view1090.h b/view1090.h
new file mode 100644
index 0000000..03ad96d
--- /dev/null
+++ b/view1090.h
@@ -0,0 +1,84 @@
+// view1090, a Mode S messages viewer for dump1090 devices.
+//
+// Copyright (C) 2013 by Malcolm Robb <Support at ATTAvionics.com>
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//  *  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 __VIEW1090_H
+#define __VIEW1090_H
+
+// ============================= Include files ==========================
+
+#include "dump1090.h"
+
+#ifndef _WIN32
+    #include <stdio.h>
+    #include <string.h>
+    #include <stdlib.h>
+    #include <pthread.h>
+    #include <stdint.h>
+    #include <errno.h>
+    #include <unistd.h>
+    #include <math.h>
+    #include <sys/time.h>
+    #include <sys/timeb.h>
+    #include <signal.h>
+    #include <fcntl.h>
+    #include <ctype.h>
+    #include <sys/stat.h>
+    #include <sys/types.h>
+    #include <sys/socket.h>
+    #include "rtl-sdr.h"
+    #include "anet.h"
+#else
+    #include "winstubs.h" //Put everything Windows specific in here
+#endif
+
+// ============================= #defines ===============================
+
+#define VIEW1090_NET_OUTPUT_IP_ADDRESS "127.0.0.1"
+
+#define NOTUSED(V) ((void) V)
+
+// ======================== structure declarations ========================
+
+// Program global state
+struct {                           // Internal state
+    // Networking
+    char    net_input_beast_ipaddr[32]; // IPv4 address or network name of server/RPi
+} View1090;
+
+// ======================== function declarations =========================
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __VIEW1090_H

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/dump1090-mutability.git



More information about the pkg-hamradio-commits mailing list