diff --git a/dump978/.gitignore b/dump978/.gitignore new file mode 100644 index 00000000..e86cb85f --- /dev/null +++ b/dump978/.gitignore @@ -0,0 +1,3 @@ +*~ +*.o +dump978 diff --git a/dump978/LICENSE b/dump978/LICENSE new file mode 100644 index 00000000..d6a93266 --- /dev/null +++ b/dump978/LICENSE @@ -0,0 +1,340 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/dump978/Makefile b/dump978/Makefile new file mode 100644 index 00000000..9e6daf4a --- /dev/null +++ b/dump978/Makefile @@ -0,0 +1,33 @@ +CFLAGS+=-O2 -g -Wall -Werror -Ifec +LDFLAGS= +LIBS=-lm +CC=gcc + +all: dump978 uat2json uat2text uat2esnt extract_nexrad + +%.o: %.c *.h + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +dump978: dump978.o fec.o fec/decode_rs_char.o fec/init_rs_char.o + $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) + +uat2json: uat2json.o uat_decode.o reader.o + $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) + +uat2text: uat2text.o uat_decode.o reader.o + $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) + +uat2esnt: uat2esnt.o uat_decode.o reader.o + $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) + +extract_nexrad: extract_nexrad.o uat_decode.o reader.o + $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) + +fec_tests: fec_tests.o fec.o fec/decode_rs_char.o fec/init_rs_char.o + $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) + +test: fec_tests + ./fec_tests + +clean: + rm -f *~ *.o fec/*.o dump978 uat2json uat2text uat2esnt fec_tests diff --git a/dump978/README.md b/dump978/README.md new file mode 100644 index 00000000..500d19cb --- /dev/null +++ b/dump978/README.md @@ -0,0 +1,124 @@ +# dump978 + +Experimental demodulator/decoder for 978MHz UAT signals. + +## A note about future development + +I'm in Europe which doesn't use UAT, so there won't be much spontaneous +development going on now that the demodulator is at a basic "it works" stage. + +I'm happy to look at signal or message captures and help with further +development, but it really needs to be driven by whoever is actually using the +code to receive UAT! + +## Demodulator + +dump978 is the demodulator. It expects 8-bit I/Q samples on stdin at +2.083334MHz, for example: + +```` +$ rtl_sdr -f 978000000 -s 2083334 -g 48 - | ./dump978 +```` + +It outputs one one line per demodulated message, in the form: + +```` ++012345678..; this is an uplink message +-012345678..; this is a downlink message +```` + +For parsers: ignore everything between the first semicolon and newline that +you don't understand, it will be used for metadata later. See reader.[ch] for +a reference implementation. + +## Decoder + +To decode messages into a readable form use uat2text: + +```` +$ rtl_sdr -f 978000000 -s 2083334 -g 48 - | ./dump978 | ./uat2text +```` + +## Sample data + +Around 1100 sample messages are in the file sample-data.txt.gz. They are the +output of the demodulator from various RF captures I have on hand. This file +can be fed to uat2text etc: + +$ zcat sample-data.txt.gz | ./uat2text + +When testing, this is much easier on your CPU (and disk space!) than starting +from the raw RF captures. + +## Filtering for just uplink or downlink messages + +As the uplink and downlink messages start with different characters, you can +filter for just one type of message very easily with grep: + +```` + # Uplink messages only: +$ zcat sample-data.txt.gz | grep "^+" | ./uat2text + # Downlink messages only: +$ zcat sample-data.txt.gz | grep "^-" | ./uat2text +```` + +## Map generation via uat2json + +uat2json writes aircraft.json files in the format expected by dump1090's +map html/javascript. + +To set up a live map feed: + +1) Get a copy of dump1090, we're going to reuse its mapping html/javascript: + +```` +$ git clone https://github.com/mutability/dump1090 dump1090-copy +```` + +2) Put the html/javascript somewhere your webserver can reach: + +```` +$ mkdir /var/www/dump978map +$ cp -a dump1090-copy/public_html/* /var/www/dump978map/ +```` + +3) Create an empty "data" subdirectory + +```` +$ mkdir /var/www/dump978map/data +```` + +4) Feed uat2json from dump978: + +```` +$ rtl_sdr -f 978000000 -s 2083334 -g 48 - | \ + ./dump978 | \ + ./uat2json /var/www/dump978map/data +```` + +5) Go look at http://localhost/dump978map/ + +## uat2esnt: convert UAT ADS-B messages to Mode S ADS-B messages. + +Warning: This one is particularly experimental. + +uat2esnt accepts 978MHz UAT downlink messages on stdin and +generates 1090MHz Extended Squitter messages on stdout. + +The generated messages mostly use DF18 with CF=6, which is +for rebroadcasts of ADS-B messages (ADS-R). + +The output format is the "AVR" text format; this can be +fed to dump1090 on port 30001 by default. Other ADS-B tools +may accept it too - e.g. VRS seems to accept most of it (though +it ignores DF18 CF=5 messages which are generated for +non-ICAO-address callsign/squawk information. + +You'll want a pipeline like this: + +```` +$ rtl_sdr -f 978000000 -s 2083334 -g 48 - | \ + ./dump978 | \ + ./uat2esnt | \ + nc -q1 localhost 30001 +```` diff --git a/dump978/dump978.c b/dump978/dump978.c new file mode 100644 index 00000000..d9a50fe0 --- /dev/null +++ b/dump978/dump978.c @@ -0,0 +1,429 @@ +// +// Copyright 2015, Oliver Jowett +// + +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include +#include +#include + +#include "uat.h" +#include "fec.h" + +static void make_atan2_table(); +static void read_from_stdin(); +static int process_buffer(uint16_t *phi, int len, uint64_t offset); +static int demod_adsb_frame(uint16_t *phi, uint8_t *to, int *rs_errors); +static int demod_uplink_frame(uint16_t *phi, uint8_t *to, int *rs_errors); +static void demod_frame(uint16_t *phi, uint8_t *frame, int bytes, int16_t center_dphi); +static void handle_adsb_frame(uint64_t timestamp, uint8_t *frame, int rs); +static void handle_uplink_frame(uint64_t timestamp, uint8_t *frame, int rs); + +#define SYNC_BITS (36) +#define ADSB_SYNC_WORD 0xEACDDA4E2UL +#define UPLINK_SYNC_WORD 0x153225B1DUL + +// relying on signed overflow is theoretically bad. Let's do it properly. + +#ifdef USE_SIGNED_OVERFLOW +#define phi_difference(from,to) ((int16_t)((to) - (from))) +#else +inline int16_t phi_difference(uint16_t from, uint16_t to) +{ + int32_t difference = to - from; // lies in the range -65535 .. +65535 + if (difference >= 32768) // +32768..+65535 + return difference - 65536; // -> -32768..-1: always in range + else if (difference < -32768) // -65535..-32769 + return difference + 65536; // -> +1..32767: always in range + else + return difference; +} +#endif + +int main(int argc, char **argv) +{ + make_atan2_table(); + init_fec(); + read_from_stdin(); + return 0; +} + +static void dump_raw_message(char updown, uint8_t *data, int len, int rs_errors) +{ + int i; + + fprintf(stdout, "%c", updown); + for (i = 0; i < len; ++i) { + fprintf(stdout, "%02x", data[i]); + } + + if (rs_errors) + fprintf(stdout, ";rs=%d", rs_errors); + fprintf(stdout, ";\n"); +} + +static void handle_adsb_frame(uint64_t timestamp, uint8_t *frame, int rs) +{ + dump_raw_message('-', frame, (frame[0]>>3) == 0 ? SHORT_FRAME_DATA_BYTES : LONG_FRAME_DATA_BYTES, rs); + fflush(stdout); +} + +static void handle_uplink_frame(uint64_t timestamp, uint8_t *frame, int rs) +{ + dump_raw_message('+', frame, UPLINK_FRAME_DATA_BYTES, rs); + fflush(stdout); +} + +uint16_t iqphase[65536]; // contains value [0..65536) -> [0, 2*pi) + +void make_atan2_table() +{ + unsigned i,q; + union { + uint8_t iq[2]; + uint16_t iq16; + } u; + + for (i = 0; i < 256; ++i) { + for (q = 0; q < 256; ++q) { + double d_i = (i - 127.5); + double d_q = (q - 127.5); + double ang = atan2(d_q, d_i) + M_PI; // atan2 returns [-pi..pi], normalize to [0..2*pi] + double scaled_ang = round(32768 * ang / M_PI); + + u.iq[0] = i; + u.iq[1] = q; + iqphase[u.iq16] = (scaled_ang < 0 ? 0 : scaled_ang > 65535 ? 65535 : (uint16_t)scaled_ang); + } + } +} + +static void convert_to_phi(uint16_t *buffer, int n) +{ + int i; + + for (i = 0; i < n; ++i) + buffer[i] = iqphase[buffer[i]]; +} + +void read_from_stdin() +{ + char buffer[65536*2]; + int n; + int used = 0; + uint64_t offset = 0; + + while ( (n = read(0, buffer+used, sizeof(buffer)-used)) > 0 ) { + int processed; + + convert_to_phi((uint16_t*) (buffer+(used&~1)), ((used&1)+n)/2); + + used += n; + processed = process_buffer((uint16_t*) buffer, used/2, offset); + used -= processed * 2; + offset += processed; + if (used > 0) { + memmove(buffer, buffer+processed*2, used); + } + } +} + + +// Return 1 if word is "equal enough" to expected +static inline int sync_word_fuzzy_compare(uint64_t word, uint64_t expected) +{ + uint64_t diff; + + if (word == expected) + return 1; + + diff = word ^ expected; // guaranteed nonzero + + // This is a bit-twiddling popcount + // hack, tweaked as we only care about + // "=N" set bits for fixed N - + // so we can bail out early after seeing N + // set bits. + // + // It relies on starting with a nonzero value + // with zero or more trailing clear bits + // after the last set bit: + // + // 010101010101010000 + // ^ + // Subtracting one, will flip the + // bits starting at the last set bit: + // + // 010101010101001111 + // ^ + // then we can use that as a bitwise-and + // mask to clear the lowest set bit: + // + // 010101010101000000 + // ^ + // And repeat until the value is zero + // or we have seen too many set bits. + + // >= 1 bit + diff &= (diff-1); // clear lowest set bit + if (!diff) + return 1; // 1 bit error + + // >= 2 bits + diff &= (diff-1); // clear lowest set bit + if (!diff) + return 1; // 2 bits error + + // >= 3 bits + diff &= (diff-1); // clear lowest set bit + if (!diff) + return 1; // 3 bits error + + // >= 4 bits + diff &= (diff-1); // clear lowest set bit + if (!diff) + return 1; // 4 bits error + + // > 4 bits in error, give up + return 0; +} + +#define MAX_SYNC_ERRORS 4 + +// check that there is a valid sync word starting at 'phi' +// that matches the sync word 'pattern'. Place the dphi +// threshold to use for bit slicing in '*center'. Return 1 +// if the sync word is OK, 0 on failure +int check_sync_word(uint16_t *phi, uint64_t pattern, int16_t *center) +{ + int i; + int32_t dphi_zero_total = 0; + int zero_bits = 0; + int32_t dphi_one_total = 0; + int one_bits = 0; + int error_bits; + + // find mean dphi for zero and one bits; + // take the mean of the two as our central value + + for (i = 0; i < SYNC_BITS; ++i) { + int16_t dphi = phi_difference(phi[i*2], phi[i*2+1]); + + if (pattern & (1UL << (35-i))) { + ++one_bits; + dphi_one_total += dphi; + } else { + ++zero_bits; + dphi_zero_total += dphi; + } + } + + dphi_zero_total /= zero_bits; + dphi_one_total /= one_bits; + + *center = (dphi_one_total + dphi_zero_total) / 2; + + // recheck sync word using our center value + error_bits = 0; + for (i = 0; i < SYNC_BITS; ++i) { + int16_t dphi = phi_difference(phi[i*2], phi[i*2+1]); + + if (pattern & (1UL << (35-i))) { + if (dphi < *center) + ++error_bits; + } else { + if (dphi >= *center) + ++error_bits; + } + } + + //fprintf(stdout, "check_sync_word: center=%.0fkHz, errors=%d\n", *center * 2083334.0 / 65536 / 1000, error_bits); + + return (error_bits <= MAX_SYNC_ERRORS); +} + +#define SYNC_MASK ((((uint64_t)1)< sync0 + // sample 2 - sample 1 -> sync1 + // sample 3 - sample 2 -> sync0 + // sample 4 - sample 3 -> sync1 + // ... + // + // We accumulate bits into two buffers, sync0 and sync1. + // Then we compare those buffers to the expected 36-bit sync word that + // should be at the start of each UAT frame. When (if) we find it, + // that tells us which sample to start decoding from. + + // Stop when we run out of remaining samples for a max-sized frame. + // Arrange for our caller to pass the trailing data back to us next time; + // ensure we don't consume any partial sync word we might be part-way + // through. This means we don't need to maintain state between calls. + + lenbits = len/2 - (SYNC_BITS + UPLINK_FRAME_BITS); + for (bit = 0; bit < lenbits; ++bit) { + int16_t dphi0 = phi_difference(phi[bit*2], phi[bit*2+1]); + int16_t dphi1 = phi_difference(phi[bit*2+1], phi[bit*2+2]); + + sync0 = ((sync0 << 1) | (dphi0 > 0 ? 1 : 0)) & SYNC_MASK; + sync1 = ((sync1 << 1) | (dphi1 > 0 ? 1 : 0)) & SYNC_MASK; + + if (bit < SYNC_BITS) + continue; // haven't fully populated sync0/1 yet + + // see if we have (the start of) a valid sync word + // It would be nice to look at popcount(expected ^ sync) + // so we can tolerate some errors, but that turns out + // to be very expensive to do on every sample + + // when we find a match, try to demodulate both with that match + // and with the next position, and pick the one with fewer + // errors. + + // check for downlink frames: + if (sync_word_fuzzy_compare(sync0, ADSB_SYNC_WORD) || sync_word_fuzzy_compare(sync1, ADSB_SYNC_WORD)) { + int startbit = (bit-SYNC_BITS+1); + int shift = (sync_word_fuzzy_compare(sync0, ADSB_SYNC_WORD) ? 0 : 1); + int index = startbit*2+shift; + + int skip_0, skip_1; + int rs_0 = -1, rs_1 = -1; + + skip_0 = demod_adsb_frame(phi+index, demod_buf_a, &rs_0); + skip_1 = demod_adsb_frame(phi+index+1, demod_buf_b, &rs_1); + if (skip_0 && rs_0 <= rs_1) { + handle_adsb_frame(offset+index, demod_buf_a, rs_0); + bit = startbit + skip_0; + continue; + } else if (skip_1 && rs_1 <= rs_0) { + handle_adsb_frame(offset+index+1, demod_buf_b, rs_1); + bit = startbit + skip_1; + continue; + } else { + // demod failed + } + } + + // check for uplink frames: + else if (sync_word_fuzzy_compare(sync0, UPLINK_SYNC_WORD) || sync_word_fuzzy_compare(sync1, UPLINK_SYNC_WORD)) { + int startbit = (bit-SYNC_BITS+1); + int shift = (sync_word_fuzzy_compare(sync0, UPLINK_SYNC_WORD) ? 0 : 1); + int index = startbit*2+shift; + + int skip_0, skip_1; + int rs_0 = -1, rs_1 = -1; + + skip_0 = demod_uplink_frame(phi+index, demod_buf_a, &rs_0); + skip_1 = demod_uplink_frame(phi+index+1, demod_buf_b, &rs_1); + if (skip_0 && rs_0 <= rs_1) { + handle_uplink_frame(offset+index, demod_buf_a, rs_0); + bit = startbit + skip_0; + continue; + } else if (skip_1 && rs_1 <= rs_0) { + handle_uplink_frame(offset+index+1, demod_buf_b, rs_1); + bit = startbit + skip_1; + continue; + } else { + // demod failed + } + } + } + + return (bit - SYNC_BITS)*2; +} + +// demodulate 'bytes' bytes from samples at 'phi' into 'frame', +// using 'center_dphi' as the bit slicing threshold +static void demod_frame(uint16_t *phi, uint8_t *frame, int bytes, int16_t center_dphi) +{ + while (--bytes >= 0) { + uint8_t b = 0; + if (phi_difference(phi[0], phi[1]) > center_dphi) b |= 0x80; + if (phi_difference(phi[2], phi[3]) > center_dphi) b |= 0x40; + if (phi_difference(phi[4], phi[5]) > center_dphi) b |= 0x20; + if (phi_difference(phi[6], phi[7]) > center_dphi) b |= 0x10; + if (phi_difference(phi[8], phi[9]) > center_dphi) b |= 0x08; + if (phi_difference(phi[10], phi[11]) > center_dphi) b |= 0x04; + if (phi_difference(phi[12], phi[13]) > center_dphi) b |= 0x02; + if (phi_difference(phi[14], phi[15]) > center_dphi) b |= 0x01; + *frame++ = b; + phi += 16; + } +} + +// Demodulate an ADSB (Long UAT or Basic UAT) downlink frame +// with the first sync bit in 'phi', storing the frame into 'to' +// of length up to LONG_FRAME_BYTES. Set '*rs_errors' to the +// number of corrected errors, or 9999 if demodulation failed. +// Return 0 if demodulation failed, or the number of bits (not +// samples) consumed if demodulation was OK. +static int demod_adsb_frame(uint16_t *phi, uint8_t *to, int *rs_errors) +{ + int16_t center_dphi; + int frametype; + + if (!check_sync_word(phi, ADSB_SYNC_WORD, ¢er_dphi)) { + *rs_errors = 9999; + return 0; + } + + demod_frame(phi + SYNC_BITS*2, to, LONG_FRAME_BYTES, center_dphi); + frametype = correct_adsb_frame(to, rs_errors); + if (frametype == 1) + return (SYNC_BITS + SHORT_FRAME_BITS); + else if (frametype == 2) + return (SYNC_BITS + LONG_FRAME_BITS); + else + return 0; +} + +// Demodulate an uplink frame +// with the first sync bit in 'phi', storing the frame into 'to' +// of length up to UPLINK_FRAME_BYTES. Set '*rs_errors' to the +// number of corrected errors, or 9999 if demodulation failed. +// Return 0 if demodulation failed, or the number of bits (not +// samples) consumed if demodulation was OK. +static int demod_uplink_frame(uint16_t *phi, uint8_t *to, int *rs_errors) +{ + int16_t center_dphi; + uint8_t interleaved[UPLINK_FRAME_BYTES]; + + if (!check_sync_word(phi, UPLINK_SYNC_WORD, ¢er_dphi)) { + *rs_errors = 9999; + return 0; + } + + demod_frame(phi + SYNC_BITS*2, interleaved, UPLINK_FRAME_BYTES, center_dphi); + + // deinterleave and correct + if (correct_uplink_frame(interleaved, to, rs_errors) == 1) + return (UPLINK_FRAME_BITS+SYNC_BITS); + else + return 0; +} diff --git a/dump978/extract_nexrad.c b/dump978/extract_nexrad.c new file mode 100644 index 00000000..a58e8948 --- /dev/null +++ b/dump978/extract_nexrad.c @@ -0,0 +1,315 @@ +// +// Copyright 2015, Oliver Jowett +// + +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include + +#include "uat.h" +#include "uat_decode.h" +#include "reader.h" + +#define BLOCK_WIDTH (48.0/60.0) +#define WIDE_BLOCK_WIDTH (96.0/60.0) +#define BLOCK_HEIGHT (4.0/60.0) +#define BLOCK_THRESHOLD 405000 +#define BLOCKS_PER_RING 450 + +// +// This reads demodulated uplink messages and extracts NEXRAD global block representation formats - type 63 and 64 +// +// The output format is a series of lines, one line per decoded block. +// Each line is space-separated and is formatted as: +// +// NEXRAD : +// +// where: +// is Regional (for type 63) or CONUS (for type 64) +// : is the time from the PDU header - all blocks from one composite radar image will have the same time +// is the scale value of this block - 0 (high res), 1 (med res), or 2 (low res) +// is the north edge of the block, in _integer arcminutes_. Divide by 60 to get degrees. +// is the west edge of the block, in _positive integer arcminutes_. Divide by 60 to get degrees; subtract 360 if you want the conventional -180..+180 range. +// is the height of the block, in integer arcminutes of latitude +// is the width of the block, in integer arcminutes of longitude +// +// Each block contains 128 evenly spaced bins, in a grid of 32 (longitude) x 4 (latitude), working west-to-east then north-to-south. +// i.e. each bin represents a pixel that covers /32 arcminutes of longitude by /4 arcminutes of latitude. +// +// is a string of 128 digits (no spaces); each character represents the intensity of one bin, in the order above. +// + +// Given: +// bn - block number +// ns - north/south flag +// sf - scale factor +// +// compute the northwest corner of the referenced block and place it in (*latN, *lonW) +// and place the total height and width of the block in (*latSize, *lonSize) +void block_location(int bn, int ns, int sf, double *latN, double *lonW, double *latSize, double *lonSize) +{ + // With SF=0: + + // blocks are (48 arcminutes longitude) x (4 arcminute latitude) between 0 and 60 degrees latitude + // (450 blocks for each ring of latitude) + // blocks are (96 arcminutes longitude) x (4 arcminute latitude) between 60 and 90 degrees latitude + // (225 blocks for each ring of latitude) - but the block numbering continues to use + // a 48-arcminute spacing, so only even numbered blocks are meaningful. + // block zero is immediately northeast of (0,0), then blocks are numbered east-to-west, south-to-north. + // + // Southern hemisphere numbering is mirrored around the equator, and indicated by the "ns" flag. + + // ^N + // | 405446 | 405448 | 405000 | 405002 | + // --------------------------------------------------------- 60 00' 00" N + // |404996|404997|404998|404999|404550|404551|404552|404553| + // --------------------------------------------------------- 59 56' 00" N + // ... + // | 896 | 897 | 898 | 899 | 450 | 451 | 452 | 453 | + // --------------------------------------------------------- 00 04' 00" N + // | 446 | 447 | 447 | 449 | 0 | 1 | 2 | 3 | + //W<------------------------------------------------------->E equator + // | 446* | 447* | 447* | 449* | 0* | 1* | 2* | 3* | + // --------------------------------------------------------- 00 04' 00" S + // | 896* | 897* | 898* | 899* | 450* | 451* | 452* | 453* | + // 2d24'W 1d36'W 0d48'W V 0d48'E 1d36'E 2d24'E + // (* = ns_flag set) + + // Each block is subdivided into 32 (longitude) x 4 (latitude) bins. + // The bins are numbered starting at the northwest corner of the block, + // west-to-east then north-to-south. + + // block 0: + // + // ------------------------------------ <- 0d04m00s N + // | 0 1 2 3 ... 28 29 30 31| <- each bin is 1 arcminute tall + // | 32 33 34 35 ... 60 61 62 63| + // | 64 65 66 67 ... 92 93 94 95| + // | 96 97 98 99 ... 124 125 126 127| + // ------------------------------------ <- 0N - equator + // ^ ^ each bin is ^ + // 0E 1.5 arcminute wide 0d48m00s E + + // With SF=1, an identical block numbering is used to locate the northwest corner of the block, + // but then each bin is 5x larger in both axes i.e. 240 x 20 or 480 x 20 arcminutes. + // this means that the block data will actually overlap 24 other block positions. + + // With SF=2, it works like SF=1 but with a scale factor of 9x. + + double raw_lat, raw_lon; + double scale; + + if (sf == 1) + scale = 5.0; + else if (sf == 2) + scale = 9.0; + else + scale = 1.0; + + if (bn >= BLOCK_THRESHOLD) { + // 60-90 degrees - even-numbered blocks only + bn = bn & ~1; + } + + raw_lat = BLOCK_HEIGHT * trunc(bn / BLOCKS_PER_RING); + raw_lon = (bn % BLOCKS_PER_RING) * BLOCK_WIDTH; + + *lonSize = (bn >= BLOCK_THRESHOLD ? WIDE_BLOCK_WIDTH : BLOCK_WIDTH) * scale; + *latSize = BLOCK_HEIGHT * scale; + + // raw_lat/raw_lon points to the southwest corner in the northern hemisphere version + *lonW = raw_lon; + if (ns) { + // southern hemisphere, mirror along the equator + *latN = 0 - raw_lat; + } else { + // adjust to the northwest corner + *latN = raw_lat + BLOCK_HEIGHT; + } +} + +void decode_nexrad(struct fisb_apdu *fisb) +{ + // Header: + // + // byte/bit 7 6 5 4 3 2 1 0 + // 0 |RLE|NS | Scale | MSB Block # | + // 1 | Block # | + // 2 | Block # LSB | + + + int rle_flag = (fisb->data[0] & 0x80) != 0; + int ns_flag = (fisb->data[0] & 0x40) != 0; + int block_num = ((fisb->data[0] & 0x0f) << 16) | (fisb->data[1] << 8) | (fisb->data[2]); + int scale_factor = (fisb->data[0] & 0x30) >> 4; + + // now decode the bins + if (rle_flag) { + // One bin, 128 values, RLE-encoded + int i; + double latN = 0, lonW = 0, latSize = 0, lonSize = 0; + block_location(block_num, ns_flag, scale_factor, &latN, &lonW, &latSize, &lonSize); + + fprintf(stdout, "NEXRAD %s %02d:%02d %d %.0f %.0f %.0f %.0f ", + fisb->product_id == 63 ? "Regional" : "CONUS", + fisb->hours, + fisb->minutes, + scale_factor, + latN * 60, + lonW * 60, + latSize * 60, + lonSize * 60); + + // each byte following the header is: + // 7 6 5 4 3 2 1 0 + // | runlength - 1 | intensity | + + for (i = 3; i < fisb->length; ++i) { + int intensity = fisb->data[i] & 7; + int runlength = (fisb->data[i] >> 3) + 1; + + while (runlength-- > 0) + fprintf(stdout, "%d", intensity); + } + fprintf(stdout, "\n"); + } else { + int L = fisb->data[3] & 15; + int i; + int row_start, row_offset, row_size; + + // + // Empty block representation, representing one + // or more blocks that are completely empty of + // data. + // + // 7 6 5 4 3 2 1 0 + // 3 |b+4 |b+3 |b+2 |b+1 | length (L) | + // 4 |b+12|b+11|b+10|b+9 |b+8 |b+7 |b+6 |b+5 | + // ... + // 3+L |b+8L-3 ... b+8L+4| + + // The block number from the header is always + // empty. + // + // If the bit for b+x is empty, then the block + // X to the right of the block from the header is + // empty. Note that the block is _always on the + // same row_ even if the offset would make the + // block cross the 0E meridian, so it is not simply + // a case of adding to the block number. + + // find the lowest-numbered block of this row + if (block_num >= 405000) { + row_start = block_num - ((block_num - 405000) % 225); + row_size = 225; + } else { + row_start = block_num - (block_num % 450); + row_size = 450; + } + + // find the offset of the first block in this row handled + // by this message + row_offset = block_num - row_start; + + for (i = 0; i < L; ++i) { + int bb; + int j; + + if (i == 0) + bb = (fisb->data[3] & 0xF0) | 0x08; // synthesize a first byte in the same format as all the other bytes + else + bb = (fisb->data[i+3]); + + for (j = 0; j < 8; ++j) { + if (bb & (1 << j)) { + // find the relevant block for this bit, limited + // to the same row as the original block. + int row_x = (row_offset + 8*i + j - 3) % row_size; + int bn = row_start + row_x; + double latN = 0, lonW = 0, latSize = 0, lonSize = 0; + int k; + block_location(bn, ns_flag, scale_factor, &latN, &lonW, &latSize, &lonSize); + + fprintf(stdout, "NEXRAD %s %02d:%02d %d %.0f %.0f %.0f %.0f ", + fisb->product_id == 63 ? "Regional" : "CONUS", + fisb->hours, + fisb->minutes, + scale_factor, + latN * 60, + lonW * 60, + latSize * 60, + lonSize * 60); + + // seems to work best if we assume that + // CONUS empty blocks = intensity 1 (valid data, but no precipitation) + // regional empty blocks = intensity 0 (valid data <5dBz) + for (k = 0; k < 128; ++k) + fprintf(stdout, "%d", (fisb->product_id == 63 ? 0 : 1)); + fprintf(stdout, "\n"); + } + } + } + } +} + +void handle_frame(frame_type_t type, uint8_t *frame, int len, void *extra) +{ + if (type == UAT_UPLINK) { + struct uat_uplink_mdb mdb; + int i; + + uat_decode_uplink_mdb(frame, &mdb); + if (!mdb.app_data_valid) + return; + + for (i = 0; i < mdb.num_info_frames; ++i) { + struct fisb_apdu *fisb; + + if (!mdb.info_frames[i].is_fisb) + continue; + + fisb = &mdb.info_frames[i].fisb; + if (fisb->product_id != 63 && fisb->product_id != 64) + continue; + + decode_nexrad(fisb); + } + } + + fflush(stdout); +} + +int main(int argc, char **argv) +{ + struct dump978_reader *reader; + int framecount; + + reader = dump978_reader_new(0,0); + if (!reader) { + perror("dump978_reader_new"); + return 1; + } + + while ((framecount = dump978_read_frames(reader, handle_frame, NULL)) > 0) + ; + + if (framecount < 0) { + perror("dump978_read_frames"); + return 1; + } + + return 0; +} + diff --git a/dump978/fec.c b/dump978/fec.c new file mode 100644 index 00000000..db405075 --- /dev/null +++ b/dump978/fec.c @@ -0,0 +1,93 @@ +// +// Copyright 2015, Oliver Jowett +// + +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include +#include +#include + +#include "uat.h" +#include "fec/rs.h" + +static void *rs_uplink; +static void *rs_adsb_short; +static void *rs_adsb_long; + +#define UPLINK_POLY 0x187 +#define ADSB_POLY 0x187 + +void init_fec(void) +{ + rs_adsb_short = init_rs_char(8, /* gfpoly */ ADSB_POLY, /* fcr */ 120, /* prim */ 1, /* nroots */ 12, /* pad */ 225); + rs_adsb_long = init_rs_char(8, /* gfpoly */ ADSB_POLY, /* fcr */ 120, /* prim */ 1, /* nroots */ 14, /* pad */ 207); + rs_uplink = init_rs_char(8, /* gfpoly */ UPLINK_POLY, /* fcr */ 120, /* prim */ 1, /* nroots */ 20, /* pad */ 163); +} + +int correct_adsb_frame(uint8_t *to, int *rs_errors) +{ + // Try decoding as a Long UAT. + // We rely on decode_rs_char not modifying the data if there were + // uncorrectable errors. + int n_corrected = decode_rs_char(rs_adsb_long, to, NULL, 0); + if (n_corrected >= 0 && n_corrected <= 7 && (to[0]>>3) != 0) { + // Valid long frame. + *rs_errors = n_corrected; + return 2; + } + + // Retry as Basic UAT + n_corrected = decode_rs_char(rs_adsb_short, to, NULL, 0); + if (n_corrected >= 0 && n_corrected <= 6 && (to[0]>>3) == 0) { + // Valid short frame + *rs_errors = n_corrected; + return 1; + } + + // Failed. + *rs_errors = 9999; + return -1; +} + +int correct_uplink_frame(uint8_t *from, uint8_t *to, int *rs_errors) +{ + int block; + int total_corrected = 0; + + for (block = 0; block < UPLINK_FRAME_BLOCKS; ++block) { + int i, n_corrected; + uint8_t *blockdata = &to[block * UPLINK_BLOCK_DATA_BYTES]; + + for (i = 0; i < UPLINK_BLOCK_BYTES; ++i) + blockdata[i] = from[i * UPLINK_FRAME_BLOCKS + block]; + + // error-correct in place + n_corrected = decode_rs_char(rs_uplink, blockdata, NULL, 0); + if (n_corrected < 0 || n_corrected > 10) { + // Failed + *rs_errors = 9999; + return -1; + } + + total_corrected += n_corrected; + // next block (if there is one) will overwrite the ECC bytes. + } + + *rs_errors = total_corrected; + return 1; +} diff --git a/dump978/fec.h b/dump978/fec.h new file mode 100644 index 00000000..d327762b --- /dev/null +++ b/dump978/fec.h @@ -0,0 +1,44 @@ +// +// Copyright 2015, Oliver Jowett +// + +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef DUMP978_FEC_H +#define DUMP978_FEC_H + +/* Initialize. Must be called once before correct_* */ +void init_fec(void); + +/* Correct a downlink frame. + * + * 'to' should contain LONG_FRAME_BYTES of data. + * Errors are corrected in-place within 'to'. + * Returns -1 on uncorrectable errors, 1 for a valid basic frame, 2 for a valid long frame. + * Sets *rs_errors to the number of corrected errors, or 9999 if uncorrectable. + */ +int correct_adsb_frame(uint8_t *to, int *rs_errors); + +/* Deinterleave and correct an uplink frame. + * + * 'from' should point to UPLINK_FRAME_BYTES of interleaved input data + * 'to' should point to UPLINK_FRAME_BYTES of space for output data + * (only the first UPLINK_FRAME_DATA_BYTES will contain useful data) + * Blocks are deinterleaved and corrected, and written to 'to'. + * Returns -1 on uncorrectable errors, 1 for a valid uplink frame. + * Sets *rs_errors to the number of corrected errors, or 9999 if uncorrectable. + */ +int correct_uplink_frame(uint8_t *from, uint8_t *to, int *rs_errors); + +#endif diff --git a/dump978/fec/README b/dump978/fec/README new file mode 100644 index 00000000..dd5529c6 --- /dev/null +++ b/dump978/fec/README @@ -0,0 +1,8 @@ +This directory contains just the Reed-Solomon decoder parts +of the fec-3.0.1 library by Phil Karn. + +The full version of the library may be found at +http://www.ka9q.net/code/fec/ + +See README.fec for the original library README and license +information. diff --git a/dump978/fec/README.fec b/dump978/fec/README.fec new file mode 100644 index 00000000..95253e27 --- /dev/null +++ b/dump978/fec/README.fec @@ -0,0 +1,120 @@ +COPYRIGHT + +This package is copyright 2006 by Phil Karn, KA9Q. It may be used +under the terms of the GNU Lesser General Public License (LGPL). See +the file "lesser.txt" in this package for license details. + +INTRODUCTION + +This package provides a set of functions that implement several +popular forward error correction (FEC) algorithms and several low-level routines +useful in modems implemented with digital signal processing (DSP). + +The following routines are provided: + +1. Viterbi decoders for the following convolutional codes: + +r=1/2 k=7 ("Voyager" code, now a widely used industry standard) +r=1/2 k=9 (Used on the IS-95 CDMA forward link) +r=1/6 k=15 ("Cassini" code, used by several NASA/JPL deep space missions) + +2. Reed-Solomon encoders and decoders for any user-specified code. + +3. Optimized encoder and decoder for the CCSDS-standard (255,223) +Reed-Solomon code, with and without the CCSDS-standard "dual basis" +symbol representation. + +4. Compute dot product between a 16-bit buffer and a set of 16-bit +coefficients. This is the basic DSP primitive for digital filtering +and correlation. + +4. Compute sum of squares of a buffer of 16-bit signed integers. This is +useful in DSP for finding the total energy in a signal. + +5. Find peak value in a buffer of 16-bit signed integers, useful for +scaling a signal to prevent overflow. + +SIMD SUPPORT + +This package automatically makes use of various SIMD (Single +Instruction stream, Multiple Data stream) instruction sets, when +available: MMX, SSE and SSE2 on the IA-32 (Intel) architecture, and +Altivec on the PowerPC G4 and G5 used by Power Macintoshes. + +"Altivec" is a Motorola trademark; Apple calls it "Velocity Engine", +and IBM calls it "VMX". Altivec is roughly comparable to SSE2 on the +IA-32. + +Many of the SIMD versions run more than an order of +magnitude faster than their portable C versions. The available SIMD +instruction sets, if any, are determined at run time and the proper +version of each routine is automatically selected. If no SIMD +instructions are available, the portable C version is invoked by +default. On targets other than IA-32 and PPC, only the portable C +version is built. + +The SIMD-assisted versions generally produce the same results as the C +versions, with a few minor exceptions. The Viterbi decoders in C have +a very slightly greater Eb/No performance due to their use of 32-bit +path metrics. On the other hand, the SIMD versions use the +"saturating" arithmetic available in these instructions to avoid the +integer wraparounds that can occur in C when argument ranges are not +properly constrained. This applies primarily to the "dotprod" (dot +product) function. + +The MMX (MultiMedia eXtensions) instruction set was introduced on +later Pentium CPUs; it is also implemented on the Pentium II and most +AMD CPUs starting with the K6. SSE (SIMD Streaming Extensions) was +introduced in the Pentium III; AMD calls it "3D Now! Professional". +Intel introduced SSE2 on the Pentium 4, and it has been picked up by +later AMD CPUs. SSE support implies MMX support, while SSE2 support +implies both SSE and MMX support. + +The latest IA-32 SIMD instruction set, SSE3 (also known as "Prescott +New Instructions") was introduced in early 2004 with the latest +("Prescott") revision of the Pentium 4. Relatively little was +introduced with SSE3, and this library currently makes no use of it. + +See the various manual pages for details on how to use the library +routines. + +Copyright 2006, Phil Karn, KA9Q +karn@ka9q.net +http://www.ka9q.net/ + +This software may be used under the terms of the GNU Lesser General +Public License (LGPL); see the file lesser.txt for details. + +Revision history: +Version 1.0 released 29 May 2001 + +Version 2.0 released 3 Dec 2001: +Restructured to add support for shared libraries. + +Version 2.0.1 released 8 Dec 2001: +Includes autoconf/configure script + +Version 2.0.2 released 4 Feb 2002: +Add SIMD version override options +Test for lack of SSE2 mnemonic support in 'as' +Build only selected version + +Version 2.0.3 released 6 Feb 2002: +Fix to parityb function in parity.h + +feclib version 1.0 released November 2003 +Merged SIMD-Viterbi, RS and DSP libraries +Changed SIMD Viterbi decoder to detect SSE2/SSE/MMX at runtime rather than build time + +feclib version 2.0 (unreleased) Mar 2004 +General speedups and cleanups +Switch from 4 to 8-bit input symbols on all Viterbi decoders +Support for Altivec on PowerPC +Support for k=15 r=1/6 Cassini/Mars Pathfinder/Mars Exploration Rover/STEREO code +Changed license to GNU Lesser General Public License (LGPL) + +feclib version 2.1 June 5 2006 +Added error checking, fixed alignment bug in SSE2 versions of Viterbi decoders causing segfaults + +feclib version 2.1.1 June 6 2006 +Fix test/benchmark time measurement on Linux diff --git a/dump978/fec/char.h b/dump978/fec/char.h new file mode 100644 index 00000000..25efd65b --- /dev/null +++ b/dump978/fec/char.h @@ -0,0 +1,24 @@ +/* Stuff specific to the 8-bit symbol version of the general purpose RS codecs + * + * Copyright 2003, Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ +typedef unsigned char data_t; + +#define MODNN(x) modnn(rs,x) + +#define MM (rs->mm) +#define NN (rs->nn) +#define ALPHA_TO (rs->alpha_to) +#define INDEX_OF (rs->index_of) +#define GENPOLY (rs->genpoly) +#define NROOTS (rs->nroots) +#define FCR (rs->fcr) +#define PRIM (rs->prim) +#define IPRIM (rs->iprim) +#define PAD (rs->pad) +#define A0 (NN) + + + + diff --git a/dump978/fec/decode_rs.h b/dump978/fec/decode_rs.h new file mode 100644 index 00000000..c165cf3d --- /dev/null +++ b/dump978/fec/decode_rs.h @@ -0,0 +1,298 @@ +/* The guts of the Reed-Solomon decoder, meant to be #included + * into a function body with the following typedefs, macros and variables supplied + * according to the code parameters: + + * data_t - a typedef for the data symbol + * data_t data[] - array of NN data and parity symbols to be corrected in place + * retval - an integer lvalue into which the decoder's return code is written + * NROOTS - the number of roots in the RS code generator polynomial, + * which is the same as the number of parity symbols in a block. + Integer variable or literal. + * NN - the total number of symbols in a RS block. Integer variable or literal. + * PAD - the number of pad symbols in a block. Integer variable or literal. + * ALPHA_TO - The address of an array of NN elements to convert Galois field + * elements in index (log) form to polynomial form. Read only. + * INDEX_OF - The address of an array of NN elements to convert Galois field + * elements in polynomial form to index (log) form. Read only. + * MODNN - a function to reduce its argument modulo NN. May be inline or a macro. + * FCR - An integer literal or variable specifying the first consecutive root of the + * Reed-Solomon generator polynomial. Integer variable or literal. + * PRIM - The primitive root of the generator poly. Integer variable or literal. + * DEBUG - If set to 1 or more, do various internal consistency checking. Leave this + * undefined for production code + + * The memset(), memmove(), and memcpy() functions are used. The appropriate header + * file declaring these functions (usually ) must be included by the calling + * program. + */ + + +#if !defined(NROOTS) +#error "NROOTS not defined" +#endif + +#if !defined(NN) +#error "NN not defined" +#endif + +#if !defined(PAD) +#error "PAD not defined" +#endif + +#if !defined(ALPHA_TO) +#error "ALPHA_TO not defined" +#endif + +#if !defined(INDEX_OF) +#error "INDEX_OF not defined" +#endif + +#if !defined(MODNN) +#error "MODNN not defined" +#endif + +#if !defined(FCR) +#error "FCR not defined" +#endif + +#if !defined(PRIM) +#error "PRIM not defined" +#endif + +#if !defined(NULL) +#define NULL ((void *)0) +#endif + +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#undef A0 +#define A0 (NN) + +{ + int deg_lambda, el, deg_omega; + int i, j, r,k; + data_t u,q,tmp,num1,num2,den,discr_r; + data_t lambda[NROOTS+1], s[NROOTS]; /* Err+Eras Locator poly + * and syndrome poly */ + data_t b[NROOTS+1], t[NROOTS+1], omega[NROOTS+1]; + data_t root[NROOTS], reg[NROOTS+1], loc[NROOTS]; + int syn_error, count; + + /* form the syndromes; i.e., evaluate data(x) at roots of g(x) */ + for(i=0;i 0) { + /* Init lambda to be the erasure locator polynomial */ + lambda[1] = ALPHA_TO[MODNN(PRIM*(NN-1-eras_pos[0]))]; + for (i = 1; i < no_eras; i++) { + u = MODNN(PRIM*(NN-1-eras_pos[i])); + for (j = i+1; j > 0; j--) { + tmp = INDEX_OF[lambda[j - 1]]; + if(tmp != A0) + lambda[j] ^= ALPHA_TO[MODNN(u + tmp)]; + } + } + +#if DEBUG >= 1 + /* Test code that verifies the erasure locator polynomial just constructed + Needed only for decoder debugging. */ + + /* find roots of the erasure location polynomial */ + for(i=1;i<=no_eras;i++) + reg[i] = INDEX_OF[lambda[i]]; + + count = 0; + for (i = 1,k=IPRIM-1; i <= NN; i++,k = MODNN(k+IPRIM)) { + q = 1; + for (j = 1; j <= no_eras; j++) + if (reg[j] != A0) { + reg[j] = MODNN(reg[j] + j); + q ^= ALPHA_TO[reg[j]]; + } + if (q != 0) + continue; + /* store root and error location number indices */ + root[count] = i; + loc[count] = k; + count++; + } + if (count != no_eras) { + printf("count = %d no_eras = %d\n lambda(x) is WRONG\n",count,no_eras); + count = -1; + goto finish; + } +#if DEBUG >= 2 + printf("\n Erasure positions as determined by roots of Eras Loc Poly:\n"); + for (i = 0; i < count; i++) + printf("%d ", loc[i]); + printf("\n"); +#endif +#endif + } + for(i=0;i 0; j--){ + if (reg[j] != A0) { + reg[j] = MODNN(reg[j] + j); + q ^= ALPHA_TO[reg[j]]; + } + } + if (q != 0) + continue; /* Not a root */ + /* store root (index-form) and error location number */ +#if DEBUG>=2 + printf("count %d root %d loc %d\n",count,i,k); +#endif + root[count] = i; + loc[count] = k; + /* If we've already found max possible roots, + * abort the search to save time + */ + if(++count == deg_lambda) + break; + } + if (deg_lambda != count) { + /* + * deg(lambda) unequal to number of roots => uncorrectable + * error detected + */ + count = -1; + goto finish; + } + /* + * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo + * x**NROOTS). in index form. Also find deg(omega). + */ + deg_omega = deg_lambda-1; + for (i = 0; i <= deg_omega;i++){ + tmp = 0; + for(j=i;j >= 0; j--){ + if ((s[i - j] != A0) && (lambda[j] != A0)) + tmp ^= ALPHA_TO[MODNN(s[i - j] + lambda[j])]; + } + omega[i] = INDEX_OF[tmp]; + } + + /* + * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = + * inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form + */ + for (j = count-1; j >=0; j--) { + num1 = 0; + for (i = deg_omega; i >= 0; i--) { + if (omega[i] != A0) + num1 ^= ALPHA_TO[MODNN(omega[i] + i * root[j])]; + } + num2 = ALPHA_TO[MODNN(root[j] * (FCR - 1) + NN)]; + den = 0; + + /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ + for (i = MIN(deg_lambda,NROOTS-1) & ~1; i >= 0; i -=2) { + if(lambda[i+1] != A0) + den ^= ALPHA_TO[MODNN(lambda[i+1] + i * root[j])]; + } +#if DEBUG >= 1 + if (den == 0) { + printf("\n ERROR: denominator = 0\n"); + count = -1; + goto finish; + } +#endif + /* Apply error to data */ + if (num1 != 0 && loc[j] >= PAD) { + data[loc[j]-PAD] ^= ALPHA_TO[MODNN(INDEX_OF[num1] + INDEX_OF[num2] + NN - INDEX_OF[den])]; + } + } + finish: + if(eras_pos != NULL){ + for(i=0;i +#endif + +#include + +#include "char.h" +#include "rs-common.h" + +int decode_rs_char(void *p, data_t *data, int *eras_pos, int no_eras){ + int retval; + struct rs *rs = (struct rs *)p; + +#include "decode_rs.h" + + return retval; +} diff --git a/dump978/fec/init_rs.h b/dump978/fec/init_rs.h new file mode 100644 index 00000000..2b2ae98c --- /dev/null +++ b/dump978/fec/init_rs.h @@ -0,0 +1,106 @@ +/* Common code for intializing a Reed-Solomon control block (char or int symbols) + * Copyright 2004 Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ +#undef NULL +#define NULL ((void *)0) + +{ + int i, j, sr,root,iprim; + + rs = NULL; + /* Check parameter ranges */ + if(symsize < 0 || symsize > 8*sizeof(data_t)){ + goto done; + } + + if(fcr < 0 || fcr >= (1<= (1<= (1<= ((1<mm = symsize; + rs->nn = (1<pad = pad; + + rs->alpha_to = (data_t *)malloc(sizeof(data_t)*(rs->nn+1)); + if(rs->alpha_to == NULL){ + free(rs); + rs = NULL; + goto done; + } + rs->index_of = (data_t *)malloc(sizeof(data_t)*(rs->nn+1)); + if(rs->index_of == NULL){ + free(rs->alpha_to); + free(rs); + rs = NULL; + goto done; + } + + /* Generate Galois field lookup tables */ + rs->index_of[0] = A0; /* log(zero) = -inf */ + rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ + sr = 1; + for(i=0;inn;i++){ + rs->index_of[sr] = i; + rs->alpha_to[i] = sr; + sr <<= 1; + if(sr & (1<nn; + } + if(sr != 1){ + /* field generator polynomial is not primitive! */ + free(rs->alpha_to); + free(rs->index_of); + free(rs); + rs = NULL; + goto done; + } + + /* Form RS code generator polynomial from its roots */ + rs->genpoly = (data_t *)malloc(sizeof(data_t)*(nroots+1)); + if(rs->genpoly == NULL){ + free(rs->alpha_to); + free(rs->index_of); + free(rs); + rs = NULL; + goto done; + } + rs->fcr = fcr; + rs->prim = prim; + rs->nroots = nroots; + + /* Find prim-th root of 1, used in decoding */ + for(iprim=1;(iprim % prim) != 0;iprim += rs->nn) + ; + rs->iprim = iprim / prim; + + rs->genpoly[0] = 1; + for (i = 0,root=fcr*prim; i < nroots; i++,root += prim) { + rs->genpoly[i+1] = 1; + + /* Multiply rs->genpoly[] by @**(root + x) */ + for (j = i; j > 0; j--){ + if (rs->genpoly[j] != 0) + rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)]; + else + rs->genpoly[j] = rs->genpoly[j-1]; + } + /* rs->genpoly[0] can never be zero */ + rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)]; + } + /* convert rs->genpoly[] to index form for quicker encoding */ + for (i = 0; i <= nroots; i++) + rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; + done:; + +} diff --git a/dump978/fec/init_rs_char.c b/dump978/fec/init_rs_char.c new file mode 100644 index 00000000..a51099a3 --- /dev/null +++ b/dump978/fec/init_rs_char.c @@ -0,0 +1,35 @@ +/* Initialize a RS codec + * + * Copyright 2002 Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ +#include + +#include "char.h" +#include "rs-common.h" + +void free_rs_char(void *p){ + struct rs *rs = (struct rs *)p; + + free(rs->alpha_to); + free(rs->index_of); + free(rs->genpoly); + free(rs); +} + +/* Initialize a Reed-Solomon codec + * symsize = symbol size, bits + * gfpoly = Field generator polynomial coefficients + * fcr = first root of RS code generator polynomial, index form + * prim = primitive element to generate polynomial roots + * nroots = RS code generator polynomial degree (number of roots) + * pad = padding bytes at front of shortened block + */ +void *init_rs_char(int symsize,int gfpoly,int fcr,int prim, + int nroots,int pad){ + struct rs *rs; + +#include "init_rs.h" + + return rs; +} diff --git a/dump978/fec/lesser.txt b/dump978/fec/lesser.txt new file mode 100644 index 00000000..b1e3f5a2 --- /dev/null +++ b/dump978/fec/lesser.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/dump978/fec/rs-common.h b/dump978/fec/rs-common.h new file mode 100644 index 00000000..e64eb39c --- /dev/null +++ b/dump978/fec/rs-common.h @@ -0,0 +1,26 @@ +/* Stuff common to all the general-purpose Reed-Solomon codecs + * Copyright 2004 Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ + +/* Reed-Solomon codec control block */ +struct rs { + int mm; /* Bits per symbol */ + int nn; /* Symbols per block (= (1<= rs->nn) { + x -= rs->nn; + x = (x >> rs->mm) + (x & rs->nn); + } + return x; +} diff --git a/dump978/fec/rs.h b/dump978/fec/rs.h new file mode 100644 index 00000000..8b5ffeb4 --- /dev/null +++ b/dump978/fec/rs.h @@ -0,0 +1,17 @@ +/* User include file for libfec + * Copyright 2004, Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ + +#ifndef _FEC_RS_H_ +#define _FEC_RS_H_ + +/* General purpose RS codec, 8-bit symbols */ +int decode_rs_char(void *rs,unsigned char *data,int *eras_pos, + int no_eras); +void *init_rs_char(int symsize,int gfpoly, + int fcr,int prim,int nroots, + int pad); +void free_rs_char(void *rs); + +#endif diff --git a/dump978/fec_tests.c b/dump978/fec_tests.c new file mode 100644 index 00000000..f70128cc --- /dev/null +++ b/dump978/fec_tests.c @@ -0,0 +1,217 @@ +// +// Copyright 2015, Oliver Jowett +// + +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include +#include +#include + +#include "uat.h" +#include "fec.h" + +// Test data from DO-282B: +// Table 2-104 "ADS-B Message Reception - Set 1" +// Table 2-105 "ADS-B Message Reception - Set 2" +struct { + const char *testname; + const char *input; + int frametype; + const char *expected; +} downlink_tests[] = { + // Table 2-104 + + { "2-104 #1", "FF8196782DD44238C1453855F89980C7524F5970940ED83AD89CE7A9BEF8761BBCD9FCC817D82E2D1ACF90CA78DA3C49", 1, "007E6987D2D74238C1453855F89980C7524F" }, + { "2-104 #2", "007E6987D328BDC73EBA3955F89980C7524F5970940ED83AD89CE7A9BEF8BB7B190B2EA0EACC7237B7B01B036E07EE04", -1, NULL }, + { "2-104 #3", "007E6987D2D74238C146C7AA07667FC7524F5970940ED83AD89CE7A9BEF8DBE78F0C386EB860D64E9BA9E06B95BEB66A", 1, "007E6987D2D74238C1453855F89980C7524F" }, + { "2-104 #4", "007E6987D2D74238C1453855F8998738ADB0A60F940ED83AD89CE7A9BEF8171F980B40C8B76AC0791254EE04AA73A982", 1, "007E6987D2D74238C1453855F89980C7524F" }, + { "2-104 #5", "007E6987D2D74238C1453855F89980C7524F597F6BF127C5E79CE7A9BEF8B22C710A46621200763F2DF70CBCBFCA1721", 1, "007E6987D2D74238C1453855F89980C7524F" }, + { "2-104 #6", "5A8CAA4ABC7AEE2AD0929EB80DA044D556B452A7A73A5716CD1DB40964F5BA105D9DAAD75342196DEBB63CC972994DEA", 2, "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB40964F5BA105D9D" }, + { "2-104 #7", "A57355B5438412D52F6D61470CA044D556B452A7A73A5716CD1DB40964F5BA105D9DAAD75342196DEBB63CC972994DEA", -1, NULL }, + { "2-104 #8", "A57355B54385ED2AD0929EBBF25FBB2AA94B52A7A73A5716CD1DB40964F5BA105D9DAAD75342196DEBB63CC972994DEA", 2, "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB40964F5BA105D9D" }, + { "2-104 #9", "A57355B54385ED2AD0929EB80DA044D556B3AD5858C5A869CD1DB40964F5BA105D9DAAD75342196DEBB63CC972994DEA", 2, "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB40964F5BA105D9D" }, + { "2-104 #10", "A57355B54385ED2AD0929EB80DA044D556B452A7A73A571932E24BF69BCABA105D9DAAD75342196DEBB63CC972994DEA", 2, "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB40964F5BA105D9D" }, + { "2-104 #11", "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB40964EA45EFA26255C85342196DEBB63CC972994DEA", 2, "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB40964F5BA105D9D" }, + { "2-104 #12", "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB40964F5BA105D9DAAE8ACBDE69214B93CC972994DEA", 2, "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB40964F5BA105D9D" }, + { "2-104 #13", "FFD7E73F83C10D2BF6B961410BEE0C2FD5A4C934E4972E7D17C8495075B2F64260211F48524C5A5590A16E3EAF4068AB", 1, "002818C07CC20D2BF6B961410BEE0C2FD5A4" }, + { "2-104 #14", "002818C07D3DF2D4094660410BEE0C2FD5A4C934E4972E7D17C8495075B26DD7F172B5B56FF8F49067515A8A55D64D5E", -1, NULL }, + { "2-104 #15", "002818C07CC20D2BF6BA9EBEF411F32FD5A4C934E4972E7D17C8495075B2C06F20916457C90BC47D261C8DEFE3ED74C5", 1, "002818C07CC20D2BF6B961410BEE0C2FD5A4" }, + { "2-104 #16", "002818C07CC20D2BF6B961410BEE0BD02A5B364BE4972E7D17C8495075B233CE68A8ABDCF663EAF24B3B2B1369D92C8D", 1, "002818C07CC20D2BF6B961410BEE0C2FD5A4" }, + { "2-104 #17", "002818C07CC20D2BF6B961410BEE0C2FD5A4C93B1B68D18228C8495075B215B194556A7E44E71C724666C96E55DEE72E", 1, "002818C07CC20D2BF6B961410BEE0C2FD5A4" }, + { "2-104 #18", "FF6BD35D16B72B5A4521DB1F0697464369387A5F2C8C9E435F936F4912E17BD726E20847E1E69847CE7504CFE31ACE30", 1, "00942CA2E9B42B5A4521DB1F069746436938" }, + { "2-104 #19", "00942CA2E84BD4A5BADEDA1F0697464369387A5F2C8C9E435F936F4912E1875A6F0477167558D0E3FCE8FC17F176AED4", -1, NULL }, + + { "2-104 #20", "00942CA2E9B42B5A452224E0F968B94369387A5F2C8C9E435F936F4912E13773225FF83FE1D774B684BC1B474D55879A", 1, "00942CA2E9B42B5A4521DB1F069746436938" }, + { "2-104 #21", "00942CA2E9B42B5A4521DB1F069741BC96C785202C8C9E435F936F4912E189CFFBB621E21E555CCC06495B619BDA4ABC", 1, "00942CA2E9B42B5A4521DB1F069746436938" }, + { "2-104 #22", "00942CA2E9B42B5A4521DB1F0697464369387A50D37361BC60936F4912E1263E0AD23CEC55487CDF1D60B972DE252D81", 1, "00942CA2E9B42B5A4521DB1F069746436938" }, + { "2-104 #23", "29E66548CD3A0A4527AB422D6A14598C515810717B02C9C79B79F029CEEE6B4DD1E8F1F77F4CAB43AD74CBB01ECD155C", 2, "D6199AB732C5094527AB422D6A14598C515810717B02C9C79B79F029CEEE6B4DD1E8" }, + { "2-104 #24", "D6199AB732C4F6BAD854BDD26B14598C515810717B02C9C79B79F029CEEE6B4DD1E8F1F77F4CAB43AD74CBB01ECD155C", -1, NULL }, + { "2-104 #25", "D6199AB732C5094527AB422E95EBA673AEA710717B02C9C79B79F029CEEE6B4DD1E8F1F77F4CAB43AD74CBB01ECD155C", 2, "D6199AB732C5094527AB422D6A14598C515810717B02C9C79B79F029CEEE6B4DD1E8" }, + { "2-104 #26", "D6199AB732C5094527AB422D6A14598C515FEF8E84FD36B89B79F029CEEE6B4DD1E8F1F77F4CAB43AD74CBB01ECD155C", 2, "D6199AB732C5094527AB422D6A14598C515810717B02C9C79B79F029CEEE6B4DD1E8" }, + { "2-104 #27", "D6199AB732C5094527AB422D6A14598C515810717B02C9C864860FD631D16B4DD1E8F1F77F4CAB43AD74CBB01ECD155C", 2, "D6199AB732C5094527AB422D6A14598C515810717B02C9C79B79F029CEEE6B4DD1E8" }, + { "2-104 #28", "287F318C2A9FFF7F0784CA4036E252DEB0226A9F9E183CBB933FA68DBF5E1F018F635195DE87894F16BDA55E42A64137", -1, "D780CE73D59CFF7F0784CA4036E252DEB022" }, // basic/long mismatch + { "2-104 #29", "D780CE73D4630080F87BCB4036E252DEB0226A9F9E183CBB933FA68DBF5E161C9333D935A0C83220FC2799D84A139CAB", -1, NULL }, + { "2-104 #30", "157A70631C57176BA3073708854B66FF4083C2AF2D96F628422CDA1A522295B85ED8C94A0DB164A4BEF4B3A402210E02", -1, NULL }, + { "2-104 #31", "773448BE613236BD3F0C53F45DCA5CC6F0B2C0A92F6B6DF3072E0BEFFC8ADAA641EE602AC8B2DDE9A4429FAAB9AFD694", 2, "88CBB7419ECD35BD3F0C53F45DCA5CC6F0B2C0A92F6B6DF3072E0BEFFC8ADAA641EE" }, + { "2-104 #32", "88CBB7419ECCCA42C0F3AC0B5CCA5CC6F0B2C0A92F6B6DF3072E0BEFFC8ADAA641EE602AC8B2DDE9A4429FAAB9AFD694", -1, NULL }, + { "2-104 #33", "88CBB7419ECD35BD3F0C53F7A235A3390F4DC0A92F6B6DF3072E0BEFFC8ADAA641EE602AC8B2DDE9A4429FAAB9AFD694", 2, "88CBB7419ECD35BD3F0C53F45DCA5CC6F0B2C0A92F6B6DF3072E0BEFFC8ADAA641EE" }, + { "2-104 #34", "88CBB7419ECD35BD3F0C53F45DCA5CC6F0B2C0A92F6B6DF3072E0BEFFC8ADAA641EE6015374D22165B4D9FAAB9AFD694", 2, "88CBB7419ECD35BD3F0C53F45DCA5CC6F0B2C0A92F6B6DF3072E0BEFFC8ADAA641EE" }, + { "2-104 #35", "EE62BBECF66420F249ECF8FBC08F8FB3092B6EB4C61D6C264DB6F333BEA5570E0407693351FA9161858E6460DE629EFE", 2, "119D4413099B23F249ECF8FBC08F8FB3092B6EB4C61D6C264DB6F333BEA5570E0407" }, + { "2-104 #36", "119D4413099ADC0DB6130704C18F8FB3092B6EB4C61D6C264DB6F333BEA5570E0407693351FA9161858E6460DE629EFE", -1, NULL }, + { "2-104 #37", "119D4413099B23F249ECF8F83F70704CF6D46EB4C61D6C264DB6F333BEA5570E0407693351FA9161858E6460DE629EFE", 2, "119D4413099B23F249ECF8FBC08F8FB3092B6EB4C61D6C264DB6F333BEA5570E0407" }, + { "2-104 #38", "119D4413099B23F249ECF8FBC08F8FB3092C914B39E293594DB6F333BEA5570E0407693351FA9161858E6460DE629EFE", 2, "119D4413099B23F249ECF8FBC08F8FB3092B6EB4C61D6C264DB6F333BEA5570E0407" }, + { "2-104 #39", "DF784C02075C59B503CFF562E1A553E2B54F40092EF8C22D0BFC643D3C5290867B135A1DB2EEC69474D7F594AC5738D8", -1, "20884C02075C59B503CF0A92E1A553E2B54F" }, // basic/long mismatch + { "2-104 #40", "5A8355B54385ED2AD0929EB80DA044D556B452A758CA5716CD1DB40964F5BA10A26DAAD75342196DEBB63CC972994DEA", 2, "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB40964F5BA105D9D" }, + + { "2-104 #41", "D616654732C5094527AB422D65EBA98C515810717B02C9C794860029CEEE6B4DD1E8F1F77F4CAB43AD74CBB01ECD155C", -1, NULL }, + { "2-104 #42", "AC8857D3318447091F3244DB71A661C7CFC7C4D859F34BD305CED0F22A0FDBE78F0C386EB860D64E9BA9E06B95BEB66A", -1, NULL }, + { "2-104 #43", "F8A60931105731F486BCE2B75D2EBAB00C4FBFEC1C827BEBDDF3BABC5DFC90867BECAA1DB2EEC69474D7F594AC5738D8", 1, "07560931105731F486BCE2B75D2EBA4FFC4F" }, + { "2-104 #44", "87AAEE174F86F7D6F81A6A5DFFE44BBE819127C89004918D683D2DD3608523EFAC727A23A788A711FCA51139D874D5C8", 2, "87AAEE174F86F7D6F8E5955DFFE44BBE816ED8C89004918D683DD22C608523EFAC72" }, + { "2-104 #45", "46D9B1EE2CE9216C8027CE5AE861E20BB742FF717C21B944DED0B164595D7181B14C131EA2DCCDD5B86D46824464BE9A", 2, "46D9B1EE2C16DE6C8027CE5AE89E1D0BB742FF717C21B9BB21D0B164595D7181B14C" }, + { "2-104 #46", "007F9667D2D74238C1453855076980C7524F5970940ED83AD89CE856BEF890867B135A1DB2EEC69474D7F594AC5738D8", -1, NULL }, + + // Table 2-105 + + { "2-105 #1", "007E6987D2D74238C1453855F89980C7524F5970940ED83AD89CE7A9BEF890867B135A1DB2EEC69474D7F594AC5738D8", 1, "007E6987D2D74238C1453855F89980C7524F" }, + { "2-105 #2", "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB4096465BA105D9DAAD75342196DEBB63CC972994DEA", 2, "A57355B54385ED2AD0929EB80DA044D556B452A7A73A5716CD1DB40964F5BA105D9D" }, + { "2-105 #3", "002818C07CC20D2BF6B961410BEE0C2FD5B9C934E4972EED17C8495075B290867B135A1DB2EEC69474D7F594AC5738D8", 1, "002818C07CC20D2BF6B961410BEE0C2FD5A4" }, + { "2-105 #4", "00942CA2E9B42B5A4521DB1F0697FE4369257A5F2C8C9ED35F936F4912E190867B135A1DB2EEC69474D7F594AC5738D8", 1, "00942CA2E9B42B5A4521DB1F069746436938" }, + { "2-105 #5", "8B199AB732C509459FAB422D6A14598C515810717B02C9DA9B79F029CE7E6B4DD1E8F1F77F4CAB43AD74CBB01ECD155C", 2, "D6199AB732C5094527AB422D6A14598C515810717B02C9C79B79F029CEEE6B4DD1E8" }, + { "2-105 #6", "00879223318447F6E03244DB2CA6D93830DADB42E877E27CF095B704326590867B135A1DB2EEC69474D7F594AC5738D8", 1, "0087A823318447F6E03244DB71A6613830C7" }, + { "2-105 #7", "DAAAD4174F86F7D640E5955DFFE44BBE816ED86290049190683DD22C601523EFAC727A23A788A711FCA51139D874D5C8", 2, "87AAEE174F86F7D6F8E5955DFFE44BBE816ED8C89004918D683DD22C608523EFAC72" }, + { "2-105 #8", "1BD98BEE5D16DE6C3827CE5AE89E1D0BB742FFDB7C21B9A621D0B16459CD7181B14C131EA2DCCDD5B86D46824464BE9A", 2, "46D9B1EE2C16DE6C8027CE5AE89E1D0BB742FF717C21B9BB21D0B164595D7181B14C" }, + { "2-105 #9", "D780F473A49C677F0784CA406BE2EADEB03F6A9F9E183C2B9395A68DBF5E90867B135A1DB2EEC69474D7F594AC5738D8", -1, NULL }, + { "2-105 #10", "487A4A636D56E894E4F8C8F7844B66FF4083C2052D96F635DA2CDA1A52B295B85ED8C94A0DE064A4BEF4B3A402210E02", -1, NULL }, + { "2-105 #11", "88CBB7419ECD35BD3F0C53F45DCA5CC6F0B2C0A92F6B6DF3072E0BEFFC8ADAA641EE602AC8B2DDE9A4429FAAB9AFD694", 2, "88CBB7419ECD35BD3F0C53F45DCA5CC6F0B2C0A92F6B6DF3072E0BEFFC8ADAA641EE" }, + { "2-105 #12", "3DC87150293C43E1218BBBB462F06CE1ABE77693C3C24648277112B17B0EC4E774EED67F4A5C486E47E688B2FFA81FAD", 2, "60C84B50293C43E1998BBBB462F06CE1ABE77693C3C24655277112B17B9EC4E774EE" }, + { "2-105 #13", "E4C735D67923E302D648F20F35C66F7027697BF77AE4F6759F5106D592E19E48996C4008A98EC6178BB74D5E05380D73", 2, "B9C70FD67923E3026E48F20F35C66F7027697B5D7AE4F6689F5106D592719E48996C" }, + { "2-105 #14", "53A810655AA6E68A2585D51E07F01003A662B66F98020986F472BD563FD6D8CE9D354AB7E56F1871EF087F2C2DC5810F", -1, NULL }, + { "2-105 #15", "294B0831CCD9B59FB2DB2078C6A796AC873977A0061D2AA5FC057583D39AA1429DFBAC89A4F1A9D94D17D93FB5A982BA", 2, "294B0831CCD9B59FB2DB2078C6A796AC873977A0061D2AB8FC057583D30AA1429DFB" }, + { "2-105 #16", "A30D2EF671D7A56136FCAAEEFA838793C58706661638720E3E2539F280FD5B1CEC3255DB60BA866B7CD2E927188DAC0A", 2, "A30D2EF671D7A5618EFCAAEEFA838793C5870666163872133E2539F2806D5B1CEC32" }, + { "2-105 #17", "1E9297DE55DDFDBC646A63E90E96784012A92DCE42660EC20BC9EC886EE60DC70D7E9B3FA52D17E62E90F4C69EDA07F6", 2, "4392ADDE55DDFDBCDC6A63E90E96784012A92DCE42660EDF0BC9EC886E760DC70D7E" }, + { "2-105 #18", "FE767C70F15A99EE3FC77178FB1EED52BBBDBEFAB8C0F0EC0C1FB1350D363A24A24DEA5F5E955696C56858C093594BF1", 2, "A3764670805A99EE87C77178FB1EED52BBBDBE50B8C0F0F10C1FB1350DA63A24A24D" }, + { "2-105 #19", "62A76B1293A13428872CF1A006431013E1E25D045594026AB07440F966277040AF3B9FD2701B68113F6BCABB38B1AB7C", -1, NULL }, + + { "2-105 #20", "00CB83BC39C90101CD97F84751E2D679CD3ADE7EE8D98C8824BE9F7565B690867B135A1DB2EEC69474D7F594AC5738D8", -1, NULL }, + { "2-105 #21", "BEF0F217696E4652DE8CB92BEC089CFA688E91363C4C627EEDC50B2B3621CD80A407357B9646CF133CDE92807BDE083A", 2, "BEF0F217696E4652DE8CB92BEC089CFA688E91363C4C627EEDC50B2B36B1CD80A407" }, + { "2-105 #22", "25F2846879F474085AD651F61011A07E55B92EF8412B72ED889CCBABD43BE51C2CECE97C250E4F5FAB16CEDAFA5CE238", 2, "25F2846879F474085AD651F61011A07E55B92EF8412B72F0889CCBABD4ABE51C2CEC" }, + { "2-105 #23", "FE8BBEFA469671700F1B9BCE491CE2E7C61DCB02668054401B2D129FBBA808A0C4F18A2B7847A35F56BFE897BFF31E71", 2, "A38B84FA46967170B71B9BCE491CE2E7C61DCBA86680545D1B2D129FBB3808A0C4F1" }, + { "2-105 #24", "B026C4F405E0F6DAF7EEA90134AD7F4C79CC105115F779A696B6FF79665B7D30DA7B0E12E49705C2EFBF64726AF41A65", 2, "ED26FEF474E0F6DA4FEEA90134AD7F4C79CC10FB15F779BB96B6FF7966CB7D30DA7B" }, + { "2-105 #25", "B2F0B85427A6B9B30ADE959C9A3597E429B4C7DB15DDD9C8E9DC9CADC4FBBDB6B7BD2446C0391DA4ACB7D62FF4DE4448", -1, NULL }, + { "2-105 #26", "6B9AC92CCE70667C6FD5D4B316DA46E4B5322A4B4151F651DEDBE85633E19768DE42F879B6BCB73C308D85D5E6EC2834", -1, NULL }, + { "2-105 #27", "07560931105731F486BCE2B75D2EBA4FFC4FBFEC1C827BEBDDF3BABC5DFC90867B135A1DB2EEC69474D7F594AC5738D8", 1, "07560931105731F486BCE2B75D2EBA4FFC4F" }, + { "2-105 #28", "8D264F73C42014181D475A812755061B88B784F2D7E57866DAF007EA4A420D7B5829E103C0E3DF069015893738D26FC1", 2, "8D264F73C42014181D475A812755061B88B784F2D7E57866DAF007EA4AD20D7B5829" }, + { "2-105 #29", "0511EE3AFD5356739D80545F1D978A8E75FFBA8D349CFBD3C19D1852B06690867B135A1DB2EEC69474D7F594AC5738D8", 1, "0511EE3AFD5356739D80545F1D978A8E75E2" }, + { "2-105 #30", "077B559BEFA8DA4D43A0ED6253FA096926C1EBE0899BC38D3A8415367D64756997DB0F53F54227A14229F3654F5FB489", 2, "5A7B6F9BEFA8DA4DFBA0ED6253FA096926C1EBE0899BC3903A8415367DF4756997DB" }, + { "2-105 #31", "14A61CC7D8766A511C98CAF387403156AFB78E35C0840FB82D6A1C4A9EEB8D3493E567AFA9EDDF7B6713935817587985", 2, "49A626C7D8766A51A498CAF387403156AFB78E9FC0840FA52D6A1C4A9E7B8D3493E5" }, + { "2-105 #32", "94032D84D22B86C1E1E2A18859083B9737E7F48C1F477D2951D333795CB75CA801BEFFF271349F7665D670AD62AF2FF3", 2, "C9031784A32B86C159E2A18859083B9737E7F4261F477D3451D333795C275CA801BE" }, + { "2-105 #33", "105B5212705BBE0DE89A8033898EFBA25AA41F9D4880BA066C874BD4AA7B90867B135A1DB2EEC69474D7F594AC5738D8", -1, NULL }, + { "2-105 #34", "0C646844862595EAA54DAD2F2DA5D1481D0DE3828C22F4E2A4D9D15D795D570AE9320018B985B188C1192A60300F0A37", 2, "0C646844862595EAA54DAD2F2DA5D1481D0DE3828C22F4E2A4D9D15D795D570AE932" }, + { "2-105 #35", "71219DC30B25D77EB24D25A1E38811FDEB9C303F0329C26F0DA358E86C327A3EAEBE2D88F3C31EC323F20D43109D7BFF", 2, "71219DC30B25D77EB24D25A1E38811FDEB9C303F0329C2720DA358E86CA27A3EAEBE" }, + { "2-105 #36", "20884C02075C59B503CF0A92E1A5EBE2B55240092EF8C22D0BFC643D3C5290867B135A1DB2EEC69474D7F594AC5738D8", -1, "20884C02075C59B503CF0A92E1A553E2B54F" }, // basic/long mismatch + { "2-105 #37", "041FAD506C2ACF832E6E709A64393C100253641E26C3D92D0F2AA4C38AF490867B135A1DB2EEC69474D7F594AC5738D8", 1, "041F97506C2ACF832E6E709A39398410024E" }, + { "2-105 #38", "00117E82A1E6F9F487423BD32B0765CFC70A65332A820C1B34702487811990867B135A1DB2EEC69474D7F594AC5738D8", -1, NULL }, + { "2-105 #39", "7C69D795C15C7E322415505445A1588476473FF6928382FD43173F9B56A96BACBBF0B5C3868CAEB2856C8AD0E4F31A95", 2, "7C69D795C15C7E322415505445A1588476473FF6928382E043173F9B56396BACBBF0" }, + { "2-105 #40", "E4D3C31F1B25232D4E43F6C3F8368596C5346C919C0960B2EE40492BEEDA77934474F26191E4D8048AB87CA49EE0BE29", 2, "B9D3F91F1B25232DF643F6C3F8368596C5346C3B9C0960AFEE40492BEE4A77934474" }, + + { "2-105 #41", "FE69CC228E786E996CCC8318C9C90F25C6B68F566C74316CB5FDAFCA226E7A19EA66AD963A6327B8A2F49291C22A656C", 2, "A369F622FF786E99D4CC8318C9C90F25C6B68FFC6C743171B5FDAFCA22FE7A19EA66" }, + { "2-105 #42", "008B74BC393401BE9D6A11D93C62435F127541E5D17C2397CCFF49EE043890867B135A1DB2EEC69474D7F594AC5738D8", -1, NULL }, + { "2-105 #43", "00FAE34A81BF609DACB0388BDC818EAAD028684EF3F7A68A856C9BD8CA0F90867B135A1DB2EEC69474D7F594AC5738D8", -1, NULL }, + { "2-105 #44", "0004E865A9FCC6E11D03557BAC9C64852F69D3174482AD9106856918E6D790867B135A1DB2EEC69474D7F594AC5738D8", 1, "0004E865A9FCC6E11D03557BAC9C64852F69" }, + { "2-105 #45", "B509125D72B9D0CDC67016287921F01E0DD52193C1BD8B50DD2A6BBECDEF7A50DDDC73D996CFD227032EB4A1EC746E4C", 2, "B509125D72B9D0CDC67016287921F01E0DD52193C1BD8B50DD2A6BBECD7F7A50DDDC" }, + { "2-105 #46", "E5A6D7BE57EBB83BFA867DA7B6978BB7161F778B63B098D72EE880885E48E6F3240528F8F1230C5537F5EA030243537A", 2, "E5A6D7BE57EBB83BFA867DA7B6978BB7161F778B63B098CA2EE880885ED8E6F32405" }, + { "2-105 #47", "00E7B451B11DAE9750011F73723404D39A5DE2647484ABC7890F0800969890867B135A1DB2EEC69474D7F594AC5738D8", 1, "00E7B451B11DAE9750011F737234BCD39A40" }, + { "2-105 #48", "FD24A99E20ECC8E347D4D46D141FCCC52A6AC35E5CE4B69338BE22E0ECB72F702E49D0EF7142D6675A4E79E2590865AF", 2, "A024939E20ECC8E3FFD4D46D141FCCC52A6AC35E5CE4B68E38BE22E0EC272F702E49" }, + { "2-105 #49", "1F83699AD2D74238C1453855F89980C7524F5C70390ED83AD89CE7A947F8BA105D9D83DADD3FC8DC9842693A4E9D7E63", 1, "007E6987D2D74238C1453855F89980C7524F" }, + { "2-105 #50", "20884C02075C59B503CFC353E1A553E2B54F4009EAF8C20E0BFC64C93C52D0AA219E83E952864A5EE34E5C06B43ED570", -1, "20884C02075C59B503CF0A92E1A553E2B54F" }, // basic/long mismatch + { "2-105 #51", "1F83699AD2D74238C1453855F89980C7524F5C70390ED83AD89CE7A9BEF8BA105D9D83DADD3FC8DC9842693A4E9D7E63", 2, "097E6987D2D74238C1453855F89980C7524F5970940ED83AD89CE7A9BEF8BA105D9D" }, + { "2-105 #52", "20884C02075C59B503CFC392E1A553E2B54F4009EAF8C20E0BFC64C93C52D0AA219E83E952864A5EE34E5C06B43ED58B", 2, "20884C02075C59B503CF0A92E1A553E2B54F40092EF8C2BD0BFC643D3C52D6AA219E" }, + + { NULL, NULL, -1, NULL } +}; + +static int hexbyte(const char *buf) +{ + int i; + char c; + + c = buf[0]; + if (c >= '0' && c <= '9') + i = (c - '0'); + else if (c >= 'a' && c <= 'f') + i = (c - 'a' + 10); + else if (c >= 'A' && c <= 'F') + i = (c - 'A' + 10); + else + return -1; + + i <<= 4; + c = buf[1]; + if (c >= '0' && c <= '9') + return i | (c - '0'); + else if (c >= 'a' && c <= 'f') + return i | (c - 'a' + 10); + else if (c >= 'A' && c <= 'F') + return i | (c - 'A' + 10); + else + return -1; +} + +static void hex_to_bytes(const char *s, uint8_t *to) +{ + for (; *s; s += 2) + *to++ = (uint8_t) hexbyte(s); +} + +int main(int argc, char **argv) +{ + int i; + uint8_t input[LONG_FRAME_BYTES]; + uint8_t expected[LONG_FRAME_DATA_BYTES]; + int all_ok = 1; + + init_fec(); + + for (i = 0; downlink_tests[i].testname; ++i) { + int rs_errors; + int frametype; + int ok = 1; + + fprintf(stderr, "%s: ", downlink_tests[i].testname); + + hex_to_bytes(downlink_tests[i].input, input); + frametype = correct_adsb_frame(input, &rs_errors); + if (frametype != downlink_tests[i].frametype) { + fprintf(stderr, "FAIL: expected frametype %d, got frametype %d\n", downlink_tests[i].frametype, frametype); + ok = 0; + } else if (downlink_tests[i].expected) { + hex_to_bytes(downlink_tests[i].expected, expected); + if (memcmp(expected, input, (frametype == 2) ? LONG_FRAME_DATA_BYTES : SHORT_FRAME_DATA_BYTES) != 0) { + fprintf(stderr, "FAIL: wrong corrected output\n"); + ok = 0; + } + } + + if (ok) + fprintf(stderr, "PASS\n"); + else + all_ok = 0; + } + + return all_ok ? 0 : 1; +} diff --git a/dump978/plot_nexrad.py b/dump978/plot_nexrad.py new file mode 100755 index 00000000..c1818ad8 --- /dev/null +++ b/dump978/plot_nexrad.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python2 + +# +# This takes the output of extract_nexrad and generates images. +# It isn't very smart at the moment and won't draw anything +# until all input has been consumed, so it's not very useful +# for realtime continuous generation of maps. +# + +import sys, math +import cairo, colorsys + +intensities = { + 0: colorsys.hls_to_rgb(240.0/360.0, 0.15, 1.0), + 1: colorsys.hls_to_rgb(240.0/360.0, 0.2, 1.0), + 2: colorsys.hls_to_rgb(200.0/360.0, 0.4, 1.0), + 3: colorsys.hls_to_rgb(160.0/360.0, 0.4, 1.0), + 4: colorsys.hls_to_rgb(120.0/360.0, 0.5, 1.0), + 5: colorsys.hls_to_rgb(80.0/360.0, 0.5, 1.0), + 6: colorsys.hls_to_rgb(40.0/360.0, 0.6, 1.0), + 7: colorsys.hls_to_rgb(0.0/360.0, 0.7, 1.0) +} + +def color_for(intensity): + r,g,b = intensities[intensity] + return cairo.SolidPattern(r,g,b,1.0) + +# mercator projection (yeah, it's not great, but it's simple) +# lat, lon are in _arcminutes_ +def project(lat,lon): + lat /= 60.0 + lat = math.pi * lat / 180.0 + + lon /= 60.0 + lon -= 360.0 + lon = math.pi * lon / 180.0 + + x = lon + y = math.log(math.tan(math.pi/4.0 + lat/2.0)) + + return (x,y) + +images = {} + +while True: + line = sys.stdin.readline() + if not line: break + + words = line.strip().split(' ') + if words[0] != 'NEXRAD': continue + + nexrad, maptype, maptime, sf, latN, lonW, latSize, lonSize, blockdata = words + sf = int(sf) + latN = int(latN) + lonW = int(lonW) + latSize = int(latSize) + lonSize = int(lonSize) + + key = maptype + '/' + maptime + if not key in images: + images[key] = { + 'type' : maptype, + 'time' : maptime, + 'lat_min' : latN - latSize, + 'lat_max' : latN, + 'lon_min' : lonW, + 'lon_max' : lonW + lonSize, + 'blocks' : { + sf : [ (latN, lonW, latSize, lonSize, blockdata) ] + } + } + else: + image = images[key] + image['lat_min'] = min(image['lat_min'], latN - latSize) + image['lat_max'] = max(image['lat_max'], latN) + image['lon_min'] = min(image['lon_min'], lonW) + image['lon_max'] = max(image['lon_max'], lonW + lonSize) + + if not sf in image['blocks']: + image['blocks'][sf] = [ (latN, lonW, latSize, lonSize, blockdata) ] + else: + image['blocks'][sf].append( (latN, lonW, latSize, lonSize, blockdata) ) + +for image in images.values(): + lat_min = image['lat_min'] + lat_max = image['lat_max'] + lon_min = image['lon_min'] + lon_max = image['lon_max'] + + # find most detailed scale; scale our image accordingly + sf = min(image['blocks'].keys()) + if sf == 1: scale = 5.0 + elif sf == 2: scale = 9.0 + else: scale = 1.0 + pixels_per_degree = 80.0 / scale + + # project, find scale + x0,y0 = project(lat_min,lon_min) + x1,y1 = project(lat_min,lon_max) + x2,y2 = project(lat_max,lon_min) + x3,y3 = project(lat_max,lon_max) + xmin = min(x0,x1,x2,x3) + xmax = max(x0,x1,x2,x3) + ymin = min(y0,y1,y2,y3) + ymax = max(y0,y1,y2,y3) + + xsize = int(pixels_per_degree * 180.0 * (xmax - xmin) / math.pi) + ysize = int(pixels_per_degree * 180.0 * (ymax - ymin) / math.pi) + + print image['type'], image['time'], 'dimensions', xsize, ysize, 'layers', len(image['blocks']) + + surface = cairo.ImageSurface(cairo.FORMAT_RGB24, xsize, ysize) + cc = cairo.Context(surface) + cc.set_antialias(cairo.ANTIALIAS_NONE) + + cc.scale(1.0 * xsize / (xmax - xmin), -1.0 * ysize / (ymax - ymin)) + cc.translate(-xmin, -ymax) + + if image['type'] == 'CONUS': + cc.set_source(color_for(0)) + else: + r,g,b = colorsys.hls_to_rgb(270.0/360.0, 0.10, 1.0) + cc.set_source(cairo.SolidPattern(r,g,b,1.0)) + + cc.paint() + + for sf in sorted(image['blocks'].keys(), reverse=True): # lowest res first + for latN, lonW, latSize, lonSize, data in image['blocks'][sf]: + for y in xrange(4): + for x in xrange(32): + intensity = int(data[x+y*32]) + lat = latN - y * latSize / 4.0 + lon = lonW + x * lonSize / 32.0 + + cc.move_to(*project(lat,lon)) + cc.line_to(*project(lat-latSize/4.0,lon)) + cc.line_to(*project(lat-latSize/4.0,lon+lonSize/32.0)) + cc.line_to(*project(lat,lon+lonSize/32.0)) + cc.close_path() + cc.set_source(color_for(intensity)) + cc.fill() + + surface.write_to_png('nexrad_%s_%s.png' % (image['type'], image['time'])) + + + diff --git a/dump978/reader.c b/dump978/reader.c new file mode 100644 index 00000000..ace98f76 --- /dev/null +++ b/dump978/reader.c @@ -0,0 +1,199 @@ +// +// Copyright 2015, Oliver Jowett +// + +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include +#include + +#include "uat.h" +#include "reader.h" + +struct dump978_reader { + int fd; + char buf[4096]; + uint8_t frame[UPLINK_FRAME_DATA_BYTES]; // max uplink frame size + int used; +}; + +static int process_input(struct dump978_reader *reader, frame_handler_t handler, void *handler_data); +static int process_line(struct dump978_reader *reader, frame_handler_t handler, void *handler_data, char *p, char *end); +static int hexbyte(char *buf); + +struct dump978_reader *dump978_reader_new(int fd, int nonblock) +{ + struct dump978_reader *reader = calloc(1, sizeof(*reader)); + if (!reader) + return NULL; + + if (nonblock) { + int flags = fcntl(fd, F_GETFL); + if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + int save_errno = errno; + free(reader); + errno = save_errno; + return NULL; + } + } + + reader->fd = fd; + reader->used = 0; + return reader; +} + +int dump978_read_frames(struct dump978_reader *reader, + frame_handler_t handler, + void *handler_data) +{ + int framecount = 0; + ssize_t bytes_read; + + if (!reader) { + errno = EINVAL; + return -1; + } + + for (;;) { + if (reader->used == sizeof(reader->buf)) { + // line too long, ditch input + reader->used = 0; + } + + bytes_read = read(reader->fd, + reader->buf + reader->used, + sizeof(reader->buf) - reader->used); + if (bytes_read <= 0) + break; + + reader->used += bytes_read; + + framecount += process_input(reader, handler, handler_data); + } + + if (bytes_read == 0) + return framecount; // EOF + + // only report EAGAIN et al if no frames were read + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return (framecount > 0 ? framecount : -1); + + return -1; // propagate unexpected error +} + +void dump978_reader_free(struct dump978_reader *reader) +{ + if (!reader) + return; + + free(reader); +} + +static int process_input(struct dump978_reader *reader, frame_handler_t handler, void *handler_data) +{ + char *p = reader->buf; + char *end = reader->buf + reader->used; + int framecount = 0; + + while (p < end) { + char *newline; + + newline = memchr(p, '\n', end - p); + if (newline == NULL) + break; + + if (*p == '-' || *p == '+') + framecount += process_line(reader, handler, handler_data, p, newline); + + p = newline+1; + } + + if (p >= end) { + reader->used = 0; + } else { + reader->used = end - p; + memmove(reader->buf, p, reader->used); + } + + return framecount; +} + +static int process_line(struct dump978_reader *reader, frame_handler_t handler, void *handler_data, char *p, char *end) +{ + uint8_t *out; + int len = 0; + frame_type_t frametype; + + if (*p == '-') + frametype = UAT_DOWNLINK; + else if (*p == '+') + frametype = UAT_UPLINK; + else + return 0; + + out = reader->frame; + ++p; + while (p < end) { + int byte; + + if (p[0] == ';') { + // ignore rest of line + handler(frametype, reader->frame, len, handler_data); + return 1; + } + + if (len >= sizeof(reader->frame)) + return 0; // oversized frame + + byte = hexbyte(p); + if (byte < 0) + return 0; // badly formatted byte + + ++len; + *out++ = byte; + p += 2; + } + + return 0; // ran off the end without seeing semicolon +} + +static int hexbyte(char *buf) +{ + int i; + char c; + + c = buf[0]; + if (c >= '0' && c <= '9') + i = (c - '0'); + else if (c >= 'a' && c <= 'f') + i = (c - 'a' + 10); + else if (c >= 'A' && c <= 'F') + i = (c - 'A' + 10); + else + return -1; + + i <<= 4; + c = buf[1]; + if (c >= '0' && c <= '9') + return i | (c - '0'); + else if (c >= 'a' && c <= 'f') + return i | (c - 'a' + 10); + else if (c >= 'A' && c <= 'F') + return i | (c - 'A' + 10); + else + return -1; +} diff --git a/dump978/reader.h b/dump978/reader.h new file mode 100644 index 00000000..8c74d268 --- /dev/null +++ b/dump978/reader.h @@ -0,0 +1,63 @@ +// +// Copyright 2015, Oliver Jowett +// + +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef DUMP978_READER_H +#define DUMP978_READER_H + +#include + +struct dump978_reader; + +typedef enum { UAT_UPLINK, UAT_DOWNLINK } frame_type_t; + +// Function pointer type for a handler called by dump978_read_frames(). +// It is called with arguments: +// t: frame type (UAT_UPLINK or UAT_DOWNLINK) +// f: pointer to frame data buffer +// l: length of frame data +// d: value of handler_data passed to dump978_read_frames +// The frame data buffer is a shared buffer owned by the caller +// and may be reused after return; if the handler needs to +// preserve the data after returning, it should take a copy. +typedef void (*frame_handler_t)(frame_type_t t,uint8_t *f,int l,void *d); + +// Allocate a new reader that reads from file descriptor 'fd'. +// If 'nonblock' is nonzero, the FD will be made nonblocking. +// Returns the reader, or NULL on error with errno set. +struct dump978_reader *dump978_reader_new(int fd, int nonblock); + +// Free a reader previously created by dump978_reader_new. +// Does not close the underlying file descriptor. +void dump978_reader_free(struct dump978_reader *reader); + +// Read frames from the given reader. +// Pass complete frames to 'handler', passing 'handler_data' +// as the 4th argument. +// +// Returns a positive number of frames read on success. +// Returns 0 on EOF +// Returns <0 on error with errno set. +// If the underlying FD is nonblocking and no frames are +// available, returns <0 with errno = EAGAIN/EINTR/EWOULDBLOCK. +int dump978_read_frames(struct dump978_reader *reader, + frame_handler_t handler, + void *handler_data); + +#endif + + + diff --git a/dump978/sample-data.txt.gz b/dump978/sample-data.txt.gz new file mode 100644 index 00000000..3478f301 Binary files /dev/null and b/dump978/sample-data.txt.gz differ diff --git a/dump978/uat.h b/dump978/uat.h new file mode 100644 index 00000000..f9da5215 --- /dev/null +++ b/dump978/uat.h @@ -0,0 +1,44 @@ +// Part of dump978, a UAT decoder. +// +// Copyright 2015, Oliver Jowett +// +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef UAT_H +#define UAT_H + +// Frame size constants + +#define SHORT_FRAME_DATA_BITS (144) +#define SHORT_FRAME_BITS (SHORT_FRAME_DATA_BITS+96) +#define SHORT_FRAME_DATA_BYTES (SHORT_FRAME_DATA_BITS/8) +#define SHORT_FRAME_BYTES (SHORT_FRAME_BITS/8) + +#define LONG_FRAME_DATA_BITS (272) +#define LONG_FRAME_BITS (LONG_FRAME_DATA_BITS+112) +#define LONG_FRAME_DATA_BYTES (LONG_FRAME_DATA_BITS/8) +#define LONG_FRAME_BYTES (LONG_FRAME_BITS/8) + +#define UPLINK_BLOCK_DATA_BITS (576) +#define UPLINK_BLOCK_BITS (UPLINK_BLOCK_DATA_BITS+160) +#define UPLINK_BLOCK_DATA_BYTES (UPLINK_BLOCK_DATA_BITS/8) +#define UPLINK_BLOCK_BYTES (UPLINK_BLOCK_BITS/8) + +#define UPLINK_FRAME_BLOCKS (6) +#define UPLINK_FRAME_DATA_BITS (UPLINK_FRAME_BLOCKS * UPLINK_BLOCK_DATA_BITS) +#define UPLINK_FRAME_BITS (UPLINK_FRAME_BLOCKS * UPLINK_BLOCK_BITS) +#define UPLINK_FRAME_DATA_BYTES (UPLINK_FRAME_DATA_BITS/8) +#define UPLINK_FRAME_BYTES (UPLINK_FRAME_BITS/8) + +#endif diff --git a/dump978/uat2esnt.c b/dump978/uat2esnt.c new file mode 100644 index 00000000..a92a3002 --- /dev/null +++ b/dump978/uat2esnt.c @@ -0,0 +1,655 @@ +// +// Copyright 2015, Oliver Jowett +// + +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include + +#include "uat.h" +#include "uat_decode.h" +#include "reader.h" + +static void checksum_and_send(uint8_t *frame, int len, uint32_t parity); + +// If you call this with constants for firstbit/lastbit +// gcc will do a pretty good job of crunching it down +// to just a couple of operations. Even more so if value +// is also constant. +static inline void setbits(uint8_t *frame, unsigned firstbit, unsigned lastbit, uint32_t value) +{ + // convert to 0-based: + unsigned lb = lastbit-1; + + // align value with byte layout: + unsigned offset = 7 - (lb&7); + unsigned nb = (lastbit - firstbit + 1 + offset); + uint32_t mask = (1 << (lastbit-firstbit+1))-1; + uint32_t imask = ~(mask << offset); + uint32_t aligned = (value & mask) << offset; + + frame[lb >> 3] = (frame[lb >> 3] & imask) | aligned; + if (nb > 8) + frame[(lb >> 3) - 1] = (frame[(lb >> 3) - 1] & (imask >> 8)) | (aligned >> 8); + if (nb > 16) + frame[(lb >> 3) - 2] = (frame[(lb >> 3) - 2] & (imask >> 16)) | (aligned >> 16); + if (nb > 24) + frame[(lb >> 3) - 3] = (frame[(lb >> 3) - 3] & (imask >> 24)) | (aligned >> 24); +} + +static int encode_altitude(int ft) +{ + int i; + + i = (ft + 1000) / 25; + if (i < 0) i = 0; + if (i > 0x7FF) i = 0x7FF; + + return (i & 0x000F) | 0x0010 | ((i & 0x07F0) << 1); +} + +static int encode_ground_speed(int kt) +{ + if (kt > 175) + return 124; + if (kt > 100) + return (kt - 100) / 5 + 108; + if (kt > 70) + return (kt - 70) / 2 + 93; + if (kt > 15) + return (kt - 15) + 38; + if (kt > 2) + return (kt - 2) * 2 + 11; + if (kt == 2) + return 12; + if (kt == 1) + return 8; + return 1; +} + +static int encode_air_speed(int kt, int supersonic) +{ + int sign; + + if (kt < 0) { + sign = 0x0400; + kt = -kt; + } else { + sign = 0; + } + + if (supersonic) + kt = kt / 4; + + ++kt; + if (kt > 1023) + kt = 1023; + + return kt | sign; +} + +static int encode_vert_rate(int rate) +{ + int sign; + + if (rate < 0) { + sign = 0x200; + rate = -rate; + } else { + sign = 0; + } + + rate = (rate / 64) + 1; + if (rate > 511) + rate = 511; + + return rate | sign; +} + +static double cprMod(double a, double b) { + double res = fmod(a, b); + if (res < 0) res += b; + return res; +} + +static int cprNL(double lat) +{ + if (lat < 0) lat = -lat; + if (lat < 10.47047130) return 59; + if (lat < 14.82817437) return 58; + if (lat < 18.18626357) return 57; + if (lat < 21.02939493) return 56; + if (lat < 23.54504487) return 55; + if (lat < 25.82924707) return 54; + if (lat < 27.93898710) return 53; + if (lat < 29.91135686) return 52; + if (lat < 31.77209708) return 51; + if (lat < 33.53993436) return 50; + if (lat < 35.22899598) return 49; + if (lat < 36.85025108) return 48; + if (lat < 38.41241892) return 47; + if (lat < 39.92256684) return 46; + if (lat < 41.38651832) return 45; + if (lat < 42.80914012) return 44; + if (lat < 44.19454951) return 43; + if (lat < 45.54626723) return 42; + if (lat < 46.86733252) return 41; + if (lat < 48.16039128) return 40; + if (lat < 49.42776439) return 39; + if (lat < 50.67150166) return 38; + if (lat < 51.89342469) return 37; + if (lat < 53.09516153) return 36; + if (lat < 54.27817472) return 35; + if (lat < 55.44378444) return 34; + if (lat < 56.59318756) return 33; + if (lat < 57.72747354) return 32; + if (lat < 58.84763776) return 31; + if (lat < 59.95459277) return 30; + if (lat < 61.04917774) return 29; + if (lat < 62.13216659) return 28; + if (lat < 63.20427479) return 27; + if (lat < 64.26616523) return 26; + if (lat < 65.31845310) return 25; + if (lat < 66.36171008) return 24; + if (lat < 67.39646774) return 23; + if (lat < 68.42322022) return 22; + if (lat < 69.44242631) return 21; + if (lat < 70.45451075) return 20; + if (lat < 71.45986473) return 19; + if (lat < 72.45884545) return 18; + if (lat < 73.45177442) return 17; + if (lat < 74.43893416) return 16; + if (lat < 75.42056257) return 15; + if (lat < 76.39684391) return 14; + if (lat < 77.36789461) return 13; + if (lat < 78.33374083) return 12; + if (lat < 79.29428225) return 11; + if (lat < 80.24923213) return 10; + if (lat < 81.19801349) return 9; + if (lat < 82.13956981) return 8; + if (lat < 83.07199445) return 7; + if (lat < 83.99173563) return 6; + if (lat < 84.89166191) return 5; + if (lat < 85.75541621) return 4; + if (lat < 86.53536998) return 3; + if (lat < 87.00000000) return 2; + else return 1; +} + +static int cprN(double lat, int odd) +{ + int nl = cprNL(lat) - (odd ? 1 : 0); + if (nl < 1) + nl = 1; + return nl; +} + +static int encode_cpr_lat(double lat, double lon, int odd, int surface) +{ + int NbPow = (surface ? 1<<19 : 1<<17); + + double Dlat = 360.0 / (odd ? 59 : 60); + int YZ = floor(NbPow * cprMod(lat, Dlat) / Dlat + 0.5); + + return YZ & 0x1FFFF; // always a 17-bit field +} + +static int encode_cpr_lon(double lat, double lon, int odd, int surface) +{ + int NbPow = (surface ? 1<<19 : 1<<17); + + double Dlat = 360.0 / (odd ? 59 : 60); + int YZ = floor(NbPow * cprMod(lat, Dlat) / Dlat + 0.5); + + double Rlat = Dlat * (1.0 * YZ / NbPow + floor(lat / Dlat)); + double Dlon = (360.0 / cprN(Rlat, odd)); + int XZ = floor(NbPow * cprMod(lon, Dlon) / Dlon + 0.5); + + return XZ & 0x1FFFF; // always a 17-bit field +} + +static int encode_imf(struct uat_adsb_mdb *mdb) +{ + // Encode the IMF bit for DF 18; this is 0 if the address + // is a regular 24-bit ICAO address, or 1 if it uses a + // different format. + switch (mdb->address_qualifier) { + case AQ_ADSB_ICAO: + case AQ_TISB_ICAO: + return 0; + + default: + return 1; + } +} + +static void send_altitude_only(struct uat_adsb_mdb *mdb) +{ + uint8_t esnt_frame[14]; + int raw_alt; + + // Need barometric altitude, see if we have it + if (mdb->altitude_type == ALT_BARO) { + raw_alt = encode_altitude(mdb->altitude); + } else if (mdb->sec_altitude_type == ALT_BARO) { + raw_alt = encode_altitude(mdb->sec_altitude); + } else { + raw_alt = 0; + } + + setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT + setbits(esnt_frame, 6, 8, 6); // CF=6, ADS-R + setbits(esnt_frame, 9, 32, mdb->address); // AA + + // ES: + setbits(esnt_frame+4, 1, 5, 0); // FORMAT TYPE CODE = 0, barometric altitude with no position + setbits(esnt_frame+4, 6, 7, 0); // SURVEILLANCE STATUS normal + setbits(esnt_frame+4, 8, 8, encode_imf(mdb)); // IMF + setbits(esnt_frame+4, 9, 20, raw_alt); // ALTITUDE + setbits(esnt_frame+4, 21, 21, 0); // TIME (T) + setbits(esnt_frame+4, 22, 22, 0); // CPR FORMAT (F) + setbits(esnt_frame+4, 23, 39, 0); // ENCODED LATITUDE + setbits(esnt_frame+4, 40, 56, 0); // ENCODED LONGITUDE + + checksum_and_send(esnt_frame, 14, 0); +} + +static void maybe_send_surface_position(struct uat_adsb_mdb *mdb) +{ + uint8_t esnt_frame[14]; + + if (mdb->airground_state != AG_GROUND) + return; // nope! + + setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT + setbits(esnt_frame, 6, 8, 6); // CF=6, ADS-R + setbits(esnt_frame, 9, 32, mdb->address); // AA + + setbits(esnt_frame+4, 1, 5, 8); // FORMAT TYPE CODE = 8, surface position (NUCp=6) + + if (!mdb->speed_valid) { + setbits(esnt_frame+4, 6, 12, 0); // MOVEMENT: invalid + } else { + setbits(esnt_frame+4, 6, 12, encode_ground_speed(mdb->speed)); // MOVEMENT + } + + if (mdb->track_type != TT_TRACK) { + setbits(esnt_frame+4, 13, 13, 0); // STATUS for ground track: invalid + setbits(esnt_frame+4, 14, 20, 0); // GROUND TRACK (TRUE) + } else { + setbits(esnt_frame+4, 13, 13, 1); // STATUS for ground track: valid + setbits(esnt_frame+4, 14, 20, mdb->track * 128 / 360); // GROUND TRACK (TRUE) + } + + setbits(esnt_frame+4, 21, 21, encode_imf(mdb)); // IMF + + // even frame: + setbits(esnt_frame+4, 22, 22, 0); // CPR FORMAT (F) = even + setbits(esnt_frame+4, 23, 39, encode_cpr_lat(mdb->lat, mdb->lon, 0, 1)); // ENCODED LATITUDE + setbits(esnt_frame+4, 40, 56, encode_cpr_lon(mdb->lat, mdb->lon, 0, 1)); // ENCODED LONGITUDE + checksum_and_send(esnt_frame, 14, 0); + + // odd frame: + setbits(esnt_frame+4, 22, 22, 1); // CPR FORMAT (F) = odd + setbits(esnt_frame+4, 23, 39, encode_cpr_lat(mdb->lat, mdb->lon, 1, 1)); // ENCODED LATITUDE + setbits(esnt_frame+4, 40, 56, encode_cpr_lon(mdb->lat, mdb->lon, 1, 1)); // ENCODED LONGITUDE + checksum_and_send(esnt_frame, 14, 0); +} + +static void maybe_send_air_position(struct uat_adsb_mdb *mdb) +{ + uint8_t esnt_frame[14]; + int raw_alt; + + if (mdb->airground_state != AG_SUPERSONIC && mdb->airground_state != AG_SUBSONIC) + return; // nope! + + if (!mdb->position_valid) { + send_altitude_only(mdb); + return; + } + + setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT + setbits(esnt_frame, 6, 8, 6); // CF=6, ADS-R + setbits(esnt_frame, 9, 32, mdb->address); // AA + + // decide on a metype + switch (mdb->altitude_type) { + case ALT_BARO: + setbits(esnt_frame+4, 1, 5, 18); // FORMAT TYPE CODE = 18, airborne position (baro alt) + raw_alt = encode_altitude(mdb->altitude); + break; + + case ALT_GEO: + setbits(esnt_frame+4, 1, 5, 22); // FORMAT TYPE CODE = 22, airborne position (GNSS alt) + raw_alt = encode_altitude(mdb->altitude); + break; + + default: + setbits(esnt_frame+4, 1, 5, 18); // FORMAT TYPE CODE = 18, airborne position (baro alt) + raw_alt = 0; // unavailable + break; + } + + setbits(esnt_frame+4, 6, 7, 0); // SURVEILLANCE STATUS normal + setbits(esnt_frame+4, 8, 8, encode_imf(mdb)); // IMF + setbits(esnt_frame+4, 9, 20, raw_alt); // ALTITUDE + setbits(esnt_frame+4, 21, 21, 0); // TIME (T) + + // even frame: + setbits(esnt_frame+4, 22, 22, 0); // CPR FORMAT (F) - even + setbits(esnt_frame+4, 23, 39, encode_cpr_lat(mdb->lat, mdb->lon, 0, 0)); // ENCODED LATITUDE + setbits(esnt_frame+4, 40, 56, encode_cpr_lon(mdb->lat, mdb->lon, 0, 0)); // ENCODED LONGITUDE + checksum_and_send(esnt_frame, 14, 0); + + // odd frame: + setbits(esnt_frame+4, 22, 22, 1); // CPR FORMAT (F) - odd + setbits(esnt_frame+4, 23, 39, encode_cpr_lat(mdb->lat, mdb->lon, 1, 0)); // ENCODED LATITUDE + setbits(esnt_frame+4, 40, 56, encode_cpr_lon(mdb->lat, mdb->lon, 1, 0)); // ENCODED LONGITUDE + checksum_and_send(esnt_frame, 14, 0); +} + +static void maybe_send_air_velocity(struct uat_adsb_mdb *mdb) +{ + uint8_t esnt_frame[14]; + int supersonic; + + if (mdb->airground_state != AG_SUPERSONIC && mdb->airground_state != AG_SUBSONIC) + return; // nope! + + if (!mdb->ew_vel_valid && !mdb->ns_vel_valid && mdb->vert_rate_source == ALT_INVALID) { + // not really any point sending this + return; + } + + setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT + setbits(esnt_frame, 6, 8, 6); // CF=6, ADS-R + setbits(esnt_frame, 9, 32, mdb->address); // AA + + supersonic = (mdb->airground_state == AG_SUPERSONIC); + setbits(esnt_frame+4, 1, 5, 19); // FORMAT TYPE CODE = 19, airborne velocity + if (supersonic) + setbits(esnt_frame+4, 6, 8, 2); // SUBTYPE = 2, supersonic, speed over ground + else + setbits(esnt_frame+4, 6, 8, 1); // SUBTYPE = 1, subsonic, speed over ground + + setbits(esnt_frame+4, 9, 9, encode_imf(mdb)); // IMF + setbits(esnt_frame+4, 10, 10, 0); // IFR + setbits(esnt_frame+4, 11, 13, 0); // NAVIGATIONAL UNCERTAINTY CATEGORY FOR VELOCITY + + // EAST/WEST DIRECTION BIT + EAST/WEST VELOCITY + if (!mdb->ew_vel_valid) + setbits(esnt_frame+4, 14, 24, 0); + else + setbits(esnt_frame+4, 14, 24, encode_air_speed(mdb->ew_vel, supersonic)); + + // NORTH/SOUTH DIRECTION BIT + NORTH/SOUTH VELOCITY + if (!mdb->ns_vel_valid) + setbits(esnt_frame+4, 25, 35, 0); + else + setbits(esnt_frame+4, 25, 35, encode_air_speed(mdb->ns_vel, supersonic)); + + switch (mdb->vert_rate_source) { + case ALT_BARO: + setbits(esnt_frame+4, 36, 36, 0); // SOURCE = BARO + setbits(esnt_frame+4, 37, 46, encode_vert_rate(mdb->vert_rate)); // SIGN BIT FOR VERTICAL RATE + VERTICAL RATE + break; + + case ALT_GEO: + setbits(esnt_frame+4, 36, 36, 1); // SOURCE = GNSS + setbits(esnt_frame+4, 37, 46, encode_vert_rate(mdb->vert_rate)); // SIGN BIT FOR VERTICAL RATE + VERTICAL RATE + break; + + default: + setbits(esnt_frame+4, 36, 36, 0); // SOURCE = BARO + setbits(esnt_frame+4, 37, 46, 0); // SIGN BIT FOR VERTICAL RATE + VERTICAL RATE = 0, no information + break; + } + + setbits(esnt_frame+4, 47, 48, 0); // RESERVED FOR TURN INDICATOR + + if (mdb->altitude_type != ALT_INVALID && mdb->sec_altitude_type != ALT_INVALID) { + int delta, sign; + + if (mdb->altitude < mdb->sec_altitude) { // secondary above primary + delta = mdb->sec_altitude - mdb->altitude; + sign = mdb->altitude_type == ALT_BARO ? 0 : 1; + } else { // primary above secondary + delta = mdb->altitude - mdb->sec_altitude; + sign = mdb->altitude_type == ALT_BARO ? 1 : 0; + } + + delta = delta / 25 + 1; + if (delta >= 127) delta = 127; + setbits(esnt_frame+4, 49, 49, sign); // DIFFERENCE SIGN BIT + setbits(esnt_frame+4, 50, 56, delta); // GNSS ALT DIFFERENCE FROM BARO ALT + } else { + setbits(esnt_frame+4, 49, 49, 0); // DIFFERENCE SIGN BIT + setbits(esnt_frame+4, 50, 56, 0); // GNSS ALT DIFFERENCE FROM BARO ALT = 0, no information + } + + checksum_and_send(esnt_frame, 14, 0); +} + +// yeah, this could be done with a lookup table, meh. +static char *ais_charset = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"; +static uint8_t char_to_ais(int ch) +{ + char *match; + if (!ch) + return 32; + + match = strchr(ais_charset, ch); + if (match) + return (uint8_t)(match - ais_charset); + else + return 32; +} + +static unsigned encodeSquawk(char *squawkStr) +{ + unsigned squawk = strtoul(squawkStr, NULL, 16); + unsigned encoded = 0; + + if (squawk & 0x1000) encoded |= 0x0800; // A1 + if (squawk & 0x2000) encoded |= 0x0200; // A2 + if (squawk & 0x4000) encoded |= 0x0080; // A4 + + if (squawk & 0x0100) encoded |= 0x0020; // B1 + if (squawk & 0x0200) encoded |= 0x0008; // B2 + if (squawk & 0x0400) encoded |= 0x0002; // B4 + + if (squawk & 0x0010) encoded |= 0x1000; // C1 + if (squawk & 0x0020) encoded |= 0x0400; // C2 + if (squawk & 0x0040) encoded |= 0x0100; // C4 + + if (squawk & 0x0001) encoded |= 0x0010; // D1 + if (squawk & 0x0002) encoded |= 0x0004; // D2 + if (squawk & 0x0004) encoded |= 0x0001; // D4 + + return encoded; +} + +static void maybe_send_callsign(struct uat_adsb_mdb *mdb) +{ + uint8_t esnt_frame[14]; + int imf = encode_imf(mdb); + + // NB: we choose a CF value based on the address type (IMF value); + // we shouldn't send CF=6 with no IMF bit for non-ICAO addresses + // (see doc 9871 B.3.4.3) + switch (mdb->callsign_type) { + case CS_CALLSIGN: + setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT + setbits(esnt_frame, 6, 8, imf ? 5 : 6); // CF=6 for ICAO, CF=5 for non-ICAO + setbits(esnt_frame, 9, 32, mdb->address); // AA + + if (mdb->emitter_category <= 7) { + setbits(esnt_frame+4, 1, 5, 4); // FORMAT TYPE CODE = 4, aircraft category A + setbits(esnt_frame+4, 6, 8, mdb->emitter_category & 7); // AIRCRAFT CATEGORY (A0 - A7) + } else if (mdb->emitter_category <= 15) { + setbits(esnt_frame+4, 1, 5, 3); // FORMAT TYPE CODE = 3, aircraft category B + setbits(esnt_frame+4, 6, 8, mdb->emitter_category & 7); // AIRCRAFT CATEGORY (B0 - B7) + } else if (mdb->emitter_category <= 23) { + setbits(esnt_frame+4, 1, 5, 2); // FORMAT TYPE CODE = 2, aircraft category C + setbits(esnt_frame+4, 6, 8, mdb->emitter_category & 7); // AIRCRAFT CATEGORY (C0 - C7) + } else if (mdb->emitter_category <= 31) { + setbits(esnt_frame+4, 1, 5, 1); // FORMAT TYPE CODE = 1, aircraft category D + setbits(esnt_frame+4, 6, 8, mdb->emitter_category & 7); // AIRCRAFT CATEGORY (D0 - D7) + } else { + // reserved, map to A0 + setbits(esnt_frame+4, 1, 5, 4); // FORMAT TYPE CODE = 4, aircraft category A + setbits(esnt_frame+4, 6, 8, 0); // AIRCRAFT CATEGORY A0 + } + + // Map callsign + setbits(esnt_frame+4, 9, 14, char_to_ais(mdb->callsign[0])); + setbits(esnt_frame+4, 15, 20, char_to_ais(mdb->callsign[1])); + setbits(esnt_frame+4, 21, 26, char_to_ais(mdb->callsign[2])); + setbits(esnt_frame+4, 27, 32, char_to_ais(mdb->callsign[3])); + setbits(esnt_frame+4, 33, 38, char_to_ais(mdb->callsign[4])); + setbits(esnt_frame+4, 39, 44, char_to_ais(mdb->callsign[5])); + setbits(esnt_frame+4, 45, 50, char_to_ais(mdb->callsign[6])); + setbits(esnt_frame+4, 51, 56, char_to_ais(mdb->callsign[7])); + checksum_and_send(esnt_frame, 14, 0); + break; + + case CS_SQUAWK: + if (imf) { + // Non-ICAO address, send as DF18 "test message" + setbits(esnt_frame, 1, 5, 18); // DF=18, ES/NT + setbits(esnt_frame, 6, 8, 5); // CF=5, TIS-B retransmission with non-ICAO address + setbits(esnt_frame, 9, 32, mdb->address); // AA + + setbits(esnt_frame+4, 1, 5, 23); // FORMAT TYPE CODE = 23, test message + setbits(esnt_frame+4, 6, 8, 7); // subtype = 7, squawk + setbits(esnt_frame+4, 9, 21, encodeSquawk(mdb->callsign)); + + checksum_and_send(esnt_frame, 14, 0); + } else { + // ICAO address, send as DF5 + setbits(esnt_frame, 1, 5, 5); // DF=5, Surveillance Identity Reply + setbits(esnt_frame, 6, 8, 0); // Flight Status + setbits(esnt_frame, 9, 13, 0); // Downlink Request + setbits(esnt_frame, 14, 19, 0); // Utility Message + setbits(esnt_frame, 20, 32, encodeSquawk(mdb->callsign)); // Identity + + checksum_and_send(esnt_frame, 7, mdb->address); // put address in checksum (Address/Parity) + } + + break; + + default: + break; + } +} + +// 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]; + +static void initCrcTables() +{ + int i; + for (i = 0; i < 256; ++i) { + uint32_t c = i << 16; + int j; + for (j = 0; j < 8; ++j) { + if (c & 0x800000) + c = (c<<1) ^ MODES_GENERATOR_POLY; + else + c = (c<<1); + } + + crc_table[i] = c & 0x00ffffff; + } +} + +static uint32_t checksum(uint8_t *message, int n) +{ + uint32_t rem = 0; + int i; + + for (i = 0; i < n; ++i) { + rem = (rem << 8) ^ crc_table[message[i] ^ ((rem & 0xff0000) >> 16)]; + rem = rem & 0xffffff; + } + + return rem; +} + +static void checksum_and_send(uint8_t *frame, int len, uint32_t parity) +{ + int j; + uint32_t rem = checksum(frame, len-3) ^ parity; + + frame[len-3] = (rem & 0xFF0000) >> 16; + frame[len-2] = (rem & 0x00FF00) >> 8; + frame[len-1] = (rem & 0x0000FF); + + fprintf(stdout, "*"); + for (j = 0; j < len; j++) + fprintf(stdout, "%02X", frame[j]); + fprintf(stdout, ";\n"); + fflush(stdout); +} + +static void generate_esnt(struct uat_adsb_mdb *mdb) +{ + maybe_send_surface_position(mdb); + maybe_send_air_position(mdb); + maybe_send_air_velocity(mdb); + maybe_send_callsign(mdb); + +} + +static void handle_frame(frame_type_t type, uint8_t *frame, int len, void *extra) +{ + if (type == UAT_DOWNLINK) { + struct uat_adsb_mdb mdb; + uat_decode_adsb_mdb(frame, &mdb); + generate_esnt(&mdb); + } +} + +int main(int argc, char **argv) +{ + struct dump978_reader *reader; + int framecount; + + initCrcTables(); + + reader = dump978_reader_new(0,0); + if (!reader) { + perror("dump978_reader_new"); + return 1; + } + + while ((framecount = dump978_read_frames(reader, handle_frame, NULL)) > 0) + ; + + if (framecount < 0) { + perror("dump978_read_frames"); + return 1; + } + + return 0; +} + diff --git a/dump978/uat2json.c b/dump978/uat2json.c new file mode 100644 index 00000000..160e7cf6 --- /dev/null +++ b/dump978/uat2json.c @@ -0,0 +1,372 @@ +// +// Copyright 2015, Oliver Jowett +// + +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "uat.h" +#include "uat_decode.h" +#include "reader.h" + +#define NON_ICAO_ADDRESS 0x1000000U + +struct aircraft { + struct aircraft *next; + uint32_t address; + + uint32_t messages; + time_t last_seen; + time_t last_seen_pos; + + int position_valid : 1; + int altitude_valid : 1; + int track_valid : 1; + int speed_valid : 1; + int vert_rate_valid : 1; + + airground_state_t airground_state; + char callsign[9]; + char squawk[9]; + + // if position_valid: + double lat; + double lon; + + // if altitude_valid: + int32_t altitude; // in feet + + // if track_valid: + uint16_t track; + + // if speed_valid: + uint16_t speed; // in kts + + // if vert_rate_valid: + int16_t vert_rate; // in ft/min +}; + +static struct aircraft *aircraft_list; +static time_t NOW; +static const char *json_dir; + +static struct aircraft *find_aircraft(uint32_t address) +{ + struct aircraft *a; + for (a = aircraft_list; a; a = a->next) + if (a->address == address) + return a; + return NULL; +} + +static struct aircraft *find_or_create_aircraft(uint32_t address) +{ + struct aircraft *a = find_aircraft(address); + if (a) + return a; + + a = calloc(1, sizeof(*a)); + a->address = address; + a->airground_state = AG_RESERVED; + + a->next = aircraft_list; + aircraft_list = a; + + return a; +} + +static void expire_old_aircraft() +{ + struct aircraft *a, **last; + for (last = &aircraft_list, a = *last; a; a = *last) { + if ((NOW - a->last_seen) > 300) { + *last = a->next; + free(a); + } else { + last = &a->next; + } + } +} + +static uint32_t message_count; + +static void process_mdb(struct uat_adsb_mdb *mdb) +{ + struct aircraft *a; + uint32_t addr; + + ++message_count; + + switch (mdb->address_qualifier) { + case AQ_ADSB_ICAO: + case AQ_TISB_ICAO: + addr = mdb->address; + break; + + default: + addr = mdb->address | NON_ICAO_ADDRESS; + break; + } + + a = find_or_create_aircraft(addr); + a->last_seen = NOW; + ++a->messages; + + // copy state into aircraft + if (mdb->airground_state != AG_RESERVED) + a->airground_state = mdb->airground_state; + + if (mdb->position_valid) { + a->position_valid = 1; + a->lat = mdb->lat; + a->lon = mdb->lon; + a->last_seen_pos = NOW; + } + + if (mdb->altitude_type != ALT_INVALID) { + a->altitude_valid = 1; + a->altitude = mdb->altitude; + } + + if (mdb->track_type != TT_INVALID) { + a->track_valid = 1; + a->track = mdb->track; + } + + if (mdb->speed_valid) { + a->speed_valid = 1; + a->speed = mdb->speed; + } + + if (mdb->vert_rate_source != ALT_INVALID) { + a->vert_rate_valid = 1; + a->vert_rate = mdb->vert_rate; + } + + if (mdb->callsign_type == CS_CALLSIGN) + strcpy(a->callsign, mdb->callsign); + else if (mdb->callsign_type == CS_SQUAWK) + strcpy(a->squawk, mdb->callsign); + + if (mdb->sec_altitude_type != ALT_INVALID) { + // only use secondary if no primary is available + if (!a->altitude_valid || mdb->altitude_type == ALT_INVALID) { + a->altitude_valid = 1; + a->altitude = mdb->sec_altitude; + } + } +} + +static int write_receiver_json(const char *dir) +{ + char path[PATH_MAX]; + FILE *f; + + if (snprintf(path, PATH_MAX, "%s/receiver.json.new", dir) >= PATH_MAX) { + fprintf(stderr, "write_receiver_json: path too long\n"); + return 0; + } + + if (!(f = fopen(path, "w"))) { + fprintf(stderr, "fopen(%s): %m\n", path); + return 0; + } + + fprintf(f, + "{\n" + " \"version\" : \"dump978-uat2json\",\n" + " \"refresh\" : 1000,\n" + " \"history\" : 0\n" + "}\n"); + fclose(f); + + return 1; +} + +static int write_aircraft_json(const char *dir) +{ + char path[PATH_MAX]; + char path_new[PATH_MAX]; + FILE *f; + struct aircraft *a; + + if (snprintf(path, PATH_MAX, "%s/aircraft.json", dir) >= PATH_MAX || snprintf(path_new, PATH_MAX, "%s/aircraft.json.new", dir) >= PATH_MAX) { + fprintf(stderr, "write_aircraft_json: path too long\n"); + return 0; + } + + if (!(f = fopen(path_new, "w"))) { + fprintf(stderr, "fopen(%s): %m\n", path_new); + return 0; + } + + fprintf(f, + "{\n" + " \"now\" : %u,\n" + " \"messages\" : %u,\n" + " \"aircraft\" : [\n", + (unsigned)NOW, + message_count); + + + for (a = aircraft_list; a; a = a->next) { + if (a != aircraft_list) + fprintf(f, ",\n"); + fprintf(f, + " {\"hex\":\"%s%06x\"", + (a->address & NON_ICAO_ADDRESS) ? "~" : "", + a->address & 0xFFFFFF); + + if (a->squawk[0]) + fprintf(f, ",\"squawk\":\"%s\"", a->squawk); + if (a->callsign[0]) + fprintf(f, ",\"flight\":\"%s\"", a->callsign); + if (a->position_valid) + fprintf(f, ",\"lat\":%.6f,\"lon\":%.6f,\"seen_pos\":%u", a->lat, a->lon, (unsigned) (NOW - a->last_seen_pos)); + if (a->altitude_valid) + fprintf(f, ",\"altitude\":%d", a->altitude); + if (a->vert_rate_valid) + fprintf(f, ",\"vert_rate\":%d", a->vert_rate); + if (a->track_valid) + fprintf(f, ",\"track\":%u", a->track); + if (a->speed_valid) + fprintf(f, ",\"speed\":%u", a->speed); + fprintf(f, ",\"messages\":%u,\"seen\":%u,\"rssi\":0}", + a->messages, (unsigned) (NOW - a->last_seen)); + } + + fprintf(f, + "\n ]\n" + "}\n"); + fclose(f); + + if (rename(path_new, path) < 0) { + fprintf(stderr, "rename(%s,%s): %m\n", path_new, path); + return 0; + } + + return 1; +} + +static void periodic_work() +{ + static time_t next_write; + if (NOW >= next_write) { + expire_old_aircraft(); + write_aircraft_json(json_dir); + next_write = NOW + 1; + } +} + +static void handle_frame(frame_type_t type, uint8_t *frame, int len, void *extra) +{ + struct uat_adsb_mdb mdb; + + if (type != UAT_DOWNLINK) + return; + + if (len == SHORT_FRAME_DATA_BYTES) { + if ((frame[0] >> 3) != 0) { + fprintf(stderr, "short frame with non-zero type\n"); + return; + } + } else if (len == LONG_FRAME_DATA_BYTES) { + if ((frame[0] >> 3) == 0) { + fprintf(stderr, "long frame with zero type\n"); + return; + } + } else { + fprintf(stderr, "odd frame size: %d\n", len); + return; + } + + uat_decode_adsb_mdb(frame, &mdb); + //uat_display_adsb_mdb(&mdb, stdout); + process_mdb(&mdb); +} + +static void read_loop() +{ + struct dump978_reader *reader; + + reader = dump978_reader_new(0, 1); + if (!reader) { + perror("dump978_reader_new"); + return; + } + + for (;;) { + fd_set readset, writeset, excset; + struct timeval timeout; + int framecount; + + FD_ZERO(&readset); + FD_ZERO(&writeset); + FD_ZERO(&excset); + FD_SET(0, &readset); + FD_SET(0, &excset); + timeout.tv_sec = 0; + timeout.tv_usec = 500000; + + select(1, &readset, &writeset, &excset, &timeout); + + NOW = time(NULL); + framecount = dump978_read_frames(reader, handle_frame, NULL); + + if (framecount == 0) + break; + + if (framecount < 0 && errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) { + perror("dump978_read_frames"); + break; + } + + periodic_work(); + } + + dump978_reader_free(reader); +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, + "Syntax: %s \n" + "\n" + "Reads UAT messages on stdin.\n" + "Periodically writes aircraft state to /aircraft.json\n" + "Also writes /receiver.json once on startup\n", + argv[0]); + return 1; + } + + json_dir = argv[1]; + + if (!write_receiver_json(json_dir)) { + fprintf(stderr, "Failed to write receiver.json - check permissions?\n"); + return 1; + } + read_loop(); + write_aircraft_json(json_dir); + return 0; +} diff --git a/dump978/uat2text.c b/dump978/uat2text.c new file mode 100644 index 00000000..f1028061 --- /dev/null +++ b/dump978/uat2text.c @@ -0,0 +1,61 @@ +// +// Copyright 2015, Oliver Jowett +// + +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#include "uat.h" +#include "uat_decode.h" +#include "reader.h" + +void handle_frame(frame_type_t type, uint8_t *frame, int len, void *extra) +{ + if (type == UAT_DOWNLINK) { + struct uat_adsb_mdb mdb; + uat_decode_adsb_mdb(frame, &mdb); + uat_display_adsb_mdb(&mdb, stdout); + } else { + struct uat_uplink_mdb mdb; + uat_decode_uplink_mdb(frame, &mdb); + uat_display_uplink_mdb(&mdb, stdout); + } + + fprintf(stdout, "\n"); + fflush(stdout); +} + +int main(int argc, char **argv) +{ + struct dump978_reader *reader; + int framecount; + + reader = dump978_reader_new(0,0); + if (!reader) { + perror("dump978_reader_new"); + return 1; + } + + while ((framecount = dump978_read_frames(reader, handle_frame, NULL)) > 0) + ; + + if (framecount < 0) { + perror("dump978_read_frames"); + return 1; + } + + return 0; +} + diff --git a/dump978/uat_decode.c b/dump978/uat_decode.c new file mode 100644 index 00000000..80a7d3c7 --- /dev/null +++ b/dump978/uat_decode.c @@ -0,0 +1,949 @@ +// Part of dump978, a UAT decoder. +// +// Copyright 2015, Oliver Jowett +// +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include + +#include "uat.h" +#include "uat_decode.h" + +static void uat_decode_hdr(uint8_t *frame, struct uat_adsb_mdb *mdb) +{ + mdb->mdb_type = (frame[0] >> 3) & 0x1f; + mdb->address_qualifier = (address_qualifier_t) (frame[0] & 0x07); + mdb->address = (frame[1] << 16) | (frame[2] << 8) | frame[3]; +} + +static const char *address_qualifier_names[8] = { + "ICAO address via ADS-B", + "reserved (national use)", + "ICAO address via TIS-B", + "TIS-B track file address", + "Vehicle address", + "Fixed ADS-B Beacon Address", + "reserved (6)", + "reserved (7)" +}; + +static void uat_display_hdr(const struct uat_adsb_mdb *mdb, FILE *to) +{ + fprintf(to, + "HDR:\n" + " MDB Type: %d\n" + " Address: %06X (%s)\n", + mdb->mdb_type, + mdb->address, + address_qualifier_names[mdb->address_qualifier]); +} + +static double dimensions_widths[16] = { + 11.5, 23, 28.5, 34, 33, 38, 39.5, 45, 45, 52, 59.5, 67, 72.5, 80, 80, 90 +}; + +static void uat_decode_sv(uint8_t *frame, struct uat_adsb_mdb *mdb) +{ + uint32_t raw_lat, raw_lon, raw_alt; + + mdb->has_sv = 1; + + mdb->nic = (frame[11] & 15); + + raw_lat = (frame[4] << 15) | (frame[5] << 7) | (frame[6] >> 1); + raw_lon = ((frame[6] & 0x01) << 23) | (frame[7] << 15) | (frame[8] << 7) | (frame[9] >> 1); + + if (mdb->nic != 0 || raw_lat != 0 || raw_lon != 0) { + mdb->position_valid = 1; + mdb->lat = raw_lat * 360.0 / 16777216.0; + if (mdb->lat > 90) + mdb->lat -= 180; + mdb->lon = raw_lon * 360.0 / 16777216.0; + if (mdb->lon > 180) + mdb->lon -= 360; + } + + raw_alt = (frame[10] << 4) | ((frame[11] & 0xf0) >> 4); + if (raw_alt != 0) { + mdb->altitude_type = (frame[9] & 1) ? ALT_GEO : ALT_BARO; + mdb->altitude = (raw_alt - 1) * 25 - 1000; + } + + mdb->airground_state = (frame[12] >> 6) & 0x03; + + switch (mdb->airground_state) { + case AG_SUBSONIC: + case AG_SUPERSONIC: + { + int raw_ns, raw_ew, raw_vvel; + + raw_ns = ((frame[12] & 0x1f) << 6) | ((frame[13] & 0xfc) >> 2); + if ((raw_ns & 0x3ff) != 0) { + mdb->ns_vel_valid = 1; + mdb->ns_vel = ((raw_ns & 0x3ff) - 1); + if (raw_ns & 0x400) + mdb->ns_vel = 0 - mdb->ns_vel; + if (mdb->airground_state == AG_SUPERSONIC) + mdb->ns_vel *= 4; + } + + raw_ew = ((frame[13] & 0x03) << 9) | (frame[14] << 1) | ((frame[15] & 0x80) >> 7); + if ((raw_ew & 0x3ff) != 0) { + mdb->ew_vel_valid = 1; + mdb->ew_vel = ((raw_ew & 0x3ff) - 1); + if (raw_ew & 0x400) + mdb->ew_vel = 0 - mdb->ew_vel; + if (mdb->airground_state == AG_SUPERSONIC) + mdb->ew_vel *= 4; + } + + if (mdb->ns_vel_valid && mdb->ew_vel_valid) { + if (mdb->ns_vel != 0 || mdb->ew_vel != 0) { + mdb->track_type = TT_TRACK; + mdb->track = (uint16_t)(360 + 90 - atan2(mdb->ns_vel, mdb->ew_vel) * 180 / M_PI) % 360; + } + + mdb->speed_valid = 1; + mdb->speed = (int) sqrt(mdb->ns_vel * mdb->ns_vel + mdb->ew_vel * mdb->ew_vel); + } + + raw_vvel = ((frame[15] & 0x7f) << 4) | ((frame[16] & 0xf0) >> 4); + if ((raw_vvel & 0x1ff) != 0) { + mdb->vert_rate_source = (raw_vvel & 0x400) ? ALT_BARO : ALT_GEO; + mdb->vert_rate = ((raw_vvel & 0x1ff) - 1) * 64; + if (raw_vvel & 0x200) + mdb->vert_rate = 0 - mdb->vert_rate; + } + } + break; + + case AG_GROUND: + { + int raw_gs, raw_track; + + raw_gs = ((frame[12] & 0x1f) << 6) | ((frame[13] & 0xfc) >> 2); + if (raw_gs != 0) { + mdb->speed_valid = 1; + mdb->speed = ((raw_gs & 0x3ff) - 1); + } + + raw_track = ((frame[13] & 0x03) << 9) | (frame[14] << 1) | ((frame[15] & 0x80) >> 7); + switch ((raw_track & 0x0600)>>9) { + case 1: mdb->track_type = TT_TRACK; break; + case 2: mdb->track_type = TT_MAG_HEADING; break; + case 3: mdb->track_type = TT_TRUE_HEADING; break; + } + + if (mdb->track_type != TT_INVALID) + mdb->track = (raw_track & 0x1ff) * 360 / 512; + + mdb->dimensions_valid = 1; + mdb->length = 15 + 10 * ((frame[15] & 0x38) >> 3); + mdb->width = dimensions_widths[(frame[15] & 0x78) >> 3]; + mdb->position_offset = (frame[15] & 0x04) ? 1 : 0; + } + break; + + case AG_RESERVED: + // nothing + break; + } + + if ((frame[0] & 7) == 2 || (frame[0] & 7) == 3) { + mdb->utc_coupled = 0; + mdb->tisb_site_id = (frame[16] & 0x0f); + } else { + mdb->utc_coupled = (frame[16] & 0x08) ? 1 : 0; + mdb->tisb_site_id = 0; + } +} + +static void uat_display_sv(const struct uat_adsb_mdb *mdb, FILE *to) +{ + if (!mdb->has_sv) + return; + + fprintf(to, + "SV:\n" + " NIC: %u\n", + mdb->nic); + + if (mdb->position_valid) + fprintf(to, + " Latitude: %+.4f\n" + " Longitude: %+.4f\n", + mdb->lat, + mdb->lon); + + switch (mdb->altitude_type) { + case ALT_BARO: + fprintf(to, + " Altitude: %d ft (barometric)\n", + mdb->altitude); + break; + case ALT_GEO: + fprintf(to, + " Altitude: %d ft (geometric)\n", + mdb->altitude); + break; + default: + break; + } + + if (mdb->ns_vel_valid) + fprintf(to, + " N/S velocity: %d kt\n", + mdb->ns_vel); + + if (mdb->ew_vel_valid) + fprintf(to, + " E/W velocity: %d kt\n", + mdb->ew_vel); + + switch (mdb->track_type) { + case TT_TRACK: + fprintf(to, + " Track: %u\n", + mdb->track); + break; + case TT_MAG_HEADING: + fprintf(to, + " Heading: %u (magnetic)\n", + mdb->track); + break; + case TT_TRUE_HEADING: + fprintf(to, + " Heading: %u (true)\n", + mdb->track); + break; + default: + break; + } + + if (mdb->speed_valid) + fprintf(to, + " Speed: %u kt\n", + mdb->speed); + + + switch (mdb->vert_rate_source) { + case ALT_BARO: + fprintf(to, + " Vertical rate: %d ft/min (from barometric altitude)\n", + mdb->vert_rate); + break; + case ALT_GEO: + fprintf(to, + " Vertical rate: %d ft/min (from geometric altitude)\n", + mdb->vert_rate); + break; + default: + break; + } + + if (mdb->dimensions_valid) + fprintf(to, + " Dimensions: %.1fm L x %.1fm W%s\n", + mdb->length, mdb->width, + mdb->position_offset ? " (position offset applied)" : ""); + + fprintf(to, + " UTC coupling: %s\n" + " TIS-B site ID: %u\n", + mdb->utc_coupled ? "yes" : "no", + mdb->tisb_site_id); +} + +static char base40_alphabet[40] = "0123456789ABCDEFGHIJKLMNOPQRTSUVWXYZ .."; +static void uat_decode_ms(uint8_t *frame, struct uat_adsb_mdb *mdb) +{ + uint16_t v; + int i; + + mdb->has_ms = 1; + + v = (frame[17]<<8) | (frame[18]); + mdb->emitter_category = (v/1600) % 40; + mdb->callsign[0] = base40_alphabet[(v/40) % 40]; + mdb->callsign[1] = base40_alphabet[v % 40]; + v = (frame[19]<<8) | (frame[20]); + mdb->callsign[2] = base40_alphabet[(v/1600) % 40]; + mdb->callsign[3] = base40_alphabet[(v/40) % 40]; + mdb->callsign[4] = base40_alphabet[v % 40]; + v = (frame[21]<<8) | (frame[22]); + mdb->callsign[5] = base40_alphabet[(v/1600) % 40]; + mdb->callsign[6] = base40_alphabet[(v/40) % 40]; + mdb->callsign[7] = base40_alphabet[v % 40]; + mdb->callsign[8] = 0; + + // trim trailing spaces + for (i = 7; i >= 0; --i) { + if (mdb->callsign[i] == ' ') + mdb->callsign[i] = 0; + else + break; + } + + mdb->emergency_status = (frame[23] >> 5) & 7; + mdb->uat_version = (frame[23] >> 2) & 7; + mdb->sil = (frame[23] & 3); + mdb->transmit_mso = (frame[24] >> 2) & 0x3f; + mdb->nac_p = (frame[25] >> 4) & 15; + mdb->nac_v = (frame[25] >> 1) & 7; + mdb->nic_baro = (frame[25] & 1); + mdb->has_cdti = (frame[26] & 0x80 ? 1 : 0); + mdb->has_acas = (frame[26] & 0x40 ? 1 : 0); + mdb->acas_ra_active = (frame[26] & 0x20 ? 1 : 0); + mdb->ident_active = (frame[26] & 0x10 ? 1 : 0); + mdb->atc_services = (frame[26] & 0x08 ? 1 : 0); + mdb->heading_type = (frame[26] & 0x04 ? HT_MAGNETIC : HT_TRUE); + if (mdb->callsign[0]) + mdb->callsign_type = (frame[26] & 0x02 ? CS_CALLSIGN : CS_SQUAWK); +} + +static const char *emitter_category_names[40] = { + "No information", // A0 + "Light <= 7000kg", + "Medium Wake 7000-34000kg", + "Medium Wake 34000-136000kg", + "Medium Wake High Vortex 34000-136000kg", + "Heavy >= 136000kg", + "Highly Maneuverable", + "Rotorcraft", // A7 + "reserved (8)", // B0 + "Glider/Sailplane", + "Lighter than air", + "Parachutist / sky diver", + "Ultra light / hang glider / paraglider", + "reserved (13)", + "UAV", + "Space / transatmospheric", // B7 + "reserved (16)", // C0 + "Emergency vehicle", + "Service vehicle", + "Point obstacle", + "Cluster obstacle", + "Line obstacle", + "reserved (22)", + "reserved (23)", // C7 + "reserved (24)", + "reserved (25)", + "reserved (26)", + "reserved (27)", + "reserved (28)", + "reserved (29)", + "reserved (30)", + "reserved (31)", + "reserved (32)", + "reserved (33)", + "reserved (34)", + "reserved (35)", + "reserved (36)", + "reserved (37)", + "reserved (38)", + "reserved (39)" +}; + +static const char *emergency_status_names[8] = { + "No emergency", + "General emergency", + "Lifeguard / Medical emergency", + "Minimum fuel", + "No communications", + "Unlawful interference", + "Downed aircraft", + "reserved" +}; + +static void uat_display_ms(const struct uat_adsb_mdb *mdb, FILE *to) +{ + if (!mdb->has_ms) + return; + + fprintf(to, + "MS:\n" + " Emitter category: %s\n" + " Callsign: %s%s\n" + " Emergency status: %s\n" + " UAT version: %u\n" + " SIL: %u\n" + " Transmit MSO: %u\n" + " NACp: %u\n" + " NACv: %u\n" + " NICbaro: %u\n" + " Capabilities: %s%s\n" + " Active modes: %s%s%s\n" + " Target track type: %s\n", + emitter_category_names[mdb->emitter_category], + mdb->callsign_type == CS_SQUAWK ? "squawk " : "", + mdb->callsign_type == CS_INVALID ? "unavailable" : mdb->callsign, + emergency_status_names[mdb->emergency_status], + mdb->uat_version, + mdb->sil, + mdb->transmit_mso, + mdb->nac_p, + mdb->nac_v, + mdb->nic_baro, + mdb->has_cdti ? "CDTI " : "", mdb->has_acas ? "ACAS " : "", + mdb->acas_ra_active ? "ACASRA " : "", mdb->ident_active ? "IDENT " : "", mdb->atc_services ? "ATC " : "", + mdb->heading_type == HT_MAGNETIC ? "magnetic heading" : "true heading"); +} + +static void uat_decode_auxsv(uint8_t *frame, struct uat_adsb_mdb *mdb) +{ + int raw_alt = (frame[29] << 4) | ((frame[30] & 0xf0) >> 4); + if (raw_alt != 0) { + mdb->sec_altitude = (raw_alt - 1) * 25 - 1000; + mdb->sec_altitude_type = (frame[9] & 1) ? ALT_BARO : ALT_GEO; + } else { + mdb->sec_altitude_type = ALT_INVALID; + } + + mdb->has_auxsv = 1; +} + + +static void uat_display_auxsv(const struct uat_adsb_mdb *mdb, FILE *to) +{ + if (!mdb->has_auxsv) + return; + + fprintf(to, + "AUXSV:\n"); + + switch (mdb->sec_altitude_type) { + case ALT_BARO: + fprintf(to, + " Sec. altitude: %d ft (barometric)\n", + mdb->sec_altitude); + break; + case ALT_GEO: + fprintf(to, + " Sec. altitude: %d ft (geometric)\n", + mdb->sec_altitude); + break; + default: + fprintf(to, + " Sec. altitude: unavailable\n"); + break; + } +} + +void uat_decode_adsb_mdb(uint8_t *frame, struct uat_adsb_mdb *mdb) +{ + static struct uat_adsb_mdb mdb_zero; + + *mdb = mdb_zero; + + uat_decode_hdr(frame, mdb); + + switch (mdb->mdb_type) { + case 0: // HDR SV + case 4: // HDR SV (TC+0) (TS) + case 7: // HDR SV reserved + case 8: // HDR SV reserved + case 9: // HDR SV reserved + case 10: // HDR SV reserved + uat_decode_sv(frame, mdb); + break; + + case 1: // HDR SV MS AUXSV + uat_decode_sv(frame, mdb); + uat_decode_ms(frame, mdb); + uat_decode_auxsv(frame, mdb); + break; + + case 2: // HDR SV AUXSV + case 5: // HDR SV (TC+1) AUXSV + case 6: // HDR SV (TS) AUXSV + uat_decode_sv(frame, mdb); + uat_decode_auxsv(frame, mdb); + break; + + case 3: // HDR SV MS (TS) + uat_decode_sv(frame, mdb); + uat_decode_ms(frame, mdb); + break; + + default: + break; + } +} + +void uat_display_adsb_mdb(const struct uat_adsb_mdb *mdb, FILE *to) +{ + uat_display_hdr(mdb, to); + uat_display_sv(mdb, to); + uat_display_ms(mdb, to); + uat_display_auxsv(mdb, to); +} + + +static void uat_decode_info_frame(struct uat_uplink_info_frame *frame) +{ + unsigned t_opt; + + frame->is_fisb = 0; + + if (frame->type != 0) + return; // not FIS-B + + if (frame->length < 4) // too short for FIS-B + return; + + t_opt = ((frame->data[1] & 0x01) << 1) | (frame->data[2] >> 7); + + switch (t_opt) { + case 0: // Hours, Minutes + frame->fisb.monthday_valid = 0; + frame->fisb.seconds_valid = 0; + frame->fisb.hours = (frame->data[2] & 0x7c) >> 2; + frame->fisb.minutes = ((frame->data[2] & 0x03) << 4) | (frame->data[3] >> 4); + frame->fisb.length = frame->length - 4; + frame->fisb.data = frame->data + 4; + break; + case 1: // Hours, Minutes, Seconds + if (frame->length < 5) + return; + frame->fisb.monthday_valid = 0; + frame->fisb.seconds_valid = 1; + frame->fisb.hours = (frame->data[2] & 0x7c) >> 2; + frame->fisb.minutes = ((frame->data[2] & 0x03) << 4) | (frame->data[3] >> 4); + frame->fisb.seconds = ((frame->data[3] & 0x0f) << 2) | (frame->data[4] >> 6); + frame->fisb.length = frame->length - 5; + frame->fisb.data = frame->data + 5; + break; + case 2: // Month, Day, Hours, Minutes + if (frame->length < 5) + return; + frame->fisb.monthday_valid = 1; + frame->fisb.seconds_valid = 0; + frame->fisb.month = (frame->data[2] & 0x78) >> 3; + frame->fisb.day = ((frame->data[2] & 0x07) << 2) | (frame->data[3] >> 6); + frame->fisb.hours = (frame->data[3] & 0x3e) >> 1; + frame->fisb.minutes = ((frame->data[3] & 0x01) << 5) | (frame->data[4] >> 3); + frame->fisb.length = frame->length - 5; // ??? + frame->fisb.data = frame->data + 5; + break; + case 3: // Month, Day, Hours, Minutes, Seconds + if (frame->length < 6) + return; + frame->fisb.monthday_valid = 1; + frame->fisb.seconds_valid = 1; + frame->fisb.month = (frame->data[2] & 0x78) >> 3; + frame->fisb.day = ((frame->data[2] & 0x07) << 2) | (frame->data[3] >> 6); + frame->fisb.hours = (frame->data[3] & 0x3e) >> 1; + frame->fisb.minutes = ((frame->data[3] & 0x01) << 5) | (frame->data[4] >> 3); + frame->fisb.seconds = ((frame->data[4] & 0x03) << 3) | (frame->data[5] >> 5); + frame->fisb.length = frame->length - 6; + frame->fisb.data = frame->data + 6; + break; + } + + frame->fisb.a_flag = (frame->data[0] & 0x80) ? 1 : 0; + frame->fisb.g_flag = (frame->data[0] & 0x40) ? 1 : 0; + frame->fisb.p_flag = (frame->data[0] & 0x20) ? 1 : 0; + frame->fisb.product_id = ((frame->data[0] & 0x1f) << 6) | (frame->data[1] >> 2); + frame->fisb.s_flag = (frame->data[1] & 0x02) ? 1 : 0; + frame->is_fisb = 1; +} + +void uat_decode_uplink_mdb(uint8_t *frame, struct uat_uplink_mdb *mdb) +{ + mdb->position_valid = (frame[5] & 0x01) ? 1 : 0; + + /* Even with position_valid = 0, there seems to be plausible data here. + * Decode it always. + */ + /*if (mdb->position_valid)*/ { + uint32_t raw_lat = (frame[0] << 15) | (frame[1] << 7) | (frame[2] >> 1); + uint32_t raw_lon = ((frame[2] & 0x01) << 23) | (frame[3] << 15) | (frame[4] << 7) | (frame[5] >> 1); + + mdb->lat = raw_lat * 360.0 / 16777216.0; + if (mdb->lat > 90) + mdb->lat -= 180; + mdb->lon = raw_lon * 360.0 / 16777216.0; + if (mdb->lon > 180) + mdb->lon -= 360; + } + + mdb->utc_coupled = (frame[6] & 0x80) ? 1 : 0; + mdb->app_data_valid = (frame[6] & 0x20) ? 1 : 0; + mdb->slot_id = (frame[6] & 0x1f); + mdb->tisb_site_id = (frame[7] >> 4); + + if (mdb->app_data_valid) { + uint8_t *data, *end; + + memcpy(mdb->app_data, frame+8, 424); + mdb->num_info_frames = 0; + + data = mdb->app_data; + end = mdb->app_data + 424; + while (mdb->num_info_frames < UPLINK_MAX_INFO_FRAMES && data+2 <= end) { + struct uat_uplink_info_frame *frame = &mdb->info_frames[mdb->num_info_frames]; + frame->length = (data[0] << 1) | (data[1] >> 7); + frame->type = (data[1] & 0x0f); + if (data + frame->length + 2 > end) { + // overrun? + break; + } + + if (frame->length == 0 && frame->type == 0) { + break; // no more frames + } + + frame->data = data + 2; + + uat_decode_info_frame(frame); + + data += frame->length + 2; + ++mdb->num_info_frames; + } + } +} + +static void display_generic_data(uint8_t *data, uint16_t length, FILE *to) +{ + unsigned i; + + fprintf(to, + " Data: "); + for (i = 0; i < length; i += 16) { + unsigned j; + + if (i > 0) + fprintf(to, + " "); + + for (j = i; j < i+16; ++j) { + if (j < length) + fprintf(to, "%02X ", data[j]); + else + fprintf(to, " "); + } + + for (j = i; j < i+16 && j < length; ++j) { + fprintf(to, "%c", + (data[j] >= 32 && data[j] < 127) ? data[j] : '.'); + } + fprintf(to, "\n"); + } +} + +// The odd two-string-literals here is to avoid \0x3ABCDEF being interpreted as a single (very large valued) character +static const char *dlac_alphabet = "\x03" "ABCDEFGHIJKLMNOPQRSTUVWXYZ\x1A\t\x1E\n| !\"#$%&'()*+,-./0123456789:;<=>?"; + +static const char *decode_dlac(uint8_t *data, unsigned bytelen) +{ + static char buf[1024]; + uint8_t *end = data + bytelen; + char *p = buf; + int step = 0; + int tab = 0; + + while (data < end) { + int ch; + + assert(step >= 0 && step <= 3); + switch (step) { + case 0: + ch = data[0] >> 2; + ++data; + break; + case 1: + ch = ((data[-1] & 0x03) << 4) | (data[0] >> 4); + ++data; + break; + case 2: + ch = ((data[-1] & 0x0f) << 2) | (data[0] >> 6); + break; + case 3: + ch = data[0] & 0x3f; + ++data; + break; + } + + if (tab) { + while (ch > 0) + *p++ = ' ', ch--; + tab = 0; + } else if (ch == 28) { // tab + tab = 1; + } else { + *p++ = dlac_alphabet[ch]; + } + + step = (step+1)%4; + } + + *p = 0; + return buf; +} + +static const char *get_fisb_product_name(uint16_t product_id) +{ + switch (product_id) { + case 0: case 20: return "METAR and SPECI"; + case 1: case 21: return "TAF and Amended TAF"; + case 2: case 22: return "SIGMET"; + case 3: case 23: return "Convective SIGMET"; + case 4: case 24: return "AIRMET"; + case 5: case 25: return "PIREP"; + case 6: case 26: return "AWW"; + case 7: case 27: return "Winds and Temperatures Aloft"; + case 8: return "NOTAM (Including TFRs) and Service Status"; + case 9: return "Aerodrome and Airspace – D-ATIS"; + case 10: return "Aerodrome and Airspace - TWIP"; + case 11: return "Aerodrome and Airspace - AIRMET"; + case 12: return "Aerodrome and Airspace - SIGMET/Convective SIGMET"; + case 13: return "Aerodrome and Airspace - SUA Status"; + case 51: return "National NEXRAD, Type 0 - 4 level"; + case 52: return "National NEXRAD, Type 1 - 8 level (quasi 6-level VIP)"; + case 53: return "National NEXRAD, Type 2 - 8 level"; + case 54: return "National NEXRAD, Type 3 - 16 level"; + case 55: return "Regional NEXRAD, Type 0 - low dynamic range"; + case 56: return "Regional NEXRAD, Type 1 - 8 level (quasi 6-level VIP)"; + case 57: return "Regional NEXRAD, Type 2 - 8 level"; + case 58: return "Regional NEXRAD, Type 3 - 16 level"; + case 59: return "Individual NEXRAD, Type 0 - low dynamic range"; + case 60: return "Individual NEXRAD, Type 1 - 8 level (quasi 6-level VIP)"; + case 61: return "Individual NEXRAD, Type 2 - 8 level"; + case 62: return "Individual NEXRAD, Type 3 - 16 level"; + case 63: return "Global Block Representation - Regional NEXRAD, Type 4 – 8 level"; + case 64: return "Global Block Representation - CONUS NEXRAD, Type 4 - 8 level"; + case 81: return "Radar echo tops graphic, scheme 1: 16-level"; + case 82: return "Radar echo tops graphic, scheme 2: 8-level"; + case 83: return "Storm tops and velocity"; + case 101: return "Lightning strike type 1 (pixel level)"; + case 102: return "Lightning strike type 2 (grid element level)"; + case 151: return "Point phenomena, vector format"; + case 201: return "Surface conditions/winter precipitation graphic"; + case 202: return "Surface weather systems"; + case 254: return "AIRMET, SIGMET: Bitmap encoding"; + case 351: return "System Time"; + case 352: return "Operational Status"; + case 353: return "Ground Station Status"; + case 401: return "Generic Raster Scan Data Product APDU Payload Format Type 1"; + case 402: case 411: return "Generic Textual Data Product APDU Payload Format Type 1"; + case 403: return "Generic Vector Data Product APDU Payload Format Type 1"; + case 404: case 412: return "Generic Symbolic Product APDU Payload Format Type 1"; + case 405: case 413: return "Generic Textual Data Product APDU Payload Format Type 2"; + case 600: return "FISDL Products – Proprietary Encoding"; + case 2000: return "FAA/FIS-B Product 1 – Developmental"; + case 2001: return "FAA/FIS-B Product 2 – Developmental"; + case 2002: return "FAA/FIS-B Product 3 – Developmental"; + case 2003: return "FAA/FIS-B Product 4 – Developmental"; + case 2004: return "WSI Products - Proprietary Encoding"; + case 2005: return "WSI Developmental Products"; + default: return "unknown"; + } +} + +static const char *get_fisb_product_format(uint16_t product_id) +{ + switch (product_id) { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + case 351: case 352: case 353: + case 402: case 405: + return "Text"; + + case 8: case 9: case 10: case 11: case 12: case 13: + return "Text/Graphic"; + + case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 27: + case 411: case 413: + return "Text (DLAC)"; + + case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58: + case 59: case 60: case 61: case 62: case 63: case 64: + case 81: case 82: case 83: + case 101: case 102: + case 151: + case 201: case 202: + case 254: + case 401: + case 403: + case 404: + return "Graphic"; + + case 412: + return "Graphic (DLAC)"; + + case 600: case 2004: + return "Proprietary"; + + case 2000: case 2001: case 2002: case 2003: case 2005: + return "Developmental"; + + default: + return "unknown"; + } +} + +static void uat_display_fisb_frame(const struct fisb_apdu *apdu, FILE *to) +{ + fprintf(to, + "FIS-B:\n" + " Flags: %s%s%s%s\n" + " Product ID: %u (%s) - %s\n", + apdu->a_flag ? "A" : "", + apdu->g_flag ? "G" : "", + apdu->p_flag ? "P" : "", + apdu->s_flag ? "S" : "", + apdu->product_id, + get_fisb_product_name(apdu->product_id), + get_fisb_product_format(apdu->product_id)); + + fprintf(to, + " Product time: "); + if (apdu->monthday_valid) + fprintf(to, "%u/%u ", apdu->month, apdu->day); + fprintf(to, "%02u:%02u", apdu->hours, apdu->minutes); + if (apdu->seconds_valid) + fprintf(to, ":%02u", apdu->seconds); + fprintf(to, "\n"); + + switch (apdu->product_id) { + case 413: + { + // Generic text, DLAC + const char *text = decode_dlac(apdu->data, apdu->length); + const char *report = text; + + while (report) { + char report_buf[1024]; + const char *next_report; + char *p, *r; + + next_report = strchr(report, '\x1e'); // RS + if (!next_report) + next_report = strchr(report, '\x03'); // ETX + if (next_report) { + memcpy(report_buf, report, next_report - report); + report_buf[next_report - report] = 0; + report = next_report + 1; + } else { + strcpy(report_buf, report); + report = NULL; + } + + if (!report_buf[0]) + continue; + + r = report_buf; + p = strchr(r, ' '); + if (p) { + *p = 0; + fprintf(to, + " Report type: %s\n", + r); + r = p+1; + } + + p = strchr(r, ' '); + if (p) { + *p = 0; + fprintf(to, + " Report location: %s\n", + r); + r = p+1; + } + + p = strchr(r, ' '); + if (p) { + *p = 0; + fprintf(to, + " Report time: %s\n", + r); + r = p+1; + } + + fprintf(to, + " Text:\n%s\n", + r); + } + } + break; + default: + display_generic_data(apdu->data, apdu->length, to); + break; + } +} + +static const char *info_frame_type_names[16] = { + "FIS-B APDU", + "Reserved for Developmental Use", + "Reserved for Future Use (2)", + "Reserved for Future Use (3)", + "Reserved for Future Use (4)", + "Reserved for Future Use (5)", + "Reserved for Future Use (6)", + "Reserved for Future Use (7)", + "Reserved for Future Use (8)", + "Reserved for Future Use (9)", + "Reserved for Future Use (10)", + "Reserved for Future Use (11)", + "Reserved for Future Use (12)", + "Reserved for Future Use (13)", + "Reserved for Future Use (14)", + "TIS-B/ADS-R Service Status" +}; + +static void uat_display_uplink_info_frame(const struct uat_uplink_info_frame *frame, FILE *to) +{ + fprintf(to, + "INFORMATION FRAME:\n" + " Length: %u bytes\n" + " Type: %u (%s)\n", + frame->length, + frame->type, + info_frame_type_names[frame->type]); + + if (frame->length > 0) { + if (frame->is_fisb) + uat_display_fisb_frame(&frame->fisb, to); + else { + display_generic_data(frame->data, frame->length, to); + } + } +} + +void uat_display_uplink_mdb(const struct uat_uplink_mdb *mdb, FILE *to) +{ + fprintf(to, + "UPLINK:\n"); + + fprintf(to, + " Site Latitude: %+.4f%s\n" + " Site Longitude: %+.4f%s\n", + mdb->lat, mdb->position_valid ? "" : " (possibly invalid)", + mdb->lon, mdb->position_valid ? "" : " (possibly invalid)"); + + fprintf(to, + " UTC coupled: %s\n" + " Slot ID: %u\n" + " TIS-B Site ID: %u\n", + mdb->utc_coupled ? "yes" : "no", + mdb->slot_id, + mdb->tisb_site_id); + + if (mdb->app_data_valid) { + unsigned i; + for (i = 0; i < mdb->num_info_frames; ++i) + uat_display_uplink_info_frame(&mdb->info_frames[i], to); + } +} diff --git a/dump978/uat_decode.h b/dump978/uat_decode.h new file mode 100644 index 00000000..3152c148 --- /dev/null +++ b/dump978/uat_decode.h @@ -0,0 +1,192 @@ +// Part of dump978, a UAT decoder. +// +// Copyright 2015, Oliver Jowett +// +// This file is free software: you may copy, redistribute and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 2 of the License, or (at your +// option) any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef UAT_DECODE_H +#define UAT_DECODE_H + +#include +#include + +#include "uat.h" + +// +// Datatypes +// + +typedef enum { AQ_ADSB_ICAO=0, AQ_NATIONAL=1, AQ_TISB_ICAO=2, AQ_TISB_OTHER=3, AQ_VEHICLE=4, + AQ_FIXED_BEACON=5, AQ_RESERVED_6=6, AQ_RESERVED_7=7 } address_qualifier_t; +typedef enum { ALT_INVALID=0, ALT_BARO, ALT_GEO } altitude_type_t; +typedef enum { AG_SUBSONIC=0, AG_SUPERSONIC=1, AG_GROUND=2, AG_RESERVED=3 } airground_state_t; +typedef enum { TT_INVALID=0, TT_TRACK, TT_MAG_HEADING, TT_TRUE_HEADING } track_type_t; +typedef enum { HT_INVALID=0, HT_MAGNETIC, HT_TRUE } heading_type_t; +typedef enum { CS_INVALID=0, CS_CALLSIGN, CS_SQUAWK } callsign_type_t; + +struct uat_adsb_mdb { + // presence bits + int has_sv : 1; + int has_ms : 1; + int has_auxsv : 1; + + int position_valid : 1; + int ns_vel_valid : 1; + int ew_vel_valid : 1; + int speed_valid : 1; + int dimensions_valid : 1; + + // + // HDR + // + uint8_t mdb_type; + address_qualifier_t address_qualifier; + uint32_t address; + + // + // SV + // + + // if position_valid: + double lat; + double lon; + + altitude_type_t altitude_type; + int32_t altitude; // in feet + + uint8_t nic; + + airground_state_t airground_state; + + // if ns_vel_valid: + int16_t ns_vel; // in kts + // if ew_vel_valid: + int16_t ew_vel; // in kts + + track_type_t track_type; + uint16_t track; + + // if speed_valid: + uint16_t speed; // in kts + + altitude_type_t vert_rate_source; + int16_t vert_rate; // in ft/min + + // if lengthwidth_valid: + double length; // in meters (just to be different) + double width; // in meters (just to be different) + int position_offset : 1; // true if Position Offset Applied + + int utc_coupled : 1; // true if UTC Coupled flag is set (ADS-B) + uint8_t tisb_site_id; // TIS-B site ID, or zero in ADS-B messages + + // + // MS + // + + uint8_t emitter_category; + callsign_type_t callsign_type; + char callsign[9]; + uint8_t emergency_status; + uint8_t uat_version; + uint8_t sil; + uint8_t transmit_mso; + uint8_t nac_p; + uint8_t nac_v; + uint8_t nic_baro; + + // capabilities: + int has_cdti : 1; + int has_acas : 1; + // operational modes: + int acas_ra_active : 1; + int ident_active : 1; + int atc_services : 1; + + heading_type_t heading_type; + + // + // AUXSV + + altitude_type_t sec_altitude_type; + int32_t sec_altitude; // in feet +}; + +// +// Decode/display prototypes +// + +void uat_decode_adsb_mdb(uint8_t *frame, struct uat_adsb_mdb *mdb); +void uat_display_adsb_mdb(const struct uat_adsb_mdb *mdb, FILE *to); + +// +// UPLINK +// + +// assume 6 byte frames: 2 header bytes, 4 byte payload +// (TIS-B heartbeat with one address, or empty FIS-B APDU) +#define UPLINK_MAX_INFO_FRAMES (424/6) + +struct fisb_apdu { + int a_flag : 1; + int g_flag : 1; + int p_flag : 1; + int s_flag : 1; + int monthday_valid : 1; + int seconds_valid : 1; + + uint16_t product_id; + uint8_t month; // if monthday_valid + uint8_t day; // if monthday_valid + uint8_t hours; + uint8_t minutes; + uint8_t seconds; // if seconds_valid + + uint16_t length; + uint8_t *data; +}; + +struct uat_uplink_info_frame { + int is_fisb : 1; + + uint16_t length; + uint8_t type; + uint8_t *data; // points within the containing appdata + + // if is_fisb: + struct fisb_apdu fisb; +}; + +struct uat_uplink_mdb { + int position_valid : 1; + int utc_coupled : 1; + int app_data_valid : 1; + + // if position_valid: + double lat; + double lon; + + uint8_t slot_id; + uint8_t tisb_site_id; + + // if app_data_valid: + uint8_t app_data[424]; + unsigned num_info_frames; + struct uat_uplink_info_frame info_frames[UPLINK_MAX_INFO_FRAMES]; +}; + +void uat_decode_uplink_mdb(uint8_t *frame, struct uat_uplink_mdb *mdb); +void uat_display_uplink_mdb(const struct uat_uplink_mdb *mdb, FILE *to); + +#endif diff --git a/image/spindle/LICENSE b/image/spindle/LICENSE new file mode 100644 index 00000000..4b667dae --- /dev/null +++ b/image/spindle/LICENSE @@ -0,0 +1,26 @@ +Spindle is licensed under the terms of the MIT license reproduced below. + +##################################################### + +Copyright (c) 2012 Alex Bradbury + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/image/spindle/README.mkd b/image/spindle/README.mkd new file mode 100644 index 00000000..70aaa7de --- /dev/null +++ b/image/spindle/README.mkd @@ -0,0 +1,82 @@ +# spindle - a tool to help spin distribution images + +## Description +spindle is a set of scripts to aid building and working on a distribution +image. I've been using this tool to generate Debian wheezy images for the +Raspberry Pi, with the aim of producing reproducible, unbooted, clean setups +that require no manual intervention. It works by producing a series of image +files in QEMU's QED format (I exploit the format's support for backing files +and copy on write). + +It's not massively user friendly yet, but hopefully that should soon change +(and of course, patches are welcome). + +## Project links +* [Home](http://asbradbury.org/projects/spindle/) +* [Source](https://github.com/asb/spindle) +* [Issues](https://github.com/asb/spindle/issues) + +## Current scripts +* `setup_spindle_environment`: Sets up an schroot using wheezy and installs +the pre-requisites needed for spindle (mainly QEMU, as it turns out I haven't +used augeas). If you don't use this, please make sure you have at least QEMU +1.0.1 +* `wheezy-stage0`: Create and partition an SD card image, perform the initial +debootstrap on the host and copy the files to the SD image. +* `wheezy-stage1`: Complete second stage of debootstrap under QEMU after first +setting up a squashfs filesystem derived from Rob Landley's excellent +Aboriginal Linux. Setup dropbear. +* `wheezy-stage2`: Add in Raspberry Pi 'firmware' and do misc config (e.g. +fstab, network interfaces, hostname). The resulting image is bootable. +* `wheezy-stage3`: Install and configure a few useful packages (such as +ifplugd, sudo). +* `wheezy-stage4-lxde`: Set up the lxde desktop. +* `wheezy-stage4-lxde-edu`: Install MIT Scratch, Python development tools and +other packages. + +## Getting started +The following describes how to start building SD card images using spindle. +The process isn't massively user friendly for people with no experience at +all, but if you're currently doing this by hand then hopefully you'll find it +helpful. Note that executing the `downgrade_qemu` script is necessary on most +Debian and Ubuntu versions right now. + + sudo ./setup_spindle_environment my_spindle_chroot + sudo modprobe nbd max_part=16 + schroot -c spindle + sudo ./downgrade_qemu + ./wheezy-stage0 + ./wheezy-stage1 + ./wheezy-stage2 + ./wheezy-stage3 + ./wheezy-stage4-lxde + ./helper export_image_for_release out/stage4-lxde.qed stage4-lxde.img + +Now you can write stage4-lxde.img to SD card. + +Take a look at `wheezy-stage4-lxde` to see how to add your own stage if you +want further customisation. + +## HACKING +If you want to contribute, please do file issues on the bug tracker or send in +patches/pull requests. + +spindle is written in POSIX shell script and tries to make use of QEMU where +possible for manipulating the filesystem of the target being generated. Look +at the current scripts for examples. Please do talk to me if you're thinking +of any large refactoring. + +Good resources for shell scripting include: + +* `man dash` +* [The POSIX standard](http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html) +* [Insufficiently known POSIX shell features](http://apenwarr.ca/log/?m=201102#28) +* [Common shell scripting mistakes](http://www.pixelbeat.org/programming/shell_script_mistakes.html) + +## License +MIT. See the LICENSE file. + +## Contact +Author: Alex Bradbury +Email: +Homepage: diff --git a/image/spindle/common b/image/spindle/common new file mode 100755 index 00000000..c3bec7dc --- /dev/null +++ b/image/spindle/common @@ -0,0 +1,446 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +set -e + +. ./config + +run_qemu() { + rm fifo.out fifo.in || true + mkfifo fifo.out fifo.in + qemu-system-arm -M versatilepb -cpu arm1136-r2 -m 256 -nographic -no-reboot \ + -kernel zImage -hda qemu_rootfs.sqf -drive file=$1,index=1,media=disk,cache=unsafe \ + -drive file=wheezy_apt_cache.$IMGFORMAT,index=2,media=disk,cache=unsafe \ + -append "root=/dev/sda rw init=/sbin/init.sh panic=1 PATH=/bin:/sbin console=ttyAMA0 HOST=armv6l"\ + -net nic,model=rtl8139 -net user -redir tcp:22000::22 -daemonize -serial pipe:fifo \ + -pidfile qemu.pid + sleep 10 +} + +ssh_in_to_qemu() { + ssh -i qemu_arm_key -p 22000 -lroot localhost "$@" +} + +scp_in_to_qemu() { + scp -i qemu_arm_key -P 22000 "$1" root@localhost:"$2" +} + +scp_r_from_qemu() { + scp -r -i qemu_arm_key -P 22000 root@localhost:"$1" "$2" +} + +onvm_chroot() { + ssh_in_to_qemu chroot /mnt "$@" +} + +shutdown_qemu() { + ssh_in_to_qemu "sync && umount -a" || true + echo "exit" > fifo.in + sleep 5 + if [ -e qemu.pid ]; then + QEMU_PID=$(cat qemu.pid) + while [ -n "$QEMU_PID" ]; do + set +e + kill -0 $QEMU_PID 2>/dev/null + if [ $? -eq 0 ]; then + printf "Qemu pid %s not finished yet. Waiting\n" "$QEMU_PID" + sleep 1 + else + QEMU_PID="" + fi + set -e + done + fi + rm fifo.in + rm fifo.out +# sleep 15 +} + +attach_image_to_nbd() { + # use -v as we seem to have problems otherwise... + sudo qemu-nbd --nocache -v -c $2 $1 & + sleep 5 +} + +detach_image_from_nbd() { + sudo qemu-nbd -d $1 +} + +inspect_image() { + cd work + qemu-img create -f $IMGFORMAT -b ../$1 temp.$IMGFORMAT + # Sigh http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=390433 + trap '[ -p fifo.in ] && shutdown_qemu' EXIT + trap '[ -p fifo.in ] && shutdown_qemu; trap - INT; kill -INT $$' INT + run_qemu temp.$IMGFORMAT + ssh_in_to_qemu # -t chroot /mnt bash -l + shutdown_qemu + cd $OLDPWD +} + +branch_image() { + qemu-img create -f $IMGFORMAT -b $1 $2 +} + +write_image() { + attach_image_to_nbd $1 /dev/nbd0 && + sudo dd if=/dev/nbd0 of=$2 bs=4M && + detach_image_from_nbd /dev/nbd0 +} + +convert_image() { + qemu-img convert -f $IMGFORMAT -O raw $1 $2 +} + +export_image_for_release() { + cd work + qemu-img create -f $IMGFORMAT -b ../$1 temp.$IMGFORMAT + run_qemu temp.$IMGFORMAT + # Change mirror in sources.list to our preferred one. This will go horribly + # wrong if there is more than one entry in sources.list + onvm_chroot sh -l -ex <<\EOF +if grep armhf /etc/rpi-issue; then + sed -i /etc/apt/sources.list -e "s|^deb [^ ]*|deb http://mirrordirector.raspbian.org/raspbian/|" +else + sed -i /etc/apt/sources.list -e "s|^deb [^ ]*|deb http://http.debian.net/debian|" +fi +cp /usr/share/doc/raspi-config/sample_profile_d.sh /etc/profile.d/raspi-config.sh +apt-get update +# Default to a known-good nameserver +printf "nameserver 8.8.8.8\n" > /etc/resolv.conf +/etc/init.d/fake-hwclock stop # save current time +#rm /usr/bin/raspi-config +#cd /usr/bin +#wget https://raw.github.com/asb/raspi-config/master/raspi-config +#chmod +x raspi-config +cat < /etc/apt/sources.list.d/raspi.list +deb http://archive.raspberrypi.org/debian/ wheezy main +# Uncomment line below then 'apt-get update' to enable 'apt-get source' +#deb-src http://archive.raspberrypi.org/debian/ wheezy main +EOF1 +apt-get update +EOF + shutdown_qemu + attach_image_to_nbd temp.$IMGFORMAT /dev/nbd0 + sudo zerofree -v /dev/nbd0p2 + mkdir -p rootfs boot + sudo mount $BOOT_DEV boot + sudo mount $ROOT_DEV rootfs + sudo cp rootfs/etc/rpi-issue boot/issue.txt + sudo wget http://asbradbury.org/tmp/raspi/oracle_license.txt -O boot/LICENSE.oracle + [ -n "$RASPBIAN" ] && sudo mv rootfs/etc/ld.so.preload.disable rootfs/etc/ld.so.preload + if [ -n "$NOOBS" ]; then + mkdir -p ../noobs + sudo tar -C boot -cJf ../noobs/boot.tar.xz --preserve . + sudo tar -C rootfs -cJf ../noobs/root.tar.xz --preserve . + cat <<\EOF > ../noobs/partition_setup.sh +#!/bin/sh + +set -ex + +if [ -z "$part1" ] || [ -z "$part2" ]; then + printf "Error: missing environment variable part1 or part2\n" 1>&2 + exit 1 +fi + +mkdir -p /tmp/1 /tmp/2 + +mount "$part1" /tmp/1 +mount "$part2" /tmp/2 + +sed /tmp/1/cmdline.txt -i -e "s|root=/dev/[^ ]*|root=${part2}|" +sed /tmp/2/etc/fstab -i -e "s|^.* / |${part2} / |" +sed /tmp/2/etc/fstab -i -e "s|^.* /boot |${part1} /boot |" + +umount /tmp/1 +umount /tmp/2 +EOF + cat <<\EOF > ../noobs/partitions.json +{ + "partitions": [ + { + "label": "boot", + "filesystem_type": "FAT", + "partition_size_nominal": 60, + "want_maximised": false, + "uncompressed_tarball_size": 19 + }, + { + "label": "root", + "filesystem_type": "ext4", + "partition_size_nominal": 2500, + "want_maximised": true, + "mkfs_options": "-O ^huge_file", + "uncompressed_tarball_size": 2280 + } + ] +} +EOF + + cat <<\EOF > ../noobs/flavours.json +{ + "flavours": [ + { + "name": "Raspbian - Boot to Scratch", + "description": "A version of Raspbian that boots straight into Scratch" + }, + { + "name": "Raspbian", + "description": "A Debian wheezy port, optimised for the Raspberry Pi" + } + ] +} +EOF + + cat <<\EOF > ../noobs/os.json +{ + "name": "Raspbian", + "version": "wheezy", + "release_date": "2015-02-16", + "kernel": "3.18", + "description": "A community-created port of Debian wheezy, optimised for the Raspberry Pi" +} +EOF + + cat <<\EOF > ../noobs/release_notes.txt +2015-02-16: + * Newer firmware with various fixes + * New Sonic Pi release + * Pi2 compatible RPi.GPIO + * Updated Wolfram Mathematica +2015-01-31: + * Support for Pi2 + * Newer firmware + * New Sonic Pi release + * Updated Scratch + * New Wolfram Mathematica release + * Updated Epiphany +2014-12-24: + * Fix regression with omission of python-pygame +2014-12-22: + * New firmware with variosu fixes and improvements + * New UI configuration for lxde + * Various package updates + * python3-pygame preinstalled + * 'nuscratch', scratch running on the Cog StackVM + * Misc other changes +2014-09-09: + * New firmware with various fixes and improvements + * Minecraft Pi pre-installed + * Sonic Pi upgraded to 2.0 + * Include Epiphany browser work from Collabora + * Switch to Java 8 from Java 7 + * Updated Mathematica + * Misc minor configuration changes +2014-06-20: + * New firmware with various fixes, and kernel bugfix +2014-06-02: + * Many, many firmware updates with major USB improvements + * pyserial installed by default + * picamera installed by default +2014-01-07: + * Firmware updated + * Some space saved on the root filesystem +2013-12-20: + * Firmware updated, includes V4L2 fixes + * Update omxplayer +2013-12-18: + * Firmware updated and now using kernel 3.10. Many, many improvements + * fbturbo XOrg driver is now included and enabled by default. Thanks to + ssvb https://github.com/ssvb/xf86-video-fbturbo + * Update Scratch image with further bug fixes + * Include Wolfram Mathematica + * Update to PyPy 2.2 + * Update omxplayer + * Include v4l-utils for use with experimental V4L2 Raspberry Pi camera driver + * Update squeak-vm to fix issues with loading JPEGs +2013-09-25: + * Update Scratch image for further performance improvements + * Include Oracle JDK + * At least a 4GiB SD card is now required (see above) + * Include PyPy 2.1 + * Include base piface packages + * Update raspi-config to include bugfix for inheriting language settings + from NOOBS +2013-09-10: + * Updated to current top of tree firmware + * Update squeak-vm, including fastblit optimised for the Raspbery Pi + * Include Sonic Pi and a fixed jackd2 package + * Support boot to Scratch + * Inherit keyboard and language settings from NOOBS +EOF + wget http://asbradbury.org/tmp/raspi/marketing.tar -O ../noobs/marketing.tar + + fi + sleep 10 + sudo umount $BOOT_DEV + sudo umount $ROOT_DEV + detach_image_from_nbd /dev/nbd0 + convert_image temp.$IMGFORMAT ../$2 +} + +disable_starting_services() { + ssh_in_to_qemu chroot /mnt sh -ex - < /usr/sbin/policy-rc.d +chmod 755 /usr/sbin/policy-rc.d +EOF +} + +allow_starting_services() { + ssh_in_to_qemu chroot /mnt sh -ex - < /etc/rpi-issue +EOF +} + +# Create a list of all installed packages, all manually installed packages and +# the debconf selections +fingerprint_debian() { + FNGPRNT_DIR="$CURIMG.fingerprint" && + onvm_chroot sh -l -e < dpkg_selections +dpkg -l > dpkg_l +apt-mark showauto > apt-mark_showauto +debconf-get-selections > debconf_selections +EOF + scp_r_from_qemu "/mnt/tmp/$FNGPRNT_DIR" . && + cd "$FNGPRNT_DIR" && + grep "[[:space:]]install$" dpkg_selections | cut -f1 | sort apt-mark_showauto - | uniq -u > manually_installed_packages && + cd "$OLDPWD" +} + +finish_image() { + chmod -w $CURIMG && + mkdir -p ../$OUTDIR && + mv -f $CURIMG ../$OUTDIR && + if [ -d "$CURIMG.fingerprint" ]; then + rm -rf "../$OUTDIR/$CURIMG.fingerprint" && + mv "$CURIMG.fingerprint" ../$OUTDIR + fi && + FINISHED_SUCCESSFULLY=1 && + printf "Completed script successfully\n" +} + +# Usage: ask_yn default prompt [printf_args...] +# Read a Yes / No user response. If $ASK_YN_USE_DEFAULT is set, assume the +# default. Return of 0 indicates a yes, and non-zero is no. +ask_yn() { + OPT="y/n" + case "$1" in + y*|Y*) OPT="Y/n"; RET=0;; + n*|N*) OPT="y/N"; RET=1;; + *) die "Programmer error: invalid argument to ask_yn" + esac + shift + if [ "$ASK_YN_USE_DEFAULT" ]; then + return $RET + else + PROMPT=$1 + shift + printf "$PROMPT [$OPT]: " "$@" && read YN + case "$YN" in + y*|Y*) return 0;; + n*|N*) return 1;; + "") return $RET + esac + fi +} + +# Usage: read_val var default prompt [printf_args...] +read_val() { + RV_VAR=$1 + RV_DEFAULT=$2 + RV_PROMPT=$3 + shift 3 + printf "$RV_PROMPT [$RV_DEFAULT]: " "$@" && read RV_VAL + [ -z "$RV_VAL" ] && RV_VAL=$RV_DEFAULT + eval "$RV_VAR=\$RV_VAL" +} + +die() { + FMTSTR=$1 + shift + printf "Died: $FMTSTR\n" "$@" >&2 + exit 1 +} + +# do a task +# Inspects $DOTASK_MODE to see if the user: +# e*: wants to have the command echoed +# q*: wants to be questioned as to whether to run, skip or quit. +# If the task fails it asks the user whether to quit (default: y), or if +# $DOTASK_QUIT_ON_FAILURE is set then quit without asking the user. +dotask() { + case "$DOTASK_MODE" in + e*) printf "Run: '%s'\n" "$*"; false;; + q*) printf "Run: '%s' [Y/n/q] ? " "$*" && read SHOULD_RUN; + case "$SHOULD_RUN" in + n*|N*|s*|S*) printf "Skip '%s'\n" "$*";; + q*|Q*) die "Exit at user request";; + *) false;; + esac;; + *) false;; + esac || "$@" || { RC=$? + [ -z "$DOTASK_QUIT_ON_FAILURE" ] && printf "'%s' failed (returned $RC) - Quit? [Y/n] : " "$*" && read YN + case "$YN" in n*|N*) true ;; *) die "Failed while performing task: %s" "$*";; esac; + } +} + + +FINISHED_SUCCESSFULLY=0 +CLEANED_UP=0 +WAS_TRAPPED=0 + +# Sigh http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=390433 +trap 'WAS_TRAPPED=1; universal_cleanup' EXIT +trap 'WAS_TRAPPED=1; universal_cleanup; trap - INT; kill -INT $$' INT + +universal_cleanup() { + set +e + if [ $CLEANED_UP -ne 1 ]; then + printf "Initiating cleanup\n" + trap - INT + trap - EXIT + [ -p fifo.in ] && shutdown_qemu + [ -b "$BOOT_DEV" ] && sudo umount $BOOT_DEV + [ -b "$ROOT_DEV" ] && sudo umount $ROOT_DEV + [ -b "$NBD_DEV" ] && sudo umount $NBD_DEV + [ -b "$NBD_DEV" ] && detach_image_from_nbd $NBD_DEV + fi + if [ $WAS_TRAPPED -eq 1 ] && [ $FINISHED_SUCCESSFULLY -eq 0 ]; then + printf "Did not complete script successfully\n" + fi + set -e +} diff --git a/image/spindle/config b/image/spindle/config new file mode 100644 index 00000000..8309565f --- /dev/null +++ b/image/spindle/config @@ -0,0 +1,25 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +RASPBIAN=1 + +if [ -n "$RASPBIAN" ]; then + TGT_ARCH=armhf + DEB_MIRROR=http://mirror.ox.ac.uk/sites/archive.raspbian.org/archive/raspbian/ +else + TGT_ARCH=armel + #DEB_MIRROR=http://ftp.uk.debian.org/debian + DEB_MIRROR=http://www-uxsup.csx.cam.ac.uk/pub/linux/debian +fi +NBD_DEV=/dev/nbd0 +BOOT_DEV="$NBD_DEV"p1 +ROOT_DEV="$NBD_DEV"p2 + +IMGFORMAT=qed +SDSIZE=3125M + +# Changing these isn't currently supported +WORKDIR=work +OUTDIR=out diff --git a/image/spindle/downgrade_qemu b/image/spindle/downgrade_qemu new file mode 100755 index 00000000..fb094f2b --- /dev/null +++ b/image/spindle/downgrade_qemu @@ -0,0 +1,25 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +if [ $(id -u) -ne 0 ]; then + printf "Script must be run as root\n" + exit 1 +fi + +mkdir -p qemu_downgrade +cd qemu_downgrade +MYARCH=$(dpkg --print-architecture) +wget http://snapshot.debian.org/archive/debian/20120306T041809Z/pool/main/q/qemu/qemu_1.0.1%2Bdfsg-1_${MYARCH}.deb +wget http://snapshot.debian.org/archive/debian-ports/20120310T114351Z/pool/main/q/qemu/qemu-keymaps_1.0.1%2Bdfsg-1_all.deb +wget http://snapshot.debian.org/archive/debian/20120306T041809Z/pool/main/q/qemu/qemu-system_1.0.1%2Bdfsg-1_${MYARCH}.deb +wget http://snapshot.debian.org/archive/debian/20120306T041809Z/pool/main/q/qemu/qemu-user_1.0.1%2Bdfsg-1_${MYARCH}.deb +wget http://snapshot.debian.org/archive/debian/20120308T042544Z/pool/main/q/qemu/qemu-utils_1.0.1%2Bdfsg-1_${MYARCH}.deb +wget http://snapshot.debian.org/archive/debian/20120303T221521Z/pool/main/c/ceph/librbd1_0.43-1_${MYARCH}.deb +wget http://snapshot.debian.org/archive/debian/20120303T221521Z/pool/main/c/ceph/librados2_0.43-1_${MYARCH}.deb +sudo dpkg -i * +sudo apt-get install -f + +printf "Hopefully downgraded to qemu 1.0.1 successfully, take a look...\n" +dpkg -l | grep qemu diff --git a/image/spindle/extras/export_img_over_nfs b/image/spindle/extras/export_img_over_nfs new file mode 100755 index 00000000..c2fd2bb8 --- /dev/null +++ b/image/spindle/extras/export_img_over_nfs @@ -0,0 +1,24 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +# NOTE: this script is rather hacky, in that you probably want to run it +# outside of the schroot set up for spindle...Ideally we'd be booting with an +# nbd rootfs anyway, so this is just a stopgap +set -ex + +. ./common + +[ "$1" ] || die "No image name given" +[ -b "$NBD_DEV" ] || die "nbd device '%s' does not exist. Try sudo modprobe nbd max_part=16" "$NBD_DEV" +dotask schroot -c spindle -- qemu-img create -f qed -b $1 nfs.qed +mkdir -p nfs_export +dotask sudo schroot -c spindle -- qemu-nbd --nocache -v -c /dev/nbd0 nfs.qed & +sleep 5 +dotask sudo mount /dev/nbd0p2 nfs_export # have to do it outside of schroot! +NFS_PATH=$(readlink -m nfs_export) +dotask sudo exportfs -v -i -o async,rw,no_root_squash ":$NFS_PATH" +while true; do + sleep 60 +done diff --git a/image/spindle/f2fsify b/image/spindle/f2fsify new file mode 100755 index 00000000..c9fe00f4 --- /dev/null +++ b/image/spindle/f2fsify @@ -0,0 +1,73 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +# TODO: this script is very rough and ready. + +set -ex + +printf "NOTICE: this script is only intended to work in an unmodified form\n" +printf "a recent Arch Linux install (with 3.8.x kernel).\n" +printf "You'll need to use grow_image to expand the rootfs above 2GiB\n" +printf "To get all deps, you want to do:\n\n" +# 1.1.0 f2fs-tools release is no good as we need commit +# http://git.kernel.org/cgit/linux/kernel/git/jaegeuk/f2fs-tools.git/commit/?id=07b815d39509135e35856cfd30b83930a0af8250 +cat <<"EOF" +pacman -S wget unzip zip parted git +git clone git://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs-tools.git +cd f2fs-tools +autoreconf -fi +./configure && make && make install +curl https://aur.archlinux.org/packages/f2/f2fs-tools-git/f2fs-tools-git.tar.gz | tar xz +cd f2fs-tools-git +makepkg -si +cd .. +EOF +sleep 5 + +if [ $(id -u) -ne 0 ]; then + printf "Script must be run as root\n" + exit 1 +fi + +if [ ! "$1" ]; then + printf "Usage: ./f2fsify IMAGEFILE\n" + exit 1 +fi +IMAGEFILE="$1" + +modprobe f2fs +modprobe loop +BOOT_START=$(parted "$IMAGEFILE" -ms unit s p | grep "^1" | cut -f 2 -d:) +BOOT_START_BYTES=$((${BOOT_START%s}*512)) +ROOT_START=$(parted "$IMAGEFILE" -ms unit s p | grep "^2" | cut -f 2 -d:) +ROOT_START_BYTES=$((${ROOT_START%s}*512)) + +# Back up the rootfs content +LOOP_DEV=$(losetup -f) +losetup --offset "$ROOT_START_BYTES" -v $LOOP_DEV "$IMAGEFILE" +mkdir -p rootfs +mount $LOOP_DEV rootfs +tar -cz rootfs > rootfs.tar.gz +umount rootfs + +# Make the f2fs rootfs +mkfs.f2fs $LOOP_DEV +mount $LOOP_DEV rootfs +tar -xvf rootfs.tar.gz +sed -i rootfs/etc/fstab -e "s/ext4/f2fs/" +cat rootfs/etc/fstab +umount rootfs +losetup -d $LOOP_DEV + +# Fix cmdline.txt on boot partition +losetup --offset "$BOOT_START_BYTES" -v $LOOP_DEV "$IMAGEFILE" +mkdir -p boot +mount $LOOP_DEV boot +sed -i boot/cmdline.txt -e "s/ext4/f2fs/" +cat boot/cmdline.txt +umount boot +losetup -d $LOOP_DEV + +printf "Done!\n" diff --git a/image/spindle/grow_image b/image/spindle/grow_image new file mode 100755 index 00000000..844c061f --- /dev/null +++ b/image/spindle/grow_image @@ -0,0 +1,66 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +# TODO: this script is very rough and ready. Currently grows an image to ~4GiB + +set -x + +if [ $(id -u) -ne 0 ]; then + printf "Script must be run as root\n" + exit 1 +fi + +if [ ! "$1" ]; then + printf "Usage: ./grow_image IMAGEFILE\n" + exit 1 +fi +IMAGEFILE="$1" + +# Grow the image file +dd of="$IMAGEFILE" bs=1 seek=3700M count=0 + +# Just use losetup to create a loopback device. fdisk will operate on files, +# but it complains about the number of cylinders not being set + +LOOP_DEV=$(losetup -f) + +losetup -v $LOOP_DEV "$IMAGEFILE" + +# Partition resizing code same as in http://github.com/asb/raspi-config +PART_START=$(parted "$IMAGEFILE" -ms unit s p | grep "^2" | cut -f 2 -d:) +if [ ! "$PART_START" ]; then + printf "Failed to extract root partition offset\n" +fi +fdisk -cu $LOOP_DEV < + + + + + + + + + + + + + + + + diff --git a/image/spindle/qa_checklist.mkd b/image/spindle/qa_checklist.mkd new file mode 100644 index 00000000..38365793 --- /dev/null +++ b/image/spindle/qa_checklist.mkd @@ -0,0 +1,31 @@ +# QA checklist +This document contains a list of things which should be tested before +releasing a distribution image as 'finished'. + +## Platform specific +* The latest version of the 'firmware' is included +* /dev/vchiq is owned by group 'video' + +## Basic configuration +* ifplugd is installed and properly configured +* A default user has been created +* If a desktop environment is installed, it defaults to Raspberry Pi branding +* sshd either starts at boot or is trivial to enable (and this mechanism has +been tested) +* An NTP daemon is installed and configured +* eth0 isn't renamed when the same SD card is put in a different Raspberry Pi +* sudo is installed and configured +* ssh host keys have been removed and will be generated on first boot or first +invocation of sshd +* `apt-get clean` has been run to remove cached packages + +## Functionality tests for desktop images +* When plugging in a USB memory stick, it is automatically mounted +* Wireless dongles with free firmware work out of the box +* The default browser works for GMail and other JavaScript-heavy sites +* `/opt/vc/bin/tvservice` has been verified to work +* Verify after installing gparted it can be launched from the graphical menu +and that in pcmanfm 'open folder as root' works (checks PolicyKit +configuration). +* Run fsck on the root partition to ensure the image generation process +produced a clean filesystem diff --git a/image/spindle/setup_spindle_environment b/image/spindle/setup_spindle_environment new file mode 100755 index 00000000..8043a544 --- /dev/null +++ b/image/spindle/setup_spindle_environment @@ -0,0 +1,117 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +set -e + +SCHROOT_SPINDLE_CONF=/etc/schroot/chroot.d/spindle +ETC_SCHROOT_SPINDLE=/etc/schroot/spindle + +if [ $(id -u) -ne 0 ]; then + printf "Script must be run as root\n" + exit 1 +fi + +show_usage() { + printf "./setup_spindle_environment DIRNAME\n" +} + +ensure_installed() { + for PKG in "$@"; do + dpkg --get-selections "$PKG" | grep -q "[[:space:]]install$" || apt-get install "$PKG" || die "Failed to install $PKG" + done +} + +[ "$1" ] || show_usage +TARGET_DIR=$(readlink -m "$1") + +. ./common + +ensure_installed schroot debootstrap debian-archive-keyring + +ask_yn y "About to set up chroot in '%s'. Ok?" "$TARGET_DIR" || die "You said no. Exiting" +read_val USERS "${USERS-$SUDO_USER}" "What user should be able to schroot?" +[ -z "$USERS" ] && die "You didn't list any users" + +if [ -e "$SCHROOT_SPINDLE_CONF" ]; then + ask_yn y "Overwrite existing config '$SCHROOT_SPINDLE_CONF'?" || die "You said no. Exiting" + rm -rf "$ETC_SCHROOT_SPINDLE" +fi + +mkdir -p $(dirname "$SCHROOT_SPINDLE_CONF") +cat << EOF > "$SCHROOT_SPINDLE_CONF" || die "Failed to configure schroot" +[spindle] +type=directory +description=Debian wheezy for spindle +directory=$TARGET_DIR +preserve-environment=true +script-config=spindle/config +users=$USERS +groups=$USERS +root-groups=root +EOF + +printf "\nCreated %s:\n\n" "$SCHROOT_SPINDLE_CONF" +cat "$SCHROOT_SPINDLE_CONF" +printf "\n" + +if [ ! -e $ETC_SCHROOT_SPINDLE ]; then + if [ -e /etc/schroot/default ]; then + cp -a /etc/schroot/default $ETC_SCHROOT_SPINDLE + else + ## Should work for schroot 1.4 as in Ubuntu 10.04 + mkdir $ETC_SCHROOT_SPINDLE + for FN in copyfiles mount nssdatabases; do + cp -a /etc/schroot/${FN}-defaults $ETC_SCHROOT_SPINDLE/$FN + done + mv $ETC_SCHROOT_SPINDLE/mount $ETC_SCHROOT_SPINDLE/fstab + fi +fi + +cat << EOF > $ETC_SCHROOT_SPINDLE/config +# Filesystems to mount inside the chroot. +FSTAB="$ETC_SCHROOT_SPINDLE/fstab" + +# Files to copy from the host system into the chroot. +COPYFILES="$ETC_SCHROOT_SPINDLE/copyfiles" + +# System NSS databases to copy into the chroot. +NSSDATABASES="$ETC_SCHROOT_SPINDLE/nssdatabases" +EOF + +# We want rbind of /home for cases where /home is provided by autofs +# See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=648459 +sed $ETC_SCHROOT_SPINDLE/fstab -e "s|^\(/home.*\),bind\(.*\)|\1,rbind\2|" > $ETC_SCHROOT_SPINDLE/fstab.new || true +mv $ETC_SCHROOT_SPINDLE/fstab.new $ETC_SCHROOT_SPINDLE/fstab || true +# Delete rather than comment out, as schroot 1.4 has no support for comments +# in nssdatabases +sed -i $ETC_SCHROOT_SPINDLE/nssdatabases -e "/^passwd$/d" +sed -i $ETC_SCHROOT_SPINDLE/nssdatabases -e "/^shadow$/d" +sed -i $ETC_SCHROOT_SPINDLE/nssdatabases -e "/^group$/d" + +debootstrap \ + --include="qemu,bash-completion,augeas-tools,debootstrap,less,\ + sudo,parted,openssh-client,e2fsprogs,dosfstools,squashfs-tools,bzip2,git,zerofree" \ + wheezy "$TARGET_DIR" http://http.debian.net/debian || die "Debootstrap failed" +rm "$TARGET_DIR/dev/shm" # required on my 10.04 for schroot to work + +# Copy passwd/shadow/group databases +getent passwd > "$TARGET_DIR/etc/passwd" +getent shadow > "$TARGET_DIR/etc/shadow" +getent group > "$TARGET_DIR/etc/group" + +# Disable launch of services in chroot +printf '#!/bin/sh\nexit 101\n' > "$TARGET_DIR"/usr/sbin/policy-rc.d +chmod 755 "$TARGET_DIR"/usr/sbin/policy-rc.d + +# Make sure sudo is enabled within chroot +chmod +w "$TARGET_DIR"/etc/sudoers +echo "%admin ALL=(ALL) ALL" >> "$TARGET_DIR"/etc/sudoers +chmod -w "$TARGET_DIR"/etc/sudoers + +printf "Please enter the password you would like for your schroot user (needed for sudo)\n" +schroot -c spindle --directory=/tmp passwd $USERS + +printf "\nchroot created at '%s'. Use \`schroot -c spindle\` to enter it\n" "$TARGET_DIR" +FINISHED_SUCCESSFULLY=1 diff --git a/image/spindle/shrink_image b/image/spindle/shrink_image new file mode 100755 index 00000000..d7266eaa --- /dev/null +++ b/image/spindle/shrink_image @@ -0,0 +1,74 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +# TODO: this script is very rough and ready. Currently shrinks ext4 image to +# minimum size. Should really be refactored and merged with grow_image + +set -x + +if [ $(id -u) -ne 0 ]; then + printf "Script must be run as root\n" + exit 1 +fi + +if [ ! "$1" ]; then + printf "Usage: ./shrink_image IMAGEFILE\n" + exit 1 +fi +IMAGEFILE="$1" + +# Partition resizing code same as in http://github.com/asb/raspi-config +PART_START=$(parted "$IMAGEFILE" -ms unit s p | grep "^2" | cut -f 2 -d:) +if [ ! "$PART_START" ]; then + printf "Failed to extract root partition offset\n" +fi + +# Just use losetup to create a loopback device. fdisk will operate on files, +# but it complains about the number of cylinders not being set + +LOOP_DEV=$(losetup -f) + +# We know the offset anyway so let's just use losetup directly rather than +# bothering with kpartx +PART_START_BYTES=$((${PART_START%s}*512)) +losetup --offset "$PART_START_BYTES" -v $LOOP_DEV "$IMAGEFILE" + +# Sanity check +file -s $LOOP_DEV + +# Run resize2fs +e2fsck -f $LOOP_DEV +resize2fs -M $LOOP_DEV + +EXT4_BLCKCNT=$(sudo tune2fs -l /dev/loop0 | grep "^Block count" | cut -d ":" -f 2 | tr -d ' ') +EXT4_BLKSIZE=$(sudo tune2fs -l /dev/loop0 | grep "^Block size" | cut -d ":" -f 2 | tr -d ' ') +EXT4_FS_BYTES=$(($EXT4_BLCKCNT * $EXT4_BLKSIZE)) + +# Unmount it all +losetup -d $LOOP_DEV + +NEW_IMG_BYTES=$(($EXT4_FS_BYTES + $PART_START_BYTES + 4194304)) + +# Shrink the image file +dd if=/dev/null of="$IMAGEFILE" bs=$NEW_IMG_BYTES seek=1 + +losetup -v $LOOP_DEV "$IMAGEFILE" + +fdisk -cu $LOOP_DEV < qemu_rootfs/etc/dropbear/dropbear_dss_host_key && + printf "%s" "$PRIVATE_SSH_KEY" > qemu_arm_key && + chmod 600 qemu_arm_key && + printf "%s" "$PUBLIC_SSH_KEY" > qemu_arm_key.pub && + cp -a qemu_arm_key.pub qemu_rootfs/root +} + +INIT_SH=$(cat <<\EOF +#!/bin/sh + +export HOME=/home/root + +mount -t proc proc proc +mount -t sysfs sys sys +mount -t devtmpfs dev dev +mkdir -p dev/pts +mount -t devpts dev/pts dev/pts + +export PS1='($HOST) \w \$ ' + +export PATH + +ifconfig eth0 10.0.2.15 +route add default gw 10.0.2.2 + +[ "$(date +%s)" -lt 1000 ] && rdate 10.0.2.2 # or time-b.nist.gov + +mount -t tmpfs /tmp /tmp + +mount -o noatime /dev/sdb2 /mnt +[ -d /mnt/tmp ] && mount --bind /tmp /mnt/tmp + +mount -t tmpfs /home /home +mkdir -p /home/root +cd $HOME + +mkdir -p /home/root/.ssh +cp -a /root/qemu_arm_key.pub /home/root/.ssh/authorized_keys +chmod 600 /home/root/.ssh/authorized_keys +dropbear -E -s +exec /sbin/oneit -c /dev/ttyAMA0 /bin/ash +EOF +) + +replace_init_sh() { + printf "%s" "$INIT_SH" > qemu_rootfs/sbin/init.sh +} + +do_second_stage_debootstrap() { + onvm_chroot sh -ex - < /etc/udev/rules.d/10-local-rpi.rules +EOF +} + +cd $WORKDIR +dotask branch_image ../$OUTDIR/stage0.$IMGFORMAT $CURIMG +dotask download_if_necessary http://asbradbury.org/tmp/raspi/simple-root-filesystem-armv6l.tar.bz2 +dotask download_if_necessary http://asbradbury.org/tmp/raspi/system-image-armv6l.tar.bz2 +dotask download_if_necessary http://asbradbury.org/tmp/raspi/dropbearmulti-armv6l +[ -f zImage ] || tar -xf system-image-armv6l.tar.bz2 --strip-components=1 system-image-armv6l/zImage +tar -xvf simple-root-filesystem-armv6l.tar.bz2 +rm -rf qemu_rootfs +mv simple-root-filesystem-armv6l qemu_rootfs +dotask setup_dropbear +dotask replace_init_sh +dotask mksquashfs qemu_rootfs qemu_rootfs.sqf -noappend -all-root +dotask run_qemu $CURIMG +dotask do_second_stage_debootstrap +#dotask configure_udev +dotask shutdown_qemu +dotask finish_image diff --git a/image/spindle/wheezy-stage2 b/image/spindle/wheezy-stage2 new file mode 100755 index 00000000..2224a30f --- /dev/null +++ b/image/spindle/wheezy-stage2 @@ -0,0 +1,359 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +set -ex + +. ./common + +CURIMG=stage2.$IMGFORMAT + +# Make changes to /etc/skel before we start adding new users +modify_etc_skel() { + onvm_chroot sh -l -ex - <<\EOF +# Essentially the same as the default debian one, but enable color by default +# and slightly tweak PS1 +cat <<\EOF1 > /etc/skel/.bashrc +# ~/.bashrc: executed by bash(1) for non-login shells. +# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) +# for examples + +# If not running interactively, don't do anything +[ -z "$PS1" ] && return + +# don't put duplicate lines or lines starting with space in the history. +# See bash(1) for more options +HISTCONTROL=ignoreboth + +# append to the history file, don't overwrite it +shopt -s histappend + +# for setting history length see HISTSIZE and HISTFILESIZE in bash(1) +HISTSIZE=1000 +HISTFILESIZE=2000 + +# check the window size after each command and, if necessary, +# update the values of LINES and COLUMNS. +shopt -s checkwinsize + +# If set, the pattern "**" used in a pathname expansion context will +# match all files and zero or more directories and subdirectories. +#shopt -s globstar + +# make less more friendly for non-text input files, see lesspipe(1) +#[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" + +# set variable identifying the chroot you work in (used in the prompt below) +if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then + debian_chroot=$(cat /etc/debian_chroot) +fi + +# set a fancy prompt (non-color, unless we know we "want" color) +case "$TERM" in + xterm-color) color_prompt=yes;; +esac + +# uncomment for a colored prompt, if the terminal has the capability; turned +# off by default to not distract the user: the focus in a terminal window +# should be on the output of commands, not on the prompt +force_color_prompt=yes + +if [ -n "$force_color_prompt" ]; then + if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then + # We have color support; assume it's compliant with Ecma-48 + # (ISO/IEC-6429). (Lack of such support is extremely rare, and such + # a case would tend to support setf rather than setaf.) + color_prompt=yes + else + color_prompt= + fi +fi + +if [ "$color_prompt" = yes ]; then + PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\] \[\033[01;34m\]\w \$\[\033[00m\] ' +else + PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' +fi +unset color_prompt force_color_prompt + +# If this is an xterm set the title to user@host:dir +case "$TERM" in +xterm*|rxvt*) + PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" + ;; +*) + ;; +esac + +# enable color support of ls and also add handy aliases +if [ -x /usr/bin/dircolors ]; then + test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" + alias ls='ls --color=auto' + #alias dir='dir --color=auto' + #alias vdir='vdir --color=auto' + + alias grep='grep --color=auto' + alias fgrep='fgrep --color=auto' + alias egrep='egrep --color=auto' +fi + +# some more ls aliases +#alias ll='ls -l' +#alias la='ls -A' +#alias l='ls -CF' + +# Alias definitions. +# You may want to put all your additions into a separate file like +# ~/.bash_aliases, instead of adding them here directly. +# See /usr/share/doc/bash-doc/examples in the bash-doc package. + +if [ -f ~/.bash_aliases ]; then + . ~/.bash_aliases +fi + +# enable programmable completion features (you don't need to enable +# this, if it's already enabled in /etc/bash.bashrc and /etc/profile +# sources /etc/bash.bashrc). +if [ -f /etc/bash_completion ] && ! shopt -oq posix; then + . /etc/bash_completion +fi +EOF1 +EOF +} + +# For now we add a root password (this is a totally minimal system). It should be +# disabled at a later stage +configure_users() { + onvm_chroot sh -l -ex - < /etc/fstab +proc /proc proc defaults 0 0 +/dev/mmcblk0p1 /boot vfat defaults 0 2 +/dev/mmcblk0p2 / ext4 defaults,noatime 0 1 +EOF2 +# Automatically repair if fsck finds issues +sed -i /etc/default/rcS -e "s/^#FSCKFIX=no/FSCKFIX=yes/" +EOF1 +} + +configure_sources_list() { + SECTIONS="main contrib non-free" + if [ -n "$RASPBIAN" ]; then + SECTIONS="$SECTIONS rpi" + fi + onvm_chroot sh -l -e - < /etc/apt/sources.list +printf "# Uncomment line below then 'apt-get update' to enable 'apt-get source'\n" >> /etc/apt/sources.list +printf "#deb-src $DEB_MIRROR wheezy $SECTIONS\n" >> /etc/apt/sources.list +EOF +} + +configure_network_interfaces() { + onvm_chroot sh -l -e - <<\EOF1 +cat < /etc/network/interfaces +auto lo + +iface lo inet loopback +iface eth0 inet dhcp +EOF2 +# Use the qemu default nameserver +printf "nameserver 10.0.2.3\n" > /etc/resolv.conf +EOF1 +} + +configure_hostname() { + onvm_chroot sh -l -e - < /etc/hostname +printf "127.0.1.1\traspberrypi\n" >> /etc/hosts +EOF +} + +disable_eth_and_wlan_renaming() { + onvm_chroot sh -l -e - <> /etc/inittab +printf "T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100\n" >> /etc/inittab +EOF +} + +adjust_motd() { + onvm_chroot sh -l -e <<\EOF1 +cat <> /etc/motd.tail + +Type 'startx' to launch a graphical session + +EOF2 +EOF1 +} + +install_firmware() { + onvm_chroot sh -l -e - < /etc/apt/sources.list.d/raspi.list +echo "deb http://archive.raspberrypi.org/debian/ wheezy main untested" > /etc/apt/sources.list.d/raspi.list +apt-key add - < /etc/apt/sources.list.d/raspi.list +apt-get update +echo "dwc_otg.lpm_enable=0 console=ttyAMA0,115200 \ +console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait" > /boot/cmdline.txt +cat < /boot/config.txt +# For more options and information see +# http://www.raspberrypi.org/documentation/configuration/config-txt.md +# Some settings may impact device functionality. See link above for details + +# uncomment if you get no picture on HDMI for a default "safe" mode +#hdmi_safe=1 + +# uncomment this if your display has a black border of unused pixels visible +# and your display can output without overscan +#disable_overscan=1 + +# uncomment the following to adjust overscan. Use positive numbers if console +# goes off screen, and negative if there is too much border +#overscan_left=16 +#overscan_right=16 +#overscan_top=16 +#overscan_bottom=16 + +# uncomment to force a console size. By default it will be display's size minus +# overscan. +#framebuffer_width=1280 +#framebuffer_height=720 + +# uncomment if hdmi display is not detected and composite is being output +#hdmi_force_hotplug=1 + +# uncomment to force a specific HDMI mode (this will force VGA) +#hdmi_group=1 +#hdmi_mode=1 + +# uncomment to force a HDMI mode rather than DVI. This can make audio work in +# DMT (computer monitor) modes +#hdmi_drive=2 + +# uncomment to increase signal to HDMI, if you have interference, blanking, or +# no display +#config_hdmi_boost=4 + +# uncomment for composite PAL +#sdtv_mode=2 + +#uncomment to overclock the arm. 700 MHz is the default. +#arm_freq=800 + +EOF1 +# cat < /etc/modprobe.d/raspi-blacklist.conf +# # blacklist spi and i2c by default (many users don't need them) + +# blacklist spi-bcm2708 +# blacklist i2c-bcm2708 +# blacklist snd-soc-pcm512x +# blacklist snd-soc-wm8804 +# EOF2 +cat < /etc/modprobe.d/ipv6.conf +# Don't load ipv6 by default +alias net-pf-10 off +#alias ipv6 off +EOF3 +EOF +} + +# Relies on the fact my repo was already added in install_firmware +install_raspi_config() { + onvm_chroot sh -l -e - <<\EOF +apt-get install -y raspi-config +# Automatically log in on tty1 until raspi-config configures it to do +# otherwise +sed -i /etc/inittab -e "s|^\(1:2345.*getty.*tty1.*\)|\ +#\1 # RPICFG_TO_ENABLE\n1:2345:respawn:/bin/login -f root tty1 /dev/tty1 2>\&1 # RPICFG_TO_DISABLE|" +EOF +} + +# Deprecated in favour of using the Debian packaging +download_and_extract_firmware_if_necessary() { + if ! [ -d firmware ]; then + git clone git://github.com/raspberrypi/firmware.git + fi +} + +cd $WORKDIR +dotask branch_image ../$OUTDIR/stage1.$IMGFORMAT $CURIMG +dotask run_qemu $CURIMG +dotask mount_apt_cache +dotask disable_starting_services +dotask modify_etc_skel +dotask configure_users +dotask configure_fstab +dotask configure_sources_list +dotask configure_network_interfaces +dotask configure_hostname +dotask disable_eth_and_wlan_renaming +dotask configure_inittab +dotask adjust_motd +dotask install_firmware +dotask install_raspi_config +dotask update_issue +dotask allow_starting_services +dotask shutdown_qemu +#dotask download_and_extract_firmware_if_necessary +sudo -v +# We have to copy the /boot from root partition to boot partition. Temporary +# hack +dotask attach_image_to_nbd $CURIMG $NBD_DEV +mkdir -p boot +dotask sudo mount $BOOT_DEV boot +mkdir -p rootfs +dotask sudo mount $ROOT_DEV rootfs +dotask sudo mv rootfs/boot/* boot +universal_cleanup +dotask finish_image diff --git a/image/spindle/wheezy-stage3 b/image/spindle/wheezy-stage3 new file mode 100755 index 00000000..6ec95745 --- /dev/null +++ b/image/spindle/wheezy-stage3 @@ -0,0 +1,451 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +set -ex + +. ./common + +WORKDIR=work +OUTDIR=out +CURIMG=stage3.$IMGFORMAT + +configure_apt() { + onvm_chroot sh -l -ex - <<\EOF +cat <<\EOF1 > /etc/apt/apt.conf.d/50raspi +# never use pdiffs. Current implementation is very slow on low-powered devices +Acquire::PDiffs "0"; + +# download up to 5 pdiffs: +#Acquire::PDiffs::FileLimit "5"; +EOF1 +EOF +} + +set_debconf_selections() { + ssh_in_to_qemu chroot /mnt sh -l -ex - < +locales locales/locales_to_be_generated multiselect en_GB.UTF-8 UTF-8 +# Keyboard model: +# Choices: +keyboard-configuration keyboard-configuration/model select Generic 105-key (Intl) PC +# Users allowed to start the X server: +# Choices: Root Only, Console Users Only, Anybody +x11-common x11-common/xwrapper/allowed_users select Anybody +# Compose key: +# Choices: No compose key, Right Alt (AltGr), Right Control, Right Logo key, Menu key, Left Logo key, Caps Lock +keyboard-configuration keyboard-configuration/compose select No compose key +# Country of origin for the keyboard: +# Choices: +keyboard-configuration keyboard-configuration/layout select English (UK) +# for internal use +keyboard-configuration keyboard-configuration/layoutcode string gb +# Keymap to use: +# Choices: +keyboard-configuration keyboard-configuration/xkb-keymap select British English +# Choices: English (UK), English (UK) - English (UK\, Colemak), English (UK) - English (UK\, Dvorak), English (UK) - English (UK\, Dvorak with UK punctuation), English (UK) - English (UK\, extended WinKeys), English (UK) - English (UK\, international with dead keys), English (UK) - English (UK\, Macintosh), English (UK) - English (UK\, Macintosh international), Other +keyboard-configuration keyboard-configuration/variant select English (UK) +# Geographic area: +# Choices: Africa, America, Antarctica, Australia, Arctic, Asia, Atlantic, Europe, Indian, Pacific, SystemV, US, Etc +tzdata tzdata/Areas select Etc +# Method for temporarily toggling between national and Latin input: +# Choices: No temporary switch, Both Logo keys, Right Alt (AltGr), Right Logo key, Left Alt, Left Logo key +keyboard-configuration keyboard-configuration/switch select No temporary switch +# Encoding to use on the console: +# Choices: ARMSCII-8, CP1251, CP1255, CP1256, GEORGIAN-ACADEMY, GEORGIAN-PS, IBM1133, ISIRI-3342, ISO-8859-1, ISO-8859-10, ISO-8859-11, ISO-8859-13, ISO-8859-14, ISO-8859-15, ISO-8859-16, ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6, ISO-8859-7, ISO-8859-8, ISO-8859-9, KOI8-R, KOI8-U, TIS-620, UTF-8, VISCII +console-setup console-setup/charmap47 select UTF-8 +# Font tuning method for screen: +# Choices: Native, Autohinter, None +fontconfig-config fontconfig/hinting_type select Native +# Font size: +# Choices: +console-setup console-setup/fontsize-fb47 select 16 +# The desktop environment to install when the desktop task is selected +# Choices: gnome, kde, xfce +tasksel tasksel/desktop multiselect xfce +# Key to function as AltGr: +# Choices: The default for the keyboard layout, No AltGr key, Right Alt (AltGr), Right Control, Right Logo key, Menu key, Left Alt, Left Logo key, Keypad Enter key, Both Logo keys, Both Alt keys +keyboard-configuration keyboard-configuration/altgr select The default for the keyboard layout +# Default locale for the system environment: +# Choices: None, en_GB.UTF-8 +locales locales/default_environment_locale select en_GB.UTF-8 +SELEOF +EOF +} + +install_packages() { + # we may want to break out DEBIAN_FRONTEND=noninteractive + ssh_in_to_qemu chroot /mnt sh -l -ex - <<\EOF +apt-get update +# install some utils +apt-get install -y ssh locales less fbset sudo psmisc strace module-init-tools ifplugd ed ncdu +apt-get install -y console-setup keyboard-configuration debconf-utils parted unzip +apt-get install -y build-essential manpages-dev python bash-completion gdb pkg-config +apt-get install -y python-rpi.gpio v4l-utils +apt-get install -y lua5.1 +[ "$(dpkg --print-architecture)" = armhf ] && apt-get install -y luajit +apt-get install -y hardlink ca-certificates curl +apt-get install -y fake-hwclock ntp nfs-common usbutils +apt-get install -y --no-install-recommends cifs-utils +echo "deb http://archive.raspberrypi.org/debian/ wheezy main untested" > /etc/apt/sources.list.d/raspi.list +apt-get update +apt-get install -y libraspberrypi-dev libraspberrypi-doc libfreetype6-dev +echo "deb http://archive.raspberrypi.org/debian/ wheezy main" > /etc/apt/sources.list.d/raspi.list +printf "# Uncomment line below then 'apt-get update' to enable 'apt-get source'\n" >> /etc/apt/sources.list.d/raspi.list +printf "#deb-src http://archive.raspberrypi.org/debian/ wheezy main\n" >> /etc/apt/sources.list.d/raspi.list +apt-get update +# Install stuff for wireless +apt-get install -y wpasupplicant wireless-tools firmware-atheros firmware-brcm80211 \ + firmware-libertas firmware-ralink firmware-realtek +/etc/init.d/fake-hwclock stop # save current time +update-rc.d hwclock.sh disable +# Don't need to start these by default, wastes boot time +update-rc.d nfs-common disable +update-rc.d rpcbind disable +apt-get install -y dosfstools +EOF +} + +cache_keymap() { + onvm_chroot sh -l -e - <<\EOF +setupcon --force --save-only -v +/etc/init.d/fake-hwclock stop # save current time +EOF +} + +add_pi_user_to_groups() { + onvm_chroot sh -l -ex - <<\EOF +groupadd -f -r input +for GRP in adm dialout cdrom audio users sudo video games plugdev input; do + adduser pi $GRP +done +EOF +} + +configure_useradd() { + onvm_chroot sh -l -ex - <<\EOF +sed -i /etc/default/useradd -e 's/^# SKEL=/SKEL=/' +sed -i /etc/default/useradd -e 's|^SHELL=.*$|SHELL=/bin/bash|' +EOF +} + +make_udev_input_rule() { + onvm_chroot sh -l -e - < /etc/udev/rules.d/99-input.rules +EOF +} + +configure_wifi() { + onvm_chroot sh -l -e - <> /etc/network/interfaces + +allow-hotplug wlan0 +iface wlan0 inet manual +wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf +iface default inet dhcp +EOF1 +cat <<\EOF2 > /etc/wpa_supplicant/wpa_supplicant.conf +ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev +update_config=1 +EOF2 +chmod 600 /etc/wpa_supplicant/wpa_supplicant.conf +adduser pi netdev +EOF +} + +apply_noobs_os_config() { + onvm_chroot sh -l -ex - <<\EOF +cat <<\EOF1 > /etc/init.d/apply_noobs_os_config +#!/bin/sh +### BEGIN INIT INFO +# Provides: apply_noobs_os_config +# Required-Start: +# Required-Stop: +# Default-Start: 2 +# Default-Stop: +# Short-Description: Apply config from /boot/os_config.json +# Description: +### END INIT INFO + +. /lib/lsb/init-functions + +set -e + +case "$1" in + start) + log_daemon_msg "Applying config from /boot/os_config.json (if it exists)" + if raspi-config --apply-os-config; then + rm /etc/init.d/apply_noobs_os_config && update-rc.d apply_noobs_os_config remove + log_end_msg 0 + else + log_end_msg 1 + fi + ;; + *) + echo "Usage: $0 start" >&2 + exit 3 + ;; +esac +EOF1 +chmod +x /etc/init.d/apply_noobs_os_config +update-rc.d apply_noobs_os_config start 2 +EOF +} + +remove_ssh_host_keys() { + onvm_chroot sh -l -ex - <<\EOF +rm -f /etc/ssh/ssh_host_*_key* +cat <<\RCL | tee /etc/rc.local +#!/bin/sh -e +# +# rc.local +# +# This script is executed at the end of each multiuser runlevel. +# Make sure that the script will "exit 0" on success or any other +# value on error. +# +# In order to enable or disable this script just change the execution +# bits. +# +# By default this script does nothing. + +# Print the IP address +_IP=$(hostname -I) || true +if [ "$_IP" ]; then + printf "My IP address is %s\n" "$_IP" +fi + +exit 0 +RCL +update-rc.d ssh disable # to be re-enabled at first boot when we regenerate ssh host keys +cat <<\EOF1 > /etc/init.d/regenerate_ssh_host_keys +#!/bin/sh +### BEGIN INIT INFO +# Provides: regenerate_ssh_host_keys +# Required-Start: +# Required-Stop: +# Default-Start: 2 +# Default-Stop: +# Short-Description: Regenerate ssh host keys +# Description: +### END INIT INFO + +. /lib/lsb/init-functions + +set -e + +case "$1" in + start) + log_daemon_msg "Regenerating ssh host keys (in background)" + nohup sh -c "yes | ssh-keygen -q -N '' -t dsa -f /etc/ssh/ssh_host_dsa_key && \ + yes | ssh-keygen -q -N '' -t rsa -f /etc/ssh/ssh_host_rsa_key && \ + yes | ssh-keygen -q -N '' -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key && \ + update-rc.d ssh enable && sync && \ + rm /etc/init.d/regenerate_ssh_host_keys && \ + update-rc.d regenerate_ssh_host_keys remove && \ + printf '\nfinished\n' && invoke-rc.d ssh start" > /var/log/regen_ssh_keys.log 2>&1 & + log_end_msg $? + ;; + *) + echo "Usage: $0 start" >&2 + exit 3 + ;; +esac +EOF1 +chmod +x /etc/init.d/regenerate_ssh_host_keys +update-rc.d regenerate_ssh_host_keys start 2 +EOF +} + +configure_ifplugd() { + onvm_chroot sh -l -ex - <<\EOF +sed /etc/default/ifplugd -i -e 's/^INTERFACES.*/INTERFACES="auto"/' +sed /etc/default/ifplugd -i -e 's/^HOTPLUG_INTERFACES.*/HOTPLUG_INTERFACES="all"/' +EOF +} + +add_opt_vc_lib_to_ld_so() { + onvm_chroot sh -l -ex - < /etc/ld.so.conf.d/vmcs.conf +ldconfig +EOF +} + +setup_sudoers() { + onvm_chroot sh -l -ex - <> /etc/sudoers +chmod -w /etc/sudoers +usermod --pass='*' root # don't need root password any more +EOF +} + +# We use a swap file rather than a swap partition for greater flexibility +setup_swap() { + onvm_chroot sh -l -e - < /etc/dphys-swapfile +EOF +} + +setup_console_setup() { + onvm_chroot sh -l -e - <<\EOF1 +cat <<\EOF2 > /etc/default/console-setup +# CONFIGURATION FILE FOR SETUPCON + +# Consult the console-setup(5) manual page. + +ACTIVE_CONSOLES="/dev/tty[1-6]" + +CHARMAP="UTF-8" + +CODESET="guess" +FONTFACE="" +FONTSIZE="" + +VIDEOMODE= + +# The following is an example how to use a braille font +# FONT='lat9w-08.psf.gz brl-8x8.psf' +EOF2 +EOF1 +} + +# Spread the word about my favourite inputrc tweak +tweak_inputrc() { + onvm_chroot sh -l -e - <<\EOF1 +cat <<\EOF2 >> /etc/inputrc + +# mappings for up and down arrows search history +# "\e[B": history-search-forward +# "\e[A": history-search-backward +EOF2 +EOF1 +} + +# It's not to have the sbin dirs in $PATH as that gives us ifconfig +fiddle_default_PATH() { + # This sed match is clearly brittle and specific to the current debian + # /etc/profile + onvm_chroot sh -l -ex - <> /etc/apt/sources.list.d/qt5pi.list +apt-get update +EOF +} + +install_memcpy_replacement() { + onvm_chroot sh -l -e <<\EOF +apt-get install -y raspi-copies-and-fills +mv /etc/ld.so.preload /etc/ld.so.preload.disable +EOF +} + +adjust_sysctl() { + onvm_chroot sh -l -e <<\EOF + printf "\n# rpi tweaks\nvm.swappiness=1\n" >> /etc/sysctl.conf + printf "vm.min_free_kbytes = 8192\n" >> /etc/sysctl.conf + # Only print important messages to console + sed /etc/sysctl.conf -i -e "s/\#kernel\.printk/kernel.printk/" +EOF +} + +set_default_kernel_modules() { + onvm_chroot sh -e - <> /etc/modules +EOF +} + +configure_sound() { + onvm_chroot sh -e - < /etc/asound.conf +pcm.mmap0 { + type mmap_emul; + slave { + pcm "hw:0,0"; + } +} + +pcm.!default { + type plug; + slave { + pcm mmap0; + } +} +EOF1 +EOF +} + +cd $WORKDIR +dotask branch_image ../$OUTDIR/stage2.$IMGFORMAT $CURIMG +dotask run_qemu $CURIMG +dotask mount_apt_cache +dotask disable_starting_services +dotask configure_apt +dotask set_debconf_selections +dotask install_packages +dotask configure_ifplugd +dotask add_pi_user_to_groups +dotask configure_useradd +dotask make_udev_input_rule +dotask configure_wifi +#dotask add_opt_vc_lib_to_ld_so +dotask setup_sudoers +dotask setup_swap +dotask setup_console_setup +dotask cache_keymap +dotask tweak_inputrc +dotask fiddle_default_PATH +[ -n "$RASPBIAN" ] && dotask install_memcpy_replacement +dotask save_space_using_hardlink +#[ -z "$RASPBIAN" ] && dotask add_qt5_apt_source +dotask adjust_sysctl +dotask allow_starting_services +dotask remove_ssh_host_keys +dotask apply_noobs_os_config +dotask set_default_kernel_modules +# Latest firmware does not need mmap emulation, so skip asound.conf creation +#dotask configure_sound +dotask update_issue +dotask fingerprint_debian +dotask shutdown_qemu +dotask finish_image diff --git a/image/spindle/wheezy-stage4-lxde b/image/spindle/wheezy-stage4-lxde new file mode 100755 index 00000000..36d814e1 --- /dev/null +++ b/image/spindle/wheezy-stage4-lxde @@ -0,0 +1,189 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +set -ex + +. ./common + +CURIMG=stage4-lxde.$IMGFORMAT + +install_packages() { + # we may want to break out DEBIAN_FRONTEND=noninteractive + ssh_in_to_qemu chroot /mnt sh -l -ex - < /etc/apt/sources.list.d/collabora.list +apt-key add - <yes|no|" + +apt-get install -y raspberrypi-artwork +# change background +update-alternatives --install /usr/share/images/desktop-base/desktop-background \ + desktop-background /usr/share/raspberrypi-artwork/raspberry-pi-logo.png 100 +PCMANFMCFG=/etc/xdg/pcmanfm/LXDE/pcmanfm.conf +sed "$PCMANFMCFG" -i -e 's/^wallpaper_mode.*/wallpaper_mode=3/' +sed "$PCMANFMCFG" -i -e 's/^desktop_bg.*/desktop_bg=#ffffff/' + +# while we're at it, let's not use xdg-su which Debian doesn't even provide +sed "$PCMANFMCFG" -i -e 's/^su_cmd.*/su_cmd=gksu %s/' +EOF +} + +setup_automounting() { + onvm_chroot sh -l -e <<\EOF1 +apt-get install -y udisks +cat < /etc/polkit-1/localauthority/50-local.d/55-storage.pkla +[Storage Permissions] +Identity=unix-group:plugdev +Action=org.freedesktop.udisks.filesystem-mount;org.freedesktop.udisks.drive-eject;org.freedesktop.udisks.drive-detach;org.freedesktop.udisks.luks-unlock;org.freedesktop.udisks.inhibit-polling;org.freedesktop.udisks.drive-set-spindown +ResultAny=yes +ResultActive=yes +ResultInactive=no +EOF2 +EOF1 +} + +install_and_configure_lightdm() { + onvm_chroot sh -l -e <<\EOF +apt-get install --no-install-recommends -y lightdm gnome-themes-standard-data gnome-icon-theme +apt-get install -y policykit-1 +update-rc.d lightdm disable 2 +sed -i /etc/lightdm/lightdm-gtk-greeter.conf -e "s/^background.*/background=#ffffff/" +# TODO: maybe change the computer icon to the Raspberry Pi logo? See +# https://wiki.archlinux.org/index.php/LightDM#Changing_the_Icon + +# Source /etc/profile and ~/.profile through XSession.d to ensure PATH is set +# properly when lightdm is used. +cat <<\EOF1 > /etc/X11/Xsession.d/75source-profile +[ -f /etc/profile ] && . /etc/profile +[ -f "$HOME/.profile" ] && . "$HOME/.profile" +EOF1 +EOF +} + +install_qt5() { + onvm_chroot sh -l -e <<\EOF +apt-get update +apt-get install --allow-unauthenticated -y qt50-snapshot qt50-quick-particle-examples +EOF +} + +cd $WORKDIR +dotask branch_image ../$OUTDIR/stage3.$IMGFORMAT $CURIMG +dotask run_qemu $CURIMG +dotask mount_apt_cache +dotask disable_starting_services +dotask install_packages +dotask configure_gksu +[ -n "$RASPBIAN" ] && dotask install_omxplayer +dotask configure_lxde +dotask install_and_configure_lightdm +dotask setup_automounting +#[ -z "$RASPBIAN" ] && dotask install_qt5 +[ -n "$RASPBIAN" ] && dotask install_wayland +dotask save_space_using_hardlink +dotask allow_starting_services +dotask update_issue +dotask fingerprint_debian +dotask shutdown_qemu +dotask finish_image diff --git a/image/spindle/wheezy-stage4-lxde-edu b/image/spindle/wheezy-stage4-lxde-edu new file mode 100755 index 00000000..e6b9b964 --- /dev/null +++ b/image/spindle/wheezy-stage4-lxde-edu @@ -0,0 +1,111 @@ +#!/bin/sh +# Part of spindle http://asbradbury.org/projects/spindle +# +# See LICENSE file for copyright and license details + +set -ex + +. ./common + +WORKDIR=work +OUTDIR=out +CURIMG=stage4-lxde-edu.$IMGFORMAT + +install_packages() { + ssh_in_to_qemu chroot /mnt sh -l -ex - <<\EOF +apt-get update +apt-get upgrade -y +apt-get install -y python idle python3-pygame python-pygame python-tk +apt-get install -y python3 idle3 python3-tk +apt-get install -y python3-rpi.gpio +apt-get install -y python-serial python3-serial +apt-get install -y python-picamera python3-picamera +apt-get install -y debian-reference-en dillo x2x +apt-get install -y scratch nuscratch +apt-get install -y raspberrypi-ui-mods +apt-get install -y --no-install-recommends timidity # needed for some python game examples +[ "$(dpkg --print-architecture)" = armhf ] && apt-get install -y smartsim penguinspuzzle +[ "$(dpkg --print-architecture)" = armhf ] && apt-get install -y pistore +[ "$(dpkg --print-architecture)" = armhf ] && apt-get install -y sonic-pi +cat <<\EOF1 > /etc/udev/rules.d/40-scratch.rules +ATTRS{idVendor}=="0694", ATTRS{idProduct}=="0003", SUBSYSTEMS=="usb", ACTION=="add", MODE="0666", GROUP="plugdev" +EOF1 +# Requested for raspberry filling +apt-get install -y python3-numpy + +# Install pypy +if [ "$(dpkg --print-architecture)" = armhf ]; then + apt-get install -y pypy-upstream +fi + +apt-get install -y python3-pifacecommon python3-pifacedigitalio \ + python3-pifacedigital-scratch-handler python-pifacecommon python-pifacedigitalio + + +if [ "$(dpkg --print-architecture)" = armhf ]; then + apt-get install -y java-common oracle-java8-jdk + apt-get install -y minecraft-pi python-minecraftpi +fi + +EOF +} + +install_wolfram() { + onvm_chroot sh -l -ex - <<\EOF +apt-get update +debconf-set-selections <> "/usr/local/share/applications/$(basename $ENTRY)" +done +EOF +} + +cd $WORKDIR +dotask branch_image ../$OUTDIR/stage4-lxde.$IMGFORMAT $CURIMG +dotask run_qemu $CURIMG +dotask disable_starting_services +dotask mount_apt_cache +dotask install_packages +[ -n "$RASPBIAN" ] && dotask install_wolfram +dotask setup_python_game_examples +dotask setup_desktop_icons +dotask hide_redundant_idle_menu_entries +dotask save_space_using_hardlink +dotask allow_starting_services +dotask fingerprint_debian +dotask update_issue +dotask shutdown_qemu +dotask finish_image