[hamradio-commits] [dump1090] 01/373: Initial commit of Dump1090, a simple Mode S decoder.

Matthew Ernisse mernisse-guest at moszumanska.debian.org
Thu Oct 23 14:57:59 UTC 2014


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

mernisse-guest pushed a commit to branch backport
in repository dump1090.

commit 7ca5a4b3a40293343be92f4b9840259935340597
Author: antirez <antirez at gmail.com>
Date:   Sat Jan 5 13:52:25 2013 +0100

    Initial commit of Dump1090, a simple Mode S decoder.
---
 .gitignore           |    3 +
 Makefile             |   18 +
 README               |  207 ++++++++
 TODO                 |   10 +
 mode1090.c           | 1340 ++++++++++++++++++++++++++++++++++++++++++++++++++
 testfiles/modes1.bin |   13 +
 6 files changed, 1591 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f0464da
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.o
+mode1090
+testfiles/test*.bin
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b6ece6f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+LIBUSB_INC_PATH=/usr/local/Cellar/libusb/1.0.9/include/libusb-1.0
+LIBUSB_LIB_PATH=/usr/local/Cellar/libusb/1.0.9/lib
+LIBRTLSDR_INC_PATH=/usr/local/Cellar/rtlsdr/HEAD/include
+LIBRTLSDR_LIB_PATH=/usr/local/Cellar/rtlsdr/HEAD/lib
+LIBS=-lusb-1.0 -lrtlsdr -lpthread -lm
+CC=gcc
+PROGNAME=mode1090
+
+all: mode1090
+
+mode1090.o: mode1090.c
+	$(CC) -O2 -g -Wall -W -I$(LIBUSB_INC_PATH) -I$(LIBRTLSDR_INC_PATH) mode1090.c -c -o mode1090.o
+
+mode1090: mode1090.o
+	$(CC) -g -L$(LIBUSB_LIB_PATH) -L$(LIBRTLSDR_LIB_PATH) -o mode1090 mode1090.o $(LIBS)
+
+clean:
+	rm -f *.o mode1090
diff --git a/README b/README
new file mode 100644
index 0000000..3c3fd62
--- /dev/null
+++ b/README
@@ -0,0 +1,207 @@
+Dump1090 README
+===
+
+Dump 1090 is a Mode S decoder specifically designed for RTLSDR devices.
+
+The main features are:
+
+* Robust decoding of weak messages.
+* 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 mode where aircrafts currently detected are shown
+  as a list refreshing as more data arrives.
+
+Installation
+---
+
+Edit the Makefile and set the following variables according to your system:
+
+LIBUSB_INC_PATH=/usr/local/Cellar/libusb/1.0.9/include/libusb-1.0
+LIBUSB_LIB_PATH=/usr/local/Cellar/libusb/1.0.9/lib
+LIBRTLSDR_INC_PATH=/usr/local/Cellar/rtlsdr/HEAD/include
+LIBRTLSDR_LIB_PATH=/usr/local/Cellar/rtlsdr/HEAD/lib
+
+Then save the modified Makefile and 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
+
+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 tries to fix single bit errors using the checksum.
+Basically the program will try to flip every bit of the message and check if
+the checksum of the resulting message matches.
+
+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).
+
+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.
+
+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.
+
+It is possible to display different categories of messages:
+
+    --debug 1       Displays all the messages correctly demoudulated.
+                    A correctly demodulated message is just one that
+                    makes sense as a Mode S message, the preamble makes
+                    sense, and there are no message errors, that is,
+                    no adiacet samples describing bits are the same
+                    magnitude.
+
+    --debug 2       Only messages with demodulation errors are displayed,
+                    That is, only messages where one or more adiacent
+                    samples that should describe bits are the same
+                    magnitude.
+
+    --debug 3       Correctly deooded messages with Bad CRC are displayed.
+
+    --debug 4       Correctly deooded messages with good CRC are displayed.
+
+    --debug 5       Preamble detection failed in some way (specified when
+                    dumping the samples) even if the current sample level
+                    is greater than MODES_DEBUG_NOPREAMBLE_LEVEL (set to
+                    25 by default).
+
+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 | ./mode1090 --snip 25 > small.bin
+
+I used it in order to create a small test file to include inside this
+program source code distribution.
+
+Contributing
+---
+
+Mode1090 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
+---
+
+Dump1090 was written by Salvatore Sanfilippo <antirez at gmail.com> and is
+released under the BSD three clause license.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..b86ceb4
--- /dev/null
+++ b/TODO
@@ -0,0 +1,10 @@
+TODO
+
+* Extract from information from captured Mode S messages. Currently we only
+  decode what is trival to decode.
+* Decode CPR encoded latitude and longitude, display it in normal and
+  interactive mode.
+* Show nationality in interactive mode and normal mode using the
+  aircraft ICAO address, like: 30xxxx -> Italy.
+* Actually use the fancy --debug feature in order to improve the recognition
+  algorithm if possibile.
diff --git a/mode1090.c b/mode1090.c
new file mode 100644
index 0000000..e261a5f
--- /dev/null
+++ b/mode1090.c
@@ -0,0 +1,1340 @@
+/* Mode1090, 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 <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 <fcntl.h>
+#include <ctype.h>
+#include "rtl-sdr.h"
+
+#define MODES_DEFAULT_RATE         2000000
+#define MODES_DEFAULT_FREQ         1090000000
+#define MODES_DEFAULT_WIDTH        1000
+#define MODES_DEFAULT_HEIGHT       700
+#define MODES_ASYNC_BUF_NUMBER     12
+#define MODES_DATA_LEN             (16*16384)   /* 256k */
+#define MODES_AUTO_GAIN            -100         /* Use automatic gain. */
+#define MODES_MAX_GAIN             999999       /* Use max available gain. */
+
+#define MODES_PREAMBLE_US 8       /* microseconds */
+#define MODES_LONG_MSG_BITS 112
+#define MODES_SHORT_MSG_BITS 56
+#define MODES_FULL_LEN (MODES_PREAMBLE_US+MODES_LONG_MSG_BITS)
+#define MODES_LONG_MSG_BYTES (112/8)
+#define MODES_SHORT_MSG_BYTES (56/8)
+
+#define MODES_ICAO_CACHE_LEN 32
+#define MODES_UNIT_FEET 0
+#define MODES_UNIT_METERS 1
+
+#define MODES_DEBUG_DEMOD 1
+#define MODES_DEBUG_DEMODERR 2
+#define MODES_DEBUG_BADCRC 3
+#define MODES_DEBUG_GOODCRC 4
+#define MODES_DEBUG_NOPREAMBLE 5
+
+/* 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_NOTUSED(V) ((void) V)
+
+/* Structure used to describe an aircraft in iteractive mode. */
+struct aircraft {
+    uint32_t addr;      /* ICAO address */
+    char hexaddr[7];    /* Printable ICAO address */
+    char flight[9];     /* Flight number */
+    int altitude;       /* Altitude */
+    int speed;          /* Velocity computed from EW and NS components. */
+    time_t seen;        /* Time at which the last packet was received. */
+    long messages;      /* Number of Mode S messages received. */
+    struct aircraft *next; /* Next aircraft in our linked list. */
+};
+
+/* 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. */
+    unsigned char *data;            /* Raw IQ samples buffer */
+    unsigned char *magnitude;       /* Magnitude vector */
+    uint32_t data_len;              /* Buffer length. */
+    int fd;                         /* --ifile option file descriptor. */
+    int data_ready;                 /* Data ready to be processed. */
+    uint32_t icao_cache[MODES_ICAO_CACHE_LEN];/* Recently seen ICAO addresses */
+    int icao_cache_idx;             /* icao_cache circular buf idx. */
+    unsigned char *maglut;          /* I/Q -> Magnitude lookup table. */
+
+    /* RTLSDR */
+    int dev_index;
+    int gain;
+    int enable_agc;
+    rtlsdr_dev_t *dev;
+    int freq;
+
+    /* Configuration */
+    char *filename;                 /* Input form file, --ifile option. */
+    int fix_errors;                 /* Single bit error correction if true. */
+    int check_crc;                  /* Only display messages with good CRC. */
+    int raw;                        /* Raw output format */
+    int debug;                      /* Debugging mode */
+    int interactive;                /* Interactive mode */
+
+    /* Interactive mode */
+    struct aircraft *aircrafts;
+    long long interactive_last_update;  /* Last screen update in milliseconds */
+} 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. */
+    int msgbits;                /* Number of bits in message */
+    int msgtype;                /* Downlink format # */
+    int crcok;                  /* True if CRC was valid */
+    uint32_t crc;               /* Message CRC */
+    int errorbit;               /* Bit corrected. -1 if no bit corrected. */
+    int aa1, aa2, aa3;          /* ICAO Address bytes 1 2 and 3 */
+
+    /* DF 11 */
+    int ca;                     /* Responder capabilities. */
+
+    /* DF 17 */
+    int metype;                 /* Extended squitter message type. */
+    int mesub;                  /* Extended squitter message subtype. */
+    int heading_is_valid;
+    int heading;
+    int aircraft_type;
+    int fflag;                  /* Odd or Even CPR message? */
+    int tflag;                  /* UTC synchronized? */
+    int raw_latitude;           /* Non decoded latitude */
+    int raw_longitude;          /* Non decoded longitude */
+    char flight[9];             /* 8 chars flight number. */
+    int ew_dir;                 /* E/W direction. */
+    int ew_velocity;            /* E/W velocity. */
+    int ns_dir;                 /* N/S direction. */
+    int ns_velocity;            /* N/S velocity. */
+    int vert_rate_source;       /* Vertical rate source. */
+    int vert_rate_sign;         /* Vertical rate sign. */
+    int vert_rate;              /* Vertical rate. */
+    int velocity;               /* Computed from EW and NS velocity. */
+
+    /* DF4, DF5, DF20, DF21 */
+    int fs;                     /* Flight status for DF4,5,20,21 */
+    int dr;                     /* Request extraction of downlink request. */
+    int um;                     /* Request extraction of downlink request. */
+    int identity;               /* 13 bits identity (Squawk). */
+
+    /* Fields used by multiple message types. */
+    int altitude, unit;
+};
+
+void interactiveShowData(void);
+void interactiveReceiveData(struct modesMessage *mm);
+
+/* ============================= Utility functions ========================== */
+
+static long long mstime(void) {
+    struct timeval tv;
+    long long mst;
+
+    gettimeofday(&tv, NULL);
+    mst = ((long long)tv.tv_sec)*1000;
+    mst += tv.tv_usec/1000;
+    return mst;
+}
+
+/* =============================== Initialization =========================== */
+
+void modesInitConfig(void) {
+    Modes.gain = MODES_MAX_GAIN;
+    Modes.dev_index = 0;
+    Modes.enable_agc = 0;
+    Modes.freq = MODES_DEFAULT_FREQ;
+    Modes.filename = NULL;
+    Modes.fix_errors = 1;
+    Modes.check_crc = 1;
+    Modes.raw = 0;
+    Modes.debug = 0;
+    Modes.interactive = 0;
+}
+
+void modesInit(void) {
+    int i, q;
+
+    pthread_mutex_init(&Modes.data_mutex,NULL);
+    pthread_cond_init(&Modes.data_cond,NULL);
+    Modes.data_len = MODES_DATA_LEN;
+    Modes.data_ready = 0;
+    memset(Modes.icao_cache,0,sizeof(Modes.icao_cache));
+    Modes.icao_cache_idx = 0;
+    Modes.aircrafts = NULL;
+    Modes.interactive_last_update = 0;
+    if ((Modes.data = malloc(Modes.data_len)) == NULL ||
+        (Modes.magnitude = malloc(Modes.data_len)) == NULL) {
+        fprintf(stderr, "Out of memory allocating data buffer.\n");
+        exit(1);
+    }
+
+    /* Populate the I/Q -> Magnitude lookup table. It is used because
+     * sqrt or round may be expensive and may vary a lot depending on
+     * the libc used.
+     *
+     * We scale to 0-255 range multiplying by 1.4 in order to ensure that
+     * every different I/Q pair will result in a different magnitude value,
+     * not losing any resolution. */
+    Modes.maglut = malloc(129*129);
+    for (i = 0; i <= 128; i++) {
+        for (q = 0; q <= 128; q++) {
+            Modes.maglut[i*129+q] = round(sqrt(i*i+q*q)*1.4);
+        }
+    }
+}
+
+/* =============================== RTLSDR handling ========================== */
+
+void modesInitRTLSDR(void) {
+    int j;
+    int device_count;
+    int ppm_error = 0;
+    char vendor[256], product[256], serial[256];
+
+    device_count = rtlsdr_get_device_count();
+    if (!device_count) {
+        fprintf(stderr, "No supported RTLSDR devices found.\n");
+        exit(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 == Modes.dev_index) ? "(currently selected)" : "");
+    }
+
+    if (rtlsdr_open(&Modes.dev, Modes.dev_index) < 0) {
+        fprintf(stderr, "Error opening the RTLSDR device: %s\n",
+            strerror(errno));
+        exit(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) {
+        if (Modes.gain == MODES_MAX_GAIN) {
+            /* Find the maximum gain available. */
+            int numgains;
+            int gains[100];
+
+            numgains = rtlsdr_get_tuner_gains(Modes.dev, gains);
+            Modes.gain = gains[numgains-1];
+            fprintf(stderr, "Max available gain is: %.2f\n", Modes.gain/10.0);
+        }
+        rtlsdr_set_tuner_gain(Modes.dev, Modes.gain);
+        fprintf(stderr, "Setting gain to: %.2f\n", Modes.gain/10.0);
+    } else {
+        fprintf(stderr, "Using automatic gain control.\n");
+    }
+    rtlsdr_set_freq_correction(Modes.dev, 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_DEFAULT_RATE);
+    rtlsdr_reset_buffer(Modes.dev);
+    fprintf(stderr, "Gain reported by device: %.2f\n",
+        rtlsdr_get_tuner_gain(Modes.dev)/10.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. */
+void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) {
+    MODES_NOTUSED(ctx);
+
+    pthread_mutex_lock(&Modes.data_mutex);
+    if (len > Modes.data_len) len = Modes.data_len;
+    memcpy(Modes.data, buf, len);
+    Modes.data_ready = 1;
+    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) {
+    while(1) {
+        ssize_t nread, toread;
+        unsigned char *p;
+
+        pthread_mutex_lock(&Modes.data_mutex);
+        toread = Modes.data_len;
+        p = Modes.data;
+        while(toread) {
+            nread = read(Modes.fd, p, toread);
+            if (nread <= 0) exit(0);
+            p += nread;
+            toread -= nread;
+        }
+        Modes.data_ready = 1;
+        pthread_cond_signal(&Modes.data_cond);
+        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);
+
+    if (Modes.filename == NULL) {
+        rtlsdr_read_async(Modes.dev, rtlsdrCallback, NULL,
+                              MODES_ASYNC_BUF_NUMBER,
+                              Modes.data_len);
+    } else {
+        readDataFromFile();
+    }
+    return NULL;
+}
+
+/* ============================== 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
+ */
+void dumpMagnitudeBar(int index, int magnitude) {
+    char *set = ".-o";
+    char buf[256];
+    int div = magnitude / 4;
+    int rem = magnitude % 4;
+
+    memset(buf,'O',div);
+    buf[div] = set[rem];
+    buf[div+1] = '\0';
+
+    if (index >= 0)
+        printf("[%.3d] |%-69s %d\n", index, buf, magnitude);
+    else
+        printf("[%.2d] |%-69s %d\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. */
+
+void dumpMagnitudeVector(unsigned char *m, uint32_t offset) {
+    uint32_t padding = 5; /* Show 5 samples before the actual start. */
+    uint32_t start = (offset < padding) ? 0 : offset-padding;
+    uint32_t end = offset + (MODES_PREAMBLE_US*2)+(MODES_SHORT_MSG_BITS*2) - 1;
+    uint32_t j;
+
+    for (j = start; j <= end; j++)
+        dumpMagnitudeBar(j-offset, m[j]);
+}
+
+/* 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
+ */
+void dumpRawMessage(char *descr, unsigned char *msg,
+                    unsigned char *m, uint32_t offset)
+{
+    int j;
+
+    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("\n");
+    dumpMagnitudeVector(m,offset);
+    printf("---\n\n");
+}
+
+/* ===================== Mode S detection and decoding  ===================== */
+
+/* Parity table for MODE S Messages.
+ * The table contains 112 elements, every element corresponds to a bit set
+ * in the message, starting from the first bit of actual data after the
+ * preamble.
+ *
+ * For messages of 112 bit, the whole table is used.
+ * For messages of 56 bits only the last 56 elements are used.
+ *
+ * The algorithm is as simple as xoring all the elements in this table
+ * for which the corresponding bit on the message is set to 1.
+ *
+ * The latest 24 elements in this table are set to 0 as the checksum at the
+ * end of the message should not affect the computation.
+ *
+ * Note: this function can be used with DF11 and DF17, other modes have
+ * the CRC xored with the sender address as they are reply to interrogations,
+ * but a casual listener can't split the address from the checksum.
+ */
+uint32_t modes_checksum_table[112] = {
+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
+};
+
+uint32_t modesChecksum(unsigned char *msg, int bits) {
+    uint32_t crc = 0;
+    int offset = (bits == 112) ? 0 : (112-56);
+    int j;
+
+    for(j = 0; j < bits; j++) {
+        int byte = j/8;
+        int bit = j%8;
+        int bitmask = 1 << (7-bit);
+
+        /* If bit is set, xor with corresponding table entry. */
+        if (msg[byte] & bitmask)
+            crc ^= modes_checksum_table[j+offset];
+    }
+    return crc; /* 24 bit checksum. */
+}
+
+/* Given the Downlink Format (DF) of the message, return the message length
+ * in bits. */
+int modesMessageLenByType(int type) {
+    if (type == 16 || type == 17 ||
+        type == 19 || type == 20 ||
+        type == 21)
+        return MODES_LONG_MSG_BITS;
+    else
+        return MODES_SHORT_MSG_BITS;
+}
+
+/* Try to fix single bit errors using the checksum. On success modifies
+ * the original buffer with the fixed version, and returns the position
+ * of the error bit. Otherwise if fixing failed -1 is returned. */
+int fixSingleBitErrors(unsigned char *msg, int bits) {
+    int j;
+    unsigned char aux[MODES_LONG_MSG_BITS/8];
+
+    for (j = 0; j < bits; j++) {
+        int byte = j/8;
+        int bitmask = 1 << (7-(j%8));
+        uint32_t crc1, crc2;
+
+        memcpy(aux,msg,bits/8);
+        aux[byte] ^= bitmask; /* Flip j-th bit. */
+
+        crc1 = ((uint32_t)aux[(bits/8)-3] << 16) |
+               ((uint32_t)aux[(bits/8)-2] << 8) |
+                (uint32_t)aux[(bits/8)-1];
+        crc2 = modesChecksum(aux,bits);
+
+        if (crc1 == crc2) {
+            /* The error is fixed. Overwrite the original buffer with
+             * the corrected sequence, and returns the error bit
+             * position. */
+            memcpy(msg,aux,bits/8);
+            return j;
+        }
+    }
+    return -1;
+}
+
+/* Add the specified entry to the list of recently seen ICAO addresses.
+ * We use the array as a circular buffer. */
+void addRecentlySeenICAOAddr(uint32_t addr) {
+    Modes.icao_cache[Modes.icao_cache_idx] = addr;
+    Modes.icao_cache_idx = (Modes.icao_cache_idx+1) % MODES_ICAO_CACHE_LEN;
+}
+
+/* If the message type appears to be about a DF that has the checksum xored
+ * with the ICAO address, try to brute force it using a list of recently
+ * seen ICAO addresses.
+ *
+ * Do this in a brute-force fashion xoring the addresses we know with the
+ * message checksum, and latest testing if the message verifies.
+ *
+ * On success the input buffer is modified to remove the xored checksum
+ * from the packet, so that the last three bytes will contain the
+ * plain ICAO address.
+ *
+ * If the function successfully recovers a message with a correct checksum
+ * it returns 1. Otherwise 0 is returned. */
+int bruteForceAP(unsigned char *msg, int msgbits) {
+    int j;
+    unsigned char aux[MODES_LONG_MSG_BITS/8];
+    int msgtype = msg[0]>>3;
+
+    if (msgtype == 0 ||         /* Short air surveillance */
+        msgtype == 4 ||         /* Surveillance, altitude reply */
+        msgtype == 5 ||         /* Surveillance, identity reply */
+        msgtype == 16 ||        /* Long Air-Air survillance */
+        msgtype == 20 ||        /* Comm-A, altitude request */
+        msgtype == 21 ||        /* Comm-A, identity request */
+        msgtype == 24)          /* Comm-C ELM */
+    {
+        for (j = 0; j < MODES_ICAO_CACHE_LEN; j++) {
+            uint32_t addr = Modes.icao_cache[j];
+            uint32_t crc1, crc2;
+            int lastbyte = (msgbits/8)-1;
+
+            if (addr == 0) continue; /* Empty field. */
+            memcpy(aux,msg,msgbits/8);
+            /* Xor with the address, so that if we picked the right address
+             * what remains is just the checksum. */
+            aux[lastbyte] ^= addr & 0xff;
+            aux[lastbyte-1] ^= (addr >> 8) & 0xff;
+            aux[lastbyte-2] ^= (addr >> 16) & 0xff;
+            crc1 = aux[lastbyte-2] << 16 |
+                   aux[lastbyte-1] << 8 |
+                   aux[lastbyte];
+            crc2 = modesChecksum(aux,msgbits);
+            if (crc1 == crc2) {
+                /* Restore the address as last three bytes. */
+                msg[lastbyte] = addr & 0xff;
+                msg[lastbyte-1] = (addr >> 8) & 0xff;
+                msg[lastbyte-2] = (addr >> 16) & 0xff;
+                return 1;
+            }
+        }
+    }
+    return 0;
+}
+
+/* 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. */
+int decodeAC13Field(unsigned char *msg, int *unit) {
+    int m_bit = msg[3] & (1<<6);
+    int q_bit = msg[3] & (1<<4);
+
+    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 = ((msg[2]&31)<<6) |
+                    ((msg[3]&0x80)>>2) |
+                    ((msg[3]&0x20)>>1) |
+                     (msg[3]&15);
+            /* The final altitude is due to the resulting number multiplied
+             * by 25, minus 1000. */
+            return n*25-1000;
+        } else {
+            /* TODO: Implement altitude where Q=0 and M=0 */
+        }
+    } else {
+        *unit = MODES_UNIT_METERS;
+        /* TODO: Implement altitude when meter unit is selected. */
+    }
+    return 0;
+}
+
+/* Decode the 12 bit AC altitude field (in DF 17 and others).
+ * Returns the altitude or 0 if it can't be decoded. */
+int decodeAC12Field(unsigned char *msg, int *unit) {
+    int q_bit = msg[5] & 1;
+
+    if (q_bit) {
+        /* N is the 11 bit integer resulting from the removal of bit
+         * Q */
+        *unit = MODES_UNIT_FEET;
+        int n = ((msg[5]>>1)<<4) | ((msg[6]&0xF0) >> 4);
+        /* The final altitude is due to the resulting number multiplied
+         * by 25, minus 1000. */
+        return n*25-1000;
+    } else {
+        return 0;
+    }
+}
+
+/* Capability table. */
+char *ca_str[8] = {
+    /* 0 */ "Level 1 (Survillance Only)",
+    /* 1 */ "Level 2 (DF0,4,5,11)",
+    /* 2 */ "Level 3 (DF0,4,5,11,20,21)",
+    /* 3 */ "Level 4 (DF0,4,5,11,20,21,24)",
+    /* 4 */ "Level 2+3+4 (DF0,4,5,11,20,21,24,code7 - is on ground)",
+    /* 5 */ "Level 2+3+4 (DF0,4,5,11,20,21,24,code7 - is on airborne)",
+    /* 6 */ "Level 2+3+4 (DF0,4,5,11,20,21,24,code7)",
+    /* 7 */ "Level 7 ???"
+};
+
+/* Flight status table. */
+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 */ "Value 6 is not assigned",
+    /* 7 */ "Value 7 is not assigned"
+};
+
+/* ME message type to description table. */
+char *me_str[] = {
+};
+
+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 == 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;
+}
+
+/* Decode a raw Mode S message demodulated as a stream of bytes by
+ * detectModeS(), and split it into fields populating a modesMessage
+ * structure. */
+void decodeModesMessage(struct modesMessage *mm, unsigned char *msg) {
+    uint32_t crc2;   /* Computed CRC, used to verify the message CRC. */
+    char *ais_charset = "?ABCDEFGHIJKLMNOPQRSTUVWXYZ????? ???????????????0123456789??????";
+
+    /* Work on our local copy */
+    memcpy(mm->msg,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);
+
+    /* CRC is always the last three bytes. */
+    mm->crc = ((uint32_t)msg[(mm->msgbits/8)-3] << 16) |
+              ((uint32_t)msg[(mm->msgbits/8)-2] << 8) |
+               (uint32_t)msg[(mm->msgbits/8)-1];
+    crc2 = modesChecksum(msg,mm->msgbits);
+
+    /* Check CRC and fix single bit errors using the CRC when
+     * possible (DF 11 and 17). */
+    mm->errorbit = -1;  /* No error */
+    mm->crcok = (mm->crc == crc2);
+
+    if (!mm->crcok && Modes.fix_errors &&
+        (mm->msgtype == 11 || mm->msgtype == 17))
+    {
+        if ((mm->errorbit = fixSingleBitErrors(msg,mm->msgbits)) != -1) {
+            mm->crc = modesChecksum(msg,mm->msgbits);
+            mm->crcok = 1;
+        }
+    }
+
+    /* Note that most of the other computation happens *after* we fix
+     * the single bit errors, otherwise we would need to recompute the
+     * fields again. */
+    mm->ca = msg[0] & 7;        /* Responder capabilities. */
+
+    /* ICAO address */
+    mm->aa1 = msg[1];
+    mm->aa2 = msg[2];
+    mm->aa3 = msg[3];
+
+    /* DF 17 type (assuming this is a DF17, otherwise not used) */
+    mm->metype = msg[4] >> 3;   /* Extended squitter message type. */
+    mm->mesub = msg[4] & 7;     /* Extended squitter message subtype. */
+
+    /* Fields for DF4,5,20,21 */
+    mm->fs = msg[0] & 7;        /* Flight status for DF4,5,20,21 */
+    mm->dr = msg[1] >> 3 & 31;  /* Request extraction of downlink request. */
+    mm->um = ((msg[1] & 7)<<3)| /* Request extraction of downlink request. */
+              msg[2]>>5;
+    mm->identity = (msg[2]&31 << 8) | msg[3]; /* 13 bits identity. */
+
+    /* Check if we can check the checksum for the Downlink Formats where
+     * the checksum is xored with the aircraft ICAO address. We try to
+     * brute force it using a list of recently seen aircraft addresses. */
+    if (mm->msgtype != 11 && mm->msgtype != 17) {
+        /* Return to the caller now if we can't resolve the API field and
+         * we need to check the checksum. */
+        if (bruteForceAP(msg,mm->msgbits)) {
+            /* We recovered the message!
+             * Populate the AA fields with the right information. */
+            mm->aa3 = msg[mm->msgbits/8-1];
+            mm->aa2 = msg[mm->msgbits/8-2];
+            mm->aa1 = msg[mm->msgbits/8-3];
+            mm->crcok = 1;
+        } else {
+            mm->crcok = 0;
+        }
+    }
+    /* If this is DF 11 or DF 17 and the checksum was ok,
+     * we can add this address to the list of recently seen
+     * addresses. */
+    if (mm->crcok && mm->errorbit == -1) {
+        uint32_t addr = (mm->aa1 << 16) | (mm->aa2 << 8) | mm->aa3;
+        addRecentlySeenICAOAddr(addr);
+    }
+
+    /* Decode 13 bit altitude for DF0, DF4, DF16, DF20 */
+    if (mm->msgtype == 0 || mm->msgtype == 4 ||
+        mm->msgtype == 16 || mm->msgtype == 20) {
+        mm->altitude = decodeAC13Field(msg, &mm->unit);
+    }
+
+    /* Decode extended squitter specific stuff. */
+    if (mm->msgtype == 17) {
+        /* Decode the extended squitter message. */
+
+        if (mm->metype >= 1 && mm->metype <= 4) {
+            /* Aircraft Identification and Category */
+            mm->aircraft_type = mm->metype-1;
+            mm->flight[0] = ais_charset[msg[5]>>2];
+            mm->flight[1] = ais_charset[((msg[5]&3)<<4)|(msg[6]>>4)];
+            mm->flight[2] = ais_charset[((msg[6]&15)<<2)|(msg[7]>>6)];
+            mm->flight[3] = ais_charset[msg[7]&63];
+            mm->flight[4] = ais_charset[msg[8]>>2];
+            mm->flight[5] = ais_charset[((msg[8]&3)<<4)|(msg[9]>>4)];
+            mm->flight[6] = ais_charset[((msg[9]&15)<<2)|(msg[10]>>6)];
+            mm->flight[7] = ais_charset[msg[10]&63];
+            mm->flight[8] = '\0';
+        } else if (mm->metype >= 9 && mm->metype <= 18) {
+            /* Airborne position Message */
+            mm->fflag = msg[6] & (1<<2);
+            mm->tflag = msg[6] & (1<<3);
+            mm->altitude = decodeAC12Field(msg,&mm->unit);
+            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];
+        } else if (mm->metype == 19 && mm->mesub >= 1 && mm->mesub <= 4) {
+            /* Airborne Velocity Message */
+            if (mm->mesub == 1 || mm->mesub == 2) {
+                mm->ew_dir = (msg[5]&4) >> 2;
+                mm->ew_velocity = ((msg[5]&3) << 8) | msg[6];
+                mm->ns_dir = (msg[7]&0x80) >> 7;
+                mm->ns_velocity = ((msg[7]&0x7f) << 3) | ((msg[8]&0xe0) >> 5);
+                mm->vert_rate_source = (msg[8]&0x10) >> 4;
+                mm->vert_rate_sign = (msg[8]&0x8) >> 5;
+                mm->vert_rate = ((msg[8]&7) << 6) | ((msg[9]&0xfc) >> 2);
+                mm->velocity = sqrt(mm->ns_velocity*mm->ns_velocity+
+                                    mm->ew_velocity*mm->ew_velocity);
+            } else if (mm->mesub == 3 || mm->mesub == 4) {
+                mm->heading_is_valid = msg[5] & (1<<2);
+                mm->heading = (360.0/128) * (((msg[5] & 3) << 5) |
+                                              (msg[6] >> 3));
+            }
+        }
+    }
+}
+
+/* This function gets a decoded Mode S Message and prints it on the screen
+ * in a human readable format. */
+void displayModesMessage(struct modesMessage *mm) {
+    int j;
+
+    /* Show the raw message. */
+    for (j = 0; j < mm->msgbits/8; j++) printf("%02x", mm->msg[j]);
+    printf("\n");
+
+    if (Modes.raw) return; /* Enough for --raw mode */
+
+    printf("CRC: %06x (%s)\n", (int)mm->crc, mm->crcok ? "ok" : "wrong");
+    if (mm->errorbit != -1)
+        printf("Single bit error fixed, bit %d\n", mm->errorbit);
+
+    if (mm->msgtype == 0) {
+        /* DF 0 */
+        printf("DF 0: Short Air-Air Surveillance.\n");
+        printf("  Altitude       : %d %s\n", mm->altitude,
+            (mm->unit == MODES_UNIT_METERS) ? "meters" : "feet");
+        printf("  ICAO Address   : %02x%02x%02x\n", mm->aa1, mm->aa2, mm->aa3);
+    } 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->dr);
+        printf("  UM             : %d\n", mm->um);
+        printf("  Altitude       : %d %s\n", mm->altitude,
+            (mm->unit == MODES_UNIT_METERS) ? "meters" : "feet");
+        printf("  ICAO Address   : %02x%02x%02x\n", mm->aa1, mm->aa2, mm->aa3);
+
+        if (mm->msgtype == 20) {
+            /* TODO: 56 bits DF20 MB additional field. */
+        }
+    } 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->dr);
+        printf("  UM             : %d\n", mm->um);
+        printf("  Squawk         : %o (octal)\n", mm->identity);
+        printf("  ICAO Address   : %02x%02x%02x\n", mm->aa1, mm->aa2, mm->aa3);
+
+        if (mm->msgtype == 21) {
+            /* TODO: 56 bits DF21 MB additional field. */
+        }
+    } else if (mm->msgtype == 11) {
+        /* DF 11 */
+        printf("DF 11: All Call Reply.\n");
+        printf("  Capability  : %s\n", ca_str[mm->ca]);
+        printf("  ICAO Address: %02x%02x%02x\n", mm->aa1, mm->aa2, mm->aa3);
+    } 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   : %02x%02x%02x\n", mm->aa1, mm->aa2, mm->aa3);
+        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. */
+            char *ac_type_str[4] = {
+                "Aircraft Type D",
+                "Aircraft Type C",
+                "Aircraft Type B",
+                "Aircraft Type A"
+            };
+
+            printf("    Aircraft Type  : %s\n", ac_type_str[mm->aircraft_type]);
+            printf("    Identification : %s\n", mm->flight);
+        } else if (mm->metype >= 9 && mm->metype <= 18) {
+            printf("    F flag   : %s\n", mm->fflag ? "odd" : "even");
+            printf("    T flag   : %s\n", mm->tflag ? "UTC" : "non-UTC");
+            printf("    Altitude : %d feet\n", mm->altitude);
+            printf("    Latitude : %d (not decoded)\n", mm->raw_latitude);
+            printf("    Longitude: %d (not decoded)\n", mm->raw_longitude);
+        } else if (mm->metype == 19 && mm->mesub >= 1 && mm->mesub <= 4) {
+            if (mm->mesub == 1 || mm->mesub == 2) {
+                /* Velocity */
+                printf("    EW direction      : %d\n", mm->ew_dir);
+                printf("    EW velocity       : %d\n", mm->ew_velocity);
+                printf("    NS direction      : %d\n", mm->ns_dir);
+                printf("    NS velocity       : %d\n", mm->ns_velocity);
+                printf("    Vertical rate src : %d\n", mm->vert_rate_source);
+                printf("    Vertical rate sign: %d\n", mm->vert_rate_sign);
+                printf("    Vertical rate     : %d\n", mm->vert_rate);
+            } else if (mm->mesub == 3 || mm->mesub == 4) {
+                printf("    Heading status: %d", mm->heading_is_valid);
+                printf("    Heading: %d", mm->heading);
+            }
+        } else {
+            printf("    Unrecognized ME type: %d subtype: %d\n", 
+                mm->metype, mm->mesub);
+        }
+    } else {
+        if (Modes.check_crc)
+            printf("DF %d with good CRC received "
+                   "(decoding still not implemented).\n",
+                mm->msgtype);
+    }
+}
+
+/* Turn I/Q samples pointed by Modes.data into the magnitude vector
+ * pointed by Modes.magnitude. */
+void computeMagnitudeVector(void) {
+    unsigned char *m = Modes.magnitude, *p = Modes.data;
+    uint32_t j;
+
+    /* 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.data_len; j += 2) {
+        int i = p[j]-127;
+        int q = p[j+1]-127;
+
+        if (i < 0) i = -i;
+        if (q < 0) q = -q;
+        m[j/2] = Modes.maglut[i*129+q];
+    }
+}
+
+/* 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 detectModeS(unsigned char *m, uint32_t mlen) {
+    unsigned char bits[MODES_LONG_MSG_BITS];
+    unsigned char msg[MODES_LONG_MSG_BITS/2];
+    uint32_t j;
+
+    /* 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 - MODES_FULL_LEN*2; j++) {
+        int low, high, i, errors;
+
+        /* 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 (!(m[j] > m[j+1] &&
+            m[j+1] < m[j+2] &&
+            m[j+2] > m[j+3] &&
+            m[j+3] < m[j] &&
+            m[j+4] < m[j] &&
+            m[j+5] < m[j] &&
+            m[j+6] < m[j] &&
+            m[j+7] > m[j+8] &&
+            m[j+8] < m[j+9] &&
+            m[j+9] > m[j+6]))
+        {
+            if (Modes.debug == MODES_DEBUG_NOPREAMBLE &&
+                m[j] > 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. */
+        high = (m[j]+m[j+2]+m[j+7]+m[j+9])/4;
+        if (m[j+3] >= high ||
+            m[j+4] >= high ||
+            m[j+5] >= high ||
+            m[j+6] >= high)
+        {
+            if (Modes.debug == MODES_DEBUG_NOPREAMBLE &&
+                m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
+                dumpRawMessage("Too high level in samples between 3 and 6",
+                    msg, m, j);
+            continue;
+        }
+
+        /* Similarly samples in the range 10-15 must be low, as it is the
+         * space between the preamble and real data. */
+        if (m[j+10] >= high ||
+            m[j+11] >= high ||
+            m[j+12] >= high ||
+            m[j+13] >= high ||
+            m[j+14] >= high ||
+            m[j+15] >= high)
+        {
+            if (Modes.debug == MODES_DEBUG_NOPREAMBLE &&
+                m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
+                dumpRawMessage("Too high level in samples between 10 and 15",
+                    msg, m, j);
+            continue;
+        }
+
+        /* Decode all the next 112 bits, regardless of the actual message
+         * size. We'll check the actual message type later. */
+        errors = 0;
+        for (i = 0; i < MODES_LONG_MSG_BITS*2; i += 2) {
+            low = m[j+i+MODES_PREAMBLE_US*2];
+            high = m[j+i+MODES_PREAMBLE_US*2+1];
+            if (low == high) {
+                /* Checking if two adiacent samples have the same magnitude
+                 * is an effective way to detect if it's just random noise
+                 * that was detected as a valid preamble. */
+                bits[i/2] = 2; /* error */
+                if (i < MODES_SHORT_MSG_BITS*2) errors++;
+            } else if (low > high) {
+                bits[i/2] = 1;
+            } else {
+                /* (low < high) for exclusion  */
+                bits[i/2] = 0;
+            }
+        }
+
+        /* Pack bits into bytes */
+        for (i = 0; i < MODES_LONG_MSG_BITS; i += 8) {
+            msg[i/8] =
+                bits[i]<<7 | 
+                bits[i+1]<<6 | 
+                bits[i+2]<<5 | 
+                bits[i+3]<<4 | 
+                bits[i+4]<<3 | 
+                bits[i+5]<<2 | 
+                bits[i+6]<<1 | 
+                bits[i+7];
+        }
+
+        int msgtype = msg[0]>>3;
+        int msglen = modesMessageLenByType(msgtype)/8;
+
+        /* Last check, high and low bits are different enough in magnitude
+         * to mark this as real message and not just noise? */
+        int delta = 0;
+        for (i = 0; i < msglen*8*2; i += 2) {
+            delta += abs(m[j+i+MODES_PREAMBLE_US*2]-
+                         m[j+i+MODES_PREAMBLE_US*2+1]);
+        }
+        delta /= msglen*4;
+
+        /* A avg delta of three is small enough to let almost every kind of
+         * message to pass, but high enough to filter some random noise,
+         * especially when --no-crc-check is used. */
+        if (delta < 3) continue;
+
+        /* If we reached this point, and error is zero, we are very likely
+         * with a Mode S message in our hands, but it may still be broken
+         * and CRC may not be correct. This is handled by the next layer. */
+        if (errors == 0) {
+            struct modesMessage mm;
+
+            decodeModesMessage(&mm,msg);
+
+            if (Modes.debug == MODES_DEBUG_DEMOD)
+                dumpRawMessage("Demodulated with 0 errors", msg, m, j);
+            else if (Modes.debug == MODES_DEBUG_BADCRC && !mm.crcok)
+                dumpRawMessage("Decoded with bad CRC", msg, m, j);
+            else if (Modes.debug == MODES_DEBUG_GOODCRC && mm.crcok &&
+                     mm.errorbit == -1)
+                dumpRawMessage("Decoded with good CRC", msg, m, j);
+
+            if (Modes.check_crc == 0 || mm.crcok) {
+                if (Modes.interactive) {
+                    interactiveReceiveData(&mm);
+                } else {
+                    displayModesMessage(&mm);
+                    if (!Modes.raw) printf("\n");
+                }
+            }
+
+            /* Skip this message. Inside it we can find only fake premables. */
+            j += (MODES_PREAMBLE_US+(msglen*8))*2;
+        } else {
+            if (Modes.debug == MODES_DEBUG_DEMODERR) {
+                printf("The following message has %d demod errors\n", errors);
+                dumpRawMessage("Demodulated with errors", msg, m, j);
+            }
+        }
+    }
+}
+
+/* ========================= Interactive mode =============================== */
+
+/* Return a new aircraft structure for the interactive mode linked list
+ * of aircrafts. */
+struct aircraft *interactiveCreateAircraft(uint32_t addr) {
+    struct aircraft *a = malloc(sizeof(*a));
+
+    a->addr = addr;
+    snprintf(a->hexaddr,sizeof(a->hexaddr),"%06x",(int)addr);
+    a->flight[0] = '\0';
+    a->altitude = 0;
+    a->seen = time(NULL);
+    a->messages = 0;
+    a->next = NULL;
+    return a;
+}
+
+/* Return the aircraft with the specified address, or NULL if no aircraft
+ * exists with this address. */
+struct aircraft *interactiveFindAircraft(uint32_t addr) {
+    struct aircraft *a = Modes.aircrafts;
+
+    while(a) {
+        if (a->addr == addr) return a;
+        a = a->next;
+    }
+    return NULL;
+}
+
+/* Receive new messages and populate the interactive mode with more info. */
+void interactiveReceiveData(struct modesMessage *mm) {
+    uint32_t addr;
+    struct aircraft *a, *aux;
+
+    if (Modes.check_crc && mm->crcok == 0) return;
+    addr = (mm->aa1 << 16) | (mm->aa2 << 8) | mm->aa3;
+
+    /* Loookup our aircraft or create a new one. */
+    a = interactiveFindAircraft(addr);
+    if (!a) {
+        a = interactiveCreateAircraft(addr);
+        a->next = Modes.aircrafts;
+        Modes.aircrafts = a;
+    } else {
+        /* If it is an already known aircraft, move it on head
+         * so we keep aircrafts ordered by received message time.
+         *
+         * However move it on head only if at least one second elapsed
+         * since the aircraft that is currently on head sent a message,
+         * othewise with multiple aircrafts at the same time we have an
+         * useless shuffle of positions on the screen. */
+        if (Modes.aircrafts != a && (time(NULL) - a->seen) >= 1) {
+            aux = Modes.aircrafts;
+            while(aux->next != a) aux = aux->next;
+            /* Now we are a node before the aircraft to remove. */
+            aux->next = aux->next->next; /* removed. */
+            /* Add on head */
+            a->next = Modes.aircrafts;
+            Modes.aircrafts = a;
+        }
+    }
+
+    a->seen = time(NULL);
+    a->messages++;
+
+    if (mm->msgtype == 0 || mm->msgtype == 4 || mm->msgtype == 20) {
+        a->altitude = mm->altitude;
+    } else if (mm->msgtype == 17) {
+        if (mm->metype >= 1 && mm->metype <= 4) {
+            memcpy(a->flight, mm->flight, sizeof(a->flight));
+        } else if (mm->metype >= 9 && mm->metype <= 18) {
+            a->altitude = mm->altitude;
+        } else if (mm->metype == 19) {
+            if (mm->mesub == 1 || mm->mesub == 2) {
+                a->speed = mm->velocity;
+            }
+        }
+    }
+}
+
+/* Show the currently captured interactive data on screen. */
+void interactiveShowData(void) {
+    struct aircraft *a = Modes.aircrafts;
+    time_t now = time(NULL);
+    char progress[4];
+    int count = 0;
+
+    memset(progress,' ',3);
+    progress[time(NULL)%3] = '.';
+    progress[3] = '\0';
+
+    printf("\x1b[H\x1b[2J");    /* Clear the screen */
+    printf("Hex    Flight   Altitude  Speed     Messages  Seen    %s\n",
+            progress);
+    printf("----------------------------------------------------\n");
+    while(a && count < 15) {
+        printf("%-6s %-8s %-9d %-9d %-9ld %d sec ago\n",
+            a->hexaddr, a->flight, a->altitude, a->speed, a->messages,
+            (int)(now - a->seen));
+        a = a->next;
+        count++;
+    }
+}
+
+/* ============================== 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;
+    long long c = 0;
+
+    while ((i = getchar()) != EOF && (q = getchar()) != EOF) {
+        if (abs(i-127) < level && abs(q-127) < level) {
+            c++;
+            if (c > MODES_PREAMBLE_US*4) continue;
+        } else {
+            c = 0;
+        }
+        putchar(i);
+        putchar(q);
+    }
+}
+
+/* ================================ Main ==================================== */
+
+void showHelp(void) {
+    printf(
+"--device-index <index>   Select RTL device (default: 0).\n"
+"--gain <db>              Set gain (default: max gain. Use 0 for auto-gain).\n"
+"--enable-agc <db>        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"
+"--raw                    Show only messages hex values.\n"
+"--no-fix                 Disalbe single-bits error correction using CRC.\n"
+"--no-crc-check           Disalbe messages with broken CRC.\n"
+"--snip <level>           Strip IQ file removing samples < level.\n"
+"--help                   Show this help.\n"
+    );
+}
+
+int main(int argc, char **argv) {
+    int j;
+
+    /* Set sane defaults. */
+    modesInitConfig();
+
+    /* 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_index = atoi(argv[++j]);
+        } else if (!strcmp(argv[j],"--gain") && more) {
+            Modes.gain = 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 = strtoll(argv[++j],NULL,10);
+        } else if (!strcmp(argv[j],"--ifile") && more) {
+            Modes.filename = strdup(argv[++j]);
+        } else if (!strcmp(argv[j],"--no-fix")) {
+            Modes.fix_errors = 0;
+        } else if (!strcmp(argv[j],"--no-crc-check")) {
+            Modes.check_crc = 0;
+        } else if (!strcmp(argv[j],"--raw")) {
+            Modes.raw = 1;
+        } else if (!strcmp(argv[j],"--interactive")) {
+            Modes.interactive = 1;
+        } else if (!strcmp(argv[j],"--debug") && more) {
+            Modes.debug = atoi(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 {
+            fprintf(stderr,
+                "Unknown or not enough arguments for option '%s'.\n\n",
+                argv[j]);
+            showHelp();
+            exit(1);
+        }
+    }
+
+    modesInit();
+    if (Modes.filename == NULL) {
+        modesInitRTLSDR();
+    } else {
+        if (Modes.filename[0] == '-' && Modes.filename[1] == '\0') {
+            Modes.fd = STDIN_FILENO;
+        } else if ((Modes.fd = open(Modes.filename,O_RDONLY)) == -1) {
+            perror("Opening data file");
+            exit(1);
+        }
+    }
+
+    /* Create the thread that will read the data from the device. */
+    pthread_create(&Modes.reader_thread, NULL, readerThreadEntryPoint, NULL);
+
+    pthread_mutex_lock(&Modes.data_mutex);
+    while(1) {
+        if (!Modes.data_ready) {
+            pthread_cond_wait(&Modes.data_cond,&Modes.data_mutex);
+            continue;
+        }
+        computeMagnitudeVector();
+
+        /* If we are reading data from the RTLSDR device, process data
+         * after releasing the lock, so that the capturing thread can
+         * read data while we perform computationally expensive stuff
+         * at the same time. (This should only be useful with very
+         * slow processors).
+         *
+         * Instead if we are reading from file, process data before
+         * releasing the lock, in order to avoid missing parts of the file
+         * to process.
+         */
+        if (Modes.filename) detectModeS(Modes.magnitude, Modes.data_len/2);
+        Modes.data_ready = 0;
+        pthread_mutex_unlock(&Modes.data_mutex);
+        if (!Modes.filename) detectModeS(Modes.magnitude, Modes.data_len/2);
+
+        pthread_mutex_lock(&Modes.data_mutex);
+
+        /* Refresh screen when in interactive mode. */
+        if (Modes.interactive &&
+            (mstime() - Modes.interactive_last_update) >
+            MODES_INTERACTIVE_REFRESH_TIME)
+        {
+            interactiveShowData();
+            Modes.interactive_last_update = mstime();
+        }
+    }
+
+    rtlsdr_close(Modes.dev);
+    return 0;
+}
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

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



More information about the pkg-hamradio-commits mailing list