pax_global_header00006660000000000000000000000064134775067660014537gustar00rootroot0000000000000052 comment=ac1b5acf9977a48aab162483a9f7bfce91ee35df direwolf-1.5+dfsg/000077500000000000000000000000001347750676600141365ustar00rootroot00000000000000direwolf-1.5+dfsg/.gitattributes000066400000000000000000000012071347750676600170310ustar00rootroot00000000000000# Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain # My rules to remove any doubt *.c text *.cpp text *.h text *.pl text *.py text *.sh text *.txt text *.desktop text *.conf text *.rc text *.spec text *.bat text *.1 text *.md text COPYING text Makefile* text README* text *.ico binary *.png binary direwolf-1.5+dfsg/.gitignore000066400000000000000000000021211347750676600161220ustar00rootroot00000000000000# Custom *.docx z* *.log *bak* *~ *.xlsx *.stackdump *.wav # Object files *.o *.ko *.obj *.elf # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Binaries, other build results aclients atest decode_aprs direwolf gen_fff gen_packets ll2utm log2gpx text2tt tt2text ttcalc utm2ll direwolf.conf fsk_fast_filter.h direwolf.desktop # ========================= # Operating System Files # ========================= # OSX # ========================= .DS_Store .AppleDouble .LSOverride # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # Windows # ========================= # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk /use_this_sdk *.dSYM direwolf-1.5+dfsg/99-direwolf-cmedia.rules000066400000000000000000000021751347750676600205110ustar00rootroot00000000000000# Normally, all of /dev/hidraw* are accessible only by root. # # $ ls -l /dev/hidraw* # crw------- 1 root root 247, 0 Sep 24 09:40 /dev/hidraw0 # # An ordinary user, trying to acccess it will be denied. # # Unnecessarily running applications as root is generally a bad idea because it makes it too easy # to accidentally trash your system. We need to relax the restrictions so ordinary users can use these devices. # # If all went well with installation, the /etc/udev/rules.d directory should contain a file called # 99-direwolf-cmedia.rules containing: # SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", GROUP="audio", MODE="0660" # # I used the "audio" group, mimicking the permissions on the sound side of the device. # # $ ls -l /dev/snd/pcm* # crw-rw----+ 1 root audio 116, 16 Sep 24 09:40 /dev/snd/pcmC0D0p # crw-rw----+ 1 root audio 116, 17 Sep 24 09:40 /dev/snd/pcmC0D1p # # You should see something similar to this where someone in the "audio" group has read-write access. # # $ ls -l /dev/hidraw* # crw-rw---- 1 root audio 247, 0 Oct 6 19:24 /dev/hidraw0 # # Read the User Guide and run the "cm108" application for more information. # direwolf-1.5+dfsg/CHANGES.md000066400000000000000000000361111347750676600155320ustar00rootroot00000000000000 # Revision History # ## Version 1.5 -- September 2018 ## ### New Features: ### - PTT using GPIO pin of CM108/CM119 (e.g. DMK URI, RB-USB RIM), Linux only. - More efficient error recovery for AX.25 connected mode. Better generation and processing of REJ and SREJ to reduce unnecessary duplicate "**I**" frames. - New configuration option, "**V20**", for listing stations known to not understand AX.25 v2.2. This will speed up connection by going right to SABM and not trying SABME first and failing. - New "**NOXID**" configuration file option to avoid sending XID command to listed station(s). If other end is a partial v2.2 implementation, which recognizes SABME, but not XID, we would waste a lot of time resending XID many times before giving up. This is less drastic than the "**V20**" option which doesn't even attempt to use v2.2 with listed station(s). - New application "**kissutil**" for troubleshooting a KISS TNC or interfacing to an application via files. - KISS "Set Hardware" command to report transmit queue length. - TCP KISS can now handle multiple concurrent applications. - Linux can use serial port for KISS in addition to a pseudo terminal. - decode_aprs utility can now accept KISS frames and AX.25 frames as series of two digit hexadecimal numbers. - Full Duplex operation. (Put "FULLDUP ON" in channel section of configuration file.) - Time slots for beaconing. - Allow single log file with fixed name rather than starting a new one each day. ### Bugs Fixed: ### - Possible crash when CDIGIPEAT did not include the optional alias. - PACLEN configuration item no longer restricts length of received frames. - Strange failures when trying to use multiple KISS client applications over TCP. Only Linux was affected. - Under certain conditions, outgoing connected mode data would get stuck in a queue and not be transmitted. This could happen if client application sends a burst of data larger than the "window" size (MAXFRAME or EMAXFRAME option). - Little typographical / spelling errors in messages. ### Documentation: ### - New document ***Bluetooth-KISS-TNC.pdf*** explaining how to use KISS over Bluetooth. - Updates describing cheap SDR frequency inaccuracy and how to compensate for it. ### Notes: ### Windows binary distribution now uses gcc (MinGW) version 6.3.0. ---------- ## Version 1.4 -- April 2017 ## ### New Features: ### - AX.25 v2.2 connected mode. See chapter 10 of User Guide for details. - New client side packet filter to select "messages" only to stations that have been heard nearby recently. This is now the default if no IS to RF filter is specified. - New beacon type, IBEACON, for sending IGate statistics. - Expanded debug options so you can understand what is going on with packet filtering. - Added new document ***Successful-APRS-IGate-Operation.pdf*** with IGate background, configuration, and troubleshooting tips. - 2400 & 4800 bps PSK modems. See ***2400-4800-PSK-for-APRS-Packet-Radio.pdf*** in the doc directory for discussion. - The top speed of 9600 bps has been increased to 38400. You will need a sound card capable of 96k or 192k samples per second for the higher rates. Radios must also have adequate bandwidth. See ***Going-beyond-9600-baud.pdf*** in the doc directory for more details. - Better decoder performance for 9600 and higher especially for low audio sample rate to baud ratios. - Generate waypoint sentences for use by AvMap G5 / G6 or other mapping devices or applications. Formats include - $GPWPL - NMEA generic with only location and name. - $PGRMW - Garmin, adds altitude, symbol, and comment to previously named waypoint. - $PMGNWPL - Magellan, more complete for stationary objects. - $PKWDWPL - Kenwood with APRS style symbol but missing comment. - DTMF tones can be sent by putting "DTMF" in the destination address, similar to the way that Morse Code is sent. - Take advantage of new 'gpio' group and new /sys/class/gpio ownership in Raspbian Jessie. - Handle more complicated gpio naming for CubieBoard, etc. - More flexible dw-start.sh start up script for both GUI and CLI environments. ### Bugs Fixed: ### - The transmitter (PTT control) was being turned off too soon when sending Morse Code. - The -qd (quiet decode) command line option now suppresses errors about improperly formed Telemetry packets. - Longer tocall.txt files can now be handled. - Sometimes kissattach would have an issue with the Dire Wolf pseudo terminal. This showed up most often on Raspbian but sometimes occurred with other versions of Linux. *kissattach: Error setting line discipline: TIOCSETD: Device or resource busy Are you sure you have enabled MKISS support in the kernel or, if you made it a module, that the module is loaded?* - Sometimes writes to a pseudo terminal would block causing the received frame processing thread to hang. The first thing you will notice is that received frames are not being printed. After a while this message will appear: *Received frame queue is out of control. Length=... Reader thread is probably frozen. This can be caused by using a pseudo terminal (direwolf -p) where another application is not reading the frames from the other side.* - -p command line option caused segmentation fault with glibc >= 2.24. - The Windows version 1.3 would crash when starting to transmit on Windows XP. There have also been some other reports of erratic behavior on Windows. The crashing problem was fixed in in the 1.3.1 patch release. Linux version was not affected. - IGate did not retain nul characters in the information part of a packet. This should never happen with a valid APRS packet but there are a couple cases where it has. If we encounter these malformed packets, pass them along as-is, rather than truncating. - Don't digipeat packets when the source is my call. ---------- ## Version 1.3 -- May 2016 ## ### New Features: ### - Support for Mac OS X. - Many APRStt enhancements including: Morse code and speech responses to to APRStt tone sequences, new 5 digit callsign suffix abbreviation, position ambiguity for latitude and longitude in object reports - APRS Telemetry Toolkit. - GPS Tracker beacons are now available for the Windows version. Previously this was only in the Linux version. - SATgate mode for IGate. Packets heard directly are delayed before being sent to the Internet Server. This favors digipeated packets because the original arrives later and gets dropped if there are duplicates. - Added support for hamlib. This provides more flexible options for PTT control. - Implemented AGW network protocol 'M' message for sending UNPROTO information without digipeater path. - A list of all symbols available can be obtained with the -S command line option. - Command line option "-a n" to print audio device statistics each n seconds. Previously this was always each 100 seconds on Linux and not available on Windows. ### Bugs Fixed: ### - Fixed several cases where crashes were caused by unexpected packet contents: - When receiving packet with unexpected form of GPS NMEA sentence. - When receiving packet with comment of a few hundred characters. - Address in path, from Internet server, more than 9 characters. - "INTERNAL ERROR: dlq_append NULL packet pointer." when using PASSALL. - In Mac OSX version: Assertion failed: (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768), function audio_get, file audio_portaudio.c, line 917. - Tracker beacons were not always updating the location properly. - AGW network protocol now works properly for big-endian processors such as PowerPC or MIPS. - Packet filtering treated telemetry metadata as messages rather than telemetry. ---------- ## Version 1.2 -- June 2015 ## ### New Features ### - Improved decoder performance. Over 1000 error-free frames decoded from WA8LMF TNC Test CD. See ***A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf*** for details. - Up to 3 soundcards and 6 radio channels can be handled at the same time. - New framework for applications which listen for Touch Tone commands and respond with voice. A sample calculator application is included as a starting point for building more interesting applications. For example, if it hears the DTMF sequence "2*3*4#" it will respond with the spoken words "Twenty Four." - Reduced latency for transfers to/from soundcards. - More accurate transmit PTT timing. - Packet filtering for digipeater and IGate. - New command line -q (quiet) option to suppress some types of output. - Attempted fixing of corrupted bits now works for 9600 baud. - Implemented AGW network protocol 'y' message so applications can throttle generation of packets when sending a large file. - When using serial port RTS/DTR to activate transmitter, the two control lines can now be driven with opposite polarity as required by some interfaces. - Data Carrier Detect (DCD) can be sent to an output line (just like PTT) to activate a carrier detect light. - Linux "man" pages for on-line documentation. - AGWPORT and KISSPORT can be set to 0 to disable the interfaces. - APRStt gateway enhancements: MGRS/USNG coordinates, new APRStt3 format call, satellite grid squares. ### Bugs fixed ### - Fixed "gen_packets" so it now handles user-specified messages correctly. - Under some circumstances PTT would be held on long after the transmit audio was finished. ### Known problems ### - Sometimes writes to a pseudo terminal will block causing the received frame processing thread to hang. The first thing you will notice is that received frames are not being printed. After a while this message will appear: Received frame queue is out of control. Length=... Reader thread is probably frozen. This can be caused by using a pseudo terminal (direwolf -p) where another application is not reading the frames from the other side. ----------- ## Version 1.1 -- December 2014 ## ### New Features ### - Logging of received packets and utility to convert log file into GPX format. - AGW network port formerly allowed only one connection at a time. It can now accept 3 client applications at the same time. (Same has not yet been done for network KISS port.) - Frequency / offset / tone standard formats are now recognized. Non-standard attempts, in the comment, are often detected and a message suggests the correct format. - Telemetry is now recognized. Messages are printed for usage that does not adhere to the published standard. - Tracker function transmits location from GPS position. New configuration file options: TBEACON and SMARTBEACONING. (For Linux only. Warning - has not been well tested.) - Experimental packet regeneration feature for HF use. Will be documented later if proves to be useful... - Several enhancements for trying to fix incorrect CRC: Additional types of attempts to fix a bad CRC. Optimized code to reduce execution time. Improved detection of duplicate packets from different fixup attempts. Set limit on number of packets in fix up later queue. - Beacon positions can be specified in either latitude / longitude or UTM coordinates. - It is still highly recommended, but no longer mandatory, that beaconing be enabled for digipeating to work. * Bugs fixed: - For Windows version, maximum serial port was COM9. It is now possible to use COM10 and higher. - Fixed issue with KISS protocol decoder state that showed up only with "binary" data in packets (e.g. RMS Express). - An extra 00 byte was being appended to packets from AGW network protocol 'K' messages. - Invalid data from an AGW client application could cause an application crash. - OSS (audio interface for non-Linux versions of Unix) should be better now. ### Known problems ### - Sometimes kissattach fails to connect with "direwolf -p". The User Guide and Raspberry Pi APRS document have a couple work-arounds. ----------- ## Version 1.0a -- May 2014 ## ### Bug fixed ### - Beacons sent directly to IGate server had incorrect source address. ----------- ## Version 1.0 -- May 2014 ## ### New Features ### - Received audio can be obtained with a UDP socket or stdin. This can be used to take audio from software defined radios such as rtl_fm or gqrx. - 9600 baud data rate. - New PBEACON and OBEACON configuration options. Previously it was necessary to handcraft beacons. - Less CPU power required for 300 baud. This is important if you want to run a bunch of decoders at the same time to tolerate off-frequency HF SSB signals. - Improved support for UTF-8 character set. - Improved troubleshooting display for APRStt macros. - In earlier versions, the DTMF decoder was always active because it took a negligible amount of CPU time. Unfortunately this sometimes resulted in too many false positives from some other types of digital transmissions heard on HF. Starting in version 1.0, the DTMF decoder is enabled only when the APRStt gateway is configured. ----------- ## Version 0.9 --November 2013 ## ### New Features ### - Selection of non-default audio device for Linux ALSA. - Simplified audio device set up for Raspberry Pi. - GPIO lines can be used for PTT on suitable Linux systems. - Improved 1200 baud decoder. - Multiple decoders per channel to tolerate HF SSB signals off frequency. - Command line option "-t 0" to disable text colors. - APRStt macros which allow short numeric only touch tone sequences to be processed as much longer predefined sequences. ### Bugs Fixed ### - Now works on 64 bit target. ### New Restriction for Windows version ### - Minimum processor is now Pentium 3 or equivalent or later. It's possible to run on something older but you will need to rebuild it from source. ----------- ## Version 0.8 -- August 2013 ## ### New Features ### - Internet Gateway (IGate) including IPv6 support. - Compatibility with YAAC. - Preemptive digipeating option. - KISS TNC should now work with connected AX.25 protocols (e.g. AX25 for Linux), not just APRS. ---------- ## Version 0.7 -- March 2013 ## ### New Features: ### - Added APRStt gateway capability. For details, see ***APRStt-Implementation-Notes.pdf*** ----------- ## Version 0.6 -- February 2013 ## ### New Features ### - Improved performance of AFSK demodulator. Now decodes 965 frames from Track 2 of WA8LMF's TNC Test CD. - KISS protocol now available thru a TCP socket. Default port is 8001. Change it with KISSPORT option in configuration file. - Ability to salvage frames with bad FCS. See section mentioning "bad apple" in the user guide. Default of fixing 1 bit works well. Fixing more bits not recommended because there is a high probability of occasional corrupted data getting thru. - Added AGW "monitor" format messages. Now compatible with APRS-TW for telemetry. ### Known Problem ### - The Linux (but not Cygwin) version eventually hangs if nothing is reading from the KISS pseudo terminal. Some operating system queue fills up, the application write blocks, and decoding stops. ### Workaround ### - If another application is not using the serial KISS interface, run this in another window: tail -f /tmp/kisstnc ----------- ## Version 0.5 -- March 2012 ## - More error checking and messages for invalid APRS data. ----------- ## Version 0.4 -- September 2011 ## - First general availability. direwolf-1.5+dfsg/LICENSE-dire-wolf.txt000066400000000000000000000355651347750676600176650ustar00rootroot00000000000000 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 direwolf-1.5+dfsg/LICENSE-other.txt000066400000000000000000000002751347750676600171040ustar00rootroot00000000000000The Windows version of Dire Wolf contains additional open source covered by BSD, GPL, and other licenses. See "regex" and "misc" subdirectories in the source distribution for more details.direwolf-1.5+dfsg/Makefile000066400000000000000000000006411347750676600155770ustar00rootroot00000000000000# Select proper Makefile for operating system. # The Windows version is built with the help of Cygwin. # In my case, I see CYGWIN_NT-6.1-WOW so we don't check for # equal to some value. Your mileage my vary. win := $(shell uname | grep CYGWIN) dar := $(shell uname | grep Darwin) ifneq ($(win),) include Makefile.win else ifeq ($(dar),Darwin) include Makefile.macosx else include Makefile.linux endif direwolf-1.5+dfsg/Makefile.linux000066400000000000000000001023451347750676600167410ustar00rootroot00000000000000# # Makefile for Linux version of Dire Wolf. # APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc kissutil cm108 all : $(APPS) direwolf.desktop direwolf.conf @echo " " @echo "Next step - install with:" @echo " " @echo " sudo make install" @echo " " CC := gcc # Just for fun, let's see how clang compares to gcc. First install like this: # sudo apt-get update # apt-cache search clang # sudo apt-get install clang-3.5 # # CC := clang-3.5 # _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24. # Explanation here: https://github.com/wb2osz/direwolf/issues/62 # There are a few source files where it had been necessary to define __USE_XOPEN2KXSI, # __USE_XOPEN, or _POSIX_C_SOURCE. Doesn't seem to be needed after adding this. # The first assignment to CFLAGS and LDFLAGS uses +=, rather than :=, so # we will inherit options already set in build environment. # Explanation - https://github.com/wb2osz/direwolf/pull/38 CFLAGS += -O3 -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 -Wall # That was fine for a recent Ubuntu and Raspbian Jessie. # However, Raspbian wheezy was then missing declaration for strsep and definition of fd_set. CFLAGS += -D_BSD_SOURCE LDFLAGS += -lm -lpthread -lrt # # The DSP filters spend a lot of time spinning around in little # loops multiplying and adding arrays of numbers. The Intel "SSE" # instructions, introduced in 1999 with the Pentium III series, # can speed this up considerably. # # SSE2 instructions, added in 2000, don't seem to offer any advantage. # # # Let's take a look at the effect of the compile options. # # # Times are elapsed time to process Track 2 of the TNC test CD. # # i.e. "./atest 02_Track_2.wav" # Default demodulator type is new "E" added for version 1.2. # # # ---------- x86 (32 bit) ---------- # # # gcc 4.6.3 running on Ubuntu 12.04.05. # Intel(R) Celeron(R) CPU 2.53GHz. Appears to have only 32 bit instructions. # Probably from around 2004 or 2005. # # When gcc is generating code for a 32 bit x86 target, it assumes the ancient # i386 processor. This is good for portability but bad for performance. # # The code can run considerably faster by taking advantage of the SSE instructions # available in the Pentium 3 or later. # # seconds options comments # ------ ------- -------- # 524 # 183 -O2 # 182 -O3 # 183 -O3 -ffast-math (should be same as -Ofast) # 184 -Ofast # 189 -O3 -ffast-math -march=pentium # 122 -O3 -ffast-math -msse # 122 -O3 -ffast-math -march=pentium -msse # 121 -O3 -ffast-math -march=pentium3 (this implies -msse) # 120 -O3 -ffast-math -march=native # # Note that "-march=native" is essentially the same as "-march=pentium3." # # If the compiler is generating code for the i386 target, we can # get much better results by telling it we have at least a Pentium 3. arch := $(shell echo | gcc -E -dM - | grep __i386__) ifneq ($(arch),) CFLAGS += -march=pentium3 endif # # ---------- x86_64 ---------- # # # gcc 4.8.2 running on Ubuntu 14.04.1. # Intel Core 2 Duo from around 2007 or 2008. # # 64 bit target implies that we have SSE and probably even better vector instructions. # # seconds options comments # ------ ------- -------- # 245 # 75 -01 # 72 -02 # 71 -03 # 73 -O3 -march=native # 42 -O3 -ffast-math # 42 -Ofast (note below) # 40 -O3 -ffast-math -march=native # # # Note that "-Ofast" is a newer option roughly equivalent to "-O3 -ffast-math". # I use the longer form because it is compatible with older compilers. # # Why don't I don't have "-march=native?" # Older compilers don't recognize "native" as one of the valid options. # One article said it was added with gcc 4.2 but I haven't verified that. # # ---------- How does clang compare? - Ubuntu x86_64 ---------- # # I keep hearing a lot about "clang." Let's see how it compares with gcc. # Simply use different compiler and keep all options the same. # # test case: atest 02_Track_2.wav # # gcc 4.8.4: 988 packets decoded in 40.503 seconds. 38.3 x realtime # 988 packets decoded in 40.403 seconds. 38.4 x realtime # # clang 3.8.0-2: 988 packets decoded in 77.454 seconds. 20.0 x realtime # 988 packets decoded in 77.232 seconds. 20.1 x realtime # # I'm not impressed. Almost twice as long. Maybe we need to try some other compiler options. # -march=native did not help. # Makefile.macosx, which uses clang, has these: # -fvectorize -fslp-vectorize -fslp-vectorize-aggressive # Those did not help. # useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math) ifneq ($(useffast),) CFLAGS += -ffast-math endif # # ---------- ARM - Raspberry Pi 1 models ---------- # # Raspberry Pi (before RPi model 2), ARM11 (ARMv6 + VFP2) # gcc (Debian 4.6.3-14+rpi1) 4.6.3 # # # seconds options comments # ------ ------- --------- # 892 -O3 # 887 -O3 -ffast-math # x -O3 -ffast-math -march=native (error: bad value for -march switch) # 887 -O3 -ffast-math -mfpu=vfp # 890 -O3 -ffast-math -march=armv6zk -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp # # # The compiler, supplied with Raspbian, is configured with these options which are # good for the pre version 2 models. # --with-arch=armv6 --with-fpu=vfp --with-float=hard # # Run "gcc --help -v 2" and look near the end. # # # # ---------- ARM - Raspberry Pi 2 ---------- # # Besides the higher clock speed, the Raspberry Pi 2 has the NEON instruction set # which which should make things considerably faster. # # # seconds options comments # ------ ------- --------- # 426 -O3 -ffast-math (already more than twice as fast) # 429 -O3 -mfpu=neon # 419 -O3 -mfpu=neon -funsafe-math-optimizations # 412 -O3 -ffast-math -mfpu=neon # 413 -O3 -ffast-math -mfpu=neon-vfpv4 # 430 -O3 -ffast-math -mfpu=neon-vfpv4 -march=armv7-a # 412 -O3 -ffast-math -mfpu=neon-vfpv4 -mtune=arm7 # 410 -O3 -ffast-math -mfpu=neon-vfpv4 -funsafe-math-optimizations # # gcc -march=armv7-a -mfpu=neon-vfpv4 # # I expected the -mfpu=neon option to have a much larger impact. # Adding -march=armv7-a makes it slower! # # If you compile with the RPi 2 specific options above and try to run it on the RPi # model B (pre version 2), it will die with "illegal instruction." # # Dire Wolf is known to work on the BeagleBone, CubieBoard2, CHIP, etc. # The best compiler options will depend on the specific type of processor # and the compiler target defaults. # neon := $(shell cat /proc/cpuinfo | grep neon) ifneq ($(neon),) CFLAGS += -mfpu=neon endif # # You would expect "-march=native" to produce the fastest code. # Why don't I use it here? # # 1. In my benchmarks, above, it has a negligible impact if any at all. # 2. Some older versions of gcc don't recognize "native" as a valid choice. # 3. Results are less portable. Not a consideration if you are # building only for your own use but very important for anyone # redistributing a "binary" version. # # If you are planning to distribute the binary version to other # people (in some ham radio software collection, RPM, or DEB package), # avoid fine tuning it for your particular computer. It could # cause compatibility issues for those with older computers. # # ---------- How does clang compare? - ARM - Raspberry Pi 2 ---------- # # I keep hearing a lot about "clang." Let's see how it compares with gcc. # Simply use different compiler and keep all options the same. # # test case: atest 02_Track_2.wav # # gcc 4.9.2-10: 988 packets decoded in 353.025 seconds. 4.4 x realtime # 988 packets decoded in 352.752 seconds. 4.4 x realtime # # clang 3.5.0-10: 988 packets decoded in 825.879 seconds. 1.9 x realtime # 988 packets decoded in 831.469 seconds. 1.9 x realtime # # There might be a different set of options which produce faster code # but the initial test doesn't look good. About 2.3 times as slow. # If you want to use OSS (for FreeBSD, OpenBSD) instead of # ALSA (for Linux), comment out (or remove) the line below. # TODO: Can we automate this somehow? alsa = 1 ifeq ($(wildcard /usr/include/pthread.h),) $(error /usr/include/pthread.h does not exist. Install it with "sudo apt-get install libc6-dev" or "sudo yum install glibc-headers" ) endif ifneq ($(alsa),) CFLAGS += -DUSE_ALSA LDFLAGS += -lasound ifeq ($(wildcard /usr/include/alsa/asoundlib.h),) $(error /usr/include/alsa/asoundlib.h does not exist. Install it with "sudo apt-get install libasound2-dev" or "sudo yum install alsa-lib-devel" ) endif endif # Enable GPS if header file is present. # Finding libgps.so* is more difficult because it # is in different places on different operating systems. enable_gpsd := $(wildcard /usr/include/gps.h) ifneq ($(enable_gpsd),) CFLAGS += -DENABLE_GPSD LDFLAGS += -lgps endif # Enable hamlib support if header file is present. enable_hamlib := $(wildcard /usr/include/hamlib/rig.h /usr/local/include/hamlib/rig.h) ifneq ($(enable_hamlib),) CFLAGS += -DUSE_HAMLIB LDFLAGS += -lhamlib endif # Should enabling of this feature be strongly encouraged or # is it quite specialized and of interest to a small audience? # If, for some reason, can obtain the libudev-dev package, or # don't want to install it, comment out the next 3 lines. #ifeq ($(wildcard /usr/include/libudev.h),) #$(error /usr/include/libudev.h does not exist. Install it with "sudo apt-get install libudev-dev" or "sudo yum install libudev-devel" ) #endif # Enable cm108 PTT support if libudev header file is present. enable_cm108 := $(wildcard /usr/include/libudev.h) ifneq ($(enable_cm108),) CFLAGS += -DUSE_CM108 LDFLAGS += -ludev endif # Name of current directory. # Used to generate zip file name for distribution. z := $(notdir ${CURDIR}) # -------------------------------- Main application ----------------------------------------- direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o \ hdlc_rec2.o multi_modem.o rdq.o rrbb.o dlq.o \ fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \ decode_aprs.o symbols.o server.o kiss.o kissserial.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \ gen_tone.o audio.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \ ptt.o beacon.o encode_aprs.o latlong.o encode_aprs.o latlong.o textcolor.o \ dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \ dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o mheard.o ax25_link.o cm108.o \ misc.a geotranz.a $(CC) -o $@ $^ $(LDFLAGS) @echo " " ifneq ($(enable_gpsd),) @echo "\t>\tThis includes support for gpsd." else @echo "\t>\tThis does NOT include support for gpsd." endif ifneq ($(enable_hamlib),) @echo "\t>\tThis includes support for hamlib." else @echo "\t>\tThis does NOT include support for hamlib." endif ifneq ($(enable_cm108),) @echo "\t>\tThis includes support for CM108/CM119 PTT." else @echo "\t>\tThis does NOT include support for CM108/CM119 PTT." endif @echo " " # Optimization for slow processors. demod.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h fsk_fast_filter.h : gen_fff ./gen_fff > fsk_fast_filter.h gen_fff : demod_afsk.c dsp.c textcolor.c echo " " > tune.h $(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ $(LDFLAGS) # # The destination field is often used to identify the manufacturer/model. # These are not hardcoded into Dire Wolf. Instead they are read from # a file called tocalls.txt at application start up time. # # The original permanent symbols are built in but the "new" symbols, # using overlays, are often updated. These are also read from files. # # You can obtain an updated copy by typing "make tocalls-symbols". # This is not part of the normal build process. You have to do this explicitly. # # The locations below appear to be the most recent. # The copy at http://www.aprs.org/tocalls.txt is out of date. # .PHONY: tocalls-symbols tocalls-symbols : cp tocalls.txt tocalls.txt~ wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt -diff -Z tocalls.txt~ tocalls.txt cp symbols-new.txt symbols-new.txt~ wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt -diff -Z symbols-new.txt~ symbols-new.txt cp symbolsX.txt symbolsX.txt~ wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt -diff -Z symbolsX.txt~ symbolsX.txt # ---------------------------------------- Other utilities included ------------------------------ # Separate application to decode raw data. # First three use .c rather than .o because they depend on DECAMAIN definition. decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o misc.a $(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ $(LDFLAGS) # Convert between text and touch tone representation. text2tt : tt_text.c misc.a $(CC) $(CFLAGS) -DENC_MAIN -o $@ $^ $(LDFLAGS) tt2text : tt_text.c misc.a $(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^ $(LDFLAGS) # Convert between Latitude/Longitude and UTM coordinates. ll2utm : ll2utm.c geotranz.a textcolor.o misc.a $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) utm2ll : utm2ll.c geotranz.a textcolor.o misc.a $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) # Convert from log file to GPX. log2gpx : log2gpx.c textcolor.o misc.a $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) # Test application to generate sound. gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c misc.a $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) # Unit test for AFSK demodulator atest : atest.c demod.o demod_afsk.o demod_psk.o demod_9600.o \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ fcs_calc.o ax25_pad.o decode_aprs.o dwgpsnmea.o \ dwgps.o dwgpsd.o serial_port.o telemetry.o dtime_now.o latlong.o symbols.o tt_text.o textcolor.o \ misc.a $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) # Multiple AGWPE network or serial port clients to test TNCs side by side. aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.o misc.a $(CC) $(CFLAGS) -g -o $@ $^ # Talk to a KISS TNC. # Note: kiss_frame.c has conditional compilation on KISSUTIL. kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o dtime_now.o sock.o misc.a $(CC) $(CFLAGS) -g -DKISSUTIL -o $@ $^ $(LDFLAGS) # List USB audio adapters than can use GPIO for PTT. cm108 : cm108.c textcolor.o misc.a $(CC) $(CFLAGS) -g -DCM108_MAIN -o $@ $^ $(LDFLAGS) # Touch Tone to Speech sample application. ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a $(CC) $(CFLAGS) -g -o $@ $^ # ----------------------------------------- Libraries -------------------------------------------- # UTM, USNG, MGRS conversions. geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o ar -cr $@ $^ error_string.o : geotranz/error_string.c $(CC) $(CFLAGS) -c -o $@ $^ mgrs.o : geotranz/mgrs.c $(CC) $(CFLAGS) -c -o $@ $^ polarst.o : geotranz/polarst.c $(CC) $(CFLAGS) -c -o $@ $^ tranmerc.o : geotranz/tranmerc.c $(CC) $(CFLAGS) -c -o $@ $^ ups.o : geotranz/ups.c $(CC) $(CFLAGS) -c -o $@ $^ usng.o : geotranz/usng.c $(CC) $(CFLAGS) -c -o $@ $^ utm.o : geotranz/utm.c $(CC) $(CFLAGS) -c -o $@ $^ # Provide our own copy of strlcpy and strlcat because they are not included with Linux. # We don't need the others in that same directory. misc.a : strlcpy.o strlcat.o ar -cr $@ $^ strlcpy.o : misc/strlcpy.c $(CC) $(CFLAGS) -I. -c -o $@ $^ strlcat.o : misc/strlcat.c $(CC) $(CFLAGS) -I. -c -o $@ $^ # ------------------------------------- Installation ---------------------------------- # Generate apprpriate sample configuration file for this platform. # Originally, there was one sample for all platforms. It got too cluttered # and confusing saying, this is for windows, and this is for Linux, and this ... # Trying to maintain 3 different versions in parallel is error prone. # We now have a single generic version which can be used to generate # the various platform specific versions. # generic.conf should be checked into source control. # direwolf.conf should NOT. It is generated when compiling on the target platform. direwolf.conf : generic.conf egrep '^C|^L' generic.conf | cut -c2-999 > direwolf.conf # Where should we install it? # Something built from source and installed locally would normally go in /usr/local/... # If not specified on the make command line, this is our default. DESTDIR ?= /usr/local # However, if you are preparing a "binary" DEB or RPM package, the installation location # would normally be /usr/... instead. In this case, use a command line like this: # # make DESTDIR=/usr install # Command to "install" to system directories. Use "ginstall" for Mac. INSTALL=install # direwolf.desktop was previously handcrafted for the Raspberry Pi. # It was hardcoded with lxterminal, /home/pi, and so on. # In version 1.2, try to customize this to match other situations better. # TODO: Test this better. direwolf.desktop : @echo "Generating customized direwolf.desktop ..." @echo '[Desktop Entry]' > $@ @echo 'Type=Application' >> $@ ifneq ($(wildcard /usr/bin/lxterminal),) @echo "Exec=lxterminal -t \"Dire Wolf\" -e \"$(DESTDIR)/bin/direwolf\"" >> $@ else ifneq ($(wildcard /usr/bin/lxterm),) @echo "Exec=lxterm -hold -title \"Dire Wolf\" -bg white -e \"$(DESTDIR)/bin/direwolf\"" >> $@ else @echo "Exec=xterm -hold -title \"Dire Wolf\" -bg white -e \"$(DESTDIR)/bin/direwolf\"" >> $@ endif @echo 'Name=Dire Wolf' >> $@ @echo 'Comment=APRS Soundcard TNC' >> $@ @echo 'Icon=$(DESTDIR)/share/direwolf/pixmaps/dw-icon.png' >> $@ @echo "Path=$(HOME)" >> $@ @echo '#Terminal=true' >> $@ @echo 'Categories=HamRadio' >> $@ @echo 'Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25' >> $@ # Installation into $(DESTDIR), usually /usr/local/... or /usr/... # Needs to be run as root or with sudo. .PHONY: install install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop # # Applications, not installed with package manager, normally go in /usr/local/bin. # /usr/bin is used instead when installing from .DEB or .RPM package. # $(INSTALL) -D --mode=755 direwolf $(DESTDIR)/bin/direwolf $(INSTALL) -D --mode=755 decode_aprs $(DESTDIR)/bin/decode_aprs $(INSTALL) -D --mode=755 text2tt $(DESTDIR)/bin/text2tt $(INSTALL) -D --mode=755 tt2text $(DESTDIR)/bin/tt2text $(INSTALL) -D --mode=755 ll2utm $(DESTDIR)/bin/ll2utm $(INSTALL) -D --mode=755 utm2ll $(DESTDIR)/bin/utm2ll $(INSTALL) -D --mode=755 aclients $(DESTDIR)/bin/aclients $(INSTALL) -D --mode=755 log2gpx $(DESTDIR)/bin/log2gpx $(INSTALL) -D --mode=755 gen_packets $(DESTDIR)/bin/gen_packets $(INSTALL) -D --mode=755 atest $(DESTDIR)/bin/atest $(INSTALL) -D --mode=755 ttcalc $(DESTDIR)/bin/ttcalc $(INSTALL) -D --mode=755 kissutil $(DESTDIR)/bin/kissutil $(INSTALL) -D --mode=755 cm108 $(DESTDIR)/bin/cm108 $(INSTALL) -D --mode=755 dwespeak.sh $(DESTDIR)/bin/dwspeak.sh # # Telemetry Toolkit executables. Other .conf and .txt files will go into doc directory. # $(INSTALL) -D --mode=755 telemetry-toolkit/telem-balloon.pl $(DESTDIR)/bin/telem-balloon.pl $(INSTALL) -D --mode=755 telemetry-toolkit/telem-bits.pl $(DESTDIR)/bin/telem-bits.pl $(INSTALL) -D --mode=755 telemetry-toolkit/telem-data.pl $(DESTDIR)/bin/telem-data.pl $(INSTALL) -D --mode=755 telemetry-toolkit/telem-data91.pl $(DESTDIR)/bin/telem-data91.pl $(INSTALL) -D --mode=755 telemetry-toolkit/telem-eqns.pl $(DESTDIR)/bin/telem-eqns.pl $(INSTALL) -D --mode=755 telemetry-toolkit/telem-parm.pl $(DESTDIR)/bin/telem-parm.pl $(INSTALL) -D --mode=755 telemetry-toolkit/telem-seq.sh $(DESTDIR)/bin/telem-seq.sh $(INSTALL) -D --mode=755 telemetry-toolkit/telem-unit.pl $(DESTDIR)/bin/telem-unit.pl $(INSTALL) -D --mode=755 telemetry-toolkit/telem-volts.py $(DESTDIR)/bin/telem-volts.py # # Misc. data such as "tocall" to system mapping. # $(INSTALL) -D --mode=644 tocalls.txt $(DESTDIR)/share/direwolf/tocalls.txt $(INSTALL) -D --mode=644 symbols-new.txt $(DESTDIR)/share/direwolf/symbols-new.txt $(INSTALL) -D --mode=644 symbolsX.txt $(DESTDIR)/share/direwolf/symbolsX.txt # # For desktop icon. # $(INSTALL) -D --mode=644 dw-icon.png $(DESTDIR)/share/direwolf/pixmaps/dw-icon.png $(INSTALL) -D --mode=644 direwolf.desktop $(DESTDIR)/share/applications/direwolf.desktop # # Documentation. Various plain text files and PDF. # $(INSTALL) -D --mode=644 CHANGES.md $(DESTDIR)/share/doc/direwolf/CHANGES.md $(INSTALL) -D --mode=644 LICENSE-dire-wolf.txt $(DESTDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt $(INSTALL) -D --mode=644 LICENSE-other.txt $(DESTDIR)/share/doc/direwolf/LICENSE-other.txt # # ./README.md is an overview for the project main page. # Maybe we could stick it in some other place. # doc/README.md contains an overview of the PDF file contents and is more useful here. # $(INSTALL) -D --mode=644 doc/README.md $(DESTDIR)/share/doc/direwolf/README.md $(INSTALL) -D --mode=644 doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf $(DESTDIR)/share/doc/direwolf/2400-4800-PSK-for-APRS-Packet-Radio.pdf $(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(DESTDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(DESTDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(INSTALL) -D --mode=644 doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf $(DESTDIR)/share/doc/direwolf/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf $(INSTALL) -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(DESTDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf $(INSTALL) -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(DESTDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf $(INSTALL) -D --mode=644 doc/APRStt-interface-for-SARTrack.pdf $(DESTDIR)/share/doc/direwolf/APRStt-interface-for-SARTrack.pdf $(INSTALL) -D --mode=644 doc/APRStt-Listening-Example.pdf $(DESTDIR)/share/doc/direwolf/APRStt-Listening-Example.pdf $(INSTALL) -D --mode=644 doc/Bluetooth-KISS-TNC.pdf $(DESTDIR)/share/doc/direwolf/Bluetooth-KISS-TNC.pdf $(INSTALL) -D --mode=644 doc/Going-beyond-9600-baud.pdf $(DESTDIR)/share/doc/direwolf/Going-beyond-9600-baud.pdf $(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(DESTDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf $(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(DESTDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf $(INSTALL) -D --mode=644 doc/Raspberry-Pi-SDR-IGate.pdf $(DESTDIR)/share/doc/direwolf/Raspberry-Pi-SDR-IGate.pdf $(INSTALL) -D --mode=644 doc/Successful-APRS-IGate-Operation.pdf $(DESTDIR)/share/doc/direwolf/Successful-APRS-IGate-Operation.pdf $(INSTALL) -D --mode=644 doc/User-Guide.pdf $(DESTDIR)/share/doc/direwolf/User-Guide.pdf $(INSTALL) -D --mode=644 doc/WA8LMF-TNC-Test-CD-Results.pdf $(DESTDIR)/share/doc/direwolf/WA8LMF-TNC-Test-CD-Results.pdf # # Various sample config and other files go into examples under the doc directory. # When building from source, these can be put in home directory with "make install-conf". # When installed from .DEB or .RPM package, the user will need to copy these to # the home directory or other desired location. # $(INSTALL) -D --mode=644 direwolf.conf $(DESTDIR)/share/doc/direwolf/examples/direwolf.conf $(INSTALL) -D --mode=755 dw-start.sh $(DESTDIR)/share/doc/direwolf/examples/dw-start.sh $(INSTALL) -D --mode=644 sdr.conf $(DESTDIR)/share/doc/direwolf/examples/sdr.conf $(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(DESTDIR)/share/doc/direwolf/examples/telem-m0xer-3.txt $(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(DESTDIR)/share/doc/direwolf/examples/telem-balloon.conf $(INSTALL) -D --mode=644 telemetry-toolkit/telem-volts.conf $(DESTDIR)/share/doc/direwolf/examples/telem-volts.conf # # "man" pages # $(INSTALL) -D --mode=644 man1/aclients.1 $(DESTDIR)/share/man/man1/aclients.1 $(INSTALL) -D --mode=644 man1/atest.1 $(DESTDIR)/share/man/man1/atest.1 $(INSTALL) -D --mode=644 man1/decode_aprs.1 $(DESTDIR)/share/man/man1/decode_aprs.1 $(INSTALL) -D --mode=644 man1/direwolf.1 $(DESTDIR)/share/man/man1/direwolf.1 $(INSTALL) -D --mode=644 man1/gen_packets.1 $(DESTDIR)/share/man/man1/gen_packets.1 $(INSTALL) -D --mode=644 man1/kissutil.1 $(DESTDIR)/share/man/man1/kissutil.1 $(INSTALL) -D --mode=644 man1/ll2utm.1 $(DESTDIR)/share/man/man1/ll2utm.1 $(INSTALL) -D --mode=644 man1/log2gpx.1 $(DESTDIR)/share/man/man1/log2gpx.1 $(INSTALL) -D --mode=644 man1/text2tt.1 $(DESTDIR)/share/man/man1/text2tt.1 $(INSTALL) -D --mode=644 man1/tt2text.1 $(DESTDIR)/share/man/man1/tt2text.1 $(INSTALL) -D --mode=644 man1/utm2ll.1 $(DESTDIR)/share/man/man1/utm2ll.1 # # Set group and mode of HID devices corresponding to C-Media USB Audio adapters. # This will allow us to use the CM108/CM119 GPIO pins for PTT. # $(INSTALL) -D --mode=644 99-direwolf-cmedia.rules /etc/udev/rules.d/99-direwolf-cmedia.rules # @echo " " @echo "If this is your first install, not an upgrade, type this to put a copy" @echo "of the sample configuration file (direwolf.conf) in your home directory:" @echo " " @echo " make install-conf" @echo " " # Put sample configuration & startup files in home directory. # This step would be done as ordinary user. # Some people like to put the direwolf config file in /etc/ax25. # Note that all of these are also in $(DESTDIR)/share/doc/direwolf/examples/. # The Raspberry Pi has ~/Desktop but Ubuntu does not. # TODO: Handle Linux variations correctly. # Version 1.4 - Add "-n" option to avoid clobbering existing, probably customized, config files. # dw-start.sh is greatly improved in version 1.4. # It was moved from isntall-rpi to install-conf because it is not just for the RPi. .PHONY: install-conf install-conf : direwolf.conf cp -n direwolf.conf ~ cp -n sdr.conf ~ cp -n telemetry-toolkit/telem-m0xer-3.txt ~ cp -n telemetry-toolkit/telem-*.conf ~ chmod +x dw-start.sh cp -n dw-start.sh ~ ifneq ($(wildcard $(HOME)/Desktop),) @echo " " @echo "This will add a desktop icon on some systems." @echo "This is known to work on Raspberry Pi but might not be compatible with other desktops." @echo " " @echo " make install-rpi" @echo " " endif .PHONY: install-rpi install-rpi : ln -f -s $(DESTDIR)/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop # ---------------------------------- Automated Smoke Test -------------------------------- # Combine some unit tests into a single regression sanity check. check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800 # Can we encode and decode at popular data rates? check-modem1200 : gen_packets atest ./gen_packets -n 100 -o /tmp/test12.wav ./atest -F0 -PE -L63 -G71 /tmp/test12.wav ./atest -F1 -PE -L70 -G75 /tmp/test12.wav rm /tmp/test12.wav check-modem300 : gen_packets atest ./gen_packets -B300 -n 100 -o /tmp/test3.wav ./atest -B300 -F0 -L68 -G69 /tmp/test3.wav ./atest -B300 -F1 -L73 -G75 /tmp/test3.wav rm /tmp/test3.wav check-modem9600 : gen_packets atest ./gen_packets -B9600 -n 100 -o /tmp/test96.wav ./atest -B9600 -F0 -L50 -G54 /tmp/test96.wav ./atest -B9600 -F1 -L55 -G59 /tmp/test96.wav rm /tmp/test96.wav check-modem19200 : gen_packets atest ./gen_packets -r 96000 -B19200 -n 100 -o /tmp/test19.wav ./atest -B19200 -F0 -L55 -G59 /tmp/test19.wav ./atest -B19200 -F1 -L60 -G64 /tmp/test19.wav rm /tmp/test19.wav check-modem2400 : gen_packets atest ./gen_packets -B2400 -n 100 -o /tmp/test24.wav ./atest -B2400 -F0 -L70 -G78 /tmp/test24.wav ./atest -B2400 -F1 -L80 -G87 /tmp/test24.wav rm /tmp/test24.wav check-modem4800 : gen_packets atest ./gen_packets -B2400 -n 100 -o /tmp/test48.wav ./atest -B2400 -F0 -L70 -G79 /tmp/test48.wav ./atest -B2400 -F1 -L80 -G90 /tmp/test48.wav rm /tmp/test48.wav # Unit test for inner digipeater algorithm .PHONY : dtest dtest : digipeater.c dedupe.c pfilter.c \ ax25_pad.o fcs_calc.o tq.o textcolor.o \ decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a $(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(LDFLAGS) ./dtest rm dtest # Unit test for APRStt tone sequence parsing. .PHONY : ttest ttest : aprs_tt.c tt_text.c latlong.o textcolor.o misc.a geotranz.a misc.a $(CC) $(CFLAGS) -DTT_MAIN -o $@ $^ $(LDFLAGS) ./ttest rm ttest # Unit test for APRStt tone sequence / text conversions. .PHONY: tttexttest tttexttest : tt_text.c textcolor.o misc.a $(CC) $(CFLAGS) -DTTT_TEST -o $@ $^ $(LDFLAGS) ./tttexttest rm tttexttest # Unit test for Packet Filtering. .PHONY: pftest pftest : pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o symbols.o telemetry.o tt_text.o misc.a $(CC) $(CFLAGS) -DPFTEST -o $@ $^ $(LDFLAGS) ./pftest rm pftest # Unit test for telemetry decoding. .PHONY: tlmtest tlmtest : telemetry.c ax25_pad.o fcs_calc.o textcolor.o misc.a $(CC) $(CFLAGS) -DTEST -o $@ $^ $(LDFLAGS) ./tlmtest rm tlmtest # Unit test for location coordinate conversion. .PHONY: lltest lltest : latlong.c textcolor.o misc.a $(CC) $(CFLAGS) -DLLTEST -o $@ $^ $(LDFLAGS) ./lltest rm lltest # Unit test for encoding position & object report. .PHONY: enctest enctest : encode_aprs.c latlong.c textcolor.c misc.a $(CC) $(CFLAGS) -DEN_MAIN -o $@ $^ $(LDFLAGS) ./enctest rm enctest # Unit test for KISS encapsulation. .PHONY: kisstest kisstest : kiss_frame.c $(CC) $(CFLAGS) -DKISSTEST -o $@ $^ $(LDFLAGS) ./kisstest rm kisstest # Unit test for constructing frames besides UI. .PHONY: pad2test pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o misc.a $(CC) $(CFLAGS) -DPAD2TEST -o $@ $^ $(LDFLAGS) ./pad2test rm pad2test # Unit Test for XID frame encode/decode. .PHONY: xidtest xidtest : xid.c textcolor.o misc.a $(CC) $(CFLAGS) -DXIDTEST -o $@ $^ $(LDFLAGS) ./xidtest rm xidtest # Unit Test for DTMF encode/decode. .PHONY: dtmftest dtmftest : dtmf.c textcolor.o $(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^ $(LDFLAGS) ./dtmftest rm dtmftest # ----------------------------- Manual tests and experiments --------------------------- # These are not included in a normal build. Might be broken. # Unit test for IGate itest : igate.c textcolor.c ax25_pad.c fcs_calc.c textcolor.o misc.a $(CC) $(CFLAGS) -DITEST -o $@ $^ ./itest # Unit test for UDP reception with AFSK demodulator. # Temporary during development. Might not be useful anymore. udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ fcs_calc.o ax25_pad.o decode_aprs.o symbols.o textcolor.o misc.a $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) ./udptest # For demodulator tweaking experiments. # Dependencies of demod*.c, rather than .o, are intentional. demod.o : tune.h demod_afsk.o : tune.h demod_9600.o : tune.h demod_psk.o : tune.h tune.h : echo " " > tune.h testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ fcs_calc.o ax25_pad.o decode_aprs.o telemetry.o dtime_now.o latlong.o symbols.o tune.h textcolor.o misc.a $(CC) $(CFLAGS) -o atest $^ $(LDFLAGS) ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o \ symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ misc.a rm -f atest96 $(CC) $(CFLAGS) -o atest96 $^ $(LDFLAGS) ./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 -P + z8-22k.wav| grep "packets decoded in" >atest.out #./atest96 -B 9600 test9600.wav | grep "packets decoded in" >atest.out echo " " > tune.h # ------------------------------- Source distribution --------------------------------- # probably obsolete and can be removed after move to github. .PHONY: dist-src dist-src : README.md CHANGES.md doc/User-Guide.pdf doc/Raspberry-Pi-APRS.pdf \ doc/Raspberry-Pi-APRS-Tracker.pdf doc/APRStt-Implementation-Notes.pdf \ dw-start.sh dwespeak.bat dwespeak.sh \ tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec rm -f fsk_fast_filter.h echo " " > tune.h rm -f ../$z-src.zip (cd .. ; zip $z-src.zip \ $z/README.md \ $z/CHANGES.md \ $z/LICENSE* \ $z/doc/User-Guide.pdf \ $z/doc/Raspberry-Pi-APRS.pdf \ $z/doc/Raspberry-Pi-APRS-Tracker.pdf \ $z/doc/APRStt-Implementation-Notes.pdf \ $z/doc/APRS-Telemetry-Toolkit.pdf \ $z/Makefile* \ $z/*.c $z/*.h \ $z/regex/* $z/misc/* $z/geotranz/* \ $z/man1/* \ $z/generic.conf \ $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ $z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \ $z/dw-start.sh $z/direwolf.spec \ $z/dwespeak.bat $z/dwespeak.sh \ $z/telemetry-toolkit/* ) # ----------------------------------------------------------------------------------------- .PHONY: clean clean : rm -f $(APPS) gen_fff tune.h fsk_fast_filter.h *.o *.a direwolf.desktop depend : $(wildcard *.c) makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^ # # The following is updated by "make depend" # # DO NOT DELETE direwolf-1.5+dfsg/Makefile.macosx000066400000000000000000000471521347750676600171000ustar00rootroot00000000000000# # Makefile for Macintosh 10.6+ version of Dire Wolf. # # TODO: This is a modified version of Makefile.linux and it # has fallen a little behind. For example, it is missing the check target. # It would be more maintainable if we could use a single file for both. # The differences are not that great. # Maybe the most of the differences could go in to platform specific include # files rather than cluttering it up with too many if blocks. # Changes: # # 16 Dec 2015 # 1. Added condition check for gps/gpsd code. Commented out due to 32/64 bit # library issues. Macports gpsd build problem. # 2. SDK version checks are now performed by a bash script 'search_sdks.sh'. # This should resolve the varied locations Apple stored the SDKs on the different # Xcode/OS versions. Executing 'make' on the first pass asks the operator # which SDK he/she wishes to use. Executing 'make clean' resets the SDK # selection and operator intervention is once again required. Selected SDK # information resides in a file named './use_this_sdk' in the current working # directory. # 3. Removed fsk_fast_filter.h from atest receipe, clang compiler was having # a hissy fit. Not check with GCC. APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc kissutil all : $(APPS) direwolf.conf @echo " " @echo "Next step install with: " @echo " " @echo " sudo make install" @echo " " @echo " " SYS_LIBS := SYS_MIN := SYS_LIBS := $(shell ./search_sdks.sh) EXTRA_CFLAGS := DARWIN_CC := $(shell which clang) ifeq (${DARWIN_CC},) DARWIN_CC := $(shell which gcc) EXTRA_CFLAGS := else EXTRA_CFLAGS := -fvectorize -fslp-vectorize -fslp-vectorize-aggressive -pthread endif # Change as required in support of the available libraries UNAME_M := $(shell uname -m) ifeq (${UNAME_M},x86_64) CC := $(DARWIN_CC) -m64 $(SYS_LIBS) $(SYS_MIN) else CC := $(DARWIN_CC) -m32 $(SYS_LIBS) $(SYS_MIN) endif # _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24. # Explanation here: https://github.com/wb2osz/direwolf/issues/62 CFLAGS := -Os -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 $(EXTRA_CFLAGS) # That was fine for a recent Ubuntu and Raspbian Jessie. # However, Raspbian wheezy was then missing declaration for strsep and definition of fd_set. CFLAGS += -D_BSD_SOURCE # $(info $$CC is [${CC}]) # If the compiler is generating code for a 32 bit target (-m32), we can # get much better results by telling it we have at least a Pentium 3 # which hass the SSE instructions. CFLAGS += -march=core2 -msse4.1 -std=gnu99 #CFLAGS += -march=pentium3 -sse # Add -ffastmath in only if compiler version recognizes it. useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math) ifneq ($(useffast),) CFLAGS += -ffast-math endif #CFLAGS += -D_FORTIFY_SOURCE # Use PortAudio Library # Force static linking of portaudio if the static library is available. PA_LIB_STATIC := $(shell find /opt/local/lib -maxdepth 1 -type f -name "libportaudio.a") #$(info $$PA_LIB_STATIC is [${PA_LIB_STATIC}]) ifeq (${PA_LIB_STATIC},) LDLIBS += -L/opt/local/lib -lportaudio else LDLIBS += /opt/local/lib/libportaudio.a endif # Include libraries portaudio requires. LDLIBS += -framework CoreAudio -framework AudioUnit -framework AudioToolbox LDLIBS += -framework Foundation -framework CoreServices CFLAGS += -DUSE_PORTAUDIO -I/opt/local/include # Uncomment following lines to enable GPS interface & tracker function. # Not available for MacOSX (as far as I know). # Although MacPorts has gpsd, wonder if it's the same thing. Add the check # just in case it works. # Well never mind, issue with Macports with 64bit libs ;-( leave the check in # until (if ever) Macports fixes the issue. #GPS_HEADER := $(shell find /opt/local/include -maxdepth 1 -type f -name "gps.h") #ifeq (${GPS_HEADER},) #GPS_OBJS := #else #CFLAGS += -DENABLE_GPSD #LDLIBS += -L/opt/local/lib -lgps -lgpsd #GPS_OBJS := dwgps.o dwgpsnmea.o dwgpsd.o #endif # Name of current directory. # Used to generate zip file name for distribution. z := $(notdir ${CURDIR}) # Main application. direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_link.o ax25_pad.o ax25_pad2.o beacon.o \ config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o demod_psk.o \ demod.o digipeater.o cdigipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \ encode_aprs.o encode_aprs.o fcs_calc.o fcs_calc.o gen_tone.o \ geotranz.a hdlc_rec.o hdlc_rec2.o hdlc_send.o igate.o kiss_frame.o \ kiss.o kissserial.o kissnet.o latlong.o latlong.o log.o morse.o multi_modem.o \ waypoint.o serial_port.o pfilter.o ptt.o rdq.o recv.o rrbb.o server.o \ symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xid.o xmit.o \ dwgps.o dwgpsnmea.o mheard.o $(CC) $(CFLAGS) -o $@ $^ -lpthread $(LDLIBS) -lm # Optimization for slow processors. demod.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h fsk_fast_filter.h : gen_fff ./gen_fff > fsk_fast_filter.h gen_fff : demod_afsk.c dsp.c textcolor.c echo " " > tune.h $(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ $(LDFLAGS) # UTM, USNG, MGRS conversions. geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o ar -cr $@ $^ error_string.o : geotranz/error_string.c $(CC) $(CFLAGS) -c -o $@ $^ mgrs.o : geotranz/mgrs.c $(CC) $(CFLAGS) -c -o $@ $^ polarst.o : geotranz/polarst.c $(CC) $(CFLAGS) -c -o $@ $^ tranmerc.o : geotranz/tranmerc.c $(CC) $(CFLAGS) -c -o $@ $^ ups.o : geotranz/ups.c $(CC) $(CFLAGS) -c -o $@ $^ usng.o : geotranz/usng.c $(CC) $(CFLAGS) -c -o $@ $^ utm.o : geotranz/utm.c $(CC) $(CFLAGS) -c -o $@ $^ # Generate apprpriate sample configuration file for this platform. direwolf.conf : generic.conf egrep '^C|^M' generic.conf | cut -c2-999 > direwolf.conf # Where should we install it? # Macports typically installs in /opt/local so maybe you want to use that instead. INSTALLDIR := /usr/local #INSTALLDIR := /opt/local # TODO: Test this better. # Optional installation into INSTALLDIR. # Needs to be run as root or with sudo. # Command to "install" to system directories. "install" for Linux. "ginstall" for Mac. INSTALL=ginstall .PHONY: install install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png # # Applications. # $(INSTALL) direwolf $(INSTALLDIR)/bin $(INSTALL) decode_aprs $(INSTALLDIR)/bin $(INSTALL) text2tt $(INSTALLDIR)/bin $(INSTALL) tt2text $(INSTALLDIR)/bin $(INSTALL) ll2utm $(INSTALLDIR)/bin $(INSTALL) utm2ll $(INSTALLDIR)/bin $(INSTALL) aclients $(INSTALLDIR)/bin $(INSTALL) log2gpx $(INSTALLDIR)/bin $(INSTALL) gen_packets $(INSTALLDIR)/bin $(INSTALL) atest $(INSTALLDIR)/bin $(INSTALL) ttcalc $(INSTALLDIR)/bin $(INSTALL) kissutil $(INSTALLDIR)/bin $(INSTALL) dwespeak.sh $(INSTALLDIR)/bin # # Telemetry Toolkit executables. Other .conf and .txt files will go into doc directory. # $(INSTALL) telemetry-toolkit/telem-balloon.pl $(INSTALLDIR)/bin $(INSTALL) telemetry-toolkit/telem-bits.pl $(INSTALLDIR)/bin $(INSTALL) telemetry-toolkit/telem-data.pl $(INSTALLDIR)/bin $(INSTALL) telemetry-toolkit/telem-data91.pl $(INSTALLDIR)/bin $(INSTALL) telemetry-toolkit/telem-eqns.pl $(INSTALLDIR)/bin $(INSTALL) telemetry-toolkit/telem-parm.pl $(INSTALLDIR)/bin $(INSTALL) telemetry-toolkit/telem-unit.pl $(INSTALLDIR)/bin $(INSTALL) telemetry-toolkit/telem-volts.py $(INSTALLDIR)/bin # # Misc. data such as "tocall" to system mapping. # $(INSTALL) -D --mode=644 tocalls.txt $(INSTALLDIR)/share/direwolf/tocalls.txt $(INSTALL) -D --mode=644 symbols-new.txt $(INSTALLDIR)/share/direwolf/symbols-new.txt $(INSTALL) -D --mode=644 symbolsX.txt $(INSTALLDIR)/share/direwolf/symbolsX.txt $(INSTALL) -D --mode=644 dw-icon.png $(INSTALLDIR)/share/direwolf/dw-icon.png # # Documentation. Various plain text files and PDF. # $(INSTALL) -D --mode=644 README.md $(INSTALLDIR)/share/doc/direwolf/README.md $(INSTALL) -D --mode=644 CHANGES.md $(INSTALLDIR)/share/doc/direwolf/CHANGES.md $(INSTALL) -D --mode=644 LICENSE-dire-wolf.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt $(INSTALL) -D --mode=644 LICENSE-other.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt # # ./README.md is an overview for the project main page. # doc/README.md contains an overview of the PDF file contents and is more useful here. # $(INSTALL) -D --mode=644 doc/README.md $(INSTALLDIR)/share/doc/direwolf/README.md $(INSTALL) -D --mode=644 doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf $(INSTALLDIR)/share/doc/direwolf/2400-4800-PSK-for-APRS-Packet-Radio.pdf $(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(INSTALL) -D --mode=644 doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf $(INSTALLDIR)/share/doc/direwolf/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf $(INSTALL) -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf $(INSTALL) -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf $(INSTALL) -D --mode=644 doc/APRStt-interface-for-SARTrack.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-interface-for-SARTrack.pdf $(INSTALL) -D --mode=644 doc/APRStt-Listening-Example.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Listening-Example.pdf $(INSTALL) -D --mode=644 doc/Bluetooth-KISS-TNC.pdf $(INSTALLDIR)/share/doc/direwolf/Bluetooth-KISS-TNC.pdf $(INSTALL) -D --mode=644 doc/Going-beyond-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/Going-beyond-9600-baud.pdf $(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf $(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf $(INSTALL) -D --mode=644 doc/Raspberry-Pi-SDR-IGate.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-SDR-IGate.pdf $(INSTALL) -D --mode=644 doc/Successful-APRS-IGate-Operation.pdf $(INSTALLDIR)/share/doc/direwolf/Successful-APRS-IGate-Operation.pdf $(INSTALL) -D --mode=644 doc/User-Guide.pdf $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf $(INSTALL) -D --mode=644 doc/WA8LMF-TNC-Test-CD-Results.pdf $(INSTALLDIR)/share/doc/direwolf/WA8LMF-TNC-Test-CD-Results.pdf # # Sample config files also go into the doc directory. # When building from source, these can be put in home directory with "make install-conf". # When installed from .DEB or .RPM package, the user will need to copy these to # the home directory or other desired location. # Someone suggested that these could go into an "examples" subdirectory under doc. # $(INSTALL) -D --mode=644 direwolf.conf $(INSTALLDIR)/share/doc/direwolf/direwolf.conf $(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(INSTALLDIR)/share/doc/direwolf/telem-m0xer-3.txt $(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(INSTALLDIR)/share/doc/direwolf/telem-balloon.conf $(INSTALL) -D --mode=644 telemetry-toolkit/telem-volts.conf $(INSTALLDIR)/share/doc/direwolf/telem-volts.conf # # "man" pages # $(INSTALL) -D --mode=644 man1/aclients.1 $(INSTALLDIR)/man/man1/aclients.1 $(INSTALL) -D --mode=644 man1/atest.1 $(INSTALLDIR)/man/man1/atest.1 $(INSTALL) -D --mode=644 man1/decode_aprs.1 $(INSTALLDIR)/man/man1/decode_aprs.1 $(INSTALL) -D --mode=644 man1/direwolf.1 $(INSTALLDIR)/man/man1/direwolf.1 $(INSTALL) -D --mode=644 man1/gen_packets.1 $(INSTALLDIR)/man/man1/gen_packets.1 $(INSTALL) -D --mode=644 man1/ll2utm.1 $(INSTALLDIR)/man/man1/ll2utm.1 $(INSTALL) -D --mode=644 man1/log2gpx.1 $(INSTALLDIR)/man/man1/log2gpx.1 $(INSTALL) -D --mode=644 man1/text2tt.1 $(INSTALLDIR)/man/man1/text2tt.1 $(INSTALL) -D --mode=644 man1/tt2text.1 $(INSTALLDIR)/man/man1/tt2text.1 $(INSTALL) -D --mode=644 man1/utm2ll.1 $(INSTALLDIR)/man/man1/utm2ll.1 # @echo " " @echo "If this is your first install, not an upgrade, type this to put a copy" @echo "of the sample configuration file (direwolf.conf) in your home directory:" @echo " " @echo " make install-conf" @echo " " # TODO: Should we put the sample direwolf.conf file somewhere like # /usr/share/doc/direwolf/examples and add that to the # end of the search path list? # That would make it easy to see user customizations compared to the # latest sample. # These would be done as ordinary user. .PHONY: install-conf install-conf : direwolf.conf cp direwolf.conf ~ cp telemetry-toolkit/telem-m0xer-3.txt ~ cp telemetry-toolkit/telem-*.conf ~ # Separate application to decode raw data. # First three use .c rather than .o because they depend on DECAMAIN definition. decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o $(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ -lm # Convert between text and touch tone representation. text2tt : tt_text.c $(CC) $(CFLAGS) -DENC_MAIN -o $@ $^ tt2text : tt_text.c $(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^ # Convert between Latitude/Longitude and UTM coordinates. ll2utm : ll2utm.c geotranz.a $(CC) $(CFLAGS) -o $@ $^ -lm utm2ll : utm2ll.c geotranz.a $(CC) $(CFLAGS) -o $@ $^ -lm # Convert from log file to GPX. log2gpx : log2gpx.c $(CC) $(CFLAGS) -o $@ $^ -lm # Test application to generate sound. gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm demod.o : tune.h demod_afsk.o : tune.h demod_9600.o : tune.h demod_psk.o : tune.h tune.h : echo " " > tune.h testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c dtime_now.o latlong.c symbols.c tune.h textcolor.c $(CC) $(CFLAGS) -o atest $^ -lm ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out # Unit test for demodulators atest : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c dtime_now.o latlong.c symbols.c textcolor.c tt_text.c $(CC) $(CFLAGS) -o $@ $^ -lm #atest : atest.c fsk_fast_filter.h demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ # fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c latlong.c symbols.c textcolor.c tt_text.c # $(CC) $(CFLAGS) -o $@ $^ -lm # Unit test for inner digipeater algorithm dtest : digipeater.c pfilter.o ax25_pad.o dedupe.o fcs_calc.o tq.o textcolor.o \ decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o $(CC) $(CFLAGS) -DTEST -o $@ $^ ./dtest # Unit test for APRStt. ttest : aprs_tt.c tt_text.c latlong.c geotranz.a $(CC) $(CFLAGS) -DTT_MAIN -o $@ $^ # Unit test for IGate itest : igate.c textcolor.c ax25_pad.c fcs_calc.c $(CC) $(CFLAGS) -DITEST -o $@ $^ ./itest # Unit test for UDP reception with AFSK demodulator udptest : udp_test.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c $(CC) $(CFLAGS) -o $@ $^ -lm ./udptest # Unit test for telemetry decoding. tlmtest : telemetry.c ax25_pad.c fcs_calc.c textcolor.c $(CC) $(CFLAGS) -o $@ $^ -lm ./tlmtest # Multiple AGWPE network or serial port clients to test TNCs side by side. aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c $(CC) $(CFLAGS) -g -o $@ $^ # Talk to a KISS TNC. # Note: kiss_frame.c has conditional compilation on KISSUTIL. kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o dtime_now.o sock.o $(CC) $(CFLAGS) -g -DKISSUTIL -o $@ $^ # Touch Tone to Speech sample application. ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o $(CC) $(CFLAGS) -g -o $@ $^ depend : $(wildcard *.c) makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^ .PHONY: clean clean : rm -f $(APPS) gen_fff \ fsk_fast_filter.h *.o *.a use_this_sdk echo " " > tune.h .PHONY: dist-mac dist-mac: direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets \ tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png rm -f ../direwolf_dist_bin.zip (cd .. ; zip direwolf_dist_bin.zip \ $(INSTALLDIR)/bin/direwolf \ $(INSTALLDIR)/bin/decode_aprs \ $(INSTALLDIR)/bin/text2tt \ $(INSTALLDIR)/bin/tt2text \ $(INSTALLDIR)/bin/ll2utm \ $(INSTALLDIR)/bin/utm2ll \ $(INSTALLDIR)/bin/aclients \ $(INSTALLDIR)/bin/log2gpx \ $(INSTALLDIR)/bin/gen_packets \ $(INSTALLDIR)/bin/atest \ $(INSTALLDIR)/bin/ttcalc \ $(INSTALLDIR)/bin/kissutil \ $(INSTALLDIR)/bin/dwespeak.sh \ $(INSTALLDIR)/share/direwolf/tocalls.txt \ $(INSTALLDIR)/share/direwolf/config/direwolf.conf \ $(INSTALLDIR)/share/direwolf/symbols-new.txt \ $(INSTALLDIR)/share/direwolf/symbolsX.txt \ $(INSTALLDIR)/share/direwolf/dw-icon.png \ $(INSTALLDIR)/share/doc/direwolf/README.md \ $(INSTALLDIR)/share/doc/direwolf/CHANGES.md \ $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt \ $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt \ $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf \ $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf \ $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf \ $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf \ $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf \ $(INSTALLDIR)/man/man1/aclients.1 \ $(INSTALLDIR)/man/man1/atest.1 \ $(INSTALLDIR)/man/man1/decode_aprs.1 \ $(INSTALLDIR)/man/man1/direwolf.1 \ $(INSTALLDIR)/man/man1/gen_packets.1 \ $(INSTALLDIR)/man/man1/kissutil.1 \ $(INSTALLDIR)/man/man1/ll2utm.1 \ $(INSTALLDIR)/man/man1/log2gpx.1 \ $(INSTALLDIR)/man/man1/text2tt.1 \ $(INSTALLDIR)/man/man1/tt2text.1 \ $(INSTALLDIR)/man/man1/utm2ll.1 \ ) # Package it up for distribution. .PHONY: dist-src dist-src : README.md CHANGES.md \ doc/User-Guide.pdf doc/Raspberry-Pi-APRS.pdf \ doc/Raspberry-Pi-APRS-Tracker.pdf doc/APRStt-Implementation-Notes.pdf \ dw-start.sh dwespeak.bat dwespeak.sh \ tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec rm -f fsk_fast_filter.h echo " " > tune.h rm -f ../$z-src.zip (cd .. ; zip $z-src.zip \ $z/README.md \ $z/CHANGES.md \ $z/LICENSE* \ $z/doc/User-Guide.pdf \ $z/doc/Raspberry-Pi-APRS.pdf \ $z/doc/Raspberry-Pi-APRS-Tracker.pdf \ $z/doc/APRStt-Implementation-Notes.pdf \ $z/Makefile* \ $z/*.c $z/*.h \ $z/regex/* $z/misc/* $z/geotranz/* \ $z/man1/* \ $z/generic.conf \ $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ $z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \ $z/dw-start.sh $z/direwolf.spec \ $z/dwespeak.bat $z/dwespeak.sh \ $z/telemetry-toolkit/* ) # # The destination field is often used to identify the manufacturer/model. # These are not hardcoded into Dire Wolf. Instead they are read from # a file called tocalls.txt at application start up time. # # The original permanent symbols are built in but the "new" symbols, # using overlays, are often updated. These are also read from files. # # You can obtain an updated copy by typing "make tocalls-symbols". # This is not part of the normal build process. You have to do this explicitly. # # The locations below appear to be the most recent. # The copy at http://www.aprs.org/tocalls.txt is out of date. # .PHONY: tocalls-symbols tocalls-symbols : cp tocalls.txt tocalls.txt~ wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt -diff -Z tocalls.txt~ tocalls.txt cp symbols-new.txt symbols-new.txt~ wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt -diff -Z symbols-new.txt~ symbols-new.txt cp symbolsX.txt symbolsX.txt~ wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt -diff -Z symbolsX.txt~ symbolsX.txt direwolf-1.5+dfsg/Makefile.win000066400000000000000000000477571347750676600164160ustar00rootroot00000000000000# # Makefile for native Windows version of Dire Wolf. # # # This is built in the Cygwin environment but with the # compiler from http://www.mingw.org/ so there is no # dependency on extra DLLs. # # The MinGW/bin directory must be in the PATH for the # compiler. e.g. export PATH=/cygdrive/c/MinGW/bin:$PATH # # Failure to have the path set correctly will result in the # obscure message: Makefile.win:... recipe for target ... failed. # # Type "which gcc" to make sure you are getting the right one! # all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest kissutil # People say we need -mthreads option for threads to work properly. # They also say it creates a dependency on mingwm10.dll but I'm not seeing that. # Maybe that is for pthreads. We are using the Windows threads. # -Ofast was added in gcc 4.6 which was the MinGW version back in 2012. CC := gcc CFLAGS := -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC -Wall -Wlogical-op AR := ar CFLAGS += -g # TEMP EXPERIMENT - DO NOT RELEASE #CFLAGS += -fsanitize=undefined # For version 1.4, we upgrade from gcc 4.6.2 to 4.9.3. # gcc 4.8 adds these. Try them just for fun. # No, it needs libasan which is not on Windows. #CFLAGS += -fsanitize=address -fno-omit-frame-pointer # Helpful for the demodulators. Overkill for non-hot spots. #CFLAGS += -Wdouble-promotion # Don't have the patience for this right now. #CFLAGS += -Wextra # Continue working on these. CFLAGS += -Wsign-compare CFLAGS += -Wuninitialized CFLAGS += -Wold-style-declaration # CFLAGS += -fdelete-null-pointer-checks -Wnull-dereference ---not recognized #CFLAGS += -Wold-style-definition #-Wmissing-prototypes # # Let's see impact of various optimization levels. # Benchmark results with MinGW gcc version 4.6.2. # # seconds options, comments # ------ ----------------- # 119.8 -O2 Used for version 0.8 # 92.1 -O3 # 88.7 -Ofast (should be same as -O3 -ffastmath) # 87.5 -Ofast -march=pentium # 74.1 -Ofast -msse # 72.2 -Ofast -march=pentium -msse # 62.0 -Ofast -march=pentium3 (this implies -msse) # 61.9 -Ofast -march=pentium3 -msse # # A minimum of Windows XP is required due to some of the system # features being used. XP requires a Pentium processor or later. # The DSP filters can be sped up considerably with the SSE instructions. # The SSE instructions were introduced in 1999 with the # Pentium III series. # SSE2 instructions, added in 2000, don't seem to offer any advantage. # # For version 0.9, a Pentium 3 or equivalent is now the minimum required # for the prebuilt Windows distribution. # If you insist on using a computer from the previous century, # you can compile this yourself with different options. # # -------------------------------------- Main application -------------------------------- # Not sure why this is here. demod.o : fsk_demod_state.h demod_9600.o : fsk_demod_state.h demod_afsk.o : fsk_demod_state.h demod_psk.o : fsk_demod_state.h direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o \ hdlc_rec2.o multi_modem.o rdq.o rrbb.o dlq.o \ fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \ decode_aprs.o symbols.o server.o kiss.o kissserial.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \ gen_tone.o morse.o audio_win.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o \ ptt.o beacon.o dwgps.o encode_aprs.o latlong.o textcolor.o \ dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \ dwgps.o dwgpsnmea.o dtime_now.o mheard.o ax25_link.o cm108.c \ dw-icon.o regex.a misc.a geotranz.a $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 dw-icon.o : dw-icon.rc dw-icon.ico windres dw-icon.rc -o $@ # Optimization for slow processors. demod.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h fsk_fast_filter.h : gen_fff ./gen_fff > fsk_fast_filter.h gen_fff : demod_afsk.c dsp.c textcolor.c echo " " > tune.h $(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ # # The destination field is often used to identify the manufacturer/model. # These are not hardcoded into Dire Wolf. Instead they are read from # a file called tocalls.txt at application start up time. # # The original permanent symbols are built in but the "new" symbols, # using overlays, are often updated. These are also read from files. # # You can obtain an updated copy by typing "make tocalls-symbols". # This is not part of the normal build process. You have to do this explicitly. # # The locations below appear to be the most recent. # The copy at http://www.aprs.org/tocalls.txt is out of date. # .PHONY: tocalls-symbols tocalls-symbols : cp tocalls.txt tocalls.txt~ wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt -diff tocalls.txt~ tocalls.txt cp symbols-new.txt symbols-new.txt~ wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt -diff symbols-new.txt~ symbols-new.txt cp symbolsX.txt symbolsX.txt~ wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt -diff symbolsX.txt~ symbolsX.txt # ---------------------------- Other utilities included with distribution ------------------------- # Separate application to decode raw data. # First three use .c rather than .o because they depend on DECAMAIN definition. decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o regex.a misc.a geotranz.a $(CC) $(CFLAGS) -DDECAMAIN -o decode_aprs $^ # Convert between text and touch tone representation. text2tt : tt_text.c misc.a $(CC) $(CFLAGS) -DENC_MAIN -o $@ $^ tt2text : tt_text.c misc.a $(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^ # Convert between Latitude/Longitude and UTM coordinates. ll2utm : ll2utm.c textcolor.c geotranz.a misc.a $(CC) $(CFLAGS) -o $@ $^ utm2ll : utm2ll.c textcolor.c geotranz.a misc.a $(CC) $(CFLAGS) -o $@ $^ # Convert from log file to GPX. log2gpx : log2gpx.c textcolor.o misc.a $(CC) $(CFLAGS) -o $@ $^ # Test application to generate sound. gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse.o dtmf.o textcolor.o dsp.o misc.a regex.a $(CC) $(CFLAGS) -o $@ $^ # Connected mode packet application server. appserver : appserver.o textcolor.o ax25_pad.o fcs_calc.o misc.a $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 # ------------------------------------------- Libraries -------------------------------------------- # UTM, USNG, MGRS conversions. geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o ar -cr $@ $^ error_string.o : geotranz/error_string.c $(CC) $(CFLAGS) -c -o $@ $^ mgrs.o : geotranz/mgrs.c $(CC) $(CFLAGS) -c -o $@ $^ polarst.o : geotranz/polarst.c $(CC) $(CFLAGS) -c -o $@ $^ tranmerc.o : geotranz/tranmerc.c $(CC) $(CFLAGS) -c -o $@ $^ ups.o : geotranz/ups.c $(CC) $(CFLAGS) -c -o $@ $^ usng.o : geotranz/usng.c $(CC) $(CFLAGS) -c -o $@ $^ utm.o : geotranz/utm.c $(CC) $(CFLAGS) -c -o $@ $^ # # When building for Linux, we use regular expression # functions supplied by the gnu C library. # For the native WIN32 version, we need to use our own copy. # These were copied from http://gnuwin32.sourceforge.net/packages/regex.htm # Consider upgrading from https://www.gnu.org/software/libc/sources.html regex.a : regex.o ar -cr $@ $^ regex.o : regex/regex.c $(CC) $(CFLAGS) -Dbool=int -Dtrue=1 -Dfalse=0 -c -o $@ $^ # There are several string functions found in Linux # but not on Windows. Need to provide our own copy. misc.a : strsep.o strtok_r.o strcasestr.o strlcpy.o strlcat.o ar -cr $@ $^ strsep.o : misc/strsep.c $(CC) $(CFLAGS) -c -o $@ $^ strtok_r.o : misc/strtok_r.c $(CC) $(CFLAGS) -c -o $@ $^ strcasestr.o : misc/strcasestr.c $(CC) $(CFLAGS) -c -o $@ $^ strlcpy.o : misc/strlcpy.c $(CC) $(CFLAGS) -I. -c -o $@ $^ strlcat.o : misc/strlcat.c $(CC) $(CFLAGS) -I. -c -o $@ $^ # --------------------------------- Automated Smoke Test -------------------------------- # Combine some unit tests into a single regression sanity check. check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800 # Can we encode and decode at popular data rates? # Verify that single bit fixup increases the count. check-modem1200 : gen_packets atest gen_packets -n 100 -o test12.wav atest -F0 -PE -L64 -G72 test12.wav atest -F1 -PE -L70 -G75 test12.wav rm test12.wav check-modem300 : gen_packets atest gen_packets -B300 -n 100 -o test3.wav atest -B300 -F0 -L68 -G69 test3.wav atest -B300 -F1 -L71 -G75 test3.wav rm test3.wav #FIXME: test full amplitude. check-modem9600 : gen_packets atest gen_packets -B9600 -a 170 -o test96.wav sleep 1 atest -B9600 -F0 -L4 -G4 test96.wav sleep 1 rm test96.wav sleep 1 gen_packets -B9600 -n 100 -o test96.wav sleep 1 atest -B9600 -F0 -L50 -G54 test96.wav atest -B9600 -F1 -L55 -G59 test96.wav sleep 1 rm test96.wav check-modem19200 : gen_packets atest gen_packets -r 96000 -B19200 -a 170 -o test19.wav sleep 1 atest -B19200 -F0 -L4 test19.wav sleep 1 rm test19.wav sleep 1 gen_packets -r 96000 -B19200 -n 100 -o test19.wav sleep 1 atest -B19200 -F0 -L55 -G59 test19.wav atest -B19200 -F1 -L60 -G64 test19.wav sleep 1 rm test19.wav check-modem2400 : gen_packets atest gen_packets -B2400 -n 100 -o test24.wav sleep 1 atest -B2400 -F0 -L70 -G78 test24.wav atest -B2400 -F1 -L80 -G87 test24.wav sleep 1 rm test24.wav check-modem4800 : gen_packets atest gen_packets -B4800 -n 100 -o test48.wav sleep 1 atest -B4800 -F0 -L70 -G74 test48.wav atest -B4800 -F1 -L79 -G84 test48.wav sleep 1 rm test48.wav # Unit test for demodulators atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ dwgpsnmea.o dwgps.o serial_port.o latlong.c \ symbols.c tt_text.c textcolor.c telemetry.c dtime_now.o \ decode_aprs.o log.o \ misc.a regex.a echo " " > tune.h $(CC) $(CFLAGS) -o $@ $^ #./atest ..\\direwolf-0.2\\02_Track_2.wav #atest -B 9600 z9.wav #atest za100.wav atest9 : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c dtime_now.o misc.a regex.a \ fsk_fast_filter.h echo " " > tune.h $(CC) $(CFLAGS) -o $@ $^ ./atest9 -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out #./atest9 -B 9600 noise96.wav # Unit test for inner digipeater algorithm .PHONY: dtest dtest : digipeater.c dedupe.c pfilter.c \ ax25_pad.o fcs_calc.o tq.o textcolor.o \ decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a regex.a $(CC) $(CFLAGS) -DDIGITEST -o $@ $^ ./dtest rm dtest.exe # Unit test for APRStt tone seqence parsing. .PHONTY: ttest ttest : aprs_tt.c tt_text.c latlong.o textcolor.o geotranz.a misc.a $(CC) $(CFLAGS) -Igeotranz -DTT_MAIN -o $@ $^ ./ttest rm ttest.exe # Unit test for APRStt tone sequence / text conversions. .PHONY: tttexttest tttexttest : tt_text.c textcolor.o misc.a $(CC) $(CFLAGS) -DTTT_TEST -o $@ $^ ./tttexttest rm tttexttest.exe # Unit test for Packet Filtering. .PHONY: pftest pftest : pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o symbols.o telemetry.o tt_text.o misc.a regex.a $(CC) $(CFLAGS) -DPFTEST -o $@ $^ ./pftest rm pftest.exe # Unit test for telemetry decoding. .PHONY: tlmtest tlmtest : telemetry.c ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a $(CC) $(CFLAGS) -DTEST -o $@ $^ ./tlmtest rm tlmtest.exe # Unit test for location coordinate conversion. .PHONY: lltest lltest : latlong.c textcolor.o misc.a $(CC) $(CFLAGS) -DLLTEST -o $@ $^ ./lltest rm lltest.exe # Unit test for encoding position & object report. .PHONY: enctest enctest : encode_aprs.c latlong.c textcolor.c misc.a $(CC) $(CFLAGS) -DEN_MAIN -o $@ $^ ./enctest rm enctest.exe # Unit test for KISS encapsulation. .PHONY: kisstest kisstest : kiss_frame.c $(CC) $(CFLAGS) -DKISSTEST -o $@ $^ ./kisstest rm kisstest.exe # Unit test for constructing frames besides UI. .PHONY: pad2test pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o regex.a misc.a $(CC) $(CFLAGS) -DPAD2TEST -o $@ $^ ./pad2test rm pad2test.exe # Unit Test for XID frame encode/decode. .PHONY: xidtest xidtest : xid.c textcolor.o misc.a $(CC) $(CFLAGS) -DXIDTEST -o $@ $^ ./xidtest rm xidtest.exe # Unit Test for DTMF encode/decode. .PHONY: dtmftest dtmftest : dtmf.c textcolor.o $(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^ ./dtmftest rm dtmftest.exe # ------------------------------ Other manual testing & experimenting ------------------------------- tnctest : tnctest.c textcolor.o dtime_now.o serial_port.o misc.a $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 # For tweaking the demodulator. demod.o : tune.h demod_9600.o : tune.h demod_afsk.o : tune.h demod_psk.o : tune.h testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.o fsk_demod_agc.h \ hdlc_rec.o hdlc_rec2.o multi_modem.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o latlong.o symbols.o textcolor.o telemetry.o \ dwgpsnmea.o dwgps.o serial_port.o tt_text.o dtime_now.o regex.a misc.a rm -f atest.exe $(CC) $(CFLAGS) -o atest $^ ./atest -P GGG- -F 0 ../02_Track_2.wav | grep "packets decoded in" >atest.out echo " " > tune.h noisy3.wav : gen_packets ./gen_packets -B 300 -n 100 -o noisy3.wav testagc3 : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c dtime_now.o regex.a misc.a \ tune.h rm -f atest3.exe $(CC) $(CFLAGS) -o atest3 $^ ./atest3 -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out echo " " > tune.h noisy96.wav : gen_packets ./gen_packets -B 9600 -n 100 -o noisy96.wav testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ dwgpsnmea.o dwgps.o serial_port.o latlong.o \ symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ misc.a regex.a rm -f atest96.exe $(CC) $(CFLAGS) -o atest96 $^ ./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out #./atest96 -B 9600 -P + z8-22k.wav| grep "packets decoded in" >atest.out #./atest96 -B 9600 test9600.wav | grep "packets decoded in" >atest.out echo " " > tune.h testagc24 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ dwgpsnmea.o dwgps.o serial_port.o latlong.o \ symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ misc.a regex.a rm -f atest24.exe sleep 1 $(CC) $(CFLAGS) -o atest24 $^ ./atest24 -B 2400 test2400.wav | grep "packets decoded in" >atest.out echo " " > tune.h testagc48 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ dwgpsnmea.o dwgps.o serial_port.o latlong.o \ symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ misc.a regex.a rm -f atest48.exe sleep 1 $(CC) $(CFLAGS) -o atest48 $^ ./atest48 -B 4800 test4800.wav | grep "packets decoded in" >atest.out #./atest48 -B 4800 test4800.wav echo " " > tune.h # Unit test for IGate itest : igate.c textcolor.c ax25_pad.c fcs_calc.c misc.a regex.a $(CC) $(CFLAGS) -DITEST -o $@ $^ -lwinmm -lws2_32 # Multiple AGWPE network or serial port clients to test TNCs side by side. aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 # Talk to a KISS TNC. # Note: kiss_frame.c has conditional compilation on KISSUTIL. kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o sock.o dtime_now.o misc.a regex.a $(CC) $(CFLAGS) -DKISSUTIL -o $@ $^ -lwinmm -lws2_32 # Touch Tone to Speech sample application. ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 # Send GPS location to KISS TNC each second. walk96 : walk96.c dwgps.o dwgpsnmea.o kiss_frame.o \ latlong.o encode_aprs.o serial_port.o textcolor.o \ ax25_pad.o fcs_calc.o \ xmit.o hdlc_send.o gen_tone.o ptt.o tq.o \ hdlc_rec.o hdlc_rec2.o rrbb.o dsp.o audio_win.o \ multi_modem.o demod.o demod_afsk.o demod_psk.c demod_9600.o rdq.o \ server.o morse.o dtmf.o audio_stats.o dtime_now.o dlq.o \ regex.a misc.a $(CC) $(CFLAGS) -DWALK96 -o $@ $^ -lwinmm -lws2_32 #-------------------------------------------------------------- .PHONY: depend depend : $(wildcard *.c) makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^ .PHONY: clean clean : rm -f *.o *.a *.exe fsk_fast_filter.h noisy96.wav echo " " > tune.h # ------------------------------- Packaging for distribution ---------------------- # Name of zip file for distribution. z := $(notdir ${CURDIR}) .PHONY: dist-win dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2ll.exe \ aclients.exe log2gpx.exe gen_packets.exe atest.exe ttcalc.exe kissutil.exe \ generic.conf dwespeak.bat \ README.md CHANGES.md \ doc/User-Guide.pdf \ doc/Raspberry-Pi-APRS.pdf \ doc/APRStt-Implementation-Notes.pdf rm -f ../$z-win.zip egrep '^C|^W' generic.conf | cut -c2-999 > direwolf.conf unix2dos direwolf.conf cp doc/README.md README-doc.md zip --junk-paths ../$z-win.zip \ README.md \ CHANGES.md \ README-doc.md \ doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf \ doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \ doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \ doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf \ doc/APRS-Telemetry-Toolkit.pdf \ doc/APRStt-Implementation-Notes.pdf \ doc/APRStt-interface-for-SARTrack.pdf \ doc/APRStt-Listening-Example.pdf \ doc/Bluetooth-KISS-TNC.pdf \ doc/Going-beyond-9600-baud.pdf \ doc/Raspberry-Pi-APRS.pdf \ doc/Raspberry-Pi-APRS-Tracker.pdf \ doc/Raspberry-Pi-SDR-IGate.pdf \ doc/Successful-APRS-IGate-Operation.pdf \ doc/User-Guide.pdf \ doc/WA8LMF-TNC-Test-CD-Results.pdf \ LICENSE* \ direwolf.conf \ direwolf.exe \ decode_aprs.exe \ tocalls.txt symbols-new.txt symbolsX.txt \ text2tt.exe tt2text.exe \ ll2utm.exe utm2ll.exe \ aclients.exe \ log2gpx.exe \ gen_packets.exe \ atest.exe \ ttcalc.exe \ kissutil.exe \ dwespeak.bat \ telemetry-toolkit/* rm README-doc.md # Reminders if pdf files are not up to date. doc/User-Guide.pdf : doc/User-Guide.docx echo "***** User-Guide.pdf is out of date *****" doc/Raspberry-Pi-APRS.pdf : doc/Raspberry-Pi-APRS.docx echo "***** Raspberry-Pi-APRS.pdf is out of date *****" doc/Raspberry-Pi-APRS-Tracker.pdf : doc/Raspberry-Pi-APRS-Tracker.docx echo "***** Raspberry-Pi-APRS-Tracker.pdf is out of date *****" doc/APRStt-Implementation-Notes.pdf : doc/APRStt-Implementation-Notes.docx echo "***** APRStt-Implementation-Notes.pdf is out of date *****" doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf : doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.docx echo "***** A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf is out of date *****" doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf : doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.docx echo "***** A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf is out of date *****" doc/APRS-Telemetry-Toolkit.pdf : doc/APRS-Telemetry-Toolkit.docx echo "***** APRS-Telemetry-Toolkit.pdf is out of date *****" .PHONY: backup backup : mkdir /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"` cp -r . /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"` # # The following is updated by "make depend" # # DO NOT DELETE direwolf-1.5+dfsg/README.md000066400000000000000000000165701347750676600154260ustar00rootroot00000000000000 # Dire Wolf # ### Decoded Information from Radio Emissions for Windows Or Linux Fans ### In the early days of Amateur Packet Radio, it was necessary to use an expensive “Terminal Node Controller†(TNC) with specialized hardware. Those days are gone. You can now get better results at lower cost by connecting your radio to the “soundcard†interface of a computer and using software to decode the signals. Why settle for mediocre receive performance from a 1980's technology TNC using an old modem chip? Dire Wolf decodes over 1000 error-free frames from Track 2 of the [WA8LMF TNC Test CD](https://github.com/wb2osz/direwolf/tree/dev/doc/WA8LMF-TNC-Test-CD-Results.pdf), leaving all the hardware TNCs, and first generation "soundcard" modems, behind in the dust. ![](tnc-test-cd-results.png) Dire Wolf is a modern software replacement for the old 1980's style TNC built with special hardware. Without any additional software, it can perform as: - APRS GPS Tracker - Digipeater - Internet Gateway (IGate) - [APRStt](http://www.aprs.org/aprstt.html) gateway It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [Winlink Express (formerly known as RMS Express, formerly known as Winlink 2000 or WL2K)](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/), and many others. ## Features & Benefits ## ![](direwolf-block-diagram.png) ### Dire Wolf includes: ### - **Beaconing, Tracker, Telemetry Toolkit.** Send periodic beacons to provide information to others. For tracking the location is provided by a GPS receiver. Build your own telemetry applications with the toolkit. - **APRStt Gateway.** Very few hams have portable equipment for APRS but nearly everyone has a handheld radio that can send DTMF tones. APRStt allows a user, equipped with only DTMF (commonly known as Touch Tone) generation capability, to enter information into the global APRS data network. Responses can be sent by Morse Code or synthesized speech. - **Digipeaters for APRS and traditional Packet Radio.** Extend the range of other stations by re-transmitting their signals. Unmatched flexibility for cross band repeating and filtering to limit what is retransmitted. - **Internet Gateway (IGate).** IGate stations allow communication between disjoint radio networks by allowing some content to flow between them over the Internet. - **AX.25 v2.2 Link Layer.** Traditional connected mode packet radio where the TNC automatically retries transmissions and delivers data in the right order. - **KISS Interface (TCP/IP, serial port, Bluetooth) & AGW network Interface (TCP/IP).** Dire Wolf can be used as a virtual TNC for applications such as APRSIS32, UI-View32, Xastir, APRS-TW,YAAC, UISS, Linux AX25, SARTrack, Winlink / RMS Express, Outpost PM, and many others. ### Radio Interfaces: ### - **Uses computer’s “soundcard†and digital signal processing.** Lower cost and better performance than specialized hardware. Compatible interfaces include [UDRC](https://nw-digital-radio.groups.io/g/udrc/wiki/UDRC%E2%84%A2-and-Direwolf-Packet-Modem), [SignaLink USB](http://www.tigertronics.com/slusbmain.htm), [DMK URI](http://www.dmkeng.com/URI_Order_Page.htm), [RB-USB RIM](http://www.repeater-builder.com/products/usb-rim-lite.html), [RA-35](http://www.masterscommunications.com/products/radio-adapter/ra35.html), and many others. - **Standard 300, 1200 & 9600 bps modems and more.** - **DTMF (“Touch Toneâ€) Decoding and Encoding.** - **Speech Synthesizer & Morse code generator.** Transmit human understandable messages. - **Compatible with Software Defined Radios such as gqrx, rtl_fm, and SDR#.** - **Concurrent operation with up to 3 soundcards and 6 radios.** ### Portable & Open Source: ### - **Runs on Windows, Linux (PC/laptop, Raspberry Pi, etc.), Mac OSX.** ## Documentation ## [Stable Version](https://github.com/wb2osz/direwolf/tree/master/doc) [Latest Development Version](https://github.com/wb2osz/direwolf/tree/dev/doc) ## Installation ## ### Windows ### Go to the [**releases** page](https://github.com/wb2osz/direwolf/releases). Download a zip file with "win" in its name, unzip it, and run direwolf.exe from a command window. For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). ### Linux - Using git clone (recommended) ### cd ~ git clone https://www.github.com/wb2osz/direwolf cd direwolf make sudo make install make install-conf This should give you the most recent stable release. If you want the latest (possibly unstable) development version, use "git checkout dev" before the first "make" command. For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf** ### Linux - Using apt-get (Debian flavor operating systems) ### Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging. sudo apt-get update apt-cache showpkg direwolf sudo apt-get install direwolf ### Linux - Using yum (Red Hat flavor operating systems) ### Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging. sudo yum check-update sudo yum list direwolf sudo yum install direwolf ### Linux - Download source in tar or zip file ### Go to the [releases page](https://github.com/wb2osz/direwolf/releases). Chose desired release and download the source as zip or compressed tar file. Unpack the files, with "unzip" or "tar xfz," and then: cd direwolf-* make sudo make install make install-conf For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf** ### Macintosh OS X ### Read the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). It is a lot more complicated than Linux. If you have problems, post them to the [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) discussion group. I don't have a Mac and probably won't be able to help you. I rely on others, in the user community, for the Mac version. ## Join the conversation ## Here are some good places to ask questions and share your experience: - [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) - [Raspberry Pi 4 Ham Radio](https://groups.io/g/RaspberryPi-4-HamRadio) - [linuxham](https://groups.io/g/linuxham) - [TAPR aprssig](http://www.tapr.org/pipermail/aprssig/) The github "issues" section is for reporting software defects and enhancement requests. It is NOT a place to ask questions or have general discussions. Please use one of the locations above. direwolf-1.5+dfsg/aclients.c000066400000000000000000000476061347750676600161210ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: aclients.c * * Purpose: Multiple concurrent APRS clients for comparing * TNC demodulator performance. * * Description: Establish connection with multiple servers and * compare results side by side. * * Usage: aclients port1=name1 port2=name2 ... * * Example: aclients 8000=AGWPE 192.168.1.64:8002=DireWolf COM1=D710A * * This will connect to multiple physical or virtual * TNCs, read packets from them, and display results. * * Each port can have the following forms: * * * host-name:tcp-port * * ip-addr:tcp-port * * tcp-port * * serial port name (e.g. COM1, /dev/ttyS0) * *---------------------------------------------------------------*/ /* * Native Windows: Use the Winsock interface. * Linux: Use the BSD socket interface. */ #include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h #if __WIN32__ #include #include // _WIN32_WINNT must be set to 0x0501 before including this #else #include #include #include #include #include #include #include #include #include #include #include #endif #include #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "version.h" struct agwpe_s { short portx; /* 0 for first, 1 for second, etc. */ short port_hi_reserved; short kind_lo; /* message type */ short kind_hi; char call_from[10]; char call_to[10]; int data_len; /* Number of data bytes following. */ int user_reserved; }; #if __WIN32__ static unsigned __stdcall client_thread_net (void *arg); static unsigned __stdcall client_thread_serial (void *arg); #else static void * client_thread_net (void *arg); static void * client_thread_serial (void *arg); #endif /* * Convert Internet address to text. * Can't use InetNtop because it is supported only on Windows Vista and later. */ static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize) { struct sockaddr_in *sa4; struct sockaddr_in6 *sa6; switch (Family) { case AF_INET: sa4 = (struct sockaddr_in *)pAddr; #if __WIN32__ snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1, sa4->sin_addr.S_un.S_un_b.s_b2, sa4->sin_addr.S_un.S_un_b.s_b3, sa4->sin_addr.S_un.S_un_b.s_b4); #else inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize); #endif break; case AF_INET6: sa6 = (struct sockaddr_in6 *)pAddr; #if __WIN32__ snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x", ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7])); #else inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize); #endif break; default: snprintf (pStringBuf, StringBufSize, "Invalid address family!"); } assert (strlen(pStringBuf) < StringBufSize); return pStringBuf; } /*------------------------------------------------------------------ * * Name: main * * Purpose: Start up multiple client threads listening to different * TNCs. Print packets. Tally up statistics. * * Usage: Described above. * *---------------------------------------------------------------*/ #define MAX_CLIENTS 6 /* Obtained from the command line. */ static int num_clients; static char hostname[MAX_CLIENTS][50]; /* DNS host name or IPv4 address. */ /* Some of the code is there for IPv6 but */ /* needs more work. */ /* Defaults to "localhost" if not specified. */ static char port[MAX_CLIENTS][30]; /* If it begins with a digit, it is considered */ /* a TCP port number at the hostname. */ /* Otherwise, we treat it as a serial port name. */ static char description[MAX_CLIENTS][50]; /* Name used in the output. */ #if __WIN32__ static HANDLE client_th[MAX_CLIENTS]; #else static pthread_t client_tid[MAX_CLIENTS]; #endif #define LINE_WIDTH 120 static int column_width; static char packets[LINE_WIDTH+4]; static int packet_count[MAX_CLIENTS]; //#define PRINT_MINUTES 2 #define PRINT_MINUTES 30 int main (int argc, char *argv[]) { int j; time_t start_time, now, next_print_time; #if __WIN32__ #else int e; setlinebuf (stdout); #endif /* * Extract command line args. */ num_clients = argc - 1; if (num_clients < 1 || num_clients > MAX_CLIENTS) { printf ("Specify up to %d TNCs on the command line.\n", MAX_CLIENTS); exit (1); } column_width = LINE_WIDTH / num_clients; for (j=0; j= next_print_time) { next_print_time = now + (PRINT_MINUTES) * 60; printf ("\nTotals after %d minutes", (int)((now - start_time) / 60)); for (j=0; jai_next) { #if DEBUG_DNS ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); printf (" %s\n", ipaddr_str); #endif hosts[num_hosts] = ai; if (num_hosts < MAX_HOSTS) num_hosts++; } #if DEBUG_DNS printf ("addresses for hostname:\n"); for (n=0; nai_family, hosts[n]->ai_addr, ipaddr_str, sizeof(ipaddr_str)); printf (" %s\n", ipaddr_str); } #endif // Try each address until we find one that is successful. for (n=0; nai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); #if __WIN32__ if (is == INVALID_SOCKET) { printf ("Socket creation failed, err=%d", WSAGetLastError()); WSACleanup(); is = -1; continue; } #else if (err != 0) { printf ("Socket creation failed, err=%s", gai_strerror(err)); (void) close (is); is = -1; continue; } #endif #ifndef DEBUG_DNS err = connect(is, ai->ai_addr, (int)ai->ai_addrlen); #if __WIN32__ if (err == SOCKET_ERROR) { #if DEBUGx printf("Connect to %s on %s (%s), port %s failed.\n", description[my_index], hostname[my_index], ipaddr_str, port[my_index]); #endif closesocket (is); is = -1; continue; } #else if (err != 0) { #if DEBUGx printf("Connect to %s on %s (%s), port %s failed.\n", description[my_index], hostname[my_index], ipaddr_str, port[my_index]); #endif (void) close (is); is = -1; continue; } int flag = 1; err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag)); if (err < 0) { printf("setsockopt TCP_NODELAY failed.\n"); } #endif /* Success. */ printf("Client %d now connected to %s on %s (%s), port %s\n", my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] ); server_sock = is; #endif break; } freeaddrinfo(ai_head); if (server_sock == -1) { printf("Client %d unable to connect to %s on %s (%s), port %s\n", my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] ); exit (1); } /* * Send command to toggle reception of frames in raw format. * * Note: Monitor format is only for UI frames. * It also discards the via path. */ memset (&mon_cmd, 0, sizeof(mon_cmd)); mon_cmd.kind_lo = 'k'; SOCK_SEND (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd)); /* * Print all of the monitored packets. */ while (1) { int n; n = SOCK_RECV (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd)); if (n != sizeof(mon_cmd)) { printf ("Read error, client %d received %d command bytes. Terminating.\n", my_index, n); exit (1); } #if DEBUGx printf ("client %d received '%c' data, data_len = %d\n", my_index, mon_cmd.kind_lo, mon_cmd.data_len); #endif assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < (int)(sizeof(data))); if (mon_cmd.data_len > 0) { n = SOCK_RECV (server_sock, data, mon_cmd.data_len); if (n != mon_cmd.data_len) { printf ("Read error, client %d received %d data bytes.\n", my_index, n); exit (1); } } /* * Print it and add to counter. * The AGWPE score was coming out double the proper value because * we were getting the same thing from ports 2 and 3. * 'use_chan' is the first channel we hear from. * Listen only to that one. */ if (mon_cmd.kind_lo == 'K' && (use_chan == -1 || use_chan == mon_cmd.portx)) { packet_t pp; char *pinfo; int info_len; char result[400]; char *p; int col, len; alevel_t alevel; //printf ("server %d, portx = %d\n", my_index, mon_cmd.portx); use_chan = mon_cmd.portx; memset (&alevel, 0xff, sizeof(alevel)); pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, alevel); assert (pp != NULL); ax25_format_addrs (pp, result); info_len = ax25_get_info (pp, (unsigned char **)(&pinfo)); pinfo[info_len] = '\0'; strlcat (result, pinfo, sizeof(result)); for (p=result; *p!='\0'; p++) { if (! isprint(*p)) *p = ' '; } #if DEBUGx printf ("[%d] %s\n", my_index, result); #endif col = column_width * my_index; len = strlen(result); #define MARGIN 3 if (len > column_width - 3) { len = column_width - 3; } if (packets[col] == ' ') { memcpy (packets+col, result, (size_t)len); } else { memcpy (packets+col, "OVERRUN! ", (size_t)10); } ax25_delete (pp); packet_count[my_index]++; } } } /* end client_thread_net */ /*------------------------------------------------------------------- * * Name: client_thread_serial * * Purpose: Establish connection with a TNC via serial port. * * Inputs: arg - My instance index, 0 thru MAX_CLIENTS-1. * * Outputs: packets - Received packets are put in the corresponding column. * *--------------------------------------------------------------------*/ #if __WIN32__ typedef HANDLE MYFDTYPE; #define MYFDERROR INVALID_HANDLE_VALUE #else typedef int MYFDTYPE; #define MYFDERROR (-1) #endif #if __WIN32__ static unsigned __stdcall client_thread_serial (void *arg) #else static void * client_thread_serial (void *arg) #endif { int my_index = (int)(long)arg; #if __WIN32__ MYFDTYPE fd; DCB dcb; int ok; // Bug: Won't work for ports above COM9. // http://support.microsoft.com/kb/115831 fd = CreateFile(port[my_index], GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (fd == MYFDERROR) { printf("Client %d unable to connect to %s on %s.\n", my_index, description[my_index], port[my_index] ); exit (1); } /* Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363201(v=vs.85).aspx */ memset (&dcb, 0, sizeof(dcb)); dcb.DCBlength = sizeof(DCB); ok = GetCommState (fd, &dcb); if (! ok) { printf ("GetCommState failed.\n"); } /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */ dcb.DCBlength = sizeof(DCB); dcb.BaudRate = 9600; dcb.fBinary = 1; dcb.fParity = 0; dcb.fOutxCtsFlow = 0; dcb.fOutxDsrFlow = 0; dcb.fDtrControl = 0; dcb.fDsrSensitivity = 0; dcb.fOutX = 0; dcb.fInX = 0; dcb.fErrorChar = 0; dcb.fNull = 0; /* Don't drop nul characters! */ dcb.fRtsControl = 0; dcb.ByteSize = 8; dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT; ok = SetCommState (fd, &dcb); if (! ok) { printf ("SetCommState failed.\n"); } #else /* Linux version. */ int fd; struct termios ts; int e; fd = open (port[my_index], O_RDWR); if (fd == MYFDERROR) { printf("Client %d unable to connect to %s on %s.\n", my_index, description[my_index], port[my_index] ); exit (1); } e = tcgetattr (fd, &ts); if (e != 0) { perror ("nm tcgetattr"); } cfmakeraw (&ts); // TODO: speed? ts.c_cc[VMIN] = 1; /* wait for at least one character */ ts.c_cc[VTIME] = 0; /* no fancy timing. */ e = tcsetattr (fd, TCSANOW, &ts); if (e != 0) { perror ("nm tcsetattr"); } #endif /* Success. */ printf("Client %d now connected to %s on %s\n", my_index, description[my_index], port[my_index] ); /* * Assume we are already in monitor mode. */ /* * Print all of the monitored packets. */ while (1) { unsigned char ch; char result[500]; int col, len; int done; char *p; len = 0; done = 0; while ( ! done) { #if __WIN32__ DWORD n; if (! ReadFile (fd, &ch, 1, &n, NULL)) { printf ("Read error on %s.\n", description[my_index]); CloseHandle (fd); exit (1); } #else int n; if ( ( n = read(fd, & ch, 1)) < 0) { printf ("Read error on %s.\n", description[my_index]); close (fd); exit (1); } #endif if (n == 1) { /* * Try to build one line for each packet. * The KPC3+ breaks a packet into two lines like this: * * KB1ZXL-1>T2QY5P,W1MHL*,WIDE2-1: <>: * `c0+!h4>/]"4a}146.520MHz Listening, V-Alert & WLNK-1= * * N8VIM>BEACON,W1XM,WB2OSZ-1,WIDE2*: : * !4240.85N/07133.99W_PHG72604/ Pepperell, MA. WX. 442.9+ PL100 * * Don't know why some are <> and some . * * Anyhow, ignore the return character if preceded by >: */ if (ch == '\r') { if (len >= 10 && result[len-2] == '>' && result[len-1] == ':') { continue; } done = 1; continue; } if (ch == '\n') continue; result[len++] = ch; } } result[len] = '\0'; /* * Print it and add to counter. */ if (len > 0) { /* Blank any unprintable characters. */ for (p=result; *p!='\0'; p++) { if (! isprint(*p)) *p = ' '; } #if DEBUGx printf ("[%d] %s\n", my_index, result); #endif col = column_width * my_index; if (len > column_width - 3) { len = column_width - 3; } if (packets[col] == ' ') { memcpy (packets+col, result, (size_t)len); } else { memcpy (packets+col, "OVERRUN! ", (size_t)10); } packet_count[my_index]++; } } } /* end client_thread_serial */ /* end aclients.c */ direwolf-1.5+dfsg/aprs_tt.c000066400000000000000000001523401347750676600157630ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: aprs_tt.c * * Purpose: First half of APRStt gateway. * * Description: This file contains functions to parse the tone sequences * and extract meaning from them. * * tt_user.c maintains information about users and * generates the APRS Object Reports. * * * References: This is based upon APRStt (TM) documents with some * artistic freedom. * * http://www.aprs.org/aprstt.html * *---------------------------------------------------------------*/ #define APRS_TT_C 1 #include "direwolf.h" // TODO: clean up terminolgy. // "Message" has a specific meaning in APRS and this is not it. // Touch Tone sequence should be appropriate. // What do we call the parts separated by * key? Field. #include #include #include #include #include #include #include #include #include "version.h" #include "ax25_pad.h" #include "hdlc_rec2.h" /* for process_rec_frame */ #include "textcolor.h" #include "aprs_tt.h" #include "tt_text.h" #include "tt_user.h" #include "symbols.h" #include "latlong.h" #include "dlq.h" #include "demod.h" /* for alevel_t & demod_get_audio_level() */ #include "tq.h" // geotranz #include "utm.h" #include "mgrs.h" #include "usng.h" #include "error_string.h" /* Convert between degrees and radians. */ #define D2R(d) ((d) * M_PI / 180.) #define R2D(r) ((r) * 180. / M_PI) /* * Touch Tone sequences are accumulated here until # terminator found. * Kept separate for each audio channel so the gateway CAN be listening * on multiple channels at the same time. */ #define MAX_MSG_LEN 100 static char msg_str[MAX_CHANS][MAX_MSG_LEN+1]; static int msg_len[MAX_CHANS]; static int parse_fields (char *msg); static int parse_callsign (char *e); static int parse_object_name (char *e); static int parse_symbol (char *e); static int parse_aprstt3_call (char *e); static int parse_location (char *e); static int parse_comment (char *e); static int expand_macro (char *e); #ifndef TT_MAIN static void raw_tt_data_to_app (int chan, char *msg); #endif static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr, size_t valstrsize); #if TT_MAIN static void check_result (void); #endif /*------------------------------------------------------------------ * * Name: aprs_tt_init * * Purpose: Initialize the APRStt gateway at system startup time. * * Inputs: Configuration options gathered by config.c. * * Global out: Make our own local copy of the structure here. * * Returns: None * * Description: The main program needs to call this at application * start up time after reading the configuration file. * * TT_MAIN is defined for unit testing. * *----------------------------------------------------------------*/ static struct tt_config_s tt_config; #if TT_MAIN #define NUM_TEST_CONFIG (sizeof(test_config) / sizeof (struct ttloc_s)) static struct ttloc_s test_config[] = { { TTLOC_POINT, "B01", .point.lat = 12.25, .point.lon = 56.25 }, { TTLOC_POINT, "B988", .point.lat = 12.50, .point.lon = 56.50 }, { TTLOC_VECTOR, "B5bbbdddd", .vector.lat = 53., .vector.lon = -1., .vector.scale = 1000. }, /* km units */ /* Hilltop Tower http://www.aprs.org/aprs-jamboree-2013.html */ { TTLOC_VECTOR, "B5bbbddd", .vector.lat = 37+55.37/60., .vector.lon = -(81+7.86/60.), .vector.scale = 16.09344 }, /* .01 mile units */ { TTLOC_GRID, "B2xxyy", .grid.lat0 = 12.00, .grid.lon0 = 56.00, .grid.lat9 = 12.99, .grid.lon9 = 56.99 }, { TTLOC_GRID, "Byyyxxx", .grid.lat0 = 37 + 50./60.0, .grid.lon0 = 81, .grid.lat9 = 37 + 59.99/60.0, .grid.lon9 = 81 + 9.99/60.0 }, { TTLOC_MHEAD, "BAxxxxxx", .mhead.prefix = "326129" }, { TTLOC_SATSQ, "BAxxxx" }, { TTLOC_MACRO, "xxyyy", .macro.definition = "B9xx*AB166*AA2B4C5B3B0Ayyy" }, { TTLOC_MACRO, "xxxxzzzzzzzzzz", .macro.definition = "BAxxxx*ACzzzzzzzzzz" }, }; #endif void aprs_tt_init (struct tt_config_s *p) { int c; #if TT_MAIN /* For unit testing. */ memset (&tt_config, 0, sizeof(struct tt_config_s)); tt_config.ttloc_size = NUM_TEST_CONFIG; tt_config.ttloc_ptr = test_config; tt_config.ttloc_len = NUM_TEST_CONFIG; /* Don't care about xmit timing or corral here. */ #else // TODO: Keep ptr instead of making a copy. memcpy (&tt_config, p, sizeof(struct tt_config_s)); #endif for (c=0; c= 0 && chan < MAX_CHANS); //if (button != '.') { // dw_printf ("aprs_tt_button (%d, '%c')\n", chan, button); //} // TODO: Might make more sense to put timeout here rather in the dtmf decoder. if (button == '$') { /* Timeout reset. */ msg_len[chan] = 0; msg_str[chan][0] = '\0'; } else if (button != '.' && button != ' ') { if (msg_len[chan] < MAX_MSG_LEN) { msg_str[chan][msg_len[chan]++] = button; msg_str[chan][msg_len[chan]] = '\0'; } if (button == '#') { /* * Put into the receive queue like any other packet. * This way they are all processed by the common receive thread * rather than the thread associated with the particular audio device. */ raw_tt_data_to_app (chan, msg_str[chan]); msg_len[chan] = 0; msg_str[chan][0] = '\0'; } } else { /* * Idle time. Poll occasionally for processing. * Timing would be off we we are listening to more than * one channel so do this only for the one specified * in the TTOBJ command. */ if (chan == tt_config.obj_recv_chan) { poll_period++; if (poll_period >= 39) { poll_period = 0; tt_user_background (); } } } } /* end aprs_tt_button */ #endif /*------------------------------------------------------------------ * * Name: aprs_tt_sequence * * Purpose: Process complete received touch tone sequence * terminated by #. * * Inputs: chan - Audio channel it came from. * * msg - String of DTMF buttons. * # should be the final character. * * Returns: None * * Description: Process a complete tone sequence. * It should have one or more fields separated by * * and terminated by a final # like these: * * callsign # * entry1 * callsign # * entry1 * entry * callsign # * * Limitation: Has one set of static data for communication among * group of functions. This shouldn't be a problem * when receiving on multiple channels at once * because they get serialized thru the receive packet queue. * *----------------------------------------------------------------*/ static char m_callsign[20]; /* really object name */ /* * Standard APRStt has symbol code 'A' (box) with overlay of 0-9, A-Z. * * Dire Wolf extension allows: * Symbol table '/' (primary), any symbol code. * Symbol table '\' (alternate), any symbol code. * Alternate table symbol code, overlay of 0-9, A-Z. */ static char m_symtab_or_overlay; static char m_symbol_code; // Default 'A' static char m_loc_text[24]; static double m_longitude; // Set to G_UNKNOWN if not defined. static double m_latitude; // Set to G_UNKNOWN if not defined. static int m_ambiguity; static char m_comment[200]; static char m_freq[12]; static char m_ctcss[8]; static char m_mic_e; static char m_dao[6]; static int m_ssid; // Default 12 for APRStt user. void aprs_tt_sequence (int chan, char *msg) { int err; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("\n\"%s\"\n", msg); #endif /* * Discard empty message. * In case # is there as optional start. */ if (msg[0] == '#') return; /* * The parse functions will fill these in. */ strlcpy (m_callsign, "", sizeof(m_callsign)); m_symtab_or_overlay = APRSTT_DEFAULT_SYMTAB; m_symbol_code = APRSTT_DEFAULT_SYMBOL; strlcpy (m_loc_text, "", sizeof(m_loc_text)); m_longitude = G_UNKNOWN; m_latitude = G_UNKNOWN; m_ambiguity = 0; strlcpy (m_comment, "", sizeof(m_comment)); strlcpy (m_freq, "", sizeof(m_freq)); strlcpy (m_ctcss, "", sizeof(m_ctcss)); m_mic_e = ' '; strlcpy (m_dao, "!T !", sizeof(m_dao)); /* start out unknown */ m_ssid = 12; /* * Parse the touch tone sequence. */ err = parse_fields (msg); #if defined(DEBUG) text_color_set(DW_COLOR_DEBUG); dw_printf ("callsign=\"%s\", ssid=%d, symbol=\"%c%c\", freq=\"%s\", ctcss=\"%s\", comment=\"%s\", lat=%.4f, lon=%.4f, dao=\"%s\"\n", m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_freq, m_ctcss, m_comment, m_latitude, m_longitude, m_dao); #endif #if TT_MAIN (void)err; // suppress variable set but not used warning. check_result (); // for unit testing. #else /* * If digested successfully. Add to our list of users and schedule transmissions. */ if (err == 0) { err = tt_user_heard (m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_loc_text, m_latitude, m_longitude, m_ambiguity, m_freq, m_ctcss, m_comment, m_mic_e, m_dao); } /* * If a command / script was supplied, run it now. * This can do additional processing and provide a custom audible response. * This is done only for the success case. * It might be useful to run it for error cases as well but we currently * don't pass in the success / failure code to know the difference. */ char script_response[1000]; strlcpy (script_response, "", sizeof(script_response)); if (err == 0 && strlen(tt_config.ttcmd) > 0) { dw_run_cmd (tt_config.ttcmd, 1, script_response, sizeof(script_response)); } /* * Send response to user by constructing packet with SPEECH or MORSE as destination. * Source shouldn't matter because it doesn't get transmitted as AX.25 frame. * Use high priority queue for consistent timing. * * Anything from script, above, will override other predefined responses. */ char audible_response[1000]; snprintf (audible_response, sizeof(audible_response), "APRSTT>%s:%s", tt_config.response[err].method, (strlen(script_response) > 0) ? script_response : tt_config.response[err].mtext); packet_t pp; pp = ax25_from_text (audible_response, 0); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error. Couldn't make frame from \"%s\"\n", audible_response); return; } tq_append (chan, TQ_PRIO_0_HI, pp); #endif /* ifndef TT_MAIN */ } /* end aprs_tt_sequence */ /*------------------------------------------------------------------ * * Name: parse_fields * * Purpose: Separate the complete string of touch tone characters * into fields, delimited by *, and process each. * * Inputs: msg - String of DTMF buttons. * * Returns: None * * Description: It should have one or more fields separated by *. * * callsign # * entry1 * callsign # * entry1 * entry * callsign # * * Note that this will be used recursively when macros * are expanded. * * "To iterate is human, to recurse divine." * * Returns: 0 for success or one of the TT_ERROR_... codes. * *----------------------------------------------------------------*/ static int parse_fields (char *msg) { char stemp[MAX_MSG_LEN+1]; char *e; char *save; int err; //text_color_set(DW_COLOR_DEBUG); //dw_printf ("parse_fields (%s).\n", msg); strlcpy (stemp, msg, sizeof(stemp)); e = strtok_r (stemp, "*#", &save); while (e != NULL) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("parse_fields () field = %s\n", e); switch (*e) { case 'A': switch (e[1]) { case 'A': /* AA object-name */ err = parse_object_name (e); if (err != 0) return (err); break; case 'B': /* AB symbol */ err = parse_symbol (e); if (err != 0) return (err); break; case 'C': /* AC new-style-callsign */ err = parse_aprstt3_call (e); if (err != 0) return (err); break; default: /* Traditional style call or suffix */ err = parse_callsign (e); if (err != 0) return (err); break; } break; case 'B': err = parse_location (e); if (err != 0) return (err); break; case 'C': err = parse_comment (e); if (err != 0) return (err); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': err = expand_macro (e); if (err != 0) return (err); break; case '\0': /* Empty field. Just ignore it. */ /* This would happen if someone uses a leading *. */ break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("Field does not start with A, B, C, or digit: \"%s\"\n", msg); return (TT_ERROR_D_MSG); } e = strtok_r (NULL, "*#", &save); } //text_color_set(DW_COLOR_DEBUG); //dw_printf ("parse_fields () normal return\n"); return (0); } /* end parse_fields */ /*------------------------------------------------------------------ * * Name: expand_macro * * Purpose: Expand compact form "macro" to full format then process. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. * In this case, it should contain only digits. * * Returns: 0 for success or one of the TT_ERROR_... codes. * * Description: Separate out the fields, perform substitution, * call parse_fields for processing. * * * Future: Generalize this to allow any lower case letter for substitution? * *----------------------------------------------------------------*/ #define VALSTRSIZE 20 static int expand_macro (char *e) { //int len; int ipat; char xstr[VALSTRSIZE], ystr[VALSTRSIZE], zstr[VALSTRSIZE], bstr[VALSTRSIZE], dstr[VALSTRSIZE]; char stemp[MAX_MSG_LEN+1]; char *d; text_color_set(DW_COLOR_DEBUG); dw_printf ("Macro tone sequence: '%s'\n", e); //len = strlen(e); ipat = find_ttloc_match (e, xstr, ystr, zstr, bstr, dstr, VALSTRSIZE); if (ipat >= 0) { // Why did we print b & d here? // Documentation says only x, y, z can be used with macros. // Only those 3 are processed below. //dw_printf ("Matched pattern %3d: '%s', x=%s, y=%s, z=%s, b=%s, d=%s\n", ipat, tt_config.ttloc_ptr[ipat].pattern, xstr, ystr, zstr, bstr, dstr); dw_printf ("Matched pattern %3d: '%s', x=%s, y=%s, z=%s\n", ipat, tt_config.ttloc_ptr[ipat].pattern, xstr, ystr, zstr); dw_printf ("Replace with: '%s'\n", tt_config.ttloc_ptr[ipat].macro.definition); if (tt_config.ttloc_ptr[ipat].type != TTLOC_MACRO) { /* Found match to a different type. Really shouldn't be here. */ /* Print internal error message... */ dw_printf ("expand_macro: type != TTLOC_MACRO\n"); return (TT_ERROR_INTERNAL); } /* * We found a match for the length and any fixed digits. * Substitute values in to the definition. */ strlcpy (stemp, "", sizeof(stemp)); for (d = tt_config.ttloc_ptr[ipat].macro.definition; *d != '\0'; d++) { while (( *d == 'x' || *d == 'y' || *d == 'z') && *d == d[1]) { /* Collapse adjacent matching substitution characters. */ d++; } switch (*d) { case 'x': strlcat (stemp, xstr, sizeof(stemp)); break; case 'y': strlcat (stemp, ystr, sizeof(stemp)); break; case 'z': strlcat (stemp, zstr, sizeof(stemp)); break; default: { char c1[2]; c1[0] = *d; c1[1] = '\0'; strlcat (stemp, c1, sizeof(stemp)); } break; } } /* * Process as if we heard this over the air. */ dw_printf ("After substitution: '%s'\n", stemp); return (parse_fields (stemp)); } else { /* Send reject sound. */ /* Does not match any macro definitions. */ text_color_set(DW_COLOR_ERROR); dw_printf ("Tone sequence did not match any pattern\n"); return (TT_ERROR_MACRO_NOMATCH); } /* should be unreachable */ return (0); } /*------------------------------------------------------------------ * * Name: parse_callsign * * Purpose: Extract traditional format callsign or object name from touch tone sequence. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. * In this case, it should start with "A". * * Outputs: m_callsign * * m_symtab_or_overlay - Set to 0-9 or A-Z if specified. * * m_symbol_code - Always set to 'A'. * NO! This should be applied only if we * have the default value at this point. * The symbol might have been explicitly * set already and we don't want to overwrite that. * * Returns: 0 for success or one of the TT_ERROR_... codes. * * Description: We recognize 3 different formats: * * Annn - 3 digits are a tactical callsign. No overlay. * * Annnvk - Abbreviation with 3 digits, numeric overlay, checksum. * Annnvvk - Abbreviation with 3 digits, letter overlay, checksum. * * Att...ttvk - Full callsign in two key method, numeric overlay, checksum. * Att...ttvvk - Full callsign in two key method, letter overlay, checksum. * * *----------------------------------------------------------------*/ static int checksum_not_ok (char *str, int len, char found) { int i; int sum; char expected; sum = 0; for (i=0; i= 'A' && str[i] <= 'D') { sum += str[i] - 'A' + 10; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("aprs_tt: checksum: bad character \"%c\" in checksum calculation!\n", str[i]); } } expected = '0' + (sum % 10); if (expected != found) { text_color_set(DW_COLOR_ERROR); dw_printf ("Bad checksum for \"%.*s\". Expected %c but received %c.\n", len, str, expected, found); return (TT_ERROR_BAD_CHECKSUM); } return (0); } static int parse_callsign (char *e) { int len; char tttemp[40], stemp[30]; assert (*e == 'A'); len = strlen(e); /* * special case: 3 digit tactical call. */ if (len == 4 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3])) { strlcpy (m_callsign, e+1, sizeof(m_callsign)); return (0); } /* * 3 digit abbreviation: We only do the parsing here. * Another part of application will try to find corresponding full call. */ if ((len == 6 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isdigit(e[5])) || (len == 7 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isupper(e[5]) && isdigit(e[6]))) { int cs_err = checksum_not_ok (e+1, len-2, e[len-1]); if (cs_err != 0) { return (cs_err); } strncpy (m_callsign, e+1, 3); m_callsign[3] = '\0'; if (len == 7) { tttemp[0] = e[len-3]; tttemp[1] = e[len-2]; tttemp[2] = '\0'; tt_two_key_to_text (tttemp, 0, stemp); m_symbol_code = APRSTT_DEFAULT_SYMBOL; m_symtab_or_overlay = stemp[0]; } else { m_symbol_code = APRSTT_DEFAULT_SYMBOL; m_symtab_or_overlay = e[len-2]; } return (0); } /* * Callsign in two key format. */ if (len >= 7 && len <= 24) { int cs_err = checksum_not_ok (e+1, len-2, e[len-1]); if (cs_err != 0) { return (cs_err); } if (isupper(e[len-2])) { strncpy (tttemp, e+1, len-4); tttemp[len-4] = '\0'; tt_two_key_to_text (tttemp, 0, m_callsign); tttemp[0] = e[len-3]; tttemp[1] = e[len-2]; tttemp[2] = '\0'; tt_two_key_to_text (tttemp, 0, stemp); m_symbol_code = APRSTT_DEFAULT_SYMBOL; m_symtab_or_overlay = stemp[0]; } else { strncpy (tttemp, e+1, len-3); tttemp[len-3] = '\0'; tt_two_key_to_text (tttemp, 0, m_callsign); m_symbol_code = APRSTT_DEFAULT_SYMBOL; m_symtab_or_overlay = e[len-2]; } return (0); } text_color_set(DW_COLOR_ERROR); dw_printf ("Touch tone callsign not valid: \"%s\"\n", e); return (TT_ERROR_INVALID_CALL); } /*------------------------------------------------------------------ * * Name: parse_object_name * * Purpose: Extract object name from touch tone sequence. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. * In this case, it should start with "AA". * * Outputs: m_callsign * * m_ssid - Cleared to remove the default of 12. * * Returns: 0 for success or one of the TT_ERROR_... codes. * * Description: Data format * * AAtt...tt - Symbol name, two key method, up to 9 characters. * *----------------------------------------------------------------*/ static int parse_object_name (char *e) { int len; //int c_length; //char tttemp[40]; //char stemp[30]; assert (e[0] == 'A'); assert (e[1] == 'A'); len = strlen(e); /* * Object name in two key format. */ if (len >= 2 + 1 && len <= 30) { if (tt_two_key_to_text (e+2, 0, m_callsign) == 0) { m_callsign[9] = '\0'; /* truncate to 9 */ m_ssid = 0; /* No ssid for object name */ return (0); } } text_color_set(DW_COLOR_ERROR); dw_printf ("Touch tone object name not valid: \"%s\"\n", e); return (TT_ERROR_INVALID_OBJNAME); } /* end parse_oject_name */ /*------------------------------------------------------------------ * * Name: parse_symbol * * Purpose: Extract symbol from touch tone sequence. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. * In this case, it should start with "AB". * * Outputs: m_symtab_or_overlay * * m_symbol_code * * Returns: 0 for success or one of the TT_ERROR_... codes. * * Description: Data format * * AB1nn - Symbol from primary symbol table. * Two digits nn are the same as in the GPSCnn * generic address used as a destination. * * AB2nn - Symbol from alternate symbol table. * Two digits nn are the same as in the GPSEnn * generic address used as a destination. * * AB0nnvv - Symbol from alternate symbol table. * Two digits nn are the same as in the GPSEnn * generic address used as a destination. * vv is an overlay digit or letter in two key method. * *----------------------------------------------------------------*/ static int parse_symbol (char *e) { int len; char nstr[3]; int nn; char stemp[10]; assert (e[0] == 'A'); assert (e[1] == 'B'); len = strlen(e); if (len >= 4 && len <= 10) { nstr[0] = e[3]; nstr[1] = e[4]; nstr[2] = '\0'; nn = atoi (nstr); if (nn < 1) { nn = 1; } else if (nn > 94) { nn = 94; } switch (e[2]) { case '1': m_symtab_or_overlay = '/'; m_symbol_code = 32 + nn; return (0); break; case '2': m_symtab_or_overlay = '\\'; m_symbol_code = 32 + nn; return (0); break; case '0': if (len >= 6) { if (tt_two_key_to_text (e+5, 0, stemp) == 0) { m_symbol_code = 32 + nn; m_symtab_or_overlay = stemp[0]; return (0); } } break; } } text_color_set(DW_COLOR_ERROR); dw_printf ("Touch tone symbol not valid: \"%s\"\n", e); return (TT_ERROR_INVALID_SYMBOL); } /* end parse_oject_name */ /*------------------------------------------------------------------ * * Name: parse_aprstt3_call * * Purpose: Extract QIKcom-2 / APRStt 3 ten digit call or five digit suffix. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. * In this case, it should start with "AC". * * Outputs: m_callsign * * Returns: 0 for success or one of the TT_ERROR_... codes. * * Description: We recognize 3 different formats: * * ACxxxxxxxxxx - 10 digit full callsign. * * ACxxxxx - 5 digit suffix. If we can find a corresponding full * callsign, that will be substituted. * Error condition is returned if we can't find one. * *----------------------------------------------------------------*/ static int parse_aprstt3_call (char *e) { assert (e[0] == 'A'); assert (e[1] == 'C'); if (strlen(e) == 2+10) { char call[12]; if (tt_call10_to_text(e+2,1,call) == 0) { strlcpy(m_callsign, call, sizeof(m_callsign)); } else { return (TT_ERROR_INVALID_CALL); /* Could not convert to text */ } } else if (strlen(e) == 2+5) { char suffix[8]; if (tt_call5_suffix_to_text(e+2,1,suffix) == 0) { #if TT_MAIN /* For unit test, use suffix rather than trying lookup. */ strlcpy (m_callsign, suffix, sizeof(m_callsign)); #else char call[12]; /* In normal operation, try to find full callsign for the suffix received. */ if (tt_3char_suffix_search (suffix, call) >= 0) { text_color_set(DW_COLOR_INFO); dw_printf ("Suffix \"%s\" was converted to full callsign \"%s\"\n", suffix, call); strlcpy(m_callsign, call, sizeof(m_callsign)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't find full callsign for suffix \"%s\"\n", suffix); return (TT_ERROR_SUFFIX_NO_CALL); /* Don't know this user. */ } #endif } else { return (TT_ERROR_INVALID_CALL); /* Could not convert to text */ } } else { return (TT_ERROR_INVALID_CALL); /* Invalid length, not 2+ (10 ir 5) */ } return (0); } /* end parse_aprstt3_call */ /*------------------------------------------------------------------ * * Name: parse_location * * Purpose: Extract location from touch tone sequence. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. * In this case, it should start with "B". * * Outputs: m_latitude * m_longitude * * m_dao It should previously be "!T !" to mean unknown or none. * We generally take the first two tones of the field. * For example, "!TB5!" for the standard bearing & range. * The point type is an exception where we use "!Tn !" for * one of ten positions or "!Tnn" for one of a hundred. * If this ever changes, be sure to update corresponding * section in process_comment() in decode_aprs.c * * m_ambiguity * * Returns: 0 for success or one of the TT_ERROR_... codes. * * Description: There are many different formats recognizable * by total number of digits and sometimes the first digit. * * We handle most of them in a general way, processing * them in 5 groups: * * * points * * vector * * grid * * utm * * usng / mgrs * * Position ambiguity is also handled here. * Latitude, Longitude, and DAO should not be touched in this case. * We only record a position ambiguity value. * *----------------------------------------------------------------*/ /* Average radius of earth in meters. */ #define R 6371000. static int parse_location (char *e) { int ipat; char xstr[VALSTRSIZE], ystr[VALSTRSIZE], zstr[VALSTRSIZE], bstr[VALSTRSIZE], dstr[VALSTRSIZE]; double x, y, dist, bearing; double lat0, lon0; double lat9, lon9; long lerr; double easting, northing; char mh[20]; char stemp[32]; assert (*e == 'B'); ipat = find_ttloc_match (e, xstr, ystr, zstr, bstr, dstr, VALSTRSIZE); if (ipat >= 0) { //dw_printf ("ipat=%d, x=%s, y=%s, b=%s, d=%s\n", ipat, xstr, ystr, bstr, dstr); switch (tt_config.ttloc_ptr[ipat].type) { case TTLOC_POINT: m_latitude = tt_config.ttloc_ptr[ipat].point.lat; m_longitude = tt_config.ttloc_ptr[ipat].point.lon; /* Is it one of ten or a hundred positions? */ /* It's not hardwired to always be B0n or B9nn. */ /* This is a pretty good approximation. */ m_dao[2] = e[0]; m_dao[3] = e[1]; if (strlen(e) == 3) { /* probably B0n --> !Tn ! */ m_dao[2] = e[2]; m_dao[3] = ' '; } if (strlen(e) == 4) { /* probably B9nn --> !Tnn! */ m_dao[2] = e[2]; m_dao[3] = e[3]; } break; case TTLOC_VECTOR: if (strlen(bstr) != 3) { text_color_set(DW_COLOR_ERROR); dw_printf ("Bearing \"%s\" should be 3 digits.\n", bstr); // return error code? } if (strlen(dstr) < 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Distance \"%s\" should 1 or more digits.\n", dstr); // return error code? } lat0 = D2R(tt_config.ttloc_ptr[ipat].vector.lat); lon0 = D2R(tt_config.ttloc_ptr[ipat].vector.lon); dist = atof(dstr) * tt_config.ttloc_ptr[ipat].vector.scale; bearing = D2R(atof(bstr)); /* Equations and caluculators found here: */ /* http://movable-type.co.uk/scripts/latlong.html */ /* This should probably be a function in latlong.c in case we have another use for it someday. */ m_latitude = R2D(asin(sin(lat0) * cos(dist/R) + cos(lat0) * sin(dist/R) * cos(bearing))); m_longitude = R2D(lon0 + atan2(sin(bearing) * sin(dist/R) * cos(lat0), cos(dist/R) - sin(lat0) * sin(D2R(m_latitude)))); m_dao[2] = e[0]; m_dao[3] = e[1]; break; case TTLOC_GRID: if (strlen(xstr) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Missing X coordinate.\n"); strlcpy (xstr, "0", sizeof(xstr)); } if (strlen(ystr) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Missing Y coordinate.\n"); strlcpy (ystr, "0", sizeof(ystr)); } lat0 = tt_config.ttloc_ptr[ipat].grid.lat0; lat9 = tt_config.ttloc_ptr[ipat].grid.lat9; y = atof(ystr); m_latitude = lat0 + y * (lat9-lat0) / (pow(10., strlen(ystr)) - 1.); lon0 = tt_config.ttloc_ptr[ipat].grid.lon0; lon9 = tt_config.ttloc_ptr[ipat].grid.lon9; x = atof(xstr); m_longitude = lon0 + x * (lon9-lon0) / (pow(10., strlen(xstr)) - 1.); m_dao[2] = e[0]; m_dao[3] = e[1]; break; case TTLOC_UTM: if (strlen(xstr) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Missing X coordinate.\n"); /* Avoid divide by zero later. Put in middle of range. */ strlcpy (xstr, "5", sizeof(xstr)); } if (strlen(ystr) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Missing Y coordinate.\n"); /* Avoid divide by zero later. Put in middle of range. */ strlcpy (ystr, "5", sizeof(ystr)); } x = atof(xstr); easting = x * tt_config.ttloc_ptr[ipat].utm.scale + tt_config.ttloc_ptr[ipat].utm.x_offset; y = atof(ystr); northing = y * tt_config.ttloc_ptr[ipat].utm.scale + tt_config.ttloc_ptr[ipat].utm.y_offset; if (isalpha(tt_config.ttloc_ptr[ipat].utm.latband)) { snprintf (m_loc_text, sizeof(m_loc_text), "%d%c %.0f %.0f", (int)(tt_config.ttloc_ptr[ipat].utm.lzone), tt_config.ttloc_ptr[ipat].utm.latband, easting, northing); } else if (tt_config.ttloc_ptr[ipat].utm.latband == '-') { snprintf (m_loc_text, sizeof(m_loc_text), "%d %.0f %.0f", (int)(- tt_config.ttloc_ptr[ipat].utm.lzone), easting, northing); } else { snprintf (m_loc_text, sizeof(m_loc_text), "%d %.0f %.0f", (int)(tt_config.ttloc_ptr[ipat].utm.lzone), easting, northing); } lerr = Convert_UTM_To_Geodetic(tt_config.ttloc_ptr[ipat].utm.lzone, tt_config.ttloc_ptr[ipat].utm.hemi, easting, northing, &lat0, &lon0); if (lerr == 0) { m_latitude = R2D(lat0); m_longitude = R2D(lon0); //dw_printf ("DEBUG: from UTM, latitude = %.6f, longitude = %.6f\n", m_latitude, m_longitude); } else { char message[300]; text_color_set(DW_COLOR_ERROR); utm_error_string (lerr, message); dw_printf ("Conversion from UTM failed:\n%s\n\n", message); } m_dao[2] = e[0]; m_dao[3] = e[1]; break; case TTLOC_MGRS: case TTLOC_USNG: if (strlen(xstr) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("MGRS/USNG: Missing X (easting) coordinate.\n"); /* Should not be possible to get here. Fake it and carry on. */ strlcpy (xstr, "5", sizeof(xstr)); } if (strlen(ystr) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("MGRS/USNG: Missing Y (northing) coordinate.\n"); /* Should not be possible to get here. Fake it and carry on. */ strlcpy (ystr, "5", sizeof(ystr)); } char loc[40]; strlcpy (loc, tt_config.ttloc_ptr[ipat].mgrs.zone, sizeof(loc)); strlcat (loc, xstr, sizeof(loc)); strlcat (loc, ystr, sizeof(loc)); //text_color_set(DW_COLOR_DEBUG); //dw_printf ("MGRS/USNG location debug: %s\n", loc); strlcpy (m_loc_text, loc, sizeof(m_loc_text)); if (tt_config.ttloc_ptr[ipat].type == TTLOC_MGRS) lerr = Convert_MGRS_To_Geodetic(loc, &lat0, &lon0); else lerr = Convert_USNG_To_Geodetic(loc, &lat0, &lon0); if (lerr == 0) { m_latitude = R2D(lat0); m_longitude = R2D(lon0); //dw_printf ("DEBUG: from MGRS/USNG, latitude = %.6f, longitude = %.6f\n", m_latitude, m_longitude); } else { char message[300]; text_color_set(DW_COLOR_ERROR); mgrs_error_string (lerr, message); dw_printf ("Conversion from MGRS/USNG failed:\n%s\n\n", message); } m_dao[2] = e[0]; m_dao[3] = e[1]; break; case TTLOC_MHEAD: /* Combine prefix from configuration and digits from user. */ strlcpy (stemp, tt_config.ttloc_ptr[ipat].mhead.prefix, sizeof(stemp)); strlcat (stemp, xstr, sizeof(stemp)); if (strlen(stemp) != 4 && strlen(stemp) != 6 && strlen(stemp) != 10 && strlen(stemp) != 12) { text_color_set(DW_COLOR_ERROR); dw_printf ("Expected total of 4, 6, 10, or 12 digits for the Maidenhead Locator \"%s\" + \"%s\"\n", tt_config.ttloc_ptr[ipat].mhead.prefix, xstr); return (TT_ERROR_INVALID_MHEAD); } //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Case MHEAD: Convert to text \"%s\".\n", stemp); if (tt_mhead_to_text (stemp, 0, mh, sizeof(mh)) == 0) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Case MHEAD: Resulting text \"%s\".\n", mh); strlcpy (m_loc_text, mh, sizeof(m_loc_text)); ll_from_grid_square (mh, &m_latitude, &m_longitude); } m_dao[2] = e[0]; m_dao[3] = e[1]; break; case TTLOC_SATSQ: if (strlen(xstr) != 4) { text_color_set(DW_COLOR_ERROR); dw_printf ("Expected 4 digits for the Satellite Square.\n"); return (TT_ERROR_INVALID_SATSQ); } /* Convert 4 digits to usual AA99 form, then to location. */ if (tt_satsq_to_text (xstr, 0, mh) == 0) { strlcpy (m_loc_text, mh, sizeof(m_loc_text)); ll_from_grid_square (mh, &m_latitude, &m_longitude); } m_dao[2] = e[0]; m_dao[3] = e[1]; break; case TTLOC_AMBIG: if (strlen(xstr) != 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Expected 1 digits for the position ambiguity.\n"); return (TT_ERROR_INVALID_LOC); } m_ambiguity = atoi(xstr); break; default: assert (0); } return (0); } /* Does not match any location specification. */ text_color_set(DW_COLOR_ERROR); dw_printf ("Received location \"%s\" does not match any definitions.\n", e); /* Send reject sound. */ return (TT_ERROR_INVALID_LOC); } /* end parse_location */ /*------------------------------------------------------------------ * * Name: find_ttloc_match * * Purpose: Try to match the received position report to a pattern * defined in the configuration file. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. * In this case, it should start with "B". * * valstrsize - size of the outputs so we can check for buffer overflow. * * Outputs: xstr - All digits matching x positions in configuration. * ystr - y * zstr - z * bstr - b * dstr - d * * Returns: >= 0 for index into table if found. * -1 if not found. * * Description: * *----------------------------------------------------------------*/ static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr, size_t valstrsize) { int ipat; /* Index into patterns from configuration file */ int len; /* Length of pattern we are trying to match. */ int match; char mc; int k; // debug dw_printf ("find_ttloc_match: e=%s\n", e); for (ipat=0; ipat%s:t%s", src, dest, msg); pp = ax25_from_text (raw_tt_msg, 1); /* * Process like a normal received frame. * NOTE: This goes directly to application rather than * thru the multi modem duplicate processing. * * Should we use a different type so it can be easily * distinguished later? * * We try to capture an overall audio level here. * Mark and space do not apply in this case. * This currently doesn't get displayed but we might want it someday. */ if (pp != NULL) { alevel = demod_get_audio_level (chan, 0); alevel.mark = -2; alevel.space = -2; dlq_rec_frame (chan, -1, 0, pp, alevel, RETRY_NONE, "tt"); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not convert \"%s\" into APRS packet.\n", raw_tt_msg); } #endif } #endif /*------------------------------------------------------------------ * * Name: dw_run_cmd * * Purpose: Run a command and capture the output. * * Inputs: cmd - The command. * * oneline - 0 = Keep original line separators. Caller * must deal with operating system differences. * 1 = Change CR, LF, TAB to space so result * is one line of text. * 2 = Also remove any trailing whitespace. * * resultsiz - Amount of space available for result. * * Outputs: result - Output captured from running command. * * Returns: -1 for any sort of error. * >0 for number of characters returned (= strlen(result)) * * Description: This is currently used for running a user-specified * script to generate a custom speech response. * * Future: There are potential other uses so it should probably * be relocated to a file of other misc. utilities. * *----------------------------------------------------------------*/ int dw_run_cmd (char *cmd, int oneline, char *result, size_t resultsiz) { FILE *fp; strlcpy (result, "", resultsiz); fp = popen (cmd, "r"); if (fp != NULL) { int remaining = (int)resultsiz; char *pr = result; int err; while (remaining > 2 && fgets(pr, remaining, fp) != NULL) { pr = result + strlen(result); remaining = (int)resultsiz - strlen(result); } if ((err = pclose(fp)) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: Unable to run \"%s\"\n", cmd); // On Windows, non-existent file produces "Operation not permitted" // Maybe we should put in a test for whether file exists. dw_printf ("%s\n", strerror(err)); return (-1); } // take out any newline characters. if (oneline) { for (pr = result; *pr != '\0'; pr++) { if (*pr == '\r' || *pr == '\n' || *pr == '\t') { *pr = ' '; } } if (oneline > 1) { pr = result + strlen(result) - 1; while (pr >= result && *pr == ' ') { *pr = '\0'; pr--; } } } //text_color_set(DW_COLOR_DEBUG); //dw_printf ("%s returns \"%s\"\n", cmd, result); return (strlen(result)); } else { // explain_popen() would be nice but doesn't seem to be commonly available. // We get here only if fork or pipe fails. // The command not existing must be caught above. text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: Unable to run \"%s\"\n", cmd); dw_printf ("%s\n", strerror(errno)); return (-1); } } /* end dw_run_cmd */ /*------------------------------------------------------------------ * * Name: main * * Purpose: Unit test for this file. * * Description: Run unit test like this: * * rm a.exe ; gcc tt_text.c -DTT_MAIN -Igeotranz aprs_tt.c latlong.o textcolor.o geotranz.a misc.a ; ./a.exe * or * make ttest * *----------------------------------------------------------------*/ #if TT_MAIN /* * Regression test for the parsing. * It does not maintain any history so abbreviation will not invoke previous full call. */ /* Some examples are derived from http://www.aprs.org/aprstt/aprstt-coding24.txt */ static const struct { char *toneseq; /* Tone sequence in. */ char *callsign; /* Expected results... */ char *ssid; char *symbol; char *freq; char *comment; char *lat; char *lon; char *dao; } testcases[] = { /* Callsigns & abbreviations, traditional */ { "A9A2B42A7A7C71#", "WB4APR", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* WB4APR/7 */ { "A27773#", "277", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* abbreviated form */ /* Intentionally wrong - Has 6 for checksum when it should be 3. */ { "A27776#", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Expect error message. */ /* Example in spec is wrong. checksum should be 5 in this case. */ { "A2A7A7C71#", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Spelled suffix, overlay, checksum */ { "A2A7A7C75#", "APR", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Spelled suffix, overlay, checksum */ { "A27773#", "277", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Suffix digits, overlay, checksum */ { "A9A2B26C7D9D71#", "WB2OSZ", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* WB2OSZ/7 numeric overlay */ { "A67979#", "679", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* abbreviated form */ { "A9A2B26C7D9D5A9#", "WB2OSZ", "12", "JA", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* WB2OSZ/J letter overlay */ { "A6795A7#", "679", "12", "JA", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* abbreviated form */ { "A277#", "277", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Tactical call "277" no overlay and no checksum */ /* QIKcom-2 style 10 digit call & 5 digit suffix */ { "AC9242771558#", "WB4APR", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, { "AC27722#", "APR", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Locations */ { "B01*A67979#", "679", "12", "7A", "", "", "12.2500", "56.2500", "!T1 !" }, { "B988*A67979#", "679", "12", "7A", "", "", "12.5000", "56.5000", "!T88!" }, { "B51000125*A67979#", "679", "12", "7A", "", "", "52.7907", "0.8309", "!TB5!" }, /* expect about 52.79 +0.83 */ { "B5206070*A67979#", "679", "12", "7A", "", "", "37.9137", "-81.1366", "!TB5!" }, /* Try to get from Hilltop Tower to Archery & Target Range. */ /* Latitude comes out ok, 37.9137 -> 55.82 min. */ /* Longitude -81.1254 -> 8.20 min */ { "B21234*A67979#", "679", "12", "7A", "", "", "12.3400", "56.1200", "!TB2!" }, { "B533686*A67979#", "679", "12", "7A", "", "", "37.9222", "81.1143", "!TB5!" }, // TODO: should test other coordinate systems. /* Comments */ { "C1", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, { "C2", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, { "C146520", "", "12", "\\A", "146.520MHz", "", "-999999.0000", "-999999.0000", "!T !" }, { "C7788444222550227776669660333666990122223333", "", "12", "\\A", "", "QUICK BROWN FOX 123", "-999999.0000", "-999999.0000", "!T !" }, /* Macros */ { "88345", "BIKE 345", "0", "/b", "", "", "12.5000", "56.5000", "!T88!" }, /* 10 digit representation for callsign & satellite grid. WB4APR near 39.5, -77 */ { "AC9242771558*BA1819", "WB4APR", "12", "\\A", "", "", "39.5000", "-77.0000", "!TBA!" }, { "18199242771558", "WB4APR", "12", "\\A", "", "", "39.5000", "-77.0000", "!TBA!" }, }; static int test_num; static int error_count; static void check_result (void) { char stemp[32]; text_color_set(DW_COLOR_DEBUG); dw_printf ("callsign=\"%s\", ssid=%d, symbol=\"%c%c\", freq=\"%s\", comment=\"%s\", lat=%.4f, lon=%.4f, dao=\"%s\"\n", m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_freq, m_comment, m_latitude, m_longitude, m_dao); if (strcmp(m_callsign, testcases[test_num].callsign) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: Expected \"%s\" for callsign.\n", testcases[test_num].callsign); error_count++; } snprintf (stemp, sizeof(stemp), "%d", m_ssid); if (strcmp(stemp, testcases[test_num].ssid) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: Expected \"%s\" for SSID.\n", testcases[test_num].ssid); error_count++; } stemp[0] = m_symtab_or_overlay; stemp[1] = m_symbol_code; stemp[2] = '\0'; if (strcmp(stemp, testcases[test_num].symbol) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: Expected \"%s\" for Symbol.\n", testcases[test_num].symbol); error_count++; } if (strcmp(m_freq, testcases[test_num].freq) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: Expected \"%s\" for Freq.\n", testcases[test_num].freq); error_count++; } if (strcmp(m_comment, testcases[test_num].comment) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: Expected \"%s\" for Comment.\n", testcases[test_num].comment); error_count++; } snprintf (stemp, sizeof(stemp), "%.4f", m_latitude); if (strcmp(stemp, testcases[test_num].lat) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: Expected \"%s\" for Latitude.\n", testcases[test_num].lat); error_count++; } snprintf (stemp, sizeof(stemp), "%.4f", m_longitude); if (strcmp(stemp, testcases[test_num].lon) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: Expected \"%s\" for Longitude.\n", testcases[test_num].lon); error_count++; } if (strcmp(m_dao, testcases[test_num].dao) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: Expected \"%s\" for DAO.\n", testcases[test_num].dao); error_count++; } } int main (int argc, char *argv[]) { aprs_tt_init (NULL); error_count = 0; for (test_num = 0; test_num < sizeof(testcases) / sizeof(testcases[0]); test_num++) { text_color_set(DW_COLOR_INFO); dw_printf ("\nTest case %d: %s\n", test_num, testcases[test_num].toneseq); aprs_tt_sequence (0, testcases[test_num].toneseq); } if (error_count != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n\nTEST FAILED, Total of %d errors.\n", error_count); return (EXIT_FAILURE); } text_color_set(DW_COLOR_REC); dw_printf ("\n\nAll tests passed.\n"); return (EXIT_SUCCESS); } /* end main */ #endif /* end aprs_tt.c */ direwolf-1.5+dfsg/aprs_tt.h000066400000000000000000000115731347750676600157720ustar00rootroot00000000000000 /* aprs_tt.h */ #ifndef APRS_TT_H #define APRS_TT_H 1 /* * For holding location format specifications from config file. * Same thing is also useful for macro definitions. * We have exactly the same situation of looking for a pattern * match and extracting fixed size groups of digits. */ struct ttloc_s { enum { TTLOC_POINT, TTLOC_VECTOR, TTLOC_GRID, TTLOC_UTM, TTLOC_MGRS, TTLOC_USNG, TTLOC_MACRO, TTLOC_MHEAD, TTLOC_SATSQ, TTLOC_AMBIG } type; char pattern[20]; /* e.g. B998, B5bbbdddd, B2xxyy, Byyyxxx, BAxxxx */ /* For macros, it should be all fixed digits, */ /* and the letters x, y, z. e.g. 911, xxyyyz */ union { struct { double lat; /* Specific locations. */ double lon; } point; struct { double lat; /* For bearing/direction. */ double lon; double scale; /* conversion to meters */ } vector; struct { double lat0; /* yyy all zeros. */ double lon0; /* xxx */ double lat9; /* yyy all nines. */ double lon9; /* xxx */ } grid; struct { double scale; double x_offset; double y_offset; long lzone; /* UTM zone, should be 1-60 */ char latband; /* Latitude band if specified, otherwise space or - */ char hemi; /* UTM Hemisphere, should be 'N' or 'S'. */ } utm; struct { char zone[8]; /* Zone and square for USNG/MGRS */ } mgrs; struct { char prefix[24]; /* should be 10, 6, or 4 digits to be */ /* prepended to the received sequence. */ } mhead; struct { char *definition; } macro; }; }; /* Error codes for sending responses to user. */ #define TT_ERROR_OK 0 /* Success. */ #define TT_ERROR_D_MSG 1 /* D was first char of field. Not implemented yet. */ #define TT_ERROR_INTERNAL 2 /* Internal error. Shouldn't be here. */ #define TT_ERROR_MACRO_NOMATCH 3 /* No definition for digit sequence. */ #define TT_ERROR_BAD_CHECKSUM 4 /* Bad checksum on call. */ #define TT_ERROR_INVALID_CALL 5 /* Invalid callsign. */ #define TT_ERROR_INVALID_OBJNAME 6 /* Invalid object name. */ #define TT_ERROR_INVALID_SYMBOL 7 /* Invalid symbol specification. */ #define TT_ERROR_INVALID_LOC 8 /* Invalid location. */ #define TT_ERROR_NO_CALL 9 /* No call or object name included. */ #define TT_ERROR_INVALID_MHEAD 10 /* Invalid Maidenhead Locator. */ #define TT_ERROR_INVALID_SATSQ 11 /* Satellite square must be 4 digits. */ #define TT_ERROR_SUFFIX_NO_CALL 12 /* No known callsign for suffix. */ #define TT_ERROR_MAXP1 13 /* Number of items above. i.e. Last number plus 1. */ #if CONFIG_C /* Is this being included from config.c? */ /* Must keep in sync with above !!! */ static const char *tt_msg_id[TT_ERROR_MAXP1] = { "OK", "D_MSG", "INTERNAL", "MACRO_NOMATCH", "BAD_CHECKSUM", "INVALID_CALL", "INVALID_OBJNAME", "INVALID_SYMBOL", "INVALID_LOC", "NO_CALL", "INVALID_MHEAD", "INVALID_SATSQ", "SUFFIX_NO_CALL" }; #endif /* * Configuration options for APRStt. */ #define TT_MAX_XMITS 10 #define TT_MTEXT_LEN 64 struct tt_config_s { int gateway_enabled; /* Send DTMF sequences to APRStt gateway. */ int obj_recv_chan; /* Channel to listen for tones. */ int obj_xmit_chan; /* Channel to transmit object report. */ /* -1 for none. This could happpen if we */ /* are only sending to application */ /* and/or IGate. */ int obj_send_to_app; /* send to attached application(s). */ int obj_send_to_ig; /* send to IGate. */ char obj_xmit_via[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN+1)]; /* e.g. empty or "WIDE2-1,WIDE1-1" */ int retain_time; /* Seconds to keep information about a user. */ int num_xmits; /* Number of times to transmit object report. */ int xmit_delay[TT_MAX_XMITS]; /* Delay between them. */ /* e.g. 3 seconds before first transmission then */ /* delays of 16, 32, seconds etc. in between repeats. */ struct ttloc_s *ttloc_ptr; /* Pointer to variable length array of above. */ int ttloc_size; /* Number of elements allocated. */ int ttloc_len; /* Number of elements actually used. */ double corral_lat; /* The "corral" for unknown locations. */ double corral_lon; double corral_offset; int corral_ambiguity; char status[10][TT_MTEXT_LEN]; /* Up to 9 status messages. e.g. "/enroute" */ /* Position 0 means none and can't be changed. */ struct { char method[AX25_MAX_ADDR_LEN]; /* SPEECH or MORSE[-n] */ char mtext[TT_MTEXT_LEN]; /* Message text. */ } response[TT_ERROR_MAXP1]; char ttcmd[80]; /* Command to generate custom audible response. */ }; void aprs_tt_init (struct tt_config_s *p_config); void aprs_tt_button (int chan, char button); #define APRSTT_LOC_DESC_LEN 32 /* Need at least 26 */ #define APRSTT_DEFAULT_SYMTAB '\\' #define APRSTT_DEFAULT_SYMBOL 'A' void aprs_tt_dao_to_desc (char *dao, char *str); void aprs_tt_sequence (int chan, char *msg); int dw_run_cmd (char *cmd, int oneline, char *result, size_t resultsiz); #endif /* end aprs_tt.h */direwolf-1.5+dfsg/atest.c000066400000000000000000000614541347750676600154340ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------- * * Name: atest.c * * Purpose: Test fixture for the AFSK demodulator. * * Inputs: Takes audio from a .WAV file insted of the audio device. * * Description: This can be used to test the AFSK demodulator under * controlled and reproducable conditions for tweaking. * * For example * * (1) Download WA8LMF's TNC Test CD image file from * http://wa8lmf.net/TNCtest/index.htm * * (2) Burn a physical CD. * * (3) "Rip" the desired tracks with Windows Media Player. * Select .WAV file format. * * "Track 2" is used for most tests because that is more * realistic for most people using the speaker output. * * * Without ONE_CHAN defined: * * Notice that the number of packets decoded, as reported by * this test program, will be twice the number expected because * we are decoding the left and right audio channels separately. * * * With ONE_CHAN defined: * * Only process one channel. * *--------------------------------------------------------------------*/ // #define X 1 #include "direwolf.h" #include #include #include #include #include #include #include #define ATEST_C 1 #include "audio.h" #include "demod.h" #include "multi_modem.h" #include "textcolor.h" #include "ax25_pad.h" #include "hdlc_rec2.h" #include "dlq.h" #include "ptt.h" #include "dtime_now.h" #if 0 /* Typical but not flexible enough. */ struct wav_header { /* .WAV file header. */ char riff[4]; /* "RIFF" */ int filesize; /* file length - 8 */ char wave[4]; /* "WAVE" */ char fmt[4]; /* "fmt " */ int fmtsize; /* 16. */ short wformattag; /* 1 for PCM. */ short nchannels; /* 1 for mono, 2 for stereo. */ int nsamplespersec; /* sampling freq, Hz. */ int navgbytespersec; /* = nblockalign*nsamplespersec. */ short nblockalign; /* = wbitspersample/8 * nchannels. */ short wbitspersample; /* 16 or 8. */ char data[4]; /* "data" */ int datasize; /* number of bytes following. */ } ; #endif /* 8 bit samples are unsigned bytes */ /* in range of 0 .. 255. */ /* 16 bit samples are signed short */ /* in range of -32768 .. +32767. */ static struct { char riff[4]; /* "RIFF" */ int filesize; /* file length - 8 */ char wave[4]; /* "WAVE" */ } header; static struct { char id[4]; /* "LIST" or "fmt " */ int datasize; } chunk; static struct { short wformattag; /* 1 for PCM. */ short nchannels; /* 1 for mono, 2 for stereo. */ int nsamplespersec; /* sampling freq, Hz. */ int navgbytespersec; /* = nblockalign*nsamplespersec. */ short nblockalign; /* = wbitspersample/8 * nchannels. */ short wbitspersample; /* 16 or 8. */ char extras[4]; } format; static struct { char data[4]; /* "data" */ int datasize; } wav_data; static FILE *fp; static int e_o_f; static int packets_decoded = 0; static int decimate = 0; /* Reduce that sampling rate if set. */ /* 1 = normal, 2 = half, etc. */ static struct audio_s my_audio_config; static int error_if_less_than = -1; /* Exit with error status if this minimum not reached. */ /* Can be used to check that performance has not decreased. */ static int error_if_greater_than = -1; /* Exit with error status if this maximum exceeded. */ /* Can be used to check that duplicate removal is not broken. */ //#define EXPERIMENT_G 1 //#define EXPERIMENT_H 1 #if defined(EXPERIMENT_G) || defined(EXPERIMENT_H) static int count[MAX_SUBCHANS]; #if EXPERIMENT_H extern float space_gain[MAX_SUBCHANS]; #endif #endif static void usage (void); static int decode_only = 0; /* Set to 0 or 1 to decode only one channel. 2 for both. */ static int sample_number = -1; /* Sample number from the file. */ /* Incremented only for channel 0. */ /* Use to print timestamp, relative to beginning */ /* of file, when frame was decoded. */ int main (int argc, char *argv[]) { int err; int c; int channel; double start_time; // Time when we started so we can measure elapsed time. double duration; // Length of the audio file in seconds. double elapsed; // Time it took us to process it. #if defined(EXPERIMENT_G) || defined(EXPERIMENT_H) int j; for (j=0; j MAX_BAUD) { text_color_set(DW_COLOR_ERROR); dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); exit (EXIT_FAILURE); } /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ /* that need to be kept in sync. Maybe it could be a common function someday. */ if (my_audio_config.achan[0].baud == 100) { my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].mark_freq = 1615; my_audio_config.achan[0].space_freq = 1785; strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); } else if (my_audio_config.achan[0].baud < 600) { my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].mark_freq = 1600; my_audio_config.achan[0].space_freq = 1800; strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); } else if (my_audio_config.achan[0].baud < 1800) { my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ; my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ; // Should default to E+ or something similar later. } else if (my_audio_config.achan[0].baud < 3600) { my_audio_config.achan[0].modem_type = MODEM_QPSK; my_audio_config.achan[0].mark_freq = 0; my_audio_config.achan[0].space_freq = 0; strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); dw_printf ("Using V.26 QPSK rather than AFSK.\n"); } else if (my_audio_config.achan[0].baud < 7200) { my_audio_config.achan[0].modem_type = MODEM_8PSK; my_audio_config.achan[0].mark_freq = 0; my_audio_config.achan[0].space_freq = 0; strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); dw_printf ("Using V.27 8PSK rather than AFSK.\n"); } else { my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE; my_audio_config.achan[0].mark_freq = 0; my_audio_config.achan[0].space_freq = 0; strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); } break; case 'P': /* -P for modem profile. */ dw_printf ("Demodulator profile set to \"%s\"\n", optarg); strlcpy (my_audio_config.achan[0].profiles, optarg, sizeof(my_audio_config.achan[0].profiles)); break; case 'D': /* -D reduce sampling rate for lower CPU usage. */ decimate = atoi(optarg); dw_printf ("Divide audio sample rate by %d\n", decimate); if (decimate < 1 || decimate > 8) { text_color_set(DW_COLOR_ERROR); dw_printf ("Unreasonable value for -D.\n"); exit (EXIT_FAILURE); } dw_printf ("Divide audio sample rate by %d\n", decimate); my_audio_config.achan[0].decimate = decimate; break; case 'F': /* -D set "fix bits" level. */ my_audio_config.achan[0].fix_bits = atoi(optarg); if (my_audio_config.achan[0].fix_bits < RETRY_NONE || my_audio_config.achan[0].fix_bits >= RETRY_MAX) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid Fix Bits level.\n"); exit (EXIT_FAILURE); } break; case 'L': /* -L error if less than this number decoded. */ error_if_less_than = atoi(optarg); break; case 'G': /* -G error if greater than this number decoded. */ error_if_greater_than = atoi(optarg); break; case '0': /* channel 0, left from stereo */ decode_only = 0; break; case '1': /* channel 1, right from stereo */ decode_only = 1; break; case '2': /* decode both from stereo */ decode_only = 2; break; case '?': /* Unknown option message was already printed. */ usage (); break; default: /* Should not be here. */ text_color_set(DW_COLOR_ERROR); dw_printf("?? getopt returned character code 0%o ??\n", c); usage (); } } memcpy (&my_audio_config.achan[1], &my_audio_config.achan[0], sizeof(my_audio_config.achan[0])); if (optind >= argc) { text_color_set(DW_COLOR_ERROR); dw_printf ("Specify .WAV file name on command line.\n"); usage (); } fp = fopen(argv[optind], "rb"); if (fp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't open file for read: %s\n", argv[optind]); //perror ("more info?"); exit (EXIT_FAILURE); } start_time = dtime_now(); /* * Read the file header. * Doesn't handle all possible cases but good enough for our purposes. */ err= fread (&header, (size_t)12, (size_t)1, fp); (void)(err); if (strncmp(header.riff, "RIFF", 4) != 0 || strncmp(header.wave, "WAVE", 4) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("This is not a .WAV format file.\n"); exit (EXIT_FAILURE); } err = fread (&chunk, (size_t)8, (size_t)1, fp); if (strncmp(chunk.id, "LIST", 4) == 0) { err = fseek (fp, (long)chunk.datasize, SEEK_CUR); err = fread (&chunk, (size_t)8, (size_t)1, fp); } if (strncmp(chunk.id, "fmt ", 4) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("WAV file error: Found \"%4.4s\" where \"fmt \" was expected.\n", chunk.id); exit(EXIT_FAILURE); } if (chunk.datasize != 16 && chunk.datasize != 18) { text_color_set(DW_COLOR_ERROR); dw_printf ("WAV file error: Need fmt chunk datasize of 16 or 18. Found %d.\n", chunk.datasize); exit(EXIT_FAILURE); } err = fread (&format, (size_t)chunk.datasize, (size_t)1, fp); err = fread (&wav_data, (size_t)8, (size_t)1, fp); if (strncmp(wav_data.data, "data", 4) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("WAV file error: Found \"%4.4s\" where \"data\" was expected.\n", wav_data.data); exit(EXIT_FAILURE); } if (format.wformattag != 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Sorry, I only understand audio format 1 (PCM). This file has %d.\n", format.wformattag); exit (EXIT_FAILURE); } if (format.nchannels != 1 && format.nchannels != 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("Sorry, I only understand 1 or 2 channels. This file has %d.\n", format.nchannels); exit (EXIT_FAILURE); } if (format.wbitspersample != 8 && format.wbitspersample != 16) { text_color_set(DW_COLOR_ERROR); dw_printf ("Sorry, I only understand 8 or 16 bits per sample. This file has %d.\n", format.wbitspersample); exit (EXIT_FAILURE); } my_audio_config.adev[0].samples_per_sec = format.nsamplespersec; my_audio_config.adev[0].bits_per_sample = format.wbitspersample; my_audio_config.adev[0].num_channels = format.nchannels; my_audio_config.achan[0].valid = 1; if (format.nchannels == 2) my_audio_config.achan[1].valid = 1; text_color_set(DW_COLOR_INFO); dw_printf ("%d samples per second. %d bits per sample. %d audio channels.\n", my_audio_config.adev[0].samples_per_sec, my_audio_config.adev[0].bits_per_sample, my_audio_config.adev[0].num_channels); duration = (double) wav_data.datasize / ((my_audio_config.adev[0].bits_per_sample / 8) * my_audio_config.adev[0].num_channels * my_audio_config.adev[0].samples_per_sec); dw_printf ("%d audio bytes in file. Duration = %.1f seconds.\n", (int)(wav_data.datasize), duration); dw_printf ("Fix Bits level = %d\n", my_audio_config.achan[0].fix_bits); /* * Initialize the AFSK demodulator and HDLC decoder. */ multi_modem_init (&my_audio_config); e_o_f = 0; while ( ! e_o_f) { int audio_sample; int c; for (c=0; c= 256 * 256) { e_o_f = 1; continue; } if (c == 0) sample_number++; if (decode_only == 0 && c != 0) continue; if (decode_only == 1 && c != 1) continue; multi_modem_process_sample(c,audio_sample); } /* When a complete frame is accumulated, */ /* process_rec_frame, below, is called. */ } text_color_set(DW_COLOR_INFO); dw_printf ("\n\n"); #if EXPERIMENT_G for (j=0; j error_if_greater_than) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n * * * TEST FAILED: number decoded is greater than %d * * * \n", error_if_greater_than); exit (EXIT_FAILURE); } exit (EXIT_SUCCESS); } /* * Simulate sample from the audio device. */ int audio_get (int a) { int ch; if (wav_data.datasize <= 0) { e_o_f = 1; return (-1); } ch = getc(fp); wav_data.datasize--; if (ch < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Unexpected end of file.\n"); e_o_f = 1; } return (ch); } /* * Rather than queuing up frames with bad FCS, * try to fix them immediately. */ void rdq_append (rrbb_t rrbb) { int chan, subchan, slice; alevel_t alevel; chan = rrbb_get_chan(rrbb); subchan = rrbb_get_subchan(rrbb); slice = rrbb_get_slice(rrbb); alevel = rrbb_get_audio_level(rrbb); hdlc_rec2_try_to_fix_later (rrbb, chan, subchan, slice, alevel); rrbb_delete (rrbb); } /* * This is called when we have a good frame. */ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) { char stemp[500]; unsigned char *pinfo; int info_len; int h; char heard[AX25_MAX_ADDR_LEN]; char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE]; packets_decoded++; ax25_format_addrs (pp, stemp); info_len = ax25_get_info (pp, &pinfo); /* Print so we can see what is going on. */ //TODO: quiet option - suppress packet printing, only the count at the end. #if 1 /* Display audio input level. */ /* Who are we hearing? Original station or digipeater? */ if (ax25_get_num_addr(pp) == 0) { /* Not AX.25. No station to display below. */ h = -1; strlcpy (heard, "", sizeof(heard)); } else { h = ax25_get_heard(pp); ax25_get_addr_with_ssid(pp, h, heard); } text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); dw_printf("DECODED[%d] ", packets_decoded ); /* Insert time stamp relative to start of file. */ double sec = (double)sample_number / my_audio_config.adev[0].samples_per_sec; int min = (int)(sec / 60.); sec -= min * 60; dw_printf ("%d:%07.4f ", min, sec); if (h != AX25_SOURCE) { dw_printf ("Digipeater "); } ax25_alevel_to_text (alevel, alevel_text); if (my_audio_config.achan[chan].fix_bits == RETRY_NONE && my_audio_config.achan[chan].passall == 0) { dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum); } else { dw_printf ("%s audio level = %s [%s] %s\n", heard, alevel_text, retry_text[(int)retries], spectrum); } #endif //#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H) // int j; // // for (j=0; j 1 && my_audio_config.achan[chan].num_slicers == 1) { dw_printf ("[%d.%d] ", chan, subchan); } else if (my_audio_config.achan[chan].num_subchan == 1 && my_audio_config.achan[chan].num_slicers > 1) { dw_printf ("[%d.%d] ", chan, slice); } else if (my_audio_config.achan[chan].num_subchan > 1 && my_audio_config.achan[chan].num_slicers > 1) { dw_printf ("[%d.%d.%d] ", chan, subchan, slice); } else { dw_printf ("[%d] ", chan); } dw_printf ("%s", stemp); /* stations followed by : */ ax25_safe_print ((char *)pinfo, info_len, 0); dw_printf ("\n"); #if 1 // temp experiment TODO: remove this. #include "decode_aprs.h" #include "log.h" if (ax25_is_aprs(pp)) { decode_aprs_t A; decode_aprs (&A, pp, 0); // Temp experiment to see how different systems set the RR bits in the source and destination. // log_rr_bits (&A, pp); } #endif ax25_delete (pp); } /* end fake dlq_append */ void ptt_set (int ot, int chan, int ptt_signal) { return; } int get_input (int it, int chan) { return -1; } static void usage (void) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n"); dw_printf ("atest is a test application which decodes AX.25 frames from an audio\n"); dw_printf ("recording. This provides an easy way to test Dire Wolf decoding\n"); dw_printf ("performance much quicker than normal real-time. \n"); dw_printf ("\n"); dw_printf ("usage:\n"); dw_printf ("\n"); dw_printf (" atest [ options ] wav-file-in\n"); dw_printf ("\n"); dw_printf (" -B n Bits/second for data. Proper modem automatically selected for speed.\n"); dw_printf (" 300 baud uses 1600/1800 Hz AFSK.\n"); dw_printf (" 1200 (default) baud uses 1200/2200 Hz AFSK.\n"); dw_printf (" 9600 baud uses K9NG/G2RUH standard.\n"); dw_printf ("\n"); dw_printf (" -D n Divide audio sample rate by n.\n"); dw_printf ("\n"); dw_printf (" -F n Amount of effort to try fixing frames with an invalid CRC. \n"); dw_printf (" 0 (default) = consider only correct frames. \n"); dw_printf (" 1 = Try to fix only a single bit. \n"); dw_printf (" more = Try modifying more bits to get a good CRC.\n"); dw_printf ("\n"); dw_printf (" -P m Select the demodulator type such as A, B, C, D (default for 300 baud),\n"); dw_printf (" E (default for 1200 baud), F, A+, B+, C+, D+, E+, F+.\n"); dw_printf ("\n"); dw_printf (" -0 Use channel 0 (left) of stereo audio (default).\n"); dw_printf (" -1 use channel 1 (right) of stereo audio.\n"); dw_printf (" -2 decode both channels of stereo audio.\n"); dw_printf ("\n"); dw_printf (" wav-file-in is a WAV format audio file.\n"); dw_printf ("\n"); dw_printf ("Examples:\n"); dw_printf ("\n"); dw_printf (" gen_packets -o test1.wav\n"); dw_printf (" atest test1.wav\n"); dw_printf ("\n"); dw_printf (" gen_packets -B 300 -o test3.wav\n"); dw_printf (" atest -B 300 test3.wav\n"); dw_printf ("\n"); dw_printf (" gen_packets -B 9600 -o test9.wav\n"); dw_printf (" atest -B 9600 test9.wav\n"); dw_printf ("\n"); dw_printf (" This generates and decodes 3 test files with 1200, 300, and 9600\n"); dw_printf (" bits per second.\n"); dw_printf ("\n"); dw_printf (" atest 02_Track_2.wav\n"); dw_printf (" atest -P C+ 02_Track_2.wav\n"); dw_printf (" atest -F 1 02_Track_2.wav\n"); dw_printf (" atest -P C+ -F 1 02_Track_2.wav\n"); dw_printf ("\n"); dw_printf (" Try different combinations of options to find the best decoding\n"); dw_printf (" performance.\n"); exit (1); } /* end atest.c */ direwolf-1.5+dfsg/audio.c000066400000000000000000001133231347750676600154060ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: audio.c * * Purpose: Interface to audio device commonly called a "sound card" for * historical reasons. * * This version is for Linux and Cygwin. * * Two different types of sound interfaces are supported: * * * OSS - For Cygwin or Linux versions with /dev/dsp. * * * ALSA - For Linux versions without /dev/dsp. * In this case, define preprocessor symbol USE_ALSA. * * References: Some tips on on using Linux sound devices. * * http://www.oreilly.de/catalog/multilinux/excerpt/ch14-05.htm * http://cygwin.com/ml/cygwin-patches/2004-q1/msg00116/devdsp.c * http://manuals.opensound.com/developer/fulldup.c.html * * "Introduction to Sound Programming with ALSA" * http://www.linuxjournal.com/article/6735?page=0,1 * * http://www.alsa-project.org/main/index.php/Asoundrc * * Credits: Release 1.0: Fabrice FAURE contributed code for the SDR UDP interface. * * Discussion here: http://gqrx.dk/doc/streaming-audio-over-udp * * Release 1.1: Gabor Berczi provided fixes for the OSS code * which had fallen into decay. * * Major Revisions: * * 1.2 - Add ability to use more than one audio device. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #include #include #include #include #if USE_ALSA #include #else #include #ifdef __OpenBSD__ #include #else #include #endif #endif #include "audio.h" #include "audio_stats.h" #include "textcolor.h" #include "dtime_now.h" #include "demod.h" /* for alevel_t & demod_get_audio_level() */ /* Audio configuration. */ static struct audio_s *save_audio_config_p; /* Current state for each of the audio devices. */ static struct adev_s { #if USE_ALSA snd_pcm_t *audio_in_handle; snd_pcm_t *audio_out_handle; int bytes_per_frame; /* number of bytes for a sample from all channels. */ /* e.g. 4 for stereo 16 bit. */ #else int oss_audio_device_fd; /* Single device, both directions. */ #endif int inbuf_size_in_bytes; /* number of bytes allocated */ unsigned char *inbuf_ptr; int inbuf_len; /* number byte of actual data available. */ int inbuf_next; /* index of next to remove. */ int outbuf_size_in_bytes; unsigned char *outbuf_ptr; int outbuf_len; enum audio_in_type_e g_audio_in_type; int udp_sock; /* UDP socket for receiving data */ } adev[MAX_ADEVS]; // Originally 40. Version 1.2, try 10 for lower latency. #define ONE_BUF_TIME 10 #if USE_ALSA static int set_alsa_params (int a, snd_pcm_t *handle, struct audio_s *pa, char *name, char *dir); //static void alsa_select_device (char *pick_dev, int direction, char *result); #else static int set_oss_params (int a, int fd, struct audio_s *pa); #endif #define roundup1k(n) (((n) + 0x3ff) & ~0x3ff) static int calcbufsize(int rate, int chans, int bits) { int size1 = (rate * chans * bits / 8 * ONE_BUF_TIME) / 1000; int size2 = roundup1k(size1); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n", rate, chans, bits, size1, size2); #endif return (size2); } /*------------------------------------------------------------------ * * Name: audio_open * * Purpose: Open the digital audio device. * For "OSS", the device name is typically "/dev/dsp". * For "ALSA", it's a lot more complicated. See User Guide. * * New in version 1.0, we recognize "udp:" optionally * followed by a port number. * * Inputs: pa - Address of structure of type audio_s. * * Using a structure, rather than separate arguments * seemed to make sense because we often pass around * the same set of parameters various places. * * The fields that we care about are: * num_channels * samples_per_sec * bits_per_sample * If zero, reasonable defaults will be provided. * * The device names are in adevice_in and adevice_out. * - For "OSS", the device name is typically "/dev/dsp". * - For "ALSA", the device names are hw:c,d * where c is the "card" (for historical purposes) * and d is the "device" within the "card." * * * Outputs: pa - The ACTUAL values are returned here. * * These might not be exactly the same as what was requested. * * Example: ask for stereo, 16 bits, 22050 per second. * An ordinary desktop/laptop PC should be able to handle this. * However, some other sort of smaller device might be * more restrictive in its capabilities. * It might say, the best I can do is mono, 8 bit, 8000/sec. * * The sofware modem must use this ACTUAL information * that the device is supplying, that could be different * than what the user specified. * * Returns: 0 for success, -1 for failure. * * *----------------------------------------------------------------*/ int audio_open (struct audio_s *pa) { int err; int chan; int a; char audio_in_name[30]; char audio_out_name[30]; save_audio_config_p = pa; memset (adev, 0, sizeof(adev)); for (a=0; aadev[a].num_channels == 0) pa->adev[a].num_channels = DEFAULT_NUM_CHANNELS; if (pa->adev[a].samples_per_sec == 0) pa->adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; if (pa->adev[a].bits_per_sample == 0) pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; for (chan=0; chanachan[chan].mark_freq == 0) pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ; if (pa->achan[chan].space_freq == 0) pa->achan[chan].space_freq = DEFAULT_SPACE_FREQ; if (pa->achan[chan].baud == 0) pa->achan[chan].baud = DEFAULT_BAUD; if (pa->achan[chan].num_subchan == 0) pa->achan[chan].num_subchan = 1; } } /* * Open audio device(s). */ for (a=0; aadev[a].defined) { adev[a].inbuf_size_in_bytes = 0; adev[a].inbuf_ptr = NULL; adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; adev[a].outbuf_size_in_bytes = 0; adev[a].outbuf_ptr = NULL; adev[a].outbuf_len = 0; /* * Determine the type of audio input. */ adev[a].g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) { adev[a].g_audio_in_type = AUDIO_IN_TYPE_STDIN; /* Change "-" to stdin for readability. */ strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in)); } if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) { adev[a].g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP; /* Supply default port if none specified. */ if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 || strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) { snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT); } } /* Let user know what is going on. */ /* If not specified, the device names should be "default". */ strlcpy (audio_in_name, pa->adev[a].adevice_in, sizeof(audio_in_name)); strlcpy (audio_out_name, pa->adev[a].adevice_out, sizeof(audio_out_name)); char ctemp[40]; if (pa->adev[a].num_channels == 2) { snprintf (ctemp, sizeof(ctemp), " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1); } else { snprintf (ctemp, sizeof(ctemp), " (channel %d)", ADEVFIRSTCHAN(a)); } text_color_set(DW_COLOR_INFO); if (strcmp(audio_in_name,audio_out_name) == 0) { dw_printf ("Audio device for both receive and transmit: %s %s\n", audio_in_name, ctemp); } else { dw_printf ("Audio input device for receive: %s %s\n", audio_in_name, ctemp); dw_printf ("Audio out device for transmit: %s %s\n", audio_out_name, ctemp); } /* * Now attempt actual opens. */ /* * Input device. */ switch (adev[a].g_audio_in_type) { /* * Soundcard - ALSA. */ case AUDIO_IN_TYPE_SOUNDCARD: #if USE_ALSA err = snd_pcm_open (&(adev[a].audio_in_handle), audio_in_name, SND_PCM_STREAM_CAPTURE, 0); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not open audio device %s for input\n%s\n", audio_in_name, snd_strerror(err)); return (-1); } adev[a].inbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_in_handle, pa, audio_in_name, "input"); #else // OSS adev[a].oss_audio_device_fd = open (pa->adev[a].adevice_in, O_RDWR); if (adev[a].oss_audio_device_fd < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("%s:\n", pa->adev[a].adevice_in); // snprintf (message, sizeof(message), "Could not open audio device %s", pa->adev[a].adevice_in); // perror (message); return (-1); } adev[a].outbuf_size_in_bytes = adev[a].inbuf_size_in_bytes = set_oss_params (a, adev[a].oss_audio_device_fd, pa); if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { return (-1); } #endif break; /* * UDP. */ case AUDIO_IN_TYPE_SDR_UDP: //Create socket and bind socket { struct sockaddr_in si_me; //int slen=sizeof(si_me); //int data_size = 0; //Create UDP Socket if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't create socket, errno %d\n", errno); return -1; } memset((char *) &si_me, 0, sizeof(si_me)); si_me.sin_family = AF_INET; si_me.sin_port = htons((short)atoi(audio_in_name+4)); si_me.sin_addr.s_addr = htonl(INADDR_ANY); //Bind to the socket if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't bind socket, errno %d\n", errno); return -1; } } adev[a].inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN; break; /* * stdin. */ case AUDIO_IN_TYPE_STDIN: /* Do we need to adjust any properties of stdin? */ adev[a].inbuf_size_in_bytes = 1024; break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error, invalid audio_in_type\n"); return (-1); } /* * Output device. Only "soundcard" is supported at this time. */ #if USE_ALSA err = snd_pcm_open (&(adev[a].audio_out_handle), audio_out_name, SND_PCM_STREAM_PLAYBACK, 0); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not open audio device %s for output\n%s\n", audio_out_name, snd_strerror(err)); return (-1); } adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output"); if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { return (-1); } #endif /* * Finally allocate buffer for each direction. */ adev[a].inbuf_ptr = malloc(adev[a].inbuf_size_in_bytes); assert (adev[a].inbuf_ptr != NULL); adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; adev[a].outbuf_ptr = malloc(adev[a].outbuf_size_in_bytes); assert (adev[a].outbuf_ptr != NULL); adev[a].outbuf_len = 0; } /* end of audio device defined */ } /* end of for each audio device */ return (0); } /* end audio_open */ #if USE_ALSA /* * Set parameters for sound card. * * See ?? for details. */ /* * Terminology: * Sample - for one channel. e.g. 2 bytes for 16 bit. * Frame - one sample for all channels. e.g. 4 bytes for 16 bit stereo * Period - size of one transfer. */ static int set_alsa_params (int a, snd_pcm_t *handle, struct audio_s *pa, char *devname, char *inout) { snd_pcm_hw_params_t *hw_params; snd_pcm_uframes_t fpp; /* Frames per period. */ unsigned int val; int dir; int err; int buf_size_in_bytes; /* result, number of bytes per transfer. */ err = snd_pcm_hw_params_malloc (&hw_params); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not alloc hw param structure.\n%s\n", snd_strerror(err)); dw_printf ("for %s %s.\n", devname, inout); return (-1); } err = snd_pcm_hw_params_any (handle, hw_params); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not init hw param structure.\n%s\n", snd_strerror(err)); dw_printf ("for %s %s.\n", devname, inout); return (-1); } /* Interleaved data: L, R, L, R, ... */ err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not set interleaved mode.\n%s\n", snd_strerror(err)); dw_printf ("for %s %s.\n", devname, inout); return (-1); } /* Signed 16 bit little endian or unsigned 8 bit. */ err = snd_pcm_hw_params_set_format (handle, hw_params, pa->adev[a].bits_per_sample == 8 ? SND_PCM_FORMAT_U8 : SND_PCM_FORMAT_S16_LE); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not set bits per sample.\n%s\n", snd_strerror(err)); dw_printf ("for %s %s.\n", devname, inout); return (-1); } /* Number of audio channels. */ err = snd_pcm_hw_params_set_channels (handle, hw_params, pa->adev[a].num_channels); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not set number of audio channels.\n%s\n", snd_strerror(err)); dw_printf ("for %s %s.\n", devname, inout); return (-1); } /* Audio sample rate. */ val = pa->adev[a].samples_per_sec; dir = 0; err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &val, &dir); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not set audio sample rate.\n%s\n", snd_strerror(err)); dw_printf ("for %s %s.\n", devname, inout); return (-1); } if (val != pa->adev[a].samples_per_sec) { text_color_set(DW_COLOR_INFO); dw_printf ("Asked for %d samples/sec but got %d.\n", pa->adev[a].samples_per_sec, val); dw_printf ("for %s %s.\n", devname, inout); pa->adev[a].samples_per_sec = val; } /* Original: */ /* Guessed around 20 reads/sec might be good. */ /* Period too long = too much latency. */ /* Period too short = more overhead of many small transfers. */ /* fpp = pa->adev[a].samples_per_sec / 20; */ /* The suggested period size was 2205 frames. */ /* I thought the later "...set_period_size_near" might adjust it to be */ /* some more optimal nearby value based hardware buffer sizes but */ /* that didn't happen. We ended up with a buffer size of 4410 bytes. */ /* In version 1.2, let's take a different approach. */ /* Reduce the latency and round up to a multiple of 1 Kbyte. */ /* For the typical case of 44100 sample rate, 1 channel, 16 bits, we calculate */ /* a buffer size of 882 and round it up to 1k. This results in 512 frames per period. */ /* A period comes out to be about 80 periods per second or about 12.5 mSec each. */ buf_size_in_bytes = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample); #if __arm__ /* Ugly hack for RPi. */ /* Reducing buffer size is fine for input but not so good for output. */ if (*inout == 'o') { buf_size_in_bytes = buf_size_in_bytes * 4; } #endif fpp = buf_size_in_bytes / (pa->adev[a].num_channels * pa->adev[a].bits_per_sample / 8); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("suggest period size of %d frames\n", (int)fpp); #endif dir = 0; err = snd_pcm_hw_params_set_period_size_near (handle, hw_params, &fpp, &dir); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not set period size\n%s\n", snd_strerror(err)); dw_printf ("for %s %s.\n", devname, inout); return (-1); } err = snd_pcm_hw_params (handle, hw_params); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not set hw params\n%s\n", snd_strerror(err)); dw_printf ("for %s %s.\n", devname, inout); return (-1); } /* Driver might not like our suggested period size */ /* and might have another idea. */ err = snd_pcm_hw_params_get_period_size (hw_params, &fpp, NULL); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not get audio period size.\n%s\n", snd_strerror(err)); dw_printf ("for %s %s.\n", devname, inout); return (-1); } snd_pcm_hw_params_free (hw_params); /* A "frame" is one sample for all channels. */ /* The read and write use units of frames, not bytes. */ adev[a].bytes_per_frame = snd_pcm_frames_to_bytes (handle, 1); assert (adev[a].bytes_per_frame == pa->adev[a].num_channels * pa->adev[a].bits_per_sample / 8); buf_size_in_bytes = fpp * adev[a].bytes_per_frame; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("audio buffer size = %d (bytes per frame) x %d (frames per period) = %d \n", adev[a].bytes_per_frame, (int)fpp, buf_size_in_bytes); #endif /* Version 1.3 - after a report of this situation for Mac OSX version. */ if (buf_size_in_bytes < 256 || buf_size_in_bytes > 32768) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio buffer has unexpected extreme size of %d bytes.\n", buf_size_in_bytes); dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__); dw_printf ("This might be caused by unusual audio device configuration values.\n"); buf_size_in_bytes = 2048; dw_printf ("Using %d to attempt recovery.\n", buf_size_in_bytes); } return (buf_size_in_bytes); } /* end alsa_set_params */ #else /* * Set parameters for sound card. (OSS only) * * See /usr/include/sys/soundcard.h for details. */ static int set_oss_params (int a, int fd, struct audio_s *pa) { int err; int devcaps; int asked_for; char message[100]; int ossbuf_size_in_bytes; err = ioctl (fd, SNDCTL_DSP_CHANNELS, &(pa->adev[a].num_channels)); if (err == -1) { text_color_set(DW_COLOR_ERROR); perror("Not able to set audio device number of channels"); return (-1); } asked_for = pa->adev[a].samples_per_sec; err = ioctl (fd, SNDCTL_DSP_SPEED, &(pa->adev[a].samples_per_sec)); if (err == -1) { text_color_set(DW_COLOR_ERROR); perror("Not able to set audio device sample rate"); return (-1); } if (pa->adev[a].samples_per_sec != asked_for) { text_color_set(DW_COLOR_INFO); dw_printf ("Asked for %d samples/sec but actually using %d.\n", asked_for, pa->adev[a].samples_per_sec); } /* This is actually a bit mask but it happens that */ /* 0x8 is unsigned 8 bit samples and */ /* 0x10 is signed 16 bit little endian. */ err = ioctl (fd, SNDCTL_DSP_SETFMT, &(pa->adev[a].bits_per_sample)); if (err == -1) { text_color_set(DW_COLOR_ERROR); perror("Not able to set audio device sample size"); return (-1); } /* * Determine capabilities. */ err = ioctl (fd, SNDCTL_DSP_GETCAPS, &devcaps); if (err == -1) { text_color_set(DW_COLOR_ERROR); perror("Not able to get audio device capabilities"); // Is this fatal? // return (-1); } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_open(): devcaps = %08x\n", devcaps); if (devcaps & DSP_CAP_DUPLEX) dw_printf ("Full duplex record/playback.\n"); if (devcaps & DSP_CAP_BATCH) dw_printf ("Device has some kind of internal buffers which may cause delays.\n"); if (devcaps & ~ (DSP_CAP_DUPLEX | DSP_CAP_BATCH)) dw_printf ("Others...\n"); #endif if (!(devcaps & DSP_CAP_DUPLEX)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio device does not support full duplex\n"); // Do we care? // return (-1); } err = ioctl (fd, SNDCTL_DSP_SETDUPLEX, NULL); if (err == -1) { // text_color_set(DW_COLOR_ERROR); // perror("Not able to set audio full duplex mode"); // Unfortunate but not a disaster. } /* * Get preferred block size. * Presumably this will provide the most efficient transfer. * * In my particular situation, this turned out to be * 2816 for 11025 Hz 16 bit mono * 5568 for 11025 Hz 16 bit stereo * 11072 for 44100 Hz 16 bit mono * * This was long ago under different conditions. * Should study this again some day. * * Your milage may vary. */ err = ioctl (fd, SNDCTL_DSP_GETBLKSIZE, &ossbuf_size_in_bytes); if (err == -1) { text_color_set(DW_COLOR_ERROR); perror("Not able to get audio block size"); ossbuf_size_in_bytes = 2048; /* pick something reasonable */ } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_open(): suggestd block size is %d\n", ossbuf_size_in_bytes); #endif /* * That's 1/8 of a second which seems rather long if we want to * respond quickly. */ ossbuf_size_in_bytes = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_open(): using block size of %d\n", ossbuf_size_in_bytes); #endif #if 0 /* Original - dies without good explanation. */ assert (ossbuf_size_in_bytes >= 256 && ossbuf_size_in_bytes <= 32768); #else /* Version 1.3 - after a report of this situation for Mac OSX version. */ if (ossbuf_size_in_bytes < 256 || ossbuf_size_in_bytes > 32768) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio buffer has unexpected extreme size of %d bytes.\n", ossbuf_size_in_bytes); dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__); dw_printf ("This might be caused by unusual audio device configuration values.\n"); ossbuf_size_in_bytes = 2048; dw_printf ("Using %d to attempt recovery.\n", ossbuf_size_in_bytes); } #endif return (ossbuf_size_in_bytes); } /* end set_oss_params */ #endif /*------------------------------------------------------------------ * * Name: audio_get * * Purpose: Get one byte from the audio device. * * Inputs: a - Our number for audio device. * * Returns: 0 - 255 for a valid sample. * -1 for any type of error. * * Description: The caller must deal with the details of mono/stereo * and number of bytes per sample. * * This will wait if no data is currently available. * *----------------------------------------------------------------*/ // Use hot attribute for all functions called for every audio sample. __attribute__((hot)) int audio_get (int a) { int n; int retries = 0; #if STATISTICS /* Gather numbers for read from audio device. */ #define duration 100 /* report every 100 seconds. */ static time_t last_time[MAX_ADEVS]; time_t this_time[MAX_ADEVS]; static int sample_count[MAX_ADEVS]; static int error_count[MAX_ADEVS]; #endif #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_get():\n"); #endif assert (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768); switch (adev[a].g_audio_in_type) { /* * Soundcard - ALSA */ case AUDIO_IN_TYPE_SOUNDCARD: #if USE_ALSA while (adev[a].inbuf_next >= adev[a].inbuf_len) { assert (adev[a].audio_in_handle != NULL); #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_get(): readi asking for %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame); #endif n = snd_pcm_readi (adev[a].audio_in_handle, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame); #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_get(): readi asked for %d and got %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n); #endif if (n > 0) { /* Success */ adev[a].inbuf_len = n * adev[a].bytes_per_frame; /* convert to number of bytes */ adev[a].inbuf_next = 0; audio_stats (a, save_audio_config_p->adev[a].num_channels, n, save_audio_config_p->statistics_interval); } else if (n == 0) { /* Didn't expect this, but it's not a problem. */ /* Wait a little while and try again. */ text_color_set(DW_COLOR_ERROR); dw_printf ("Audio input got zero bytes: %s\n", snd_strerror(n)); SLEEP_MS(10); adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; } else { /* Error */ // TODO: Needs more study and testing. // Only expected error conditions: // -EBADFD PCM is not in the right state (SND_PCM_STATE_PREPARED or SND_PCM_STATE_RUNNING) // -EPIPE an overrun occurred // -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery) // Data overrun is displayed as "broken pipe" which seems a little misleading. // Add our own message which says something about CPU being too slow. text_color_set(DW_COLOR_ERROR); dw_printf ("Audio input device %d error code %d: %s\n", a, n, snd_strerror(n)); if (n == (-EPIPE)) { dw_printf ("This is most likely caused by the CPU being too slow to keep up with the audio stream.\n"); dw_printf ("Use the \"top\" command, in another command window, to look at CPU usage.\n"); dw_printf ("This might be a temporary condition so we will attempt to recover a few times before giving up.\n"); } audio_stats (a, save_audio_config_p->adev[a].num_channels, 0, save_audio_config_p->statistics_interval); /* Try to recover a few times and eventually give up. */ if (++retries > 10) { adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; return (-1); } if (n == -EPIPE) { /* EPIPE means overrun */ snd_pcm_recover (adev[a].audio_in_handle, n, 1); } else { /* Could be some temporary condition. */ /* Wait a little then try again. */ /* Sometimes I get "Resource temporarily available" */ /* when the Update Manager decides to run. */ SLEEP_MS (250); snd_pcm_recover (adev[a].audio_in_handle, n, 1); } } } #else /* end ALSA, begin OSS */ /* Fixed in 1.2. This was formerly outside of the switch */ /* so the OSS version did not process stdin or UDP. */ while (adev[a].g_audio_in_type == AUDIO_IN_TYPE_SOUNDCARD && adev[a].inbuf_next >= adev[a].inbuf_len) { assert (adev[a].oss_audio_device_fd > 0); n = read (adev[a].oss_audio_device_fd, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes); //text_color_set(DW_COLOR_DEBUG); // dw_printf ("audio_get(): read %d returns %d\n", adev[a].inbuf_size_in_bytes, n); if (n < 0) { text_color_set(DW_COLOR_ERROR); perror("Can't read from audio device"); adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; audio_stats (a, save_audio_config_p->adev[a].num_channels, 0, save_audio_config_p->statistics_interval); return (-1); } adev[a].inbuf_len = n; adev[a].inbuf_next = 0; audio_stats (a, save_audio_config_p->adev[a].num_channels, n / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), save_audio_config_p->statistics_interval); } #endif /* USE_ALSA */ break; /* * UDP. */ case AUDIO_IN_TYPE_SDR_UDP: while (adev[a].inbuf_next >= adev[a].inbuf_len) { int res; assert (adev[a].udp_sock > 0); res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0); if (res < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't read from udp socket, res=%d", res); adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; audio_stats (a, save_audio_config_p->adev[a].num_channels, 0, save_audio_config_p->statistics_interval); return (-1); } adev[a].inbuf_len = res; adev[a].inbuf_next = 0; audio_stats (a, save_audio_config_p->adev[a].num_channels, res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), save_audio_config_p->statistics_interval); } break; /* * stdin. */ case AUDIO_IN_TYPE_STDIN: while (adev[a].inbuf_next >= adev[a].inbuf_len) { //int ch, res,i; int res; res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes); if (res <= 0) { text_color_set(DW_COLOR_INFO); dw_printf ("\nEnd of file on stdin. Exiting.\n"); exit (0); } audio_stats (a, save_audio_config_p->adev[a].num_channels, res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), save_audio_config_p->statistics_interval); adev[a].inbuf_len = res; adev[a].inbuf_next = 0; } break; } if (adev[a].inbuf_next < adev[a].inbuf_len) n = adev[a].inbuf_ptr[adev[a].inbuf_next++]; //No data to read, avoid reading outside buffer else n = 0; #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_get(): returns %d\n", n); #endif return (n); } /* end audio_get */ /*------------------------------------------------------------------ * * Name: audio_put * * Purpose: Send one byte to the audio device. * * Inputs: a * * c - One byte in range of 0 - 255. * * Returns: Normally non-negative. * -1 for any type of error. * * Description: The caller must deal with the details of mono/stereo * and number of bytes per sample. * * See Also: audio_flush * audio_wait * *----------------------------------------------------------------*/ int audio_put (int a, int c) { /* Should never be full at this point. */ assert (adev[a].outbuf_len < adev[a].outbuf_size_in_bytes); adev[a].outbuf_ptr[adev[a].outbuf_len++] = c; if (adev[a].outbuf_len == adev[a].outbuf_size_in_bytes) { return (audio_flush(a)); } return (0); } /* end audio_put */ /*------------------------------------------------------------------ * * Name: audio_flush * * Purpose: Push out any partially filled output buffer. * * Returns: Normally non-negative. * -1 for any type of error. * * See Also: audio_flush * audio_wait * *----------------------------------------------------------------*/ int audio_flush (int a) { #if USE_ALSA int k; unsigned char *psound; int retries = 10; snd_pcm_status_t *status; assert (adev[a].audio_out_handle != NULL); /* * Trying to set the automatic start threshold didn't have the desired * effect. After the first transmitted packet, they are saved up * for a few minutes and then all come out together. * * "Prepare" it if not already in the running state. * We stop it at the end of each transmitted packet. */ snd_pcm_status_alloca(&status); k = snd_pcm_status (adev[a].audio_out_handle, status); if (k != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k)); } if ((k = snd_pcm_status_get_state(status)) != SND_PCM_STATE_RUNNING) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Audio output state = %d. Try to start.\n", k); k = snd_pcm_prepare (adev[a].audio_out_handle); if (k != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio output start error.\n%s\n", snd_strerror(k)); } } psound = adev[a].outbuf_ptr; while (retries-- > 0) { k = snd_pcm_writei (adev[a].audio_out_handle, psound, adev[a].outbuf_len / adev[a].bytes_per_frame); #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n", adev[a].outbuf_len / adev[a].bytes_per_frame, k); fflush (stdout); #endif if (k == -EPIPE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio output data underrun.\n"); /* No problemo. Recover and go around again. */ snd_pcm_recover (adev[a].audio_out_handle, k, 1); } else if (k < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio write error: %s\n", snd_strerror(k)); /* Some other error condition. */ /* Try again. What do we have to lose? */ snd_pcm_recover (adev[a].audio_out_handle, k, 1); } else if (k != adev[a].outbuf_len / adev[a].bytes_per_frame) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio write took %d frames rather than %d.\n", k, adev[a].outbuf_len / adev[a].bytes_per_frame); /* Go around again with the rest of it. */ psound += k * adev[a].bytes_per_frame; adev[a].outbuf_len -= k * adev[a].bytes_per_frame; } else { /* Success! */ adev[a].outbuf_len = 0; return (0); } } text_color_set(DW_COLOR_ERROR); dw_printf ("Audio write error retry count exceeded.\n"); adev[a].outbuf_len = 0; return (-1); #else /* OSS */ int k; unsigned char *ptr; int len; ptr = adev[a].outbuf_ptr; len = adev[a].outbuf_len; while (len > 0) { assert (adev[a].oss_audio_device_fd > 0); k = write (adev[a].oss_audio_device_fd, ptr, len); #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_flush(): write %d returns %d\n", len, k); fflush (stdout); #endif if (k < 0) { text_color_set(DW_COLOR_ERROR); perror("Can't write to audio device"); adev[a].outbuf_len = 0; return (-1); } if (k < len) { /* presumably full but didn't block. */ usleep (10000); } ptr += k; len -= k; } adev[a].outbuf_len = 0; return (0); #endif } /* end audio_flush */ /*------------------------------------------------------------------ * * Name: audio_wait * * Purpose: Finish up audio output before turning PTT off. * * Inputs: a - Index for audio device (not channel!) * * Returns: None. * * Description: Flush out any partially filled audio output buffer. * Wait until all the queued up audio out has been played. * Take any other necessary actions to stop audio output. * * In an ideal world: * * We would like to ask the hardware when all the queued * up sound has actually come out the speaker. * * In reality: * * This has been found to be less than reliable in practice. * * Caller does the following: * * (1) Make note of when PTT is turned on. * (2) Calculate how long it will take to transmit the * frame including TXDELAY, frame (including * "flags", data, FCS and bit stuffing), and TXTAIL. * (3) Call this function, which might or might not wait long enough. * (4) Add (1) and (2) resulting in when PTT should be turned off. * (5) Take difference between current time and desired PPT off time * and wait for additoinal time if required. * *----------------------------------------------------------------*/ void audio_wait (int a) { audio_flush (a); #if USE_ALSA /* For playback, this should wait for all pending frames */ /* to be played and then stop. */ snd_pcm_drain (adev[a].audio_out_handle); /* * When this was first implemented, I observed: * * "Experimentation reveals that snd_pcm_drain doesn't * actually wait. It returns immediately. * However it does serve a useful purpose of stopping * the playback after all the queued up data is used." * * * Now that I take a closer look at the transmit timing, for * version 1.2, it seems that snd_pcm_drain DOES wait until all * all pending frames have been played. * Either way, the caller will now compensate for it. */ #else assert (adev[a].oss_audio_device_fd > 0); // This caused a crash later on Cygwin. // Haven't tried it on other (non-Linux) Unix yet. // err = ioctl (adev[a].oss_audio_device_fd, SNDCTL_DSP_SYNC, NULL); #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_wait(): after sync, status=%d\n", err); #endif } /* end audio_wait */ /*------------------------------------------------------------------ * * Name: audio_close * * Purpose: Close the audio device(s). * * Returns: Normally non-negative. * -1 for any type of error. * * *----------------------------------------------------------------*/ int audio_close (void) { int err = 0; int a; for (a = 0; a < MAX_ADEVS; a++) { #if USE_ALSA if (adev[a].audio_in_handle != NULL && adev[a].audio_out_handle != NULL) { audio_wait (a); snd_pcm_close (adev[a].audio_in_handle); snd_pcm_close (adev[a].audio_out_handle); #else if (adev[a].oss_audio_device_fd > 0) { audio_wait (a); close (adev[a].oss_audio_device_fd); adev[a].oss_audio_device_fd = -1; #endif free (adev[a].inbuf_ptr); free (adev[a].outbuf_ptr); adev[a].inbuf_size_in_bytes = 0; adev[a].inbuf_ptr = NULL; adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; adev[a].outbuf_size_in_bytes = 0; adev[a].outbuf_ptr = NULL; adev[a].outbuf_len = 0; } } return (err); } /* end audio_close */ /* end audio.c */ direwolf-1.5+dfsg/audio.h000066400000000000000000000316621347750676600154200ustar00rootroot00000000000000 /*------------------------------------------------------------------ * * Module: audio.h * * Purpose: Interface to audio device commonly called a "sound card" * for historical reasons. * *---------------------------------------------------------------*/ #ifndef AUDIO_H #define AUDIO_H 1 #ifdef USE_HAMLIB #include #endif #include "direwolf.h" /* for MAX_CHANS used throughout the application. */ #include "ax25_pad.h" /* for AX25_MAX_ADDR_LEN */ /* * PTT control. */ enum ptt_method_e { PTT_METHOD_NONE, /* VOX or no transmit. */ PTT_METHOD_SERIAL, /* Serial port RTS or DTR. */ PTT_METHOD_GPIO, /* General purpose I/O, Linux only. */ PTT_METHOD_LPT, /* Parallel printer port, Linux only. */ PTT_METHOD_HAMLIB, /* HAMLib, Linux only. */ PTT_METHOD_CM108 }; /* GPIO pin of CM108/CM119/etc. Linux only. */ typedef enum ptt_method_e ptt_method_t; enum ptt_line_e { PTT_LINE_NONE = 0, PTT_LINE_RTS = 1, PTT_LINE_DTR = 2 }; // Important: 0 for neither. typedef enum ptt_line_e ptt_line_t; enum audio_in_type_e { AUDIO_IN_TYPE_SOUNDCARD, AUDIO_IN_TYPE_SDR_UDP, AUDIO_IN_TYPE_STDIN }; /* For option to try fixing frames with bad CRC. */ typedef enum retry_e { RETRY_NONE=0, RETRY_INVERT_SINGLE=1, RETRY_INVERT_DOUBLE=2, RETRY_INVERT_TRIPLE=3, RETRY_INVERT_TWO_SEP=4, RETRY_MAX = 5} retry_t; typedef enum sanity_e { SANITY_APRS, SANITY_AX25, SANITY_NONE } sanity_t; struct audio_s { /* Previously we could handle only a single audio device. */ /* In version 1.2, we generalize this to handle multiple devices. */ /* This means we can now have more than 2 radio channels. */ struct adev_param_s { /* Properites of the sound device. */ int defined; /* Was device defined? */ /* First one defaults to yes. */ char adevice_in[80]; /* Name of the audio input device (or file?). */ /* TODO: Can be "-" to read from stdin. */ char adevice_out[80]; /* Name of the audio output device (or file?). */ int num_channels; /* Should be 1 for mono or 2 for stereo. */ int samples_per_sec; /* Audio sampling rate. Typically 11025, 22050, or 44100. */ int bits_per_sample; /* 8 (unsigned char) or 16 (signed short). */ } adev[MAX_ADEVS]; /* Common to all channels. */ char tts_script[80]; /* Script for text to speech. */ int statistics_interval; /* Number of seconds between the audio */ /* statistics reports. This is set by */ /* the "-a" option. 0 to disable feature. */ int xmit_error_rate; /* For testing purposes, we can generate frames with an invalid CRC */ /* to simulate corruption while going over the air. */ /* This is the probability, in per cent, of randomly corrupting it. */ /* Normally this is 0. 25 would mean corrupt it 25% of the time. */ int recv_error_rate; /* Similar but the % probablity of dropping a received frame. */ char timestamp_format[40]; /* -T option */ /* Precede received & transmitted frames with timestamp. */ /* Command line option uses "strftime" format string. */ /* Properties for each audio channel, common to receive and transmit. */ /* Can be different for each radio channel. */ struct achan_param_s { int valid; /* Is this channel valid? */ char mycall[AX25_MAX_ADDR_LEN]; /* Call associated with this radio channel. */ /* Could all be the same or different. */ enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_QPSK, MODEM_8PSK, MODEM_OFF } modem_type; /* Usual AFSK. */ /* Baseband signal. Not used yet. */ /* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */ /* Might try MFJ-2400 / CCITT v.26 / Bell 201 someday. */ /* No modem. Might want this for DTMF only channel. */ enum dtmf_decode_t { DTMF_DECODE_OFF, DTMF_DECODE_ON } dtmf_decode; /* Originally the DTMF ("Touch Tone") decoder was always */ /* enabled because it took a negligible amount of CPU. */ /* There were complaints about the false positives when */ /* hearing other modulation schemes on HF SSB so now it */ /* is enabled only when needed. */ /* "On" will send special "t" packet to attached applications */ /* and process as APRStt. Someday we might want to separate */ /* these but for now, we have a single off/on. */ int decimate; /* Reduce AFSK sample rate by this factor to */ /* decrease computational requirements. */ int interleave; /* If > 1, interleave samples among multiple decoders. */ /* Quick hack for experiment. */ int mark_freq; /* Two tones for AFSK modulation, in Hz. */ int space_freq; /* Standard tones are 1200 and 2200 for 1200 baud. */ int baud; /* Data bits per second. */ /* Standard rates are 1200 for VHF and 300 for HF. */ /* This should really be called bits per second. */ /* Next 3 come from config file or command line. */ char profiles[16]; /* zero or more of ABC etc, optional + */ int num_freq; /* Number of different frequency pairs for decoders. */ int offset; /* Spacing between filter frequencies. */ int num_slicers; /* Number of different threshold points to decide */ /* between mark or space. */ /* This is derived from above by demod_init. */ int num_subchan; /* Total number of modems for each channel. */ /* These are for dealing with imperfect frames. */ enum retry_e fix_bits; /* Level of effort to recover from */ /* a bad FCS on the frame. */ /* 0 = no effort */ /* 1 = try fixing a single bit */ /* 2... = more techniques... */ enum sanity_e sanity_test; /* Sanity test to apply when finding a good */ /* CRC after making a change. */ /* Must look like APRS, AX.25, or anything. */ int passall; /* Allow thru even with bad CRC. */ /* Additional properties for transmit. */ /* Originally we had control outputs only for PTT. */ /* In version 1.2, we generalize this to allow others such as DCD. */ /* In version 1.4 we add CON for connected to another station. */ /* Index following structure by one of these: */ #define OCTYPE_PTT 0 #define OCTYPE_DCD 1 #define OCTYPE_CON 2 #define NUM_OCTYPES 3 /* number of values above. i.e. last value +1. */ struct { ptt_method_t ptt_method; /* none, serial port, GPIO, LPT, HAMLIB, CM108. */ char ptt_device[100]; /* Serial device name for PTT. e.g. COM1 or /dev/ttyS0 */ /* Also used for HAMLIB. Could be host:port when model is 1. */ /* For years, 20 characters was plenty then we start getting extreme names like this: */ /* /dev/serial/by-id/usb-FTDI_Navigator__CAT___2nd_PTT__00000000-if00-port0 */ /* /dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller_D-if00-port0 */ /* Issue 104, changed to 100 bytes in version 1.5. */ /* This same field is also used for CM108 GPIO PTT which will */ /* have a name like /dev/hidraw1. */ ptt_line_t ptt_line; /* Control line when using serial port. PTT_LINE_RTS, PTT_LINE_DTR. */ ptt_line_t ptt_line2; /* Optional second one: PTT_LINE_NONE when not used. */ int out_gpio_num; /* GPIO number. Originally this was only for PTT. */ /* It is now more general. */ /* octrl array is indexed by PTT, DCD, or CONnected indicator. */ /* For CM108, this should be in range of 1-8. */ #define MAX_GPIO_NAME_LEN 20 // 12 would cover any case I've seen so this should be safe char out_gpio_name[MAX_GPIO_NAME_LEN]; /* orginally, gpio number NN was assumed to simply */ /* have the name gpioNN but this turned out not to be */ /* the case for CubieBoard where it was longer. */ /* This is filled in by ptt_init so we don't have to */ /* recalculate it each time we access it. */ /* This could probably be collapsed into ptt_device instead of being separate. */ int ptt_lpt_bit; /* Bit number for parallel printer port. */ /* Bit 0 = pin 2, ..., bit 7 = pin 9. */ int ptt_invert; /* Invert the output. */ int ptt_invert2; /* Invert the secondary output. */ #ifdef USE_HAMLIB int ptt_model; /* HAMLIB model. -1 for AUTO. 2 for rigctld. Others are radio model. */ #endif } octrl[NUM_OCTYPES]; /* Each channel can also have associated input lines. */ /* So far, we just have one for transmit inhibit. */ #define ICTYPE_TXINH 0 #define NUM_ICTYPES 1 /* number of values above. i.e. last value +1. */ struct { ptt_method_t method; /* none, serial port, GPIO, LPT. */ int in_gpio_num; /* GPIO number */ char in_gpio_name[MAX_GPIO_NAME_LEN]; /* orginally, gpio number NN was assumed to simply */ /* have the name gpioNN but this turned out not to be */ /* the case for CubieBoard where it was longer. */ /* This is filled in by ptt_init so we don't have to */ /* recalculate it each time we access it. */ int invert; /* 1 = active low */ } ictrl[NUM_ICTYPES]; /* Transmit timing. */ int dwait; /* First wait extra time for receiver squelch. */ /* Default 0 units of 10 mS each . */ int slottime; /* Slot time in 10 mS units for persistance algorithm. */ /* Typical value is 10 meaning 100 milliseconds. */ int persist; /* Sets probability for transmitting after each */ /* slot time delay. Transmit if a random number */ /* in range of 0 - 255 <= persist value. */ /* Otherwise wait another slot time and try again. */ /* Default value is 63 for 25% probability. */ int txdelay; /* After turning on the transmitter, */ /* send "flags" for txdelay * 10 mS. */ /* Default value is 30 meaning 300 milliseconds. */ int txtail; /* Amount of time to keep transmitting after we */ /* are done sending the data. This is to avoid */ /* dropping PTT too soon and chopping off the end */ /* of the frame. Again 10 mS units. */ /* At this point, I'm thinking of 10 (= 100 mS) as the default */ /* because we're not quite sure when the soundcard audio stops. */ int fulldup; /* Full Duplex. */ } achan[MAX_CHANS]; #ifdef USE_HAMLIB int rigs; /* Total number of configured rigs */ RIG *rig[MAX_RIGS]; /* HAMLib rig instances */ #endif }; #if __WIN32__ || __APPLE__ #define DEFAULT_ADEVICE "" /* Windows: Empty string = default audio device. */ #else #if USE_ALSA #define DEFAULT_ADEVICE "default" /* Use default device for ALSA. */ #else #define DEFAULT_ADEVICE "/dev/dsp" /* First audio device for OSS. */ #endif #endif /* * UDP audio receiving port. Couldn't find any standard or usage precedent. * Got the number from this example: http://gqrx.dk/doc/streaming-audio-over-udp * Any better suggestions? */ #define DEFAULT_UDP_AUDIO_PORT 7355 // Maximum size of the UDP buffer (for allowing IP routing, udp packets are often limited to 1472 bytes) #define SDR_UDP_BUF_MAXLEN 2000 #define DEFAULT_NUM_CHANNELS 1 #define DEFAULT_SAMPLES_PER_SEC 44100 /* Very early observations. Might no longer be valid. */ /* 22050 works a lot better than 11025. */ /* 44100 works a little better than 22050. */ /* If you have a reasonable machine, use the highest rate. */ #define MIN_SAMPLES_PER_SEC 8000 //#define MAX_SAMPLES_PER_SEC 48000 /* Originally 44100. Later increased because */ /* Software Defined Radio often uses 48000. */ #define MAX_SAMPLES_PER_SEC 192000 /* The cheap USB-audio adapters (e.g. CM108) can handle 44100 and 48000. */ /* The "soundcard" in my desktop PC can do 96kHz or even 192kHz. */ /* We will probably need to increase the sample rate to go much above 9600 baud. */ #define DEFAULT_BITS_PER_SAMPLE 16 #define DEFAULT_FIX_BITS RETRY_INVERT_SINGLE /* * Standard for AFSK on VHF FM. * Reversing mark and space makes no difference because * NRZI encoding only cares about change or lack of change * between the two tones. * * HF SSB uses 300 baud and 200 Hz shift. * 1600 & 1800 Hz is a popular tone pair, sometimes * called the KAM tones. */ #define DEFAULT_MARK_FREQ 1200 #define DEFAULT_SPACE_FREQ 2200 #define DEFAULT_BAUD 1200 /* Used for sanity checking in config file and command line options. */ /* 9600 baud is known to work. */ /* TODO: Is 19200 possible with a soundcard at 44100 samples/sec or do we need a higher sample rate? */ #define MIN_BAUD 100 //#define MAX_BAUD 10000 #define MAX_BAUD 40000 // Anyone want to try 38.4 k baud? /* * Typical transmit timings for VHF. */ #define DEFAULT_DWAIT 0 #define DEFAULT_SLOTTIME 10 #define DEFAULT_PERSIST 63 #define DEFAULT_TXDELAY 30 #define DEFAULT_TXTAIL 10 #define DEFAULT_FULLDUP 0 /* * Note that we have two versions of these in audio.c and audio_win.c. * Use one or the other depending on the platform. */ int audio_open (struct audio_s *pa); int audio_get (int a); /* a = audio device, 0 for first */ int audio_put (int a, int c); int audio_flush (int a); void audio_wait (int a); int audio_close (void); #endif /* ifdef AUDIO_H */ /* end audio.h */ direwolf-1.5+dfsg/audio_portaudio.c000066400000000000000000001122401347750676600174710ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2015 Robert Stiles, KK5VD // // 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, see . // /*------------------------------------------------------------------ * * Module: audio_portaudio.c * * Purpose: Interface to audio device commonly called a "sound card" for * historical reasons. * * This version is for Various OS' using Port Audio * * Major Revisions: * * 1.2 - Add ability to use more than one audio device. * 1.3 - New file added for Port Audio for Mac and possibly others. * *---------------------------------------------------------------*/ #if defined(USE_PORTAUDIO) #include "direwolf.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "audio.h" #include "audio_stats.h" #include "textcolor.h" #include "dtime_now.h" #include "demod.h" /* for alevel_t & demod_get_audio_level() */ #include "portaudio.h" /* Audio configuration. */ static struct audio_s *save_audio_config_p; /* Current state for each of the audio devices. */ static struct adev_s { pthread_mutex_t input_mutex; pthread_cond_t input_cond; PaStream *inStream; PaStreamParameters inputParameters; int pa_input_device_number; int no_of_input_channels; int input_finished; int input_pause; int input_flush; void *audio_in_handle; int inbuf_size_in_bytes; /* number of bytes allocated */ unsigned char *inbuf_ptr; int inbuf_len; /* number byte of actual data available. */ int inbuf_next; /* index of next to remove. */ int inbuf_bytes_per_frame; /* number of bytes for a sample from all channels. */ int inbuf_frames_per_buffer; /* number of frames in a buffer. */ pthread_mutex_t output_mutex; pthread_cond_t output_cond; PaStream *outStream; PaStreamParameters outputParameters; int pa_output_device_number; int no_of_output_channels; int output_pause; int output_finished; int output_flush; int output_wait_flag; void *audio_out_handle; int outbuf_size_in_bytes; unsigned char *outbuf_ptr; int outbuf_len; int outbuf_next; /* index of next to remove. */ int outbuf_bytes_per_frame; /* number of bytes for a sample from all channels. */ int outbuf_frames_per_buffer; /* number of frames in a buffer. */ enum audio_in_type_e g_audio_in_type; int udp_sock; /* UDP socket for receiving data */ } adev[MAX_ADEVS]; // Originally 40. Version 1.2, try 10 for lower latency. #define ONE_BUF_TIME 10 #define SAMPLE_SILENCE 0 #define PA_INPUT 1 #define PA_OUTPUT 2 #define roundup1k(n) (((n) + 0x3ff) & ~0x3ff) #undef FOR_FUTURE_USE static int set_portaudio_params (int a, struct adev_s *dev, struct audio_s *pa, char *devname, char *inout); static void print_pa_devices(void); static int check_pa_configure(struct adev_s *dev, int sample_rate); static void list_supported_sample_rates(struct adev_s *dev); static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo); static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag); static int calcbufsize(int rate, int chans, int bits); static int calcbufsize(int rate, int chans, int bits) { int size1 = (rate * chans * bits / 8 * ONE_BUF_TIME) / 1000; int size2 = roundup1k(size1); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n", rate, chans, bits, size1, size2); #endif return (size2); } /*------------------------------------------------------------------ * Search the portaudio device tree looking for the request device. * One of the issues with portaudio has to do with devices returning * the same device name for more then one connected device * (ie two SignaLinks). Appending a Portaudio device index to the * the device name ensure we can find the correct one. And if it's not * available return the first occurence that matches the device name. *----------------------------------------------------------------*/ static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag) { int numDevices = Pa_GetDeviceCount(); const PaDeviceInfo * di = (PaDeviceInfo *)0; int i = 0; // First check to see if the requested index matches the device name. if(reqDeviceNo < numDevices) { di = Pa_GetDeviceInfo((PaDeviceIndex) reqDeviceNo); if(strncmp(di->name, _devName, 80) == 0) { if((io_flag == PA_INPUT) && di->maxInputChannels) return reqDeviceNo; if((io_flag == PA_OUTPUT) && di->maxOutputChannels) return reqDeviceNo; } } // Requested device index doesn't match device name. Search for one. for(i = 0; i < numDevices; i++) { di = Pa_GetDeviceInfo((PaDeviceIndex) i); if(strncmp(di->name, _devName, 80) == 0) { if((io_flag == PA_INPUT) && di->maxInputChannels) return i; if((io_flag == PA_OUTPUT) && di->maxOutputChannels) return i; } } // No Matches found return -1; } /*------------------------------------------------------------------ * Extract device name and number. *----------------------------------------------------------------*/ static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo) { char *cPtr = (char *)0; char cVal = 0; int count = 0; char numStr[8]; if(!deviceStr || !_devName || !_devNo) { dw_printf( "Internal Error: Func %s passed null pointer.\n", __func__); return -1; } cPtr = deviceStr; memset(_devName, 0, length); memset(numStr, 0, sizeof(numStr)); while(*cPtr) { cVal = *cPtr++; if(cVal == ':') break; if(((cVal >= ' ') && (cVal <= '~')) && (count < length)) { _devName[count++] = cVal; } } count = 0; while(*cPtr) { cVal = *cPtr++; if(isdigit(cVal) && (count < (sizeof(numStr) - 1))) { numStr[count++] = cVal; } } if(numStr[0] == 0) { *_devNo = 0; } else { sscanf(numStr, "%d", _devNo); } return 0; } /*------------------------------------------------------------------ * List the supported sample rates. *----------------------------------------------------------------*/ static void list_supported_sample_rates(struct adev_s *dev) { static double standardSampleRates[] = { 8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0, 44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated list */ }; int i, printCount; PaError err; printCount = 0; for(i = 0; standardSampleRates[i] > 0; i++ ) { err = Pa_IsFormatSupported(&dev->inputParameters, &dev->outputParameters, standardSampleRates[i] ); if( err == paFormatIsSupported ) { if( printCount == 0 ) { dw_printf( "\t%8.2f", standardSampleRates[i] ); printCount = 1; } else if( printCount == 4 ) { dw_printf( ",\n\t%8.2f", standardSampleRates[i] ); printCount = 1; } else { dw_printf( ", %8.2f", standardSampleRates[i] ); ++printCount; } } } if( !printCount ) dw_printf( "None\n" ); else dw_printf( "\n" ); } /*------------------------------------------------------------------ * Check PA Configure parameters. *----------------------------------------------------------------*/ static int check_pa_configure(struct adev_s *dev, int sample_rate) { if(!dev) { dw_printf( "Internal Error: Func %s struct adev_s *dev null pointer.\n", __func__); return -1; } PaError err = 0; err = Pa_IsFormatSupported(&dev->inputParameters, &dev->outputParameters, sample_rate); if(err == paFormatIsSupported) return 0; dw_printf( "PortAudio Config Error: %s\n", Pa_GetErrorText(err)); return err; } /*------------------------------------------------------------------ * Print a list of device names and parameters *----------------------------------------------------------------*/ static void print_pa_devices(void) { int i, numDevices, defaultDisplayed; const PaDeviceInfo *deviceInfo; numDevices = Pa_GetDeviceCount(); if( numDevices < 0 ) { dw_printf( "ERROR: Pa_GetDeviceCount returned 0x%x\n", numDevices ); return; } dw_printf( "Number of devices = %d\n", numDevices ); for(i = 0; i < numDevices; i++ ) { deviceInfo = Pa_GetDeviceInfo( i ); dw_printf( "--------------------------------------- device #%d\n", i ); /* Mark global and API specific default devices */ defaultDisplayed = 0; if( i == Pa_GetDefaultInputDevice() ) { dw_printf( "[ Default Input" ); defaultDisplayed = 1; } else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultInputDevice ) { const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); dw_printf( "[ Default %s Input", hostInfo->name ); defaultDisplayed = 1; } if( i == Pa_GetDefaultOutputDevice() ) { dw_printf( (defaultDisplayed ? "," : "[") ); dw_printf( " Default Output" ); defaultDisplayed = 1; } else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultOutputDevice ) { const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); dw_printf( (defaultDisplayed ? "," : "[") ); dw_printf( " Default %s Output", hostInfo->name ); defaultDisplayed = 1; } if( defaultDisplayed ) dw_printf( " ]\n" ); /* print device info fields */ dw_printf( "Name = \"%s\"\n", deviceInfo->name ); dw_printf( "Host API = %s\n", Pa_GetHostApiInfo( deviceInfo->hostApi )->name ); dw_printf( "Max inputs = %d\n", deviceInfo->maxInputChannels ); dw_printf( "Max outputs = %d\n", deviceInfo->maxOutputChannels ); } } /*------------------------------------------------------------------ * Port Audio Input Callback *----------------------------------------------------------------*/ static int paInput16CB( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ) { struct adev_s *data = (struct adev_s *) userData; const int16_t *rptr = (const int16_t *) inputBuffer; size_t framesToCalc = 0; size_t i = 0; int finished = 0; int word = 0; size_t bytes_left = data->inbuf_size_in_bytes - data->inbuf_len; size_t framesLeft = bytes_left / data->inbuf_bytes_per_frame; (void) outputBuffer; /* Prevent unused variable warnings. */ (void) timeInfo; (void) statusFlags; (void) userData; if( framesLeft < framesPerBuffer ) { framesToCalc = framesLeft; finished = paComplete; } else { framesToCalc = framesPerBuffer; finished = paContinue; } if( inputBuffer == NULL || data->input_flush) { for(i = 0; i < framesToCalc; i++) { data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE; data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE; if(data->no_of_input_channels == 2) { data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE; data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE; } } } else { for(i = 0; i < framesToCalc; i++) { word = *rptr++; /* left */ data->inbuf_ptr[data->inbuf_len++] = word & 0xff; data->inbuf_ptr[data->inbuf_len++] = (word >> 8) & 0xff; if(data->no_of_input_channels == 2) { word = *rptr++; /* right */ data->inbuf_ptr[data->inbuf_len++] = word & 0xff; data->inbuf_ptr[data->inbuf_len++] = (word >> 8) & 0xff; } } } if((finished == paComplete) || (data->inbuf_len >= data->inbuf_size_in_bytes)) { pthread_cond_signal(&data->input_cond); finished = data->input_finished; } return finished; } #if FOR_FUTURE_USE /*------------------------------------------------------------------ * Port Audio Output Callback *----------------------------------------------------------------*/ static int paOutput16CB( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { struct adev_s *data = (struct adev_s *) userData; int16_t *wptr = (int16_t *) outputBuffer; size_t i = 0; int finished = 0; size_t bytes_left = data->outbuf_size_in_bytes - data->outbuf_len; size_t framesLeft = bytes_left / data->outbuf_bytes_per_frame; int word = 0; (void) inputBuffer; /* Prevent unused variable warnings. */ (void) timeInfo; (void) statusFlags; (void) userData; if(framesLeft && (framesLeft < framesPerBuffer)) { /* final buffer... */ for(i = 0; i < framesLeft; i++ ) { word = data->outbuf_ptr[data->outbuf_len++] & 0xff; word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff; *wptr++ = word; /* left */ if(data->no_of_output_channels == 2 ) { word = data->outbuf_ptr[data->outbuf_len++] & 0xff; word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff; *wptr++ = word; /* right */ } } for( ; i < framesPerBuffer; i++ ) { *wptr++ = 0; /* left */ if(data->no_of_output_channels == 2 ) *wptr++ = 0; /* right */ } finished = paContinue; } else { for(i = 0; i < framesPerBuffer; i++ ) { word = data->outbuf_ptr[data->outbuf_len++] & 0xff; word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff; *wptr++ = word; /* left */ if(data->no_of_output_channels == 2) { word = data->outbuf_ptr[data->outbuf_len++] & 0xff; word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff; *wptr++ = word; /* right */ } } finished = paComplete; } if(data->output_flush) { data->output_flush = 0; finished = paComplete; } pthread_cond_signal(&data->output_cond); finished = data->output_finished; return finished; } #endif /*------------------------------------------------------------------ * * Name: audio_open * * Purpose: Open the digital audio device. * * New in version 1.0, we recognize "udp:" optionally * followed by a port number. * * Inputs: pa - Address of structure of type audio_s. * * Using a structure, rather than separate arguments * seemed to make sense because we often pass around * the same set of parameters various places. * * The fields that we care about are: * num_channels * samples_per_sec * bits_per_sample * If zero, reasonable defaults will be provided. * * The device names are in adevice_in and adevice_out. * where c is the "card" (for historical purposes) * and d is the "device" within the "card." * * * Outputs: pa - The ACTUAL values are returned here. * * These might not be exactly the same as what was requested. * * Example: ask for stereo, 16 bits, 22050 per second. * An ordinary desktop/laptop PC should be able to handle this. * However, some other sort of smaller device might be * more restrictive in its capabilities. * It might say, the best I can do is mono, 8 bit, 8000/sec. * * The sofware modem must use this ACTUAL information * that the device is supplying, that could be different * than what the user specified. * * Returns: 0 for success, -1 for failure. * * *----------------------------------------------------------------*/ int audio_open (struct audio_s *pa) { int err = 0; int chan = 0; int a = 0; int clear_value = 0; char audio_in_name[80]; char audio_out_name[80]; static int initalize_flag = 0; PaError paerr = paNoError; if(!initalize_flag) { paerr = Pa_Initialize(); initalize_flag = -1; } if(paerr != paNoError ) return -1; save_audio_config_p = pa; memset (adev, 0, sizeof(adev)); memset (audio_in_name, 0, sizeof(audio_in_name)); memset (audio_out_name, 0, sizeof(audio_out_name)); for (a = 0; a < MAX_ADEVS; a++) { adev[a].udp_sock = -1; } /* * Fill in defaults for any missing values. */ for (a = 0; a < MAX_ADEVS; a++) { if (pa->adev[a].num_channels == 0) pa->adev[a].num_channels = DEFAULT_NUM_CHANNELS; if (pa->adev[a].samples_per_sec == 0) pa->adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; if (pa->adev[a].bits_per_sample == 0) pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; for (chan = 0; chan < MAX_CHANS; chan++) { if (pa->achan[chan].mark_freq == 0) pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ; if (pa->achan[chan].space_freq == 0) pa->achan[chan].space_freq = DEFAULT_SPACE_FREQ; if (pa->achan[chan].baud == 0) pa->achan[chan].baud = DEFAULT_BAUD; if (pa->achan[chan].num_subchan == 0) pa->achan[chan].num_subchan = 1; } } /* * Open audio device(s). */ for (a = 0; a < MAX_ADEVS; a++) { if (pa->adev[a].defined) { adev[a].inbuf_size_in_bytes = 0; adev[a].outbuf_size_in_bytes = 0; /* * Determine the type of audio input. */ adev[a].g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) { adev[a].g_audio_in_type = AUDIO_IN_TYPE_STDIN; /* Change "-" to stdin for readability. */ strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in)); } if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) { adev[a].g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP; /* Supply default port if none specified. */ if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 || strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) { snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT); } } /* Let user know what is going on. */ /* If not specified, the device names should be "default". */ strlcpy (audio_in_name, pa->adev[a].adevice_in, sizeof(audio_in_name)); strlcpy (audio_out_name, pa->adev[a].adevice_out, sizeof(audio_out_name)); char ctemp[40]; if (pa->adev[a].num_channels == 2) { snprintf (ctemp, sizeof(ctemp), " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1); } else { snprintf (ctemp, sizeof(ctemp), " (channel %d)", ADEVFIRSTCHAN(a)); } text_color_set(DW_COLOR_INFO); if (strcmp(audio_in_name,audio_out_name) == 0) { dw_printf ("Audio device for both receive and transmit: %s %s\n", audio_in_name, ctemp); } else { dw_printf ("Audio input device for receive: %s %s\n", audio_in_name, ctemp); dw_printf ("Audio out device for transmit: %s %s\n", audio_out_name, ctemp); } /* * Now attempt actual opens. */ /* * Input device. */ switch (adev[a].g_audio_in_type) { case AUDIO_IN_TYPE_SOUNDCARD: print_pa_devices(); err = set_portaudio_params (a, &adev[a], pa, audio_in_name, audio_out_name); if(err < 0) return -1; pthread_mutex_init(&adev[a].input_mutex, NULL); pthread_cond_init(&adev[a].input_cond, NULL); pthread_mutex_init(&adev[a].output_mutex, NULL); pthread_cond_init(&adev[a].output_cond, NULL); if(pa->adev[a].bits_per_sample == 8) clear_value = 128; else clear_value = 0; break; /* * UDP. */ case AUDIO_IN_TYPE_SDR_UDP: // Create socket and bind socket { struct sockaddr_in si_me; //Create UDP Socket if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't create socket, errno %d\n", errno); return -1; } memset((char *) &si_me, 0, sizeof(si_me)); si_me.sin_family = AF_INET; si_me.sin_port = htons((short)atoi(audio_in_name+4)); si_me.sin_addr.s_addr = htonl(INADDR_ANY); //Bind to the socket if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't bind socket, errno %d\n", errno); return -1; } } //adev[a].inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN; break; /* * stdin. */ case AUDIO_IN_TYPE_STDIN: /* Do we need to adjust any properties of stdin? */ //adev[a].inbuf_size_in_bytes = 1024; break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error, invalid audio_in_type\n"); return (-1); } /* * Finally allocate buffer for each direction. */ /* Version 1.3 - Add sanity check on buffer size. */ /* There was a reported case of assert failure on buffer size in audio_get(). */ if (adev[a].inbuf_size_in_bytes < 256 || adev[a].inbuf_size_in_bytes > 32768) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio input buffer has unexpected extreme size of %d bytes.\n", adev[a].inbuf_size_in_bytes); dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__); dw_printf ("This might be caused by unusual audio device configuration values.\n"); adev[a].inbuf_size_in_bytes = 2048; dw_printf ("Using %d to attempt recovery.\n", adev[a].inbuf_size_in_bytes); } if (adev[a].outbuf_size_in_bytes < 256 || adev[a].outbuf_size_in_bytes > 32768) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio output buffer has unexpected extreme size of %d bytes.\n", adev[a].outbuf_size_in_bytes); dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__); dw_printf ("This might be caused by unusual audio device configuration values.\n"); adev[a].outbuf_size_in_bytes = 2048; dw_printf ("Using %d to attempt recovery.\n", adev[a].outbuf_size_in_bytes); } adev[a].inbuf_ptr = malloc(adev[a].inbuf_size_in_bytes); assert (adev[a].inbuf_ptr != NULL); adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; memset(adev[a].inbuf_ptr, clear_value, adev[a].inbuf_size_in_bytes); adev[a].outbuf_ptr = malloc(adev[a].outbuf_size_in_bytes); assert (adev[a].outbuf_ptr != NULL); adev[a].outbuf_len = 0; adev[a].outbuf_next = 0; memset(adev[a].outbuf_ptr, clear_value, adev[a].outbuf_size_in_bytes); if(adev[a].inStream) { err = Pa_StartStream(adev[a].inStream); if(err != paNoError) { dw_printf ("Input stream start Error %s\n", Pa_GetErrorText(err)); } } if(adev[a].outStream) { err = Pa_StartStream(adev[a].outStream); if(err != paNoError) { dw_printf ("Output stream start Error %s\n", Pa_GetErrorText(err)); } } } /* end of audio device defined */ } /* end of for each audio device */ return (0); } /* end audio_open */ /* * Set parameters for sound card. * * See ?? for details. */ static int set_portaudio_params (int a, struct adev_s *dev, struct audio_s *pa, char *_audio_in_name, char *_audio_out_name) { int numDevices = 0; int err = 0; int buffer_size = 0; int sampleFormat = 0; int no_of_bytes_per_sample = 0; int reqInDeviceNo = 0; int reqOutDeviceNo = 0; char input_devName[80]; char output_devName[80]; text_color_set(DW_COLOR_ERROR); if(!dev || !pa || !_audio_in_name || !_audio_out_name) { dw_printf ("Internal error, invalid function parameter pointer(s) (null)\n"); return -1; } if(_audio_in_name[0] == 0) { dw_printf ("Input device name null\n"); return -1; } if(_audio_out_name[0] == 0) { dw_printf ("Output device name null\n"); return -1; } numDevices = Pa_GetDeviceCount(); if( numDevices < 0 ) { dw_printf( "ERROR: Pa_GetDeviceCount returned 0x%x\n", numDevices ); return -1; } err = pa_devNN(_audio_in_name, input_devName, sizeof(input_devName), &reqInDeviceNo); if(err < 0) return -1; reqInDeviceNo = searchPADevice(dev, input_devName, reqInDeviceNo, PA_INPUT); if(reqInDeviceNo < 0) { dw_printf ("Requested Input Audio Device not found %s.\n", input_devName); return -1; } err = pa_devNN(_audio_out_name, output_devName, sizeof(output_devName), &reqOutDeviceNo); if(err < 0) return -1; reqOutDeviceNo = searchPADevice(dev, output_devName, reqOutDeviceNo, PA_OUTPUT); if(reqOutDeviceNo < 0) { dw_printf ("Requested Output Audio Device not found %s.\n", output_devName); return -1; } dev->pa_input_device_number = reqInDeviceNo; dev->pa_output_device_number = reqOutDeviceNo; switch(pa->adev[a].bits_per_sample) { case 8: sampleFormat = paInt8; no_of_bytes_per_sample = sizeof(int8_t); assert("int8_t size not equal to 1" && sizeof(int8_t) == 1); break; case 16: sampleFormat = paInt16; no_of_bytes_per_sample = sizeof(int16_t); assert("int16_t size not equal to 2" && sizeof(int16_t) == 2); break; default: dw_printf ("Unsupported Sample Size %s.\n", output_devName); return -1; } buffer_size = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample); dev->inbuf_size_in_bytes = buffer_size; dev->inbuf_bytes_per_frame = no_of_bytes_per_sample * pa->adev[a].num_channels; dev->inbuf_frames_per_buffer = dev->inbuf_size_in_bytes / dev->inbuf_bytes_per_frame; dev->inputParameters.device = dev->pa_input_device_number; dev->inputParameters.channelCount = pa->adev[a].num_channels; dev->inputParameters.sampleFormat = sampleFormat; dev->inputParameters.suggestedLatency = Pa_GetDeviceInfo(dev->inputParameters.device)->defaultLowInputLatency; dev->inputParameters.hostApiSpecificStreamInfo = NULL; dev->outbuf_size_in_bytes = buffer_size; dev->outbuf_bytes_per_frame = no_of_bytes_per_sample * pa->adev[a].num_channels; dev->outbuf_frames_per_buffer = dev->outbuf_size_in_bytes / dev->outbuf_bytes_per_frame; dev->outputParameters.device = dev->pa_output_device_number; dev->outputParameters.channelCount = pa->adev[a].num_channels; dev->outputParameters.sampleFormat = sampleFormat; dev->outputParameters.suggestedLatency = Pa_GetDeviceInfo(dev->outputParameters.device)->defaultHighOutputLatency; dev->outputParameters.hostApiSpecificStreamInfo = NULL; err = check_pa_configure(dev, pa->adev[a].samples_per_sec); if(err) { if(err == paInvalidSampleRate) list_supported_sample_rates(dev); return -1; } err = Pa_OpenStream(&dev->inStream, &dev->inputParameters, NULL, pa->adev[a].samples_per_sec, dev->inbuf_frames_per_buffer, 0, paInput16CB, dev ); if( err != paNoError ) { dw_printf( "PortAudio OpenStream (input) Error: %s\n", Pa_GetErrorText(err)); return -1; } err = Pa_OpenStream(&dev->outStream, NULL, &dev->outputParameters, // pa->adev[a].samples_per_sec, framesPerBuffer, 0, paOutput16CB, dev ); pa->adev[a].samples_per_sec, dev->outbuf_frames_per_buffer, 0, NULL, dev ); if( err != paNoError ) { dw_printf( "PortAudio OpenStream (output) Error: %s\n", Pa_GetErrorText(err)); return -1; } dev->input_finished = paContinue; dev->output_finished = paContinue; return buffer_size; } /*------------------------------------------------------------------ * * Name: audio_get * * Purpose: Get one byte from the audio device. * * Inputs: a - Our number for audio device. * * Returns: 0 - 255 for a valid sample. * -1 for any type of error. * * Description: The caller must deal with the details of mono/stereo * and number of bytes per sample. * * This will wait if no data is currently available. * *----------------------------------------------------------------*/ // Use hot attribute for all functions called for every audio sample. __attribute__((hot)) int audio_get (int a) { int n; int retries = 0; #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_get():\n"); #endif assert (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768); switch (adev[a].g_audio_in_type) { /* * Soundcard - PortAudio */ case AUDIO_IN_TYPE_SOUNDCARD: while (adev[a].inbuf_next >= adev[a].inbuf_len) { assert (adev[a].inStream != NULL); #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_get(): readi asking for %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame); #endif if(adev[a].inbuf_len >= adev[a].inbuf_size_in_bytes) { adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; } pthread_mutex_lock(&adev[a].input_mutex); pthread_cond_wait(&adev[a].input_cond, &adev[a].input_mutex); pthread_mutex_unlock(&adev[a].input_mutex); n = adev[a].inbuf_len / adev[a].inbuf_bytes_per_frame; #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_get(): readi asked for %d and got %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n); #endif if (n > 0) { /* Success */ adev[a].inbuf_len = n * adev[a].inbuf_bytes_per_frame; /* convert to number of bytes */ adev[a].inbuf_next = 0; audio_stats (a, save_audio_config_p->adev[a].num_channels, n, save_audio_config_p->statistics_interval); } else if (n == 0) { /* Didn't expect this, but it's not a problem. */ /* Wait a little while and try again. */ text_color_set(DW_COLOR_ERROR); dw_printf ("[%s], Audio input got zero bytes\n", __func__); SLEEP_MS(10); adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; } else { /* Error */ // TODO: Needs more study and testing. // TODO: print n. should snd_strerror use n or errno? // Audio input device error: Unknown error text_color_set(DW_COLOR_ERROR); dw_printf ("Audio input device %d error\n", a); audio_stats (a, save_audio_config_p->adev[a].num_channels, 0, save_audio_config_p->statistics_interval); /* Try to recover a few times and eventually give up. */ if (++retries > 10) { adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; return (-1); } if (n == -EPIPE) { /* EPIPE means overrun */ //snd_pcm_recover (adev[a].audio_in_handle, n, 1); } else { /* Could be some temporary condition. */ /* Wait a little then try again. */ /* Sometimes I get "Resource temporarily available" */ /* when the Update Manager decides to run. */ SLEEP_MS (250); //snd_pcm_recover (adev[a].audio_in_handle, n, 1); } } } break; /* * UDP. */ case AUDIO_IN_TYPE_SDR_UDP: while (adev[a].inbuf_next >= adev[a].inbuf_len) { int res; assert (adev[a].udp_sock > 0); res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0); if (res < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't read from udp socket, res=%d", res); adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; audio_stats (a, save_audio_config_p->adev[a].num_channels, 0, save_audio_config_p->statistics_interval); return (-1); } adev[a].inbuf_len = res; adev[a].inbuf_next = 0; audio_stats (a, save_audio_config_p->adev[a].num_channels, res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), save_audio_config_p->statistics_interval); } break; /* * stdin. */ case AUDIO_IN_TYPE_STDIN: while (adev[a].inbuf_next >= adev[a].inbuf_len) { int res; res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes); if (res <= 0) { text_color_set(DW_COLOR_INFO); dw_printf ("\nEnd of file on stdin. Exiting.\n"); exit (0); } audio_stats (a, save_audio_config_p->adev[a].num_channels, res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), save_audio_config_p->statistics_interval); adev[a].inbuf_len = res; adev[a].inbuf_next = 0; } break; } if (adev[a].inbuf_next < adev[a].inbuf_len) n = adev[a].inbuf_ptr[adev[a].inbuf_next++]; else n = 0; #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_get(): returns %d\n", n); #endif return (n); } /* end audio_get */ /*------------------------------------------------------------------ * * Name: audio_put * * Purpose: Send one byte to the audio device. * * Inputs: a * * c - One byte in range of 0 - 255. * * Returns: Normally non-negative. * -1 for any type of error. * * Description: The caller must deal with the details of mono/stereo * and number of bytes per sample. * * See Also: audio_flush * audio_wait * *----------------------------------------------------------------*/ int audio_put (int a, int c) { int err = 0; size_t frames = 0; //#define __TIMED__ #ifdef __TIMED__ static int count = 0; static double start = 0, end = 0, diff = 0; if(adev[a].outbuf_len == 0) start = dtime_now(); #endif if(c >= 0) { adev[a].outbuf_ptr[adev[a].outbuf_len++] = c; } if ((adev[a].outbuf_len >= adev[a].outbuf_size_in_bytes) || (c < 0)) { frames = adev[a].outbuf_len / adev[a].outbuf_bytes_per_frame; if(frames > 0) { err = Pa_WriteStream(adev[a].outStream, adev[a].outbuf_ptr, frames); } // Getting underflow error for some reason on the first pass. Upon examination of the // audio data revealed no discontinuity in the signal. Time measurements indicate this routine // on this machine (2.8Ghz/Xeon E5462/2008 vintage) can handle ~6 times the current // sample rate (44100/2 bytes per frame). For now, mask the error. // Transfer Time:0.184750080 No of Frames:56264 Per frame:0.000003284 speed:6.905695 if ((err != paNoError) && (err != paOutputUnderflowed)) { text_color_set(DW_COLOR_ERROR); dw_printf ("[%s] Audio Output Error: %s\n", __func__, Pa_GetErrorText(err)); } #ifdef __TIMED__ count += frames; if(c < 0) { // When the Ax25 frames are flushed. end = dtime_now(); diff = end - start; if(count) dw_printf ("Transfer Time:%3.9f No of Frames:%d Per frame:%3.9f speed:%f\n", diff, count, diff/(count * 1.0), (1.0/44100.0)/(diff/(count * 1.0))); count = 0; } #endif adev[a].outbuf_len = 0; adev[a].outbuf_next = 0; } return (0); } /*------------------------------------------------------------------ * * Name: audio_flush * * Purpose: Push out any partially filled output buffer. * * Returns: Normally non-negative. * -1 for any type of error. * * See Also: audio_flush * audio_wait * *----------------------------------------------------------------*/ int audio_flush (int a) { audio_put(a, -1); return 0; } /* end audio_flush */ /*------------------------------------------------------------------ * * Name: audio_wait * * Purpose: Finish up audio output before turning PTT off. * * Inputs: a - Index for audio device (not channel!) * * Returns: None. * * Description: Flush out any partially filled audio output buffer. * Wait until all the queued up audio out has been played. * Take any other necessary actions to stop audio output. * * In an ideal world: * * We would like to ask the hardware when all the queued * up sound has actually come out the speaker. * * In reality: * * This has been found to be less than reliable in practice. * * Caller does the following: * * (1) Make note of when PTT is turned on. * (2) Calculate how long it will take to transmit the * frame including TXDELAY, frame (including * "flags", data, FCS and bit stuffing), and TXTAIL. * (3) Call this function, which might or might not wait long enough. * (4) Add (1) and (2) resulting in when PTT should be turned off. * (5) Take difference between current time and desired PPT off time * and wait for additoinal time if required. * *----------------------------------------------------------------*/ void audio_wait (int a) { audio_flush(a); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_wait(): after sync, status=%d\n", err); #endif } /* end audio_wait */ /*------------------------------------------------------------------ * * Name: audio_close * * Purpose: Close the audio device(s). * * Returns: Normally non-negative. * -1 for any type of error. * * *----------------------------------------------------------------*/ int audio_close (void) { int err = 0; int a; for (a = 0; a < MAX_ADEVS; a++) { if(adev[a].g_audio_in_type == AUDIO_IN_TYPE_SOUNDCARD) { audio_wait (a); if (adev[a].inStream != NULL) { pthread_mutex_destroy(&adev[a].input_mutex); pthread_cond_destroy(&adev[a].input_cond); err |= (int) Pa_CloseStream(adev[a].inStream); } if(adev[a].outStream != NULL) { pthread_mutex_destroy(&adev[a].output_mutex); pthread_cond_destroy(&adev[a].output_cond); err |= (int) Pa_CloseStream(adev[a].outStream); } err |= (int) Pa_Terminate(); } if(adev[a].inbuf_ptr) free (adev[a].inbuf_ptr); if(adev[a].outbuf_ptr) free (adev[a].outbuf_ptr); adev[a].inbuf_size_in_bytes = 0; adev[a].inbuf_ptr = NULL; adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; adev[a].outbuf_size_in_bytes = 0; adev[a].outbuf_ptr = NULL; adev[a].outbuf_len = 0; adev[a].outbuf_next = 0; } if(err < 0) err = -1; return (err); } /* end audio_close */ /* end audio_portaudio.c */ #endif // USE_PORTAUDIO direwolf-1.5+dfsg/audio_stats.c000066400000000000000000000124621347750676600166260ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: audio_stats.c * * Purpose: Print statistics for audio input stream. * * A common complaint is that there is no indication of * audio input level until a packet is received correctly. * That's true for the Windows version but the Linux version * prints something like this each 100 seconds: * * ADEVICE0: Sample rate approx. 44.1 k, 0 errors, receive audio level CH0 73 * * Some complain about the clutter but it has been a useful * troubleshooting tool. In the earlier RPi days, the sample * rate was quite low due to a device driver issue. * Using a USB hub on the RPi also caused audio problems. * One adapter, that I tried, produces samples at the * right rate but all the samples are 0. * * Here we pull the code out of the Linux version of audio.c * so we have a common function for all the platforms. * * We also add a command line option to adjust the time * between reports or turn them off entirely. * * Revisions: This is new in version 1.3. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #include "audio_stats.h" #include "textcolor.h" #include "dtime_now.h" #include "demod.h" /* for alevel_t & demod_get_audio_level() */ /*------------------------------------------------------------------ * * Name: audio_stats * * Purpose: Add sample count from one buffer to the statistics. * Print if specified amount of time has passed. * * Inputs: adev - Audio device number: 0, 1, ..., MAX_ADEVS-1 * nchan - Number of channels for this device, 1 or 2. * * nsamp - How many audio samples were read. * * interval - How many seconds between reports. * 0 to turn off. * * Returns: none * * Description: ... * *----------------------------------------------------------------*/ void audio_stats (int adev, int nchan, int nsamp, int interval) { /* Gather numbers for read from audio device. */ static time_t last_time[MAX_ADEVS] = { 0, 0, 0 }; time_t this_time[MAX_ADEVS]; static int sample_count[MAX_ADEVS]; static int error_count[MAX_ADEVS]; static int suppress_first[MAX_ADEVS]; if (interval <= 0) { return; } assert (adev >= 0 && adev < MAX_ADEVS); /* * Print information about the sample rate as a troubleshooting aid. * I've never seen an issue with Windows or x86 Linux but the Raspberry Pi * has a very troublesome audio input system where many samples got lost. * * While we are at it we can also print the current audio level(s) providing * more clues if nothing is being decoded. */ if (last_time[adev] == 0) { last_time[adev] = time(NULL); sample_count[adev] = 0; error_count[adev] = 0; suppress_first[adev] = 1; /* suppressing the first one could mean a rather */ /* long wait for the first message. We make the */ /* first collection interval 3 seconds. */ last_time[adev] -= (interval - 3); } else { if (nsamp > 0) { sample_count[adev] += nsamp; } else { error_count[adev]++; } this_time[adev] = time(NULL); if (this_time[adev] >= last_time[adev] + interval) { if (suppress_first[adev]) { /* The issue we had is that the first time the rate */ /* would be off considerably because we didn't start */ /* on a second boundary. So we will suppress printing */ /* of the first one. */ suppress_first[adev] = 0; } else { float ave_rate = (sample_count[adev] / 1000.0) / interval; text_color_set(DW_COLOR_DEBUG); if (nchan > 1) { int ch0 = ADEVFIRSTCHAN(adev); alevel_t alevel0 = demod_get_audio_level(ch0,0); int ch1 = ADEVFIRSTCHAN(adev) + 1; alevel_t alevel1 = demod_get_audio_level(ch1,0); dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio levels CH%d %d, CH%d %d\n\n", adev, ave_rate, error_count[adev], ch0, alevel0.rec, ch1, alevel1.rec); } else { int ch0 = ADEVFIRSTCHAN(adev); alevel_t alevel0 = demod_get_audio_level(ch0,0); dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio level CH%d %d\n\n", adev, ave_rate, error_count[adev], ch0, alevel0.rec); } } last_time[adev] = this_time[adev]; sample_count[adev] = 0; error_count[adev] = 0; } } } /* end audio_stats.c */ direwolf-1.5+dfsg/audio_stats.h000066400000000000000000000001411347750676600166220ustar00rootroot00000000000000 /* audio_stats.h */ extern void audio_stats (int adev, int nchan, int nsamp, int interval); direwolf-1.5+dfsg/audio_win.c000066400000000000000000000767101347750676600162730ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: audio_win.c * * Purpose: Interface to audio device commonly called a "sound card" for * historical reasons. * * This version uses the native Windows sound interface. * * Credits: Fabrice FAURE contributed Linux code for the SDR UDP interface. * * Discussion here: http://gqrx.dk/doc/streaming-audio-over-udp * * Major revisions: * * 1.2 - Add ability to use more than one audio device. * *---------------------------------------------------------------*/ #include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h // Also includes windows.h. #include #include #include #include #include #include #include #include #include #ifndef WAVE_FORMAT_96M16 #define WAVE_FORMAT_96M16 0x40000 #define WAVE_FORMAT_96S16 0x80000 #endif #include #include // _WIN32_WINNT must be set to 0x0501 before including this #include "audio.h" #include "audio_stats.h" #include "textcolor.h" #include "ptt.h" #include "demod.h" /* for alevel_t & demod_get_audio_level() */ /* Audio configuration. */ static struct audio_s *save_audio_config_p; /* * Allocate enough buffers for 1 second each direction. * Each buffer size is a trade off between being responsive * to activity on the channel vs. overhead of having too * many little transfers. */ /* * Originally, we had an abitrary buf time of 40 mS. * * For mono, the buffer size was rounded up from 3528 to 4k so * it was really about 50 mS per buffer or about 20 per second. * For stereo, the buffer size was rounded up from 7056 to 7k so * it was really about 43.7 mS per buffer or about 23 per second. * * In version 1.2, let's try changing it to 10 to reduce the latency. * For mono, the buffer size was rounded up from 882 to 1k so it * was really about 12.5 mS per buffer or about 80 per second. */ #define TOTAL_BUF_TIME 1000 #define ONE_BUF_TIME 10 #define NUM_IN_BUF ((TOTAL_BUF_TIME)/(ONE_BUF_TIME)) #define NUM_OUT_BUF ((TOTAL_BUF_TIME)/(ONE_BUF_TIME)) #define roundup1k(n) (((n) + 0x3ff) & ~0x3ff) static int calcbufsize(int rate, int chans, int bits) { int size1 = (rate * chans * bits / 8 * ONE_BUF_TIME) / 1000; int size2 = roundup1k(size1); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n", rate, chans, bits, size1, size2); #endif /* Version 1.3 - add a sanity check. */ if (size2 < 256 || size2 > 32768) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio buffer has unexpected extreme size of %d bytes.\n", size2); dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__); dw_printf ("This might be caused by unusual audio device configuration values.\n"); size2 = 2048; dw_printf ("Using %d to attempt recovery.\n", size2); } return (size2); } /* Information for each audio stream (soundcard, stdin, or UDP) */ static struct adev_s { enum audio_in_type_e g_audio_in_type; /* * UDP socket for receiving audio stream. * Buffer, length, and pointer for UDP or stdin. */ SOCKET udp_sock; char stream_data[SDR_UDP_BUF_MAXLEN]; int stream_len; int stream_next; /* For sound output. */ /* out_wavehdr.dwUser is used to keep track of output buffer state. */ #define DWU_FILLING 1 /* Ready to use or in process of being filled. */ #define DWU_PLAYING 2 /* Was given to sound system for playing. */ #define DWU_DONE 3 /* Sound system is done with it. */ HWAVEOUT audio_out_handle; volatile WAVEHDR out_wavehdr[NUM_OUT_BUF]; int out_current; /* index to above. */ int outbuf_size; /* For sound input. */ /* In this case dwUser is index of next available byte to remove. */ HWAVEIN audio_in_handle; WAVEHDR in_wavehdr[NUM_IN_BUF]; volatile WAVEHDR *in_headp; /* head of queue to process. */ CRITICAL_SECTION in_cs; } adev[MAX_ADEVS]; /*------------------------------------------------------------------ * * Name: audio_open * * Purpose: Open the digital audio device. * * New in version 1.0, we recognize "udp:" optionally * followed by a port number. * * Inputs: pa - Address of structure of type audio_s. * * Using a structure, rather than separate arguments * seemed to make sense because we often pass around * the same set of parameters various places. * * The fields that we care about are: * num_channels * samples_per_sec * bits_per_sample * If zero, reasonable defaults will be provided. * * Outputs: pa - The ACTUAL values are returned here. * * The Linux version adjusts strange values to the * nearest valid value. Don't know, yet, if Windows * does the same or just fails. Or performs some * expensive resampling from a rate supported by * hardware. * * These might not be exactly the same as what was requested. * * Example: ask for stereo, 16 bits, 22050 per second. * An ordinary desktop/laptop PC should be able to handle this. * However, some other sort of smaller device might be * more restrictive in its capabilities. * It might say, the best I can do is mono, 8 bit, 8000/sec. * * The sofware modem must use this ACTUAL information * that the device is supplying, that could be different * than what the user specified. * * Returns: 0 for success, -1 for failure. * * References: Multimedia Reference * * http://msdn.microsoft.com/en-us/library/windows/desktop/dd743606%28v=vs.85%29.aspx * *----------------------------------------------------------------*/ static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWORD param1, DWORD param2); static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DWORD param1, DWORD param2); int audio_open (struct audio_s *pa) { int a; int err; int chan; int n; int in_dev_no[MAX_ADEVS]; int out_dev_no[MAX_ADEVS]; int num_devices; WAVEINCAPS wic; WAVEOUTCAPS woc; save_audio_config_p = pa; for (a=0; aadev[a].defined) { struct adev_s *A = &(adev[a]); assert (A->audio_in_handle == 0); assert (A->audio_out_handle == 0); //text_color_set(DW_COLOR_DEBUG); //dw_printf ("pa->adev[a].adevice_in = '%s'\n", pa->adev[a].adevice_in); //dw_printf ("pa->adev[a].adevice_out = '%s'\n", pa->adev[a].adevice_out); /* * Fill in defaults for any missing values. */ if (pa -> adev[a].num_channels == 0) pa -> adev[a].num_channels = DEFAULT_NUM_CHANNELS; if (pa -> adev[a].samples_per_sec == 0) pa -> adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; if (pa -> adev[a].bits_per_sample == 0) pa -> adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; A->g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; for (chan=0; chan achan[chan].mark_freq == 0) pa -> achan[chan].mark_freq = DEFAULT_MARK_FREQ; if (pa -> achan[chan].space_freq == 0) pa -> achan[chan].space_freq = DEFAULT_SPACE_FREQ; if (pa -> achan[chan].baud == 0) pa -> achan[chan].baud = DEFAULT_BAUD; if (pa->achan[chan].num_subchan == 0) pa->achan[chan].num_subchan = 1; } A->udp_sock = INVALID_SOCKET; in_dev_no[a] = WAVE_MAPPER; /* = ((UINT)-1) in mmsystem.h */ out_dev_no[a] = WAVE_MAPPER; /* * Determine the type of audio input and select device. * This can be soundcard, UDP stream, or stdin. */ if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) { A->g_audio_in_type = AUDIO_IN_TYPE_STDIN; /* Change - to stdin for readability. */ strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in)); } else if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) { A->g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP; /* Supply default port if none specified. */ if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 || strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) { snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT); } } else { A->g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; /* Does config file have a number? */ /* If so, it is an index into list of devices. */ /* Originally only a single digit was recognized. */ /* v 1.5 also recognizes two digits. (Issue 116) */ if (strlen(pa->adev[a].adevice_in) == 1 && isdigit(pa->adev[a].adevice_in[0])) { in_dev_no[a] = atoi(pa->adev[a].adevice_in); } else if (strlen(pa->adev[a].adevice_in) == 2 && isdigit(pa->adev[a].adevice_in[0]) && isdigit(pa->adev[a].adevice_in[1])) { in_dev_no[a] = atoi(pa->adev[a].adevice_in); } /* Otherwise, does it have search string? */ if ((UINT)(in_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_in) >= 1) { num_devices = waveInGetNumDevs(); for (n=0 ; nadev[a].adevice_in) != NULL) { in_dev_no[a] = n; } } } if ((UINT)(in_dev_no[a]) == WAVE_MAPPER) { text_color_set(DW_COLOR_ERROR); dw_printf ("\"%s\" doesn't match any of the input devices.\n", pa->adev[a].adevice_in); } } } /* * Select output device. * Only soundcard at this point. * Purhaps we'd like to add UDP for an SDR transmitter. */ if (strlen(pa->adev[a].adevice_out) == 1 && isdigit(pa->adev[a].adevice_out[0])) { out_dev_no[a] = atoi(pa->adev[a].adevice_out); } else if (strlen(pa->adev[a].adevice_out) == 2 && isdigit(pa->adev[a].adevice_out[0]) && isdigit(pa->adev[a].adevice_out[1])) { out_dev_no[a] = atoi(pa->adev[a].adevice_out); } if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) { num_devices = waveOutGetNumDevs(); for (n=0 ; nadev[a].adevice_out) != NULL) { out_dev_no[a] = n; } } } if ((UINT)(out_dev_no[a]) == WAVE_MAPPER) { text_color_set(DW_COLOR_ERROR); dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out); } } } /* if defined */ } /* for each device */ /* * Display the input devices (soundcards) available and what is selected. */ text_color_set(DW_COLOR_INFO); dw_printf ("Available audio input devices for receive (*=selected):\n"); num_devices = waveInGetNumDevs(); for (a=0; aadev[a].defined) { if (in_dev_no[a] < -1 || in_dev_no[a] >= num_devices) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid input (receive) audio device number %d.\n", in_dev_no[a]); in_dev_no[a] = WAVE_MAPPER; } } } text_color_set(DW_COLOR_INFO); for (n=0; nadev[a].defined) { dw_printf (" %c", n==in_dev_no[a] ? '*' : ' '); } } dw_printf (" %d: %s", n, wic.szPname); for (a=0; aadev[a].defined && n==in_dev_no[a]) { if (pa->adev[a].num_channels == 2) { dw_printf (" (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1); } else { dw_printf (" (channel %d)", ADEVFIRSTCHAN(a)); } } } dw_printf ("\n"); } } // Add UDP or stdin to end of device list if used. for (a=0; aadev[a].defined) { struct adev_s *A = &(adev[a]); /* Display stdin or udp:port if appropriate. */ if (A->g_audio_in_type != AUDIO_IN_TYPE_SOUNDCARD) { int aaa; for (aaa=0; aaaadev[aaa].defined) { dw_printf (" %c", a == aaa ? '*' : ' '); } } dw_printf (" %s ", pa->adev[a].adevice_in); /* should be UDP:nnnn or stdin */ if (pa->adev[a].num_channels == 2) { dw_printf (" (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1); } else { dw_printf (" (channel %d)", ADEVFIRSTCHAN(a)); } dw_printf ("\n"); } } } /* * Display the output devices (soundcards) available and what is selected. */ dw_printf ("Available audio output devices for transmit (*=selected):\n"); /* TODO? */ /* No "*" is currently displayed when using the default device. */ /* Should we put "*" next to the default device when using it? */ /* Which is the default? The first one? */ num_devices = waveOutGetNumDevs(); for (a=0; aadev[a].defined) { if (out_dev_no[a] < -1 || out_dev_no[a] >= num_devices) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid output (transmit) audio device number %d.\n", out_dev_no[a]); out_dev_no[a] = WAVE_MAPPER; } } } text_color_set(DW_COLOR_INFO); for (n=0; nadev[a].defined) { dw_printf (" %c", n==out_dev_no[a] ? '*' : ' '); } } dw_printf (" %d: %s", n, woc.szPname); for (a=0; aadev[a].defined && n==out_dev_no[a]) { if (pa->adev[a].num_channels == 2) { dw_printf (" (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1); } else { dw_printf (" (channel %d)", ADEVFIRSTCHAN(a)); } } } dw_printf ("\n"); } } /* * Open for each audio device input/output pair. */ for (a=0; aadev[a].defined) { struct adev_s *A = &(adev[a]); WAVEFORMATEX wf; wf.wFormatTag = WAVE_FORMAT_PCM; wf.nChannels = pa -> adev[a].num_channels; wf.nSamplesPerSec = pa -> adev[a].samples_per_sec; wf.wBitsPerSample = pa -> adev[a].bits_per_sample; wf.nBlockAlign = (wf.wBitsPerSample / 8) * wf.nChannels; wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; wf.cbSize = 0; A->outbuf_size = calcbufsize(wf.nSamplesPerSec,wf.nChannels,wf.wBitsPerSample); /* * Open the audio output device. * Soundcard is only possibility at this time. */ err = waveOutOpen (&(A->audio_out_handle), out_dev_no[a], &wf, (DWORD_PTR)out_callback, a, CALLBACK_FUNCTION); if (err != MMSYSERR_NOERROR) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not open audio device for output.\n"); return (-1); } /* * Set up the output buffers. * We use dwUser to indicate it is available for filling. */ memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr)); for (n = 0; n < NUM_OUT_BUF; n++) { A->out_wavehdr[n].lpData = malloc(A->outbuf_size); A->out_wavehdr[n].dwUser = DWU_FILLING; A->out_wavehdr[n].dwBufferLength = 0; } A->out_current = 0; /* * Open audio input device. * More possibilities here: soundcard, UDP port, stdin. */ switch (A->g_audio_in_type) { /* * Soundcard. */ case AUDIO_IN_TYPE_SOUNDCARD: InitializeCriticalSection (&(A->in_cs)); err = waveInOpen (&(A->audio_in_handle), in_dev_no[a], &wf, (DWORD_PTR)in_callback, a, CALLBACK_FUNCTION); if (err != MMSYSERR_NOERROR) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not open audio device for input.\n"); return (-1); } /* * Set up the input buffers. */ memset ((void*)(A->in_wavehdr), 0, sizeof(A->in_wavehdr)); for (n = 0; n < NUM_OUT_BUF; n++) { A->in_wavehdr[n].dwBufferLength = A->outbuf_size; /* all the same size */ A->in_wavehdr[n].lpData = malloc(A->outbuf_size); } A->in_headp = NULL; /* * Give them to the sound input system. */ for (n = 0; n < NUM_OUT_BUF; n++) { waveInPrepareHeader(A->audio_in_handle, &(A->in_wavehdr[n]), sizeof(WAVEHDR)); waveInAddBuffer(A->audio_in_handle, &(A->in_wavehdr[n]), sizeof(WAVEHDR)); } /* * Start it up. * The callback function is called when one is filled. */ waveInStart (A->audio_in_handle); break; /* * UDP. */ case AUDIO_IN_TYPE_SDR_UDP: { WSADATA wsadata; struct sockaddr_in si_me; //int slen=sizeof(si_me); //int data_size = 0; int err; err = WSAStartup (MAKEWORD(2,2), &wsadata); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf("WSAStartup failed: %d\n", err); return (-1); } if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { text_color_set(DW_COLOR_ERROR); dw_printf("Could not find a usable version of Winsock.dll\n"); WSACleanup(); return (-1); } // Create UDP Socket A->udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (A->udp_sock == INVALID_SOCKET) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't create socket, errno %d\n", WSAGetLastError()); return -1; } memset((char *) &si_me, 0, sizeof(si_me)); si_me.sin_family = AF_INET; si_me.sin_port = htons((short)atoi(pa->adev[a].adevice_in + 4)); si_me.sin_addr.s_addr = htonl(INADDR_ANY); // Bind to the socket if (bind(A->udp_sock, (SOCKADDR *) &si_me, sizeof(si_me)) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't bind socket, errno %d\n", WSAGetLastError()); return -1; } A->stream_next= 0; A->stream_len = 0; } break; /* * stdin. */ case AUDIO_IN_TYPE_STDIN: setmode (STDIN_FILENO, _O_BINARY); A->stream_next= 0; A->stream_len = 0; break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error, invalid audio_in_type\n"); return (-1); } } } return (0); } /* end audio_open */ /* * Called when input audio block is ready. */ static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWORD param1, DWORD param2) { int a = instance; //dw_printf ("in_callback, handle = %d, a = %d\n", (int)handle, a); assert (a >= 0 && a < MAX_ADEVS); struct adev_s *A = &(adev[a]); if (msg == WIM_DATA) { WAVEHDR *p = (WAVEHDR*)param1; p->dwUser = -1; /* needs to be unprepared. */ p->lpNext = NULL; EnterCriticalSection (&(A->in_cs)); if (A->in_headp == NULL) { A->in_headp = p; /* first one in list */ } else { WAVEHDR *last = (WAVEHDR*)(A->in_headp); while (last->lpNext != NULL) { last = last->lpNext; } last->lpNext = p; /* append to last one */ } LeaveCriticalSection (&(A->in_cs)); } } /* * Called when output system is done with a block and it * is again available for us to fill. */ static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DWORD param1, DWORD param2) { if (msg == WOM_DONE) { WAVEHDR *p = (WAVEHDR*)param1; p->dwBufferLength = 0; p->dwUser = DWU_DONE; } } /*------------------------------------------------------------------ * * Name: audio_get * * Purpose: Get one byte from the audio device. * * * Inputs: a - Audio soundcard number. * * Returns: 0 - 255 for a valid sample. * -1 for any type of error. * * Description: The caller must deal with the details of mono/stereo * and number of bytes per sample. * * This will wait if no data is currently available. * *----------------------------------------------------------------*/ // Use hot attribute for all functions called for every audio sample. __attribute__((hot)) int audio_get (int a) { struct adev_s *A; WAVEHDR *p; int n; int sample; A = &(adev[a]); switch (A->g_audio_in_type) { /* * Soundcard. */ case AUDIO_IN_TYPE_SOUNDCARD: while (1) { /* * Wait if nothing available. * Could use an event to wake up but this is adequate. */ int timeout = 25; while (A->in_headp == NULL) { //SLEEP_MS (ONE_BUF_TIME / 5); SLEEP_MS (ONE_BUF_TIME); timeout--; if (timeout <= 0) { text_color_set(DW_COLOR_ERROR); // TODO1.2: Need more details. Can we keep going? dw_printf ("Timeout waiting for input from audio device %d.\n", a); audio_stats (a, save_audio_config_p->adev[a].num_channels, 0, save_audio_config_p->statistics_interval); return (-1); } } p = (WAVEHDR*)(A->in_headp); /* no need to be volatile at this point */ if (p->dwUser == (DWORD)(-1)) { waveInUnprepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR)); p->dwUser = 0; /* Index for next byte. */ audio_stats (a, save_audio_config_p->adev[a].num_channels, p->dwBytesRecorded / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), save_audio_config_p->statistics_interval); } if (p->dwUser < p->dwBytesRecorded) { n = ((unsigned char*)(p->lpData))[p->dwUser++]; #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_get(): returns %d\n", n); #endif return (n); } /* * Buffer is all used up. Give it back to sound input system. */ EnterCriticalSection (&(A->in_cs)); A->in_headp = p->lpNext; LeaveCriticalSection (&(A->in_cs)); p->dwFlags = 0; waveInPrepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR)); waveInAddBuffer(A->audio_in_handle, p, sizeof(WAVEHDR)); } break; /* * UDP. */ case AUDIO_IN_TYPE_SDR_UDP: while (A->stream_next >= A->stream_len) { int res; assert (A->udp_sock > 0); res = SOCK_RECV (A->udp_sock, A->stream_data, SDR_UDP_BUF_MAXLEN); if (res <= 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't read from udp socket, errno %d", WSAGetLastError()); A->stream_len = 0; A->stream_next = 0; audio_stats (a, save_audio_config_p->adev[a].num_channels, 0, save_audio_config_p->statistics_interval); return (-1); } audio_stats (a, save_audio_config_p->adev[a].num_channels, res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), save_audio_config_p->statistics_interval); A->stream_len = res; A->stream_next = 0; } sample = A->stream_data[A->stream_next] & 0xff; A->stream_next++; return (sample); break; /* * stdin. */ case AUDIO_IN_TYPE_STDIN: while (A->stream_next >= A->stream_len) { int res; res = read(STDIN_FILENO, A->stream_data, 1024); if (res <= 0) { text_color_set(DW_COLOR_INFO); dw_printf ("\nEnd of file on stdin. Exiting.\n"); exit (0); } audio_stats (a, save_audio_config_p->adev[a].num_channels, res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), save_audio_config_p->statistics_interval); A->stream_len = res; A->stream_next = 0; } return (A->stream_data[A->stream_next++] & 0xff); break; } return (-1); } /* end audio_get */ /*------------------------------------------------------------------ * * Name: audio_put * * Purpose: Send one byte to the audio device. * * Inputs: a - Index for audio device. * * c - One byte in range of 0 - 255. * * * Global In: out_current - index of output buffer currenly being filled. * * Returns: Normally non-negative. * -1 for any type of error. * * Description: The caller must deal with the details of mono/stereo * and number of bytes per sample. * * See Also: audio_flush * audio_wait * *----------------------------------------------------------------*/ int audio_put (int a, int c) { WAVEHDR *p; struct adev_s *A; A = &(adev[a]); /* * Wait if no buffers are available. * Don't use p yet because compiler might might consider dwFlags a loop invariant. */ int timeout = 10; while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) { SLEEP_MS (ONE_BUF_TIME); timeout--; if (timeout <= 0) { text_color_set(DW_COLOR_ERROR); // TODO: open issues 78 & 165. How can we avoid/improve this? dw_printf ("Audio output failure waiting for buffer.\n"); dw_printf ("This can occur when we are producing audio output for\n"); dw_printf ("transmit and the operating system doesn't provide buffer\n"); dw_printf ("space after waiting and retrying many times.\n"); //dw_printf ("In recent years, this has been reported only when running the\n"); //dw_printf ("Windows version with VMWare on a Macintosh.\n"); ptt_term (); return (-1); } } p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); if (p->dwUser == DWU_DONE) { waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR)); p->dwBufferLength = 0; p->dwUser = DWU_FILLING; } /* Should never be full at this point. */ assert (p->dwBufferLength >= 0); assert (p->dwBufferLength < (DWORD)(A->outbuf_size)); p->lpData[p->dwBufferLength++] = c; if (p->dwBufferLength == (DWORD)(A->outbuf_size)) { return (audio_flush(a)); } return (0); } /* end audio_put */ /*------------------------------------------------------------------ * * Name: audio_flush * * Purpose: Send current buffer to the audio output system. * * Inputs: a - Index for audio device. * * Returns: Normally non-negative. * -1 for any type of error. * * See Also: audio_flush * audio_wait * *----------------------------------------------------------------*/ int audio_flush (int a) { WAVEHDR *p; MMRESULT e; struct adev_s *A; A = &(adev[a]); p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) { p->dwUser = DWU_PLAYING; waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR)); e = waveOutWrite(A->audio_out_handle, p, sizeof(WAVEHDR)); if (e != MMSYSERR_NOERROR) { text_color_set (DW_COLOR_ERROR); dw_printf ("audio out write error %d\n", e); /* I don't expect this to ever happen but if it */ /* does, make the buffer available for filling. */ p->dwUser = DWU_DONE; return (-1); } A->out_current = (A->out_current + 1) % NUM_OUT_BUF; } return (0); } /* end audio_flush */ /*------------------------------------------------------------------ * * Name: audio_wait * * Purpose: Finish up audio output before turning PTT off. * * Inputs: a - Index for audio device (not channel!) * * Returns: None. * * Description: Flush out any partially filled audio output buffer. * Wait until all the queued up audio out has been played. * Take any other necessary actions to stop audio output. * * In an ideal world: * * We would like to ask the hardware when all the queued * up sound has actually come out the speaker. * * In reality: * * This has been found to be less than reliable in practice. * * Caller does the following: * * (1) Make note of when PTT is turned on. * (2) Calculate how long it will take to transmit the * frame including TXDELAY, frame (including * "flags", data, FCS and bit stuffing), and TXTAIL. * (3) Call this function, which might or might not wait long enough. * (4) Add (1) and (2) resulting in when PTT should be turned off. * (5) Take difference between current time and desired PPT off time * and wait for additoinal time if required. * *----------------------------------------------------------------*/ void audio_wait (int a) { audio_flush (a); } /* end audio_wait */ /*------------------------------------------------------------------ * * Name: audio_close * * * Purpose: Close all of the audio devices. * * Returns: Normally non-negative. * -1 for any type of error. * * *----------------------------------------------------------------*/ int audio_close (void) { int err = 0; int n; int a; for (a=0; aadev[a].defined) { struct adev_s *A = &(adev[a]); assert (A->audio_in_handle != 0); assert (A->audio_out_handle != 0); audio_wait (a); /* Shutdown audio input. */ waveInReset(A->audio_in_handle); waveInStop(A->audio_in_handle); waveInClose(A->audio_in_handle); A->audio_in_handle = 0; for (n = 0; n < NUM_IN_BUF; n++) { waveInUnprepareHeader (A->audio_in_handle, (LPWAVEHDR)(&(A->in_wavehdr[n])), sizeof(WAVEHDR)); A->in_wavehdr[n].dwFlags = 0; free (A->in_wavehdr[n].lpData); A->in_wavehdr[n].lpData = NULL; } DeleteCriticalSection (&(A->in_cs)); /* Make sure all output buffers have been played then free them. */ for (n = 0; n < NUM_OUT_BUF; n++) { if (A->out_wavehdr[n].dwUser == DWU_PLAYING) { int timeout = 2 * NUM_OUT_BUF; while (A->out_wavehdr[n].dwUser == DWU_PLAYING) { SLEEP_MS (ONE_BUF_TIME); timeout--; if (timeout <= 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio output failure on close.\n"); } } waveOutUnprepareHeader (A->audio_out_handle, (LPWAVEHDR)(&(A->out_wavehdr[n])), sizeof(WAVEHDR)); A->out_wavehdr[n].dwUser = DWU_DONE; } free (A->out_wavehdr[n].lpData); A->out_wavehdr[n].lpData = NULL; } waveOutClose (A->audio_out_handle); A->audio_out_handle = 0; } /* if device configured */ } /* for each device. */ /* Not right. always returns 0 but at this point, doesn't matter. */ return (err); } /* end audio_close */ /* end audio_win.c */ direwolf-1.5+dfsg/ax25_link.c000066400000000000000000007156211347750676600161120ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2016, 2017, 2018 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Name: ax25_link * * Purpose: Data Link State Machine. * Establish connections and transfer data in the proper * order with retries. * * Description: * * Typical sequence for establishing a connection * initiated by a client application. Try version 2.2, * get refused, and fall back to trying version 2.0. * * * State Client App State Machine Peer * ----- ---------- ------------- ---- * * 0 disc * Conn. Req ---> * SABME ---> * 5 await 2.2 * <--- FRMR or DM *note * SABM ---> * 1 await 2.0 * <--- UA * <--- CONN Ind. * 3 conn * * * Typical sequence when other end initiates connection. * * * State Client App State Machine Peer * ----- ---------- ------------- ---- * * 0 disc * <--- SABME or SABM * UA ---> * <--- CONN Ind. * 3 conn * * * *note: * * After carefully studying the v2.2 spec, I expected a 2.0 implementation to send * FRMR in response to SABME. This is important. If a v2.2 implementation * gets FRMR, in response to SABME, it switches to v2.0 and sends SABM instead. * * The v2.0 protocol spec, section 2.3.4.3.3.1, states that FRMR should be sent when * an invalid or not implemented command is received. That all fits together. * * In testing, I found that the KPC-3+ sent DM. * * I can see where they might get that idea. * The v2.0 spec says that when in disconnected mode, it should respond to any * command other than SABM or UI frame with a DM response with P/F set to 1. * I think it was implemented wrong. 2.3.4.3.3.1 should take precedence. * * The TM-D710 does absolutely nothing in response to SABME. * Not responding at all is just plain wrong. To work around this, I put * in a special hack to start sending SABM after a certain number of * SABME go unanswered. There is more discussion in the User Guide. * * References: * * AX.25 Amateur Packet-Radio Link-Layer Protocol Version 2.0, October 1984 * * https://www.tapr.org/pub_ax25.html * http://lea.hamradio.si/~s53mv/nbp/nbp/AX25V20.pdf * * At first glance, they look pretty much the same, but the second one * is more complete with 4 appendices, including a state table. * * * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998 * * https://www.tapr.org/pdf/AX25.2.2.pdf * * * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998 * * http://www.ax25.net/AX25.2.2-Jul%2098-2.pdf * * I accidentally stumbled across this one when searching for some sort of errata * list for the original protocol specification. * * "This is a new version of the 1998 standard. It has had all figures * redone using Microsoft Visio. Errors in the SDL have been corrected." * * The SDL diagrams are dated 2006. I wish I had known about this version, with * several corrections, before doing most of the implementation. :-( * * The title page still says July 1998 so it's not immediately obvious this * is different than the one on the TAPR site. * * * AX.25 ... Latest revision, in progress. * * http://www.nj7p.org/ * * This is currently being revised in cooperation with software authors * who have noticed some issues during implementation. * * The functions here are based on the SDL diagrams but turned inside out. * It seems more intuitive to have a function for each type of input and then decide * what to do depending on the state. This also reduces duplicate code because we * often see the same flow chart segments, for the same input, appearing in multiple states. * * Errata: The protocol spec has many places that appear to be errors or are ambiguous so I wasn't * sure what to do. These should be annotated with "erratum" comments so we can easily go * back and revisit them. * * X.25: The AX.25 protocol is based on, but does not necessarily adhere to, the X.25 protocol. * Consulting this might provide some insights where the AX.25 spec is not clear. * * http://www.itu.int/rec/T-REC-X.25-199610-I/en/ * * Version 1.4, released April 2017: * * Features tested reasonably well: * * Connect to/from a KPC-3+ and send I frames in both directions. * Same with TM-D710A. * v2.2 connect between two instances of direwolf. (Can't find another v2.2 for testing.) * Modulo 8 & 128 sequence numbers. * Recovery from simulated transmission errors using either REJ or SREJ. * XID frame for parameter negotiation. * Segments to allow data larger than max info part size. * * Implemented but not tested properly: * * Connecting thru digipeater(s). * Acting as a digipeater. * T3 timer. * Compatibility with additional types of TNC. * * Version 1.5, December 2017: * * Implemented Multi Selective Reject. * More efficient generation of SREJ frames. * Reduced number of duplicate I frames sent for both REJ and SREJ cases. * Avoided unnecessary RR when I frame could take care of the ack. * (This led to issue 132 where outgoing data sometimes got stuck in the queue.) * *------------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "ax25_pad.h" #include "ax25_pad2.h" #include "xid.h" #include "textcolor.h" #include "dlq.h" #include "tq.h" #include "ax25_link.h" #include "dtime_now.h" #include "server.h" #include "ptt.h" #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) // Debug switches for different types of information. // Should have command line options instead of changing source and recompiling. static int s_debug_protocol_errors = 1; // Less serious Protocol errors. // Useful for debugging but unnecessarily alarming other times. static int s_debug_client_app = 0; // Interaction with client application. // dl_connect_request, dl_data_request, dl_data_indication, etc. static int s_debug_radio = 0; // Received frames and channel busy status. // lm_data_indication, lm_channel_busy static int s_debug_variables = 0; // Variables, state changes. static int s_debug_retry = 0; // Related to lost I frames, REJ, SREJ, timeout, resending. static int s_debug_timers = 0; // Timer details. static int s_debug_link_handle = 0; // Create data link state machine or pick existing one, // based on my address, peer address, client app index, and radio channel. static int s_debug_stats = 0; // Statistics when connection is closed. static int s_debug_misc = 0; // Anything left over that might be interesting. /* * AX.25 data link state machine. * * One instance for each link identified by * [ client, channel, owncall, peercall ] */ enum dlsm_state_e { state_0_disconnected = 0, state_1_awaiting_connection = 1, state_2_awaiting_release = 2, state_3_connected = 3, state_4_timer_recovery = 4, state_5_awaiting_v22_connection = 5 }; typedef struct ax25_dlsm_s { int magic1; // Look out for bad pointer or corruption. #define MAGIC1 0x11592201 struct ax25_dlsm_s *next; // Next in linked list. int stream_id; // Unique number for each stream. // Internally we use a pointer but this is more user-friendly. int chan; // Radio channel being used. int client; // We have have multiple client applications, // each with their own links. We need to know // which client should receive the data or // notifications about state changes. char addrs[AX25_MAX_REPEATERS][AX25_MAX_ADDR_LEN]; // Up to 10 addresses, same order as in frame. int num_addr; // Number of addresses. Should be in range 2 .. 10. #define OWNCALL AX25_SOURCE // addrs[OWNCALL] is owncall for this end of link. // Note that we are acting on behalf of // a client application so the APRS mycall // might not be relevent. #define PEERCALL AX25_DESTINATION // addrs[PEERCALL] is call for other end. double start_time; // Clock time when this was allocated. Used only for // debug output for timestamps relative to start. enum dlsm_state_e state; // Current state. int modulo; // 8 or 128. // Determines whether we have one or two control // octets. 128 allows a much larger window size. enum srej_e srej_enable; // Is other end capable of processing SREJ? (Am I allowed to send it?) // Starts out as 'srej_none' for v2.0 or 'srej_single' for v2.2. // Can be changed to 'srej_multi' with XID exchange. // Should be used only with modulo 128. (Is this enforced?) int n1_paclen; // Maximum length of information field, in bytes. // Starts out as 256 but can be negotiated higher. // (Protocol Spec has this in bits. It is in bytes here.) // "PACLEN" in configuration file. int n2_retry; // Maximum number of retries permitted. // Typically 10. // "RETRY" parameter in configuration file. int k_maxframe; // Window size. Defaults to 4 (mod 8) or 32 (mod 128). // Maximum number of unacknowledged information // frames that can be outstanding. // "MAXFRAME" or "EMAXFRAME" parameter in configuration file. int rc; // Retry count. Give up after n2. int vs; // 4.2.4.1. Send State Variable V(S) // The send state variable exists within the TNC and is never sent. // It contains the next sequential number to be assigned to the next // transmitted I frame. // This variable is updated with the transmission of each I frame. int va; // 4.2.4.5. Acknowledge State Variable V(A) // The acknowledge state variable exists within the TNC and is never sent. // It contains the sequence number of the last frame acknowledged by // its peer [V(A)-1 equals the N(S) of the last acknowledged I frame]. int vr; // 4.2.4.3. Receive State Variable V(R) // The receive state variable exists within the TNC. // It contains the sequence number of the next expected received I frame // This variable is updated upon the reception of an error-free I frame // whose send sequence number equals the present received state variable value. int layer_3_initiated; // SABM(E) was sent by request of Layer 3; i.e. DL-CONNECT request primitive. // I think this means that it is set only if we initiated the connection. // It would not be set if we are in the middle of accepting a connection from the other station. // Next 5 are called exception conditions. int peer_receiver_busy; // Remote station is busy and can't receive I frames. int reject_exception; // A REJ frame has been sent to the remote station. (boolean) // This is used only when receving an I frame, in states 3 & 4, SREJ not enabled. // When an I frame has an unepected N(S), // - if not already set, set it and send REJ. // When an I frame with expected N(S) is received, clear it. // This would prevent us from sending additional REJ while // waiting for result from first one. // What happens if the REJ gets lost? Is it resent somehow? int own_receiver_busy; // Layer 3 is busy and can't receive I frames. // We have no API to convey this information so it should always be 0. int acknowledge_pending; // I frames have been successfully received but not yet // acknowledged TO the remote station. // Set when receiving the next expected I frame and P=0. // This gets cleared by sending any I, RR, RNR, REJ. // Cleared when sending SREJ with F=1. // Timing. float srt; // Smoothed roundtrip time in seconds. // This is used to dynamically adjust t1v. // Sometimes the flow chart has SAT instead of SRT. // I think that is a typographical error. float t1v; // How long to wait for an acknowlegement before resending. // Value used when starting timer T1, in seconds. // "FRACK" parameter in some implementations. // Typically it might be 3 seconds after frame has been // sent. Add more for each digipeater in path. // Here it is dynamically adjusted. // Set initial value for T1V. // Multiply FRACK by 2*m+1, where m is number of digipeaters. #define INIT_T1V_SRT \ S->t1v = g_misc_config_p->frack * (2 * (S->num_addr - 2) + 1); \ S->srt = S->t1v / 2.0; int radio_channel_busy; // Either due to DCD or PTT. // Timer T1. // Timer values all use the usual unix time() value but double precision // so we can have fractions of seconds. // T1 is used for retries along with the retry counter, "rc." // When timer T1 is started, the value is obtained from t1v plus the current time. // Appropriate functions should be used rather than accessing the values directly. // This gets a little tricky because we need to pause the timers when the radio // channel is busy. Suppose we sent an I frame and set T1 to 4 seconds so we could // take corrective action if there is no response in a reasonable amount of time. // What if some other station has the channel tied up for 10 seconds? We don't want // T1 to timeout and start a retry sequence. The solution is to pause the timers while // the channel is busy. We don't want to get a timer expiry event when t1_exp is in // the past if it is currently paused. When it is un-paused, the expiration time is adjusted // for the amount of time it was paused. double t1_exp; // This is the time when T1 will expire or 0 if not running. double t1_paused_at; // Time when it was paused or 0 if not paused. float t1_remaining_when_last_stopped; // Number of seconds that were left on T1 when it was stopped. // This is used to fine tune t1v. // Set to negative initially to mean invalid, don't use in calculation. int t1_had_expired; // Set when T1 expires. // Cleared for start & stop. // Timer T3. // T3 is used to terminate connection after extended inactivity. // Similar to T1 except there is not mechanism to capture the remaining time when it is stopped // and it is not paused when the channel is busy. double t3_exp; // When it expires or 0 if not running. #define T3_DEFAULT 300.0 // Copied 5 minutes from Ax.25 for Linux. // http://www.linux-ax25.org/wiki/Run_time_configurable_parameters // D710A also defaults to 30*10 = 300 seconds. // Should it be user-configurable? // KPC-3+ and TM-D710A have "CHECK" command for this purpose. // Statistics for testing purposes. // Count how many frames of each type we received. // This is easy to do because they all come in thru lm_data_indication. // Counting outgoing could probably be done in lm_data_request so // it would not have to be scattered all over the place. TBD int count_recv_frame_type[frame_not_AX25+1]; int peak_rc_value; // Peak value of retry count (rc). // For sending data. cdata_t *i_frame_queue; // Connected data from client which has not been transmitted yet. // Linked list. // The name is misleading because these are just blocks of // data, not "I frames" at this point. The name comes from // the protocol specification. cdata_t *txdata_by_ns[128]; // Data which has already been transmitted. // Indexed by N(S) in case it gets lost and needs to be sent again. // Cleared out when we get ACK for it. int magic3; // Look out for out of bounds for above. #define MAGIC3 0x03331301 cdata_t *rxdata_by_ns[128]; // "Receive buffer" // Data which has been received out of sequence. // Indexed by N(S). int magic2; // Look out for out of bounds for above. #define MAGIC2 0x02221201 // "Management Data Link" (MDL) state machine for XID exchange. enum mdl_state_e { mdl_state_0_ready=0, mdl_state_1_negotiating=1 } mdl_state; int mdl_rc; // Retry count, waiting to get XID response. // The spec has provision for a separate maximum, NM201, but we // just use the regular N2 same as other retries. double tm201_exp; // Timer. Similar to T1. // The spec mentions a separate timeout value but // we will just use the same as T1. double tm201_paused_at; // Time when it was paused or 0 if not paused. // Segment reassembler. cdata_t *ra_buff; // Reassembler buffer. NULL when in ready state. int ra_following; // Most recent number following to predict next expected. } ax25_dlsm_t; /* * List of current state machines for each link. * There is potential many client apps, each with multiple links * connected all at the same time. * * Everything coming thru here should be from a single thread. * The Data Link Queue should serialize all processing. * Therefore, we don't have to worry about critical regions. */ static ax25_dlsm_t *list_head = NULL; /* * Registered callsigns for incoming connections. */ #define RC_MAGIC 0x08291951 typedef struct reg_callsign_s { char callsign[AX25_MAX_ADDR_LEN]; int chan; int client; struct reg_callsign_s *next; int magic; } reg_callsign_t; static reg_callsign_t *reg_callsign_list = NULL; // Use these, rather than setting variables directly, to make debug out easier. #define SET_VS(n) { S->vs = (n); \ if (s_debug_variables) { \ text_color_set(DW_COLOR_DEBUG); \ dw_printf ("V(S) = %d at %s %d\n", S->vs, __func__, __LINE__); \ } \ assert (S->vs >= 0 && S->vs < S->modulo); \ } // If other guy acks reception of an I frame, we should never get an REJ or SREJ // asking for it again. When we update V(A), we should be able to remove the saved // transmitted data, and everything preceding it, from S->txdata_by_ns[]. #define SET_VA(n) { S->va = (n); \ if (s_debug_variables) { \ text_color_set(DW_COLOR_DEBUG); \ dw_printf ("V(A) = %d at %s %d\n", S->va, __func__, __LINE__); \ } \ assert (S->va >= 0 && S->va < S->modulo); \ int x = AX25MODULO(n-1, S->modulo, __FILE__, __func__, __LINE__); \ while (S->txdata_by_ns[x] != NULL) { \ cdata_delete (S->txdata_by_ns[x]); \ S->txdata_by_ns[x] = NULL; \ x = AX25MODULO(x-1, S->modulo, __FILE__, __func__, __LINE__); \ } \ } #define SET_VR(n) { S->vr = (n); \ if (s_debug_variables) { \ text_color_set(DW_COLOR_DEBUG); \ dw_printf ("V(R) = %d at %s %d\n", S->vr, __func__, __LINE__); \ } \ assert (S->vr >= 0 && S->vr < S->modulo); \ } #define SET_RC(n) { S->rc = (n); \ if (s_debug_variables) { \ text_color_set(DW_COLOR_DEBUG); \ dw_printf ("rc = %d at %s %d, state = %d\n", S->rc, __func__, __LINE__, S->state); \ } \ } //TODO: Make this a macro so we can simplify calls yet keep debug output if something goes wrong. #if 0 #define AX25MODULO(n) ax25modulo((n), S->modulo, __FILE__, __func__, __LINE__) static int ax25modulo(int n, int m, const char *file, const char *func, int line) #else static int AX25MODULO(int n, int m, const char *file, const char *func, int line) #endif { if (m != 8 && m != 128) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: %d modulo %d, %s, %s, %d\n", n, m, file, func, line); m = 8; } // Use masking, rather than % operator, so negative numbers are handled properly. return (n & (m-1)); } // Test whether we can send more or if we need to wait // because we have reached 'maxframe' outstanding frames. // Argument must be 'S'. #define WITHIN_WINDOW_SIZE(x) (x->vs != AX25MODULO(x->va + x->k_maxframe, x->modulo, __FILE__, __func__, __LINE__)) // Timer macros to provide debug output with location from where they are called. #define START_T1 start_t1(S, __func__, __LINE__) #define IS_T1_RUNNING is_t1_running(S, __func__, __LINE__) #define STOP_T1 stop_t1(S, __func__, __LINE__) #define PAUSE_T1 pause_t1(S, __func__, __LINE__) #define RESUME_T1 resume_t1(S, __func__, __LINE__) #define START_T3 start_t3(S, __func__, __LINE__) #define STOP_T3 stop_t3(S, __func__, __LINE__) #define START_TM201 start_tm201(S, __func__, __LINE__) #define STOP_TM201 stop_tm201(S, __func__, __LINE__) #define PAUSE_TM201 pause_tm201(S, __func__, __LINE__) #define RESUME_TM201 resume_tm201(S, __func__, __LINE__) static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len); static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len); static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len); static int is_ns_in_window (ax25_dlsm_t *S, int ns); static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1); static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr); static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr); static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr, unsigned char *info_ptr, int info_len); static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p); static void disc_frame (ax25_dlsm_t *S, int f); static void dm_frame (ax25_dlsm_t *S, int f); static void ua_frame (ax25_dlsm_t *S, int f); static void frmr_frame (ax25_dlsm_t *S); static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf); static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len); static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len); static void t1_expiry (ax25_dlsm_t *S); static void t3_expiry (ax25_dlsm_t *S); static void tm201_expiry (ax25_dlsm_t *S); static void nr_error_recovery (ax25_dlsm_t *S); static void clear_exception_conditions (ax25_dlsm_t *S); static void transmit_enquiry (ax25_dlsm_t *S); static void select_t1_value (ax25_dlsm_t *S); static void establish_data_link (ax25_dlsm_t *S); static void set_version_2_0 (ax25_dlsm_t *S); static void set_version_2_2 (ax25_dlsm_t *S); static int is_good_nr (ax25_dlsm_t *S, int nr); static void i_frame_pop_off_queue (ax25_dlsm_t *S); static void discard_i_queue (ax25_dlsm_t *S); static void invoke_retransmission (ax25_dlsm_t *S, int nr_input); static void check_i_frame_ackd (ax25_dlsm_t *S, int nr); static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf); static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f); static void enter_new_state(ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line); static void mdl_negotiate_request (ax25_dlsm_t *S); static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param); static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param); static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param); // Use macros above rather than calling these directly. static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line); static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line); static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line); static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line); static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line); /* * Configuration settings from file or command line. */ static struct misc_config_s *g_misc_config_p; /*------------------------------------------------------------------- * * Name: ax25_link_init * * Purpose: Initialize the ax25_link module. * * Inputs: pconfig - misc. configuration from config file or command line. * Beacon stuff ended up here. * * Outputs: Remember required information for future use. That's all. * *--------------------------------------------------------------------*/ void ax25_link_init (struct misc_config_s *pconfig) { /* * Save parameters for later use. */ g_misc_config_p = pconfig; } /* end ax25_link_init */ /*------------------------------------------------------------------------------ * * Name: get_link_handle * * Purpose: Find existing (or possibly create) state machine for a given link. * It should be possible to have a large number of links active at the * same time. They are uniquely identified by * (owncall, peercall, client id, radio channel) * Note that we could have multiple client applications, all sharing one * TNC, on the same or different radio channels, completely unware of each other. * * Inputs: addrs - Owncall, peercall, and optional digipeaters. * For ease of passing this around, it is an array in the * same order as in the frame. * * num_addr - Number of addresses, 2 thru 10. * * chan - Radio channel number. * * client - Client app number. * We allow multiple concurrent applications with the * AGW network protocol. These are identified as 0, 1, ... * We don't know this for an incoming frame from the radio * so it is -1 at this point. At a later time will will * associate the stream with the right client. * * create - True if OK to create a new one. * Otherwise, return only one already existing. * * This should always be true for outgoing frames. * For incoming frames this would be true only for SABM(e) * with all digipeater fields marked as used. * * Here, we will also check to see if it is in our * registered callsign list. * * Returns: Handle for data link state machine. * NULL if not found and 'create' is false. * * Description: Try to find an existing entry matching owncall, peercall, channel, * and client. If not found create a new one. * *------------------------------------------------------------------------------*/ static int next_stream_id = 0; static ax25_dlsm_t *get_link_handle (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int create) { ax25_dlsm_t *p; if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle (%s>%s, chan=%d, client=%d, create=%d)\n", addrs[AX25_SOURCE], addrs[AX25_DESTINATION], chan, client, create); } // Look for existing. if (client == -1) { // from the radio. // address order is reversed for compare. for (p = list_head; p != NULL; p = p->next) { if (p->chan == chan && strcmp(addrs[AX25_DESTINATION], p->addrs[OWNCALL]) == 0 && strcmp(addrs[AX25_SOURCE], p->addrs[PEERCALL]) == 0) { if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle returns existing stream id %d for incoming.\n", p->stream_id); } return (p); } } } else { // from client app for (p = list_head; p != NULL; p = p->next) { if (p->chan == chan && p->client == client && strcmp(addrs[AX25_SOURCE], p->addrs[OWNCALL]) == 0 && strcmp(addrs[AX25_DESTINATION], p->addrs[PEERCALL]) == 0) { if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle returns existing stream id %d for outgoing.\n", p->stream_id); } return (p); } } } // Could not find existing. Should we create a new one? if ( ! create) { if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle: Search failed. Do not create new.\n"); } return (NULL); } // If it came from the radio, search for destination our registered callsign list. int incoming_for_client = -1; // which client app registered the callsign? if (client == -1) { // from the radio. reg_callsign_t *r, *found; found = NULL; for (r = reg_callsign_list; r != NULL && found == NULL; r = r->next) { if (strcmp(addrs[AX25_DESTINATION], r->callsign) == 0 && chan == r->chan) { found = r; incoming_for_client = r->client; } } if (found == NULL) { if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle: not for me. Ignore it.\n"); } return (NULL); } } // Create new data link state machine. p = calloc (sizeof(ax25_dlsm_t), 1); p->magic1 = MAGIC1; p->start_time = dtime_now(); p->stream_id = next_stream_id++; p->modulo = 8; p->chan = chan; p->num_addr = num_addr; // If it came in over the radio, we need to swap source/destination and reverse any digi path. if (incoming_for_client >= 0) { strlcpy (p->addrs[AX25_SOURCE], addrs[AX25_DESTINATION], sizeof(p->addrs[AX25_SOURCE])); strlcpy (p->addrs[AX25_DESTINATION], addrs[AX25_SOURCE], sizeof(p->addrs[AX25_DESTINATION])); int j = AX25_REPEATER_1; int k = num_addr - 1; while (k >= AX25_REPEATER_1) { strlcpy (p->addrs[j], addrs[k], sizeof(p->addrs[j])); j++; k--; } p->client = incoming_for_client; } else { memcpy (p->addrs, addrs, sizeof(p->addrs)); p->client = client; } p->state = state_0_disconnected; p->t1_remaining_when_last_stopped = -999; // Invalid, don't use. p->magic2 = MAGIC2; p->magic3 = MAGIC3; // No need for critical region because this should all be in one thread. p->next = list_head; list_head = p; if (s_debug_link_handle) { text_color_set(DW_COLOR_DECODED); dw_printf ("get_link_handle returns NEW stream id %d\n", p->stream_id); } return (p); } //################################################################################### //################################################################################### // // Data Link state machine for sending data in connected mode. // // Incoming: // // Requests from the client application. Set s_debug_client_app for debugging. // // dl_connect_request // dl_disconnect_request // dl_data_request - send connected data // dl_unit_data_request - not implemented. APRS & KISS bypass this // dl_flow_off - not implemented. Not in AGW API. // dl_flow_on - not implemented. Not in AGW API. // dl_register_callsign - Register callsigns(s) for incoming connection requests. // dl_unregister_callsign - Unregister callsigns(s) ... // dl_client_cleanup - Clean up after client which has disappeared. // // Stuff from the radio channel. Set s_debug_radio for debugging. // // lm_data_indication - Received frame. // lm_channel_busy - Change in PTT or DCD. // lm_seize_confirm - We have started to transmit. // // Timer expiration. Set s_debug_timers for debugging. // // dl_timer_expiry // // Outgoing: // // To the client application: // // dl_data_indication - received connected data. // // To the transmitter: // // lm_data_request - Queue up a frame for transmission. // // lm_seize_request - Start transmitter when possible. // lm_seize_confirm will be called when it has. // // // It is important that all requests come thru the data link queue so // everything is serialized. // We don't have to worry about being reentrant or critical regions. // Nothing here should consume a significant amount of time. // i.e. There should be no sleep delay or anything that would block waiting on someone else. // //################################################################################### //################################################################################### /*------------------------------------------------------------------------------ * * Name: dl_connect_request * * Purpose: Client app wants to connect to another station. * * Inputs: E - Event from the queue. * The caller will free it. * * Description: * *------------------------------------------------------------------------------*/ void dl_connect_request (dlq_item_t *E) { ax25_dlsm_t *S; int ok_to_create = 1; int old_version; int n; if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_connect_request ()\n"); } text_color_set(DW_COLOR_INFO); dw_printf ("Attempting connect to %s ...\n", E->addrs[PEERCALL]); S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); switch (S->state) { case state_0_disconnected: INIT_T1V_SRT; // See if destination station is in list for v2.0 only. old_version = 0; for (n = 0; n < g_misc_config_p->v20_count && ! old_version; n++) { if (strcmp(E->addrs[AX25_DESTINATION],g_misc_config_p->v20_addrs[n]) == 0) { old_version = 1; } } if (old_version || g_misc_config_p->maxv22 == 0) { // Don't attempt v2.2. set_version_2_0 (S); establish_data_link (S); S->layer_3_initiated = 1; enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); } else { // Try v2.2 first, then fall back if appropriate. set_version_2_2 (S); establish_data_link (S); S->layer_3_initiated = 1; enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__); } break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: discard_i_queue(S); S->layer_3_initiated = 1; // Keep current state. break; case state_2_awaiting_release: // Keep current state. break; case state_3_connected: case state_4_timer_recovery: discard_i_queue(S); establish_data_link(S); S->layer_3_initiated = 1; // My enhancement. Original always sent SABM and went to state 1. // If we were using v2.2, why not reestablish with that? enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); break; } } /* end dl_connect_request */ /*------------------------------------------------------------------------------ * * Name: dl_disconnect_request * * Purpose: Client app wants to terminate connection with another station. * * Inputs: E - Event from the queue. * The caller will free it. * * Outputs: * * Description: * *------------------------------------------------------------------------------*/ void dl_disconnect_request (dlq_item_t *E) { ax25_dlsm_t *S; int ok_to_create = 1; if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_disconnect_request ()\n"); } text_color_set(DW_COLOR_INFO); dw_printf ("Disconnect from %s ...\n", E->addrs[PEERCALL]); S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); switch (S->state) { case state_0_disconnected: // DL-DISCONNECT *confirm* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // TODO: "requeue." Not sure what to do here. // If we put it back in the queue we will get it back again probably still in same state. // Need a way to defer it until the next state change. break; case state_2_awaiting_release: { // We have previously started the disconnect sequence and are waiting // for a UA from the other guy. Meanwhile, the application got // impatient and sent us another disconnect request. What should // we do? Ignore it and let the disconnect sequence run its // course? Or should we complete the sequence without waiting // for the other guy to ack? // Erratum. Flow chart simply says "DM (expedited)." // This is the only place we have expedited. Is this correct? cmdres_t cr = cr_res; // DM can only be response. int p = 0; int nopid = 0; // PID applies only to I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cr, frame_type_U_DM, p, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // HI means expedited. // Erratum: Shouldn't we inform the user when going to disconnected state? // Notifying the application, here, is my own enhancement. text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } break; case state_3_connected: case state_4_timer_recovery: discard_i_queue (S); SET_RC(0); // I think this should be 1 but I'm not that worried about it. cmdres_t cmd = cr_cmd; int p = 1; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_DISC, p, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); STOP_T3; START_T1; enter_new_state (S, state_2_awaiting_release, __func__, __LINE__); break; } } /* end dl_disconnect_request */ /*------------------------------------------------------------------------------ * * Name: dl_data_request * * Purpose: Client app wants to send data to another station. * * Inputs: E - Event from the queue. * The caller will free it. * * Description: Append the transmit data block to the I frame queue for later processing. * * We also perform the segmentation handling here. * * C6.1 Segmenter State Machine * Only the following DL primitives will be candidates for modification by the segmented * state machine: * * DL-DATA Request. The user employs this primitive to provide information to be * transmitted using connection-oriented procedures; i.e., using I frames. The * segmenter state machine examines the quantity of data to be transmitted. If the * quantity of data to be transmitted is less than or equal to the data link parameter * N1, the segmenter state machine passes the primitive through transparently. If the * quantity of data to be transmitted exceeds the data link parameter N1, the * segmenter chops up the data into segments of length N1-2 octets. Each segment is * prepended with a two octet header. (See Figures 3.1 and 3.2.) The segments are * then turned over to the Data-link State Machine for transmission, using multiple DL * Data Request primitives. All segments are turned over immediately; therefore the * Data-link State Machine will transmit them consecutively on the data link. * * Erratum: Not sure how to interpret that. See example below for how it was implemented. * *------------------------------------------------------------------------------*/ static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata); void dl_data_request (dlq_item_t *E) { ax25_dlsm_t *S; int ok_to_create = 1; int nseg_to_follow; int orig_offset, remaining_len; S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_data_request (\""); ax25_safe_print (E->txdata->data, E->txdata->len, 1); dw_printf ("\") state=%d\n", S->state); } if (E->txdata->len <= S->n1_paclen) { data_request_good_size (S, E->txdata); E->txdata = NULL; // Now part of transmit I frame queue. return; } // More interesting case. // It is too large to fit in one frame so we segment it. // As an example, suppose we had 6 bytes of data "ABCDEF". // If N1 >= 6, it would be sent normally. // (addresses) // (control bytes) // PID, typically 0xF0 // 'A' - first byte of information field // 'B' // 'C' // 'D' // 'E' // 'F' // Now consider the case where it would not fit. // We would change the PID to 0x08 meaning a segment. // The information part is the segment identifier of this format: // // x xxxxxxx // | ---+--- // | | // | +- Number of additional segments to follow. // | // +- '1' means it is the first segment. // If N1 = 4, it would be split up like this: // (addresses) // (control bytes) // PID = 0x08 means segment // 0x82 - Start of info field. // MSB set indicates FIRST segment. // 2, in lower 7 bits, means 2 more segments to follow. // 0xF0 - original PID, typical value. // 'A' - For the FIRST segment, we have PID and N1-2 data bytes. // 'B' // (addresses) // (control bytes) // PID = 0x08 means segment // 0x01 - Means 1 more segment follows. // 'C' - For subsequent (not first) segments, we have up to N1-1 data bytes. // 'D' // 'E' // (addresses) // (control bytes) // PID = 0x08 // 0x00 - 0 means no more to follow. i.e. This is the last. // 'E' // Number of segments is ceiling( (datalen + 1 ) / (N1 - 1)) // we add one to datalen for the original PID. // We subtract one from N1 for the segment identifier header. #define DIVROUNDUP(a,b) (((a)+(b)-1) / (b)) // Compute number of segments. // We will decrement this before putting it in the frame so the first // will have one less than this number. nseg_to_follow = DIVROUNDUP(E->txdata->len + 1, S->n1_paclen - 1); if (nseg_to_follow < 2 || nseg_to_follow > 128) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, number of segments = %d\n", __LINE__, E->txdata->len, S->n1_paclen, nseg_to_follow); cdata_delete (E->txdata); E->txdata = NULL; return; } orig_offset = 0; remaining_len = E->txdata->len; // First segment. int seglen; struct { char header; // 0x80 + number of segments to follow. char original_pid; char segdata[AX25_N1_PACLEN_MAX]; } first_segment; cdata_t *new_txdata; nseg_to_follow--; first_segment.header = 0x80 | nseg_to_follow; first_segment.original_pid = E->txdata->pid; seglen = MIN(S->n1_paclen - 2, remaining_len); if (seglen < 1 || seglen > S->n1_paclen - 2 || seglen > remaining_len || seglen > (int)(sizeof(first_segment.segdata))) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n", __LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow); cdata_delete (E->txdata); E->txdata = NULL; return; } memcpy (first_segment.segdata, E->txdata->data + orig_offset, seglen); new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&first_segment), seglen+2); data_request_good_size (S, new_txdata); orig_offset += seglen; remaining_len -= seglen; // Subsequent segments. do { struct { char header; // Number of segments to follow. char segdata[AX25_N1_PACLEN_MAX]; } subsequent_segment; nseg_to_follow--; subsequent_segment.header = nseg_to_follow; seglen = MIN(S->n1_paclen - 1, remaining_len); if (seglen < 1 || seglen > S->n1_paclen - 1 || seglen > remaining_len || seglen > (int)(sizeof(subsequent_segment.segdata))) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n", __LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow); cdata_delete (E->txdata); E->txdata = NULL; return; } memcpy (subsequent_segment.segdata, E->txdata->data + orig_offset, seglen); new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&subsequent_segment), seglen+1); data_request_good_size (S, new_txdata); orig_offset += seglen; remaining_len -= seglen; } while (nseg_to_follow > 0); if (remaining_len != 0 || orig_offset != E->txdata->len) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, remaining length = %d (not 0), orig offset = %d (not %d)\n", __LINE__, E->txdata->len, S->n1_paclen, remaining_len, orig_offset, E->txdata->len); } cdata_delete (E->txdata); E->txdata = NULL; } /* end dl_data_request */ static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata) { switch (S->state) { case state_0_disconnected: case state_2_awaiting_release: /* * Discard it. */ cdata_delete (txdata); break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: /* * Erratum? * The flow chart shows "push on I frame queue" if layer 3 initiated * is NOT set. This seems backwards but I don't understand enough yet * to make a compelling argument that it is wrong. * Implemented as in flow chart. * TODO: Get better understanding of what'layer_3_initiated' means. */ if (S->layer_3_initiated) { cdata_delete (txdata); break; } // otherwise fall thru. case state_3_connected: case state_4_timer_recovery: /* * "push on I frame queue" * Append to the end would have been a better description because push implies a stack. */ if (S->i_frame_queue == NULL) { txdata->next = NULL; S->i_frame_queue = txdata; } else { cdata_t *plast = S->i_frame_queue; while (plast->next != NULL) { plast = plast->next; } txdata->next = NULL; plast->next = txdata; } break; } // v1.5 change in strategy. // New I frames, not sent yet, are delayed until after processing anything in the received transmission. // Give the transmit process a kick unless other side is busy or we have reached our window size. // Previously we had i_frame_pop_off_queue here which would start sending new stuff before we // finished dealing with stuff already in progress. switch (S->state) { case state_3_connected: case state_4_timer_recovery: if ( ( ! S->peer_receiver_busy ) && WITHIN_WINDOW_SIZE(S) ) { S->acknowledge_pending = 1; lm_seize_request (S->chan); } break; default: break; } } /* end data_request_good_size */ /*------------------------------------------------------------------------------ * * Name: dl_register_callsign * dl_unregister_callsign * * Purpose: Register / Unregister callsigns that we will accept connections for. * * Inputs: E - Event from the queue. * The caller will free it. * * Outputs: New item is pushed on the head of the reg_callsign_list. * We don't bother checking for duplicates so the most recent wins. * * Description: The data link state machine does not use MYCALL from the APRS configuration. * For outgoing frames, the client supplies the source callsign. * For incoming connection requests, we need to know what address(es) to respond to. * * Note that one client application can register multiple callsigns for * multiple channels. * Different clients can register different different addresses on the same channel. * *------------------------------------------------------------------------------*/ void dl_register_callsign (dlq_item_t *E) { reg_callsign_t *r; if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_register_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client); } r = calloc(sizeof(reg_callsign_t),1); strlcpy (r->callsign, E->addrs[0], sizeof(r->callsign)); r->chan = E->chan; r->client = E->client; r->next = reg_callsign_list; r->magic = RC_MAGIC; reg_callsign_list = r; } /* end dl_register_callsign */ void dl_unregister_callsign (dlq_item_t *E) { reg_callsign_t *r, *prev; if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_unregister_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client); } prev = NULL; r = reg_callsign_list; while (r != NULL) { assert (r->magic == RC_MAGIC); if (strcmp(r->callsign,E->addrs[0]) == 0 && r->chan == E->chan && r->client == E->client) { if (r == reg_callsign_list) { reg_callsign_list = r->next; memset (r, 0, sizeof(reg_callsign_t)); free (r); r = reg_callsign_list; } else { prev->next = r->next; memset (r, 0, sizeof(reg_callsign_t)); free (r); r = prev->next; } } else { prev = r; r = r->next; } } } /* end dl_unregister_callsign */ /*------------------------------------------------------------------------------ * * Name: dl_client_cleanup * * Purpose: Client app has gone away. Clean up any data associated with it. * * Inputs: E - Event from the queue. * The caller will free it. * * Description: By client application we mean something that attached with the * AGW network protocol. * * Clean out anything related to the specfied client application. * This would include state machines and registered callsigns. * *------------------------------------------------------------------------------*/ void dl_client_cleanup (dlq_item_t *E) { ax25_dlsm_t *S; ax25_dlsm_t *dlprev; reg_callsign_t *r, *rcprev; if (s_debug_client_app) { text_color_set(DW_COLOR_INFO); dw_printf ("dl_client_cleanup (%d)\n", E->client); } dlprev = NULL; S = list_head; while (S != NULL) { // Look for corruption or double freeing. assert (S->magic1 == MAGIC1); assert (S->magic2 == MAGIC2); assert (S->magic3 == MAGIC3); if (S->client == E->client ) { int n; if (s_debug_stats) { text_color_set(DW_COLOR_INFO); dw_printf ("%d I frames received\n", S->count_recv_frame_type[frame_type_I]); dw_printf ("%d RR frames received\n", S->count_recv_frame_type[frame_type_S_RR]); dw_printf ("%d RNR frames received\n", S->count_recv_frame_type[frame_type_S_RNR]); dw_printf ("%d REJ frames received\n", S->count_recv_frame_type[frame_type_S_REJ]); dw_printf ("%d SREJ frames received\n", S->count_recv_frame_type[frame_type_S_SREJ]); dw_printf ("%d SABME frames received\n", S->count_recv_frame_type[frame_type_U_SABME]); dw_printf ("%d SABM frames received\n", S->count_recv_frame_type[frame_type_U_SABM]); dw_printf ("%d DISC frames received\n", S->count_recv_frame_type[frame_type_U_DISC]); dw_printf ("%d DM frames received\n", S->count_recv_frame_type[frame_type_U_DM]); dw_printf ("%d UA frames received\n", S->count_recv_frame_type[frame_type_U_UA]); dw_printf ("%d FRMR frames received\n", S->count_recv_frame_type[frame_type_U_FRMR]); dw_printf ("%d UI frames received\n", S->count_recv_frame_type[frame_type_U_UI]); dw_printf ("%d XID frames received\n", S->count_recv_frame_type[frame_type_U_XID]); dw_printf ("%d TEST frames received\n", S->count_recv_frame_type[frame_type_U_TEST]); dw_printf ("%d peak retry count\n", S->peak_rc_value); } if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dl_client_cleanup: remove %s>%s\n", S->addrs[AX25_SOURCE], S->addrs[AX25_DESTINATION]); } discard_i_queue (S); for (n = 0; n < 128; n++) { if (S->txdata_by_ns[n] != NULL) { cdata_delete (S->txdata_by_ns[n]); S->txdata_by_ns[n] = NULL; } } for (n = 0; n < 128; n++) { if (S->rxdata_by_ns[n] != NULL) { cdata_delete (S->rxdata_by_ns[n]); S->rxdata_by_ns[n] = NULL; } } if (S->ra_buff != NULL) { cdata_delete (S->ra_buff); S->ra_buff = NULL; } // Put into disconnected state. // If "connected" indicator (e.g. LED) was on, this will turn it off. enter_new_state (S, state_0_disconnected, __func__, __LINE__); // Take S out of list. S->magic1 = 0; S->magic2 = 0; S->magic3 = 0; if (S == list_head) { // first one on list. list_head = S->next; free (S); S = list_head; } else { // not the first one. dlprev->next = S->next; free (S); S = dlprev->next; } } else { dlprev = S; S = S->next; } } /* * If there are no link state machines (streams) remaining, there should be no txdata items still allocated. */ if (list_head == NULL) { cdata_check_leak(); } /* * Remove registered callsigns for this client. */ rcprev = NULL; r = reg_callsign_list; while (r != NULL) { assert (r->magic == RC_MAGIC); if (r->client == E->client) { if (r == reg_callsign_list) { reg_callsign_list = r->next; memset (r, 0, sizeof(reg_callsign_t)); free (r); r = reg_callsign_list; } else { rcprev->next = r->next; memset (r, 0, sizeof(reg_callsign_t)); free (r); r = rcprev->next; } } else { rcprev = r; r = r->next; } } } /* end dl_client_cleanup */ /*------------------------------------------------------------------------------ * * Name: dl_data_indication * * Purpose: send connected data to client application. * * Inputs: pid - Protocol ID. * * data - Pointer to array of bytes. * * len - Number of bytes in data. * * * Description: TODO: We perform reassembly of segments here if necessary. * *------------------------------------------------------------------------------*/ static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len) { // Now it gets more interesting. We need to combine segments before passing it along. // See example in dl_data_request. if (S->ra_buff == NULL) { // Ready state. if (pid != AX25_PID_SEGMENTATION_FRAGMENT) { server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len); return; } else if (data[0] & 0x80) { // Ready state, First segment. S->ra_following = data[0] & 0x7f; int total = (S->ra_following + 1) * (len - 1) - 1; // len should be other side's N1 S->ra_buff = cdata_new(data[1], NULL, total); S->ra_buff->size = total; // max that we are expecting. S->ra_buff->len = len - 2; // how much accumulated so far. memcpy (S->ra_buff->data, data + 2, len - 2); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not first segment in ready state.\n", S->stream_id); } } else { // Reassembling data state if (pid != AX25_PID_SEGMENTATION_FRAGMENT) { server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len); text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not segment in reassembling state.\n", S->stream_id); cdata_delete(S->ra_buff); S->ra_buff = NULL; return; } else if (data[0] & 0x80) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: First segment in reassembling state.\n", S->stream_id); cdata_delete(S->ra_buff); S->ra_buff = NULL; return; } else if ((data[0] & 0x7f) != S->ra_following - 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments out of sequence.\n", S->stream_id); cdata_delete(S->ra_buff); S->ra_buff = NULL; return; } else { // Reassembling data state, Not first segment. S->ra_following = data[0] & 0x7f; if (S->ra_buff->len + len - 1 <= S->ra_buff->size) { memcpy (S->ra_buff->data + S->ra_buff->len, data + 1, len - 1); S->ra_buff->len += len - 1; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments exceed buffer space.\n", S->stream_id); cdata_delete(S->ra_buff); S->ra_buff = NULL; return; } if (S->ra_following == 0) { // Last one. server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], S->ra_buff->pid, S->ra_buff->data, S->ra_buff->len); cdata_delete(S->ra_buff); S->ra_buff = NULL; } } } } /* end dl_data_indication */ /*------------------------------------------------------------------------------ * * Name: lm_channel_busy * * Purpose: Change in DCD or PTT status for channel so we know when it is busy. * * Inputs: E - Event from the queue. * * E->chan - Radio channel number. * * E->activity - OCTYPE_PTT for my transmission start/end. * - OCTYPE_DCD if we hear someone else. * * E->status - 1 for active or 0 for quiet. * * Outputs: S->radio_channel_busy * * T1 & TM201 paused/resumed if running. * * Description: We need to pause the timers when the channel is busy. * *------------------------------------------------------------------------------*/ static int dcd_status[MAX_CHANS]; static int ptt_status[MAX_CHANS]; void lm_channel_busy (dlq_item_t *E) { int busy; assert (E->chan >= 0 && E->chan < MAX_CHANS); assert (E->activity == OCTYPE_PTT || E->activity == OCTYPE_DCD); assert (E->status == 1 || E->status == 0); switch (E->activity) { case OCTYPE_DCD: if (s_debug_radio) { text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_channel_busy: DCD chan %d = %d\n", E->chan, E->status); } dcd_status[E->chan] = E->status; break; case OCTYPE_PTT: if (s_debug_radio) { text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_channel_busy: PTT chan %d = %d\n", E->chan, E->status); } ptt_status[E->chan] = E->status; break; default: break; } busy = dcd_status[E->chan] | ptt_status[E->chan]; /* * We know if the given radio channel is busy or not. * This must be applied to all data link state machines associated with that radio channel. */ ax25_dlsm_t *S; for (S = list_head; S != NULL; S = S->next) { if (E->chan == S->chan) { if (busy && ! S->radio_channel_busy) { S->radio_channel_busy = 1; PAUSE_T1; PAUSE_TM201; } else if ( ! busy && S->radio_channel_busy) { S->radio_channel_busy = 0; RESUME_T1; RESUME_TM201; } } } } /* end lm_channel_busy */ /*------------------------------------------------------------------------------ * * Name: lm_seize_confirm * * Purpose: Notification the the channel is clear. * * Description: C4.2. This primitive indicates to the Data-link State Machine that * the transmission opportunity has arrived. * * Version 1.5: Originally this only invoked inquiry_response to provide an ack if not already * taken care of by an earlier frame in this transmission. * After noticing the unnecessary I frame duplication and differing N(R) in the same * transmission, I came to the conclusion that we should delay sending of new * (not resends as a result of rej or srej) frames until after after processing * of everything in the incoming transmission. * The protocol spec simply has "I frame pops off queue" without any indication about * what might trigger this event. * *------------------------------------------------------------------------------*/ void lm_seize_confirm (dlq_item_t *E) { assert (E->chan >= 0 && E->chan < MAX_CHANS); ax25_dlsm_t *S; for (S = list_head; S != NULL; S = S->next) { if (E->chan == S->chan) { switch (S->state) { case state_0_disconnected: case state_1_awaiting_connection: case state_2_awaiting_release: case state_5_awaiting_v22_connection: break; case state_3_connected: case state_4_timer_recovery: // v1.5 change in strategy. // New I frames, not sent yet, are delayed until after processing anything in the received transmission. // Previously we started sending new frames, from the client app, as soon as they arrived. // Now, we first take care of those in progress before throwing more into the mix. i_frame_pop_off_queue(S); // Need an RR if we didn't have I frame send the necessary ack. if (S->acknowledge_pending) { S->acknowledge_pending = 0; enquiry_response (S, frame_not_AX25, 0); } // Implementation difference: The flow chart for state 3 has LM-RELEASE Request here. // I don't think I need it because the transmitter will turn off // automatically once the queue is empty. // Erratum: The original spec had LM-SEIZE request here, for state 4, which didn't seem right. // The 2006 revision has LM-RELEASE Request so states 3 & 4 are the same. break; } } } } /* lm_seize_confirm */ /*------------------------------------------------------------------------------ * * Name: lm_data_indication * * Purpose: We received some sort of frame over the radio. * * Inputs: E - Event from the queue. * Caller is responsible for freeing it. * * Description: First determine if is of interest to me. Two cases: * * (1) We already have a link handle for (from-addr, to-addr, channel). * This could have been set up by an outgoing connect request. * * (2) It is addressed to one of the registered callsigns. This would * catch the case of incoming connect requests. The APRS MYCALL * is not involved at all. The attached client app might have * much different ideas about what the station is called or * aliases it might respond to. * *------------------------------------------------------------------------------*/ void lm_data_indication (dlq_item_t *E) { ax25_frame_type_t ftype; char desc[80]; cmdres_t cr; int pf; int nr; int ns; ax25_dlsm_t *S; int client_not_applicable = -1; int n; int any_unused_digi; if (E->pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal Error, packet pointer is null. %s %s %d\n", __FILE__, __func__, __LINE__); return; } E->num_addr = ax25_get_num_addr(E->pp); // Digipeating is not done here so consider only those with no unused digipeater addresses. any_unused_digi = 0; for (n = AX25_REPEATER_1; n < E->num_addr; n++) { if ( ! ax25_get_h(E->pp, n)) { any_unused_digi = 1; } } if (any_unused_digi) { if (s_debug_radio) { text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_data_indication (%d, %s>%s) - ignore due to unused digi address.\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]); } return; } // Copy addresses from frame into event structure. for (n = 0; n < E->num_addr; n++) { ax25_get_addr_with_ssid (E->pp, n, E->addrs[n]); } if (s_debug_radio) { text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_data_indication (%d, %s>%s)\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]); } // Look for existing, or possibly create new, link state matching addresses and channel. // In most cases, we can ignore the frame if we don't have a corresponding // data link state machine. However, we might want to create a new one for SABM or SABME. // get_link_handle will check to see if the destination matches my address. // TODO: This won't work right because we don't know the modulo yet. // Maybe we should have a shorter form that only returns the frame type. // That is all we need at this point. ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns); S = get_link_handle (E->addrs, E->num_addr, E->chan, client_not_applicable, (ftype == frame_type_U_SABM) | (ftype == frame_type_U_SABME)); if (S == NULL) { return; } /* * There is not a reliable way to tell if a frame, out of context, has modulo 8 or 128 * sequence numbers. This needs to be supplied from the data link state machine. * * We can't do this until we get the link handle. */ ax25_set_modulo (E->pp, S->modulo); /* * Now we need to use ax25_frame_type again because the previous results, for nr and ns, might be wrong. */ ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns); // Gather statistics useful for testing. if (ftype <= frame_not_AX25) { S->count_recv_frame_type[ftype]++; } switch (ftype) { case frame_type_I: if (cr != cr_cmd) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error S: %s must be COMMAND.\n", S->stream_id, desc); } break; case frame_type_S_RR: case frame_type_S_RNR: case frame_type_S_REJ: if (cr != cr_cmd && cr != cr_res) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc); } break; case frame_type_U_SABME: case frame_type_U_SABM: case frame_type_U_DISC: if (cr != cr_cmd) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND.\n", S->stream_id, desc); } break; // Erratum: The AX.25 spec is not clear about whether SREJ should be command, response, or both. // The underlying X.25 spec clearly says it is reponse only. Let's go with that. case frame_type_S_SREJ: case frame_type_U_DM: case frame_type_U_UA: case frame_type_U_FRMR: if (cr != cr_res) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error: %s must be RESPONSE.\n", S->stream_id, desc); } break; case frame_type_U_XID: case frame_type_U_TEST: if (cr != cr_cmd && cr != cr_res) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc); } break; case frame_type_U_UI: // Don't test at this point in case an APRS frame gets thru. // APRS doesn't specify what to put in the Source and Dest C bits. // In practice we see all 4 possble combinations. // I have an opinion about what would be "correct" (discussed elsewhere) // but in practice no one seems to care. break; case frame_type_U: case frame_not_AX25: // not expected. break; } switch (ftype) { case frame_type_I: // Information { int pid; unsigned char *info_ptr; int info_len; pid = ax25_get_pid (E->pp); info_len = ax25_get_info (E->pp, &info_ptr); i_frame (S, cr, pf, nr, ns, pid, (char *)info_ptr, info_len); } break; case frame_type_S_RR: // Receive Ready - System Ready To Receive rr_rnr_frame (S, 1, cr, pf, nr); break; case frame_type_S_RNR: // Receive Not Ready - TNC Buffer Full rr_rnr_frame (S, 0, cr, pf, nr); break; case frame_type_S_REJ: // Reject Frame - Out of Sequence or Duplicate rej_frame (S, cr, pf, nr); break; case frame_type_S_SREJ: // Selective Reject - Ask for selective frame(s) repeat { unsigned char *info_ptr; int info_len; info_len = ax25_get_info (E->pp, &info_ptr); srej_frame (S, cr, pf, nr, info_ptr, info_len); } break; case frame_type_U_SABME: // Set Async Balanced Mode, Extended sabm_e_frame (S, 1, pf); break; case frame_type_U_SABM: // Set Async Balanced Mode sabm_e_frame (S, 0, pf); break; case frame_type_U_DISC: // Disconnect disc_frame (S, pf); break; case frame_type_U_DM: // Disconnect Mode dm_frame (S, pf); break; case frame_type_U_UA: // Unnumbered Acknowledge ua_frame (S, pf); break; case frame_type_U_FRMR: // Frame Reject frmr_frame (S); break; case frame_type_U_UI: // Unnumbered Information ui_frame (S, cr, pf); break; case frame_type_U_XID: // Exchange Identification { unsigned char *info_ptr; int info_len; info_len = ax25_get_info (E->pp, &info_ptr); xid_frame (S, cr, pf, info_ptr, info_len); } break; case frame_type_U_TEST: // Test { unsigned char *info_ptr; int info_len; info_len = ax25_get_info (E->pp, &info_ptr); test_frame (S, cr, pf, info_ptr, info_len); } break; case frame_type_U: // other Unnumbered, not used by AX.25. break; case frame_not_AX25: // Could not get control byte from frame. break; } // An incoming frame might have ack'ed frames we sent or indicated peer is no longer busy. // Rather than putting this test in many places, where those conditions, may have changed, // we will try to catch them all on this single path. // Start transmission if we now have some outgoing data ready to go. // (Added in 1.5 beta 3 for issue 132.) if ( S->i_frame_queue != NULL && (S->state == state_3_connected || S->state == state_4_timer_recovery) && ( ! S->peer_receiver_busy ) && WITHIN_WINDOW_SIZE(S) ) { //S->acknowledge_pending = 1; lm_seize_request (S->chan); } } /* end lm_data_indication */ /*------------------------------------------------------------------------------ * * Name: i_frame * * Purpose: Process I Frame. * * Inputs: S - Data Link State Machine. * cr - Command or Response. We have already issued an error if not command. * p - Poll bit. Assuming we checked earlier that it was a command. * The meaning is described below. * nr - N(R) from the frame. Next expected seq. for other end. * ns - N(S) from the frame. Seq. number of this incoming frame. * pid - protocol id. * info_ptr - pointer to information part of frame. * info_len - Number of bytes in information part of frame. * Should be in range of 0 thru n1_paclen. * * Description: * 6.4.2. Receiving I Frames * * The reception of I frames that contain zero-length information fields is reported to the next layer; no information * field will be transferred. * * 6.4.2.1. Not Busy * * If a TNC receives a valid I frame (one with a correct FCS and whose send sequence number equals the * receiver's receive state variable) and is not in the busy condition, it accepts the received I frame, increments its * receive state variable, and acts in one of the following manners: * * a) If it has an I frame to send, that I frame may be sent with the transmitted N(R) equal to its receive state * variable V(R) (thus acknowledging the received frame). Alternately, the TNC may send an RR frame with N(R) * equal to V(R), and then send the I frame. * * or b) If there are no outstanding I frames, the receiving TNC sends an RR frame with N(R) equal to V(R). The * receiving TNC may wait a small period of time before sending the RR frame to be sure additional I frames are * not being transmitted. * * 6.4.2.2. Busy * * If the TNC is in a busy condition, it ignores any received I frames without reporting this condition, other than * repeating the indication of the busy condition. * If a busy condition exists, the TNC receiving the busy condition indication polls the sending TNC periodically * until the busy condition disappears. * A TNC may poll the busy TNC periodically with RR or RNR frames with the P bit set to "1". * * 6.4.6. Receiving Acknowledgement * * Whenever an I or S frame is correctly received, even in a busy condition, the N(R) of the received frame is * checked to see if it includes an acknowledgement of outstanding sent I frames. The T1 timer is canceled if the * received frame actually acknowledges previously unacknowledged frames. If the T1 timer is canceled and there * are still some frames that have been sent that are not acknowledged, T1 is started again. If the T1 timer expires * before an acknowledgement is received, the TNC proceeds with the retransmission procedure outlined in Section * 6.4.11. * * * 6.2. Poll/Final (P/F) Bit Procedures * * The next response frame returned to an I frame with the P bit set to "1", received during the information * transfer state, is an RR, RNR or REJ response with the F bit set to "1". * * The next response frame returned to a S or I command frame with the P bit set to "1", received in the * disconnected state, is a DM response frame with the F bit set to "1". * *------------------------------------------------------------------------------*/ static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len) { switch (S->state) { case state_0_disconnected: // Logic from flow chart for "all other commands." if (cr == cr_cmd) { cmdres_t r = cr_res; // DM response with F taken from P. int f = p; int nopid = 0; // PID applies only for I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // Ignore it. Keep same state. break; case state_2_awaiting_release: // Logic from flow chart for "I, RR, RNR, REJ, SREJ commands." if (cr == cr_cmd && p == 1) { cmdres_t r = cr_res; // DM response with F = 1. int f = 1; int nopid = 0; // PID applies only for I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } break; case state_3_connected: case state_4_timer_recovery: // Look carefully. The original had two tiny differences between the two states. // In the 2006 version, these differences no longer exist. // Erratum: SDL asks: Is information field length <= N1 (paclen). // (github issue 102 - Thanks to KK6WHJ for pointing this out.) // Just because we are limiting the size of our transmitted data, it doesn't mean // that the other end will be doing the same. With v2.2, the XID frame can be // used to negotiate a maximum info length but with v2.0, there is no way for the // other end to know our paclen value. if (info_len >= 0 && info_len <= AX25_MAX_INFO_LEN) { if (is_good_nr(S,nr)) { // Erratum? // I wonder if this difference is intentional or if only one place was // was modified after a cut-n-paste of the flow chart segment. // Erratum: Discrepancy between original and 2006 version. // Pattern noticed: Anytime we have "is_good_nr" which tests for V(A) <= N(R) <= V(S), // we should always call "check_i_frame_ackd" or at least set V(A) from N(R). #if 0 // Erratum: original - states 3 & 4 differ here. if (S->state == state_3_connected) { // This sets "S->va = nr" and also does some timer stuff. check_i_frame_ackd (S,nr); } else { SET_VA(nr); } #else // 2006 version - states 3 & 4 same here. // This sets "S->va = nr" and also does some timer stuff. check_i_frame_ackd (S,nr); #endif // Erratum: v1.5 - My addition. // I noticed that we sometimes got stuck in state 4 and rc crept up slowly even though // we received 'I' frames with N(R) values indicating that the other side received everything // that we sent. Eventually rc could reach the limit and we would get an error. // If we are in state 4, and other guy ack'ed last I frame we sent, transition to state 3. // We had a similar situation for RR/RNR for cases other than response, F=1. if (S->state == state_4_timer_recovery && S->va == S->vs) { STOP_T1; select_t1_value (S); START_T3; SET_RC(0); enter_new_state (S, state_3_connected, __func__, __LINE__); } if (S->own_receiver_busy) { // This should be unreachable because we currently don't have a way to set own_receiver_busy. // But we might the capability someday so implement this while we are here. if (p == 1) { cmdres_t cr = cr_res; // Erratum: The use of "F" in the flow chart implies that RNR is a response // in this case, but I'm not confident about that. The text says frame. int f = 1; int nr = S->vr; packet_t pp; pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f, NULL, 0); // I wonder if this difference is intentional or if only one place was // was modified after a cut-n-paste of the flow chart segment. #if 0 // Erratum: Original - state 4 has expedited. if (S->state == state_4_timer_recovery) { lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // "expedited" } else { lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } #else // 2006 version - states 3 & 4 the same. lm_data_request (S->chan, TQ_PRIO_1_LO, pp); #endif S->acknowledge_pending = 0; } } else { // Own receiver not busy. i_frame_continued (S, p, ns, pid, info_ptr, info_len); } } else { // N(R) not in expected range. nr_error_recovery (S); // my enhancement. See below. enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } } else { // Bad information length. // Wouldn't even get to CRC check if not octet aligned. text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error O: Information part length, %d, not in range of 0 thru %d.\n", S->stream_id, info_len, AX25_MAX_INFO_LEN); establish_data_link (S); S->layer_3_initiated = 0; // The original spec always sent SABM and went to state 1. // I was thinking, why not use v2.2 instead of we were already connected with v2.2? // My version of establish_data_link combined the two original functions and // already uses SABME or SABM based on S->modulo. enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } break; } } /* end i_frame */ /*------------------------------------------------------------------------------ * * Name: i_frame_continued * * Purpose: The I frame processing logic gets pretty complicated. * Some of it has been split out into a separate function to make * things more readable. * We have already done some error checking and processed N(R). * This is used for both states 3 & 4. * * Inputs: S - Data Link State Machine. We are in state 3. * p - Poll bit. * ns - N(S) from the frame. Seq. number of this incoming frame. * pid - protocol id. * info_ptr - pointer to information part of frame. * info_len - Number of bytes in information part of frame. Already verified. * * Description: * * 4.3.2.3. Reject (REJ) Command and Response * * The reject frame requests retransmission of I frames starting with N(R). Any frames sent with a sequence * number of N(R)-1 or less are acknowledged. Additional I frames which may exist may be appended to the * retransmission of the N(R) frame. * Only one reject frame condition is allowed in each direction at a time. The reject condition is cleared by the * proper reception of I frames up to the I frame that caused the reject condition to be initiated. * The status of the TNC at the other end of the link is requested by sending a REJ command frame with the P bit * set to one. * * 4.3.2.4. Selective Reject (SREJ) Command and Response * * (Erratum: SREJ is only response with F bit.) * * The selective reject, SREJ, frame is used by the receiving TNC to request retransmission of the single I frame * numbered N(R). If the P/F bit in the SREJ frame is set to "1", then I frames numbered up to N(R)-1 inclusive are * considered as acknowledged. However, if the P/F bit in the SREJ frame is set to "0", then the N(R) of the SREJ * frame does not indicate acknowledgement of I frames. * * Each SREJ exception condition is cleared (reset) upon receipt of the I frame with an N(S) equal to the N(R) * of the SREJ frame. * * A receiving TNC may transmit one or more SREJ frames, each containing a different N(R) with the P bit set * to "0", before one or more earlier SREJ exception conditions have been cleared. However, a SREJ is not * transmitted if an earlier REJ exception condition has not been cleared as indicated in Section 4.5.4. (To do so * would request retransmission of an I frame that would be retransmitted by the REJ operation.) Likewise, a REJ * frame is not transmitted if one or more earlier SREJ exception conditions have not been cleared as indicated in * * Section 4.5.4. * * I frames transmitted following the I frame indicated by the SREJ frame are not retransmitted as the result of * receiving a SREJ frame. Additional I frames awaiting initial transmission may be transmitted following the * retransmission of the specific I frame requested by the SREJ frame. * * * 6.4.4. Reception of Out-of-Sequence Frames * * 6.4.4.1. Implicit Reject (REJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, the frame is discarded. A REJ frame is sent with a receive sequence number * equal to one higher than the last correctly received I frame if an uncleared N(S) sequence error condition has not * been previously established. The received state variable and poll bit of the discarded frame is checked and acted * upon, if necessary. * This mode requires no frame queueing and frame resequencing at the receiver. However, because the mode * requires transmission of frames that may not be in error, its throughput is not as high as selective reject. This * mode is ineffective on systems with long round-trip delays and high data rates. * * 6.4.4.2. Selective Reject (SREJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number * equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously * established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable * and poll bit of the received frame are checked and acted upon, if necessary. * This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can * consume precious buffer space, especially if the user device has limited memory available and several active * links are operational. * * 6.4.4.3. Selective Reject-Reject (SREJ/REJ) * * (Erratum: REJ/SREJ should not be mixed. Basic (mod 8) allows only REJ. * Extended (mod 128) gives you a choice of one or the other for a link.) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted. * All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is * missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The * received state variable and poll bit of the received frame are checked and acted upon. If another frame error * occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame * and discards frames received after the second errored frame until the first errored frame is recovered. Then, a * REJ is issued to recover the second errored frame and all subsequent discarded frames. * *------------------------------------------------------------------------------*/ static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len) { if (ns == S->vr) { // The receive sequence number, N(S), is the same as what we were expecting, V(R). // Send it to the application and increment the next expected. // It is possible that this was resent and we tucked away others with the following // sequence numbers. If so, process them too. SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__)); S->reject_exception = 0; if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); ax25_safe_print (info_ptr, info_len, 1); dw_printf ("\"\n"); } dl_data_indication (S, pid, info_ptr, info_len); if (S->rxdata_by_ns[ns] != NULL) { // There is a possibility that we might have another received frame stashed // away from 8 or 128 (modulo) frames back. Remove it so it doesn't accidently // show up at some future inopportune time. cdata_delete (S->rxdata_by_ns[ns]); S->rxdata_by_ns[ns] = NULL; } while (S->rxdata_by_ns[S->vr] != NULL) { // dl_data_indication - send connected data to client application. if (s_debug_client_app) { text_color_set(DW_COLOR_DEBUG); dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, data=\"", __func__, __LINE__, ns, S->vr); ax25_safe_print (S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len, 1); dw_printf ("\"\n"); } dl_data_indication (S, S->rxdata_by_ns[S->vr]->pid, S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len); // Don't keep around anymore after sending it to client app. cdata_delete (S->rxdata_by_ns[S->vr]); S->rxdata_by_ns[S->vr] = NULL; SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__)); } if (p) { // Mentioned in section 6.2. // The next response frame returned to an I frame with the P bit set to "1", received during the information // transfer state, is an RR, RNR or REJ response with the F bit set to "1". int f = 1; int nr = S->vr; // Next expected sequence number. cmdres_t cr = cr_res; // response with F set to 1. packet_t pp; pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } else if ( ! S->acknowledge_pending) { S->acknowledge_pending = 1; // Probably want to set this before the LM-SEIZE Request // in case the LM-SEIZE Confirm gets processed before we // return from it. // Force start of transmission even if the transmit frame queue is empty. // Notify me, with lm_seize_confirm, when transmission has started. // When that event arrives, we check acknowledge_pending and send something // to be determined later. lm_seize_request (S->chan); } } else if (S->reject_exception) { // This is not the sequence we were expecting. // We previously sent REJ, asking for a resend so don't send another. // In this case, send RR only if the Poll bit is set. // Again, reference section 6.2. if (p) { int f = 1; int nr = S->vr; // Next expected sequence number. cmdres_t cr = cr_res; // response with F set to 1. packet_t pp; pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } } else if (S->srej_enable == srej_none) { // The received sequence number is not the expected one and we can't use SREJ. // The old v2.0 approach is to send and REJ with the number we are expecting. // This can be very inefficient. For example if we received 1,3,4,5,6 in one transmission, // we discard 3,4,5,6, and tell the other end to resend everything starting with 2. // At one time, I had some doubts about when to use command or response for REJ. // I now believe that reponse, as implied by setting F in the flow chart, is correct. int f = p; int nr = S->vr; // Next expected sequence number. cmdres_t cr = cr_res; // response with F copied from P in I frame. packet_t pp; S->reject_exception = 1; if (s_debug_retry) { text_color_set(DW_COLOR_ERROR); // make it more noticable. dw_printf ("sending REJ, at %s %d, SREJ not enabled case, V(R)=%d", __func__, __LINE__, S->vr); } pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_REJ, S->modulo, nr, f, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } else { // Selective reject is enabled so we can use the more efficient method. // This is normally enabled for v2.2 but XID can be used to change that. // First we save the current frame so we can retrieve it later after getting the fill in. if (S->modulo != 128) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: Should not be sending SREJ in basic (modulo 8) mode.\n"); } #if 1 // Erratum: AX.25 protocol spec did not handle SREJ very well. // Based on X.25 section 2.4.6.4. if (is_ns_in_window(S, ns)) { // X.25 2.4.6.4 (b) // v(R) < N(S) < V(R)+k so it is in the expected range. // Save it in the receive buffer. if (S->rxdata_by_ns[ns] != NULL) { cdata_delete (S->rxdata_by_ns[ns]); S->rxdata_by_ns[ns] = NULL; } S->rxdata_by_ns[ns] = cdata_new(pid, info_ptr, info_len); if (s_debug_misc) { dw_printf ("%s %d, save to rxdata_by_ns N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); ax25_safe_print (info_ptr, info_len, 1); dw_printf ("\"\n"); } if (p == 1) { int f = 1; enquiry_response (S, frame_type_I, f); } else if (S->own_receiver_busy) { cmdres_t cr = cr_res; // send RNR response int f = 0; // we know p=0 here. int nr = S->vr; packet_t pp; pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } else if (S->rxdata_by_ns[ AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__)] == NULL) { // Ask for missing frames when we don't have N(S)-1 in the receive buffer. // In version 1.4: // We end up sending more SREJ than necessary and and get back redundant information. Example: // When we see 113 missing, we ask for a resend. // When we see 115 & 116 missing, a cummulative SREJ asks for everything. // The other end dutifully sends 113 twice. // // [0.4] DW1>DW0:(SREJ res, n(r)=113, f=0) // [0.4] DW1>DW0:(SREJ res, n(r)=113, f=1)<0xe6><0xe8> // // [0L] DW0>DW1:(I cmd, n(s)=113, n(r)=11, p=0, pid=0xf0)0114 send data<0x0d> // [0L] DW0>DW1:(I cmd, n(s)=113, n(r)=11, p=0, pid=0xf0)0114 send data<0x0d> // [0L] DW0>DW1:(I cmd, n(s)=115, n(r)=11, p=0, pid=0xf0)0116 send data<0x0d> // [0L] DW0>DW1:(I cmd, n(s)=116, n(r)=11, p=0, pid=0xf0)0117 send data<0x0d> // Version 1.5: // Don't generate duplicate requests for gaps in the same transmission. // Ideally, we might wait until carrier drops and then use one Multi-SREJ for entire transmission but // we will keep that for another day. // Probably need a flag similar to acknowledge_pending (or ask_resend_count, here) and the ask_for_resend array. // It could then be processed first in lm_seize_confirm. int ask_for_resend[128]; int ask_resend_count = 0; int x; // Version 1.5 // Erratum: AX.25 says use F=0 here. Doesn't make sense. // We would want to set F when sending N(R) = V(R). // int allow_f1 = 0; // F=1 from X.25 2.4.6.4 b) 3) int allow_f1 = 1; // F=1 from X.25 2.4.6.4 b) 3) // send only for this gap, not cummulative from V(R). int last = AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__); int first = last; while (first != S->vr && S->rxdata_by_ns[AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__)] == NULL) { first = AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__); } x = first; do { ask_for_resend[ask_resend_count++] = AX25MODULO(x, S->modulo, __FILE__, __func__, __LINE__); x = AX25MODULO(x + 1, S->modulo, __FILE__, __func__, __LINE__); } while (x != AX25MODULO(last + 1, S->modulo, __FILE__, __func__, __LINE__)); send_srej_frames (S, ask_for_resend, ask_resend_count, allow_f1); } } else { // X.25 2.4.6.4 a) // N(S) is not in expected range. Discard it. Send response if P=1. if (p == 1) { int f = 1; enquiry_response (S, frame_type_I, f); } } #else // my earlier attempt before taking a close look at X.25 spec. // Keeping it around for a little while because I might want to // use earlier technique of sending only needed SREJ for any second // and later gaps in a single multiframe transmission. if (S->rxdata_by_ns[ns] != NULL) { cdata_delete (S->rxdata_by_ns[ns]); S->rxdata_by_ns[ns] = NULL; } S->rxdata_by_ns[ns] = cdata_new(pid, info_ptr, info_len); S->outstanding_srej[ns] = 0; // Don't care if it was previously set or not. // We have this one so there is no outstanding SREJ for it. if (s_debug_misc) { dw_printf ("%s %d, save to rxdata_by_ns N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr); ax25_safe_print (info_ptr, info_len, 1); dw_printf ("\"\n"); } if (selective_reject_exception(S) == 0) { // Erratum: This is vastly different than the SDL in the AX.25 protocol spec. // That would use SREJ if only one was missing and REJ instead. // Here we do not mix the them. // This agrees with the X.25 protocol spec that says use one or the other. Not both. // Suppose we had incoming I frames 0, 3, 7. // 0 was already processed and V(R)=1 meaning that is the next expected. // At this point we area processing N(S)=3. // In this case, we need to ask for a resend of 1 & 2. // More generally, the range of V(R) thru N(S)-1. int ask_for_resend[128]; int ask_resend_count = 0; int i; int allow_f1 = 1; text_color_set(DW_COLOR_ERROR); dw_printf ("%s:%d, zero exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__, S->vr, ns); for (i = S->vr; i != ns; i = AX25MODULO(i+1, S->modulo, __FILE__, __func__, __LINE__)) { ask_for_resend[ask_resend_count++] = i; } send_srej_frames (S, ask_for_resend, ask_resend_count, allow_f1); } else { // Erratum: The SDL says ask for N(S) which is clearly wrong because that's what we just received. // Instead we want to ask for any missing frames up to but not including N(S). // Let's continue with the example above. I frames with N(S) of 0, 3, 7. // selective_reject_exception is non zero meaning there are outstanding requests to resend specified I frames. // V(R) is still 1 because 0 is the last one received with contiguous N(S) values. // 3 has been saved into S->rxdata_by_ns. // We now have N(S)=7. We want to ask for a resend of 4, 5, 6. // This can be achieved by searching S->rxdata_by_ns, starting with N(S)-1, and counting // how many empty slots we have before finding a saved frame. int ask_resend_count = 0; int first; text_color_set(DW_COLOR_ERROR); dw_printf ("%s:%d, %d srej exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__, selective_reject_exception(S), S->vr, ns); first = AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__); while (S->rxdata_by_ns[first] == NULL) { if (first == AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__)) { // Oops! Went too far. This I frame was already processed. text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR calulating what to put in SREJ, %s line %d\n", __func__, __LINE__); dw_printf ("V(R)=%d, N(S)=%d, SREJ exception=%d, first=%d, ask_resend_count=%d\n", S->vr, ns, selective_reject_exception(S), first, ask_resend_count); int k; for (k=0; k<128; k++) { if (S->rxdata_by_ns[k] != NULL) { dw_printf ("rxdata_by_ns[%d] has data\n", k); } } break; } ask_resend_count++; first = AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__); } // Go beyond the slot where we already have an I frame. first = AX25MODULO(first + 1, S->modulo, __FILE__, __func__, __LINE__); // The ask_resend_count could be 0. e.g. We got 4 rather than 7 in this example. if (ask_resend_count > 0) { int ask_for_resend[128]; int n; int allow_f1 = 1; for (n = 0; n < ask_resend_count; n++) { ask_for_resend[n] = AX25MODULO(first + n, S->modulo, __FILE__, __func__, __LINE__);; } send_srej_frames (S, ask_for_resend, ask_resend_count, allow_f1); } } /* end SREJ exception */ #endif // my earlier attempt. // Erratum: original has following but 2006 rev does not. // I think the 2006 version is correct. // SREJ does not always satisfy the need for ack. // There is a special case where F=1. We take care of that inside of send_srej_frames. #if 0 S->acknowledge_pending = 0; #endif } /* end srej enabled */ } /* end i_frame_continued */ /*------------------------------------------------------------------------------ * * Name: is_ns_in_window * * Purpose: Is the N(S) value of the incoming I frame in the expected range? * * Inputs: ns - Sequence from I frame. * * Description: With selective reject, it is possible that we could receive a repeat of * an I frame with N(S) less than V(R). In this case, we just want to * discard it rather than getting upset and reestablishing the connection. * * The X.25 spec,section 2.4.6.4 (b) asks whether V(R) < N(S) < V(R)+k. * * The problem here is that it depends on the value of k for the other end. * X.25 says that both sides need to agree on a common value of k ahead of time. * We might have k=8 for our sending but the other side could have k=32 so * this test could fail. * * As a hack, we could use the value 63 here. If too small we would discard * I frames that are in the acceptable range because they would be >= V(R)+k. * On the other hand, if this value is too big, the range < V(R) would not be * large enough and we would accept frame we shouldn't. * As a practical matter, using a window size that large is pretty unlikely. * Maybe I could put a limit of 63, rather than 127 in the configuration. * *------------------------------------------------------------------------------*/ #define GENEROUS_K 63 static int is_ns_in_window (ax25_dlsm_t *S, int ns) { int adjusted_vr, adjusted_ns, adjusted_vrpk; int result; /* Shift all values relative to V(R) before comparing so we won't have wrap around. */ #define adjust_by_vr(x) (AX25MODULO((x) - S->vr, S->modulo, __FILE__, __func__, __LINE__)) adjusted_vr = adjust_by_vr(S->vr); // A clever compiler would know it is zero. adjusted_ns = adjust_by_vr(ns); adjusted_vrpk = adjust_by_vr(S->vr + GENEROUS_K); result = adjusted_vr < adjusted_ns && adjusted_ns < adjusted_vrpk; if (s_debug_retry) { text_color_set(DW_COLOR_DEBUG); dw_printf ("is_ns_in_window, V(R) %d < N(S) %d < V(R)+k %d, returns %d\n", S->vr, ns, S->vr + GENEROUS_K, result); } return (result); } /*------------------------------------------------------------------------------ * * Name: send_srej_frames * * Purpose: Ask for a resend of I frames with specified sequence numbers. * * Inputs: resend - Array of N(S) values for missing I frames. * * count - Number of items in array. * * allow_f1 - When true, set F=1 when asking for V(R). * * X.25 section 2.4.6.4 b) 3) says F should be set to 0 * when receiving I frame out of sequence. * * X.25 sections 2.4.6.11 & 2.3.5.2.2 say set F to 1 when * responding to command with P=1. (our enquiry_response function). * * Version 1.5: The X.25 protocol spec allows additional sequence numbers in one frame * by using the INFO part. * By default that feature is off but can be negotiated with XID. * We should be able to use this between two direwolf stations while * maintaining compatibility with the original AX.25 v2.2. * *------------------------------------------------------------------------------*/ static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1) { int f; // Set if we are ack-ing one before. int nr; cmdres_t cr = cr_res; // SREJ is always response. int i; packet_t pp; if (count <= 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, count=%d, %s line %d\n", count, __func__, __LINE__); return; } if (s_debug_retry) { text_color_set(DW_COLOR_INFO); dw_printf ("%s line %d\n", __func__, __LINE__); //dw_printf ("state=%d, count=%d, k=%d, V(R)=%d, SREJ exeception=%d\n", S->state, count, S->k_maxframe, S->vr, selective_reject_exception(S)); dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr); dw_printf ("resend[]="); for (i = 0; i < count; i++) { dw_printf (" %d", resend[i]); } dw_printf ("\n"); dw_printf ("rxdata_by_ns[]="); for (i = 0; i < 128; i++) { if (S->rxdata_by_ns[i] != NULL) { dw_printf (" %d", i); } } dw_printf ("\n"); } // Something is wrong! We ask for more than the window size. if (count > S->k_maxframe) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR - Extreme number of SREJ, %s line %d\n", __func__, __LINE__); dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr); dw_printf ("resend[]="); for (i = 0; i < count; i++) { dw_printf (" %d", resend[i]); } dw_printf ("\n"); dw_printf ("rxdata_by_ns[]="); for (i = 0; i < 128; i++) { if (S->rxdata_by_ns[i] != NULL) { dw_printf (" %d", i); } } dw_printf ("\n"); } // Multi-SREJ - Use info part for additional sequence number(s) instead of sending separate SREJ for each. if (S->srej_enable == srej_multi && count > 1) { unsigned char info[128]; int info_len = 0; for (i = 1; i < count; i++) { // skip first one if (resend[i] < 0 || resend[i] >= S->modulo) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, additional nr=%d, modulo=%d, %s line %d\n", resend[i], S->modulo, __func__, __LINE__); } // There is also a form to specify a range but I don't // think it is worth the effort to generate it. Maybe later. if (S->modulo == 8) { info[info_len++] = resend[i] << 5; } else { info[info_len++] = resend[i] << 1; } } f = 0; nr = resend[0]; f = allow_f1 && (nr == S->vr); // Possibly set if we are asking for the next after // the last one received in contiguous order. // This could only apply to the first in // the list so this would not go in the loop. if (f) { // In this case the other end is being // informed of my V(R) so no additional // RR etc. is needed. // TODO: Need to think about this. S->acknowledge_pending = 0; } if (nr < 0 || nr >= S->modulo) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, nr=%d, modulo=%d, %s line %d\n", nr, S->modulo, __func__, __LINE__); nr = AX25MODULO(nr, S->modulo, __FILE__, __func__, __LINE__); } pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_SREJ, S->modulo, nr, f, info, info_len); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); return; } // Multi-SREJ not enabled. Send separate SREJ for each desired sequence number. for (i = 0; i < count; i++) { nr = resend[i]; f = allow_f1 && (nr == S->vr); // Possibly set if we are asking for the next after // the last one received in contiguous order. if (f) { // In this case the other end is being // informed of my V(R) so no additional // RR etc. is needed. S->acknowledge_pending = 0; } if (nr < 0 || nr >= S->modulo) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, nr=%d, modulo=%d, %s line %d\n", nr, S->modulo, __func__, __LINE__); nr = AX25MODULO(nr, S->modulo, __FILE__, __func__, __LINE__); } pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_SREJ, S->modulo, nr, f, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } } /* end send_srej_frames */ /*------------------------------------------------------------------------------ * * Name: rr_rnr_frame * * Purpose: Process RR or RNR Frame. * Processing is the essentially the same so they are handled by a single function. * * Inputs: S - Data Link State Machine. * ready - True for RR, false for RNR * cr - Is this command or response? * pf - Poll/Final bit. * nr - N(R) from the frame. * * Description: 4.3.2.1. Receive Ready (RR) Command and Response * * Receive Ready accomplishes the following: * a) indicates that the sender of the RR is now able to receive more I frames; * b) acknowledges properly received I frames up to, and including N(R)-1;and * c) clears a previously-set busy condition created by an RNR command having been sent. * The status of the TNC at the other end of the link can be requested by sending an RR command frame with the * P-bit set to one. * * 4.3.2.2. Receive Not Ready (RNR) Command and Response * * Receive Not Ready indicates to the sender of I frames that the receiving TNC is temporarily busy and cannot * accept any more I frames. Frames up to N(R)-1 are acknowledged. Frames N(R) and above that may have been * transmitted are discarded and must be retransmitted when the busy condition clears. * The RNR condition is cleared by the sending of a UA, RR, REJ or SABM(E) frame. * The status of the TNC at the other end of the link is requested by sending an RNR command frame with the * P bit set to one. * *------------------------------------------------------------------------------*/ static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr) { // dw_printf ("rr_rnr_frame (ready=%d, cr=%d, pf=%d, nr=%d) state=%d\n", ready, cr, pf, nr, S->state); switch (S->state) { case state_0_disconnected: if (cr == cr_cmd) { cmdres_t r = cr_res; // DM response with F taken from P. int f = pf; int nopid = 0; // PID only for I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // do nothing. break; case state_2_awaiting_release: // Logic from flow chart for "I, RR, RNR, REJ, SREJ commands." if (cr == cr_cmd && pf == 1) { cmdres_t r = cr_res; // DM response with F = 1. int f = 1; int nopid = 0; // PID applies only for I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } // Erratum: We have a disagreement here between original and 2006 version. // RR, RNR, REJ, SREJ responses would fall under "all other primitives." // In the original, we simply ignore it and stay in state 2. // The 2006 version, page 94, says go into "1 awaiting connection" state. // That makes no sense to me. break; case state_3_connected: S->peer_receiver_busy = ! ready; // Erratum: the flow charts have unconditional check_need_for_response here. // I don't recall exactly why I added the extra test for command and P=1. // It might have been because we were reporting error A for response with F=1. // Other than avoiding that error message, this is functionally equivalent. if (cr == cr_cmd && pf) { check_need_for_response (S, ready ? frame_type_S_RR : frame_type_S_RNR, cr, pf); } if (is_good_nr(S,nr)) { // dw_printf ("rr_rnr_frame (), line %d, state=%d, good nr=%d, calling check_i_frame_ackd\n", __LINE__, S->state, nr); check_i_frame_ackd (S, nr); } else { if (s_debug_retry) { text_color_set(DW_COLOR_DEBUG); dw_printf ("rr_rnr_frame (), line %d, state=%d, bad nr, calling nr_error_recovery\n", __LINE__, S->state); } nr_error_recovery (S); // My enhancement. Original always sent SABM and went to state 1. enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } break; case state_4_timer_recovery: S->peer_receiver_busy = ! ready; if (cr == cr_res && pf == 1) { // RR/RNR Response with F==1. if (s_debug_retry) { text_color_set(DW_COLOR_DEBUG); dw_printf ("rr_rnr_frame (), Response, f=%d, line %d, state=%d, good nr, calling check_i_frame_ackd\n", pf, __LINE__, S->state); } STOP_T1; select_t1_value(S); if (is_good_nr(S,nr)) { SET_VA(nr); if (S->vs == S->va) { // all caught up with ack from other guy. START_T3; SET_RC(0); // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); } else { invoke_retransmission (S, nr); // my addition // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R) so no need for extra RR at the end only for that. STOP_T3; START_T1; S->acknowledge_pending = 0; // end of my addition } } else { nr_error_recovery (S); // Erratum: Another case of my enhancement. // The flow charts go into state 1 after nr_error_recovery. // I use state 5 instead if we were oprating in extended (modulo 128) mode. enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } } else { // RR/RNR command, either P value. // RR/RNR response, F==0 if (cr == cr_cmd && pf == 1) { int f = 1; enquiry_response (S, ready ? frame_type_S_RR : frame_type_S_RNR, f); } if (is_good_nr(S,nr)) { SET_VA(nr); // Erratum: v1.5 - my addition. // I noticed that we sometimes got stuck in state 4 and rc crept up slowly even though // we received RR frames with N(R) values indicating that the other side received everything // that we sent. Eventually rc could reach the limit and we would get an error. // If we are in state 4, and other guy ack'ed last I frame we sent, transition to state 3. // The same thing was done for receving I frames after check_i_frame_ackd. // Thought: Could we simply call check_i_frame_ackd, for consistency, rather than only setting V(A)? if (cr == cr_res && pf == 0) { if (S->vs == S->va) { // all caught up with ack from other guy. STOP_T1; select_t1_value (S); START_T3; SET_RC(0); enter_new_state (S, state_3_connected, __func__, __LINE__); } } } else { nr_error_recovery (S); enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } } break; } } /* end rr_rnr_frame */ /*------------------------------------------------------------------------------ * * Name: rej_frame * * Purpose: Process REJ Frame. * * Inputs: S - Data Link State Machine. * cr - Is this command or response? * pf - Poll/Final bit. * nr - N(R) from the frame. * * Description: 4.3.2.2. Receive Not Ready (RNR) Command and Response * * ... The RNR condition is cleared by the sending of a UA, RR, REJ or SABM(E) frame. ... * * * 4.3.2.3. Reject (REJ) Command and Response * * The reject frame requests retransmission of I frames starting with N(R). Any frames sent with a sequence * number of N(R)-1 or less are acknowledged. Additional I frames which may exist may be appended to the * retransmission of the N(R) frame. * Only one reject frame condition is allowed in each direction at a time. The reject condition is cleared by the * proper reception of I frames up to the I frame that caused the reject condition to be initiated. * The status of the TNC at the other end of the link is requested by sending a REJ command frame with the P bit * set to one. * * 4.4.3. Reject (REJ) Recovery * * The REJ frame requests a retransmission of I frames following the detection of a N(S) sequence error. Only * one outstanding "sent REJ" condition is allowed at a time. This condition is cleared when the requested I frame * has been received. * A TNC receiving the REJ command clears the condition by resending all outstanding I frames (up to the * window size), starting with the frame indicated in N(R) of the REJ frame. * * * 4.4.5.1. T1 Timer Recovery * * If a transmission error causes a TNC to fail to receive (or to receive and discard) a single I frame, or the last I * frame in a sequence of I frames, then the TNC does not detect a send-sequence-number error and consequently * does not transmit a REJ/SREJ. The TNC that transmitted the unacknowledged I frame(s) following the completion * of timeout period T1, takes appropriate recovery action to determine when I frame retransmission as described * in Section 6.4.10 should begin. This condition is cleared by the reception of an acknowledgement for the sent * frame(s), or by the link being reset. * * 6.2. Poll/Final (P/F) Bit Procedures * * The response frame returned by a TNC depends on the previous command received, as described in the * following paragraphs. * ... * * The next response frame returned to an I frame with the P bit set to "1", received during the information5 * transfer state, is an RR, RNR or REJ response with the F bit set to "1". * * The next response frame returned to a supervisory command frame with the P bit set to "1", received during * the information transfer state, is an RR, RNR or REJ response frame with the F bit set to "1". * ... * * The P bit is used in conjunction with the timeout recovery condition discussed in Section 4.5.5. * When not used, the P/F bit is set to "0". * * 6.4.4.1. Implicit Reject (REJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, the frame is discarded. A REJ frame is sent with a receive sequence number * equal to one higher than the last correctly received I frame if an uncleared N(S) sequence error condition has not * been previously established. The received state variable and poll bit of the discarded frame is checked and acted * upon, if necessary. * This mode requires no frame queueing and frame resequencing at the receiver. However, because the mode * requires transmission of frames that may not be in error, its throughput is not as high as selective reject. This * mode is ineffective on systems with long round-trip delays and high data rates. * * 6.4.7. Receiving REJ * * After receiving a REJ frame, the transmitting TNC sets its send state variable to the same value as the REJ * frame's received sequence number in the control field. The TNC then retransmits any I frame(s) outstanding at * the next available opportunity in accordance with the following: * * a) If the TNC is not transmitting at the time and the channel is open, the TNC may begin retransmission of the * I frame(s) immediately. * b) If the TNC is operating on a full-duplex channel transmitting a UI or S frame when it receives a REJ frame, * it may finish sending the UI or S frame and then retransmit the I frame(s). * c) If the TNC is operating in a full-duplex channel transmitting another I frame when it receives a REJ frame, * it may abort the I frame it was sending and start retransmission of the requested I frames immediately. * d) The TNC may send just the one I frame outstanding, or it may send more than the one indicated if more I * frames followed the first unacknowledged frame, provided that the total to be sent does not exceed the flowcontrol * window (k frames). * If the TNC receives a REJ frame with the poll bit set, it responds with either an RR or RNR frame with the * final bit set before retransmitting the outstanding I frame(s). * *------------------------------------------------------------------------------*/ static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr) { switch (S->state) { case state_0_disconnected: // states 0 and 2 are very similar with one tiny little difference. if (cr == cr_cmd) { cmdres_t r = cr_res; // DM response with F taken from P. int f = pf; int nopid = 0; // PID is only for I and UI. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // Do nothing. break; case state_2_awaiting_release: if (cr == cr_cmd && pf == 1) { cmdres_t r = cr_res; // DM response with F = 1. int f = 1; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } // Erratum: We have a disagreement here between original and 2006 version. // RR, RNR, REJ, SREJ responses would fall under "all other primitives." // In the original, we simply ignore it and stay in state 2. // The 2006 version, page 94, says go into "1 awaiting connection" state. // That makes no sense to me. break; case state_3_connected: S->peer_receiver_busy = 0; // Receipt of the REJ "frame" (either command or response) causes us to // start resending I frames at the specified number. // I think there are 3 possibilities here: // Response is used when incoming I frame processing detects one is missing. // In this case, F is copied from the I frame P bit. I don't think we care here. // Command with P=1 is used during timeout recovery. // The rule is that we are supposed to send a response with F=1 for I, RR, RNR, or REJ with P=1. check_need_for_response (S, frame_type_S_REJ, cr, pf); if (is_good_nr(S,nr)) { SET_VA(nr); STOP_T1; STOP_T3; select_t1_value(S); invoke_retransmission (S, nr); // my addition // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R) so no need for extra RR at the end only for that. // We ran into cases where I frame(s) would be resent but lost. // T1 was stopped so we just waited and waited and waited instead of trying again. // I added the following after each invoke_retransmission. // This seems clearer than hiding the timer stuff inside of it. // T3 is already stopped. START_T1; S->acknowledge_pending = 0; } else { nr_error_recovery (S); enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } break; case state_4_timer_recovery: S->peer_receiver_busy = 0; if (cr == cr_res && pf == 1) { STOP_T1; select_t1_value(S); if (is_good_nr(S,nr)) { SET_VA(nr); if (S->vs == S->va) { START_T3; SET_RC(0); // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); } else { invoke_retransmission (S, nr); // my addition. // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R) so no need for extra RR at the end only for that. STOP_T3; START_T1; S->acknowledge_pending = 0; } } else { nr_error_recovery (S); enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } } else { if (cr == cr_cmd && pf == 1) { int f = 1; enquiry_response (S, frame_type_S_REJ, f); } if (is_good_nr(S,nr)) { SET_VA(nr); if (S->vs != S->va) { // Observation: RR/RNR state 4 is identical but it doesn't have invoke_retransmission here. invoke_retransmission (S, nr); // my addition. // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R) so no need for extra RR at the end only for that. STOP_T3; START_T1; S->acknowledge_pending = 0; } } else { nr_error_recovery (S); enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } } break; } } /* end rej_frame */ /*------------------------------------------------------------------------------ * * Name: srej_frame * * Purpose: Process SREJ Response. * * Inputs: S - Data Link State Machine. * cr - Is this command or response? * f - Final bit. When set, it is ack-ing up thru N(R)-1 * nr - N(R) from the frame. Peer has asked for a resend of I frame with this N(S). * info - Information field, used only for Multi-SREJ * info_len - Information field length, bytes. * * Description: 4.3.2.4. Selective Reject (SREJ) Command and Response * * The selective reject, SREJ, frame is used by the receiving TNC to request retransmission of the single I frame * numbered N(R). If the P/F bit in the SREJ frame is set to "1", then I frames numbered up to N(R)-1 inclusive are * considered as acknowledged. However, if the P/F bit in the SREJ frame is set to "0", then the N(R) of the SREJ * frame does not indicate acknowledgement of I frames. * * Each SREJ exception condition is cleared (reset) upon receipt of the I frame with an N(S) equal to the N(R) * of the SREJ frame. * * A receiving TNC may transmit one or more SREJ frames, each containing a different N(R) with the P bit set * to "0", before one or more earlier SREJ exception conditions have been cleared. However, a SREJ is not * transmitted if an earlier REJ exception condition has not been cleared as indicated in Section 4.5.4. (To do so * would request retransmission of an I frame that would be retransmitted by the REJ operation.) Likewise, a REJ * frame is not transmitted if one or more earlier SREJ exception conditions have not been cleared as indicated in * Section 4.5.4. * * I frames transmitted following the I frame indicated by the SREJ frame are not retransmitted as the result of * receiving a SREJ frame. Additional I frames awaiting initial transmission may be transmitted following the * retransmission of the specific I frame requested by the SREJ frame. * * Erratum: The section above always refers to SREJ "frames." There doesn't seem to be any clue about when * command vs. response would be used. When we look in the flow charts, we see that we generate only * responses but the code is there to process command and response slightly differently. * * Description: 4.4.4. Selective Reject (SREJ) Recovery * * The SREJ command/response initiates more-efficient error recovery by requesting the retransmission of a * single I frame following the detection of a sequence error. This is an advancement over the earlier versions in * which the requested I frame was retransmitted togther with all additional I frames subsequently transmitted and * successfully received. * * When a TNC sends one or more SREJ commands, each with the P bit set to "0" or "1", or one or more SREJ * responses, each with the F bit set to "0", and the "sent SREJ" conditions are not cleared when the TNC is ready * to issue the next response frame with the F bit set to "1", the TNC sends a SREJ response with the F bit set to "1", * with the same N(R) as the oldest unresolved SREJ frame. * * Because an I or S format frame with the F bit set to "1" can cause checkpoint retransmission, a TNC does not * send SREJ frames until it receives at least one in-sequence I frame, or it perceives by timeout that the checkpoint * retransmission will not be initiated at the remote TNC. * * With respect to each direction of transmission on the data link, one or more "sent SREJ" exception conditions * from a TNC to another TNC may be established at a time. A "sent SREJ" exception condition is cleared when * the requested I frame is received. * * The SREJ frame may be repeated when a TNC perceives by timeout that a requested I frame will not be * received, because either the requested I frame or the SREJ frame was in error or lost. * * When appropriate, a TNC receiving one or more SREJ frames initiates retransmission of the individual I * frames indicated by the N(R) contained in each SREJ frame. After having retransmitted the above frames, new * I frames are transmitted later if they become available. * * When a TNC receives and acts on one or more SREJ commands, each with the P bit set to "0", or an SREJ * command with the P bit set to "1", or one or more SREJ responses each with the F bit set to "0", it disables any * action on the next SREJ response frame if that SREJ frame has the F bit set to "1" and has the same N(R) (i.e., * the same value and the same numbering cycle) as a previously actioned SREJ frame, and if the resultant * retransmission was made following the transmission of the P bit set to a "1". * When the SREJ mechanism is used, the receiving station retains correctly-received I frames and delivers * them to the higher layer in sequence number order. * * * 6.4.4.2. Selective Reject (SREJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number * equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously * established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable * and poll bit of the received frame are checked and acted upon, if necessary. * * This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can * consume precious buffer space, especially if the user device has limited memory available and several active * links are operational. * * * 6.4.4.3. Selective Reject-Reject (SREJ/REJ) * * When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current * receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted. * All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is * missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The * received state variable and poll bit of the received frame are checked and acted upon. If another frame error * occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame * and discards frames received after the second errored frame until the first errored frame is recovered. Then, a * REJ is issued to recover the second errored frame and all subsequent discarded frames. * * X.25: States that SREJ is only response. I'm following that and it simplifies matters. * * X.25 2.4.6.6.1 & 2.4.6.6.2 make a distinction between F being 0 or 1 besides copying N(R) into V(A). * They talk about sending a poll under some conditions. * We don't do that here. It seems to work reliably so leave well enough alone. * *------------------------------------------------------------------------------*/ static int resend_for_srej (ax25_dlsm_t *S, int nr, unsigned char *info, int info_len); static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int f, int nr, unsigned char *info, int info_len) { switch (S->state) { case state_0_disconnected: break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // Do nothing. // Erratum: The original spec said stay in same state. (Seems correct.) // 2006 revision shows state 5 transitioning into 1. I think that is wrong. // probably a cut-n-paste from state 1 to 5 and that part not updated. break; case state_2_awaiting_release: // Erratum: Flow chart says send DM(F=1) for "I, RR, RNR, REJ, SREJ commands" and P=1. // It is wrong for two reasons. // If SREJ was a command, the P flag has a different meaning than the other Supervisory commands. // It means ack reception of frames up thru N(R)-1; it is not a poll to get a response. // Based on X.25, I don't think SREJ can be a command. // It should say, "I, RR, RNR, REJ commands" // Erratum: We have a disagreement here between original and 2006 version. // RR, RNR, REJ, SREJ responses would fall under "all other primitives." // In the original, we simply ignore it and stay in state 2. // The 2006 version, page 94, says go into "1 awaiting connection" state. // That makes no sense to me. break; case state_3_connected: S->peer_receiver_busy = 0; // Erratum: Flow chart has "check need for response here." // check_need_for_response() does the following: // - for command & P=1, send RR or RNR. // - for response & F=1, error A. // SREJ can only be a response. We don't want to produce an error when F=1. if (is_good_nr(S,nr)) { if (f) { SET_VA(nr); } STOP_T1; START_T3; select_t1_value(S); int num_resent = resend_for_srej (S, nr, info, info_len); if (num_resent) { // my addition // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R), from V(R), so no need for extra RR at the end only for that. // We would sometimes end up in a situation where T1 was stopped on // both ends and everyone would wait for the other guy to timeout and do something. // My solution was to Start T1 after every place we send an I frame if not already there. STOP_T3; START_T1; S->acknowledge_pending = 0; } // keep same state. } else { nr_error_recovery (S); // Erratum? Flow chart shows state 1 but that would not be appropriate if modulo is 128. enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } break; case state_4_timer_recovery: if (s_debug_timers) { text_color_set(DW_COLOR_ERROR); dw_printf ("state 4 timer recovery, %s %d nr=%d, f=%d\n", __func__, __LINE__, nr, f); } S->peer_receiver_busy = 0; // Erratum: Original Flow chart has "check need for response here." // The 2006 version correctly removed it. // check_need_for_response() does the following: // - for command & P=1, send RR or RNR. // - for response & F=1, error A. // SREJ can only be a response. We don't want to produce an error when F=1. // The flow chart splits into two paths for command/response with a lot of duplication. // Command path has been omitted because SREJ can only be response. STOP_T1; select_t1_value(S); if (is_good_nr(S,nr)) { if (f) { // f=1 means ack up thru previous sequence. // Erratum: 2006 version tests "P". Original has "F." SET_VA(nr); if (s_debug_timers) { text_color_set(DW_COLOR_ERROR); dw_printf ("state 4 timer recovery, %s %d set v(a)= %d\n", __func__, __LINE__, S->va); } } if (S->vs == S->va) { // ACKs all caught up. Back to state 3. // Erratum: I think this is unreachable. // If the other side is asking for I frame with sequence X, it must have // received X+1 or later. That means my V(S) must be X+2 or greater. // So, I don't think we can ever have V(S) == V(A) here. // If we were to remove the 'if' test and true part, case 4 would then // be exactly the same as state 4. We need to rely on RR to get us // back to state 3. START_T3; SET_RC(0); // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); // text_color_set(DW_COLOR_ERROR); // dw_printf ("state 4 timer recovery, go to state 3 \n"); } else { // Erratum: Difference between two AX.25 revisions. #if 1 // This is from the original protocol spec. // Resend I frame with N(S) equal to the N(R) in the SREJ. //text_color_set(DW_COLOR_ERROR); //dw_printf ("state 4 timer recovery, send requested frame(s) \n"); int num_resent = resend_for_srej (S, nr, info, info_len); if (num_resent) { // my addition // Erratum: We sent I frame(s) and want to timeout if no ack comes back. // We also sent N(R), from V(R), so no need for extra RR at the end only for that. // We would sometimes end up in a situation where T1 was stopped on // both ends and everyone would wait for the other guy to timeout and do something. // My solution was to Start T1 after every place we send an I frame if not already there. STOP_T3; START_T1; S->acknowledge_pending = 0; } #else // Erratum! This is from the 2006 revision. // We should resend only the single requested I frame. // I think there was a cut-n-paste from the REJ flow chart and this particular place did not get changed. invoke_retransmission(S); #endif } } else { nr_error_recovery (S); enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); } break; } } /* end srej_frame */ /*------------------------------------------------------------------------------ * * Name: resend_for_srej * * Purpose: Resend the I frame(s) specified in SREJ response. * * Inputs: S - Data Link State Machine. * nr - N(R) from the frame. Peer has asked for a resend of I frame with this N(S). * info - Information field, might contain additional sequence numbers for Multi-SREJ. * info_len - Information field length, bytes. * * Returns: Number of frames sent. Should be at least one. * * Description: Simply resend requested frame(s). * The calling context will worry about the F bit and other state stuff. * *------------------------------------------------------------------------------*/ static int resend_for_srej (ax25_dlsm_t *S, int nr, unsigned char *info, int info_len) { cmdres_t cr = cr_cmd; int i_frame_nr = S->vr; int i_frame_ns = nr; int p = 0; int num_resent = 0; // Resend I frame with N(S) equal to the N(R) in the SREJ. // Additional sequence numbers can be in optional information part. cdata_t *txdata = S->txdata_by_ns[i_frame_ns]; if (txdata != NULL) { packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, i_frame_nr, i_frame_ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len); // dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); num_resent++; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: INTERNAL ERROR for SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, i_frame_ns); } // Multi-SREJ if there is an information part. int j; for (j = 0; j < info_len; j++) { // We can have a single sequence number like this: // xxx00000 (mod 8) // xxxxxxx0 (mod 128) // or we can have span (mod 128 only) like this, with the first and last: // xxxxxxx1 // xxxxxxx1 // // Note that the sequence number is shifted left by one // and if the LSB is set, there should be two adjacent bytes // with it set. if (S->modulo == 8) { i_frame_ns = (info[j] >> 5) & 0x07; // no provision for span. } else { i_frame_ns = (info[j] >> 1) & 0x7f; // TODO: test LSB and possible loop here. } txdata = S->txdata_by_ns[i_frame_ns]; if (txdata != NULL) { packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, i_frame_nr, i_frame_ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); num_resent++; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: INTERNAL ERROR for Multi-SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, i_frame_ns); } } return (num_resent); } /* end resend_for_srej */ /*------------------------------------------------------------------------------ * * Name: sabm_e_frame * * Purpose: Process SABM or SABME Frame. * * Inputs: S - Data Link State Machine. * * extended - True for SABME. False for SABM. * * p - Poll bit. TODO: What does it mean in this case? * * Description: This is a request, from the other end, to establish a connection. * * 4.3.3.1. Set Asynchronous Balanced Mode (SABM) Command * * The SABM command places two Terminal Node Comtrollers (TNC) in the asynchronous balanced mode * (modulo 8). This a balanced mode of operation in which both devices are treated as equals or peers. * * Information fields are not allowed in SABM commands. Any outstanding I frames left when the SABM * command is issued remain unacknowledged. * * The TNC confirms reception and acceptance of a SABM command by sending a UA response frame at the * earliest opportunity. If the TNC is not capable of accepting a SABM command, it responds with a DM frame if * possible. * * 4.3.3.2. Set Asynchronous Balanced Mode Extended (SABME) Command * * The SABME command places two TNCs in the asynchronous balanced mode extended (modulo 128). This * is a balanced mode of operation in which both devices are treated as equals or peers. * Information fields are not allowed in SABME commands. Any outstanding I frames left when the SABME * command is issued remains unacknowledged. * * The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the * earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame. * * A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. ** (see note below) * * * Note: The KPC-3+, which does not appear to support v2.2, responds with a DM. * The 2.0 spec, section 2.3.4.3.5, states, "While a DXE is in the disconnected mode, it will respond * to any command other than a SABM or UI frame with a DM response with the P/F bit set to 1." * I think it is a bug in the KPC but I can see how someone might implement it that way. * However, another place says FRMR is sent for any unrecognized frame type. That would seem to take priority. * *------------------------------------------------------------------------------*/ static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p) { switch (S->state) { case state_0_disconnected: // Flow chart has decision: "Able to establish?" // I think this means, are we willing to accept connection requests? // We are always willing to accept connections. // Of course, we wouldn't get this far if local callsigns were not "registered." if (extended) { set_version_2_2 (S); } else { set_version_2_0 (S); } cmdres_t res = cr_res; int f = p; // I don't understand the purpose of "P" in SABM/SABME // but we dutifully copy it into "F" for the UA response. int nopid = 0; // PID is only for I and UI. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); clear_exception_conditions (S); SET_VS(0); SET_VA(0); SET_VR(0); text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], extended ? "v2.2" : "v2.0"); // dl connect indication - inform the client app. int incoming = 1; server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); INIT_T1V_SRT; START_T3; SET_RC(0); // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); break; case state_1_awaiting_connection: // Don't combine with state 5. They are slightly different. if (extended) { // SABME - respond with DM, enter state 5. cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__); } else { // SABM - respond with UA. // Erratum! 2006 version shows SAMBE twice for state 1. // First one should be SABM in last page of Figure C4.2 // Original appears to be correct. cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // stay in state 1. } break; case state_5_awaiting_v22_connection: if (extended) { // SABME - respond with UA cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // stay in state 5 } else { // SABM, respond with UA, enter state 1 cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); } break; case state_2_awaiting_release: // Erratum! Flow charts don't list SABME for state 2. // Probably just want to treat it the same as SABM here. { cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // expedited // stay in state 2. } break; case state_3_connected: case state_4_timer_recovery: { cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // State 3 & 4 handling are the same except for this one difference. if (S->state == state_4_timer_recovery) { if (extended) { set_version_2_2 (S); } else { set_version_2_0 (S); } } clear_exception_conditions (S); if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error F: Data Link reset; i.e. SABM(e) received in state %d.\n", S->stream_id, S->state); } if (S->vs != S->va) { discard_i_queue (S); // dl connect indication int incoming = 1; server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); } STOP_T1; START_T3; SET_VS(0); SET_VA(0); SET_VR(0); SET_RC(0); // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); } break; } } /* end sabm_e_frame */ /*------------------------------------------------------------------------------ * * Name: disc_frame * * Purpose: Process DISC command. * * Inputs: S - Data Link State Machine. * p - Poll bit. * * Description: 4.3.3.3. Disconnect (DISC) Command * * The DISC command terminates a link session between two stations. An information field is not permitted in * a DISC command frame. * * Prior to acting on the DISC frame, the receiving TNC confirms acceptance of the DISC by issuing a UA * response frame at its earliest opportunity. The TNC sending the DISC enters the disconnected state when it * receives the UA response. * * Any unacknowledged I frames left when this command is acted upon remain unacknowledged. * * * 6.3.4. Link Disconnection * * While in the information-transfer state, either TNC may indicate a request to disconnect the link by transmitting * a DISC command frame and starting timer T1. * * After receiving a valid DISC command, the TNC sends a UA response frame and enters the disconnected * state. After receiving a UA or DM response to a sent DISC command, the TNC cancels timer T1 and enters the * disconnected state. * * If a UA or DM response is not correctly received before T1 times out, the DISC frame is sent again and T1 is * restarted. If this happens N2 times, the TNC enters the disconnected state. * *------------------------------------------------------------------------------*/ static void disc_frame (ax25_dlsm_t *S, int p) { switch (S->state) { case state_0_disconnected: case state_1_awaiting_connection: case state_5_awaiting_v22_connection: { cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } // keep current state, 0, 1, or 5. break; case state_2_awaiting_release: { cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // expedited } // keep current state, 2. break; case state_3_connected: case state_4_timer_recovery: { discard_i_queue (S); cmdres_t res = cr_res; int f = p; int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // dl disconnect *indication* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; STOP_T3; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } break; } } /* end disc_frame */ /*------------------------------------------------------------------------------ * * Name: dm_frame * * Purpose: Process DM Response Frame. * * Inputs: S - Data Link State Machine. * f - Final bit. * * Description: 4.3.3.1. Set Asynchronous Balanced Mode (SABM) Command * * The TNC confirms reception and acceptance of a SABM command by sending a UA response frame at the * earliest opportunity. If the TNC is not capable of accepting a SABM command, it responds with a DM frame if * possible. * * The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the * earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame. * * A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. * ( I think the KPC-3+ has a bug - it replys with DM - WB2OSZ ) * * 4.3.3.5. Disconnected Mode (DM) Response * * The disconnected mode response is sent whenever a TNC receives a frame other than a SABM(E) or UI * frame while in a disconnected mode. The disconnected mode response also indicates that the TNC cannot * accept a connection at the moment. The DM response does not have an information field. * Whenever a SABM(E) frame is received and it is determined that a connection is not possible, a DM frame is * sent. This indicates that the called station cannot accept a connection at that time. * While a TNC is in the disconnected mode, it responds to any command other than a SABM(E) or UI frame * with a DM response with the P/F bit set to "1". * * 4.3.3.6. Unnumbered Information (UI) Frame * * A received UI frame with the P bit set causes a response to be transmitted. This response is a DM frame when * in the disconnected state, or an RR (or RNR, if appropriate) frame in the information transfer state. * * 6.3.1. AX.25 Link Connection Establishment * * If the distant TNC receives a SABM command and cannot enter the indicated state, it sends a DM frame. * When the originating TNC receives a DM response to its SABM(E) frame, it cancels its T1 timer and does * not enter the information-transfer state. * * 6.3.4. Link Disconnection * * After receiving a valid DISC command, the TNC sends a UA response frame and enters the disconnected * state. After receiving a UA or DM response to a sent DISC command, the TNC cancels timer T1 and enters the * disconnected state. * * 6.5. Resetting Procedure * * If a DM response is received, the TNC enters the disconnected state and stops timer T1. If timer T1 expires * before a UA or DM response frame is received, the SABM(E) is retransmitted and timer T1 restarted. If timer T1 * expires N2 times, the TNC enters the disconnected state. Any previously existing link conditions are cleared. * Other commands or responses received by the TNC before completion of the reset procedure are discarded. * * Erratum: The flow chart shows the same behavior for states 1 and 5. * For state 5, I think we should treat DM the same as FRMR. * *------------------------------------------------------------------------------*/ static void dm_frame (ax25_dlsm_t *S, int f) { switch (S->state) { case state_0_disconnected: // Do nothing. break; case state_1_awaiting_connection: if (f == 1) { discard_i_queue (S); // dl disconnect *indication* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { // keep current state. } break; case state_2_awaiting_release: if (f == 1) { // Erratum! Original flow chart, page 91, shows DL-CONNECT confirm. // It should clearly be DISconnect rather than Connect. // 2006 has DISCONNECT *Indication*. // Should it be indication or confirm? Not sure. // dl disconnect *confirm* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { // keep current state. } break; case state_3_connected: case state_4_timer_recovery: if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error E: DM received in state %d.\n", S->stream_id, S->state); } // dl disconnect *indication* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); discard_i_queue (S); STOP_T1; STOP_T3; enter_new_state (S, state_0_disconnected, __func__, __LINE__); break; case state_5_awaiting_v22_connection: #if 0 // Erratum: The flow chart says we should do this. // I'm not saying it is wrong. I just found it necessary to change this // to work around an apparent bug in a popular hardware TNC. if (f == 1) { discard_i_queue (S); // dl disconnect *indication* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { // keep current state. } #else // Erratum: This is not in original spec. It's copied from the FRMR case. // I was expecting FRMR to mean the other end did not understand v2.2. // Experimentation, with KPC-3+, revealed that we get DM instead. // One part of the the 2.0 spec sort of indicates this might be intentional. // But another part more clearly states it should be FRMR. // At first I thought it was an error in the protocol spec. // Later, I tend to believe it was just implemented wrong in the KPC-3+. if (f == 1) { text_color_set(DW_COLOR_INFO); dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]); INIT_T1V_SRT; // Erratum: page 105. We are in state 5 so I think that means modulo is 128, // k is probably something > 7, and selective reject is enabled. // At the end of this we go to state 1. // It seems to me, that we really want to set version 2.0 in here so we have // compatible settings. set_version_2_0 (S); establish_data_link (S); S->layer_3_initiated = 1; enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); } #endif break; } } /* end dm_frame */ /*------------------------------------------------------------------------------ * * Name: UA_frame * * Purpose: Process UA Response Frame. * * Inputs: S - Data Link State Machine. * f - Final bit. * * Description: 4.3.3.4. Unnumbered Acknowledge (UA) Response * * The UA response frame acknowledges the reception and acceptance of a SABM(E) or DISC command * frame. A received command is not actually processed until the UA response frame is sent. Information fields are * not permitted in a UA frame. * * 4.4.1. TNC Busy Condition * * When a TNC is temporarily unable to receive I frames (e.g., when receive buffers are full), it sends a Receive * Not Ready (RNR) frame. This informs the sending TNC that the receiving TNC cannot handle any more I * frames at the moment. This receiving TNC clears this condition by the sending a UA, RR, REJ or SABM(E) * command frame. * * 6.2. Poll/Final (P/F) Bit Procedures * * The response frame returned by a TNC depends on the previous command received, as described in the * following paragraphs. * The next response frame returned by the TNC to a SABM(E) or DISC command with the P bit set to "1" is a * UA or DM response with the F bit set to "1". * * 6.3.1. AX.25 Link Connection Establishment * * To connect to a distant TNC, the originating TNC sends a SABM command frame to the distant TNC and * starts its T1 timer. If the distant TNC exists and accepts the connect request, it responds with a UA response * frame and resets all of its internal state variables (V(S), V(A) and V(R)). Reception of the UA response frame by * the originating TNC causes it to cancel the T1 timer and set its internal state variables to "0". * * 6.5. Resetting Procedure * * A TNC initiates a reset procedure whenever it receives an unexpected UA response frame, or after receipt of * a FRMR frame from a TNC using an older version of the protocol. * *------------------------------------------------------------------------------*/ static void ua_frame (ax25_dlsm_t *S, int f) { switch (S->state) { case state_0_disconnected: // Erratum: flow chart says errors C and D. Neither one really makes sense. // "Unexpected UA in states 3, 4, or 5." We are in state 0 here. // "UA received without F=1 when SABM or DISC was sent P=1." if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error C: Unexpected UA in state %d.\n", S->stream_id, S->state); } break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: if (f == 1) { if (S->layer_3_initiated) { text_color_set(DW_COLOR_INFO); // TODO: add via if apppropriate. dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0"); // There is a subtle difference here between connect confirm and indication. // connect *confirm* means "has been made" // The AGW API distinguishes between incoming (initiated by other station) and // outgoing (initiated by me) connections. int incoming = 0; server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); } else if (S->vs != S->va) { #if 1 // Erratum: 2006 version has this. INIT_T1V_SRT; START_T3; // Erratum: Rather pointless because we immediately stop it below. // In the original flow chart, that is. // I think there is an error as explained below. // In my version this is still pointless because we start T3 later. #else // Erratum: Original version has this. // I think this could be harmful. // The client app might have been impatient and started sending // information already. I don't see why we would want to discard it. discard_i_queue (S); #endif text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0"); // Erratum: 2006 version says DL-CONNECT *confirm* but original has *indication*. // connect *indication* means "has been requested". // *confirm* seems right because we got a reply from the other side. int incoming = 0; server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming); } STOP_T1; #if 1 // My version. START_T3; #else // As shown in flow chart. STOP_T3; // Erratum? I think this is wrong. // We are about to enter state 3. When in state 3 either T1 or T3 should be // running. In state 3, we always see start one / stop the other pairs except where // we are about to enter a different state. // Since there is nothing outstanding where we expect a response, T1 would // not be started. #endif SET_VS(0); SET_VA(0); SET_VR(0); select_t1_value (S); // Erratum: mdl_negotiate_request does not appear in the SDL flow chart. // It is mentioned here: // // C5.3 Internal Operation of the Machine // // The Management Data link State Machine handles the negotiation/notification of // operational parameters. It uses a single command/response exchange to negotiate the // final values of negotiable parameters. // // The station initiating the AX.25 connection will send an XID command after it receives // the UA frame. If the other station is using a version of AX.25 earlier than 2.2, it will // respond with an FRMR of the XID command and the default version 2.0 parameters will // be used. If the other station is using version 2.2 or better, it will respond with an XID // response. if (S->state == state_5_awaiting_v22_connection) { mdl_negotiate_request (S); } SET_RC(0); // My enhancement. See Erratum note in select_t1_value. enter_new_state (S, state_3_connected, __func__, __LINE__); } else { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error D: UA received without F=1 when SABM or DISC was sent P=1.\n", S->stream_id); } // stay in current state, either 1 or 5. } break; case state_2_awaiting_release: // Erratum: 2006 version is missing yes/no labels on this test. // DL-ERROR Indication does not mention error D. if (f == 1) { text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); STOP_T1; enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error D: UA received without F=1 when SABM or DISC was sent P=1.\n", S->stream_id); } // stay in same state. } break; case state_3_connected: case state_4_timer_recovery: if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error C: Unexpected UA in state %d.\n", S->stream_id, S->state); } establish_data_link (S); S->layer_3_initiated = 0; // Erratum? Flow chart goes to state 1. Wouldn't we want this to be state 5 if modulo is 128? enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__); break; } } /* end ua_frame */ /*------------------------------------------------------------------------------ * * Name: frmr_frame * * Purpose: Process FRMR Response Frame. * * Inputs: S - Data Link State Machine. * * Description: 4.3.3.2. Set Asynchronous Balanced Mode Extended (SABME) Command * ... * The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the * earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame. * A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. * * 4.3.3.9. FRMR Response Frame * * The FRMR response is removed from the standard for the following reasons: * a) UI frame transmission was not allowed during FRMR recovery; * b) During FRMR recovery, the link could not be reestablished by the station that sent the FRMR; * c) The above functions are better handled by simply resetting the link with a SABM(E) + UA exchange; * d) An implementation that receives and process FRMRs but does not transmit them is compatible with older * versions of the standard; and * e) SDL is simplified and removes the need for one state. * This version of AX.25 operates with previous versions of AX.25. It does not generate a FRMR Response * frame, but handles error conditions by resetting the link. * * 6.3.2. Parameter Negotiation Phase * * Parameter negotiation occurs at any time. It is accomplished by sending the XID command frame and * receiving the XID response frame. Implementations of AX.25 prior to version 2.2 respond to an XID command * frame with a FRMR response frame. The TNC receiving the FRMR uses a default set of parameters compatible * with previous versions of AX.25. * * 6.5. Resetting Procedure * * The link resetting procedure initializes both directions of data flow after a unrecoverable error has occurred. * This resetting procedure is used only in the information-transfer state of an AX.25 link. * A TNC initiates a reset procedure whenever it receives an unexpected UA response frame, or after receipt of * a FRMR frame from a TNC using an older version of the protocol. * *------------------------------------------------------------------------------*/ static void frmr_frame (ax25_dlsm_t *S) { switch (S->state) { case state_0_disconnected: case state_1_awaiting_connection: case state_2_awaiting_release: // Ignore it. Keep current state. break; case state_3_connected: case state_4_timer_recovery: if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error K: FRMR not expected in state %d.\n", S->stream_id, S->state); } set_version_2_0 (S); // Erratum: FRMR can only be sent by v2.0. // Need to force v2.0. Should be added to flow chart. establish_data_link (S); S->layer_3_initiated = 0; enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); break; case state_5_awaiting_v22_connection: text_color_set(DW_COLOR_INFO); dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]); INIT_T1V_SRT; set_version_2_0 (S); // Erratum: Need to force v2.0. This is not in flow chart. establish_data_link (S); S->layer_3_initiated = 1; // Erratum? I don't understand the difference here. // State 1 clears it. State 5 sets it. Why not the same? enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); break; } // part of state machine for the XID negotiation. // I would not expect this to happen. // To get here: // We sent SABME. (not SABM) // Other side responded with UA so it understands v2.2. // We sent XID command which puts us int the negotiating state. // Presumably this is in response to the XID and not something else. // Anyhow, we will fall back to v2.0 parameters. switch (S->mdl_state) { case mdl_state_0_ready: break; case mdl_state_1_negotiating: set_version_2_0 (S); S->mdl_state = mdl_state_0_ready; break; } } /* end frmr_frame */ /*------------------------------------------------------------------------------ * * Name: ui_frame * * Purpose: Process XID frame for negotiating protocol parameters. * * Inputs: S - Data Link State Machine. * * cr - Is it command or response? * * pf - Poll/Final bit. * * Description: 4.3.3.6. Unnumbered Information (UI) Frame * * The Unnumbered Information frame contains PID and information fields and passes information along the * link outside the normal information controls. This allows information fields to be exchanged on the link, bypassing * flow control. * * Because these frames cannot be acknowledged, if one such frame is obliterated, it cannot be recovered. * A received UI frame with the P bit set causes a response to be transmitted. This response is a DM frame when * in the disconnected state, or an RR (or RNR, if appropriate) frame in the information transfer state. * * Reality: The data link state machine was an add-on after APRS and client APIs were already done. * UI frames don't go thru here for normal operation. * The only reason we have this function is so that we can send a response to a UI command with P=1. * *------------------------------------------------------------------------------*/ static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf) { if (cr == cr_cmd && pf == 1) { switch (S->state) { case state_0_disconnected: case state_1_awaiting_connection: case state_2_awaiting_release: case state_5_awaiting_v22_connection: { cmdres_t r = cr_res; // DM response with F taken from P. int nopid = 0; // PID applies only for I and UI frames. packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, pf, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } break; case state_3_connected: case state_4_timer_recovery: enquiry_response (S, frame_type_U_UI, pf); break; } } } /* end ui_frame */ /*------------------------------------------------------------------------------ * * Name: xid_frame * * Purpose: Process XID frame for negotiating protocol parameters. * * Inputs: S - Data Link State Machine. * * cr - Is it command or response? * * pf - Poll/Final bit. * * Description: 4.3.3.7 Exchange Identification (XID) Frame * * The Exchange Identification frame causes the addressed station to identify itself, and * to provide its characteristics to the sending station. An information field is optional within * the XID frame. A station receiving an XID command returns an XID response unless a UA * response to a mode setting command is awaiting transmission, or a FRMR condition * exists. * * The XID frame complies with ISO 8885. Only those fields applicable to AX.25 are * described. All other fields are set to an appropriate value. This implementation is * compatible with any implementation which follows ISO 8885. Only the general-purpose * XID information field identifier is required in this version of AX.25. * * The information field consists of zero or more information elements. The information * elements start with a Format Identifier (FI) octet. The second octet is the Group Identifier * (GI). The third and forth octets form the Group Length (GL). The rest of the information * field contains parameter fields. * * The FI takes the value 82 hex for the general-purpose XID information. The GI takes * the value 80 hex for the parameter-negotiation identifier. The GL indicates the length of * the associated parameter field. This length is expressed as a two-octet binary number * representing the length of the associated parameter field in octets. The high-order bits of * length value are in the first of the two octets. A group length of zero indicates the lack of * an associated parameter field and that all parameters assume their default values. The GL * does not include its own length or the length of the GI. * * The parameter field contains a series of Parameter Identifier (PI), Parameter Length * (PL), and Parameter Value (PV) set structures, in that order. Each PI identifies a * parameter and is one octet in length. Each PL indicates the length of the associated PV in * octets, and is one octet in length. Each PV contains the parameter value and is PL octets * in length. The PL does not include its own length or the length of its associated PI. A PL * value of zero indicates that the associated PV is absent; the parameter assumes the * default value. A PI/PL/PV set may be omitted if it is not required to convey information, or * if present values for the parameter are to be used. The PI/PL/PV fields are placed into the * information field of the XID frame in ascending order. There is only one entry for each * PI/PL/PV field used. A parameter field containing an unrecognized PI is ignored. An * omitted parameter field assumes the currently negotiated value. * *------------------------------------------------------------------------------*/ static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len) { struct xid_param_s param; char desc[150]; int ok; unsigned char xinfo[40]; int xlen; cmdres_t res = cr_res; int f = 1; int nopid = 0; packet_t pp; switch (S->mdl_state) { case mdl_state_0_ready: if (cr == cr_cmd) { if (pf == 1) { // Take parameters sent by other station. // Generally we take minimum of what he wants and what I can do. // Adjust my working configuration and send it back. ok = xid_parse (info_ptr, info_len, ¶m, desc, sizeof(desc)); if (ok) { negotiation_response (S, ¶m); xlen = xid_encode (¶m, xinfo, res); pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_XID, f, nopid, xinfo, xlen); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error MDL-A: XID command without P=1.\n", S->stream_id); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error MDL-B: Unexpected XID response.\n", S->stream_id); } break; case mdl_state_1_negotiating: if (cr == cr_res) { if (pf == 1) { // Got expected response. Copy into my working parameters. ok = xid_parse (info_ptr, info_len, ¶m, desc, sizeof(desc)); if (ok) { complete_negotiation (S, ¶m); } S->mdl_state = mdl_state_0_ready; STOP_TM201; //#define TEST_TEST 1 #if TEST_TEST // Send TEST command to see how it responds. // We currently have no Client API for sending this or reporting result. { char info[80] = "The quick brown fox jumps over the lazy dog."; cmdres_t cmd = cr_cmd; int p = 0; int nopid = 0; packet_t pp; pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_TEST, p, nopid, (unsigned char *)info, (int)strlen(info)); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } #endif } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error MDL-D: XID response without F=1.\n", S->stream_id); } } else { // Not expecting to receive a command when I sent one. // Flow chart says requeue but I just drop it. // The other end can retry and maybe I will be back to ready state by then. } break; } } /* end xid_frame */ /*------------------------------------------------------------------------------ * * Name: test_frame * * Purpose: Process TEST command for checking link. * * Inputs: S - Data Link State Machine. * * cr - Is it command or response? * * pf - Poll/Final bit. * * Description: 4.3.3.8. Test (TEST) Frame * * The Test command causes the addressed station to respond with the TEST response at the first respond * opportunity; this performs a basic test of the data-link control. An information field is optional with the TEST * command. If present, the received information field is returned, if possible, by the addressed station, with the * TEST response. The TEST command has no effect on the mode or sequence variables maintained by the station. * * A FRMR condition may be established if the received TEST command information field exceeds the maximum * defined storage capability of the station. If a FRMR response is not returned for this condition, a TEST response * without an information field is returned. * * The station considers the data-link layer test terminated on receipt of the TEST response, or when a time-out * period has expired. The results of the TEST command/response exchange are made available for interrogation * by a higher layer. * * Erratum: TEST frame is not mentioned in the SDL flow charts. * Don't know how P/F is supposed to be used. * Here, the response sends back what was received in the command. * *------------------------------------------------------------------------------*/ static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len) { cmdres_t res = cr_res; int f = pf; int nopid = 0; packet_t pp; if (cr == cr_cmd) { pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_TEST, f, nopid, info_ptr, info_len); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); } } /* end test_frame */ /*------------------------------------------------------------------------------ * * Name: dl_timer_expiry * * Purpose: Some timer expired. Figure out which one and act accordingly. * * Inputs: none. * *------------------------------------------------------------------------------*/ void dl_timer_expiry (void) { ax25_dlsm_t *p; double now = dtime_now(); // Examine all of the data link state machines. // Process only those where timer: // - is running. // - is not paused. // - expiration time has arrived or passed. for (p = list_head; p != NULL; p = p->next) { if (p->t1_exp != 0 && p->t1_paused_at == 0 && p->t1_exp <= now) { p->t1_exp = 0; p->t1_paused_at = 0; p->t1_had_expired = 1; t1_expiry (p); } } for (p = list_head; p != NULL; p = p->next) { if (p->t3_exp != 0 && p->t3_exp <= now) { p->t3_exp = 0; t3_expiry (p); } } for (p = list_head; p != NULL; p = p->next) { if (p->tm201_exp != 0 && p->tm201_paused_at == 0 && p->tm201_exp <= now) { p->tm201_exp = 0; p->tm201_paused_at = 0; tm201_expiry (p); } } } /* end dl_timer_expiry */ /*------------------------------------------------------------------------------ * * Name: t1_expiry * * Purpose: Handle T1 timer expiration for outstanding I frame or P-bit. * * Inputs: S - Data Link State Machine. * * Description: 4.4.5.1. T1 Timer Recovery * * If a transmission error causes a TNC to fail to receive (or to receive and discard) a single I frame, or the last I * frame in a sequence of I frames, then the TNC does not detect a send-sequence-number error and consequently * does not transmit a REJ/SREJ. The TNC that transmitted the unacknowledged I frame(s) following the completion * of timeout period T1, takes appropriate recovery action to determine when I frame retransmission as described * in Section 6.4.10 should begin. This condition is cleared by the reception of an acknowledgement for the sent * frame(s), or by the link being reset. * * 6.7.1.1. Acknowledgment Timer T1 * * T1, the Acknowledgement Timer, ensures that a TNC does not wait indefinitely for a response to a frame it * sends. This timer cannot be expressed in absolute time; the time required to send frames varies greatly with the * signaling rate used at Layer 1. T1 should take at least twice the amount of time it would take to send maximum * length frame to the distant TNC and get the proper response frame back from the distant TNC. This allows time * for the distant TNC to do some processing before responding. * If Layer 2 repeaters are used, the value of T1 should be adjusted according to the number of repeaters through * which the frame is being transferred. * *------------------------------------------------------------------------------*/ // Make timer start, stop, expiry a different color to stand out. #define DW_COLOR_DEBUG_TIMER DW_COLOR_ERROR static void t1_expiry (ax25_dlsm_t *S) { if (s_debug_timers) { double now = dtime_now(); text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("t1_expiry (), [now=%.3f], state=%d, rc=%d\n", now - S->start_time, S->state, S->rc); } switch (S->state) { case state_0_disconnected: // Ignore it. break; case state_1_awaiting_connection: case state_5_awaiting_v22_connection: // MAXV22 hack. // If we already sent the maximum number of SABME, fall back to v2.0 SABM. if (S->state == state_5_awaiting_v22_connection && S->rc == g_misc_config_p->maxv22) { set_version_2_0 (S); enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__); } if (S->rc == S->n2_retry) { discard_i_queue(S); text_color_set(DW_COLOR_INFO); dw_printf ("Failed to connect to %s after %d tries.\n", S->addrs[PEERCALL], S->n2_retry); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 1); enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { cmdres_t cmd = cr_cmd; int p = 1; int nopid = 0; packet_t pp; SET_RC(S->rc+1); if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; // Keep statistics. pp = ax25_u_frame (S->addrs, S->num_addr, cmd, (S->state == state_5_awaiting_v22_connection) ? frame_type_U_SABME : frame_type_U_SABM, p, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); select_t1_value(S); START_T1; // Keep same state. } break; case state_2_awaiting_release: if (S->rc == S->n2_retry) { text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0); enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { cmdres_t cmd = cr_cmd; int p = 1; int nopid = 0; packet_t pp; SET_RC(S->rc+1); if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_DISC, p, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); select_t1_value(S); START_T1; // stay in same state } break; case state_3_connected: SET_RC(1); transmit_enquiry (S); enter_new_state (S, state_4_timer_recovery, __func__, __LINE__); break; case state_4_timer_recovery: if (S->rc == S->n2_retry) { // Erratum: 2006 version, page 103, is missing yes/no labels on decision blocks. if (S->va != S->vs) { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error I: %d timeouts: unacknowledged sent data.\n", S->stream_id, S->n2_retry); } } else if (S->peer_receiver_busy) { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error U: %d timeouts: extended peer busy condition.\n", S->stream_id, S->n2_retry); } } else { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error T: %d timeouts: no response to enquiry.\n", S->stream_id, S->n2_retry); } } // Erratum: Flow chart says DL-DISCONNECT "request" in both original and 2006 revision. // That is clearly wrong because a "request" would come FROM the higher level protocol/client app. // I think it should be "indication" rather than "confirm" because the peer condition is unknown. // dl disconnect *indication* text_color_set(DW_COLOR_INFO); dw_printf ("Stream %d: Disconnected from %s due to timeouts.\n", S->stream_id, S->addrs[PEERCALL]); server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 1); discard_i_queue (S); cmdres_t cr = cr_res; // DM can only be response. int f = 0; // Erratum: Assuming F=0 because it is not response to P=1 int nopid = 0; packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cr, frame_type_U_DM, f, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); enter_new_state (S, state_0_disconnected, __func__, __LINE__); } else { SET_RC(S->rc+1); if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; // gather statistics. transmit_enquiry (S); // Keep same state. } break; } } /* end t1_expiry */ /*------------------------------------------------------------------------------ * * Name: t3_expiry * * Purpose: Handle T3 timer expiration. * * Inputs: S - Data Link State Machine. * * Description: TODO: still don't understand this. * * 4.4.5.2. Timer T3 Recovery * * Timer T3 ensures that the link is still functional during periods of low information transfer. When T1 is not * running (no outstanding I frames), T3 periodically causes the TNC to poll the other TNC of a link. When T3 * times out, an RR or RNR frame is transmitted as a command with the P bit set, and then T1 is started. When a * response to this command is received, T1 is stopped and T3 is started. If T1 expires before a response is * received, then the waiting acknowledgement procedure (Section 6.4.11) is executed. * * 6.7.1.3. Inactive Link Timer T3 * * T3, the Inactive Link Timer, maintains link integrity whenever T1 is not running. It is recommended that * whenever there are no outstanding unacknowledged I frames or P-bit frames (during the information-transfer * state), an RR or RNR frame with the P bit set to "1" be sent every T3 time units to query the status of the other * TNC. The period of T3 is locally defined, and depends greatly on Layer 1 operation. T3 should be greater than * T1; it may be very large on channels of high integrity. * *------------------------------------------------------------------------------*/ static void t3_expiry (ax25_dlsm_t *S) { if (s_debug_timers) { double now = dtime_now(); text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("t3_expiry (), [now=%.3f]\n", now - S->start_time); } switch (S->state) { case state_0_disconnected: case state_1_awaiting_connection: case state_5_awaiting_v22_connection: case state_2_awaiting_release: case state_4_timer_recovery: break; case state_3_connected: // Erratum: Original sets RC to 0, 2006 revision sets RC to 1 which makes more sense. SET_RC(1); transmit_enquiry (S); enter_new_state (S, state_4_timer_recovery, __func__, __LINE__); break; } } /* end t3_expiry */ /*------------------------------------------------------------------------------ * * Name: tm201_expiry * * Purpose: Handle TM201 timer expiration. * * Inputs: S - Data Link State Machine. * * Description: This is used when waiting for a response to an XID command. * *------------------------------------------------------------------------------*/ static void tm201_expiry (ax25_dlsm_t *S) { struct xid_param_s param; unsigned char xinfo[40]; int xlen; cmdres_t cmd = cr_cmd; int p = 1; int nopid = 0; packet_t pp; if (s_debug_timers) { double now = dtime_now(); text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("tm201_expiry (), [now=%.3f], state=%d, rc=%d\n", now - S->start_time, S->state, S->rc); } switch (S->mdl_state) { case mdl_state_0_ready: // Timer shouldn't be running when in this state. break; case mdl_state_1_negotiating: S->mdl_rc++; if (S->mdl_rc > S->n2_retry) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error MDL-C: Management retry limit exceeded.\n", S->stream_id); S->mdl_state = mdl_state_0_ready; } else { // No response. Ask again. initiate_negotiation (S, ¶m); xlen = xid_encode (¶m, xinfo, cmd); pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_XID, p, nopid, xinfo, xlen); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); START_TM201; } break; } } /* end tm201_expiry */ //################################################################################### //################################################################################### // // Subroutines from protocol spec, pages 106 - 109 // //################################################################################### //################################################################################### // FIXME: continue review here. /*------------------------------------------------------------------------------ * * Name: nr_error_recovery * * Purpose: Try to recover after receiving an expected N(r) value. * *------------------------------------------------------------------------------*/ static void nr_error_recovery (ax25_dlsm_t *S) { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error J: N(r) sequence error.\n", S->stream_id); } establish_data_link (S); S->layer_3_initiated = 0; } /* end nr_error_recovery */ /*------------------------------------------------------------------------------ * * Name: establish_data_link * (Combined with "establish extended data link") * * Purpose: Send SABM or SABME to other station. * * Inputs: S-> * addrs destination, source, and optional digi addresses. * num_addr Number of addresses. Should be 2 .. 10. * modulo Determines if we send SABME or SABM. * * Description: Original spec had two different functions that differed * only by sending SABM or SABME. Here they are combined into one. * *------------------------------------------------------------------------------*/ static void establish_data_link (ax25_dlsm_t *S) { cmdres_t cmd = cr_cmd; int p = 1; packet_t pp; int nopid = 0; clear_exception_conditions (S); // Erratum: We have an off-by-one error here. // Flow chart shows setting RC to 0 and we end up sending SAMB(e) 11 times when N2 (RETRY) is 10. // It should be 1 rather than 0. SET_RC(1); pp = ax25_u_frame (S->addrs, S->num_addr, cmd, (S->modulo == 128) ? frame_type_U_SABME : frame_type_U_SABM, p, nopid, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); STOP_T3; START_T1; } /* end establish_data_link */ /*------------------------------------------------------------------------------ * * Name: clear_exception_conditions * *------------------------------------------------------------------------------*/ static void clear_exception_conditions (ax25_dlsm_t *S) { S->peer_receiver_busy = 0; S->reject_exception = 0; S->own_receiver_busy = 0; S->acknowledge_pending = 0; // My enhancement. If we are establishing a new connection, we should discard any saved out of sequence incoming I frames. int n; for (n = 0; n < 128; n++) { if (S->rxdata_by_ns[n] != NULL) { cdata_delete (S->rxdata_by_ns[n]); S->rxdata_by_ns[n] = NULL; } } // We retain the transmit I frame queue so we can continue after establishing a new connection. } /* end clear_exception_conditions */ /*------------------------------------------------------------------------------ * * Name: transmit_enquiry, page 106 * * Purpose: This is called only when a timer expires. * * T1: We sent I frames and timed out waiting for the ack. * Poke the other end to determine how much it got so far * so we know where to continue. * * T3: Not activity for substantial amount of time. * Poke the other end to see if it is still there. * * * Observation: This is the only place where we send RR command with P=1. * * Sequence of events: * * We send some I frames to the other guy. * There are outstanding sent I frames for which we did not receive ACK. * * Timer 1 expires when we are in state 3: send RR/RNR command P=1 (here). Enter state 4. * Timer 1 expires when we are in state 4: same until max retry count is exceeded. * * Other guy gets RR/RNR command P=1. * Same action for either state 3 or 4. * Whether he has outstanding un-ack'ed sent I frames is irrelevent. * He calls "enquiry response" which sends RR/RNR response F=1. * (Read about detour 1 below and in enquiry_response.) * * I get back RR/RNR response F=1. Still in state 4. * Of course, N(R) gets copied into V(A). * Now here is the interesting part. * If the ACKs are caught up, i.e. V(A) == V(S), stop T1 and enter state 3. * Otherwise, "invoke retransmission" to resend everything after N(R). * * * Detour 1: You were probably thinking, "Suppose SREJ is enabled and the other guy * had a record of the SREJ frames sent which were not answered by filled in * I frames. Why not send the SREJ again instead of backing up and resending * stuff which already got there OK?" * * The code to handle incoming SREJ in state 4 is there but stop T1 is in the * wrong place as mentioned above. * *------------------------------------------------------------------------------*/ static void transmit_enquiry (ax25_dlsm_t *S) { int p = 1; int nr = S->vr; cmdres_t cmd = cr_cmd; packet_t pp; if (s_debug_retry) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n****** TRANSMIT ENQUIRY RR/RNR cmd P=1 ****** state=%d, rc=%d\n\n", S->state, S->rc); } // This is the ONLY place that we send RR/RNR *command* with P=1. // Everywhere else should be response. // I don't think we ever use RR/RNR command P=0 but need to check on that. pp = ax25_s_frame (S->addrs, S->num_addr, cmd, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, p, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; START_T1; } /* end transmit_enquiry */ /*------------------------------------------------------------------------------ * * Name: enquiry_response * * Inputs: frame_type - Type of frame received or frame_not_AX25 for LM seize confirm. * I think that this function is being called from too many * different contexts where it really needs to react differently. * So pass in more information about where we are coming from. * * F - Always specified as parameter in the references. * * Description: This is called for: * - UI command with P=1 then F=1. * - LM seize confirm with ack pending then F=0. (TODO: not clear on this yet.) * TODO: I think we want to ensure that this function is called ONLY * for RR/RNR/I command with P=1. LM Seize confirm can do its own thing and * not get involved in this complication. * - check_need_for_response(), command & P=1, then F=1 * - RR/RNR/REJ command & P=1, then F=1 * * In all cases, we see that F has been specified, usually 1 because it is * a response to a command with P=1. * Specifying F would imply response when the flow chart says RR/RNR command. * The documentation says: * * 6.2. Poll/Final (P/F) Bit Procedures * * The next response frame returned to an I frame with the P bit set to "1", received during the information * transfer state, is an RR, RNR or REJ response with the F bit set to "1". * * The next response frame returned to a supervisory command frame with the P bit set to "1", received during * the information transfer state, is an RR, RNR or REJ response frame with the F bit set to "1". * * Erattum! The flow chart says RR/RNR *command* but I'm confident it should be response. * * Erratum: Ax.25 spec has nothing here for SREJ. See X.25 2.4.6.11 for explanation. * *------------------------------------------------------------------------------*/ static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f) { cmdres_t cr = cr_res; // Response, not command as seen in flow chart. int nr = S->vr; packet_t pp; if (s_debug_retry) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n****** ENQUIRY RESPONSE F=%d ******\n\n", f); } #if 1 // Detour 1 // My addition, Based on X.25 2.4.6.11. // Only for RR, RNR, I. // See sequence of events in transmit_enquiry comments. if (f == 1 && (frame_type == frame_type_S_RR || frame_type == frame_type_S_RNR || frame_type == frame_type_I)) { if (S->own_receiver_busy) { // I'm busy. pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; // because we sent N(R) from V(R). } else if (S->srej_enable == srej_single || S->srej_enable == srej_multi) { // SREJ is enabled. This is based on X.25 2.4.6.11. if (S->modulo != 128) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: enquiry response should not be sending SREJ for modulo 8.\n"); } // Suppose we received I frames with N(S) of 0, 3, 7. // V(R) is still 1 because 0 is the last one received with contiguous N(S) values. // 3 and 7 have been saved into S->rxdata_by_ns. // We have outstanding requests to resend 1, 2, 4, 5, 6. // Either those requests or the replies got lost. // The other end timed out and asked us what is happening by sending RR/RNR command P=1. // First see if we have any out of sequence frames in the receive buffer. int last; last = AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__); while (last != S->vr && S->rxdata_by_ns[last] == NULL) { last = AX25MODULO(last - 1, S->modulo, __FILE__, __func__, __LINE__); } if (last != S->vr) { // Ask for missing frames to be sent again. X.25 2.4.6.11 b) & 2.3.5.2.2 int resend[128]; int count = 0; int j; int allow_f1 = 1; j = S->vr; while (j != last) { if (S->rxdata_by_ns[j] == NULL) { resend[count++] = j; } j = AX25MODULO(j + 1, S->modulo, __FILE__, __func__, __LINE__); } send_srej_frames (S, resend, count, allow_f1); } else { // Not waiting for fill in of missing frames. X.25 2.4.6.11 c) pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } } else { // SREJ not enabled. // One might get the idea that it would make sense send REJ here if the reject exception is set. // However, I can't seem to find that buried in X.25 2.4.5.9. // And when we look at what happens when RR response, F=1 is received in state 4, it is // effectively REJ when N(R) is not the same as V(S). if (s_debug_retry) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n****** ENQUIRY RESPONSE srej not enbled, sending RR resp F=%d ******\n\n", f); } pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } } // end of RR,RNR,I cmd with P=1 else { // For cases other than (RR, RNR, I) command, P=1. pp = ax25_s_frame (S->addrs, S->num_addr, cr, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, f, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; } #else // As found in AX.25 spec. // Erratum: This is woefully inadequate when SREJ is enabled. // Erratum: Flow chart says RR/RNR command but I'm confident it should be response. pp = ax25_s_frame (S->addrs, S->num_addr, cr, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, f, NULL, 0); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->acknowledge_pending = 0; # endif } /* end enquiry_response */ /*------------------------------------------------------------------------------ * * Name: invoke_retransmission * * Inputs: nr_input - Resend starting with this. * Continue will all up to and including current V(S) value. * * Description: Resend one or more frames that have already been sent. * Should always send at least one. * * This is probably the result of getting REJ asking for a resend. * * Context: I would expect the caller to clear 'acknowledge_pending' after calling this * because we sent N(R), from V(R), to ack what was received from other guy. * I would also expect Stop T3 & Start T1 at the same place. * *------------------------------------------------------------------------------*/ static void invoke_retransmission (ax25_dlsm_t *S, int nr_input) { // Original flow chart showed saving V(S) into temp variable x, // using V(S) as loop control variable, and finally restoring it // to original value before returning. // Here we just a local variable instead of messing with it. // This should be equivalent but safer. int local_vs; int sent_count = 0; if (s_debug_misc) { text_color_set(DW_COLOR_ERROR); dw_printf ("invoke_retransmission(): starting with %d, state=%d, rc=%d, \n", nr_input, S->state, S->rc); } // I don't think we should be here if SREJ is enabled. // TODO: Figure out why this happens occasionally. // if (S->srej_enable != srej_none) { // text_color_set(DW_COLOR_ERROR); // dw_printf ("Internal Error, Did not expect to be here when SREJ enabled. %s %s %d\n", __FILE__, __func__, __LINE__); // } if (S->txdata_by_ns[nr_input] == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal Error, Can't resend starting with N(S) = %d. It is not available. %s %s %d\n", nr_input, __FILE__, __func__, __LINE__); return; } local_vs = nr_input; do { if (S->txdata_by_ns[local_vs] != NULL) { cmdres_t cr = cr_cmd; int ns = local_vs; int nr = S->vr; int p = 0; if (s_debug_misc) { text_color_set(DW_COLOR_INFO); dw_printf ("invoke_retransmission(): Resending N(S) = %d\n", ns); } packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, nr, ns, p, S->txdata_by_ns[ns]->pid, (unsigned char *)(S->txdata_by_ns[ns]->data), S->txdata_by_ns[ns]->len); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // Keep it around in case we need to send again. sent_count++; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal Error, state=%d, need to retransmit N(S) = %d for REJ but it is not available. %s %s %d\n", S->state, local_vs, __FILE__, __func__, __LINE__); } local_vs = AX25MODULO(local_vs + 1, S->modulo, __FILE__, __func__, __LINE__); } while (local_vs != S->vs); if (sent_count == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal Error, Nothing to retransmit. N(R)=%d, %s %s %d\n", nr_input, __FILE__, __func__, __LINE__); } } /* end invoke_retransmission */ /*------------------------------------------------------------------------------ * * Name: check_i_frame_ackd * * Purpose: * * Inputs: nr - N(R) from I or S frame, acknowledging receipt thru N(R)-1. * i.e. The next one expected by the peer is N(R). * * Outputs: S->va - updated from nr. * * Description: This is called for: * - 'I' frame received and N(R) is in expected range, states 3 & 4. * - RR/RNR command with p=1 received and N(R) is in expected range, state 3 only. * *------------------------------------------------------------------------------*/ static void check_i_frame_ackd (ax25_dlsm_t *S, int nr) { if (S->peer_receiver_busy) { SET_VA(nr); // Erratum? This looks odd to me. // It doesn't seem right that we would have T3 and T1 running at the same time. // Normally we stop one when starting the other. // Should this be Stop T3 instead? START_T3; if ( ! IS_T1_RUNNING) { START_T1; } } else if (nr == S->vs) { SET_VA(nr); STOP_T1; START_T3; select_t1_value (S); } else if (nr != S->va) { if (s_debug_misc) { text_color_set(DW_COLOR_DEBUG); dw_printf ("check_i_frame_ackd n(r)=%d, v(a)=%d, Set v(a) to new value %d\n", nr, S->va, nr); } SET_VA(nr); START_T1; // Erratum? Flow chart says "restart" rather than "start." // Is this intentional, what is the difference? } } /* check_i_frame_ackd */ /*------------------------------------------------------------------------------ * * Name: check_need_for_response * * Inputs: frame_type - frame_type_S_RR, etc. * * cr - Is it a command or response? * * pf - P/F from the frame. * * Description: This is called for RR, RNR, and REJ frames. * If it is a command with P=1, we reply with RR or RNR with F=1. * *------------------------------------------------------------------------------*/ static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf) { if (cr == cr_cmd && pf == 1) { int f = 1; enquiry_response (S, frame_type, f); } else if (cr == cr_res && pf == 1) { if (s_debug_protocol_errors) { text_color_set(DW_COLOR_ERROR); dw_printf ("Stream %d: AX.25 Protocol Error A: F=1 received but P=1 not outstanding.\n", S->stream_id); } } } /* end check_need_for_response */ /*------------------------------------------------------------------------------ * * Name: ui_check * * Description: I don't think we need this because UI frames are processed * without going thru the data link state machine. * *------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------ * * Name: select_t1_value * * Purpose: Dynamically adjust the T1 timeout value, commonly a fixed time known as FRACK. * * Inputs: S->rc Retry counter. * * S->srt Smoothed roundtrip time in seconds. * * S->t1_remaining_when_last_stopped * Seconds left on T1 when it is stopped. * * Outputs: S->srt New smoothed roundtrip time. * * S->t1v How long to wait for an acknowlegement before resending. * Value used when starting timer T1, in seconds. * Here it is dynamically adjusted. * * Description: How long should we wait for an ACK before sending again or giving up? * some implementations have a fixed length time. This is usually the FRACK parameter, * typically 3 seconds (D710A) or 4 seconds (KPC-3+). * * This should be increased for each digipeater in the path. * Here it is dynamically adjusted by taking the average time it takes to get a response * and then we double it. * * Rambling: It seems like a good idea to adapt to channel conditions, such as digipeater delays, * but it is fraught with peril if you are not careful. * * For example, if we accept an incoming connection and only receive some I frames and * send no I frames, T1 never gets started. In my earlier attempt, 't1_remaining_when_last_stopped' * had the initial value of 0 lacking any reason to set it differently. The calculation here * then kept pushing t1v up up up. After receiving 20 I frames and sending none, * t1v was over 300 seconds!!! * * We need some way to indicate that 't1_remaining_when_last_stopped' is not valid and * not to use it. Rather than adding a new variable, it is set to a negative value * initially to mean it has not been set yet. That solves one problem. * * T1 is paused whenever the channel is busy, either transmitting or receiving, * so the measured time could turn out to be a tiny fraction of a second, much less than * the frame transmission time. * If this gets too low, an unusually long random delay, before the sender's transmission, * could exceed this. I put in a lower limit for t1v, currently 1 second. * * What happens if we get multiple timeouts because we don't get a response? * For example, when we try to connect to a station which is not there, a KPC-3+ will give * up and report failure after 10 tries x 4 sec = 40 seconds. * * The algorithm in the AX.25 protocol spec shows increasing timeout values. * It might seem like a good idea but either it was not thought out very well * or I am not understanding it. If it is doubled each time, it gets awful large * very quickly. If we try to connect to a station which is not there, * we want to know within a minute, not an hour later. * * Keeping with the spirit of increasing the time but keeping it sane, * I increase the time linearly by a fraction of a second. * *------------------------------------------------------------------------------*/ static void select_t1_value (ax25_dlsm_t *S) { float old_srt = S->srt; // Erratum: I don't think this test for RC == 0 is valid. // We would need to set RC to 0 whenever we enter state 3 and we don't do that. // I think a more appropriate test would be to check if we are in state 3. // When things are going smoothly, it makes sense to fine tune timeout based on smoothed round trip time. // When in some other state, we might want to slowly increase the time to minimize collisions. // Maybe the solution is to set RC=0 when we enter state 3. // TODO: come back and revisit this. if (S->rc == 0) { if (S->t1_remaining_when_last_stopped >= 0) { // Negative means invalid, don't use it. // This is an IIR low pass filter. // Algebraically equivalent to version in AX.25 protocol spec but I think the // original intent is clearer by having 1/8 appear only once. S->srt = 7./8. * S->srt + 1./8. * ( S->t1v - S->t1_remaining_when_last_stopped ); } // We pause T1 when the channel is busy. // This includes both receiving someone else and us transmitting. // This can result in the round trip time going down to almost nothing. // My enhancement is to prevent srt from going below one second so // t1v should never be less than 2 seconds. // When t1v was allowed to go down to 1, we got occastional timeouts // even under ideal conditions, probably due to random CSMA delay time. if (S->srt < 1) { S->srt = 1; // Add another 2 seconds for each digipeater in path. if (S->num_addr > 2) { S->srt += 2 * (S->num_addr - 2); } } S->t1v = S->srt * 2; } else { if (S->t1_had_expired) { // This goes up exponentially if implemented as documented! // For example, if we were trying to connect to a station which is not there, we // would retry after 3, the 8, 16, 32, ... and not time out for over an hour. // That's ridiculous. Let's try increasing it by a quarter second each time. // We now give up after about a minute. // NO! S->t1v = powf(2, S->rc+1) * S->srt; S->t1v = S->rc * 0.25 + S->srt * 2; } } if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, new t1v = %.3f\n", S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v); } if (S->t1v < 0.99 || S->t1v > 30) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR? Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, Extreme new t1v = %.3f\n", S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v); } } /* end select_t1_value */ /*------------------------------------------------------------------------------ * * Name: set_version_2_0 * * Erratum: Flow chart refers to T2 which doesn't appear anywhere else. * *------------------------------------------------------------------------------*/ static void set_version_2_0 (ax25_dlsm_t *S) { S->srej_enable = srej_none; S->modulo = 8; S->n1_paclen = g_misc_config_p->paclen; S->k_maxframe = g_misc_config_p->maxframe_basic; S->n2_retry = g_misc_config_p->retry; } /* end set_version_2_0 */ /*------------------------------------------------------------------------------ * * Name: set_version_2_2 * *------------------------------------------------------------------------------*/ static void set_version_2_2 (ax25_dlsm_t *S) { S->srej_enable = srej_single; // Start with single. // Can be increased to multi with XID exchange. S->modulo = 128; S->n1_paclen = g_misc_config_p->paclen; S->k_maxframe = g_misc_config_p->maxframe_extended; S->n2_retry = g_misc_config_p->retry; } /* end set_version_2_2 */ /*------------------------------------------------------------------------------ * * Name: is_good_nr * * Purpose: Evaluate condition "V(a) <= N(r) <= V(s)" which appears in flow charts * for incoming I, RR, RNR, REJ, and SREJ frames. * * Inputs: S - state machine. Contains V(a) and V(s). * * nr - N(r) found in the incoming frame. * * Description: This determines whether the Received Sequence Number, N(R), is in * the expected range for normal processing or if we have an error * condition that needs recovery. * * This gets tricky due to the wrap around of sequence numbers. * * 4.2.4.4. Received Sequence Number N(R) * * The received sequence number exists in both I and S frames. * Prior to sending an I or S frame, this variable is updated to equal that * of the received state variable, thus implicitly acknowledging the proper * reception of all frames up to and including N(R)-1. * * Pattern noticed: Anytime we have "is_good_nr" returning true, we should always * - set V(A) from N(R) or * - call "check_i_frame_ackd" which does the same and some timer stuff. * *------------------------------------------------------------------------------*/ static int is_good_nr (ax25_dlsm_t *S, int nr) { int adjusted_va, adjusted_nr, adjusted_vs; int result; /* Adjust all values relative to V(a) before comparing so we won't have wrap around. */ #define adjust_by_va(x) (AX25MODULO((x) - S->va, S->modulo, __FILE__, __func__, __LINE__)) adjusted_va = adjust_by_va(S->va); // A clever compiler would know it is zero. adjusted_nr = adjust_by_va(nr); adjusted_vs = adjust_by_va(S->vs); result = adjusted_va <= adjusted_nr && adjusted_nr <= adjusted_vs; if (s_debug_misc) { text_color_set(DW_COLOR_DEBUG); dw_printf ("is_good_nr, V(a) %d <= nr %d <= V(s) %d, returns %d\n", S->va, nr, S->vs, result); } return (result); } /* end is_good_nr */ /*------------------------------------------------------------------------------ * * Name: i_frame_pop_off_queue * * Purpose: Transmit an I frame if we have one in the queue and conditions are right. * This appears two slightly different ways in the flow charts: * "frame pop off queue" * "I frame pops off queue" * * Inputs: i_frame_queue - Remove items from here. * peer_receiver_busy - If other end not busy. * V(s) - and we haven't reached window size. * V(a) * k * * Outputs: v(s) is incremented for each processed. * acknowledge_pending = 0 * *------------------------------------------------------------------------------*/ static void i_frame_pop_off_queue (ax25_dlsm_t *S) { if (s_debug_misc) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("i_frame_pop_off_queue () state=%d\n", S->state); } // TODO: Were we expecting something in the queue? // or is empty an expected situation? if (S->i_frame_queue == NULL) { if (s_debug_misc) { // TODO: add different switch for I frame queue. //text_color_set(DW_COLOR_DEBUG); //dw_printf ("i_frame_pop_off_queue () queue is empty get out, line %d\n", __LINE__); } // I Frame queue is empty. // Nothing to see here, folks. Move along. return; } switch (S->state) { case state_1_awaiting_connection: case state_5_awaiting_v22_connection: if (s_debug_misc) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("i_frame_pop_off_queue () line %d\n", __LINE__); } // This seems to say remove the I Frame from the queue and discard it if "layer 3 initiated" is set. // For the case of removing it from the queue and putting it back in we just leave it there. // Erratum? The flow chart seems to be backwards. // It would seem like we want to keep it if we are further along in the connection process. // I don't understand the intention here, and can't make a compelling argument on why it // is backwards, so it is implemented as documented. if (S->layer_3_initiated) { cdata_t *txdata; if (s_debug_misc) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("i_frame_pop_off_queue () discarding due to L3 init. line %d\n", __LINE__); } txdata = S->i_frame_queue; // Remove from head of list. S->i_frame_queue = txdata->next; cdata_delete (txdata); } break; case state_3_connected: case state_4_timer_recovery: if (s_debug_misc) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("i_frame_pop_off_queue () state %d, line %d\n", S->state, __LINE__); } while ( ( ! S->peer_receiver_busy ) && S->i_frame_queue != NULL && WITHIN_WINDOW_SIZE(S) ) { cdata_t *txdata; txdata = S->i_frame_queue; // Remove from head of list. S->i_frame_queue = txdata->next; txdata->next = NULL; cmdres_t cr = cr_cmd; int ns = S->vs; int nr = S->vr; int p = 0; if (s_debug_misc || s_debug_radio) { //dw_printf ("i_frame_pop_off_queue () ns=%d, queue for transmit \"", ns); //ax25_safe_print (txdata->data, txdata->len, 1); //dw_printf ("\"\n"); } packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, nr, ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len); if (s_debug_misc) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__); } lm_data_request (S->chan, TQ_PRIO_1_LO, pp); // Stash in sent array in case it gets lost and needs to be sent again. if (S->txdata_by_ns[ns] != NULL) { cdata_delete (S->txdata_by_ns[ns]); } S->txdata_by_ns[ns] = txdata; SET_VS(AX25MODULO(S->vs + 1, S->modulo, __FILE__, __func__, __LINE__)); // increment sequence of last sent. S->acknowledge_pending = 0; // Erratum: I think we always want to restart T1 when an I frame is sent. // Otherwise we could time out too soon. #if 1 STOP_T3; START_T1; #else if ( ! IS_T1_RUNNING) { STOP_T3; START_T1; } #endif } break; case state_0_disconnected: case state_2_awaiting_release: // Do nothing. break; } } /* end i_frame_pop_off_queue */ /*------------------------------------------------------------------------------ * * Name: discard_i_queue * * Purpose: Discard any data chunks waiting to be sent. * *------------------------------------------------------------------------------*/ static void discard_i_queue (ax25_dlsm_t *S) { cdata_t *t; while (S->i_frame_queue != NULL) { t = S->i_frame_queue; S->i_frame_queue = S->i_frame_queue->next; cdata_delete (t); } } /* end discard_i_queue */ /*------------------------------------------------------------------------------ * * Name: enter_new_state * * Purpose: Switch to new state. * * Description: Use a function, rather than setting variable directly, so we have * one common point for debug output and possibly other things we * might want to do at a state change. * *------------------------------------------------------------------------------*/ // TODO: requeuing??? static void enter_new_state (ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line) { if (s_debug_variables) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n"); dw_printf (">>> NEW STATE = %d, previously %d, called from %s %d <<<\n", new_state, S->state, from_func, from_line); dw_printf ("\n"); } assert (new_state >= 0 && new_state <= 5); if (( new_state == state_3_connected || new_state == state_4_timer_recovery) && S->state != state_3_connected && S->state != state_4_timer_recovery ) { ptt_set (OCTYPE_CON, S->chan, 1); // Turn on connected indicator if configured. } else if (( new_state != state_3_connected && new_state != state_4_timer_recovery) && ( S->state == state_3_connected || S->state == state_4_timer_recovery ) ) { ptt_set (OCTYPE_CON, S->chan, 0); // Turn off connected indicator if configured. // Ideally we should look at any other link state machines // for this channel and leave the indicator on if any // are connected. I'm not that worried about it. } S->state = new_state; } /* end enter_new_state */ /*------------------------------------------------------------------------------ * * Name: mdl_negotiate_request * * Purpose: After receiving UA, in response to SABME, this starts up the XID exchange. * * Description: Send XID command. * Start timer TM201 so we can retry if timeout waiting for response. * Enter MDL negotiating state. * *------------------------------------------------------------------------------*/ static void mdl_negotiate_request (ax25_dlsm_t *S) { struct xid_param_s param; unsigned char xinfo[40]; int xlen; cmdres_t cmd = cr_cmd; int p = 1; int nopid = 0; packet_t pp; int n; // At least one known [partial] v2.2 implementation understands SABME but not XID. // Rather than wasting time, sending XID repeatedly until giving up, we have a workaround. // The configuration file can contain a list of stations known not to respond to XID. // Obviously this applies only to v2.2 because XID was not part of v2.0. for (n = 0; n < g_misc_config_p->noxid_count; n++) { if (strcmp(S->addrs[PEERCALL],g_misc_config_p->noxid_addrs[n]) == 0) { return; } } switch (S->mdl_state) { case mdl_state_0_ready: initiate_negotiation (S, ¶m); xlen = xid_encode (¶m, xinfo, cmd); pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_XID, p, nopid, xinfo, xlen); lm_data_request (S->chan, TQ_PRIO_1_LO, pp); S->mdl_rc = 0; START_TM201; S->mdl_state = mdl_state_1_negotiating; break; case mdl_state_1_negotiating: // SDL says "requeue" but I don't understand how it would be useful or how to do it. break; } } /* end mdl_negotiate_request */ /*------------------------------------------------------------------------------ * * Name: initiate_negotiation * * Purpose: Used when preparing the XID *command*. * * Description: Prepare set of parameters to request from the other station. * *------------------------------------------------------------------------------*/ static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param) { param->full_duplex = 0; switch (S->srej_enable) { case srej_single: case srej_multi: param->srej = srej_multi; // see if other end reconizes it. break; case srej_none: default: param->srej = srej_none; break; } param->modulo = S->modulo; param->i_field_length_rx = S->n1_paclen; // Hmmmm. Should we ask for what the user // specified for PACLEN or offer the maximum // that we can handle, AX25_N1_PACLEN_MAX? param->window_size_rx = S->k_maxframe; param->ack_timer = (int)(g_misc_config_p->frack * 1000); param->retries = S->n2_retry; } /*------------------------------------------------------------------------------ * * Name: negotiation_response * * Purpose: Used when receiving the XID command and preparing the XID response. * * Description: Take what other station has asked for and reduce if we have lesser capabilities. * For example if other end wants 8k information part we reduce it to 2k. * Ack time and retries are the opposite, we take the maximum. * * Question: If the other send leaves anything undefined should we leave it * undefined or fill in what we would like before sending it back? * * The original version of the protocol spec left this open. * The 2006 revision, in red, says we should fill in defaults for anything * not specified. This makes sense. We send back a complete set of parameters * so both ends should agree. * *------------------------------------------------------------------------------*/ static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param) { // TODO: Integrate with new full duplex capability in v1.5. param->full_duplex = 0; // Other end might want 8. // Seems unlikely. If it implements XID it should have modulo 128. if (param->modulo == modulo_unknown) { param->modulo = 8; // Not specified. Set default. } else { param->modulo = MIN(param->modulo, 128); } // We can do REJ or SREJ but won't combine them. // Erratum: 2006 version, section, 4.3.3.7 says default selective reject - reject. // We can't do that. if (param->srej == srej_not_specified) { param->srej = (param->modulo == 128) ? srej_single : srej_none; // not specified, set default } // We can currently do up to 2k. // Take minimum of that and what other guy asks for. if (param->i_field_length_rx == G_UNKNOWN) { param->i_field_length_rx = 256; // Not specified, take default. } else { param->i_field_length_rx = MIN(param->i_field_length_rx, AX25_N1_PACLEN_MAX); } // In theory extended mode can have window size of 127 but // I'm limiting it to 63 for the reason mentioned in the SREJ logic. if (param->window_size_rx == G_UNKNOWN) { param->window_size_rx = (param->modulo == 128) ? 32 : 4; // not specified, set default. } else { if (param->modulo == 128) param->window_size_rx = MIN(param->window_size_rx, AX25_K_MAXFRAME_EXTENDED_MAX); else param->window_size_rx = MIN(param->window_size_rx, AX25_K_MAXFRAME_BASIC_MAX); } // Erratum: Unclear. Is the Acknowledgement Timer before or after compensating for digipeaters // in the path? e.g. Typically TNCs use the FRACK parameter for this and it often defaults to 3. // However, the actual timeout value might be something like FRACK*(2*m+1) where m is the number of // digipeaters in the path. I'm assuming this is the FRACK value and any additional time, for // digipeaters will be added in locally at each end on top of this exchanged value. if (param->ack_timer == G_UNKNOWN) { param->ack_timer = 3000; // not specified, set default. } else { param->ack_timer = MAX(param->ack_timer, (int)(g_misc_config_p->frack * 1000)); } if (param->retries == G_UNKNOWN) { param->retries = 10; // not specified, set default. } else { param->retries = MAX(param->retries, S->n2_retry); } // IMPORTANT: Take values we have agreed upon and put into my running configuration. complete_negotiation(S, param); } /*------------------------------------------------------------------------------ * * Name: complete_negotiation * * Purpose: Used when preparing or receiving the XID *response*. * * Description: Take set of parameters which we have agreed upon and apply * to the running configuration. * * TODO: Should do some checking here in case other station * sends something crazy. * *------------------------------------------------------------------------------*/ static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param) { if (param->srej != srej_not_specified) { S->srej_enable = param->srej; } if (param->modulo != modulo_unknown) { // Disaster if aren't agreeing on this. S->modulo = param->modulo; } if (param->i_field_length_rx != G_UNKNOWN) { S->n1_paclen = param->i_field_length_rx; } if (param->window_size_rx != G_UNKNOWN) { S->k_maxframe = param->window_size_rx; } if (param->ack_timer != G_UNKNOWN) { S->t1v = param->ack_timer * 0.001; } if (param->retries != G_UNKNOWN) { S->n2_retry = param->retries; } } //################################################################################### //################################################################################### // // Timers. // // Start. // Stop. // Pause (when channel busy) & resume. // Is it running? // Did it expire before being stopped? // When will next one expire? // //################################################################################### //################################################################################### static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) { double now = dtime_now(); if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("Start T1 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line); } S->t1_exp = now + S->t1v; if (S->radio_channel_busy) { S->t1_paused_at = now; } else { S->t1_paused_at = 0; } S->t1_had_expired = 0; } /* end start_t1 */ static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) { double now = dtime_now(); RESUME_T1; // adjust expire time if paused. if (S->t1_exp == 0.0) { // Was already stopped. } else { S->t1_remaining_when_last_stopped = S->t1_exp - now; if (S->t1_remaining_when_last_stopped < 0) S->t1_remaining_when_last_stopped = 0; } // Normally this would be at the top but we don't know time remaining at that point. if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG_TIMER); if (S->t1_exp == 0.0) { dw_printf ("Stop T1. Wasn't running, [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line); } else { dw_printf ("Stop T1, %.3f remaining, [now=%.3f] from %s %d\n", S->t1_remaining_when_last_stopped, now - S->start_time, from_func, from_line); } } S->t1_exp = 0.0; // now stopped. S->t1_had_expired = 0; // remember that it did not expire. } /* end stop_t1 */ static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line) { int result = S->t1_exp != 0.0; if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("is_t1_running? returns %d\n", result); } return (result); } /* end is_t1_running */ static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) { if (S->t1_exp == 0.0) { // Stopped so there is nothing to do. } else if (S->t1_paused_at == 0.0) { // Running and not paused. double now = dtime_now(); S->t1_paused_at = now; if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Paused T1 with %.3f still remaining, [now=%.3f] from %s %d\n", S->t1_exp - now, now - S->start_time, from_func, from_line); } } else { if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("T1 error: Didn't expect pause when already paused.\n"); } } } /* end pause_t1 */ static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line) { if (S->t1_exp == 0.0) { // Stopped so there is nothing to do. } else if (S->t1_paused_at == 0.0) { // Running but not paused. } else { double now = dtime_now(); double paused_for_sec = now - S->t1_paused_at; S->t1_exp += paused_for_sec; S->t1_paused_at = 0.0; if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Resumed T1 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->t1_exp - now, now - S->start_time); } } } /* end resume_t1 */ // T3 is a lot simpler. // Here we are talking about minutes of inactivity with the peer // rather than expecting a response within seconds where timing is more critical. // We don't need to capture remaining time when stopped. // I don't think there is a need to pause it due to the large time frame. static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line) { double now = dtime_now(); if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("Start T3 for %.3f sec, [now=%.3f] from %s %d\n", T3_DEFAULT, now - S->start_time, from_func, from_line); } S->t3_exp = now + T3_DEFAULT; } static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line) { if (s_debug_timers) { double now = dtime_now(); text_color_set(DW_COLOR_DEBUG_TIMER); if (S->t3_exp == 0.0) { dw_printf ("Stop T3. Wasn't running.\n"); } else { dw_printf ("Stop T3, %.3f remaining, [now=%.3f] from %s %d\n", S->t3_exp - now, now - S->start_time, from_func, from_line); } } S->t3_exp = 0.0; } // TM201 is similar to T1. // It needs to be paused whent the channel is busy. // Simpler because we don't need to keep track of time remaining when stopped. static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) { double now = dtime_now(); if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("Start TM201 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line); } S->tm201_exp = now + S->t1v; if (S->radio_channel_busy) { S->tm201_paused_at = now; } else { S->tm201_paused_at = 0; } } /* end start_tm201 */ static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) { double now = dtime_now(); if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG_TIMER); dw_printf ("Stop TM201. [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line); } S->tm201_exp = 0.0; // now stopped. } /* end stop_tm201 */ static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) { if (S->tm201_exp == 0.0) { // Stopped so there is nothing to do. } else if (S->tm201_paused_at == 0.0) { // Running and not paused. double now = dtime_now(); S->tm201_paused_at = now; if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Paused TM201 with %.3f still remaining, [now=%.3f] from %s %d\n", S->tm201_exp - now, now - S->start_time, from_func, from_line); } } else { if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("TM201 error: Didn't expect pause when already paused.\n"); } } } /* end pause_tm201 */ static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line) { if (S->tm201_exp == 0.0) { // Stopped so there is nothing to do. } else if (S->tm201_paused_at == 0.0) { // Running but not paused. } else { double now = dtime_now(); double paused_for_sec = now - S->tm201_paused_at; S->tm201_exp += paused_for_sec; S->tm201_paused_at = 0.0; if (s_debug_timers) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Resumed TM201 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->tm201_exp - now, now - S->start_time); } } } /* end resume_tm201 */ double ax25_link_get_next_timer_expiry (void) { double tnext = 0; ax25_dlsm_t *p; for (p = list_head; p != NULL; p = p->next) { // Consider if running and not paused. if (p->t1_exp != 0 && p->t1_paused_at == 0) { if (tnext == 0) { tnext = p->t1_exp; } else if (p->t1_exp < tnext) { tnext = p->t1_exp; } } if (p->t3_exp != 0) { if (tnext == 0) { tnext = p->t3_exp; } else if (p->t3_exp < tnext) { tnext = p->t3_exp; } } if (p->tm201_exp != 0 && p->tm201_paused_at == 0) { if (tnext == 0) { tnext = p->tm201_exp; } else if (p->tm201_exp < tnext) { tnext = p->tm201_exp; } } } if (s_debug_timers > 1) { text_color_set(DW_COLOR_DEBUG); if (tnext == 0.0) { dw_printf ("ax25_link_get_next_timer_expiry returns none.\n"); } else { dw_printf ("ax25_link_get_next_timer_expiry returns %.3f sec from now.\n", tnext - dtime_now()); } } return (tnext); } /* end ax25_link_get_next_timer_expiry */ /* end ax25_link.c */ direwolf-1.5+dfsg/ax25_link.h000066400000000000000000000037721347750676600161140ustar00rootroot00000000000000 /* ax25_link.h */ #ifndef AX25_LINK_H #define AX25_LINK_H 1 #include "ax25_pad.h" // for AX25_MAX_INFO_LEN #include "dlq.h" // for dlq_item_t #include "config.h" // for struct misc_config_s // Limits and defaults for parameters. #define AX25_N1_PACLEN_MIN 1 // Max bytes in Information part of frame. #define AX25_N1_PACLEN_DEFAULT 256 // some v2.0 implementations have 128 #define AX25_N1_PACLEN_MAX AX25_MAX_INFO_LEN // from ax25_pad.h #define AX25_N2_RETRY_MIN 1 // Number of times to retry before giving up. #define AX25_N2_RETRY_DEFAULT 10 #define AX25_N2_RETRY_MAX 15 #define AX25_T1V_FRACK_MIN 1 // Number of seconds to wait before retrying. #define AX25_T1V_FRACK_DEFAULT 3 // KPC-3+ has 4. TM-D710A has 3. #define AX25_T1V_FRACK_MAX 15 #define AX25_K_MAXFRAME_BASIC_MIN 1 // Window size - number of I frames to send before waiting for ack. #define AX25_K_MAXFRAME_BASIC_DEFAULT 4 #define AX25_K_MAXFRAME_BASIC_MAX 7 #define AX25_K_MAXFRAME_EXTENDED_MIN 1 #define AX25_K_MAXFRAME_EXTENDED_DEFAULT 32 #define AX25_K_MAXFRAME_EXTENDED_MAX 63 // In theory 127 but I'm restricting as explained in SREJ handling. // Call once at startup time. void ax25_link_init (struct misc_config_s *pconfig); // IMPORTANT: // These functions must be called on a single thread, one at a time. // The Data Link Queue (DLQ) is used to serialize events from multiple sources. // Maybe the dispatch switch should be moved to ax25_link.c so they can all // be made static and they can't be called from the wrong place accidentally. void dl_connect_request (dlq_item_t *E); void dl_disconnect_request (dlq_item_t *E); void dl_data_request (dlq_item_t *E); void dl_register_callsign (dlq_item_t *E); void dl_unregister_callsign (dlq_item_t *E); void dl_client_cleanup (dlq_item_t *E); void lm_data_indication (dlq_item_t *E); void lm_seize_confirm (dlq_item_t *E); void lm_channel_busy (dlq_item_t *E); void dl_timer_expiry (void); double ax25_link_get_next_timer_expiry (void); #endif /* end ax25_link.h */direwolf-1.5+dfsg/ax25_pad.c000066400000000000000000002350111347750676600157070ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011 , 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Name: ax25_pad * * Purpose: Packet assembler and disasembler. * * This was written when I was only concerned about APRS which * uses only UI frames. ax25_pad2.c, added years later, has * functions for dealing with other types of frames. * * We can obtain AX.25 packets from different sources: * * (a) from an HDLC frame. * (b) from text representation. * (c) built up piece by piece. * * We also want to use a packet in different ways: * * (a) transmit as an HDLC frame. * (b) print in human-readable text. * (c) take it apart piece by piece. * * Looking at the more general case, we also want to modify * an existing packet. For instance an APRS repeater might * want to change "WIDE2-2" to "WIDE2-1" and retransmit it. * * * Description: * * * APRS uses only UI frames. * Each starts with 2-10 addressses (14-70 octets): * * * Destination Address (note: opposite order in printed format) * * * Source Address * * * 0-8 Digipeater Addresses (Could there ever be more as a result of * digipeaters inserting their own call for * the tracing feature? * NO. The limit is 8 when transmitting AX.25 over the * radio. * Communication with an IGate server could * have a longer VIA path but that is only in text form, * not as an AX.25 frame.) * * Each address is composed of: * * * 6 upper case letters or digits, blank padded. * These are shifted left one bit, leaving the LSB always 0. * * * a 7th octet containing the SSID and flags. * The LSB is always 0 except for the last octet of the address field. * * The final octet of the Destination has the form: * * C R R SSID 0, where, * * C = command/response = 1 * R R = Reserved = 1 1 * SSID = substation ID * 0 = zero * * The AX.25 spec states that the RR bits should be 11 if not used. * There are a couple documents talking about possible uses for APRS. * I'm ignoring them for now. * http://www.aprs.org/aprs12/preemptive-digipeating.txt * http://www.aprs.org/aprs12/RR-bits.txt * * I don't recall why I originally intended to set the source/destination C bits both to 1. * Reviewing this 5 years later, after spending more time delving into the * AX.25 spec, I think it should be 1 for destination and 0 for source. * In practice you see all four combinations being used by APRS stations * and no one really cares about these two bits. * * The final octet of the Source has the form: * * C R R SSID 0, where, * * C = command/response = 1 (originally, now I think it should be 0 for source.) * (Haven't gone back to check to see what code actually does.) * R R = Reserved = 1 1 * SSID = substation ID * 0 = zero (or 1 if no repeaters) * * The final octet of each repeater has the form: * * H R R SSID 0, where, * * H = has-been-repeated = 0 initially. * Set to 1 after this address has been used. * R R = Reserved = 1 1 * SSID = substation ID * 0 = zero (or 1 if last repeater in list) * * A digipeater would repeat this frame if it finds its address * with the "H" bit set to 0 and all earlier repeater addresses * have the "H" bit set to 1. * The "H" bit would be set to 1 in the repeated frame. * * In standard monitoring format, an asterisk is displayed after the last * digipeater with the "H" bit set. That indicates who you are hearing * over the radio. * (That is if digipeaters update the via path properly. Some don't so * we don't know who we are hearing. This is discussed in the User Guide.) * No asterisk means the source is being heard directly. * * Example, if we can hear all stations involved, * * SRC>DST,RPT1,RPT2,RPT3: -- we heard SRC * SRC>DST,RPT1*,RPT2,RPT3: -- we heard RPT1 * SRC>DST,RPT1,RPT2*,RPT3: -- we heard RPT2 * SRC>DST,RPT1,RPT2,RPT3*: -- we heard RPT3 * * * Next we have: * * * One byte Control Field - APRS uses 3 for UI frame * The more general AX.25 frame can have two. * * * One byte Protocol ID - APRS uses 0xf0 for no layer 3 * * Finally the Information Field of 1-256 bytes. * * And, of course, the 2 byte CRC. * * The descriptions above, for the C, H, and RR bits, are for APRS usage. * When operating as a KISS TNC we just pass everything along and don't * interpret or change them. * * * Constructors: ax25_init - Clear everything. * ax25_from_text - Tear apart a text string * ax25_from_frame - Tear apart an AX.25 frame. * Must be called before any other function. * * Get methods: .... - Extract destination, source, or digipeater * address from frame. * * Assumptions: CRC has already been verified to be correct. * *------------------------------------------------------------------*/ #define AX25_PAD_C /* this will affect behavior of ax25_pad.h */ #include "direwolf.h" #include #include #include #include #include #include "regex.h" #if __WIN32__ char *strtok_r(char *str, const char *delim, char **saveptr); #endif #include "textcolor.h" #include "ax25_pad.h" #include "fcs_calc.h" /* * Accumulate statistics. * If new_count gets much larger than delete_count plus the size of * the transmit queue we have a memory leak. */ static volatile int new_count = 0; static volatile int delete_count = 0; static volatile int last_seq_num = 0; #if AX25MEMDEBUG int ax25memdebug = 0; void ax25memdebug_set(void) { ax25memdebug = 1; } int ax25memdebug_get (void) { return (ax25memdebug); } int ax25memdebug_seq (packet_t this_p) { return (this_p->seq); } #endif #define CLEAR_LAST_ADDR_FLAG this_p->frame_data[this_p->num_addr*7-1] &= ~ SSID_LAST_MASK #define SET_LAST_ADDR_FLAG this_p->frame_data[this_p->num_addr*7-1] |= SSID_LAST_MASK /*------------------------------------------------------------------------------ * * Name: ax25_new * * Purpose: Allocate memory for a new packet object. * * Returns: Identifier for a new packet object. * In the current implementation this happens to be a pointer. * *------------------------------------------------------------------------------*/ packet_t ax25_new (void) { struct packet_s *this_p; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("ax25_new(): before alloc, new=%d, delete=%d\n", new_count, delete_count); #endif last_seq_num++; new_count++; /* * check for memory leak. */ // version 1.4 push up the threshold. We could have considerably more with connected mode. //if (new_count > delete_count + 100) { if (new_count > delete_count + 256) { text_color_set(DW_COLOR_ERROR); dw_printf ("Report to WB2OSZ - Memory leak for packet objects. new=%d, delete=%d\n", new_count, delete_count); #if AX25MEMDEBUG // Force on debug option to gather evidence. ax25memdebug_set(); #endif } this_p = calloc(sizeof (struct packet_s), (size_t)1); if (this_p == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - can't allocate memory in ax25_new.\n"); } assert (this_p != NULL); this_p->magic1 = MAGIC; this_p->seq = last_seq_num; this_p->magic2 = MAGIC; this_p->num_addr = (-1); return (this_p); } /*------------------------------------------------------------------------------ * * Name: ax25_delete * * Purpose: Destroy a packet object, freeing up memory it was using. * *------------------------------------------------------------------------------*/ #if AX25MEMDEBUG void ax25_delete_debug (packet_t this_p, char *src_file, int src_line) #else void ax25_delete (packet_t this_p) #endif { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("ax25_delete(): before free, new=%d, delete=%d\n", new_count, delete_count); #endif if (this_p == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - NULL pointer passed to ax25_delete.\n"); return; } delete_count++; #if AX25MEMDEBUG if (ax25memdebug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("ax25_delete, seq=%d, called from %s %d, new_count=%d, delete_count=%d\n", this_p->seq, src_file, src_line, new_count, delete_count); } #endif assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); this_p->magic1 = 0; this_p->magic1 = 0; //memset (this_p, 0, sizeof (struct packet_s)); free (this_p); } /*------------------------------------------------------------------------------ * * Name: ax25_from_text * * Purpose: Parse a frame in human-readable monitoring format and change * to internal representation. * * Input: monitor - "TNC-2" monitor format for packet. i.e. * source>dest[,repeater1,repeater2,...]:information * * The information part can have non-printable characters * in the form of <0xff>. This will be converted to single * bytes. e.g. <0x0d> is carriage return. * In version 1.4H we will allow nul characters which means * we have to maintain a length rather than using strlen(). * I maintain that it violates the spec but want to handle it * because it does happen and we want to preserve it when * acting as an IGate rather than corrupting it. * * strict - True to enforce rules for packets sent over the air. * False to be more lenient for packets from IGate server. * * Messages from an IGate server can have longer * addresses after qAC. Up to 9 observed so far. * * We can just truncate the name because we will only * end up discarding it. TODO: check on this. * * Returns: Pointer to new packet object in the current implementation. * * Outputs: Use the "get" functions to retrieve information in different ways. * *------------------------------------------------------------------------------*/ #if AX25MEMDEBUG packet_t ax25_from_text_debug (char *monitor, int strict, char *src_file, int src_line) #else packet_t ax25_from_text (char *monitor, int strict) #endif { /* * Tearing it apart is destructive so make our own copy first. */ char stuff[512]; char *pinfo; char *pa; char *saveptr; /* Used with strtok_r because strtok is not thread safe. */ int ssid_temp, heard_temp; char atemp[AX25_MAX_ADDR_LEN]; char info_part[AX25_MAX_INFO_LEN+1]; int info_len; packet_t this_p = ax25_new (); #if AX25MEMDEBUG if (ax25memdebug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("ax25_from_text, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line); } #endif /* Is it possible to have a nul character (zero byte) in the */ /* information field of an AX.25 frame? */ /* At this point, we have a normal C string. */ /* It is possible that will convert <0x00> to a nul character later. */ /* There we need to maintain a separate length and not use normal C string functions. */ strlcpy (stuff, monitor, sizeof(stuff)); /* * Initialize the packet structure with two addresses and control/pid * for APRS. */ memset (this_p->frame_data + AX25_DESTINATION*7, ' ' << 1, 6); this_p->frame_data[AX25_DESTINATION*7+6] = SSID_H_MASK | SSID_RR_MASK; memset (this_p->frame_data + AX25_SOURCE*7, ' ' << 1, 6); this_p->frame_data[AX25_SOURCE*7+6] = SSID_H_MASK | SSID_RR_MASK | SSID_LAST_MASK; this_p->frame_data[14] = AX25_UI_FRAME; this_p->frame_data[15] = AX25_PID_NO_LAYER_3; this_p->frame_len = 7 + 7 + 1 + 1; this_p->num_addr = (-1); assert (ax25_get_num_addr(this_p) == 2); /* * Separate the addresses from the rest. */ pinfo = strchr (stuff, ':'); if (pinfo == NULL) { ax25_delete (this_p); return (NULL); } *pinfo = '\0'; pinfo++; /* * Separate the addresses. * Note that source and destination order is swappped. */ /* * Source address. * Don't use traditional strtok because it is not thread safe. */ pa = strtok_r (stuff, ">", &saveptr); if (pa == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create packet from text. No source address\n"); ax25_delete (this_p); return (NULL); } if ( ! ax25_parse_addr (AX25_SOURCE, pa, strict, atemp, &ssid_temp, &heard_temp)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create packet from text. Bad source address\n"); ax25_delete (this_p); return (NULL); } ax25_set_addr (this_p, AX25_SOURCE, atemp); ax25_set_h (this_p, AX25_SOURCE); // c/r in this position ax25_set_ssid (this_p, AX25_SOURCE, ssid_temp); /* * Destination address. */ pa = strtok_r (NULL, ",", &saveptr); if (pa == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create packet from text. No destination address\n"); ax25_delete (this_p); return (NULL); } if ( ! ax25_parse_addr (AX25_DESTINATION, pa, strict, atemp, &ssid_temp, &heard_temp)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create packet from text. Bad destination address\n"); ax25_delete (this_p); return (NULL); } ax25_set_addr (this_p, AX25_DESTINATION, atemp); ax25_set_h (this_p, AX25_DESTINATION); // c/r in this position ax25_set_ssid (this_p, AX25_DESTINATION, ssid_temp); /* * VIA path. */ while (( pa = strtok_r (NULL, ",", &saveptr)) != NULL && this_p->num_addr < AX25_MAX_ADDRS ) { int k; k = this_p->num_addr; if ( ! ax25_parse_addr (k, pa, strict, atemp, &ssid_temp, &heard_temp)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create packet from text. Bad digipeater address\n"); ax25_delete (this_p); return (NULL); } ax25_set_addr (this_p, k, atemp); ax25_set_ssid (this_p, k, ssid_temp); // Does it have an "*" at the end? // TODO: Complain if more than one "*". // Could also check for all has been repeated bits are adjacent. if (heard_temp) { for ( ; k >= AX25_REPEATER_1; k--) { ax25_set_h (this_p, k); } } } /* * Finally, process the information part. * * Translate hexadecimal values like <0xff> to single bytes. * MIC-E format uses 5 different non-printing characters. * We might want to manually generate UTF-8 characters such as degree. */ //#define DEBUG14H 1 #if DEBUG14H text_color_set(DW_COLOR_DEBUG); dw_printf ("BEFORE: %s\nSAFE: ", pinfo); ax25_safe_print (pinfo, -1, 0); dw_printf ("\n"); #endif info_len = 0; while (*pinfo != '\0' && info_len < AX25_MAX_INFO_LEN) { if (strlen(pinfo) >= 6 && pinfo[0] == '<' && pinfo[1] == '0' && pinfo[2] == 'x' && isxdigit(pinfo[3]) && isxdigit(pinfo[4]) && pinfo[5] == '>') { char *p; info_part[info_len] = strtol (pinfo + 3, &p, 16); info_len++; pinfo += 6; } else { info_part[info_len] = *pinfo; info_len++; pinfo++; } } info_part[info_len] = '\0'; #if DEBUG14H text_color_set(DW_COLOR_DEBUG); dw_printf ("AFTER: %s\nSAFE: ", info_part); ax25_safe_print (info_part, info_len, 0); dw_printf ("\n"); #endif /* * Append the info part. */ memcpy ((char*)(this_p->frame_data+this_p->frame_len), info_part, info_len); this_p->frame_len += info_len; return (this_p); } /*------------------------------------------------------------------------------ * * Name: ax25_from_frame * * Purpose: Split apart an HDLC frame to components. * * Inputs: fbuf - Pointer to beginning of frame. * * flen - Length excluding the two FCS bytes. * * alevel - Audio level of received signal. * Maximum range 0 - 100. * -1 might be used when not applicable. * * Returns: Pointer to new packet object or NULL if error. * * Outputs: Use the "get" functions to retrieve information in different ways. * *------------------------------------------------------------------------------*/ #if AX25MEMDEBUG packet_t ax25_from_frame_debug (unsigned char *fbuf, int flen, alevel_t alevel, char *src_file, int src_line) #else packet_t ax25_from_frame (unsigned char *fbuf, int flen, alevel_t alevel) #endif { packet_t this_p; /* * First make sure we have an acceptable length: * * We are not concerned with the FCS (CRC) because someone else checked it. * * Is is possible to have zero length for info? * * In the original version, assuming APRS, the answer was no. * We always had at least 3 octets after the address part: * control, protocol, and first byte of info part for data type. * * In later versions, this restriction was relaxed so other * variations of AX.25 could be used. Now the minimum length * is 7+7 for addresses plus 1 for control. * */ if (flen < AX25_MIN_PACKET_LEN || flen > AX25_MAX_PACKET_LEN) { text_color_set(DW_COLOR_ERROR); dw_printf ("Frame length %d not in allowable range of %d to %d.\n", flen, AX25_MIN_PACKET_LEN, AX25_MAX_PACKET_LEN); return (NULL); } this_p = ax25_new (); #if AX25MEMDEBUG if (ax25memdebug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("ax25_from_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line); } #endif /* Copy the whole thing intact. */ memcpy (this_p->frame_data, fbuf, flen); this_p->frame_data[flen] = 0; this_p->frame_len = flen; /* Find number of addresses. */ this_p->num_addr = (-1); (void) ax25_get_num_addr (this_p); return (this_p); } /*------------------------------------------------------------------------------ * * Name: ax25_dup * * Purpose: Make a copy of given packet object. * * Inputs: copy_from - Existing packet object. * * Returns: Pointer to new packet object or NULL if error. * * *------------------------------------------------------------------------------*/ #if AX25MEMDEBUG packet_t ax25_dup_debug (packet_t copy_from, char *src_file, int src_line) #else packet_t ax25_dup (packet_t copy_from) #endif { int save_seq; packet_t this_p; this_p = ax25_new (); assert (this_p != NULL); save_seq = this_p->seq; memcpy (this_p, copy_from, sizeof (struct packet_s)); this_p->seq = save_seq; #if AX25MEMDEBUG if (ax25memdebug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("ax25_dup, seq=%d, called from %s %d, clone of seq %d\n", this_p->seq, src_file, src_line, copy_from->seq); } #endif return (this_p); } /*------------------------------------------------------------------------------ * * Name: ax25_parse_addr * * Purpose: Parse address with optional ssid. * * Inputs: position - AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER_1... * Used for more specific error message. -1 if not used. * * in_addr - Input such as "WB2OSZ-15*" * * strict - 1 (true) for strict checking (6 characters, no lower case, * SSID must be in range of 0 to 15). * Strict is appropriate for packets sent * over the radio. Communication with IGate * allows lower case (e.g. "qAR") and two * alphanumeric characters for the SSID. * We also get messages like this from a server. * KB1POR>APU25N,TCPIP*,qAC,T2NUENGLD:... * * 2 (extra true) will complain if * is found at end. * * Outputs: out_addr - Address without any SSID. * Must be at least AX25_MAX_ADDR_LEN bytes. * * out_ssid - Numeric value of SSID. * * out_heard - True if "*" found. * * Returns: True (1) if OK, false (0) if any error. * When 0, out_addr, out_ssid, and out_heard are unpredictable. * * *------------------------------------------------------------------------------*/ static const char *position_name[1 + AX25_MAX_ADDRS] = { "", "Destination ", "Source ", "Digi1 ", "Digi2 ", "Digi3 ", "Digi4 ", "Digi5 ", "Digi6 ", "Digi7 ", "Digi8 " }; int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard) { char *p; char sstr[8]; /* Should be 1 or 2 digits for SSID. */ int i, j, k; int maxlen; *out_addr = '\0'; *out_ssid = 0; *out_heard = 0; if (position < -1) position = -1; if (position > AX25_REPEATER_8) position = AX25_REPEATER_8; position++; /* Adjust for position_name above. */ if (strict && strlen(in_addr) >= 2 && strncmp(in_addr, "qA", 2) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("%sAddress \"%s\" is a \"q-construct\" used for communicating with\n", position_name[position], in_addr); dw_printf ("APRS Internet Servers. It should never appear when going over the radio.\n"); } //dw_printf ("ax25_parse_addr in: %s\n", in_addr); maxlen = strict ? 6 : (AX25_MAX_ADDR_LEN-1); p = in_addr; i = 0; for (p = in_addr; *p != '\0' && *p != '-' && *p != '*'; p++) { if (i >= maxlen) { text_color_set(DW_COLOR_ERROR); dw_printf ("%sAddress is too long. \"%s\" has more than %d characters.\n", position_name[position], in_addr, maxlen); return 0; } if ( ! isalnum(*p)) { text_color_set(DW_COLOR_ERROR); dw_printf ("%sAddress, \"%s\" contains character other than letter or digit in character position %d.\n", position_name[position], in_addr, (int)(long)(p-in_addr)+1); return 0; } out_addr[i++] = *p; out_addr[i] = '\0'; #if DECAMAIN // Hack when running in decode_aprs utility. // Exempt the "qA..." case because it was already mentioned. if (strict && islower(*p) && strncmp(in_addr, "qA", 2) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("%sAddress has lower case letters. \"%s\" must be all upper case.\n", position_name[position], in_addr); } #else if (strict && islower(*p)) { text_color_set(DW_COLOR_ERROR); dw_printf ("%sAddress has lower case letters. \"%s\" must be all upper case.\n", position_name[position], in_addr); return 0; } #endif } j = 0; sstr[j] = '\0'; if (*p == '-') { for (p++; isalnum(*p); p++) { if (j >= 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("%sSSID is too long. SSID part of \"%s\" has more than 2 characters.\n", position_name[position], in_addr); return 0; } sstr[j++] = *p; sstr[j] = '\0'; if (strict && ! isdigit(*p)) { text_color_set(DW_COLOR_ERROR); dw_printf ("%sSSID must be digits. \"%s\" has letters in SSID.\n", position_name[position], in_addr); return 0; } } k = atoi(sstr); if (k < 0 || k > 15) { text_color_set(DW_COLOR_ERROR); dw_printf ("%sSSID out of range. SSID of \"%s\" not in range of 0 to 15.\n", position_name[position], in_addr); return 0; } *out_ssid = k; } if (*p == '*') { *out_heard = 1; p++; if (strict == 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("\"*\" is not allowed at end of address \"%s\" here.\n", in_addr); return 0; } } if (*p != '\0') { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid character \"%c\" found in %saddress \"%s\".\n", *p, position_name[position], in_addr); return 0; } //dw_printf ("ax25_parse_addr out: %s %d %d\n", out_addr, *out_ssid, *out_heard); return (1); } /* end ax25_parse_addr */ /*------------------------------------------------------------------- * * Name: ax25_check_addresses * * Purpose: Check addresses of given packet and print message if any issues. * We call this when receiving and transmitting. * * Inputs: pp - packet object pointer. * * Errors: Print error message. * * Returns: 1 for all valid. 0 if not. * * Examples: I was surprised to get this from an APRS-IS server with * a lower case source address. * * n1otx>APRS,TCPIP*,qAC,THIRD:@141335z4227.48N/07111.73W_348/005g014t044r000p000h60b10075.wview_5_20_2 * * I haven't gotten to the bottom of this yet but it sounds * like "q constructs" are somehow getting on to the air when * they should only appear in conversations with IGate servers. * * https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/topics/678 * * WB0VGI-7>APDW12,W0YC-5*,qAR,AE0RF-10:}N0DZQ-10>APWW10,TCPIP,WB0VGI-7*:;145.230MN*080306z4607.62N/09230.58WrKE0ACL/R 145.230- T146.2 (Pine County ARES) * * Typical result: * * Digipeater WIDE2 (probably N3LEE-4) audio level = 28(10/6) [NONE] __||||||| * [0.5] VE2DJE-9>P_0_P?,VE2PCQ-3,K1DF-7,N3LEE-4,WIDE2*:'{S+l <0x1c>>/ * Invalid character "_" in MIC-E destination/latitude. * Invalid character "_" in MIC-E destination/latitude. * Invalid character "?" in MIC-E destination/latitude. * Invalid MIC-E N/S encoding in 4th character of destination. * Invalid MIC-E E/W encoding in 6th character of destination. * MIC-E, normal car (side view), Unknown manufacturer, Returning * N 00 00.0000, E 005 55.1500, 0 MPH * Invalid character "_" found in Destination address "P_0_P?". * * *** The origin and journey of this packet should receive some scrutiny. *** * *--------------------------------------------------------------------*/ int ax25_check_addresses (packet_t pp) { int n; char addr[AX25_MAX_ADDR_LEN]; char ignore1[AX25_MAX_ADDR_LEN]; int ignore2, ignore3; int all_ok = 1; for (n = 0; n < ax25_get_num_addr(pp); n++) { ax25_get_addr_with_ssid (pp, n, addr); all_ok &= ax25_parse_addr (n, addr, 1, ignore1, &ignore2, &ignore3); } if (! all_ok) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n"); dw_printf ("*** The origin and journey of this packet should receive some scrutiny. ***\n"); dw_printf ("\n"); } return (all_ok); } /* end ax25_check_addresses */ /*------------------------------------------------------------------------------ * * Name: ax25_unwrap_third_party * * Purpose: Unwrap a third party messge from the header. * * Inputs: copy_from - Existing packet object. * * Returns: Pointer to new packet object or NULL if error. * * Example: Input: A>B,C:}D>E,F:info * Output: D>E,F:info * *------------------------------------------------------------------------------*/ packet_t ax25_unwrap_third_party (packet_t from_pp) { unsigned char *info_p; packet_t result_pp; if (ax25_get_dti(from_pp) != '}') { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error: ax25_unwrap_third_party: wrong data type.\n"); return (NULL); } (void) ax25_get_info (from_pp, &info_p); // Want strict because addresses should conform to AX.25 here. // That's not the case for something from an Internet Server. result_pp = ax25_from_text((char *)info_p + 1, 1); return (result_pp); } /*------------------------------------------------------------------------------ * * Name: ax25_set_addr * * Purpose: Add or change an address. * * Inputs: n - Index of address. Use the symbols * AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc. * * Must be either an existing address or one greater * than the final which causes a new one to be added. * * ad - Address with optional dash and substation id. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * TODO: ax25_from_text could use this. * * Returns: None. * *------------------------------------------------------------------------------*/ void ax25_set_addr (packet_t this_p, int n, char *ad) { int ssid_temp, heard_temp; char atemp[AX25_MAX_ADDR_LEN]; int i; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); assert (n >= 0 && n < AX25_MAX_ADDRS); //dw_printf ("ax25_set_addr (%d, %s) num_addr=%d\n", n, ad, this_p->num_addr); if (n >= 0 && n < this_p->num_addr) { //dw_printf ("ax25_set_addr , existing case\n"); /* * Set existing address position. */ // Why aren't we setting 'strict' here? // Messages from IGate have q-constructs. // We use this to parse it and later remove unwanted parts. ax25_parse_addr (n, ad, 0, atemp, &ssid_temp, &heard_temp); memset (this_p->frame_data + n*7, ' ' << 1, 6); for (i=0; i<6 && atemp[i] != '\0'; i++) { this_p->frame_data[n*7+i] = atemp[i] << 1; } ax25_set_ssid (this_p, n, ssid_temp); } else if (n == this_p->num_addr) { //dw_printf ("ax25_set_addr , appending case\n"); /* * One beyond last position, process as insert. */ ax25_insert_addr (this_p, n, ad); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error, ax25_set_addr, bad position %d for '%s'\n", n, ad); } //dw_printf ("------\n"); //dw_printf ("dump after ax25_set_addr (%d, %s)\n", n, ad); //ax25_hex_dump (this_p); //dw_printf ("------\n"); } /*------------------------------------------------------------------------------ * * Name: ax25_insert_addr * * Purpose: Insert address at specified position, shifting others up one * position. * This is used when a digipeater wants to insert its own call * for tracing purposes. * For example: * W1ABC>TEST,WIDE3-3 * Would become: * W1ABC>TEST,WB2OSZ-1*,WIDE3-2 * * Inputs: n - Index of address. Use the symbols * AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc. * * ad - Address with optional dash and substation id. * * Bugs: Little validity or bounds checking is performed. Be careful. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: None. * * *------------------------------------------------------------------------------*/ void ax25_insert_addr (packet_t this_p, int n, char *ad) { int ssid_temp, heard_temp; char atemp[AX25_MAX_ADDR_LEN]; int i; int expect; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); assert (n >= AX25_REPEATER_1 && n < AX25_MAX_ADDRS); //dw_printf ("ax25_insert_addr (%d, %s)\n", n, ad); /* Don't do it if we already have the maximum number. */ /* Should probably return success/fail code but currently the caller doesn't care. */ if ( this_p->num_addr >= AX25_MAX_ADDRS) { return; } CLEAR_LAST_ADDR_FLAG; this_p->num_addr++; memmove (this_p->frame_data + (n+1)*7, this_p->frame_data + n*7, this_p->frame_len - (n*7)); memset (this_p->frame_data + n*7, ' ' << 1, 6); this_p->frame_len += 7; this_p->frame_data[n*7+6] = SSID_RR_MASK; SET_LAST_ADDR_FLAG; // Why aren't we setting 'strict' here? // Messages from IGate have q-constructs. // We use this to parse it and later remove unwanted parts. ax25_parse_addr (n, ad, 0, atemp, &ssid_temp, &heard_temp); memset (this_p->frame_data + n*7, ' ' << 1, 6); for (i=0; i<6 && atemp[i] != '\0'; i++) { this_p->frame_data[n*7+i] = atemp[i] << 1; } ax25_set_ssid (this_p, n, ssid_temp); // Sanity check after messing with number of addresses. expect = this_p->num_addr; this_p->num_addr = (-1); if (expect != ax25_get_num_addr (this_p)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error ax25_remove_addr expect %d, actual %d\n", expect, this_p->num_addr); } } /*------------------------------------------------------------------------------ * * Name: ax25_remove_addr * * Purpose: Remove address at specified position, shifting others down one position. * This is used when we want to remove something from the digipeater list. * * Inputs: n - Index of address. Use the symbols * AX25_REPEATER1, AX25_REPEATER2, etc. * * Bugs: Little validity or bounds checking is performed. Be careful. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: None. * * *------------------------------------------------------------------------------*/ void ax25_remove_addr (packet_t this_p, int n) { int expect; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); assert (n >= AX25_REPEATER_1 && n < AX25_MAX_ADDRS); /* Shift those beyond to fill this position. */ CLEAR_LAST_ADDR_FLAG; this_p->num_addr--; memmove (this_p->frame_data + n*7, this_p->frame_data + (n+1)*7, this_p->frame_len - ((n+1)*7)); this_p->frame_len -= 7; SET_LAST_ADDR_FLAG; // Sanity check after messing with number of addresses. expect = this_p->num_addr; this_p->num_addr = (-1); if (expect != ax25_get_num_addr (this_p)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error ax25_remove_addr expect %d, actual %d\n", expect, this_p->num_addr); } } /*------------------------------------------------------------------------------ * * Name: ax25_get_num_addr * * Purpose: Return number of addresses in current packet. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: Number of addresses in the current packet. * Should be in the range of 2 .. AX25_MAX_ADDRS. * * Version 0.9: Could be zero for a non AX.25 frame in KISS mode. * *------------------------------------------------------------------------------*/ int ax25_get_num_addr (packet_t this_p) { //unsigned char *pf; int a; int addr_bytes; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); /* Use cached value if already set. */ if (this_p->num_addr >= 0) { return (this_p->num_addr); } /* Otherwise, determine the number ofaddresses. */ this_p->num_addr = 0; /* Number of addresses extracted. */ addr_bytes = 0; for (a = 0; a < this_p->frame_len && addr_bytes == 0; a++) { if (this_p->frame_data[a] & SSID_LAST_MASK) { addr_bytes = a + 1; } } if (addr_bytes % 7 == 0) { int addrs = addr_bytes / 7; if (addrs >= AX25_MIN_ADDRS && addrs <= AX25_MAX_ADDRS) { this_p->num_addr = addrs; } } return (this_p->num_addr); } /*------------------------------------------------------------------------------ * * Name: ax25_get_num_repeaters * * Purpose: Return number of repeater addresses in current packet. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: Number of addresses in the current packet - 2. * Should be in the range of 0 .. AX25_MAX_ADDRS - 2. * *------------------------------------------------------------------------------*/ int ax25_get_num_repeaters (packet_t this_p) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (this_p->num_addr >= 2) { return (this_p->num_addr - 2); } return (0); } /*------------------------------------------------------------------------------ * * Name: ax25_get_addr_with_ssid * * Purpose: Return specified address with any SSID in current packet. * * Inputs: n - Index of address. Use the symbols * AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc. * * Outputs: station - String representation of the station, including the SSID. * e.g. "WB2OSZ-15" * Usually variables will be AX25_MAX_ADDR_LEN bytes * but 10 would be adequate. * * Bugs: No bounds checking is performed. Be careful. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: Character string in usual human readable format, * * *------------------------------------------------------------------------------*/ void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station) { int ssid; char sstr[8]; /* Should be 1 or 2 digits for SSID. */ int i; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (n < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error detected in ax25_get_addr_with_ssid, %s, line %d.\n", __FILE__, __LINE__); dw_printf ("Address index, %d, is less than zero.\n", n); strlcpy (station, "??????", 10); return; } if (n >= this_p->num_addr) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error detected in ax25_get_addr_with_ssid, %s, line %d.\n", __FILE__, __LINE__); dw_printf ("Address index, %d, is too large for number of addresses, %d.\n", n, this_p->num_addr); strlcpy (station, "??????", 10); return; } // At one time this would stop at the first space, on the assumption we would have only trailing spaces. // Then there was a forum discussion where someone encountered the address " WIDE2" with a leading space. // In that case, we would have returned a zero length string here. // Now we return exactly what is in the address field and trim trailing spaces. // This will provide better information for troubleshooting. for (i=0; i<6; i++) { station[i] = (this_p->frame_data[n*7+i] >> 1) & 0x7f; } station[6] = '\0'; for (i=5; i>=0; i--) { if (station[i] == ' ') station[i] = '\0'; else break; } ssid = ax25_get_ssid (this_p, n); if (ssid != 0) { snprintf (sstr, sizeof(sstr), "-%d", ssid); strlcat (station, sstr, 10); } } /* end ax25_get_addr_with_ssid */ /*------------------------------------------------------------------------------ * * Name: ax25_get_addr_no_ssid * * Purpose: Return specified address WITHOUT any SSID. * * Inputs: n - Index of address. Use the symbols * AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc. * * Outputs: station - String representation of the station, WITHOUT the SSID. * e.g. "WB2OSZ" * Usually variables will be AX25_MAX_ADDR_LEN bytes * but 7 would be adequate. * * Bugs: No bounds checking is performed. Be careful. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: Character string in usual human readable format, * * *------------------------------------------------------------------------------*/ void ax25_get_addr_no_ssid (packet_t this_p, int n, char *station) { int i; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (n < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error detected in ax25_get_addr_no_ssid, %s, line %d.\n", __FILE__, __LINE__); dw_printf ("Address index, %d, is less than zero.\n", n); strlcpy (station, "??????", 7); return; } if (n >= this_p->num_addr) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error detected in ax25_get_no_with_ssid, %s, line %d.\n", __FILE__, __LINE__); dw_printf ("Address index, %d, is too large for number of addresses, %d.\n", n, this_p->num_addr); strlcpy (station, "??????", 7); return; } // At one time this would stop at the first space, on the assumption we would have only trailing spaces. // Then there was a forum discussion where someone encountered the address " WIDE2" with a leading space. // In that case, we would have returned a zero length string here. // Now we return exactly what is in the address field and trim trailing spaces. // This will provide better information for troubleshooting. for (i=0; i<6; i++) { station[i] = (this_p->frame_data[n*7+i] >> 1) & 0x7f; } station[6] = '\0'; for (i=5; i>=0; i--) { if (station[i] == ' ') station[i] = '\0'; else break; } } /* end ax25_get_addr_no_ssid */ /*------------------------------------------------------------------------------ * * Name: ax25_get_ssid * * Purpose: Return SSID of specified address in current packet. * * Inputs: n - Index of address. Use the symbols * AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: Substation id, as integer 0 .. 15. * *------------------------------------------------------------------------------*/ int ax25_get_ssid (packet_t this_p, int n) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (n >= 0 && n < this_p->num_addr) { return ((this_p->frame_data[n*7+6] & SSID_SSID_MASK) >> SSID_SSID_SHIFT); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error: ax25_get_ssid(%d), num_addr=%d\n", n, this_p->num_addr); return (0); } } /*------------------------------------------------------------------------------ * * Name: ax25_set_ssid * * Purpose: Set the SSID of specified address in current packet. * * Inputs: n - Index of address. Use the symbols * AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc. * * ssid - New SSID. Must be in range of 0 to 15. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Bugs: Rewrite to keep call and SSID separate internally. * *------------------------------------------------------------------------------*/ void ax25_set_ssid (packet_t this_p, int n, int ssid) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (n >= 0 && n < this_p->num_addr) { this_p->frame_data[n*7+6] = (this_p->frame_data[n*7+6] & ~ SSID_SSID_MASK) | ((ssid << SSID_SSID_SHIFT) & SSID_SSID_MASK) ; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error: ax25_set_ssid(%d,%d), num_addr=%d\n", n, ssid, this_p->num_addr); } } /*------------------------------------------------------------------------------ * * Name: ax25_get_h * * Purpose: Return "has been repeated" flag of specified address in current packet. * * Inputs: n - Index of address. Use the symbols * AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc. * * Bugs: No bounds checking is performed. Be careful. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: True or false. * *------------------------------------------------------------------------------*/ int ax25_get_h (packet_t this_p, int n) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); assert (n >= 0 && n < this_p->num_addr); if (n >= 0 && n < this_p->num_addr) { return ((this_p->frame_data[n*7+6] & SSID_H_MASK) >> SSID_H_SHIFT); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error: ax25_get_h(%d), num_addr=%d\n", n, this_p->num_addr); return (0); } } /*------------------------------------------------------------------------------ * * Name: ax25_set_h * * Purpose: Set the "has been repeated" flag of specified address in current packet. * * Inputs: n - Index of address. Use the symbols * Should be in range of AX25_REPEATER_1 .. AX25_REPEATER_8. * * Bugs: No bounds checking is performed. Be careful. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: None * *------------------------------------------------------------------------------*/ void ax25_set_h (packet_t this_p, int n) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (n >= 0 && n < this_p->num_addr) { this_p->frame_data[n*7+6] |= SSID_H_MASK; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error: ax25_set_hd(%d), num_addr=%d\n", n, this_p->num_addr); } } /*------------------------------------------------------------------------------ * * Name: ax25_get_heard * * Purpose: Return index of the station that we heard. * * Inputs: none * * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: If any of the digipeaters have the has-been-repeated bit set, * return the index of the last one. Otherwise return index for source. * *------------------------------------------------------------------------------*/ int ax25_get_heard(packet_t this_p) { int i; int result; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); result = AX25_SOURCE; for (i = AX25_REPEATER_1; i < ax25_get_num_addr(this_p); i++) { if (ax25_get_h(this_p,i)) { result = i; } } return (result); } /*------------------------------------------------------------------------------ * * Name: ax25_get_first_not_repeated * * Purpose: Return index of the first repeater that does NOT have the * "has been repeated" flag set or -1 if none. * * Inputs: none * * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: In range of X25_REPEATER_1 .. X25_REPEATER_8 or -1 if none. * *------------------------------------------------------------------------------*/ int ax25_get_first_not_repeated(packet_t this_p) { int i; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); for (i = AX25_REPEATER_1; i < ax25_get_num_addr(this_p); i++) { if ( ! ax25_get_h(this_p,i)) { return (i); } } return (-1); } /*------------------------------------------------------------------------------ * * Name: ax25_get_rr * * Purpose: Return the two reserved "RR" bits in the specified address field. * * Inputs: pp - Packet object. * * n - Index of address. Use the symbols * AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc. * * Returns: 0, 1, 2, or 3. * *------------------------------------------------------------------------------*/ int ax25_get_rr (packet_t this_p, int n) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); assert (n >= 0 && n < this_p->num_addr); if (n >= 0 && n < this_p->num_addr) { return ((this_p->frame_data[n*7+6] & SSID_RR_MASK) >> SSID_RR_SHIFT); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error: ax25_get_rr(%d), num_addr=%d\n", n, this_p->num_addr); return (0); } } /*------------------------------------------------------------------------------ * * Name: ax25_get_info * * Purpose: Obtain Information part of current packet. * * Inputs: this_p - Packet object pointer. * * Outputs: paddr - Starting address of information part is returned here. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: Number of octets in the Information part. * Should be in the range of AX25_MIN_INFO_LEN .. AX25_MAX_INFO_LEN. * *------------------------------------------------------------------------------*/ int ax25_get_info (packet_t this_p, unsigned char **paddr) { unsigned char *info_ptr; int info_len; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (this_p->num_addr >= 2) { /* AX.25 */ info_ptr = this_p->frame_data + ax25_get_info_offset(this_p); info_len = ax25_get_num_info(this_p); } else { /* Not AX.25. Treat Whole packet as info. */ info_ptr = this_p->frame_data; info_len = this_p->frame_len; } /* Add nul character in case caller treats as printable string. */ assert (info_len >= 0); info_ptr[info_len] = '\0'; *paddr = info_ptr; return (info_len); } /* end ax25_get_info */ /*------------------------------------------------------------------------------ * * Name: ax25_cut_at_crlf * * Purpose: Truncate the information part at the first CR or LF. * This is used for the RF>IS IGate function. * CR/LF is used as record separator so we must remove it * before packaging up packet to sending to server. * * Inputs: this_p - Packet object pointer. * * Outputs: Packet is modified in place. * * Returns: Number of characters removed from the end. * 0 if not changed. * * Assumption: ax25_from_text or ax25_from_frame was called first. * *------------------------------------------------------------------------------*/ int ax25_cut_at_crlf (packet_t this_p) { unsigned char *info_ptr; int info_len; int j; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); info_len = ax25_get_info (this_p, &info_ptr); // Can't use strchr because there is potential of nul character. for (j = 0; j < info_len; j++) { if (info_ptr[j] == '\r' || info_ptr[j] == '\n') { int chop = info_len - j; this_p->frame_len -= chop; return (chop); } } return (0); } /*------------------------------------------------------------------------------ * * Name: ax25_get_dti * * Purpose: Get Data Type Identifier from Information part. * * Inputs: None. * * Assumption: ax25_from_text or ax25_from_frame was called first. * * Returns: First byte from the information part. * *------------------------------------------------------------------------------*/ int ax25_get_dti (packet_t this_p) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (this_p->num_addr >= 2) { return (this_p->frame_data[ax25_get_info_offset(this_p)]); } return (' '); } /*------------------------------------------------------------------------------ * * Name: ax25_set_nextp * * Purpose: Set next packet object in queue. * * Inputs: this_p - Current packet object. * * next_p - pointer to next one * * Description: This is used to build a linked list for a queue. * *------------------------------------------------------------------------------*/ void ax25_set_nextp (packet_t this_p, packet_t next_p) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); this_p->nextp = next_p; } /*------------------------------------------------------------------------------ * * Name: ax25_get_nextp * * Purpose: Obtain next packet object in queue. * * Inputs: Packet object. * * Returns: Following object in queue or NULL. * *------------------------------------------------------------------------------*/ packet_t ax25_get_nextp (packet_t this_p) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); return (this_p->nextp); } /*------------------------------------------------------------------------------ * * Name: ax25_set_release_time * * Purpose: Set release time * * Inputs: this_p - Current packet object. * * release_time - Time as returned by dtime_now(). * *------------------------------------------------------------------------------*/ void ax25_set_release_time (packet_t this_p, double release_time) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); this_p->release_time = release_time; } /*------------------------------------------------------------------------------ * * Name: ax25_get_release_time * * Purpose: Get release time. * *------------------------------------------------------------------------------*/ double ax25_get_release_time (packet_t this_p) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); return (this_p->release_time); } /*------------------------------------------------------------------------------ * * Name: ax25_set_modulo * * Purpose: Set modulo value for I and S frame sequence numbers. * *------------------------------------------------------------------------------*/ void ax25_set_modulo (packet_t this_p, int modulo) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); this_p->modulo = modulo; } /*------------------------------------------------------------------ * * Function: ax25_format_addrs * * Purpose: Format all the addresses suitable for printing. * * The AX.25 spec refers to this as "Source Path Header" - "TNC-2" Format * * Inputs: Current packet. * * Outputs: result - All addresses combined into a single string of the form: * * "Source > Destination [ , repeater ... ] :" * * An asterisk is displayed after the last digipeater * with the "H" bit set. e.g. If we hear RPT2, * * SRC>DST,RPT1,RPT2*,RPT3: * * No asterisk means the source is being heard directly. * Needs to be 101 characters to avoid overflowing. * (Up to 100 characters + \0) * * Errors: No error checking so caller needs to be careful. * * *------------------------------------------------------------------*/ // TODO: max len for result. buffer overflow? void ax25_format_addrs (packet_t this_p, char *result) { int i; int heard; char stemp[AX25_MAX_ADDR_LEN]; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); *result = '\0'; /* New in 0.9. */ /* Don't get upset if no addresses. */ /* This will allow packets that do not comply to AX.25 format. */ if (this_p->num_addr == 0) { return; } ax25_get_addr_with_ssid (this_p, AX25_SOURCE, stemp); strcat (result, stemp); strcat (result, ">"); ax25_get_addr_with_ssid (this_p, AX25_DESTINATION, stemp); strcat (result, stemp); heard = ax25_get_heard(this_p); for (i=(int)AX25_REPEATER_1; inum_addr; i++) { ax25_get_addr_with_ssid (this_p, i, stemp); strcat (result, ","); strcat (result, stemp); if (i == heard) { strcat (result, "*"); } } strcat (result, ":"); } /*------------------------------------------------------------------ * * Function: ax25_format_via_path * * Purpose: Format via path addresses suitable for printing. * * Inputs: Current packet. * * result_size - Number of bytes available for result. * We can have up to 8 addresses x 9 characters * plus 7 commas, possible *, and nul = 81 minimum. * * Outputs: result - Digipeater field addresses combined into a single string of the form: * * "repeater, repeater ..." * * An asterisk is displayed after the last digipeater * with the "H" bit set. e.g. If we hear RPT2, * * RPT1,RPT2*,RPT3 * * No asterisk means the source is being heard directly. * *------------------------------------------------------------------*/ void ax25_format_via_path (packet_t this_p, char *result, size_t result_size) { int i; int heard; char stemp[AX25_MAX_ADDR_LEN]; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); *result = '\0'; /* Don't get upset if no addresses. */ /* This will allow packets that do not comply to AX.25 format. */ if (this_p->num_addr == 0) { return; } heard = ax25_get_heard(this_p); for (i=(int)AX25_REPEATER_1; inum_addr; i++) { if (i > (int)AX25_REPEATER_1) { strlcat (result, ",", result_size); } ax25_get_addr_with_ssid (this_p, i, stemp); strlcat (result, stemp, result_size); if (i == heard) { strlcat (result, "*", result_size); } } } /* end ax25_format_via_path */ /*------------------------------------------------------------------ * * Function: ax25_pack * * Purpose: Put all the pieces into format ready for transmission. * * Inputs: this_p - pointer to packet object. * * Outputs: result - Frame buffer, AX25_MAX_PACKET_LEN bytes. * Should also have two extra for FCS to be * added later. * * Returns: Number of octets in the frame buffer. * Does NOT include the extra 2 for FCS. * * Errors: Returns -1. * *------------------------------------------------------------------*/ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN]) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); assert (this_p->frame_len >= 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN); memcpy (result, this_p->frame_data, this_p->frame_len); return (this_p->frame_len); } /*------------------------------------------------------------------ * * Function: ax25_frame_type * * Purpose: Extract the type of frame. * This is derived from the control byte(s) but * is an enumerated type for easier handling. * * Inputs: this_p - pointer to packet object. * * Outputs: desc - Text description such as "I frame" or * "U frame SABME". * Supply 40 bytes to be safe. * * cr - Command or response? * * pf - P/F - Poll/Final or -1 if not applicable * * nr - N(R) - receive sequence or -1 if not applicable. * * ns - N(S) - send sequence or -1 if not applicable. * * Returns: Frame type from enum ax25_frame_type_e. * *------------------------------------------------------------------*/ // TODO: need someway to ensure caller allocated enough space. // Should pass in as parameter. #define DESC_SIZ 40 ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns) { int c; // U frames are always one control byte. int c2 = 0; // I & S frames can have second Control byte. assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); strlcpy (desc, "????", DESC_SIZ); *cr = cr_11; *pf = -1; *nr = -1; *ns = -1; c = ax25_get_control(this_p); if (c < 0) { strlcpy (desc, "Not AX.25", DESC_SIZ); return (frame_not_AX25); } /* * TERRIBLE HACK :-( for display purposes. * * I and S frames can have 1 or 2 control bytes but there is * no good way to determine this without dipping into the data * link state machine. Can we guess? * * S frames have no protocol id or information so if there is one * more byte beyond the control field, we could assume there are * two control bytes. * * For I frames, the protocol id will usually be 0xf0. If we find * that as the first byte of the information field, it is probably * the pid and not part of the information. Ditto for segments 0x08. * Not fool proof but good enough for troubleshooting text out. * * If we have a link to the peer station, this will be set properly * before it needs to be used for other reasons. * * Setting one of the RR bits (find reference!) is sounding better and better. * It's in common usage so I should lobby to get that in the official protocol spec. */ if (this_p->modulo == 0 && (c & 3) == 1 && ax25_get_c2(this_p) != -1) { this_p->modulo = modulo_128; } else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0xF0) { this_p->modulo = modulo_128; } else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0x08) { // same for segments this_p->modulo = modulo_128; } if (this_p->modulo == modulo_128) { c2 = ax25_get_c2 (this_p); } int dst_c = this_p->frame_data[AX25_DESTINATION * 7 + 6] & SSID_H_MASK; int src_c = this_p->frame_data[AX25_SOURCE * 7 + 6] & SSID_H_MASK; char cr_text[8]; char pf_text[8]; if (dst_c) { if (src_c) { *cr = cr_11; strcpy(cr_text,"cc=11"); strcpy(pf_text,"p/f"); } else { *cr = cr_cmd; strcpy(cr_text,"cmd"); strcpy(pf_text,"p"); } } else { if (src_c) { *cr = cr_res; strcpy(cr_text,"res"); strcpy(pf_text,"f"); } else { *cr = cr_00; strcpy(cr_text,"cc=00"); strcpy(pf_text,"p/f"); } } if ((c & 1) == 0) { // Information rrr p sss 0 or sssssss 0 rrrrrrr p if (this_p->modulo == modulo_128) { *ns = (c >> 1) & 0x7f; *pf = c2 & 1; *nr = (c2 >> 1) & 0x7f; } else { *ns = (c >> 1) & 7; *pf = (c >> 4) & 1; *nr = (c >> 5) & 7; } //snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d", cr_text, *ns, *nr, pf_text, *pf); snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d, pid=0x%02x", cr_text, *ns, *nr, pf_text, *pf, ax25_get_pid(this_p)); return (frame_type_I); } else if ((c & 2) == 0) { // Supervisory rrr p/f ss 0 1 or 0000 ss 0 1 rrrrrrr p/f if (this_p->modulo == modulo_128) { *pf = c2 & 1; *nr = (c2 >> 1) & 0x7f; } else { *pf = (c >> 4) & 1; *nr = (c >> 5) & 7; } switch ((c >> 2) & 3) { case 0: snprintf (desc, DESC_SIZ, "RR %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_RR); break; case 1: snprintf (desc, DESC_SIZ, "RNR %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_RNR); break; case 2: snprintf (desc, DESC_SIZ, "REJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_REJ); break; case 3: snprintf (desc, DESC_SIZ, "SREJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_SREJ); break; } } else { // Unnumbered mmm p/f mm 1 1 *pf = (c >> 4) & 1; switch (c & 0xef) { case 0x6f: snprintf (desc, DESC_SIZ, "SABME %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_SABME); break; case 0x2f: snprintf (desc, DESC_SIZ, "SABM %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_SABM); break; case 0x43: snprintf (desc, DESC_SIZ, "DISC %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_DISC); break; case 0x0f: snprintf (desc, DESC_SIZ, "DM %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_DM); break; case 0x63: snprintf (desc, DESC_SIZ, "UA %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_UA); break; case 0x87: snprintf (desc, DESC_SIZ, "FRMR %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_FRMR); break; case 0x03: snprintf (desc, DESC_SIZ, "UI %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_UI); break; case 0xaf: snprintf (desc, DESC_SIZ, "XID %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_XID); break; case 0xe3: snprintf (desc, DESC_SIZ, "TEST %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_TEST); break; default: snprintf (desc, DESC_SIZ, "U other???"); return (frame_type_U); break; } } // Should be unreachable but compiler doesn't realize that. // Here only to suppress "warning: control reaches end of non-void function" return (frame_not_AX25); } /* end ax25_frame_type */ /*------------------------------------------------------------------ * * Function: ax25_hex_dump * * Purpose: Print out packet in hexadecimal for debugging. * * Inputs: fptr - Pointer to frame data. * * flen - Frame length, bytes. Does not include CRC. * *------------------------------------------------------------------*/ static void hex_dump (unsigned char *p, int len) { int n, i, offset; offset = 0; while (len > 0) { n = len < 16 ? len : 16; dw_printf (" %03x: ", offset); for (i=0; i>5)&7, (c>>4)&1, (c>>1)&7); } else if ((c & 0xf) == 0x01) { snprintf (out, outsiz, "S frame RR: n(r)=%d, p/f=%d", (c>>5)&7, (c>>4)&1); } else if ((c & 0xf) == 0x05) { snprintf (out, outsiz, "S frame RNR: n(r)=%d, p/f=%d", (c>>5)&7, (c>>4)&1); } else if ((c & 0xf) == 0x09) { snprintf (out, outsiz, "S frame REJ: n(r)=%d, p/f=%d", (c>>5)&7, (c>>4)&1); } else if ((c & 0xf) == 0x0D) { snprintf (out, outsiz, "S frame sREJ: n(r)=%d, p/f=%d", (c>>5)&7, (c>>4)&1); } else if ((c & 0xef) == 0x6f) { snprintf (out, outsiz, "U frame SABME: p=%d", (c>>4)&1); } else if ((c & 0xef) == 0x2f) { snprintf (out, outsiz, "U frame SABM: p=%d", (c>>4)&1); } else if ((c & 0xef) == 0x43) { snprintf (out, outsiz, "U frame DISC: p=%d", (c>>4)&1); } else if ((c & 0xef) == 0x0f) { snprintf (out, outsiz, "U frame DM: f=%d", (c>>4)&1); } else if ((c & 0xef) == 0x63) { snprintf (out, outsiz, "U frame UA: f=%d", (c>>4)&1); } else if ((c & 0xef) == 0x87) { snprintf (out, outsiz, "U frame FRMR: f=%d", (c>>4)&1); } else if ((c & 0xef) == 0x03) { snprintf (out, outsiz, "U frame UI: p/f=%d", (c>>4)&1); } else if ((c & 0xef) == 0xAF) { snprintf (out, outsiz, "U frame XID: p/f=%d", (c>>4)&1); } else if ((c & 0xef) == 0xe3) { snprintf (out, outsiz, "U frame TEST: p/f=%d", (c>>4)&1); } else { snprintf (out, outsiz, "Unknown frame type for control = 0x%02x", c); } } /* Text description of protocol id octet. */ #define PID_TEXT_SIZE 80 static void pid_to_text (int p, char out[PID_TEXT_SIZE]) { if ((p & 0x30) == 0x10) { snprintf (out, PID_TEXT_SIZE, "AX.25 layer 3 implemented."); } else if ((p & 0x30) == 0x20) { snprintf (out, PID_TEXT_SIZE, "AX.25 layer 3 implemented."); } else if (p == 0x01) { snprintf (out, PID_TEXT_SIZE, "ISO 8208/CCITT X.25 PLP"); } else if (p == 0x06) { snprintf (out, PID_TEXT_SIZE, "Compressed TCP/IP packet. Van Jacobson (RFC 1144)"); } else if (p == 0x07) { snprintf (out, PID_TEXT_SIZE, "Uncompressed TCP/IP packet. Van Jacobson (RFC 1144)"); } else if (p == 0x08) { snprintf (out, PID_TEXT_SIZE, "Segmentation fragment"); } else if (p == 0xC3) { snprintf (out, PID_TEXT_SIZE, "TEXNET datagram protocol"); } else if (p == 0xC4) { snprintf (out, PID_TEXT_SIZE, "Link Quality Protocol"); } else if (p == 0xCA) { snprintf (out, PID_TEXT_SIZE, "Appletalk"); } else if (p == 0xCB) { snprintf (out, PID_TEXT_SIZE, "Appletalk ARP"); } else if (p == 0xCC) { snprintf (out, PID_TEXT_SIZE, "ARPA Internet Protocol"); } else if (p == 0xCD) { snprintf (out, PID_TEXT_SIZE, "ARPA Address resolution"); } else if (p == 0xCE) { snprintf (out, PID_TEXT_SIZE, "FlexNet"); } else if (p == 0xCF) { snprintf (out, PID_TEXT_SIZE, "NET/ROM"); } else if (p == 0xF0) { snprintf (out, PID_TEXT_SIZE, "No layer 3 protocol implemented."); } else if (p == 0xFF) { snprintf (out, PID_TEXT_SIZE, "Escape character. Next octet contains more Level 3 protocol information."); } else { snprintf (out, PID_TEXT_SIZE, "Unknown protocol id = 0x%02x", p); } } void ax25_hex_dump (packet_t this_p) { int n; unsigned char *fptr = this_p->frame_data; int flen = this_p->frame_len; if (this_p->num_addr >= AX25_MIN_ADDRS && this_p->num_addr <= AX25_MAX_ADDRS) { int c, p; char cp_text[120]; char l_text[20]; c = fptr[this_p->num_addr*7]; p = fptr[this_p->num_addr*7+1]; ctrl_to_text (c, cp_text, sizeof(cp_text)); // TODO: use ax25_frame_type() instead. if ( (c & 0x01) == 0 || /* I xxxx xxx0 */ c == 0x03 || c == 0x13) { /* UI 000x 0011 */ char pid_text[PID_TEXT_SIZE]; pid_to_text (p, pid_text); strlcat (cp_text, ", ", sizeof(cp_text)); strlcat (cp_text, pid_text, sizeof(cp_text)); } snprintf (l_text, sizeof(l_text), ", length = %d", flen); strlcat (cp_text, l_text, sizeof(cp_text)); dw_printf ("%s\n", cp_text); } dw_printf (" dest %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", fptr[0]>>1, fptr[1]>>1, fptr[2]>>1, fptr[3]>>1, fptr[4]>>1, fptr[5]>>1, (fptr[6]&SSID_SSID_MASK)>>SSID_SSID_SHIFT, (fptr[6]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[6]&SSID_RR_MASK)>>SSID_RR_SHIFT, fptr[6]&SSID_LAST_MASK); dw_printf (" source %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", fptr[7]>>1, fptr[8]>>1, fptr[9]>>1, fptr[10]>>1, fptr[11]>>1, fptr[12]>>1, (fptr[13]&SSID_SSID_MASK)>>SSID_SSID_SHIFT, (fptr[13]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[13]&SSID_RR_MASK)>>SSID_RR_SHIFT, fptr[13]&SSID_LAST_MASK); for (n=2; nnum_addr; n++) { dw_printf (" digi %d %c%c%c%c%c%c %2d h=%d res=%d last=%d\n", n - 1, fptr[n*7+0]>>1, fptr[n*7+1]>>1, fptr[n*7+2]>>1, fptr[n*7+3]>>1, fptr[n*7+4]>>1, fptr[n*7+5]>>1, (fptr[n*7+6]&SSID_SSID_MASK)>>SSID_SSID_SHIFT, (fptr[n*7+6]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[n*7+6]&SSID_RR_MASK)>>SSID_RR_SHIFT, fptr[n*7+6]&SSID_LAST_MASK); } hex_dump (fptr, flen); } /* end ax25_hex_dump */ /*------------------------------------------------------------------ * * Function: ax25_is_aprs * * Purpose: Is this packet APRS format? * * Inputs: this_p - pointer to packet object. * * Returns: True if this frame has the proper control * octets for an APRS packet. * control 3 for UI frame * protocol id 0xf0 for no layer 3 * * * Description: Dire Wolf should be able to act as a KISS TNC for * any type of AX.25 activity. However, there are other * places where we want to process only APRS. * (e.g. digipeating and IGate.) * *------------------------------------------------------------------*/ int ax25_is_aprs (packet_t this_p) { int ctrl, pid, is_aprs; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (this_p->frame_len == 0) return(0); ctrl = ax25_get_control(this_p); pid = ax25_get_pid(this_p); is_aprs = this_p->num_addr >= 2 && ctrl == AX25_UI_FRAME && pid == AX25_PID_NO_LAYER_3; #if 0 text_color_set(DW_COLOR_ERROR); dw_printf ("ax25_is_aprs(): ctrl=%02x, pid=%02x, is_aprs=%d\n", ctrl, pid, is_aprs); #endif return (is_aprs); } /*------------------------------------------------------------------ * * Function: ax25_is_null_frame * * Purpose: Is this packet structure empty? * * Inputs: this_p - pointer to packet object. * * Returns: True if frame data length is 0. * * Description: This is used when we want to wake up the * transmit queue processing thread but don't * want to transmit a frame. * *------------------------------------------------------------------*/ int ax25_is_null_frame (packet_t this_p) { int is_null; assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); is_null = this_p->frame_len == 0; #if 0 text_color_set(DW_COLOR_ERROR); dw_printf ("ax25_is_null_frame(): is_null=%d\n", is_null); #endif return (is_null); } /*------------------------------------------------------------------ * * Function: ax25_get_control ax25_get_c2 * * Purpose: Get Control field from packet. * * Inputs: this_p - pointer to packet object. * * Returns: APRS uses AX25_UI_FRAME. * This could also be used in other situations. * *------------------------------------------------------------------*/ int ax25_get_control (packet_t this_p) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (this_p->frame_len == 0) return(-1); if (this_p->num_addr >= 2) { return (this_p->frame_data[ax25_get_control_offset(this_p)]); } return (-1); } int ax25_get_c2 (packet_t this_p) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); if (this_p->frame_len == 0) return(-1); if (this_p->num_addr >= 2) { int offset2 = ax25_get_control_offset(this_p)+1; if (offset2 < this_p->frame_len) { return (this_p->frame_data[offset2]); } else { return (-1); /* attempt to go beyond the end of frame. */ } } return (-1); /* not AX.25 */ } /*------------------------------------------------------------------ * * Function: ax25_get_pid * * Purpose: Get protocol ID from packet. * * Inputs: this_p - pointer to packet object. * * Returns: APRS uses 0xf0 for no layer 3. * This could also be used in other situations. * * AX.25: "The Protocol Identifier (PID) field appears in information * frames (I and UI) only. It identifies which kind of * Layer 3 protocol, if any, is in use." * *------------------------------------------------------------------*/ int ax25_get_pid (packet_t this_p) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); // TODO: handle 2 control byte case. // TODO: sanity check: is it I or UI frame? if (this_p->frame_len == 0) return(-1); if (this_p->num_addr >= 2) { return (this_p->frame_data[ax25_get_pid_offset(this_p)]); } return (-1); } /*------------------------------------------------------------------ * * Function: ax25_get_frame_len * * Purpose: Get length of frame. * * Inputs: this_p - pointer to packet object. * * Returns: Number of octets in the frame buffer. * Does NOT include the extra 2 for FCS. * *------------------------------------------------------------------*/ int ax25_get_frame_len (packet_t this_p) { assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); assert (this_p->frame_len >= 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN); return (this_p->frame_len); } /* end ax25_get_frame_len */ /*------------------------------------------------------------------------------ * * Name: ax25_dedupe_crc * * Purpose: Calculate a checksum for the packet source, destination, and * information but NOT the digipeaters. * This is used for duplicate detection in the digipeater * and IGate algorithms. * * Input: pp - Pointer to packet object. * * Returns: Value which will be the same for a duplicate but very unlikely * to match a non-duplicate packet. * * Description: For detecting duplicates, we need to look * + source station * + destination * + information field * but NOT the changing list of digipeaters. * * Typically, only a checksum is kept to reduce memory * requirements and amount of compution for comparisons. * There is a very very small probability that two unrelated * packets will result in the same checksum, and the * undesired dropping of the packet. * * There is a 1 / 65536 chance of getting a false positive match * which is good enough for this application. * We could reduce that with a 32 bit CRC instead of reusing * code from the AX.25 frame CRC calculation. * * Version 1.3: We exclude any trailing CR/LF at the end of the info part * so we can detect duplicates that are received only over the * air and those which have gone thru an IGate where the process * removes any trailing CR/LF. Example: * * Original via RF only: * W1TG-1>APU25N,N3LEE-10*,WIDE2-1: * * When we get the same thing via APRS-IS: * W1TG-1>APU25N,K1FFK,WIDE2*,qAR,WB2ZII-15:= 1 && (pinfo[info_len-1] == '\r' || pinfo[info_len-1] == '\n' || pinfo[info_len-1] == ' ')) { // Temporary for debugging! // if (pinfo[info_len-1] == ' ') { // text_color_set(DW_COLOR_ERROR); // dw_printf ("DEBUG: ax25_dedupe_crc ignoring trailing space.\n"); // } info_len--; } crc = 0xffff; crc = crc16((unsigned char *)src, strlen(src), crc); crc = crc16((unsigned char *)dest, strlen(dest), crc); crc = crc16(pinfo, info_len, crc); return (crc); } /*------------------------------------------------------------------------------ * * Name: ax25_m_m_crc * * Purpose: Calculate a checksum for the packet. * This is used for the multimodem duplicate detection. * * Input: pp - Pointer to packet object. * * Returns: Value which will be the same for a duplicate but very unlikely * to match a non-duplicate packet. * * Description: For detecting duplicates, we need to look the entire packet. * * Typically, only a checksum is kept to reduce memory * requirements and amount of compution for comparisons. * There is a very very small probability that two unrelated * packets will result in the same checksum, and the * undesired dropping of the packet. *------------------------------------------------------------------------------*/ unsigned short ax25_m_m_crc (packet_t pp) { unsigned short crc; unsigned char fbuf[AX25_MAX_PACKET_LEN]; int flen; flen = ax25_pack (pp, fbuf); crc = 0xffff; crc = crc16(fbuf, flen, crc); return (crc); } /*------------------------------------------------------------------ * * Function: ax25_safe_print * * Purpose: Print given string, changing non printable characters to * hexadecimal notation. Note that character values * , 28, 29, 30, and 31 can appear in MIC-E message. * * Inputs: pstr - Pointer to string. * * len - Number of bytes. If < 0 we use strlen(). * * ascii_only - Restrict output to only ASCII. * Normally we allow UTF-8. * * Stops after non-zero len characters or at nul. * * Returns: none * * Description: Print a string in a "safe" manner. * Anything that is not a printable character * will be converted to a hexadecimal representation. * For example, a Line Feed character will appear as <0x0a> * rather than dropping down to the next line on the screen. * * ax25_from_text can accept this format. * * * Example: W1MED-1>T2QP0S,N1OHZ,N8VIM*,WIDE1-1:'cQBl <0x1c>-/]<0x0d> * ------ ------ * * Questions: What should we do about UTF-8? Should that be displayed * as hexadecimal for troubleshooting? Maybe an option so the * packet raw data is in hexadecimal but an extracted * comment displays UTF-8? Or a command line option for only ASCII? * * Trailing space: * I recently noticed a case where a packet has space character * at the end. If the last character of the line is a space, * this will be displayed in hexadecimal to make it obvious. * *------------------------------------------------------------------*/ #define MAXSAFE 500 void ax25_safe_print (char *pstr, int len, int ascii_only) { int ch; char safe_str[MAXSAFE*6+1]; int safe_len; safe_len = 0; safe_str[safe_len] = '\0'; if (len < 0) len = strlen(pstr); if (len > MAXSAFE) len = MAXSAFE; while (len > 0) { ch = *((unsigned char *)pstr); if (ch == ' ' && (len == 1 || pstr[1] == '\0')) { snprintf (safe_str + safe_len, sizeof(safe_str)-safe_len, "<0x%02x>", ch); safe_len += 6; } else if (ch < ' ' || ch == 0x7f || ch == 0xfe || ch == 0xff || (ascii_only && ch >= 0x80) ) { /* Control codes and delete. */ /* UTF-8 does not use fe and ff except in a possible */ /* "Byte Order Mark" (BOM) at the beginning. */ snprintf (safe_str + safe_len, sizeof(safe_str)-safe_len, "<0x%02x>", ch); safe_len += 6; } else { /* Let everything else thru so we can handle UTF-8 */ /* Maybe we should have an option to display 0x80 */ /* and above as hexadecimal. */ safe_str[safe_len++] = ch; safe_str[safe_len] = '\0'; } pstr++; len--; } // TODO1.2: should return string rather printing to remove a race condition. dw_printf ("%s", safe_str); } /* end ax25_safe_print */ /*------------------------------------------------------------------ * * Function: ax25_alevel_to_text * * Purpose: Convert audio level to text representation. * * Inputs: alevel - Audio levels collected from demodulator. * * Outputs: text - Text representation for presentation to user. * Currently it will look something like this: * * r(m/s) * * With n,m,s corresponding to received, mark, and space. * Comma is to be avoided because one place this * ends up is in a CSV format file. * * size should be AX25_ALEVEL_TO_TEXT_SIZE. * * Returns: True if something to print. (currently if alevel.original >= 0) * False if not. * * Description: Audio level used to be simple; it was a single number. * In version 1.2, we start collecting more details. * At the moment, it includes: * * - Received level from new method. * - Levels from mark & space filters to examine the ratio. * * We print this in multiple places so put it into a function. * *------------------------------------------------------------------*/ int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]) { if (alevel.rec < 0) { strlcpy (text, "", AX25_ALEVEL_TO_TEXT_SIZE); return (0); } // TODO1.2: haven't thought much about non-AFSK cases yet. // What should we do for 9600 baud? // For DTMF omit the two extra numbers. if (alevel.mark >= 0 && alevel.space < 0) { /* baseband */ snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space); } else if (alevel.mark == -1 && alevel.space == -1) { /* PSK - single number. */ snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec); } else if (alevel.mark == -2 && alevel.space == -2) { /* DTMF - single number. */ snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec); } else { /* AFSK */ //snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d:%d(%d/%d=%05.3f=)", alevel.original, alevel.rec, alevel.mark, alevel.space, alevel.ms_ratio); snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%d/%d)", alevel.rec, alevel.mark, alevel.space); } return (1); } /* end ax25_alevel_to_text */ /* end ax25_pad.c */ direwolf-1.5+dfsg/ax25_pad.h000066400000000000000000000301421347750676600157120ustar00rootroot00000000000000/*------------------------------------------------------------------- * * Name: ax25_pad.h * * Purpose: Header file for using ax25_pad.c * *------------------------------------------------------------------*/ #ifndef AX25_PAD_H #define AX25_PAD_H 1 #define AX25_MAX_REPEATERS 8 #define AX25_MIN_ADDRS 2 /* Destinatin & Source. */ #define AX25_MAX_ADDRS 10 /* Destination, Source, 8 digipeaters. */ #define AX25_DESTINATION 0 /* Address positions in frame. */ #define AX25_SOURCE 1 #define AX25_REPEATER_1 2 #define AX25_REPEATER_2 3 #define AX25_REPEATER_3 4 #define AX25_REPEATER_4 5 #define AX25_REPEATER_5 6 #define AX25_REPEATER_6 7 #define AX25_REPEATER_7 8 #define AX25_REPEATER_8 9 #define AX25_MAX_ADDR_LEN 12 /* In theory, you would expect the maximum length */ /* to be 6 letters, dash, 2 digits, and nul for a */ /* total of 10. However, object labels can be 10 */ /* characters so throw in a couple extra bytes */ /* to be safe. */ #define AX25_MIN_INFO_LEN 0 /* Previously 1 when considering only APRS. */ #define AX25_MAX_INFO_LEN 2048 /* Maximum size for APRS. */ /* AX.25 starts out with 256 as the default max */ /* length but the end stations can negotiate */ /* something different. */ /* version 0.8: Change from 256 to 2028 to */ /* handle the larger paclen for Linux AX25. */ /* These don't include the 2 bytes for the */ /* HDLC frame FCS. */ /* * Previously, for APRS only. * #define AX25_MIN_PACKET_LEN ( 2 * 7 + 2 + AX25_MIN_INFO_LEN) * #define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + AX25_MAX_INFO_LEN) */ /* The more general case. */ /* An AX.25 frame can have a control byte and no protocol. */ #define AX25_MIN_PACKET_LEN ( 2 * 7 + 1 ) #define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + 3 + AX25_MAX_INFO_LEN) /* * packet_t is a pointer to a packet object. * * The actual implementation is not visible outside ax25_pad.c. */ #define AX25_UI_FRAME 3 /* Control field value. */ #define AX25_PID_NO_LAYER_3 0xf0 /* protocol ID used for APRS */ #define AX25_PID_SEGMENTATION_FRAGMENT 0x08 #define AX25_PID_ESCAPE_CHARACTER 0xff #ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */ struct packet_s { int magic1; /* for error checking. */ int seq; /* unique sequence number for debugging. */ double release_time; /* Time stamp in format returned by dtime_now(). */ /* When to release from the SATgate mode delay queue. */ #define MAGIC 0x41583235 struct packet_s *nextp; /* Pointer to next in queue. */ int num_addr; /* Number of addresses in frame. */ /* Range of AX25_MIN_ADDRS .. AX25_MAX_ADDRS for AX.25. */ /* It will be 0 if it doesn't look like AX.25. */ /* -1 is used temporarily at allocation to mean */ /* not determined yet. */ /* * The 7th octet of each address contains: * * Bits: H R R SSID 0 * * H for digipeaters set to 0 intially. * Changed to 1 when position has been used. * * for source & destination it is called * command/response. Normally both 1 for APRS. * They should be opposites for connected mode. * * R R Reserved. Normally set to 1 1. * * SSID Substation ID. Range of 0 - 15. * * 0 Usually 0 but 1 for last address. */ #define SSID_H_MASK 0x80 #define SSID_H_SHIFT 7 #define SSID_RR_MASK 0x60 #define SSID_RR_SHIFT 5 #define SSID_SSID_MASK 0x1e #define SSID_SSID_SHIFT 1 #define SSID_LAST_MASK 0x01 int frame_len; /* Frame length without CRC. */ int modulo; /* I & S frames have sequence numbers of either 3 bits (modulo 8) */ /* or 7 bits (modulo 128). This is conveyed by either 1 or 2 */ /* control bytes. Unfortunately, we can't determine this by looking */ /* at an isolated frame. We need to know about the context. If we */ /* are part of the conversation, we would know. But if we are */ /* just listening to others, this would be more difficult to determine. */ /* For U frames: set to 0 - not applicable */ /* For I & S frames: 8 or 128 if known. 0 if unknown. */ unsigned char frame_data[AX25_MAX_PACKET_LEN+1]; /* Raw frame contents, without the CRC. */ int magic2; /* Will get stomped on if above overflows. */ }; #else /* Public view. */ struct packet_s { int secret; }; #endif typedef struct packet_s *packet_t; typedef enum cmdres_e { cr_00 = 2, cr_cmd = 1, cr_res = 0, cr_11 = 3 } cmdres_t; extern packet_t ax25_new (void); #ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */ /* * APRS always has one control octet of 0x03 but the more * general AX.25 case is one or two control bytes depending on * whether "modulo 128 operation" is in effect. */ //#define DEBUGX 1 static inline int ax25_get_control_offset (packet_t this_p) { return (this_p->num_addr*7); } static inline int ax25_get_num_control (packet_t this_p) { int c; c = this_p->frame_data[ax25_get_control_offset(this_p)]; if ( (c & 0x01) == 0 ) { /* I xxxx xxx0 */ #if DEBUGX dw_printf ("ax25_get_num_control, %02x is I frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1); #endif return ((this_p->modulo == 128) ? 2 : 1); } if ( (c & 0x03) == 1 ) { /* S xxxx xx01 */ #if DEBUGX dw_printf ("ax25_get_num_control, %02x is S frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1); #endif return ((this_p->modulo == 128) ? 2 : 1); } #if DEBUGX dw_printf ("ax25_get_num_control, %02x is U frame, always returns 1.\n", c); #endif return (1); /* U xxxx xx11 */ } /* * APRS always has one protocol octet of 0xF0 meaning no level 3 * protocol but the more general case is 0, 1 or 2 protocol ID octets. */ static inline int ax25_get_pid_offset (packet_t this_p) { return (ax25_get_control_offset (this_p) + ax25_get_num_control(this_p)); } static int ax25_get_num_pid (packet_t this_p) { int c; int pid; c = this_p->frame_data[ax25_get_control_offset(this_p)]; if ( (c & 0x01) == 0 || /* I xxxx xxx0 */ c == 0x03 || c == 0x13) { /* UI 000x 0011 */ pid = this_p->frame_data[ax25_get_pid_offset(this_p)]; #if DEBUGX dw_printf ("ax25_get_num_pid, %02x is I or UI frame, pid = %02x, returns %d\n", c, pid, (pid==AX25_PID_ESCAPE_CHARACTER) ? 2 : 1); #endif if (pid == AX25_PID_ESCAPE_CHARACTER) { return (2); /* pid 1111 1111 means another follows. */ } return (1); } #if DEBUGX dw_printf ("ax25_get_num_pid, %02x is neither I nor UI frame, returns 0\n", c); #endif return (0); } /* * AX.25 has info field for 5 frame types depending on the control field. * * xxxx xxx0 I * 000x 0011 UI (which includes APRS) * 101x 1111 XID * 111x 0011 TEST * 100x 0111 FRMR * * APRS always has an Information field with at least one octet for the Data Type Indicator. */ static inline int ax25_get_info_offset (packet_t this_p) { int offset = ax25_get_control_offset (this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p); #if DEBUGX dw_printf ("ax25_get_info_offset, returns %d\n", offset); #endif return (offset); } static inline int ax25_get_num_info (packet_t this_p) { int len; /* assuming AX.25 frame. */ len = this_p->frame_len - this_p->num_addr * 7 - ax25_get_num_control(this_p) - ax25_get_num_pid(this_p); if (len < 0) { len = 0; /* print error? */ } return (len); } #endif typedef enum ax25_modulo_e { modulo_unknown = 0, modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t; typedef enum ax25_frame_type_e { frame_type_I = 0, // Information frame_type_S_RR, // Receive Ready - System Ready To Receive frame_type_S_RNR, // Receive Not Ready - TNC Buffer Full frame_type_S_REJ, // Reject Frame - Out of Sequence or Duplicate frame_type_S_SREJ, // Selective Reject - Request single frame repeat frame_type_U_SABME, // Set Async Balanced Mode, Extended frame_type_U_SABM, // Set Async Balanced Mode frame_type_U_DISC, // Disconnect frame_type_U_DM, // Disconnect Mode frame_type_U_UA, // Unnumbered Acknowledge frame_type_U_FRMR, // Frame Reject frame_type_U_UI, // Unnumbered Information frame_type_U_XID, // Exchange Identification frame_type_U_TEST, // Test frame_type_U, // other Unnumbered, not used by AX.25. frame_not_AX25 // Could not get control byte from frame. // This must be last because value plus 1 is // for the size of an array. } ax25_frame_type_t; /* * Originally this was a single number. * Let's try something new in version 1.2. * Also collect AGC values from the mark and space filters. */ typedef struct alevel_s { int rec; int mark; int space; //float ms_ratio; // TODO: take out after temporary investigation. } alevel_t; #ifndef AXTEST // TODO: remove this? #define AX25MEMDEBUG 1 #endif #if AX25MEMDEBUG // to investigate a memory leak problem extern void ax25memdebug_set(void); extern int ax25memdebug_get (void); extern int ax25memdebug_seq (packet_t this_p); extern packet_t ax25_from_text_debug (char *monitor, int strict, char *src_file, int src_line); #define ax25_from_text(m,s) ax25_from_text_debug(m,s,__FILE__,__LINE__) extern packet_t ax25_from_frame_debug (unsigned char *data, int len, alevel_t alevel, char *src_file, int src_line); #define ax25_from_frame(d,l,a) ax25_from_frame_debug(d,l,a,__FILE__,__LINE__); extern packet_t ax25_dup_debug (packet_t copy_from, char *src_file, int src_line); #define ax25_dup(p) ax25_dup_debug(p,__FILE__,__LINE__); extern void ax25_delete_debug (packet_t pp, char *src_file, int src_line); #define ax25_delete(p) ax25_delete_debug(p,__FILE__,__LINE__); #else extern packet_t ax25_from_text (char *monitor, int strict); extern packet_t ax25_from_frame (unsigned char *data, int len, alevel_t alevel); extern packet_t ax25_dup (packet_t copy_from); extern void ax25_delete (packet_t pp); #endif extern int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard); extern int ax25_check_addresses (packet_t pp); extern packet_t ax25_unwrap_third_party (packet_t from_pp); extern void ax25_set_addr (packet_t pp, int, char *); extern void ax25_insert_addr (packet_t this_p, int n, char *ad); extern void ax25_remove_addr (packet_t this_p, int n); extern int ax25_get_num_addr (packet_t pp); extern int ax25_get_num_repeaters (packet_t this_p); extern void ax25_get_addr_with_ssid (packet_t pp, int n, char *station); extern void ax25_get_addr_no_ssid (packet_t pp, int n, char *station); extern int ax25_get_ssid (packet_t pp, int n); extern void ax25_set_ssid (packet_t this_p, int n, int ssid); extern int ax25_get_h (packet_t pp, int n); extern void ax25_set_h (packet_t pp, int n); extern int ax25_get_heard(packet_t this_p); extern int ax25_get_first_not_repeated(packet_t pp); extern int ax25_get_rr (packet_t this_p, int n); extern int ax25_get_info (packet_t pp, unsigned char **paddr); extern int ax25_cut_at_crlf (packet_t this_p); extern void ax25_set_nextp (packet_t this_p, packet_t next_p); extern int ax25_get_dti (packet_t this_p); extern packet_t ax25_get_nextp (packet_t this_p); extern void ax25_set_release_time (packet_t this_p, double release_time); extern double ax25_get_release_time (packet_t this_p); extern void ax25_set_modulo (packet_t this_p, int modulo); extern void ax25_format_addrs (packet_t pp, char *); extern void ax25_format_via_path (packet_t this_p, char *result, size_t result_size); extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]); extern ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns); extern void ax25_hex_dump (packet_t this_p); extern int ax25_is_aprs (packet_t pp); extern int ax25_is_null_frame (packet_t this_p); extern int ax25_get_control (packet_t this_p); extern int ax25_get_c2 (packet_t this_p); extern int ax25_get_pid (packet_t this_p); extern int ax25_get_frame_len (packet_t this_p); extern unsigned short ax25_dedupe_crc (packet_t pp); extern unsigned short ax25_m_m_crc (packet_t pp); extern void ax25_safe_print (char *, int, int ascii_only); #define AX25_ALEVEL_TO_TEXT_SIZE 32 // overkill but safe. extern int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]); #endif /* AX25_PAD_H */ /* end ax25_pad.h */ direwolf-1.5+dfsg/ax25_pad2.c000066400000000000000000000616141347750676600157770ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Name: ax25_pad2.c * * Purpose: Packet assembler and disasembler, part 2. * * Description: * * The original ax25_pad.c was written with APRS in mind. * It handles UI frames and transparency for a KISS TNC. * Here we add new functions that can handle the * more general cases of AX.25 frames. * * * * Destination Address (note: opposite order in printed format) * * * Source Address * * * 0-8 Digipeater Addresses * (The AX.25 v2.2 spec reduced this number to * a maximum of 2 but I allow the original 8.) * * Each address is composed of: * * * 6 upper case letters or digits, blank padded. * These are shifted left one bit, leaving the LSB always 0. * * * a 7th octet containing the SSID and flags. * The LSB is always 0 except for the last octet of the address field. * * The final octet of the Destination has the form: * * C R R SSID 0, where, * * C = command/response. Set to 1 for command. * R R = Reserved = 1 1 (See RR note, below) * SSID = substation ID * 0 = zero * * The final octet of the Source has the form: * * C R R SSID 0, where, * * C = command/response. Must be inverse of destination C bit. * R R = Reserved = 1 1 (See RR note, below) * SSID = substation ID * 0 = zero (or 1 if no repeaters) * * The final octet of each repeater has the form: * * H R R SSID 0, where, * * H = has-been-repeated = 0 initially. * Set to 1 after this address has been used. * R R = Reserved = 1 1 * SSID = substation ID * 0 = zero (or 1 if last repeater in list) * * A digipeater would repeat this frame if it finds its address * with the "H" bit set to 0 and all earlier repeater addresses * have the "H" bit set to 1. * The "H" bit would be set to 1 in the repeated frame. * * In standard monitoring format, an asterisk is displayed after the last * digipeater with the "H" bit set. That indicates who you are hearing * over the radio. * * * Next we have: * * * One or two byte Control Field - A U frame always has one control byte. * When using modulo 128 sequence numbers, the * I and S frames can have a second byte allowing * 7 bit fields instead of 3 bit fields. * Unfortunately, we can't tell which we have by looking * at a frame out of context. :-( * If we are one end of the link, we would know this * from SABM/SABME and possible later negotiation * with XID. But if we start monitoring two other * stations that are already conversing, we don't know. * * RR note: It seems that some implementations put a hint * in the "RR" reserved bits. * http://www.tapr.org/pipermail/ax25-layer2/2005-October/000297.html * The RR bits can also be used for "DAMA" which is * some sort of channel access coordination scheme. * http://internet.freepage.de/cgi-bin/feets/freepage_ext/41030x030A/rewrite/hennig/afu/afudoc/afudama.html * Neither is part of the official protocol spec. * * * One byte Protocol ID - Only for I and UI frames. * Normally we would use 0xf0 for no layer 3. * * Finally the Information Field. The initial max size is 256 but it * can be negotiated higher if both ends agree. * * Only these types of frames can have an information part: * - I * - UI * - XID * - TEST * - FRMR * * The 2 byte CRC is not stored here. * * * Constructors: * ax25_u_frame - Construct a U frame. * ax25_s_frame - Construct a S frame. * ax25_i_frame - Construct a I frame. * * Get methods: .... ??? * *------------------------------------------------------------------*/ #define AX25_PAD_C /* this will affect behavior of ax25_pad.h */ #include "direwolf.h" #include #include #include #include #include #include "textcolor.h" #include "ax25_pad.h" #include "ax25_pad2.h" extern int ax25memdebug; static int set_addrs (packet_t pp, char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr); //#if AX25MEMDEBUG //#undef AX25MEMDEBUG //#endif /*------------------------------------------------------------------------------ * * Name: ax25_u_frame * * Purpose: Construct a U frame. * * Input: addrs - Array of addresses. * * num_addr - Number of addresses, range 2 .. 10. * * cr - cr_cmd command frame, cr_res for a response frame. * * ftype - One of: * frame_type_U_SABME // Set Async Balanced Mode, Extended * frame_type_U_SABM // Set Async Balanced Mode * frame_type_U_DISC // Disconnect * frame_type_U_DM // Disconnect Mode * frame_type_U_UA // Unnumbered Acknowledge * frame_type_U_FRMR // Frame Reject * frame_type_U_UI // Unnumbered Information * frame_type_U_XID // Exchange Identification * frame_type_U_TEST // Test * * pf - Poll/Final flag. * * pid - Protocol ID. >>> Used ONLY for the UI type. <<< * Normally 0xf0 meaning no level 3. * Could be other values for NET/ROM, etc. * * pinfo - Pointer to data for Info field. Allowed only for UI, XID, TEST, FRMR. * * info_len - Length for Info field. * * * Returns: Pointer to new packet object. * *------------------------------------------------------------------------------*/ #if AX25MEMDEBUG packet_t ax25_u_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line) #else packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len) #endif { packet_t this_p; unsigned char *p; int ctrl = 0; unsigned int t = 999; // 1 = must be cmd, 0 = must be response, 2 = can be either. int i = 0; // Is Info part allowed? this_p = ax25_new (); #if AX25MEMDEBUG if (ax25memdebug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("ax25_u_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line); } #endif if (this_p == NULL) return (NULL); this_p->modulo = 0; if ( ! set_addrs (this_p, addrs, num_addr, cr)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Could not set addresses for U frame.\n", __func__); ax25_delete (this_p); return (NULL); } switch (ftype) { // 1 = cmd only, 0 = res only, 2 = either case frame_type_U_SABME: ctrl = 0x6f; t = 1; break; case frame_type_U_SABM: ctrl = 0x2f; t = 1; break; case frame_type_U_DISC: ctrl = 0x43; t = 1; break; case frame_type_U_DM: ctrl = 0x0f; t = 0; break; case frame_type_U_UA: ctrl = 0x63; t = 0; break; case frame_type_U_FRMR: ctrl = 0x87; t = 0; i = 1; break; case frame_type_U_UI: ctrl = 0x03; t = 2; i = 1; break; case frame_type_U_XID: ctrl = 0xaf; t = 2; i = 1; break; case frame_type_U_TEST: ctrl = 0xe3; t = 2; i = 1; break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Invalid ftype %d for U frame.\n", __func__, ftype); ax25_delete (this_p); return (NULL); break; } if (pf) ctrl |= 0x10; if (t != 2) { if (cr != t) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: U frame, cr is %d but must be %d. ftype=%d\n", __func__, cr, t, ftype); } } p = this_p->frame_data + this_p->frame_len; *p++ = ctrl; this_p->frame_len++; if (ftype == frame_type_U_UI) { // Definitely don't want pid value of 0 (not in valid list) // or 0xff (which means more bytes follow). if (pid < 0 || pid == 0 || pid == 0xff) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: U frame, Invalid pid value 0x%02x.\n", __func__, pid); pid = AX25_PID_NO_LAYER_3; } *p++ = pid; this_p->frame_len++; } if (i) { if (pinfo != NULL && info_len > 0) { if (info_len > AX25_MAX_INFO_LEN) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: U frame, Invalid information field length %d.\n", __func__, info_len); info_len = AX25_MAX_INFO_LEN; } memcpy (p, pinfo, info_len); p += info_len; this_p->frame_len += info_len; } } else { if (pinfo != NULL && info_len > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Info part not allowed for U frame type.\n", __func__); } } *p = '\0'; assert (p == this_p->frame_data + this_p->frame_len); assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); #if PAD2TEST ax25_frame_type_t check_ftype; cmdres_t check_cr; char check_desc[80]; int check_pf; int check_nr; int check_ns; check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns); text_color_set(DW_COLOR_DEBUG); dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d\n", check_ftype, check_desc, check_pf); assert (check_cr == cr); assert (check_ftype == ftype); assert (check_pf == pf); assert (check_nr == -1); assert (check_ns == -1); #endif return (this_p); } /* end ax25_u_frame */ /*------------------------------------------------------------------------------ * * Name: ax25_s_frame * * Purpose: Construct an S frame. * * Input: addrs - Array of addresses. * * num_addr - Number of addresses, range 2 .. 10. * * cr - cr_cmd command frame, cr_res for a response frame. * * ftype - One of: * frame_type_S_RR, // Receive Ready - System Ready To Receive * frame_type_S_RNR, // Receive Not Ready - TNC Buffer Full * frame_type_S_REJ, // Reject Frame - Out of Sequence or Duplicate * frame_type_S_SREJ, // Selective Reject - Request single frame repeat * * modulo - 8 or 128. Determines if we have 1 or 2 control bytes. * * nr - N(R) field --- describe. * * pf - Poll/Final flag. * * pinfo - Pointer to data for Info field. Allowed only for SREJ. * * info_len - Length for Info field. * * * Returns: Pointer to new packet object. * *------------------------------------------------------------------------------*/ #if AX25MEMDEBUG packet_t ax25_s_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, unsigned char *pinfo, int info_len, char *src_file, int src_line) #else packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, unsigned char *pinfo, int info_len) #endif { packet_t this_p; unsigned char *p; int ctrl = 0; this_p = ax25_new (); #if AX25MEMDEBUG if (ax25memdebug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("ax25_s_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line); } #endif if (this_p == NULL) return (NULL); if ( ! set_addrs (this_p, addrs, num_addr, cr)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Could not set addresses for S frame.\n", __func__); ax25_delete (this_p); return (NULL); } if (modulo != 8 && modulo != 128) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Invalid modulo %d for S frame.\n", __func__, modulo); modulo = 8; } this_p->modulo = modulo; if (nr < 0 || nr >= modulo) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Invalid N(R) %d for S frame.\n", __func__, nr); nr &= (modulo - 1); } // Erratum: The AX.25 spec is not clear about whether SREJ should be command, response, or both. // The underlying X.25 spec clearly says it is reponse only. Let's go with that. if (ftype == frame_type_S_SREJ && cr != cr_res) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: SREJ must be response.\n", __func__); } switch (ftype) { case frame_type_S_RR: ctrl = 0x01; break; case frame_type_S_RNR: ctrl = 0x05; break; case frame_type_S_REJ: ctrl = 0x09; break; case frame_type_S_SREJ: ctrl = 0x0d; break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Invalid ftype %d for S frame.\n", __func__, ftype); ax25_delete (this_p); return (NULL); break; } p = this_p->frame_data + this_p->frame_len; if (modulo == 8) { if (pf) ctrl |= 0x10; ctrl |= nr << 5; *p++ = ctrl; this_p->frame_len++; } else { *p++ = ctrl; this_p->frame_len++; ctrl = pf & 1; ctrl |= nr << 1; *p++ = ctrl; this_p->frame_len++; } if (ftype == frame_type_S_SREJ) { if (pinfo != NULL && info_len > 0) { if (info_len > AX25_MAX_INFO_LEN) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: SREJ frame, Invalid information field length %d.\n", __func__, info_len); info_len = AX25_MAX_INFO_LEN; } memcpy (p, pinfo, info_len); p += info_len; this_p->frame_len += info_len; } } else { if (pinfo != NULL || info_len != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Info part not allowed for RR, RNR, REJ frame.\n", __func__); } } *p = '\0'; assert (p == this_p->frame_data + this_p->frame_len); assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); #if PAD2TEST ax25_frame_type_t check_ftype; cmdres_t check_cr; char check_desc[80]; int check_pf; int check_nr; int check_ns; // todo modulo must be input. check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns); text_color_set(DW_COLOR_DEBUG); dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d, nr=%d\n", check_ftype, check_desc, check_pf, check_nr); assert (check_cr == cr); assert (check_ftype == ftype); assert (check_pf == pf); assert (check_nr == nr); assert (check_ns == -1); #endif return (this_p); } /* end ax25_s_frame */ /*------------------------------------------------------------------------------ * * Name: ax25_i_frame * * Purpose: Construct an I frame. * * Input: addrs - Array of addresses. * * num_addr - Number of addresses, range 2 .. 10. * * cr - cr_cmd command frame, cr_res for a response frame. * * modulo - 8 or 128. * * nr - N(R) field --- describe. * * ns - N(S) field --- describe. * * pf - Poll/Final flag. * * pid - Protocol ID. * Normally 0xf0 meaning no level 3. * Could be other values for NET/ROM, etc. * * pinfo - Pointer to data for Info field. * * info_len - Length for Info field. * * * Returns: Pointer to new packet object. * *------------------------------------------------------------------------------*/ #if AX25MEMDEBUG packet_t ax25_i_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line) #else packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len) #endif { packet_t this_p; unsigned char *p; int ctrl = 0; this_p = ax25_new (); #if AX25MEMDEBUG if (ax25memdebug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("ax25_i_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line); } #endif if (this_p == NULL) return (NULL); if ( ! set_addrs (this_p, addrs, num_addr, cr)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Could not set addresses for I frame.\n", __func__); ax25_delete (this_p); return (NULL); } if (modulo != 8 && modulo != 128) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Invalid modulo %d for I frame.\n", __func__, modulo); modulo = 8; } this_p->modulo = modulo; if (nr < 0 || nr >= modulo) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Invalid N(R) %d for I frame.\n", __func__, nr); nr &= (modulo - 1); } if (ns < 0 || ns >= modulo) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: Invalid N(S) %d for I frame.\n", __func__, ns); ns &= (modulo - 1); } p = this_p->frame_data + this_p->frame_len; if (modulo == 8) { ctrl = (nr << 5) | (ns << 1); if (pf) ctrl |= 0x10; *p++ = ctrl; this_p->frame_len++; } else { ctrl = ns << 1; *p++ = ctrl; this_p->frame_len++; ctrl = nr << 1; if (pf) ctrl |= 0x01; *p++ = ctrl; this_p->frame_len++; } // Definitely don't want pid value of 0 (not in valid list) // or 0xff (which means more bytes follow). if (pid < 0 || pid == 0 || pid == 0xff) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Warning: Client application provided invalid PID value, 0x%02x, for I frame.\n", pid); pid = AX25_PID_NO_LAYER_3; } *p++ = pid; this_p->frame_len++; if (pinfo != NULL && info_len > 0) { if (info_len > AX25_MAX_INFO_LEN) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error in %s: I frame, Invalid information field length %d.\n", __func__, info_len); info_len = AX25_MAX_INFO_LEN; } memcpy (p, pinfo, info_len); p += info_len; this_p->frame_len += info_len; } *p = '\0'; assert (p == this_p->frame_data + this_p->frame_len); assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); #if PAD2TEST ax25_frame_type_t check_ftype; cmdres_t check_cr; char check_desc[80]; int check_pf; int check_nr; int check_ns; unsigned char *check_pinfo; int check_info_len; check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns); text_color_set(DW_COLOR_DEBUG); dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d, nr=%d, ns=%d\n", check_ftype, check_desc, check_pf, check_nr, check_ns); check_info_len = ax25_get_info (this_p, &check_pinfo); assert (check_cr == cr); assert (check_ftype == frame_type_I); assert (check_pf == pf); assert (check_nr == nr); assert (check_ns == ns); assert (check_info_len == info_len); assert (strcmp((char*)check_pinfo,(char*)pinfo) == 0); #endif return (this_p); } /* end ax25_i_frame */ /*------------------------------------------------------------------------------ * * Name: set_addrs * * Purpose: Set address fields * * Input: pp - Packet object. * * addrs - Array of addresses. Same order as in frame. * * num_addr - Number of addresses, range 2 .. 10. * * cr - cr_cmd command frame, cr_res for a response frame. * * Output: pp->frame_data - 7 bytes for each address. * * pp->frame_len - num_addr * 7 * * p->num_addr - num_addr * * Returns: 1 for success. 0 for failure. * *------------------------------------------------------------------------------*/ static int set_addrs (packet_t pp, char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr) { int n; assert (pp->frame_len == 0); assert (cr == cr_cmd || cr == cr_res); if (num_addr < AX25_MIN_ADDRS || num_addr > AX25_MAX_ADDRS) { text_color_set(DW_COLOR_DEBUG); dw_printf ("INTERNAL ERROR: %s %s %d, num_addr = %d\n", __FILE__, __func__, __LINE__, num_addr); return (0); } for (n = 0; n < num_addr; n++) { unsigned char *pa = pp->frame_data + n * 7; int ok; int strict = 1; char oaddr[AX25_MAX_ADDR_LEN]; int ssid; int heard; int j; ok = ax25_parse_addr (n, addrs[n], strict, oaddr, &ssid, &heard); if (! ok) return (0); // Fill in address. memset (pa, ' ' << 1, 6); for (j = 0; oaddr[j]; j++) { pa[j] = oaddr[j] << 1; } pa += 6; // Fill in SSID. *pa = 0x60 | ((ssid & 0xf) << 1); // Command / response flag. switch (n) { case AX25_DESTINATION: if (cr == cr_cmd) *pa |= 0x80; break; case AX25_SOURCE: if (cr == cr_res) *pa |= 0x80; break; default: break; } // Is this the end of address field? if (n == num_addr - 1) { *pa |= 1; } pp->frame_len += 7; } pp->num_addr = num_addr; return (1); } /* end set_addrs */ /*------------------------------------------------------------------------------ * * Name: main * * Purpose: Quick unit test for this file. * * Description: Generate a variety of frames. * Each function calls ax25_frame_type to verify results. * * $ gcc -DPAD2TEST -DUSE_REGEX_STATIC -Iregex ax25_pad.c ax25_pad2.c fcs_calc.o textcolor.o regex.a misc.a * *------------------------------------------------------------------------------*/ #if PAD2TEST int main () { char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN]; int num_addr = 2; cmdres_t cr; ax25_frame_type_t ftype; int pf = 0; int pid = 0xf0; int modulo; int nr, ns; unsigned char *pinfo = NULL; int info_len = 0; packet_t pp; strcpy (addrs[0], "W2UB"); strcpy (addrs[1], "WB2OSZ-15"); num_addr = 2; /* U frame */ for (ftype = frame_type_U_SABME; ftype <= frame_type_U_TEST; ftype++) { for (pf = 0; pf <= 1; pf++) { int cmin, cmax; switch (ftype) { // 0 = response, 1 = command case frame_type_U_SABME: cmin = 1; cmax = 1; break; case frame_type_U_SABM: cmin = 1; cmax = 1; break; case frame_type_U_DISC: cmin = 1; cmax = 1; break; case frame_type_U_DM: cmin = 0; cmax = 0; break; case frame_type_U_UA: cmin = 0; cmax = 0; break; case frame_type_U_FRMR: cmin = 0; cmax = 0; break; case frame_type_U_UI: cmin = 0; cmax = 1; break; case frame_type_U_XID: cmin = 0; cmax = 1; break; case frame_type_U_TEST: cmin = 0; cmax = 1; break; default: break; // avoid compiler warning. } for (cr = cmin; cr <= cmax; cr++) { text_color_set(DW_COLOR_INFO); dw_printf ("\nConstruct U frame, cr=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); pp = ax25_u_frame (addrs, num_addr, cr, ftype, pf, pid, pinfo, info_len); ax25_hex_dump (pp); ax25_delete (pp); } } } dw_printf ("\n----------\n\n"); /* S frame */ strcpy (addrs[2], "DIGI1-1"); num_addr = 3; for (ftype = frame_type_S_RR; ftype <= frame_type_S_SREJ; ftype++) { for (pf = 0; pf <= 1; pf++) { modulo = 8; nr = modulo / 2 + 1; for (cr = 0; cr <= 1; cr++) { text_color_set(DW_COLOR_INFO); dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, NULL, 0); ax25_hex_dump (pp); ax25_delete (pp); } modulo = 128; nr = modulo / 2 + 1; for (cr = 0; cr <= 1; cr++) { text_color_set(DW_COLOR_INFO); dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, NULL, 0); ax25_hex_dump (pp); ax25_delete (pp); } } } /* SREJ is only S frame which can have information part. */ static char srej_info[] = { 1<<1, 2<<1, 3<<1, 4<<1 }; ftype = frame_type_S_SREJ; for (pf = 0; pf <= 1; pf++) { modulo = 128; nr = 127; cr = cr_res; text_color_set(DW_COLOR_INFO); dw_printf ("\nConstruct Multi-SREJ S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, srej_info, (int)(sizeof(srej_info))); ax25_hex_dump (pp); ax25_delete (pp); } dw_printf ("\n----------\n\n"); /* I frame */ pinfo = (unsigned char*)"The rain in Spain stays mainly on the plain."; info_len = strlen((char*)pinfo); for (pf = 0; pf <= 1; pf++) { modulo = 8; nr = 0x55 & (modulo - 1); ns = 0xaa & (modulo - 1); for (cr = 0; cr <= 1; cr++) { text_color_set(DW_COLOR_INFO); dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len); ax25_hex_dump (pp); ax25_delete (pp); } modulo = 128; nr = 0x55 & (modulo - 1); ns = 0xaa & (modulo - 1); for (cr = 0; cr <= 1; cr++) { text_color_set(DW_COLOR_INFO); dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid); pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len); ax25_hex_dump (pp); ax25_delete (pp); } } text_color_set(DW_COLOR_REC); dw_printf ("\n----------\n\n"); dw_printf ("\nSUCCESS!\n"); exit (EXIT_SUCCESS); } /* end main */ #endif /* end ax25_pad2.c */ direwolf-1.5+dfsg/ax25_pad2.h000066400000000000000000000037221347750676600160000ustar00rootroot00000000000000/*------------------------------------------------------------------- * * Name: ax25_pad2.h * * Purpose: Header file for using ax25_pad2.c * ax25_pad dealt only with UI frames. * This adds a facility for the other types: U, s, I. * *------------------------------------------------------------------*/ #ifndef AX25_PAD2_H #define AX25_PAD2_H 1 #include "ax25_pad.h" #if AX25MEMDEBUG // to investigate a memory leak problem packet_t ax25_u_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line); packet_t ax25_s_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, unsigned char *pinfo, int info_len, char *src_file, int src_line); packet_t ax25_i_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line); #define ax25_u_frame(a,n,c,f,p,q,i,l) ax25_u_frame_debug(a,n,c,f,p,q,i,l,__FILE__,__LINE__) #define ax25_s_frame(a,n,c,f,m,r,p,i,l) ax25_s_frame_debug(a,n,c,f,m,r,p,i,l,__FILE__,__LINE__) #define ax25_i_frame(a,n,c,m,r,s,p,q,i,l) ax25_i_frame_debug(a,n,c,m,r,s,p,q,i,l,__FILE__,__LINE__) #else packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len); packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, unsigned char *pinfo, int info_len); packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len); #endif #endif /* AX25_PAD2_H */ /* end ax25_pad2.h */ direwolf-1.5+dfsg/beacon.c000066400000000000000000000675551347750676600155530ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: beacon.c * * Purpose: Transmit messages on a fixed schedule. * * Description: Transmit periodic messages as specified in the config file. * *---------------------------------------------------------------*/ //#define DEBUG 1 #include "direwolf.h" #include #include #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "tq.h" #include "xmit.h" #include "config.h" #include "version.h" #include "encode_aprs.h" #include "beacon.h" #include "latlong.h" #include "dwgps.h" #include "log.h" #include "dlq.h" #include "aprs_tt.h" // for dw_run_cmd - should relocate someday. #include "mheard.h" #if __WIN32__ /* * Windows doesn't have localtime_r. * It should have the equivalent localtime_s, with opposite parameter * order, but I get undefined reference when trying to use it. */ struct tm *localtime_r(time_t *clock, struct tm *res) { struct tm *tm; tm = localtime (clock); memcpy (res, tm, sizeof(struct tm)); return (res); } #endif /* * Save pointers to configuration settings. */ static struct audio_s *g_modem_config_p; static struct misc_config_s *g_misc_config_p; static struct igate_config_s *g_igate_config_p; #if __WIN32__ static unsigned __stdcall beacon_thread (void *arg); #else static void * beacon_thread (void *arg); #endif static int g_tracker_debug_level = 0; // 1 for data from gps. // 2 + Smart Beaconing logic. // 3 + Send transmissions to log file. void beacon_tracker_set_debug (int level) { g_tracker_debug_level = level; } static time_t sb_calculate_next_time (time_t now, float current_speed_mph, float current_course, time_t last_xmit_time, float last_xmit_course); static void beacon_send (int j, dwgps_info_t *gpsinfo); /*------------------------------------------------------------------- * * Name: beacon_init * * Purpose: Initialize the beacon process. * * Inputs: pmodem - Audio device and modem configuration. * Used only to find valid channels. * * pconfig - misc. configuration from config file. * Beacon stuff ended up here. * * pigate - IGate configuration. * Need this for calculating IGate statistics. * * * Outputs: Remember required information for future use. * * Description: Do some validity checking on the beacon configuration. * * Start up beacon_thread to actually send the packets * at the appropriate time. * *--------------------------------------------------------------------*/ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate) { time_t now; struct tm tm; int j; int count; #if __WIN32__ HANDLE beacon_th; #else pthread_t beacon_tid; #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("beacon_init ( ... )\n"); #endif /* * Save parameters for later use. */ g_modem_config_p = pmodem; g_misc_config_p = pconfig; g_igate_config_p = pigate; /* * Precompute the packet contents so any errors are * Reported once at start up time rather than for each transmission. * If a serious error is found, set type to BEACON_IGNORE and that * table entry should be ignored later on. */ for (j=0; jnum_beacons; j++) { int chan = g_misc_config_p->beacon[j].sendto_chan; if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */ if (g_modem_config_p->achan[chan].valid) { if (strlen(g_modem_config_p->achan[chan].mycall) > 0 && strcasecmp(g_modem_config_p->achan[chan].mycall, "N0CALL") != 0 && strcasecmp(g_modem_config_p->achan[chan].mycall, "NOCALL") != 0) { switch (g_misc_config_p->beacon[j].btype) { case BEACON_OBJECT: /* Object name is required. */ if (strlen(g_misc_config_p->beacon[j].objname) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: OBJNAME is required for OBEACON.\n", g_misc_config_p->beacon[j].lineno); g_misc_config_p->beacon[j].btype = BEACON_IGNORE; continue; } /* Fall thru. Ignore any warning about missing break. */ case BEACON_POSITION: /* Location is required. */ if (g_misc_config_p->beacon[j].lat == G_UNKNOWN || g_misc_config_p->beacon[j].lon == G_UNKNOWN) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Latitude and longitude are required.\n", g_misc_config_p->beacon[j].lineno); g_misc_config_p->beacon[j].btype = BEACON_IGNORE; continue; } break; case BEACON_TRACKER: { dwgps_info_t gpsinfo; dwfix_t fix; fix = dwgps_read (&gpsinfo); if (fix == DWFIX_NOT_INIT) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: GPS must be configured to use TBEACON.\n", g_misc_config_p->beacon[j].lineno); g_misc_config_p->beacon[j].btype = BEACON_IGNORE; } } break; case BEACON_CUSTOM: /* INFO or INFOCMD is required. */ if (g_misc_config_p->beacon[j].custom_info == NULL && g_misc_config_p->beacon[j].custom_infocmd == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: INFO or INFOCMD is required for custom beacon.\n", g_misc_config_p->beacon[j].lineno); g_misc_config_p->beacon[j].btype = BEACON_IGNORE; continue; } break; case BEACON_IGATE: /* Doesn't make sense if IGate is not configured. */ if (strlen(g_igate_config_p->t2_server_name) == 0 || strlen(g_igate_config_p->t2_login) == 0 || strlen(g_igate_config_p->t2_passcode) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Doesn't make sense to use IBEACON without IGate Configured.\n", g_misc_config_p->beacon[j].lineno); dw_printf ("IBEACON has been disabled.\n"); g_misc_config_p->beacon[j].btype = BEACON_IGNORE; continue; } break; case BEACON_IGNORE: break; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: MYCALL must be set for beacon on channel %d. \n", g_misc_config_p->beacon[j].lineno, chan); g_misc_config_p->beacon[j].btype = BEACON_IGNORE; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Invalid channel number %d for beacon. \n", g_misc_config_p->beacon[j].lineno, chan); g_misc_config_p->beacon[j].btype = BEACON_IGNORE; } } /* * Calculate first time for each beacon from the 'slot' or 'delay' value. */ now = time(NULL); localtime_r (&now, &tm); for (j=0; jnum_beacons; j++) { struct beacon_s *bp = & (g_misc_config_p->beacon[j]); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("beacon[%d] chan=%d, delay=%d, slot=%d, every=%d\n", j, bp->sendto_chan, bp->delay, bp->slot, bp->every); #endif /* * If timeslots, there must be a full number of beacon intervals per hour. */ #define IS_GOOD(x) ((3600/(x))*(x) == 3600) if (bp->slot != G_UNKNOWN) { if ( ! IS_GOOD(bp->every)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: When using timeslots, there must be a whole number of beacon intervals per hour.\n", bp->lineno); // Try to make it valid by adjusting up or down. int n; for (n=1; ; n++) { int e; e = bp->every + n; if (e > 3600) { bp->every = 3600; break; } if (IS_GOOD(e)) { bp->every = e; break; } e = bp->every - n; if (e < 1) { bp->every = 1; // Impose a larger minimum? break; } if (IS_GOOD(e)) { bp->every = e; break; } } text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Time between slotted beacons has been adjusted to %d seconds.\n", bp->lineno, bp->every); } /* * Determine when next slot time will arrive. */ bp->delay = bp->slot - (tm.tm_min * 60 + tm.tm_sec); while (bp->delay > bp->every) bp->delay -= bp->every; while (bp->delay < 5) bp->delay += bp->every; } g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].delay; } /* * Start up thread for processing only if at least one is valid. */ count = 0; for (j=0; jnum_beacons; j++) { if (g_misc_config_p->beacon[j].btype != BEACON_IGNORE) { count++; } } if (count >= 1) { #if __WIN32__ beacon_th = (HANDLE)_beginthreadex (NULL, 0, &beacon_thread, NULL, 0, NULL); if (beacon_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create beacon thread\n"); return; } #else int e; e = pthread_create (&beacon_tid, NULL, beacon_thread, (void *)0); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create beacon thread"); return; } #endif } } /* end beacon_init */ /*------------------------------------------------------------------- * * Name: beacon_thread * * Purpose: Transmit beacons when it is time. * * Inputs: g_misc_config_p->beacon * * Outputs: g_misc_config_p->beacon[].next_time * * Description: Go to sleep until it is time for the next beacon. * Transmit any beacons scheduled for now. * Repeat. * *--------------------------------------------------------------------*/ #define MIN(x,y) ((x) < (y) ? (x) : (y)) #if __WIN32__ static unsigned __stdcall beacon_thread (void *arg) #else static void * beacon_thread (void *arg) #endif { int j; /* Index into array of beacons. */ time_t earliest; time_t now; /* Current time. */ int number_of_tbeacons; /* Number of tracker beacons. */ /* * SmartBeaconing state. */ time_t sb_prev_time = 0; /* Time of most recent transmission. */ float sb_prev_course = 0; /* Most recent course reported. */ #if DEBUG struct tm tm; char hms[20]; now = time(NULL); localtime_r (&now, &tm); strftime (hms, sizeof(hms), "%H:%M:%S", &tm); text_color_set(DW_COLOR_DEBUG); dw_printf ("beacon_thread: started %s\n", hms); #endif /* * See if any tracker beacons are configured. * No need to obtain GPS data if none. */ number_of_tbeacons = 0; for (j=0; jnum_beacons; j++) { if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) { number_of_tbeacons++; } } now = time(NULL); while (1) { dwgps_info_t gpsinfo; /* * Sleep until time for the earliest scheduled or * the soonest we could transmit due to corner pegging. */ earliest = now + 60 * 60; for (j=0; jnum_beacons; j++) { if (g_misc_config_p->beacon[j].btype != BEACON_IGNORE) { earliest = MIN(g_misc_config_p->beacon[j].next, earliest); } } if (g_misc_config_p->sb_configured && number_of_tbeacons > 0) { earliest = MIN(now + g_misc_config_p->sb_turn_time, earliest); earliest = MIN(now + g_misc_config_p->sb_fast_rate, earliest); } if (earliest > now) { SLEEP_SEC (earliest - now); } /* * Woke up. See what needs to be done. */ now = time(NULL); #if DEBUG localtime_r (&now, &tm); strftime (hms, sizeof(hms), "%H:%M:%S", &tm); text_color_set(DW_COLOR_DEBUG); dw_printf ("beacon_thread: woke up %s\n", hms); #endif /* * Get information from GPS if being used. * This needs to be done before the next scheduled tracker * beacon because corner pegging make it sooner. */ if (number_of_tbeacons > 0) { dwfix_t fix = dwgps_read (&gpsinfo); float my_speed_mph = DW_KNOTS_TO_MPH(gpsinfo.speed_knots); if (g_tracker_debug_level >= 1) { struct tm tm; char hms[20]; localtime_r (&now, &tm); strftime (hms, sizeof(hms), "%H:%M:%S", &tm); text_color_set(DW_COLOR_DEBUG); if (fix == 3) { dw_printf ("%s 3D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0, %.1f m\n", hms, gpsinfo.dlat, gpsinfo.dlon, my_speed_mph, gpsinfo.track, gpsinfo.altitude); } else if (fix == 2) { dw_printf ("%s 2D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0\n", hms, gpsinfo.dlat, gpsinfo.dlon, my_speed_mph, gpsinfo.track); } else { dw_printf ("%s No GPS fix\n", hms); } } /* Don't complain here for no fix. */ /* Possibly at the point where about to transmit. */ /* * Run SmartBeaconing calculation if configured and GPS data available. */ if (g_misc_config_p->sb_configured && fix >= DWFIX_2D) { time_t tnext = sb_calculate_next_time (now, DW_KNOTS_TO_MPH(gpsinfo.speed_knots), gpsinfo.track, sb_prev_time, sb_prev_course); for (j=0; jnum_beacons; j++) { if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) { /* Haven't thought about the consequences of SmartBeaconing */ /* and having more than one tbeacon configured. */ if (tnext < g_misc_config_p->beacon[j].next) { g_misc_config_p->beacon[j].next = tnext; } } } /* Update next time if sooner. */ } /* apply SmartBeaconing */ } /* tbeacon(s) configured. */ /* * Send if the time has arrived. */ for (j=0; jnum_beacons; j++) { struct beacon_s *bp = & (g_misc_config_p->beacon[j]); if (bp->btype == BEACON_IGNORE) continue; if (bp->next <= now) { /* Send the beacon. */ beacon_send (j, &gpsinfo); /* Calculate when the next one should be sent. */ /* Easy for fixed interval. SmartBeaconing takes more effort. */ if (bp->btype == BEACON_TRACKER) { if (gpsinfo.fix < DWFIX_2D) { /* Fix not available so beacon was not sent. */ if (g_misc_config_p->sb_configured) { /* Try again in a couple seconds. */ bp->next = now + 2; } else { /* Stay with the schedule. */ /* Important for slotted. Might reconsider otherwise. */ bp->next += bp->every; } } else if (g_misc_config_p->sb_configured) { /* Remember most recent tracker beacon. */ /* Compute next time if not turning. */ sb_prev_time = now; sb_prev_course = gpsinfo.track; bp->next = sb_calculate_next_time (now, DW_KNOTS_TO_MPH(gpsinfo.speed_knots), gpsinfo.track, sb_prev_time, sb_prev_course); } else { /* Tracker beacon, fixed spacing. */ bp->next += bp->every; } } else { /* Non-tracker beacon, fixed spacing. */ /* Increment by 'every' so slotted times come out right. */ /* i.e. Don't take relative to now in case there was some delay. */ bp->next += bp->every; } } /* if time to send it */ } /* for each configured beacon */ } /* do forever */ #if __WIN32__ return(0); /* unreachable but warning if not here. */ #else return(NULL); #endif } /* end beacon_thread */ /*------------------------------------------------------------------- * * Name: sb_calculate_next_time * * Purpose: Calculate next transmission time using the SmartBeaconing algorithm. * * Inputs: now - Current time. * * current_speed_mph - Current speed from GPS. * Not expecting G_UNKNOWN but should check for it. * * current_course - Current direction of travel. * Could be G_UNKNOWN if stationary. * * last_xmit_time - Time of most recent transmission. * * last_xmit_course - Direction included in most recent transmission. * * Global In: g_misc_config_p-> * sb_configured TRUE if SmartBeaconing is configured. * sb_fast_speed MPH * sb_fast_rate seconds * sb_slow_speed MPH * sb_slow_rate seconds * sb_turn_time seconds * sb_turn_angle degrees * sb_turn_slope degrees * MPH * * Returns: Time of next transmission. * Could vary from now to sb_slow_rate in the future. * * Caution: The algorithm is defined in MPH units. GPS uses knots. * The caller must be careful about using the proper conversions. * *--------------------------------------------------------------------*/ /* Difference between two angles. */ static float heading_change (float a, float b) { float diff; diff = fabs(a - b); if (diff <= 180.) return (diff); else return (360. - diff); } static time_t sb_calculate_next_time (time_t now, float current_speed_mph, float current_course, time_t last_xmit_time, float last_xmit_course) { int beacon_rate; time_t next_time; /* * Compute time between beacons for travelling in a straight line. */ if (current_speed_mph == G_UNKNOWN) { beacon_rate = (int)roundf((g_misc_config_p->sb_fast_rate + g_misc_config_p->sb_slow_rate) / 2.); } else if (current_speed_mph > g_misc_config_p->sb_fast_speed) { beacon_rate = g_misc_config_p->sb_fast_rate; } else if (current_speed_mph < g_misc_config_p->sb_slow_speed) { beacon_rate = g_misc_config_p->sb_slow_rate; } else { /* Can't divide by 0 assuming sb_slow_speed > 0. */ beacon_rate = (int)roundf(( g_misc_config_p->sb_fast_rate * g_misc_config_p->sb_fast_speed ) / current_speed_mph); } if (g_tracker_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("SmartBeaconing: Beacon Rate = %d seconds for %.1f MPH\n", beacon_rate, current_speed_mph); } next_time = last_xmit_time + beacon_rate; /* * Test for "Corner Pegging" if moving. */ if (current_speed_mph != G_UNKNOWN && current_speed_mph >= 1.0 && current_course != G_UNKNOWN && last_xmit_course != G_UNKNOWN) { float change = heading_change(current_course, last_xmit_course); float turn_threshold = g_misc_config_p->sb_turn_angle + g_misc_config_p->sb_turn_slope / current_speed_mph; if (change > turn_threshold && now >= last_xmit_time + g_misc_config_p->sb_turn_time) { if (g_tracker_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("SmartBeaconing: Send now for heading change of %.0f\n", change); } next_time = now; } } return (next_time); } /* end sb_calculate_next_time */ /*------------------------------------------------------------------- * * Name: beacon_send * * Purpose: Transmit one beacon after it was determined to be time. * * Inputs: j Index into beacon configuration array below. * * gpsinfo Information from GPS. Used only for TBEACON. * * Global In: g_misc_config_p->beacon Array of beacon configurations. * * Outputs: Destination(s) specified: * - Transmit queue. * - IGate. * - Simulated reception. * * Description: Prepare text in monitor format. * Convert to packet object. * Send to desired destination(s). * *--------------------------------------------------------------------*/ static void beacon_send (int j, dwgps_info_t *gpsinfo) { struct beacon_s *bp = & (g_misc_config_p->beacon[j]); int strict = 1; /* Strict packet checking because they will go over air. */ char stemp[20]; char info[AX25_MAX_INFO_LEN]; char beacon_text[AX25_MAX_PACKET_LEN]; packet_t pp = NULL; char mycall[AX25_MAX_ADDR_LEN]; char super_comment[AX25_MAX_INFO_LEN]; // Fixed part + any dynamic part. /* * Obtain source call for the beacon. * This could potentially be different on different channels. * When sending to IGate server, use call from first radio channel. * * Check added in version 1.0a. Previously used index of -1. * * Version 1.1 - channel should now be 0 for IGate. * Type of destination is encoded separately. */ strlcpy (mycall, "NOCALL", sizeof(mycall)); assert (bp->sendto_chan >= 0); strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall)); if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("MYCALL not set for beacon in config file line %d.\n", bp->lineno); return; } /* * Prepare the monitor format header. * * src > dest [ , via ] */ strlcpy (beacon_text, mycall, sizeof(beacon_text)); strlcat (beacon_text, ">", sizeof(beacon_text)); if (bp->dest != NULL) { strlcat (beacon_text, bp->dest, sizeof(beacon_text)); } else { snprintf (stemp, sizeof(stemp), "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION); strlcat (beacon_text, stemp, sizeof(beacon_text)); } if (bp->via != NULL) { strlcat (beacon_text, ",", sizeof(beacon_text)); strlcat (beacon_text, bp->via, sizeof(beacon_text)); } strlcat (beacon_text, ":", sizeof(beacon_text)); /* * If the COMMENTCMD option was specified, run specified command to get variable part. * Result is any fixed part followed by any variable part. */ // TODO: test & document. strlcpy (super_comment, "", sizeof(super_comment)); if (bp->comment != NULL) { strlcpy (super_comment, bp->comment, sizeof(super_comment)); } if (bp->commentcmd != NULL) { char var_comment[AX25_MAX_INFO_LEN]; int k; /* Run given command to get variable part of comment. */ k = dw_run_cmd (bp->commentcmd, 2, var_comment, sizeof(var_comment)); if (k > 0) { strlcat (super_comment, var_comment, sizeof(super_comment)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("xBEACON, config file line %d, COMMENTCMD failure.\n", bp->lineno); } } /* * Add the info part depending on beacon type. */ switch (bp->btype) { case BEACON_POSITION: encode_position (bp->messaging, bp->compress, bp->lat, bp->lon, bp->ambiguity, (int)roundf(DW_METERS_TO_FEET(bp->alt_m)), bp->symtab, bp->symbol, bp->power, bp->height, bp->gain, bp->dir, G_UNKNOWN, G_UNKNOWN, /* course, speed */ bp->freq, bp->tone, bp->offset, super_comment, info, sizeof(info)); strlcat (beacon_text, info, sizeof(beacon_text)); break; case BEACON_OBJECT: encode_object (bp->objname, bp->compress, 0, bp->lat, bp->lon, bp->ambiguity, bp->symtab, bp->symbol, bp->power, bp->height, bp->gain, bp->dir, G_UNKNOWN, G_UNKNOWN, /* course, speed */ bp->freq, bp->tone, bp->offset, super_comment, info, sizeof(info)); strlcat (beacon_text, info, sizeof(beacon_text)); break; case BEACON_TRACKER: if (gpsinfo->fix >= DWFIX_2D) { int coarse; /* Round to nearest integer. retaining unknown state. */ int my_alt_ft; /* Transmit altitude only if user asked for it. */ /* A positive altitude in the config file enables */ /* transmission of altitude from GPS. */ my_alt_ft = G_UNKNOWN; if (gpsinfo->fix >= 3 && gpsinfo->altitude != G_UNKNOWN && bp->alt_m > 0) { my_alt_ft = (int)roundf(DW_METERS_TO_FEET(gpsinfo->altitude)); } coarse = G_UNKNOWN; if (gpsinfo->track != G_UNKNOWN) { coarse = (int)roundf(gpsinfo->track); } encode_position (bp->messaging, bp->compress, gpsinfo->dlat, gpsinfo->dlon, bp->ambiguity, my_alt_ft, bp->symtab, bp->symbol, bp->power, bp->height, bp->gain, bp->dir, coarse, (int)roundf(gpsinfo->speed_knots), bp->freq, bp->tone, bp->offset, super_comment, info, sizeof(info)); strlcat (beacon_text, info, sizeof(beacon_text)); /* Write to log file for testing. */ /* The idea is to run log2gpx and map the result rather than */ /* actually transmitting and relying on someone else to receive */ /* the signals. */ if (g_tracker_debug_level >= 3) { decode_aprs_t A; alevel_t alevel; memset (&A, 0, sizeof(A)); A.g_freq = G_UNKNOWN; A.g_offset = G_UNKNOWN; A.g_tone = G_UNKNOWN; A.g_dcs = G_UNKNOWN; strlcpy (A.g_src, mycall, sizeof(A.g_src)); A.g_symbol_table = bp->symtab; A.g_symbol_code = bp->symbol; A.g_lat = gpsinfo->dlat; A.g_lon = gpsinfo->dlon; A.g_speed_mph = DW_KNOTS_TO_MPH(gpsinfo->speed_knots); A.g_course = coarse; A.g_altitude_ft = DW_METERS_TO_FEET(gpsinfo->altitude); /* Fake channel of 999 to distinguish from real data. */ memset (&alevel, 0, sizeof(alevel)); log_write (999, &A, NULL, alevel, 0); } } else { return; /* No fix. Skip this time. */ } break; case BEACON_CUSTOM: if (bp->custom_info != NULL) { /* Fixed handcrafted text. */ strlcat (beacon_text, bp->custom_info, sizeof(beacon_text)); } else if (bp->custom_infocmd != NULL) { char info_part[AX25_MAX_INFO_LEN]; int k; /* Run given command to obtain the info part for packet. */ k = dw_run_cmd (bp->custom_infocmd, 2, info_part, sizeof(info_part)); if (k > 0) { strlcat (beacon_text, info_part, sizeof(beacon_text)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("CBEACON, config file line %d, INFOCMD failure.\n", bp->lineno); strlcpy (beacon_text, "", sizeof(beacon_text)); // abort! } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error. custom_info is null. %s %d\n", __FILE__, __LINE__); strlcpy (beacon_text, "", sizeof(beacon_text)); // abort! } break; case BEACON_IGATE: { int last_minutes = 30; char stuff[256]; snprintf (stuff, sizeof(stuff), "max_digi_hops,last_minutes), mheard_count(8,last_minutes), igate_get_upl_cnt(), igate_get_dnl_cnt()); strlcat (beacon_text, stuff, sizeof(beacon_text)); } break; case BEACON_IGNORE: default: break; } /* switch beacon type. */ /* * Parse monitor format into form for transmission. */ if (strlen(beacon_text) == 0) { return; } pp = ax25_from_text (beacon_text, strict); if (pp != NULL) { /* Send to desired destination. */ alevel_t alevel; switch (bp->sendto_type) { case SENDTO_IGATE: text_color_set(DW_COLOR_XMIT); dw_printf ("[ig] %s\n", beacon_text); igate_send_rec_packet (0, pp); ax25_delete (pp); break; case SENDTO_XMIT: default: tq_append (bp->sendto_chan, TQ_PRIO_1_LO, pp); break; case SENDTO_RECV: /* Simulated reception from radio. */ memset (&alevel, 0xff, sizeof(alevel)); dlq_rec_frame (bp->sendto_chan, 0, 0, pp, alevel, 0, ""); break; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Failed to parse packet constructed from line %d.\n", bp->lineno); dw_printf ("%s\n", beacon_text); } } /* end beacon_send */ /* end beacon.c */ direwolf-1.5+dfsg/beacon.h000066400000000000000000000002461347750676600155400ustar00rootroot00000000000000 /* beacon.h */ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate); void beacon_tracker_set_debug (int level); direwolf-1.5+dfsg/cdigipeater.c000066400000000000000000000232351347750676600165670ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2016, 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Name: cdigipeater.c * * Purpose: Act as an digital repeater for connected AX.25 mode. * Similar digipeater.c is for APRS. * * * Description: Decide whether the specified packet should * be digipeated. Put my callsign in the digipeater field used. * * APRS and connected mode were two split into two * separate files. Yes, there is duplicate code but they * are significantly different and I thought it would be * too confusing to munge them together. * * References: The Ax.25 protcol barely mentions digipeaters and * and doesn't describe how they should work. * *------------------------------------------------------------------*/ #define CDIGIPEATER_C #include "direwolf.h" #include #include #include #include #include /* for isdigit, isupper */ #include "regex.h" #include #include "ax25_pad.h" #include "cdigipeater.h" #include "textcolor.h" #include "tq.h" #include "pfilter.h" static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, int has_alias, regex_t *alias, int to_chan, char *cfilter_str); /* * Keep pointer to configuration options. * Set by cdigipeater_init and used later. */ static struct audio_s *save_audio_config_p; static struct cdigi_config_s *save_cdigi_config_p; /* * Maintain count of packets digipeated for each combination of from/to channel. */ static int cdigi_count[MAX_CHANS][MAX_CHANS]; int cdigipeater_get_count (int from_chan, int to_chan) { return (cdigi_count[from_chan][to_chan]); } /*------------------------------------------------------------------------------ * * Name: cdigipeater_init * * Purpose: Initialize with stuff from configuration file. * * Inputs: p_audio_config - Configuration for audio channels. * * p_cdigi_config - Connected Digipeater configuration details. * * Outputs: Save pointers to configuration for later use. * * Description: Called once at application startup time. * *------------------------------------------------------------------------------*/ void cdigipeater_init (struct audio_s *p_audio_config, struct cdigi_config_s *p_cdigi_config) { save_audio_config_p = p_audio_config; save_cdigi_config_p = p_cdigi_config; } /*------------------------------------------------------------------------------ * * Name: cdigipeater * * Purpose: Re-transmit packet if it matches the rules. * * Inputs: chan - Radio channel where it was received. * * pp - Packet object. * * Returns: None. * *------------------------------------------------------------------------------*/ void cdigipeater (int from_chan, packet_t pp) { int to_chan; if ( from_chan < 0 || from_chan >= MAX_CHANS || ( ! save_audio_config_p->achan[from_chan].valid) ) { text_color_set(DW_COLOR_ERROR); dw_printf ("cdigipeater: Did not expect to receive on invalid channel %d.\n", from_chan); return; } /* * First pass: Look at packets being digipeated to same channel. * * There was a reason for two passes for APRS. * Might not have a benefit here. */ for (to_chan=0; to_chanenabled[from_chan][to_chan]) { if (to_chan == from_chan) { packet_t result; result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, save_audio_config_p->achan[to_chan].mycall, save_cdigi_config_p->has_alias[from_chan][to_chan], &(save_cdigi_config_p->alias[from_chan][to_chan]), to_chan, save_cdigi_config_p->cfilter_str[from_chan][to_chan]); if (result != NULL) { tq_append (to_chan, TQ_PRIO_0_HI, result); cdigi_count[from_chan][to_chan]++; } } } } /* * Second pass: Look at packets being digipeated to different channel. */ for (to_chan=0; to_chanenabled[from_chan][to_chan]) { if (to_chan != from_chan) { packet_t result; result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, save_audio_config_p->achan[to_chan].mycall, save_cdigi_config_p->has_alias[from_chan][to_chan], &(save_cdigi_config_p->alias[from_chan][to_chan]), to_chan, save_cdigi_config_p->cfilter_str[from_chan][to_chan]); if (result != NULL) { tq_append (to_chan, TQ_PRIO_0_HI, result); cdigi_count[from_chan][to_chan]++; } } } } } /* end cdigipeater */ /*------------------------------------------------------------------------------ * * Name: cdigipeat_match * * Purpose: A simple digipeater for connected mode AX.25. * * Input: pp - Pointer to a packet object. * * mycall_rec - Call of my station, with optional SSID, * associated with the radio channel where the * packet was received. * * mycall_xmit - Call of my station, with optional SSID, * associated with the radio channel where the * packet is to be transmitted. Could be the same as * mycall_rec or different. * * has_alias - True if we have an alias. * * alias - Optional compiled pattern for my station aliases. * Do NOT attempt to use this if 'has_alias' is false. * * to_chan - Channel number that we are transmitting to. * * cfilter_str - Filter expression string for the from/to channel pair or NULL. * Note that only a subset of the APRS filters are applicable here. * * Returns: Packet object for transmission or NULL. * The original packet is not modified. The caller is responsible for freeing it. * We make a copy and return that modified copy! * This is very important because we could digipeat from one channel to many. * * Description: The packet will be digipeated if the next unused digipeater * field matches one of the following: * * - mycall_rec * - alias list * * APRS digipeating drops duplicates within 30 seconds but we don't do that here. * *------------------------------------------------------------------------------*/ static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, int has_alias, regex_t *alias, int to_chan, char *cfilter_str) { int r; char repeater[AX25_MAX_ADDR_LEN]; int err; char err_msg[100]; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("cdigipeat_match (from_chan=%d, pp=%p, mycall_rec=%s, mycall_xmit=%s, has_alias=%d, alias=%p, to_chan=%d, cfilter_str=%s\n", from_chan, pp, mycall_rec, mycall_xmit, has_alias, alias, to_chan, cfilter_str); #endif /* * First check if filtering has been configured. * Note that we have three different config file filter commands: * * FILTER - APRS digipeating and IGate client side. * Originally this was the only one. * Should we change it to AFILTER to make it clearer? * CFILTER - Similar for connected moded digipeater. * IGFILTER - APRS-IS (IGate) server side - completely diffeent. * Confusing with similar name but much different idea. * Maybe this should be renamed to SUBSCRIBE or something like that. * * Logically this should come later, after an address/alias match. * But here we only have to do it once. */ if (cfilter_str != NULL) { if (pfilter(from_chan, to_chan, cfilter_str, pp, 0) != 1) { return(NULL); } } /* * Find the first repeater station which doesn't have "has been repeated" set. * * r = index of the address position in the frame. */ r = ax25_get_first_not_repeated(pp); if (r < AX25_REPEATER_1) { return (NULL); // Nothing to do. } ax25_get_addr_with_ssid(pp, r, repeater); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("First unused digipeater is %s\n", repeater); #endif /* * First check for explicit use of my call. * Note that receive and transmit channels could have different callsigns. */ if (strcmp(repeater, mycall_rec) == 0) { packet_t result; result = ax25_dup (pp); assert (result != NULL); /* If using multiple radio channels, they could have different calls. */ ax25_set_addr (result, r, mycall_xmit); ax25_set_h (result, r); return (result); } /* * If we have an alias match, substitute MYCALL. */ if (has_alias) { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("Checking %s for alias match.\n", repeater); #endif err = regexec(alias,repeater,0,NULL,0); if (err == 0) { packet_t result; result = ax25_dup (pp); assert (result != NULL); ax25_set_addr (result, r, mycall_xmit); ax25_set_h (result, r); return (result); } else if (err != REG_NOMATCH) { regerror(err, alias, err_msg, sizeof(err_msg)); text_color_set (DW_COLOR_ERROR); dw_printf ("%s\n", err_msg); } } else { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("No alias was specified.\n"); #endif } /* * Don't repeat it if we get here. */ return (NULL); } /* end cdigipeat_match */ /* end cdigipeater.c */ direwolf-1.5+dfsg/cdigipeater.h000066400000000000000000000026711347750676600165750ustar00rootroot00000000000000 #ifndef CDIGIPEATER_H #define CDIGIPEATER_H 1 #include "regex.h" #include "direwolf.h" /* for MAX_CHANS */ #include "ax25_pad.h" /* for packet_t */ #include "audio.h" /* for radio channel properties */ /* * Information required for Connected mode digipeating. * * The configuration file reader fills in this information * and it is passed to cdigipeater_init at application start up time. */ struct cdigi_config_s { /* * Rules for each of the [from_chan][to_chan] combinations. */ int enabled[MAX_CHANS][MAX_CHANS]; // Is it enabled for from/to pair? int has_alias[MAX_CHANS][MAX_CHANS]; // If there was no alias in the config file, // the structure below will not be set up // properly and an attempt to use it could // result in a crash. (fixed v1.5) // Not needed for [APRS] DIGIPEAT because // the alias is mandatory there. regex_t alias[MAX_CHANS][MAX_CHANS]; char *cfilter_str[MAX_CHANS][MAX_CHANS]; // NULL or optional Packet Filter strings such as "t/m". }; /* * Call once at application start up time. */ extern void cdigipeater_init (struct audio_s *p_audio_config, struct cdigi_config_s *p_cdigi_config); /* * Call this for each packet received. * Suitable packets will be queued for transmission. */ extern void cdigipeater (int from_chan, packet_t pp); /* Make statistics available. */ int cdigipeater_get_count (int from_chan, int to_chan); #endif /* end cdigipeater.h */ direwolf-1.5+dfsg/cm108.c000066400000000000000000000556261347750676600151500ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2017 John Langner, WB2OSZ // // Parts of this were adapted from "hamlib" which contains the notice: // // * Copyright (c) 2000-2012 by Stephane Fillod // * Copyright (c) 2011 by Andrew Errington // * CM108 detection code Copyright (c) Thomas Sailer used with permission // // 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, see . /*------------------------------------------------------------------ * * Module: cm108.c * * Purpose: Use the CM108/CM119 (or compatible) GPIO pins for the Push To Talk (PTT) Control. * * Description: * * There is an incresing demand for using the GPIO pins of USB audio devices for PTT. * We have a couple commercial products: * * DMK URI http://www.dmkeng.com/URI_Order_Page.htm * RB-USB RIM http://www.repeater-builder.com/products/usb-rim-lite.html * * and homebrew projects which are all very similar. * * http://www.qsl.net/kb9mwr/projects/voip/usbfob-119.pdf * http://rtpdir.weebly.com/uploads/1/6/8/7/1687703/usbfob.pdf * http://www.repeater-builder.com/projects/fob/USB-Fob-Construction.pdf * https://irongarment.wordpress.com/2011/03/29/cm108-compatible-chips-with-gpio/ * * Usually GPIO 3 is used because it is easier to tack solder a wire to a pin on the end. * * Soundmodem and hamlib paved the way but didn't get too far. * Dire Wolf 1.3 added HAMLIB support (Linux only) which theoretically allows this in a * roundabout way. This is documented in the User Guide, section called, * "Hamlib PTT Example 2: Use GPIO of USB audio adapter. (e.g. DMK URI)" * * It's rather involved and the explantion doesn't cover the case of multiple * USB-Audio adapters. It is not as straightforward as you might expect. Here we have * an example of 3 C-Media USB adapters, a SignaLink USB, a keyboard, and a mouse. * * * VID PID Product Sound ADEVICE HID [ptt] * --- --- ------- ----- ------- --------- * ** 0d8c 000c C-Media USB Headphone Set /dev/snd/pcmC1D0c plughw:1,0 /dev/hidraw0 * ** 0d8c 000c C-Media USB Headphone Set /dev/snd/pcmC1D0p plughw:1,0 /dev/hidraw0 * ** 0d8c 000c C-Media USB Headphone Set /dev/snd/controlC1 /dev/hidraw0 * 08bb 2904 USB Audio CODEC /dev/snd/pcmC2D0c plughw:2,0 /dev/hidraw2 * 08bb 2904 USB Audio CODEC /dev/snd/pcmC2D0p plughw:2,0 /dev/hidraw2 * 08bb 2904 USB Audio CODEC /dev/snd/controlC2 /dev/hidraw2 * ** 0d8c 000c C-Media USB Headphone Set /dev/snd/pcmC0D0c plughw:0,0 /dev/hidraw1 * ** 0d8c 000c C-Media USB Headphone Set /dev/snd/pcmC0D0p plughw:0,0 /dev/hidraw1 * ** 0d8c 000c C-Media USB Headphone Set /dev/snd/controlC0 /dev/hidraw1 * ** 0d8c 0008 C-Media USB Audio Device /dev/snd/pcmC4D0c plughw:4,0 /dev/hidraw6 * ** 0d8c 0008 C-Media USB Audio Device /dev/snd/pcmC4D0p plughw:4,0 /dev/hidraw6 * ** 0d8c 0008 C-Media USB Audio Device /dev/snd/controlC4 /dev/hidraw6 * 413c 2010 Dell USB Keyboard /dev/hidraw4 * 0461 4d15 USB Optical Mouse /dev/hidraw5 * * * The USB soundcards (/dev/snd/pcm...) have an associated Human Interface Device (HID) * corresponding to the GPIO pins which are sometimes connected to pushbuttons. * The mapping has no obvious pattern. * * Sound Card 0 HID 1 * Sound Card 1 HID 0 * Sound Card 2 HID 2 * Sound Card 4 HID 6 * * That would be a real challenge if you had to figure that all out and configure manually. * Dire Wolf version 1.5 makes this much more flexible and easier to use by supporting multiple * sound devices and automatically determining the corresponding HID for the PTT signal. * *---------------------------------------------------------------*/ #ifndef USE_CM108 #ifdef CM108_MAIN #include "direwolf.h" #include "textcolor.h" int main (void) { text_color_init (0); // Turn off text color. dw_printf ("CM108 PTT support was disabled in Makefile.linux.\n"); dw_printf ("It was excluded because /usr/include/libudev.h was missing.\n"); dw_printf ("Install it with \"sudo apt-get install libudev-dev\" or\n"); dw_printf ("\"sudo yum install libudev-devel\" then rebuild.\n"); return (0); } #endif #else // USE_CM108 is defined. #include "direwolf.h" #include #include #include #include #include #include #include #include #include #include // ioctl, _IOR #include #include #include // for HIDIOCGRAWINFO #include "textcolor.h" #include "cm108.h" static int cm108_write (char *name, int iomask, int iodata); // The CM108, CM109, and CM119 datasheets all say that idProduct can be in the range // of 0008 to 000f programmable by MSEL and MODE pin. How can we tell the difference? // CM108B is 0012. // CM119B is 0013. // CM108AH is 0139 programmable by MSEL and MODE pin. // CM119A is 013A programmable by MSEL and MODE pin. // To make matters even more confusing, these can be overridden // with an external EEPROM. Some have 8, rather than 4 GPIO. #define CMEDIA_VID 0xd8c // Vendor ID #define CMEDIA_PID1_MIN 0x0008 // range for CM108, CM109, CM119 (no following letters) #define CMEDIA_PID1_MAX 0x000f #define CMEDIA_PID_CM108AH 0x0139 // CM108AH #define CMEDIA_PID_CM108B 0x0012 // CM108B #define CMEDIA_PID_CM119A 0x013a // CM119A #define CMEDIA_PID_CM119B 0x0013 // CM119B #define CMEDIA_PID_HS100 0x013c // HS100 // The SSS chips seem to be pretty much compatible but they have only two GPIO. // https://irongarment.wordpress.com/2011/03/29/cm108-compatible-chips-with-gpio/ // Data sheet says VID/PID is from an EEPROM but mentions no default. #define SSS_VID 0x0c76 // SSS1621, SSS1623 #define SSS_PID1 0x1605 #define SSS_PID2 0x1607 #define SSS_PID3 0x160b // Device VID PID Number of GPIO // ------ --- --- -------------- // CM108 0d8c 0008-000f * 4 // CM108AH 0d8c 0139 * 3 Has GPIO 1,3,4 but not 2 // CM108B 0d8c 0012 3 Has GPIO 1,3,4 but not 2 // CM109 0d8c 0008-000f * 8 // CM119 0d8c 0008-000f * 8 // CM119A 0d8c 013a * 8 // CM119B 0d8c 0013 8 // HS100 0d8c 013c 0 // // SSS1621 0c76 1605 2 per ZL3AME, Can't find data sheet // SSS1623 0c76 1607,160b 2 per ZL3AME, Not in data sheet. // // * idProduct programmable by MSEL and MODE pin. // // CMedia pin GPIO Notes // ---------- ---- ----- // 43 1 // 11 2 N.C. for CM108AH, CM108B // 13 3 Most popular for PTT because it is on the end. // 15 4 // 16 5 CM109, CM119, CM119A, CM119B only // 17 6 " // 20 7 " // 22 8 " // Test for supported devices. #define GOOD_DEVICE(v,p) ( (v == CMEDIA_VID && ((p >= CMEDIA_PID1_MIN && p <= CMEDIA_PID1_MAX) \ || p == CMEDIA_PID_CM108AH \ || p == CMEDIA_PID_CM108B \ || p == CMEDIA_PID_CM119A \ || p == CMEDIA_PID_CM119B )) \ || \ (v == SSS_VID && (p == SSS_PID1 || p == SSS_PID2 || p == SSS_PID3)) ) // Look out for null source pointer, and avoid buffer overflow on destination. #define SAFE_STRCPY(to,from) { if (from != NULL) { strncpy(to,from,sizeof(to)); to[sizeof(to)-1] = '\0'; } } // Used to process regular expression matching results. static void inline substr_se (char *dest, const char *src, int start, int endp1) { int len = endp1 - start; if (start < 0 || endp1 < 0 || len <= 0) { dest[0] = '\0'; return; } memcpy (dest, src + start, len); dest[len] = '\0'; } /* end substr_se */ /* * Result of taking inventory of USB soundcards and USB HIDs. */ struct thing_s { int vid; // vendor id int pid; // product id char product[32]; // product name (e.g. manufacturer, model) char devnode_sound[22]; // e.g. /dev/snd/pcmC0D0p char plughw[15]; // Above in more familiar format e.g. plughw:0,0 char devnode_hidraw[17]; // e.g. /dev/hidraw3 char devnode_usb[25]; // e.g. /dev/bus/usb/001/012 }; int cm108_inventory (struct thing_s *things, int max_things); /*------------------------------------------------------------------- * * Name: main * * Purpose: Test program to list USB audio and HID devices. * * sudo apt-get install libudev-dev * gcc -DCM108_MAIN textcolor.c -l udev * *------------------------------------------------------------------*/ #define MAXX_THINGS 60 #ifdef CM108_MAIN int main (void) { struct thing_s things[MAXX_THINGS]; int num_things; int i; text_color_init (0); // Turn off text color. num_things = cm108_inventory (things, MAXX_THINGS); dw_printf (" VID PID %-*s %-*s %-*s %-*s" #if EXTRA " %-*s" #endif "\n", (int)sizeof(things[0].product), "Product", (int)sizeof(things[0].devnode_sound), "Sound", (int)sizeof(things[0].plughw), "ADEVICE", (int)sizeof(things[0].devnode_hidraw), "HID [ptt]" #if EXTRA , (int)sizeof(things[0].devnode_usb), "USB" #endif ); dw_printf (" --- --- %-*s %-*s %-*s %-*s" #if EXTRA " %-*s" #endif "\n", (int)sizeof(things[0].product), "-------", (int)sizeof(things[0].devnode_sound), "-----", (int)sizeof(things[0].plughw), "-------", (int)sizeof(things[0].devnode_hidraw), "---------" #if EXTRA , (int)sizeof(things[0].devnode_usb), "---" #endif ); for (i = 0; i < num_things; i++) { dw_printf ("%2s %04x %04x %-*s %-*s %-*s %-*s" #if EXTRA " %-*s" #endif "\n", GOOD_DEVICE(things[i].vid,things[i].pid) ? "**" : " ", things[i].vid, things[i].pid, (int)sizeof(things[i].product), things[i].product, (int)sizeof(things[i].devnode_sound), things[i].devnode_sound, (int)sizeof(things[0].plughw), things[i].plughw, (int)sizeof(things[i].devnode_hidraw), things[i].devnode_hidraw #if EXTRA , (int)sizeof(things[i].devnode_usb), things[i].devnode_usb #endif ); } return (0); } #endif // CM108_MAIN /*------------------------------------------------------------------- * * Name: cm108_inventory * * Purpose: Take inventory of USB audio and HID. * * Inputs: max_things - Maximum number of items to collect. * * Outputs: things - Array of items collected. * Corresponding sound device and HID are merged into one item. * * Returns: Number of items placed in things array. * Should be in the range of 0 thru max_things. * -1 for a bad unexpected error. * *------------------------------------------------------------------*/ int cm108_inventory (struct thing_s *things, int max_things) { struct udev *udev; struct udev_enumerate *enumerate; struct udev_list_entry *devices, *dev_list_entry; struct udev_device *dev; struct udev_device *parentdev; int num_things = 0; memset (things, 0, sizeof(struct thing_s) * max_things); /* * First get a list of the USB audio devices. * This is based on the example in http://www.signal11.us/oss/udev/ */ udev = udev_new(); if (!udev) { text_color_set(DW_COLOR_ERROR); dw_printf("INTERNAL ERROR: Can't create udev.\n"); return (-1); } enumerate = udev_enumerate_new(udev); udev_enumerate_add_match_subsystem(enumerate, "sound"); udev_enumerate_scan_devices(enumerate); devices = udev_enumerate_get_list_entry(enumerate); udev_list_entry_foreach(dev_list_entry, devices) { const char *path; path = udev_list_entry_get_name(dev_list_entry); dev = udev_device_new_from_syspath(udev, path); char const *devnode = udev_device_get_devnode(dev); if (devnode != NULL) { parentdev = udev_device_get_parent_with_subsystem_devtype( dev, "usb", "usb_device"); if (parentdev != NULL) { char const *p; int vid = 0; int pid = 0; p = udev_device_get_sysattr_value(parentdev,"idVendor"); if (p != NULL) vid = strtol(p, NULL, 16); p = udev_device_get_sysattr_value(parentdev,"idProduct"); if (p != NULL) pid = strtol(p, NULL, 16); if (num_things < max_things) { things[num_things].vid = vid; things[num_things].pid = pid; SAFE_STRCPY (things[num_things].product, udev_device_get_sysattr_value(parentdev,"product")); SAFE_STRCPY (things[num_things].devnode_sound, devnode); SAFE_STRCPY (things[num_things].devnode_usb, udev_device_get_devnode(parentdev)); num_things++; } udev_device_unref(parentdev); } } } udev_enumerate_unref(enumerate); udev_unref(udev); /* * Now merge in all of the USB HID. */ udev = udev_new(); if (!udev) { text_color_set(DW_COLOR_ERROR); dw_printf("INTERNAL ERROR: Can't create udev.\n"); return (-1); } enumerate = udev_enumerate_new(udev); udev_enumerate_add_match_subsystem(enumerate, "hidraw"); udev_enumerate_scan_devices(enumerate); devices = udev_enumerate_get_list_entry(enumerate); udev_list_entry_foreach(dev_list_entry, devices) { const char *path; path = udev_list_entry_get_name(dev_list_entry); dev = udev_device_new_from_syspath(udev, path); char const *devnode = udev_device_get_devnode(dev); if (devnode != NULL) { parentdev = udev_device_get_parent_with_subsystem_devtype( dev, "usb", "usb_device"); if (parentdev != NULL) { char const *p; int vid = 0; int pid = 0; p = udev_device_get_sysattr_value(parentdev,"idVendor"); if (p != NULL) vid = strtol(p, NULL, 16); p = udev_device_get_sysattr_value(parentdev,"idProduct"); if (p != NULL) pid = strtol(p, NULL, 16); int j, matched = 0; char const *usb = udev_device_get_devnode(parentdev); // Add hidraw name to any matching existing. for (j = 0; j < num_things; j++) { if (things[j].vid == vid && things[j].pid == pid && usb != NULL && strcmp(things[j].devnode_usb,usb) == 0) { matched = 1; SAFE_STRCPY (things[j].devnode_hidraw, devnode); } } // If it did not match to existing, add new entry. if (matched == 0 && num_things < max_things) { things[num_things].vid = vid; things[num_things].pid = pid; SAFE_STRCPY (things[num_things].product, udev_device_get_sysattr_value(parentdev,"product")); SAFE_STRCPY (things[num_things].devnode_hidraw, devnode); SAFE_STRCPY (things[num_things].devnode_usb, usb); num_things++; } udev_device_unref(parentdev); } } } udev_enumerate_unref(enumerate); udev_unref(udev); /* * Seeing the form /dev/snd/pcmC4D0p will be confusing to many because we * would generally something like plughw:4,0 for in the direwolf configuration file. * Construct the more familiar form. */ int i; regex_t pcm_re; char emsg[100]; int e = regcomp (&pcm_re, "pcmC([0-9]+)D([0-9]+)[cp]", REG_EXTENDED); if (e) { regerror (e, &pcm_re, emsg, sizeof(emsg)); text_color_set(DW_COLOR_ERROR); dw_printf("INTERNAL ERROR: %s:%d: %s\n", __FILE__, __LINE__, emsg); return (-1); } for (i = 0; i < num_things; i++) { regmatch_t match[3]; if (regexec (&pcm_re, things[i].devnode_sound, 3, match, 0) == 0) { char c[32], d[32]; substr_se (c, things[i].devnode_sound, match[1].rm_so, match[1].rm_eo); substr_se (d, things[i].devnode_sound, match[2].rm_so, match[2].rm_eo); snprintf (things[i].plughw, sizeof(things[i].plughw), "plughw:%s,%s", c, d); } } return (num_things); } /* end cm108_inventory */ /*------------------------------------------------------------------- * * Name: cm108_find_ptt * * Purpose: Try to find /dev/hidraw corresponding to plughw:n,n * * Inputs: output_audio_device * - Device name used in the ADEVICE configuration. * This would generally be something like plughw:1,0 * * ptt_device_size - Size of result area to avoid buffer overflow. * * Outputs: ptt_device - Device name, something like /dev/hidraw2. * Will be emptry string if no match found. * * Returns: none * *------------------------------------------------------------------*/ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_device_size) { struct thing_s things[MAXX_THINGS]; int num_things; int i; strlcpy (ptt_device, "", ptt_device_size); num_things = cm108_inventory (things, MAXX_THINGS); for (i = 0; i < num_things; i++) { if (GOOD_DEVICE(things[i].vid,things[i].pid) ) { if (strcmp(output_audio_device, things[i].plughw) == 0) { strlcpy (ptt_device, things[i].devnode_hidraw, ptt_device_size); } } } } /* end cm108_find_ptt */ /*------------------------------------------------------------------- * * Name: cm108_set_gpio_pin * * Purpose: Set one GPIO pin of the CM108 or similar. * * Inputs: name - Name of device such as /dev/hidraw2. * * num - GPIO number, range 1 thru 8. * * state - 1 for on, 0 for off. * * Returns: 0 for success. -1 for error. * * Errors: A descriptive error message will be printed for any problem. * * Future: For our initial implementation we are making the simplifying * restriction of using only one GPIO pin per device and limit * configuratin to PTT only. * Longer term, we might want to have DCD, and maybe other * controls thru the same chip. * In this case, we would need to retain bit masks for each * device so new data can be merged with old before sending it out. * *------------------------------------------------------------------*/ int cm108_set_gpio_pin (char *name, int num, int state) { int iomask; int iodata; if (num < 1 || num > 8) { text_color_set(DW_COLOR_ERROR); dw_printf("%s CM108 GPIO number %d must be in range of 1 thru 8.\n", name, num); return (-1); } if (state != 0 && state != 1) { text_color_set(DW_COLOR_ERROR); dw_printf("%s CM108 GPIO state %d must be 0 or 1.\n", name, state); return (-1); } iomask = 1 << (num - 1); iodata = state << (num - 1); return (cm108_write (name, iomask, iodata)); } /* end cm108_set_gpio_pin */ /*------------------------------------------------------------------- * * Name: cm108_write * * Purpose: Set the GPIO pins of the CM108 or similar. * * Inputs: name - Name of device such as /dev/hidraw2. * * iomask - Bit mask for I/O direction. * LSB is GPIO1, bit 1 is GPIO2, etc. * 1 for output, 0 for input. * * iodata - Output data, same bit order as iomask. * * Returns: 0 for success. -1 for error. * * Errors: A descriptive error message will be printed for any problem. * * Description: This is the lowest level function. * An application probably wants to use cm108_set_gpio_pin. * *------------------------------------------------------------------*/ static int cm108_write (char *name, int iomask, int iodata) { int fd; struct hidraw_devinfo info; char io[5]; int n; //text_color_set(DW_COLOR_DEBUG); //dw_printf ("TEMP DEBUG cm108_write: %s %d %d\n", name, iomask, iodata); /* * By default, the USB HID are accessible only by root: * * crw------- 1 root root 249, 1 ... /dev/hidraw1 * * How should we handle this? * Manually changing it will revert back on the next reboot or * when the device is removed and reinserted. * * According to various articles on the Internet, we should be able to * add a file to /etc/udev/rules.d. "99-direwolf-cmedia.rules" would be a * suitable name. The leading number is the order. We want this to be * near the end. I think the file extension must be ".rules." * * We could completely open it up to everyone like this: * * # Allow ordinary user to access CMedia GPIO for PTT. * SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", MODE="0666" * * Whenever we have CMedia USB audio adapter, it should be accessible by everyone. * This would not apply to other /dev/hidraw* corresponding to keyboard, mouse, etc. * * Notice the == (double =) for testing and := for setting a property. * * If you are concerned about security, you could restrict access to * a particular group, something like this: * * SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", GROUP="audio", MODE="0660" * * I figure "audio" makes more sense than "gpio" because we need to be part of * audio group to use the USB Audio adapter for sound. */ fd = open (name, O_WRONLY); if (fd == -1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not open %s for write, errno=%d\n", name, errno); if (errno == EACCES) { // 13 dw_printf ("Type \"ls -l %s\" and verify that it has audio group rw similar to this:\n", name); dw_printf (" crw-rw---- 1 root audio 247, 0 Oct 6 19:24 %s\n", name); dw_printf ("rather than root-only access like this:\n"); dw_printf (" crw------- 1 root root 247, 0 Sep 24 09:40 %s\n", name); } return (-1); } // Just for fun, let's get the device information. #if 1 n = ioctl(fd, HIDIOCGRAWINFO, &info); if (n == 0) { if ( ! GOOD_DEVICE(info.vendor, info.product)) { text_color_set(DW_COLOR_ERROR); dw_printf ("ioctl HIDIOCGRAWINFO failed for %s. errno = %d.\n", name, errno); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("%s is not a supported device type. Proceed at your own risk. vid=%04x pid=%04x\n", name, info.vendor, info.product); } #endif // To make a long story short, I think we need 0 for the first two bytes. io[0] = 0; io[1] = 0; io[2] = iomask; io[3] = iodata; io[4] = 0; // Writing 4 bytes fails with errno 32, EPIPE, "broken pipe." // Hamlib writes 5 bytes which I don't understand. // Writing 5 bytes works. // I have no idea why. From the CMedia datasheet it looks like we need 4. n = write (fd, io, sizeof(io)); if (n != sizeof(io)) { // Errors observed during development. // as pi EACCES 13 /* Permission denied */ // as root EPIPE 32 /* Broken pipe - Happens if we send 4 bytes */ text_color_set(DW_COLOR_ERROR); dw_printf ("Write to %s failed, n=%d, errno=%d\n", name, n, errno); if (errno == EACCES) { dw_printf ("Type \"ls -l %s\" and verify that it has audio group rw similar to this:\n", name); dw_printf (" crw-rw---- 1 root audio 247, 0 Oct 6 19:24 %s\n", name); dw_printf ("rather than root-only access like this:\n"); dw_printf (" crw------- 1 root root 247, 0 Sep 24 09:40 %s\n", name); } close (fd); return (-1); } close (fd); return (0); } /* end cm108_write */ #endif // ifdef USE_CM108 /* end cm108.c */ direwolf-1.5+dfsg/cm108.h000066400000000000000000000002701347750676600151360ustar00rootroot00000000000000/* Dire Wolf cm108.h */ extern void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_device_size); extern int cm108_set_gpio_pin (char *name, int num, int state);direwolf-1.5+dfsg/config.c000066400000000000000000005003061347750676600155530ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 John Langner, WB2OSZ // // 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, see . // #define CONFIG_C 1 // influences behavior of aprs_tt.h // #define DEBUG 1 /*------------------------------------------------------------------ * * Module: config.c * * Purpose: Read configuration information from a file. * * Description: This started out as a simple little application with a few * command line options. Due to creeping featurism, it's now * time to add a configuration file to specify options. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #if ENABLE_GPSD #include /* for DEFAULT_GPSD_PORT (2947) */ #endif #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "digipeater.h" #include "cdigipeater.h" #include "config.h" #include "aprs_tt.h" #include "igate.h" #include "latlong.h" #include "symbols.h" #include "xmit.h" #include "tt_text.h" #include "ax25_link.h" #ifdef USE_CM108 // Linux only #include "cm108.h" #endif // geotranz #include "utm.h" #include "mgrs.h" #include "usng.h" #include "error_string.h" #define D2R(d) ((d) * M_PI / 180.) #define R2D(r) ((r) * 180. / M_PI) //#include "tq.h" /* * Conversions from various units to meters. * There is some disagreement about the exact values for some of these. * Close enough for our purposes. * Parsec, light year, and angstrom are probably not useful. */ static const struct units_s { char *name; float meters; } units[] = { { "barleycorn", 0.008466667 }, { "inch", 0.0254 }, { "in", 0.0254 }, { "hand", 0.1016 }, { "shaku", 0.3030 }, { "foot", 0.304801 }, { "ft", 0.304801 }, { "cubit", 0.4572 }, { "megalithicyard", 0.8296 }, { "my", 0.8296 }, { "yard", 0.914402 }, { "yd", 0.914402 }, { "m", 1. }, { "meter", 1. }, { "metre", 1. }, { "ell", 1.143 }, { "ken", 1.818 }, { "hiro", 1.818 }, { "fathom", 1.8288 }, { "fath", 1.8288 }, { "toise", 1.949 }, { "jo", 3.030 }, { "twain", 3.6576074 }, { "rod", 5.0292 }, { "rd", 5.0292 }, { "perch", 5.0292 }, { "pole", 5.0292 }, { "rope", 6.096 }, { "dekameter", 10. }, { "dekametre", 10. }, { "dam", 10. }, { "chain", 20.1168 }, { "ch", 20.1168 }, { "actus", 35.47872 }, { "arpent", 58.471 }, { "hectometer", 100. }, { "hectometre", 100. }, { "hm", 100. }, { "cho", 109.1 }, { "furlong", 201.168 }, { "fur", 201.168 }, { "kilometer", 1000. }, { "kilometre", 1000. }, { "km", 1000. }, { "mile", 1609.344 }, { "mi", 1609.344 }, { "ri", 3927. }, { "league", 4828.032 }, { "lea", 4828.032 } }; #define NUM_UNITS ((int)((sizeof(units) / sizeof(struct units_s)))) static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config); /* Do we have a string of all digits? */ static int alldigits(char *p) { if (p == NULL) return (0); if (strlen(p) == 0) return (0); while (*p != '\0') { if ( ! isdigit(*p)) return (0); p++; } return (1); } /* Do we have a string of all letters or + or - ? */ static int alllettersorpm(char *p) { if (p == NULL) return (0); if (strlen(p) == 0) return (0); while (*p != '\0') { if ( ! isalpha(*p) && *p != '+' && *p != '-') return (0); p++; } return (1); } /*------------------------------------------------------------------ * * Name: parse_ll * * Purpose: Parse latitude or longitude from configuration file. * * Inputs: str - String like [-]deg[^min][hemisphere] * * which - LAT or LON for error checking and message. * * line - Line number for use in error message. * * Returns: Coordinate in signed degrees. * *----------------------------------------------------------------*/ /* Acceptable symbols to separate degrees & minutes. */ /* Degree symbol is not in ASCII so documentation says to use "^" instead. */ /* Some wise guy will try to use degree symbol. */ /* UTF-8 is more difficult because it is a two byte sequence, c2 b0. */ #define DEG1 '^' #define DEG2 0xb0 /* ISO Latin1 */ #define DEG3 0xf8 /* Microsoft code page 437 */ enum parse_ll_which_e { LAT, LON }; static double parse_ll (char *str, enum parse_ll_which_e which, int line) { char stemp[40]; int sign; double degrees, minutes; char *endptr; char hemi; int limit; unsigned char sep; /* * Remove any negative sign. */ strlcpy (stemp, str, sizeof(stemp)); sign = +1; if (stemp[0] == '-') { sign = -1; stemp[0] = ' '; } /* * Process any hemisphere on the end. */ if (strlen(stemp) >= 2) { endptr = stemp + strlen(stemp) - 1; if (isalpha(*endptr)) { hemi = *endptr; *endptr = '\0'; if (islower(hemi)) { hemi = toupper(hemi); } if (hemi == 'W' || hemi == 'S') { sign = -sign; } if (which == LAT) { if (hemi != 'N' && hemi != 'S') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Latitude hemisphere in \"%s\" is not N or S.\n", line, str); } } else { if (hemi != 'E' && hemi != 'W') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Longitude hemisphere in \"%s\" is not E or W.\n", line, str); } } } } /* * Parse the degrees part. */ degrees = strtod (stemp, &endptr); /* * Is there a minutes part? */ sep = *endptr; if (sep != '\0') { if (sep == DEG1 || sep == DEG2 || sep == DEG3) { minutes = strtod (endptr+1, &endptr); if (*endptr != '\0') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unexpected character '%c' in location \"%s\"\n", line, sep, str); } if (minutes >= 60.0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Number of minutes in \"%s\" is >= 60.\n", line, str); } degrees += minutes / 60.0; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unexpected character '%c' in location \"%s\"\n", line, sep, str); } } degrees = degrees * sign; limit = which == LAT ? 90 : 180; if (degrees < -limit || degrees > limit) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Number of degrees in \"%s\" is out of range for %s\n", line, str, which == LAT ? "latitude" : "longitude"); } //dw_printf ("%s = %f\n", str, degrees); return (degrees); } /*------------------------------------------------------------------ * * Name: parse_utm_zone * * Purpose: Parse UTM zone from configuration file. * * Inputs: szone - String like [-]number[letter] * * Outputs: latband - Latitude band if specified, otherwise space or -. * * hemi - Hemisphere, always one of 'N' or 'S'. * * Returns: Zone as number. * Type is long because Convert_UTM_To_Geodetic expects that. * * Errors: Prints message and return 0. * * Description: * It seems there are multiple conventions for specifying the UTM hemisphere. * * - MGRS latitude band. North if missing or >= 'N'. * - Negative zone for south. * - Separate North or South. * * I'm using the first alternatve. * GEOTRANS uses the third. * We will also recognize the second one but I'm not sure if I want to document it. * *----------------------------------------------------------------*/ long parse_utm_zone (char *szone, char *latband, char *hemi) { long lzone; char *zlet; *latband = ' '; *hemi = 'N'; /* default */ lzone = strtol(szone, &zlet, 10); if (*zlet == '\0') { /* Number is not followed by letter something else. */ /* Allow negative number to mean south. */ if (lzone < 0) { *latband = '-'; *hemi = 'S'; lzone = (- lzone); } } else { if (islower (*zlet)) { *zlet = toupper(*zlet); } *latband = *zlet; if (strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) != NULL) { if (*zlet < 'N') { *hemi = 'S'; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Latitudinal band in \"%s\" must be one of CDEFGHJKLMNPQRSTUVWX.\n", szone); *hemi = '?'; } } if (lzone < 1 || lzone > 60) { text_color_set(DW_COLOR_ERROR); dw_printf ("UTM Zone number %ld must be in range of 1 to 60.\n", lzone); } return (lzone); } /* end parse_utm_zone */ #if 0 main () { parse_ll ("12.5", LAT); parse_ll ("12.5N", LAT); parse_ll ("12.5E", LAT); // error parse_ll ("-12.5", LAT); parse_ll ("12.5S", LAT); parse_ll ("12.5W", LAT); // error parse_ll ("12.5", LON); parse_ll ("12.5E", LON); parse_ll ("12.5N", LON); // error parse_ll ("-12.5", LON); parse_ll ("12.5W", LON); parse_ll ("12.5S", LON); // error parse_ll ("12^30", LAT); parse_ll ("12\xb030", LAT); // ISO Latin-1 degree symbol parse_ll ("91", LAT); // out of range parse_ll ("91", LON); parse_ll ("181", LON); // out of range parse_ll ("12&5", LAT); // bad character } #endif /*------------------------------------------------------------------ * * Name: parse_interval * * Purpose: Parse time interval from configuration file. * * Inputs: str - String like 10 or 9:30 * * line - Line number for use in error message. * * Returns: Number of seconds. * * Description: This is used by the BEACON configuration items * for initial delay or time between beacons. * * The format is either minutes or minutes:seconds. * *----------------------------------------------------------------*/ static int parse_interval (char *str, int line) { char *p; int sec; int nc = 0; int bad = 0; for (p = str; *p != '\0'; p++) { if (*p == ':') nc++; else if ( ! isdigit(*p)) bad++; } if (bad > 0 || nc > 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Time interval must be of the form minutes or minutes:seconds.\n", line); } p = strchr (str, ':'); if (p != NULL) { sec = atoi(str) * 60 + atoi(p+1); } else { sec = atoi(str) * 60; } return (sec); } /* end parse_interval */ /*------------------------------------------------------------------ * * Name: check_via_path * * Purpose: Check for valid path in beacons, IGate, and APRStt configuration. * * Inputs: via_path - Zero or more comma separated stations. * * Returns: Maximum number of digipeater hops or -1 for error. * * Description: Beacons and IGate can use via paths such as: * * WIDE1-1,MA3-3 * N2GH,RARA-7 * * Each part could be a specific station, an alias, or a path * from the "New n-N Paradigm." * In the first example above, the maximum number of digipeater * hops would be 4. In the second example, 2. * *----------------------------------------------------------------*/ // Put something like this in the config file as a quick test. // Not worth adding to "make check" regression tests. // // IBEACON via= // IBEACON via=W2UB // IBEACON via=W2UB-7 // IBEACON via=WIDE1-1,WIDE2-2,WIDE3-3 // IBEACON via=Lower // IBEACON via=T00LONG // IBEACON via=W2UB-16 // IBEACON via=D1,D2,D3,D4,D5,D6,D7,D8 // IBEACON via=D1,D2,D3,D4,D5,D6,D7,D8,D9 // // Define below and visually check results. //#define DEBUG8 1 static int check_via_path (char *via_path) { char stemp[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN + 1)]; int num_digi = 0; int max_digi_hops = 0; char *r; char *a; #if DEBUG8 text_color_set(DW_COLOR_DEBUG); dw_printf ("check_via_path %s\n", via_path); #endif if (strlen(via_path) == 0) { return (0); } strlcpy (stemp, via_path, sizeof(stemp)); r = stemp; while (( a = strsep(&r,",")) != NULL) { int strict = 2; int ok; char addr[AX25_MAX_ADDR_LEN]; int ssid; int heard; num_digi++; ok = ax25_parse_addr (AX25_REPEATER_1 - 1 + num_digi, a, strict, addr, &ssid, &heard); if ( ! ok) { #if DEBUG8 text_color_set(DW_COLOR_DEBUG); dw_printf ("check_via_path bad address\n"); #endif return (-1); } /* Based on assumption that a callsign can't end with a digit. */ /* For something of the form xxx9-9, we take the ssid as max hop count. */ if (ssid > 0 && strlen(addr) >= 2 && isdigit(addr[strlen(addr)-1])) { max_digi_hops += ssid; } else { max_digi_hops++; } } if (num_digi > AX25_MAX_REPEATERS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Maximum of 8 digipeaters has been exceeded.\n"); return (-1); } #if DEBUG8 text_color_set(DW_COLOR_DEBUG); dw_printf ("check_via_path %d addresses, %d max digi hops\n", num_digi, max_digi_hops); #endif return (max_digi_hops); } /* end check_via_path */ /*------------------------------------------------------------------- * * Name: split * * Purpose: Separate a line into command and parameters. * * Inputs: string - Complete command line to start process. * NULL for subsequent calls. * * rest_of_line - Caller wants remainder of line, not just * the next parameter. * * Returns: Pointer to next part with any quoting removed. * * Description: the configuration file started out very simple and strtok * was used to split up the lines. As more complicated options * were added, there were several different situations where * parameter values might contain spaces. These were handled * inconsistently in different places. In version 1.3, we now * treat them consistently in one place. * * *--------------------------------------------------------------------*/ #define MAXCMDLEN 256 static char *split (char *string, int rest_of_line) { static char cmd[MAXCMDLEN]; static char token[MAXCMDLEN]; static char shutup[] = " "; // Shut up static analysis which gets upset // over the case where this could be called with // string NULL and c was not yet initialized. static char *c = shutup; // Current position in command line. char *s, *t; int in_quotes; /* * If string is provided, make a copy. * Drop any CRLF at the end. * Change any tabs to spaces so we don't have to check for it later. */ if (string != NULL) { // dw_printf("split in: '%s'\n", string); c = cmd; for (s = string; *s != '\0'; s++) { if (*s == '\t') { *c++ = ' '; } else if (*s == '\r' || *s == '\n') { ; } else { *c++ = *s; } } *c = '\0'; c = cmd; } /* * Get next part, separated by whitespace, keeping spaces within quotes. * Quotation marks inside need to be doubled. */ while (*c == ' ') { c++; }; t = token; in_quotes = 0; for ( ; *c != '\0'; c++) { if (*c == '"') { if (in_quotes) { if (c[1] == '"') { *t++ = *c++; } else { in_quotes = 0; } } else { in_quotes = 1; } } else if (*c == ' ') { if (in_quotes || rest_of_line) { *t++ = *c; } else { break; } } else { *t++ = *c; } } *t = '\0'; // dw_printf("split out: '%s'\n", token); t = token; if (*t == '\0') { return (NULL); } return (t); } /* end split */ /*------------------------------------------------------------------- * * Name: config_init * * Purpose: Read configuration file when application starts up. * * Inputs: fname - Name of configuration file. * * Outputs: p_audio_config - Radio channel parameters stored here. * * p_digi_config - APRS Digipeater configuration stored here. * * p_cdigi_config - Connected Digipeater configuration stored here. * * p_tt_config - APRStt stuff. * * p_igate_config - Internet Gateway. * * p_misc_config - Everything else. This wasn't thought out well. * * Description: Apply default values for various parameters then read the * the configuration file which can override those values. * * Errors: For invalid input, display line number and message on stdout (not stderr). * In many cases this will result in keeping the default rather than aborting. * * Bugs: Very simple-minded parsing. * Not much error checking. (e.g. atoi() will return 0 for invalid string.) * Not very forgiving about sloppy input. * *--------------------------------------------------------------------*/ void config_init (char *fname, struct audio_s *p_audio_config, struct digi_config_s *p_digi_config, struct cdigi_config_s *p_cdigi_config, struct tt_config_s *p_tt_config, struct igate_config_s *p_igate_config, struct misc_config_s *p_misc_config) { FILE *fp; char filepath[128]; char stuff[MAXCMDLEN]; int line; int channel; int adevice; int m; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("config_init ( %s )\n", fname); #endif /* * First apply defaults. */ memset (p_audio_config, 0, sizeof(struct audio_s)); /* First audio device is always available with defaults. */ /* Others must be explicitly defined before use. */ for (adevice=0; adeviceadev[adevice].adevice_in, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_in)); strlcpy (p_audio_config->adev[adevice].adevice_out, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_out)); p_audio_config->adev[adevice].defined = 0; p_audio_config->adev[adevice].num_channels = DEFAULT_NUM_CHANNELS; /* -2 stereo */ p_audio_config->adev[adevice].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; /* -r option */ p_audio_config->adev[adevice].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* -8 option for 8 instead of 16 bits */ } p_audio_config->adev[0].defined = 1; for (channel=0; channelachan[channel].valid = 0; /* One or both channels will be */ /* set to valid when corresponding */ /* audio device is defined. */ p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; /* -m option */ p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; /* -s option */ p_audio_config->achan[channel].baud = DEFAULT_BAUD; /* -b option */ /* None. Will set default later based on other factors. */ strlcpy (p_audio_config->achan[channel].profiles, "", sizeof(p_audio_config->achan[channel].profiles)); p_audio_config->achan[channel].num_freq = 1; p_audio_config->achan[channel].offset = 0; p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS; p_audio_config->achan[channel].sanity_test = SANITY_APRS; p_audio_config->achan[channel].passall = 0; for (ot = 0; ot < NUM_OCTYPES; ot++) { p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_NONE; strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, "", sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_NONE; p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_NONE; p_audio_config->achan[channel].octrl[ot].out_gpio_num = 0; p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = 0; p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0; } for (it = 0; it < NUM_ICTYPES; it++) { p_audio_config->achan[channel].ictrl[it].method = PTT_METHOD_NONE; p_audio_config->achan[channel].ictrl[it].in_gpio_num = 0; p_audio_config->achan[channel].ictrl[it].invert = 0; } p_audio_config->achan[channel].dwait = DEFAULT_DWAIT; p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME; p_audio_config->achan[channel].persist = DEFAULT_PERSIST; p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY; p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL; p_audio_config->achan[channel].fulldup = DEFAULT_FULLDUP; } /* First channel should always be valid. */ /* If there is no ADEVICE, it uses default device in mono. */ p_audio_config->achan[0].valid = 1; memset (p_digi_config, 0, sizeof(struct digi_config_s)); // APRS digipeater p_digi_config->dedupe_time = DEFAULT_DEDUPE; memset (p_cdigi_config, 0, sizeof(struct cdigi_config_s)); // Connected mode digipeater memset (p_tt_config, 0, sizeof(struct tt_config_s)); p_tt_config->gateway_enabled = 0; p_tt_config->ttloc_size = 2; /* Start with at least 2. */ /* When full, it will be increased by 50 %. */ p_tt_config->ttloc_ptr = malloc (sizeof(struct ttloc_s) * p_tt_config->ttloc_size); p_tt_config->ttloc_len = 0; /* Retention time and decay algorithm from 13 Feb 13 version of */ /* http://www.aprs.org/aprstt/aprstt-coding24.txt */ /* Reduced by transmit count by one. An 8 minute delay in between transmissions seems awful long. */ p_tt_config->retain_time = 80 * 60; p_tt_config->num_xmits = 6; assert (p_tt_config->num_xmits <= TT_MAX_XMITS); p_tt_config->xmit_delay[0] = 3; /* Before initial transmission. */ p_tt_config->xmit_delay[1] = 16; p_tt_config->xmit_delay[2] = 32; p_tt_config->xmit_delay[3] = 64; p_tt_config->xmit_delay[4] = 2 * 60; p_tt_config->xmit_delay[5] = 4 * 60; p_tt_config->xmit_delay[6] = 8 * 60; // not currently used. strlcpy (p_tt_config->status[0], "", sizeof(p_tt_config->status[0])); strlcpy (p_tt_config->status[1], "/off duty", sizeof(p_tt_config->status[1])); strlcpy (p_tt_config->status[2], "/enroute", sizeof(p_tt_config->status[2])); strlcpy (p_tt_config->status[3], "/in service", sizeof(p_tt_config->status[3])); strlcpy (p_tt_config->status[4], "/returning", sizeof(p_tt_config->status[4])); strlcpy (p_tt_config->status[5], "/committed", sizeof(p_tt_config->status[5])); strlcpy (p_tt_config->status[6], "/special", sizeof(p_tt_config->status[6])); strlcpy (p_tt_config->status[7], "/priority", sizeof(p_tt_config->status[7])); strlcpy (p_tt_config->status[8], "/emergency", sizeof(p_tt_config->status[8])); strlcpy (p_tt_config->status[9], "/custom 1", sizeof(p_tt_config->status[9])); for (m = 0; m < TT_ERROR_MAXP1; m++) { strlcpy (p_tt_config->response[m].method, "MORSE", sizeof(p_tt_config->response[m].method)); strlcpy (p_tt_config->response[m].mtext, "?", sizeof(p_tt_config->response[m].mtext)); } strlcpy (p_tt_config->response[TT_ERROR_OK].mtext, "R", sizeof(p_tt_config->response[TT_ERROR_OK].mtext)); memset (p_misc_config, 0, sizeof(struct misc_config_s)); p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT; p_misc_config->kiss_port = DEFAULT_KISS_PORT; p_misc_config->enable_kiss_pt = 0; /* -p option */ /* Defaults from http://info.aprs.net/index.php?title=SmartBeaconing */ p_misc_config->sb_configured = 0; /* TRUE if SmartBeaconing is configured. */ p_misc_config->sb_fast_speed = 60; /* MPH */ p_misc_config->sb_fast_rate = 180; /* seconds */ p_misc_config->sb_slow_speed = 5; /* MPH */ p_misc_config->sb_slow_rate = 1800; /* seconds */ p_misc_config->sb_turn_time = 15; /* seconds */ p_misc_config->sb_turn_angle = 30; /* degrees */ p_misc_config->sb_turn_slope = 255; /* degrees * MPH */ memset (p_igate_config, 0, sizeof(struct igate_config_s)); p_igate_config->t2_server_port = DEFAULT_IGATE_PORT; p_igate_config->tx_chan = -1; /* IS->RF not enabled */ p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_DEFAULT; p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_DEFAULT; p_igate_config->igmsp = 1; p_igate_config->rx2ig_dedupe_time = IGATE_RX2IG_DEDUPE_TIME; /* People find this confusing. */ /* Ideally we'd like to figure out if com0com is installed */ /* and automatically enable this. */ strlcpy (p_misc_config->kiss_serial_port, "", sizeof(p_misc_config->kiss_serial_port)); p_misc_config->kiss_serial_speed = 0; p_misc_config->kiss_serial_poll = 0; strlcpy (p_misc_config->gpsnmea_port, "", sizeof(p_misc_config->gpsnmea_port)); strlcpy (p_misc_config->waypoint_port, "", sizeof(p_misc_config->waypoint_port)); p_misc_config->log_daily_names = 0; strlcpy (p_misc_config->log_path, "", sizeof(p_misc_config->log_path)); /* connected mode. */ p_misc_config->frack = AX25_T1V_FRACK_DEFAULT; /* Number of seconds to wait for ack to transmission. */ p_misc_config->retry = AX25_N2_RETRY_DEFAULT; /* Number of times to retry before giving up. */ p_misc_config->paclen = AX25_N1_PACLEN_DEFAULT; /* Max number of bytes in information part of frame. */ p_misc_config->maxframe_basic = AX25_K_MAXFRAME_BASIC_DEFAULT; /* Max frames to send before ACK. mod 8 "Window" size. */ p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; /* Max frames to send before ACK. mod 128 "Window" size. */ p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 2; /* Max SABME before falling back to SABM. */ p_misc_config->v20_addrs = NULL; /* Go directly to v2.0 for stations listed. */ p_misc_config->v20_count = 0; p_misc_config->noxid_addrs = NULL; /* Don't send XID to these stations. */ p_misc_config->noxid_count = 0; /* * Try to extract options from a file. * * Windows: File must be in current working directory. * * Linux: Search current directory then home directory. * * Future possibility - Could also search home directory * for Windows by combinting two variables: * HOMEDRIVE=C: * HOMEPATH=\Users\John * * It's not clear if this always points to same location: * USERPROFILE=C:\Users\John */ channel = 0; adevice = 0; // TODO: Would be better to have a search list and loop thru it. strlcpy(filepath, fname, sizeof(filepath)); fp = fopen (filepath, "r"); #ifndef __WIN32__ if (fp == NULL && strcmp(fname, "direwolf.conf") == 0) { /* Failed to open the default location. Try home dir. */ char *p; strlcpy (filepath, "", sizeof(filepath)); p = getenv("HOME"); if (p != NULL) { strlcpy (filepath, p, sizeof(filepath)); strlcat (filepath, "/direwolf.conf", sizeof(filepath)); fp = fopen (filepath, "r"); } } #endif if (fp == NULL) { // TODO: not exactly right for all situations. text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Could not open config file %s\n", filepath); dw_printf ("Try using -c command line option for alternate location.\n"); return; } dw_printf ("\nReading config file %s\n", filepath); line = 0; while (fgets(stuff, sizeof(stuff), fp) != NULL) { char *t; line++; t = split(stuff,0); if (t == NULL) { continue; } if (*t == '#' || *t == '*') { continue; } /* * ==================== Audio device parameters ==================== */ /* * ADEVICE[n] - Name of input sound device, and optionally output, if different. * * ADEVICE plughw:1,0 -- same for in and out. * ADEVICE plughw:2,0 plughw:3,0 -- different in/out for a channel or channel pair. * ADEVICE1 udp:7355 default -- from Software defined radio (SDR) via UDP. * */ /* Note that ALSA name can contain comma such as hw:1,0 */ if (strncasecmp(t, "ADEVICE", 7) == 0) { /* "ADEVICE" is equivalent to "ADEVICE0". */ adevice = 0; if (strlen(t) >= 8) { adevice = atoi(t+7); } if (adevice < 0 || adevice >= MAX_ADEVS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Device number %d out of range for ADEVICE command on line %d.\n", adevice, line); dw_printf ("If you really need more than %d audio devices, increase MAX_ADEVS and recompile.\n", MAX_ADEVS); adevice = 0; continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing name of audio device for ADEVICE command on line %d.\n", line); continue; } p_audio_config->adev[adevice].defined = 1; /* First channel of device is valid. */ p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1; strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in)); strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); t = split(NULL,0); if (t != NULL) { strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); } } /* * PAIDEVICE[n] input-device * PAODEVICE[n] output-device * * This was submitted by KK5VD for the Mac OS X version. (__APPLE__) * * It looks like device names can contain spaces making it a little * more difficult to put two names on the same line unless we come up with * some other delimiter between them or a quoting scheme to handle * embedded spaces in a name. * * It concerns me that we could have one defined without the other * if we don't put in more error checking later. * * version 1.3 dev snapshot C: * * We now have a general quoting scheme so the original ADEVICE can handle this. * These options will probably be removed before general 1.3 release. */ else if (strcasecmp(t, "PAIDEVICE") == 0) { adevice = 0; if (isdigit(t[9])) { adevice = t[9] - '0'; } if (adevice < 0 || adevice >= MAX_ADEVS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Device number %d out of range for PADEVICE command on line %d.\n", adevice, line); adevice = 0; continue; } t = split(NULL,1); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing name of audio device for PADEVICE command on line %d.\n", line); continue; } p_audio_config->adev[adevice].defined = 1; /* First channel of device is valid. */ p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1; strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in)); } else if (strcasecmp(t, "PAODEVICE") == 0) { adevice = 0; if (isdigit(t[9])) { adevice = t[9] - '0'; } if (adevice < 0 || adevice >= MAX_ADEVS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Device number %d out of range for PADEVICE command on line %d.\n", adevice, line); adevice = 0; continue; } t = split(NULL,1); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing name of audio device for PADEVICE command on line %d.\n", line); continue; } p_audio_config->adev[adevice].defined = 1; /* First channel of device is valid. */ p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1; strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); } /* * ARATE - Audio samples per second, 11025, 22050, 44100, etc. */ else if (strcasecmp(t, "ARATE") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing audio sample rate for ARATE command.\n", line); continue; } n = atoi(t); if (n >= MIN_SAMPLES_PER_SEC && n <= MAX_SAMPLES_PER_SEC) { p_audio_config->adev[adevice].samples_per_sec = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Use a more reasonable audio sample rate in range of %d - %d.\n", line, MIN_SAMPLES_PER_SEC, MAX_SAMPLES_PER_SEC); } } /* * ACHANNELS - Number of audio channels for current device: 1 or 2 */ else if (strcasecmp(t, "ACHANNELS") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing number of audio channels for ACHANNELS command.\n", line); continue; } n = atoi(t); if (n ==1 || n == 2) { p_audio_config->adev[adevice].num_channels = n; /* Set valid channels depending on mono or stereo. */ p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1; if (n == 2) { p_audio_config->achan[ADEVFIRSTCHAN(adevice) + 1].valid = 1; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Number of audio channels must be 1 or 2.\n", line); } } /* * ==================== Radio channel parameters ==================== */ /* * CHANNEL - Set channel for following commands. */ else if (strcasecmp(t, "CHANNEL") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing channel number for CHANNEL command.\n", line); continue; } n = atoi(t); if (n >= 0 && n < MAX_CHANS) { channel = n; if ( ! p_audio_config->achan[n].valid) { if ( ! p_audio_config->adev[ACHAN2ADEV(n)].defined) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Channel number %d is not valid because audio device %d is not defined.\n", line, n, ACHAN2ADEV(n)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Channel number %d is not valid because audio device %d is not in stereo.\n", line, n, ACHAN2ADEV(n)); } } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Channel number must in range of 0 to %d.\n", line, MAX_CHANS-1); } } /* * MYCALL station */ else if (strcasecmp(t, "mycall") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing value for MYCALL command on line %d.\n", line); continue; } else { char *p; int const strict = 2; char call_no_ssid[AX25_MAX_ADDR_LEN]; int ssid, heard; for (p = t; *p != '\0'; p++) { if (islower(*p)) { *p = toupper(*p); /* Silently force upper case. */ /* Might change to warning someday. */ } } if ( ! ax25_parse_addr (-1, t, strict, call_no_ssid, &ssid, &heard)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Invalid value for MYCALL command on line %d.\n", line); continue; } // Definitely set for current channel. // Set for other channels which have not been set yet. int c; for (c = 0; c < MAX_CHANS; c++) { if (c == channel || strlen(p_audio_config->achan[c].mycall) == 0 || strcasecmp(p_audio_config->achan[c].mycall, "NOCALL") == 0 || strcasecmp(p_audio_config->achan[c].mycall, "N0CALL") == 0) { strlcpy (p_audio_config->achan[c].mycall, t, sizeof(p_audio_config->achan[c].mycall)); } } } } /* * MODEM - Set modem properties for current channel. * * * Old style: * MODEM baud [ mark space [A][B][C][+] [ num-decoders spacing ] ] * * New style, version 1.2: * MODEM speed [ option ] ... * * Options: * mark:space - AFSK tones. Defaults based on speed. * num@offset - Multiple decoders on different frequencies. * */ else if (strcasecmp(t, "MODEM") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing data transmission speed for MODEM command.\n", line); continue; } n = atoi(t); if (n >= MIN_BAUD && n <= MAX_BAUD) { p_audio_config->achan[channel].baud = n; if (n != 300 && n != 1200 && n != 2400 && n != 4800 && n != 9600 && n != 19200) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Warning: Non-standard data rate of %d bits per second. Are you sure?\n", line, n); } } else { p_audio_config->achan[channel].baud = DEFAULT_BAUD; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable data rate. Using %d bits per second.\n", line, p_audio_config->achan[channel].baud); } /* Set defaults based on speed. */ /* Should be same as -B command line option in direwolf.c. */ /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ /* that need to be kept in sync. Maybe it could be a common function someday. */ if (p_audio_config->achan[channel].baud < 600) { p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].mark_freq = 1600; p_audio_config->achan[channel].space_freq = 1800; } else if (p_audio_config->achan[channel].baud < 1800) { p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; } else if (p_audio_config->achan[channel].baud < 3600) { p_audio_config->achan[channel].modem_type = MODEM_QPSK; p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } else if (p_audio_config->achan[channel].baud < 7200) { p_audio_config->achan[channel].modem_type = MODEM_8PSK; p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } else { p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } /* Get mark frequency. */ t = split(NULL,0); if (t == NULL) { /* all done. */ continue; } if (alldigits(t)) { /* old style */ n = atoi(t); /* Originally the upper limit was 3000. */ /* Version 1.0 increased to 5000 because someone */ /* wanted to use 2400/4800 Hz AFSK. */ /* Of course the MIC and SPKR connections won't */ /* have enough bandwidth so radios must be modified. */ if (n >= 300 && n <= 5000) { p_audio_config->achan[channel].mark_freq = n; } else { p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d.\n", line, p_audio_config->achan[channel].mark_freq); } /* Get space frequency */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing tone frequency for space.\n", line); continue; } n = atoi(t); if (n >= 300 && n <= 5000) { p_audio_config->achan[channel].space_freq = n; } else { p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable space tone frequency. Using %d.\n", line, p_audio_config->achan[channel].space_freq); } /* Gently guide users toward new format. */ if (p_audio_config->achan[channel].baud == 1200 && p_audio_config->achan[channel].mark_freq == 1200 && p_audio_config->achan[channel].space_freq == 2200) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: The AFSK frequencies can be omitted when using the 1200 baud default 1200:2200.\n", line); } if (p_audio_config->achan[channel].baud == 300 && p_audio_config->achan[channel].mark_freq == 1600 && p_audio_config->achan[channel].space_freq == 1800) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: The AFSK frequencies can be omitted when using the 300 baud default 1600:1800.\n", line); } /* New feature in 0.9 - Optional filter profile(s). */ t = split(NULL,0); if (t != NULL) { /* Look for some combination of letter(s) and + */ if (isalpha(t[0]) || t[0] == '+') { char *pc; /* Here we only catch something other than letters and + mixed in. */ /* Later, we check for valid letters and no more than one letter if + specified. */ for (pc = t; *pc != '\0'; pc++) { if ( ! isalpha(*pc) && ! (*pc == '+')) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Demodulator type can only contain letters and + character.\n", line); } } strlcpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles)); t = split(NULL,0); if (strlen(p_audio_config->achan[channel].profiles) > 1 && t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Can't combine multiple demodulator types and multiple frequencies.\n", line); continue; } } } /* New feature in 0.9 - optional number of decoders and frequency offset between. */ if (t != NULL) { n = atoi(t); if (n < 1 || n > MAX_SUBCHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Number of demodulators is out of range. Using 3.\n", line); n = 3; } p_audio_config->achan[channel].num_freq = n; t = split(NULL,0); if (t != NULL) { n = atoi(t); if (n < 5 || n > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable value for offset between modems. Using 50 Hz.\n", line); n = 50; } p_audio_config->achan[channel].offset = n; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: New style for multiple demodulators is %d@%d\n", line, p_audio_config->achan[channel].num_freq, p_audio_config->achan[channel].offset); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing frequency offset between modems. Using 50 Hz.\n", line); p_audio_config->achan[channel].offset = 50; } } } else { /* New style in version 1.2. */ while (t != NULL) { char *s; if ((s = strchr(t, ':')) != NULL) { /* mark:space */ p_audio_config->achan[channel].mark_freq = atoi(t); p_audio_config->achan[channel].space_freq = atoi(s+1); if (p_audio_config->achan[channel].mark_freq == 0 && p_audio_config->achan[channel].space_freq == 0) { p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; } else { p_audio_config->achan[channel].modem_type = MODEM_AFSK; if (p_audio_config->achan[channel].mark_freq < 300 || p_audio_config->achan[channel].mark_freq > 5000) { p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d instead.\n", line, p_audio_config->achan[channel].mark_freq); } if (p_audio_config->achan[channel].space_freq < 300 || p_audio_config->achan[channel].space_freq > 5000) { p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable space tone frequency. Using %d instead.\n", line, p_audio_config->achan[channel].space_freq); } } } else if ((s = strchr(t, '@')) != NULL) { /* num@offset */ p_audio_config->achan[channel].num_freq = atoi(t); p_audio_config->achan[channel].offset = atoi(s+1); if (p_audio_config->achan[channel].num_freq < 1 || p_audio_config->achan[channel].num_freq > MAX_SUBCHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Number of demodulators is out of range. Using 3.\n", line); p_audio_config->achan[channel].num_freq = 3; } if (p_audio_config->achan[channel].offset < 5 || p_audio_config->achan[channel].offset > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Offset between demodulators is unreasonable. Using 50 Hz.\n", line); p_audio_config->achan[channel].offset = 50; } } else if (alllettersorpm(t)) { /* profile of letter(s) + - */ // Will be validated later. strlcpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles)); } else if (*t == '/') { /* /div */ int n = atoi(t+1); if (n >= 1 && n <= 8) { p_audio_config->achan[channel].decimate = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Ignoring unreasonable sample rate division factor of %d.\n", line, n); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unrecognized option for MODEM: %s\n", line, t); } t = split(NULL,0); } /* A later place catches disallowed combination of + and @. */ /* A later place sets /n for 300 baud if not specified by user. */ //dw_printf ("debug: div = %d\n", p_audio_config->achan[channel].decimate); } } /* * DTMF - Enable DTMF decoder. * * Future possibilities: * Option to determine if it goes to APRStt gateway and/or application. * Disable normal demodulator to reduce CPU requirements. */ else if (strcasecmp(t, "DTMF") == 0) { p_audio_config->achan[channel].dtmf_decode = DTMF_DECODE_ON; } /* * FIX_BITS n [ APRS | AX25 | NONE ] [ PASSALL ] * * - Attempt to fix frames with bad FCS. */ else if (strcasecmp(t, "FIX_BITS") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for FIX_BITS command.\n", line); continue; } n = atoi(t); if (n >= RETRY_NONE && n < RETRY_MAX) { // MAX is actually last valid +1 p_audio_config->achan[channel].fix_bits = (retry_t)n; } else { p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid value %d for FIX_BITS. Using default of %d.\n", line, n, p_audio_config->achan[channel].fix_bits); } if (p_audio_config->achan[channel].fix_bits > RETRY_INVERT_SINGLE) { text_color_set(DW_COLOR_INFO); dw_printf ("Line %d: Using a FIX_BITS value greater than %d is not recommended for normal operation.\n", line, RETRY_INVERT_SINGLE); } if (p_audio_config->achan[channel].fix_bits >= RETRY_INVERT_TWO_SEP) { text_color_set(DW_COLOR_INFO); dw_printf ("Line %d: Using a FIX_BITS value of %d will waste a lot of CPU power and produce wrong results.\n", line, RETRY_INVERT_TWO_SEP); } t = split(NULL,0); while (t != NULL) { // If more than one sanity test, we silently take the last one. if (strcasecmp(t, "APRS") == 0) { p_audio_config->achan[channel].sanity_test = SANITY_APRS; } else if (strcasecmp(t, "AX25") == 0 || strcasecmp(t, "AX.25") == 0) { p_audio_config->achan[channel].sanity_test = SANITY_AX25; } else if (strcasecmp(t, "NONE") == 0) { p_audio_config->achan[channel].sanity_test = SANITY_NONE; } else if (strcasecmp(t, "PASSALL") == 0) { p_audio_config->achan[channel].passall = 1; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: There is an old saying, \"Be careful what you ask for because you might get it.\"\n", line); dw_printf ("The PASSALL option means allow all frames even when they are invalid.\n"); dw_printf ("You are asking to receive random trash and you WILL get your wish.\n"); dw_printf ("Don't complain when you see all sorts of random garbage. That's what you asked for.\n"); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid option '%s' for FIX_BITS.\n", line, t); } t = split(NULL,0); } } /* * PTT - Push To Talk signal line. * DCD - Data Carrier Detect indicator. * CON - Connected to another station indicator. * * xxx serial-port [-]rts-or-dtr [ [-]rts-or-dtr ] * xxx GPIO [-]gpio-num * xxx LPT [-]bit-num * PTT RIG model port * PTT RIG AUTO port * PTT CM108 [ [-]bit-num ] [ hid-device ] * * When model is 2, port would host:port like 127.0.0.1:4532 * Otherwise, port would be a serial port like /dev/ttyS0 * * * Applies to most recent CHANNEL command. */ else if (strcasecmp(t, "PTT") == 0 || strcasecmp(t, "DCD") == 0 || strcasecmp(t, "CON") == 0) { int ot; char otname[8]; if (strcasecmp(t, "PTT") == 0) { ot = OCTYPE_PTT; strlcpy (otname, "PTT", sizeof(otname)); } else if (strcasecmp(t, "DCD") == 0) { ot = OCTYPE_DCD; strlcpy (otname, "DCD", sizeof(otname)); } else { ot = OCTYPE_CON; strlcpy (otname, "CON", sizeof(otname)); } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing output control device for %s command.\n", line, otname); continue; } if (strcasecmp(t, "GPIO") == 0) { /* GPIO case, Linux only. */ #if __WIN32__ text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with GPIO is only available on Linux.\n", line, otname); #else t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing GPIO number for %s.\n", line, otname); continue; } if (*t == '-') { p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t+1); p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else { p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t); p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIO; #endif } else if (strcasecmp(t, "LPT") == 0) { /* Parallel printer case, x86 Linux only. */ #if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) ) t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing LPT bit number for %s.\n", line, otname); continue; } if (*t == '-') { p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t+1); p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else { p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t); p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_LPT; #else text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with LPT is only available on x86 Linux.\n", line, otname); #endif } else if (strcasecmp(t, "RIG") == 0) { #ifdef USE_HAMLIB t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing model number for hamlib.\n", line); continue; } if (strcasecmp(t, "AUTO") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_model = -1; } else { int n = atoi(t); if (n < 1 || n > 9999) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Unreasonable model number %d for hamlib.\n", line, n); continue; } p_audio_config->achan[channel].octrl[ot].ptt_model = n; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing port for hamlib.\n", line); continue; } strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); t = split(NULL,0); if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s was not expected after model & port for hamlib.\n", line, t); } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_HAMLIB; #else #if __WIN32__ text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Windows version of direwolf does not support HAMLIB.\n", line); exit (EXIT_FAILURE); #else text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with RIG is only available when hamlib support is enabled.\n", line, otname); dw_printf ("You must rebuild direwolf with hamlib support.\n"); dw_printf ("See User Guide for details.\n"); #endif #endif } else if (strcasecmp(t, "CM108") == 0) { /* CM108 - GPIO of USB sound card. case, Linux only. */ #ifdef USE_CM108 if (ot != OCTYPE_PTT) { // Future project: Allow DCD and CON via the same device. // This gets more complicated because we can't selectively change a single GPIO bit. // We would need to keep track of what is currently there, change one bit, in our local // copy of the status and then write out the byte for all of the pins. // Let's keep it simple with just PTT for the first stab at this. text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: PTT CM108 option is only valid for PTT, not %s.\n", line, otname); continue; } p_audio_config->achan[channel].octrl[ot].out_gpio_num = 3; // All known designs use GPIO 3. // User can override for special cases. p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; // High for transmit. strcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, ""); // Try to find PTT device for audio output device. // Simplifiying assumption is that we have one radio per USB Audio Adapter. // Failure at this point is not an error. // See if config file sets it explicitly before complaining. cm108_find_ptt (p_audio_config->adev[ACHAN2ADEV(channel)].adevice_out, p_audio_config->achan[channel].octrl[ot].ptt_device, (int)sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); while ((t = split(NULL,0)) != NULL) { if (*t == '-') { p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t+1); p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else if (isdigit(*t)) { p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t); p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } else if (*t == '/') { strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Found \"%s\" when expecting GPIO number or device name like /dev/hidraw1.\n", line, t); continue; } } if (p_audio_config->achan[channel].octrl[ot].out_gpio_num < 1 || p_audio_config->achan[channel].octrl[ot].out_gpio_num > 8) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: CM108 GPIO number %d is not in range of 1 thru 8.\n", line, p_audio_config->achan[channel].octrl[ot].out_gpio_num); continue; } if (strlen(p_audio_config->achan[channel].octrl[ot].ptt_device) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Could not determine USB Audio GPIO PTT device for audio output %s.\n", line, p_audio_config->adev[ACHAN2ADEV(channel)].adevice_out); dw_printf ("You must explicitly mention a device name such as /dev/hidraw1.\n"); dw_printf ("See User Guide for details.\n"); continue; } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_CM108; #else #if __WIN32__ text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: CM108 USB Audio GPIO PTT is not available for Windows.\n", line); #else text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with CM108 is only available when USB Audio GPIO support is enabled.\n", line, otname); dw_printf ("You must rebuild direwolf with CM108 Audio Adapter GPIO PTT support.\n"); dw_printf ("See User Guide for details.\n"); #endif exit (EXIT_FAILURE); #endif } else { /* serial port case. */ strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing RTS or DTR after %s device name.\n", line, otname); continue; } if (strcasecmp(t, "rts") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_RTS; p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } else if (strcasecmp(t, "dtr") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_DTR; p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } else if (strcasecmp(t, "-rts") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_RTS; p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else if (strcasecmp(t, "-dtr") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_DTR; p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Expected RTS or DTR after %s device name.\n", line, otname); continue; } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_SERIAL; /* In version 1.2, we allow a second one for same serial port. */ /* Some interfaces want the two control lines driven with opposite polarity. */ /* e.g. PTT COM1 RTS -DTR */ t = split(NULL,0); if (t != NULL) { if (strcasecmp(t, "rts") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_RTS; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0; } else if (strcasecmp(t, "dtr") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_DTR; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0; } else if (strcasecmp(t, "-rts") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_RTS; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 1; } else if (strcasecmp(t, "-dtr") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_DTR; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 1; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Expected RTS or DTR after first RTS or DTR.\n", line); continue; } /* Would not make sense to specify the same one twice. */ if (p_audio_config->achan[channel].octrl[ot].ptt_line == p_audio_config->achan[channel].octrl[ot].ptt_line2) { dw_printf ("Config file line %d: Doesn't make sense to specify the some control line twice.\n", line); } } /* end of second serial port control line. */ } /* end of serial port case. */ } /* end of PTT, DCD, CON */ /* * INPUTS * * TXINH - TX holdoff input * * TXINH GPIO [-]gpio-num (only type supported so far) */ else if (strcasecmp(t, "TXINH") == 0) { char itname[8]; strlcpy (itname, "TXINH", sizeof(itname)); t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing input type name for %s command.\n", line, itname); continue; } if (strcasecmp(t, "GPIO") == 0) { #if __WIN32__ text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with GPIO is only available on Linux.\n", line, itname); #else t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing GPIO number for %s.\n", line, itname); continue; } if (*t == '-') { p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].in_gpio_num = atoi(t+1); p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].invert = 1; } else { p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].in_gpio_num = atoi(t); p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].invert = 0; } p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].method = PTT_METHOD_GPIO; #endif } } /* * DWAIT n - Extra delay for receiver squelch. n = 10 mS units. */ else if (strcasecmp(t, "DWAIT") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing delay time for DWAIT command.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= 255) { p_audio_config->achan[channel].dwait = n; } else { p_audio_config->achan[channel].dwait = DEFAULT_DWAIT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid delay time for DWAIT. Using %d.\n", line, p_audio_config->achan[channel].dwait); } } /* * SLOTTIME n - For non-digipeat transmit delay timing. n = 10 mS units. */ else if (strcasecmp(t, "SLOTTIME") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing delay time for SLOTTIME command.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= 255) { p_audio_config->achan[channel].slottime = n; } else { p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid delay time for persist algorithm. Using %d.\n", line, p_audio_config->achan[channel].slottime); } } /* * PERSIST - For non-digipeat transmit delay timing. */ else if (strcasecmp(t, "PERSIST") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing probability for PERSIST command.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= 255) { p_audio_config->achan[channel].persist = n; } else { p_audio_config->achan[channel].persist = DEFAULT_PERSIST; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid probability for persist algorithm. Using %d.\n", line, p_audio_config->achan[channel].persist); } } /* * TXDELAY n - For transmit delay timing. n = 10 mS units. */ else if (strcasecmp(t, "TXDELAY") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing time for TXDELAY command.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= 255) { p_audio_config->achan[channel].txdelay = n; } else { p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid time for transmit delay. Using %d.\n", line, p_audio_config->achan[channel].txdelay); } } /* * TXTAIL n - For transmit timing. n = 10 mS units. */ else if (strcasecmp(t, "TXTAIL") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing time for TXTAIL command.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= 255) { p_audio_config->achan[channel].txtail = n; } else { p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid time for transmit timing. Using %d.\n", line, p_audio_config->achan[channel].txtail); } } /* * FULLDUP {on|off} - Full Duplex */ else if (strcasecmp(t, "FULLDUP") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing parameter for FULLDUP command. Expecting ON or OFF.\n", line); continue; } if (strcasecmp(t, "ON") == 0) { p_audio_config->achan[channel].fulldup = 1; } else if (strcasecmp(t, "OFF") == 0) { p_audio_config->achan[channel].fulldup = 0; } else { p_audio_config->achan[channel].fulldup = 0; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Expected ON or OFF for FULLDUP.\n", line); } } /* * SPEECH script * * Specify script for text-to-speech function. */ else if (strcasecmp(t, "SPEECH") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing script for Text-to-Speech function.\n", line); continue; } /* See if we can run it. */ if (xmit_speak_it(t, -1, " ") == 0) { if (strlcpy (p_audio_config->tts_script, t, sizeof(p_audio_config->tts_script)) >= sizeof(p_audio_config->tts_script)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Script for text-to-speech function is too long.\n", line); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Error trying to run Text-to-Speech function.\n", line); continue; } } /* * ==================== APRS Digipeater parameters ==================== */ /* * DIGIPEAT from-chan to-chan alias-pattern wide-pattern [ OFF|DROP|MARK|TRACE ] */ else if (strcasecmp(t, "DIGIPEAT") == 0 || strcasecmp(t, "DIGIPEATER") == 0) { int from_chan, to_chan; int e; char message[100]; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } from_chan = atoi(t); if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[from_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } to_chan = atoi(t); if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[to_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing alias pattern on line %d.\n", line); continue; } e = regcomp (&(p_digi_config->alias[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB); if (e != 0) { regerror (e, &(p_digi_config->alias[from_chan][to_chan]), message, sizeof(message)); text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Invalid alias matching pattern on line %d:\n%s\n", line, message); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing wide pattern on line %d.\n", line); continue; } e = regcomp (&(p_digi_config->wide[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB); if (e != 0) { regerror (e, &(p_digi_config->wide[from_chan][to_chan]), message, sizeof(message)); text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Invalid wide matching pattern on line %d:\n%s\n", line, message); continue; } p_digi_config->enabled[from_chan][to_chan] = 1; p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF; t = split(NULL,0); if (t != NULL) { if (strcasecmp(t, "OFF") == 0) { p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF; t = split(NULL,0); } else if (strcasecmp(t, "DROP") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Preemptive digipeating DROP option is discouraged.\n", line); dw_printf ("It can create a via path which is misleading about the actual path taken.\n"); dw_printf ("TRACE is the best choice for this feature.\n"); p_digi_config->preempt[from_chan][to_chan] = PREEMPT_DROP; t = split(NULL,0); } else if (strcasecmp(t, "MARK") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Preemptive digipeating MARK option is discouraged.\n", line); dw_printf ("It can create a via path which is misleading about the actual path taken.\n"); dw_printf ("TRACE is the best choice for this feature.\n"); p_digi_config->preempt[from_chan][to_chan] = PREEMPT_MARK; t = split(NULL,0); } else if (strcasecmp(t, "TRACE") == 0) { p_digi_config->preempt[from_chan][to_chan] = PREEMPT_TRACE; t = split(NULL,0); } } if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Found \"%s\" where end of line was expected.\n", line, t); } } /* * DEDUPE - Time to suppress digipeating of duplicate packets. */ else if (strcasecmp(t, "DEDUPE") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing time for DEDUPE command.\n", line); continue; } n = atoi(t); if (n >= 0 && n < 600) { p_digi_config->dedupe_time = n; } else { p_digi_config->dedupe_time = DEFAULT_DEDUPE; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable value for dedupe time. Using %d.\n", line, p_digi_config->dedupe_time); } } /* * REGEN - Signal regeneration. */ else if (strcasecmp(t, "regen") == 0) { int from_chan, to_chan; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } from_chan = atoi(t); if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[from_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } to_chan = atoi(t); if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[to_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); continue; } p_digi_config->regen[from_chan][to_chan] = 1; } /* * ==================== Connected Digipeater parameters ==================== */ /* * CDIGIPEAT from-chan to-chan [ alias-pattern ] */ else if (strcasecmp(t, "CDIGIPEAT") == 0 || strcasecmp(t, "CDIGIPEATER") == 0) { int from_chan, to_chan; int e; char message[100]; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } from_chan = atoi(t); if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[from_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } to_chan = atoi(t); if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[to_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); continue; } t = split(NULL,0); if (t != NULL) { e = regcomp (&(p_cdigi_config->alias[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB); if (e == 0) { p_cdigi_config->has_alias[from_chan][to_chan] = 1; } else { regerror (e, &(p_cdigi_config->alias[from_chan][to_chan]), message, sizeof(message)); text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Invalid alias matching pattern on line %d:\n%s\n", line, message); continue; } t = split(NULL,0); } p_cdigi_config->enabled[from_chan][to_chan] = 1; if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Found \"%s\" where end of line was expected.\n", line, t); } } /* * ==================== Packet Filtering for APRS digipeater or IGate ==================== */ /* * FILTER from-chan to-chan filter_specification_expression * FILTER from-chan IG filter_specification_expression * FILTER IG to-chan filter_specification_expression * * * Note that we have three different config file filter commands: * * FILTER - Originally for APRS digipeating but later enhanced * to include IGate client side. Maybe it should be * renamed AFILTER to make it clearer after adding CFILTER. * * CFILTER - Similar for connected moded digipeater. * * IGFILTER - APRS-IS (IGate) server side - completely diffeent. * I'm not happy with this name because IG sounds like IGate * which is really the client side. More comments later. */ else if (strcasecmp(t, "FILTER") == 0) { int from_chan, to_chan; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } if (*t == 'i' || *t == 'I') { from_chan = MAX_CHANS; } else { from_chan = isdigit(*t) ? atoi(t) : -999; if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d or \"IG\" on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[from_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); continue; } } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } if (*t == 'i' || *t == 'I') { to_chan = MAX_CHANS; } else { to_chan = isdigit(*t) ? atoi(t) : -999; if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d or \"IG\" on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[to_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); continue; } } t = split(NULL,1); /* Take rest of line including spaces. */ if (t == NULL) { t = " "; /* Empty means permit nothing. */ } if (p_digi_config->filter_str[from_chan][to_chan] != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Replacing previous filter for same from/to pair:\n %s\n", line, p_digi_config->filter_str[from_chan][to_chan]); free (p_digi_config->filter_str[from_chan][to_chan]); p_digi_config->filter_str[from_chan][to_chan] = NULL; } p_digi_config->filter_str[from_chan][to_chan] = strdup(t); //TODO: Do a test run to see errors now instead of waiting. } /* * ==================== Packet Filtering for connected digipeater ==================== */ /* * CFILTER from-chan to-chan filter_specification_expression */ else if (strcasecmp(t, "CFILTER") == 0) { int from_chan, to_chan; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } from_chan = isdigit(*t) ? atoi(t) : -999; if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[from_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } to_chan = isdigit(*t) ? atoi(t) : -999; if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[to_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); continue; } t = split(NULL,1); /* Take rest of line including spaces. */ if (t == NULL) { t = " "; /* Empty means permit nothing. */ } p_cdigi_config->cfilter_str[from_chan][to_chan] = strdup(t); //TODO1.2: Do a test run to see errors now instead of waiting. } /* * ==================== APRStt gateway ==================== */ /* * TTCORRAL - How to handle unknown positions * * TTCORRAL latitude longitude offset-or-ambiguity */ else if (strcasecmp(t, "TTCORRAL") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTCORRAL command.\n", line); continue; } p_tt_config->corral_lat = parse_ll(t,LAT,line); t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line); continue; } p_tt_config->corral_lon = parse_ll(t,LON,line); t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line); continue; } p_tt_config->corral_offset = parse_ll(t,LAT,line); if (p_tt_config->corral_offset == 1 || p_tt_config->corral_offset == 2 || p_tt_config->corral_offset == 3) { p_tt_config->corral_ambiguity = p_tt_config->corral_offset; p_tt_config->corral_offset = 0; } //dw_printf ("DEBUG: corral %f %f %f %d\n", p_tt_config->corral_lat, // p_tt_config->corral_lon, p_tt_config->corral_offset, p_tt_config->corral_ambiguity); } /* * TTPOINT - Define a point represented by touch tone sequence. * * TTPOINT pattern latitude longitude */ else if (strcasecmp(t, "TTPOINT") == 0) { struct ttloc_s *tl; int j; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); // Should make this a function/macro instead of repeating code. /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_POINT; strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->point.lat = 0; tl->point.lon = 0; /* Pattern: B and digits */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTPOINT command.\n", line); continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTPOINT pattern must begin with upper case 'B'.\n", line); } for (j=1; j<(int)(strlen(t)); j++) { if ( ! isdigit(t[j])) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTPOINT pattern must be B and digits only.\n", line); } } /* Latitude */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTPOINT command.\n", line); continue; } tl->point.lat = parse_ll(t,LAT,line); /* Longitude */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTPOINT command.\n", line); continue; } tl->point.lon = parse_ll(t,LON,line); /* temp debugging */ //for (j=0; jttloc_len; j++) { // dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, // p_tt_config->ttloc_ptr[j].pattern); //} } /* * TTVECTOR - Touch tone location with bearing and distance. * * TTVECTOR pattern latitude longitude scale unit */ else if (strcasecmp(t, "TTVECTOR") == 0) { struct ttloc_s *tl; int j; double scale; double meters; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_VECTOR; strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->vector.lat = 0; tl->vector.lon = 0; tl->vector.scale = 1; /* Pattern: B5bbbd... */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTVECTOR command.\n", line); continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTVECTOR pattern must begin with upper case 'B'.\n", line); } if (strncmp(t+1, "5bbb", 4) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTVECTOR pattern would normally contain \"5bbb\".\n", line); } for (j=1; j<(int)(strlen(t)); j++) { if ( ! isdigit(t[j]) && t[j] != 'b' && t[j] != 'd') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTVECTOR pattern must contain only B, digits, b, and d.\n", line); } } /* Latitude */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTVECTOR command.\n", line); continue; } tl->vector.lat = parse_ll(t,LAT,line); /* Longitude */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTVECTOR command.\n", line); continue; } tl->vector.lon = parse_ll(t,LON,line); /* Longitude */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing scale for TTVECTOR command.\n", line); continue; } scale = atof(t); /* Unit. */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing unit for TTVECTOR command.\n", line); continue; } meters = 0; for (j=0; jvector.scale = scale * meters; //dw_printf ("ttvector: %f meters\n", tl->vector.scale); /* temp debugging */ //for (j=0; jttloc_len; j++) { // dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, // p_tt_config->ttloc_ptr[j].pattern); //} } /* * TTGRID - Define a grid for touch tone locations. * * TTGRID pattern min-latitude min-longitude max-latitude max-longitude */ else if (strcasecmp(t, "TTGRID") == 0) { struct ttloc_s *tl; int j; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_GRID; strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->grid.lat0 = 0; tl->grid.lon0 = 0; tl->grid.lat9 = 0; tl->grid.lon9 = 0; /* Pattern: B [digit] x... y... */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTGRID command.\n", line); continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTGRID pattern must begin with upper case 'B'.\n", line); } for (j=1; j<(int)(strlen(t)); j++) { if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTGRID pattern must be B, optional digit, xxx, yyy.\n", line); } } /* Minimum Latitude - all zeros in received data */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line); continue; } tl->grid.lat0 = parse_ll(t,LAT,line); /* Minimum Longitude - all zeros in received data */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line); continue; } tl->grid.lon0 = parse_ll(t,LON,line); /* Maximum Latitude - all nines in received data */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line); continue; } tl->grid.lat9 = parse_ll(t,LAT,line); /* Maximum Longitude - all nines in received data */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line); continue; } tl->grid.lon0 = parse_ll(t,LON,line); /* temp debugging */ //for (j=0; jttloc_len; j++) { // dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, // p_tt_config->ttloc_ptr[j].pattern); //} } /* * TTUTM - Specify UTM zone for touch tone locations. * * TTUTM pattern zone [ scale [ x-offset y-offset ] ] */ else if (strcasecmp(t, "TTUTM") == 0) { struct ttloc_s *tl; int j; double dlat, dlon; long lerr; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_UTM; strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->utm.lzone = 0; tl->utm.scale = 1; tl->utm.x_offset = 0; tl->utm.y_offset = 0; /* Pattern: B [digit] x... y... */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTUTM command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTUTM pattern must begin with upper case 'B'.\n", line); p_tt_config->ttloc_len--; continue; } for (j=1; j < (int)(strlen(t)); j++) { if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTUTM pattern must be B, optional digit, xxx, yyy.\n", line); // Bail out somehow. continue would match inner for. } } /* Zone 1 - 60 and optional latitudinal letter. */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing zone for TTUTM command.\n", line); p_tt_config->ttloc_len--; continue; } tl->utm.lzone = parse_utm_zone (t, &(tl->utm.latband), &(tl->utm.hemi)); /* Optional scale. */ t = split(NULL,0); if (t != NULL) { tl->utm.scale = atof(t); /* Optional x offset. */ t = split(NULL,0); if (t != NULL) { tl->utm.x_offset = atof(t); /* Optional y offset. */ t = split(NULL,0); if (t != NULL) { tl->utm.y_offset = atof(t); } } } /* Practice run to see if conversion might fail later with actual location. */ lerr = Convert_UTM_To_Geodetic(tl->utm.lzone, tl->utm.hemi, tl->utm.x_offset + 5 * tl->utm.scale, tl->utm.y_offset + 5 * tl->utm.scale, &dlat, &dlon); if (lerr != 0) { char message [300]; utm_error_string (lerr, message); text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message); p_tt_config->ttloc_len--; continue; } } /* * TTUSNG, TTMGRS - Specify zone/square for touch tone locations. * * TTUSNG pattern zone_square * TTMGRS pattern zone_square */ else if (strcasecmp(t, "TTUSNG") == 0 || strcasecmp(t, "TTMGRS") == 0) { struct ttloc_s *tl; int j; int num_x, num_y; double lat, lon; long lerr; char message[300]; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); // TODO1.2: in progress... if (strcasecmp(t, "TTMGRS") == 0) { tl->type = TTLOC_MGRS; } else { tl->type = TTLOC_USNG; } strlcpy(tl->pattern, "", sizeof(tl->pattern)); strlcpy(tl->mgrs.zone, "", sizeof(tl->mgrs.zone)); /* Pattern: B [digit] x... y... */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTUSNG/TTMGRS command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTUSNG/TTMGRS pattern must begin with upper case 'B'.\n", line); p_tt_config->ttloc_len--; continue; } num_x = 0; num_y = 0; for (j=1; j<(int)(strlen(t)); j++) { if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTUSNG/TTMGRS pattern must be B, optional digit, xxx, yyy.\n", line); // Bail out somehow. continue would match inner for. } if (t[j] == 'x') num_x++; if (t[j] == 'y') num_y++; } if (num_x < 1 || num_x > 5 || num_x != num_y) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTUSNG/TTMGRS must have 1 to 5 x and same number y.\n", line); p_tt_config->ttloc_len--; continue; } /* Zone 1 - 60 and optional latitudinal letter. */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing zone & square for TTUSNG/TTMGRS command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->mgrs.zone, t, sizeof(tl->mgrs.zone)); /* Try converting it rather do our own error checking. */ if (tl->type == TTLOC_MGRS) { lerr = Convert_MGRS_To_Geodetic (tl->mgrs.zone, &lat, &lon); } else { lerr = Convert_USNG_To_Geodetic (tl->mgrs.zone, &lat, &lon); } if (lerr != 0) { mgrs_error_string (lerr, message); text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid USNG/MGRS zone & square: %s\n%s\n", line, tl->mgrs.zone, message); p_tt_config->ttloc_len--; continue; } /* Should be the end. */ t = split(NULL,0); if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unexpected stuff at end ignored: %s\n", line, t); } } /* * TTMHEAD - Define pattern to be used for Maidenhead Locator. * * TTMHEAD pattern [ prefix ] * * Pattern would be B[0-9A-D]xxxx... * Optional prefix is 10, 6, or 4 digits. * * The total number of digts in both must be 4, 6, 10, or 12. */ else if (strcasecmp(t, "TTMHEAD") == 0) { // TODO1.3: TTMHEAD needs testing. struct ttloc_s *tl; int j; int k; int count_x; int count_other; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_MHEAD; strlcpy(tl->pattern, "", sizeof(tl->pattern)); strlcpy(tl->mhead.prefix, "", sizeof(tl->mhead.prefix)); /* Pattern: B, optional additional button, some number of xxxx... for matching */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTMHEAD command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMHEAD pattern must begin with upper case 'B'.\n", line); p_tt_config->ttloc_len--; continue; } /* Optionally one of 0-9ABCD */ if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) { j = 2; } else { j = 1; } count_x = 0; count_other = 0; for (k = j ; k < (int)(strlen(t)); k++) { if (t[k] == 'x') count_x++; else count_other++; } if (count_other != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMHEAD must have only lower case x to match received data.\n", line); p_tt_config->ttloc_len--; continue; } // optional prefix t = split(NULL,0); if (t != NULL) { char mh[30]; strlcpy(tl->mhead.prefix, t, sizeof(tl->mhead.prefix)); if (!alldigits(t) || (strlen(t) != 4 && strlen(t) != 6 && strlen(t) != 10)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMHEAD prefix must be 4, 6, or 10 digits.\n", line); p_tt_config->ttloc_len--; continue; } if (tt_mhead_to_text(t, 0, mh, sizeof(mh)) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMHEAD prefix not a valid DTMF sequence.\n", line); p_tt_config->ttloc_len--; continue; } } k = strlen(tl->mhead.prefix) + count_x; if (k != 4 && k != 6 && k != 10 && k != 12 ) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMHEAD prefix and user data must have a total of 4, 6, 10, or 12 digits.\n", line); p_tt_config->ttloc_len--; continue; } } /* * TTSATSQ - Define pattern to be used for Satellite square. * * TTSATSQ pattern * * Pattern would be B[0-9A-D]xxxx * * Must have exactly 4 x. */ else if (strcasecmp(t, "TTSATSQ") == 0) { // TODO1.2: TTSATSQ To be continued... struct ttloc_s *tl; int j; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_SATSQ; strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->point.lat = 0; tl->point.lon = 0; /* Pattern: B, optional additional button, exactly xxxx for matching */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTSATSQ command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTSATSQ pattern must begin with upper case 'B'.\n", line); p_tt_config->ttloc_len--; continue; } /* Optionally one of 0-9ABCD */ if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) { j = 2; } else { j = 1; } if (strcmp(t+j, "xxxx") != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTSATSQ pattern must end with exactly xxxx in lower case.\n", line); p_tt_config->ttloc_len--; continue; } /* temp debugging */ //for (j=0; jttloc_len; j++) { // dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, // p_tt_config->ttloc_ptr[j].pattern); //} } /* * TTAMBIG - Define pattern to be used for Object Location Ambiguity. * * TTAMBIG pattern * * Pattern would be B[0-9A-D]x * * Must have exactly one x. */ else if (strcasecmp(t, "TTAMBIG") == 0) { // TODO1.3: TTAMBIG To be continued... struct ttloc_s *tl; int j; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_AMBIG; strlcpy(tl->pattern, "", sizeof(tl->pattern)); /* Pattern: B, optional additional button, exactly x for matching */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTAMBIG command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTAMBIG pattern must begin with upper case 'B'.\n", line); p_tt_config->ttloc_len--; continue; } /* Optionally one of 0-9ABCD */ if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) { j = 2; } else { j = 1; } if (strcmp(t+j, "x") != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTAMBIG pattern must end with exactly one x in lower case.\n", line); p_tt_config->ttloc_len--; continue; } /* temp debugging */ //for (j=0; jttloc_len; j++) { // dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, // p_tt_config->ttloc_ptr[j].pattern); //} } /* * TTMACRO - Define compact message format with full expansion * * TTMACRO pattern definition * * pattern can contain: * 0-9 which must match exactly. * In version 1.2, also allow A,B,C,D for exact match. * x, y, z which are used for matching of variable fields. * * definition can contain: * 0-9, A, B, C, D, *, #, x, y, z. * Not sure why # was included in there. * * new for version 1.3 - in progress * * AA{objname} * AB{symbol} * AC{call} * * These provide automatic conversion from plain text to the TT encoding. * */ else if (strcasecmp(t, "TTMACRO") == 0) { struct ttloc_s *tl; int j; int p_count[3], d_count[3]; int tt_error = 0; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_MACRO; strlcpy(tl->pattern, "", sizeof(tl->pattern)); /* Pattern: Any combination of digits, x, y, and z. */ /* Also make note of which letters are used in pattern and defintition. */ /* Version 1.2: also allow A,B,C,D in the pattern. */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTMACRO command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); p_count[0] = p_count[1] = p_count[2] = 0; for (j=0; j<(int)(strlen(t)); j++) { if ( strchr ("0123456789ABCDxyz", t[j]) == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMACRO pattern can contain only digits, A, B, C, D, and lower case x, y, or z.\n", line); p_tt_config->ttloc_len--; continue; } /* Count how many x, y, z in the pattern. */ if (t[j] >= 'x' && t[j] <= 'z') { p_count[t[j]-'x']++; } } //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Line %d: TTMACRO pattern \"%s\" p_count = %d %d %d.\n", line, t, p_count[0], p_count[1], p_count[2]); /* Next we should find the definition. */ /* It can contain touch tone characters and lower case x, y, z for substitutions. */ t = split(NULL,1);; if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing definition for TTMACRO command.\n", line); tl->macro.definition = ""; /* Don't die on null pointer later. */ p_tt_config->ttloc_len--; continue; } /* Make a pass over the definition, looking for the xx{...} substitutions. */ /* These are done just once when reading the configuration file. */ char *pi; char *ps; char stemp[100]; // text inside of xx{...} char ttemp[300]; // Converted to tone sequences. char otemp[1000]; // Result after any substitutions. char t2[2]; strlcpy (otemp, "", sizeof(otemp)); t2[1] = '\0'; pi = t; while (*pi == ' ' || *pi == '\t') { pi++; } for ( ; *pi != '\0'; pi++) { if (strncmp(pi, "AC{", 3) == 0) { // Convert to fixed length 10 digit callsign. pi += 3; ps = stemp; while (*pi != '}' && *pi != '*' && *pi != '\0') { *ps++ = *pi++; } if (*pi == '}') { *ps = '\0'; if (tt_text_to_call10 (stemp, 0, ttemp) == 0) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("DEBUG Line %d: AC{%s} -> AC%s\n", line, stemp, ttemp); strlcat (otemp, "AC", sizeof(otemp)); strlcat (otemp, ttemp, sizeof(otemp)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: AC{%s} could not be converted to tones for callsign.\n", line, stemp); tt_error++; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: AC{... is missing matching } in TTMACRO definition.\n", line); tt_error++; } } else if (strncmp(pi, "AA{", 3) == 0) { // Convert to object name. pi += 3; ps = stemp; while (*pi != '}' && *pi != '*' && *pi != '\0') { *ps++ = *pi++; } if (*pi == '}') { *ps = '\0'; if (strlen(stemp) > 9) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Object name %s has been truncated to 9 characters.\n", line, stemp); stemp[9] = '\0'; } if (tt_text_to_two_key (stemp, 0, ttemp) == 0) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("DEBUG Line %d: AA{%s} -> AA%s\n", line, stemp, ttemp); strlcat (otemp, "AA", sizeof(otemp)); strlcat (otemp, ttemp, sizeof(otemp)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: AA{%s} could not be converted to tones for object name.\n", line, stemp); tt_error++; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: AA{... is missing matching } in TTMACRO definition.\n", line); tt_error++; } } else if (strncmp(pi, "AB{", 3) == 0) { // Attempt conversion from description to symbol code. pi += 3; ps = stemp; while (*pi != '}' && *pi != '*' && *pi != '\0') { *ps++ = *pi++; } if (*pi == '}') { char symtab; char symbol; *ps = '\0'; // First try to find something matching the description. if (symbols_code_from_description (' ', stemp, &symtab, &symbol) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Couldn't convert \"%s\" to APRS symbol code. Using default.\n", line, stemp); symtab = '\\'; // Alternate symbol = 'A'; // Box } // Convert symtab(overlay) & symbol to tone sequence. symbols_to_tones (symtab, symbol, ttemp, sizeof(ttemp)); //text_color_set(DW_COLOR_DEBUG); //dw_printf ("DEBUG config file Line %d: AB{%s} -> %s\n", line, stemp, ttemp); strlcat (otemp, ttemp, sizeof(otemp)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: AB{... is missing matching } in TTMACRO definition.\n", line); tt_error++; } } else if (strncmp(pi, "CA{", 3) == 0) { // Convert to enhanced comment that can contain any ASCII character. pi += 3; ps = stemp; while (*pi != '}' && *pi != '*' && *pi != '\0') { *ps++ = *pi++; } if (*pi == '}') { *ps = '\0'; if (tt_text_to_ascii2d (stemp, 0, ttemp) == 0) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("DEBUG Line %d: CA{%s} -> CA%s\n", line, stemp, ttemp); strlcat (otemp, "CA", sizeof(otemp)); strlcat (otemp, ttemp, sizeof(otemp)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: CA{%s} could not be converted to tones for enhanced comment.\n", line, stemp); tt_error++; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: CA{... is missing matching } in TTMACRO definition.\n", line); tt_error++; } } else if (strchr("0123456789ABCD*#xyz", *pi) != NULL) { t2[0] = *pi; strlcat (otemp, t2, sizeof(otemp)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMACRO definition can contain only 0-9, A, B, C, D, *, #, x, y, z.\n", line); tt_error++; } } /* Make sure that number of x, y, z, in pattern and definition match. */ d_count[0] = d_count[1] = d_count[2] = 0; for (j=0; j<(int)(strlen(otemp)); j++) { if (otemp[j] >= 'x' && otemp[j] <= 'z') { d_count[otemp[j]-'x']++; } } /* A little validity checking. */ for (j=0; j<3; j++) { if (p_count[j] > 0 && d_count[j] == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: '%c' is in TTMACRO pattern but is not used in definition.\n", line, 'x'+j); } if (d_count[j] > 0 && p_count[j] == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: '%c' is referenced in TTMACRO definition but does not appear in the pattern.\n", line, 'x'+j); } } //text_color_set(DW_COLOR_DEBUG); //dw_printf ("DEBUG Config Line %d: %s -> %s\n", line, t, otemp); if (tt_error == 0) { tl->macro.definition = strdup(otemp); } else { p_tt_config->ttloc_len--; } } /* * TTOBJ - TT Object Report options. * * TTOBJ recv-chan where-to [ via-path ] * * whereto is any combination of transmit channel, APP, IG. */ else if (strcasecmp(t, "TTOBJ") == 0) { int r, x = -1; int app = 0; int ig = 0; char *p; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing DTMF receive channel for TTOBJ command.\n", line); continue; } r = atoi(t); if (r < 0 || r > MAX_CHANS-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: DTMF receive channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if ( ! p_audio_config->achan[r].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TTOBJ DTMF receive channel %d is not valid.\n", line, r); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing transmit channel for TTOBJ command.\n", line); continue; } // Can have any combination of number, APP, IG. // Would it be easier with strtok? for (p = t; *p != '\0'; p++) { if (isdigit(*p)) { x = *p - '0'; if (x < 0 || x > MAX_CHANS-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); x = -1; } else if ( ! p_audio_config->achan[x].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TTOBJ transmit channel %d is not valid.\n", line, x); x = -1; } } else if (*p == 'a' || *p == 'A') { app = 1; } else if (*p == 'i' || *p == 'I') { ig = 1; } else if (strchr("pPgG,", *p) != NULL) { ; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Expected comma separated list with some combination of transmit channel, APP, and IG.\n", line); } } // This enables the DTMF decoder on the specified channel. // Additional channels can be enabled with the DTMF command. // Note that DTMF command does not enable the APRStt gateway. //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Debug TTOBJ r=%d, x=%d, app=%d, ig=%d\n", r, x, app, ig); p_audio_config->achan[r].dtmf_decode = DTMF_DECODE_ON; p_tt_config->gateway_enabled = 1; p_tt_config->obj_recv_chan = r; p_tt_config->obj_xmit_chan = x; p_tt_config->obj_send_to_app = app; p_tt_config->obj_send_to_ig = ig; t = split(NULL,0); if (t != NULL) { if (check_via_path(t) >= 0) { strlcpy (p_tt_config->obj_xmit_via, t, sizeof(p_tt_config->obj_xmit_via)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: invalid via path.\n", line); } } } /* * TTERR - TT responses for success or errors. * * TTERR msg_id method text... */ else if (strcasecmp(t, "TTERR") == 0) { int n, msg_num; char *p; char method[AX25_MAX_ADDR_LEN]; int ssid; int heard; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing message identifier for TTERR command.\n", line); continue; } msg_num = -1; for (n=0; n= 0 && msg_num < TT_ERROR_MAXP1); strlcpy (p_tt_config->response[msg_num].method, method, sizeof(p_tt_config->response[msg_num].method)); // TODO1.3: Need SSID too! strlcpy (p_tt_config->response[msg_num].mtext, t, sizeof(p_tt_config->response[msg_num].mtext)); p_tt_config->response[msg_num].mtext[TT_MTEXT_LEN-1] = '\0'; } /* * TTSTATUS - TT custom status messages. * * TTSTATUS status_id text... */ else if (strcasecmp(t, "TTSTATUS") == 0) { int status_num; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing status number for TTSTATUS command.\n", line); continue; } status_num = atoi(t); if (status_num < 1 || status_num > 9) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Status number for TTSTATUS command must be in range of 1 to 9.\n", line); continue; } t = split(NULL,1);; if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing status text for TTSTATUS command.\n", line); continue; } //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Line %d: TTSTATUS debug %d \"%s\"\n", line, status_num, t); while (*t == ' ' || *t == '\t') t++; // remove leading white space. strlcpy (p_tt_config->status[status_num], t, sizeof(p_tt_config->status[status_num])); } /* * TTCMD - Command to run when valid sequence is received. * Any text generated will be sent back to user. * * TTCMD ... */ else if (strcasecmp(t, "TTCMD") == 0) { t = split(NULL,1); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing command for TTCMD command.\n", line); continue; } strlcpy (p_tt_config->ttcmd, t, sizeof(p_tt_config->ttcmd)); } /* * ==================== Internet gateway ==================== */ /* * IGSERVER - Name of IGate server. * * IGSERVER hostname [ port ] -- original implementation. * * IGSERVER hostname:port -- more in line with usual conventions. */ else if (strcasecmp(t, "IGSERVER") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing IGate server name for IGSERVER command.\n", line); continue; } strlcpy (p_igate_config->t2_server_name, t, sizeof(p_igate_config->t2_server_name)); /* If there is a : in the name, split it out as the port number. */ t = strchr (p_igate_config->t2_server_name, ':'); if (t != NULL) { *t = '\0'; t++; int n = atoi(t); if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) { p_igate_config->t2_server_port = n; } else { p_igate_config->t2_server_port = DEFAULT_IGATE_PORT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid port number for IGate server. Using default %d.\n", line, p_igate_config->t2_server_port); } } /* Alternatively, the port number could be separated by white space. */ t = split(NULL,0); if (t != NULL) { int n = atoi(t); if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) { p_igate_config->t2_server_port = n; } else { p_igate_config->t2_server_port = DEFAULT_IGATE_PORT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid port number for IGate server. Using default %d.\n", line, p_igate_config->t2_server_port); } } //dw_printf ("DEBUG server=%s port=%d\n", p_igate_config->t2_server_name, p_igate_config->t2_server_port); //exit (0); } /* * IGLOGIN - Login callsign and passcode for IGate server * * IGLOGIN callsign passcode */ else if (strcasecmp(t, "IGLOGIN") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing login callsign for IGLOGIN command.\n", line); continue; } // TODO: Wouldn't hurt to do validity checking of format. strlcpy (p_igate_config->t2_login, t, sizeof(p_igate_config->t2_login)); t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing passcode for IGLOGIN command.\n", line); continue; } strlcpy (p_igate_config->t2_passcode, t, sizeof(p_igate_config->t2_passcode)); } /* * IGTXVIA - Transmit channel and VIA path for messages from IGate server * * IGTXVIA channel [ path ] */ else if (strcasecmp(t, "IGTXVIA") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing transmit channel for IGTXVIA command.\n", line); continue; } n = atoi(t); if (n < 0 || n > MAX_CHANS-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } p_igate_config->tx_chan = n; t = split(NULL,0); if (t != NULL) { #if 1 // proper checking n = check_via_path(t); if (n >= 0) { p_igate_config->max_digi_hops = n; p_igate_config->tx_via[0] = ','; strlcpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-1); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: invalid via path.\n", line); } #else // previously char *p; p_igate_config->tx_via[0] = ','; strlcpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-1); for (p = p_igate_config->tx_via; *p != '\0'; p++) { if (islower(*p)) { *p = toupper(*p); /* silently force upper case. */ } } #endif } } /* * IGFILTER - IGate Server side filters. * Is this name too confusing. Too similar to FILTER IG 0 ... * Maybe SSFILTER suggesting Server Side. * SUBSCRIBE might be better because it's not a filter that limits. * * IGFILTER filter-spec ... */ else if (strcasecmp(t, "IGFILTER") == 0) { t = split(NULL,1); /* Take rest of line as one string. */ if (p_igate_config->t2_filter != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Warning - Earlier IGFILTER value will be replaced by this one.\n", line); continue; } if (t != NULL && strlen(t) > 0) { p_igate_config->t2_filter = strdup (t); } } /* * IGTXLIMIT - Limit transmissions during 1 and 5 minute intervals. * * IGTXLIMIT one-minute-limit five-minute-limit */ else if (strcasecmp(t, "IGTXLIMIT") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing one minute limit for IGTXLIMIT command.\n", line); continue; } n = atoi(t); if (n < 1) { p_igate_config->tx_limit_1 = 1; } else if (n <= IGATE_TX_LIMIT_1_MAX) { p_igate_config->tx_limit_1 = n; } else { p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_MAX; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: One minute transmit limit has been reduced to %d.\n", line, p_igate_config->tx_limit_1); dw_printf ("You won't make friends by setting a limit this high.\n"); } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing five minute limit for IGTXLIMIT command.\n", line); continue; } n = atoi(t); if (n < 1) { p_igate_config->tx_limit_5 = 1; } else if (n <= IGATE_TX_LIMIT_5_MAX) { p_igate_config->tx_limit_5 = n; } else { p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_MAX; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Five minute transmit limit has been reduced to %d.\n", line, p_igate_config->tx_limit_5); dw_printf ("You won't make friends by setting a limit this high.\n"); } } /* * IGMSP - Number of times to send position of message sender. * * IGMSP n */ else if (strcasecmp(t, "IGMSP") == 0) { t = split(NULL,0); if (t != NULL) { int n = atoi(t); if (n >= 0 && n <= 10) { p_igate_config->igmsp = n; } else { p_igate_config->igmsp = 1; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable number of times for message sender position. Using default 1.\n", line); } } else { p_igate_config->igmsp = 1; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing number of times for message sender position. Using default 1.\n", line); } } /* * SATGATE - Special SATgate mode to delay packets heard directly. * * SATGATE [ n ] */ else if (strcasecmp(t, "SATGATE") == 0) { text_color_set(DW_COLOR_INFO); dw_printf ("Line %d: SATGATE is pretty useless and will be removed in a future version.\n", line); t = split(NULL,0); if (t != NULL) { int n = atoi(t); if (n >= MIN_SATGATE_DELAY && n <= MAX_SATGATE_DELAY) { p_igate_config->satgate_delay = n; } else { p_igate_config->satgate_delay = DEFAULT_SATGATE_DELAY; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable SATgate delay. Using default.\n", line); } } else { p_igate_config->satgate_delay = DEFAULT_SATGATE_DELAY; } } /* * ==================== All the left overs ==================== */ /* * AGWPORT - Port number for "AGW TCPIP Socket Interface" * * In version 1.2 we allow 0 to disable listening. */ else if (strcasecmp(t, "AGWPORT") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing port number for AGWPORT command.\n", line); continue; } n = atoi(t); if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) { p_misc_config->agwpe_port = n; } else { p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid port number for AGW TCPIP Socket Interface. Using %d.\n", line, p_misc_config->agwpe_port); } } /* * KISSPORT - Port number for KISS over IP. */ else if (strcasecmp(t, "KISSPORT") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing port number for KISSPORT command.\n", line); continue; } n = atoi(t); if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) { p_misc_config->kiss_port = n; } else { p_misc_config->kiss_port = DEFAULT_KISS_PORT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid port number for KISS TCPIP Socket Interface. Using %d.\n", line, p_misc_config->kiss_port); } } /* * NULLMODEM name [ speed ] - Device name for serial port or our end of the virtual "null modem" * SERIALKISS name [ speed ] * * Version 1.5: Added SERIALKISS which is equivalent to NULLMODEM. * The original name sort of made sense when it was used only for one end of a virtual * null modem cable on Windows only. Now it is also available for Linux. * TODO1.5: In retrospect, this doesn't seem like such a good name. */ else if (strcasecmp(t, "NULLMODEM") == 0 || strcasecmp(t, "SERIALKISS") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing serial port name on line %d.\n", line); continue; } else { if (strlen(p_misc_config->kiss_serial_port) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Warning serial port name on line %d replaces earlier value.\n", line); } strlcpy (p_misc_config->kiss_serial_port, t, sizeof(p_misc_config->kiss_serial_port)); p_misc_config->kiss_serial_speed = 0; p_misc_config->kiss_serial_poll = 0; } t = split(NULL,0); if (t != NULL) { p_misc_config->kiss_serial_speed = atoi(t); } } /* * SERIALKISSPOLL name - Poll for serial port name that might come and go. * e.g. /dev/rfcomm0 for bluetooth. */ else if (strcasecmp(t, "SERIALKISSPOLL") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing serial port name on line %d.\n", line); continue; } else { if (strlen(p_misc_config->kiss_serial_port) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Warning serial port name on line %d replaces earlier value.\n", line); } strlcpy (p_misc_config->kiss_serial_port, t, sizeof(p_misc_config->kiss_serial_port)); p_misc_config->kiss_serial_speed = 0; p_misc_config->kiss_serial_poll = 1; // set polling. } } /* * GPSNMEA - Device name for reading from GPS receiver. */ else if (strcasecmp(t, "gpsnmea") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Missing serial port name for GPS receiver.\n", line); continue; } else { strlcpy (p_misc_config->gpsnmea_port, t, sizeof(p_misc_config->gpsnmea_port)); } } /* * GPSD - Use GPSD server. * * GPSD [ host [ port ] ] */ else if (strcasecmp(t, "gpsd") == 0) { #if __WIN32__ text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: The GPSD interface is not available for Windows.\n", line); continue; #elif ENABLE_GPSD strlcpy (p_misc_config->gpsd_host, "localhost", sizeof(p_misc_config->gpsd_host)); p_misc_config->gpsd_port = atoi(DEFAULT_GPSD_PORT); t = split(NULL,0); if (t != NULL) { strlcpy (p_misc_config->gpsd_host, t, sizeof(p_misc_config->gpsd_host)); t = split(NULL,0); if (t != NULL) { int n = atoi(t); if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) { p_misc_config->gpsd_port = n; } else { p_misc_config->gpsd_port = atoi(DEFAULT_GPSD_PORT); text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid port number for GPSD Socket Interface. Using default of %d.\n", line, p_misc_config->gpsd_port); } } } #else text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: The GPSD interface has not been enabled.\n", line); dw_printf ("Install gpsd and libgps-dev packages then rebuild direwolf.\n"); continue; #endif } /* * WAYPOINT - Generate WPL NMEA sentences for display on map. * * WAYPOINT serial-device [ formats ] * */ else if (strcasecmp(t, "waypoint") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing device name for WAYPOINT on line %d.\n", line); continue; } else { strlcpy (p_misc_config->waypoint_port, t, sizeof(p_misc_config->waypoint_port)); } t = split(NULL,1); if (t != NULL) { for ( ; *t != '\0' ; t++ ) { switch (toupper(*t)) { case 'N': p_misc_config->waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; break; case 'G': p_misc_config->waypoint_formats |= WPT_FORMAT_GARMIN; break; case 'M': p_misc_config->waypoint_formats |= WPT_FORMAT_MAGELLAN; break; case 'K': p_misc_config->waypoint_formats |= WPT_FORMAT_KENWOOD; break; case ' ': case ',': break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Invalid output format '%c' for WAYPOINT on line %d.\n", *t, line); break; } } } } /* * LOGDIR - Directory name for automatically named daily log files. Use "." for current working directory. */ else if (strcasecmp(t, "logdir") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing directory name for LOGDIR on line %d.\n", line); continue; } else { if (strlen(p_misc_config->log_path) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: LOGDIR on line %d is replacing an earlier LOGDIR or LOGFILE.\n", line); } p_misc_config->log_daily_names = 1; strlcpy (p_misc_config->log_path, t, sizeof(p_misc_config->log_path)); } t = split(NULL,0); if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: LOGDIR on line %d should have directory path and nothing more.\n", line); } } /* * LOGFILE - Log file name, including any directory part. */ else if (strcasecmp(t, "logfile") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing file name for LOGFILE on line %d.\n", line); continue; } else { if (strlen(p_misc_config->log_path) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: LOGFILE on line %d is replacing an earlier LOGDIR or LOGFILE.\n", line); } p_misc_config->log_daily_names = 0; strlcpy (p_misc_config->log_path, t, sizeof(p_misc_config->log_path)); } t = split(NULL,0); if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: LOGFILE on line %d should have file name and nothing more.\n", line); } } /* * BEACON channel delay every message * * Original handcrafted style. Removed in version 1.0. */ else if (strcasecmp(t, "BEACON") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Old style 'BEACON' has been replaced with new commands.\n", line); dw_printf ("Use PBEACON, OBEACON, or CBEACON instead.\n"); } /* * PBEACON keyword=value ... * OBEACON keyword=value ... * TBEACON keyword=value ... * CBEACON keyword=value ... * IBEACON keyword=value ... * * New style with keywords for options. */ else if (strcasecmp(t, "PBEACON") == 0 || strcasecmp(t, "OBEACON") == 0 || strcasecmp(t, "TBEACON") == 0 || strcasecmp(t, "CBEACON") == 0 || strcasecmp(t, "IBEACON") == 0) { if (p_misc_config->num_beacons < MAX_BEACONS) { memset (&(p_misc_config->beacon[p_misc_config->num_beacons]), 0, sizeof(struct beacon_s)); if (strcasecmp(t, "PBEACON") == 0) { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_POSITION; } else if (strcasecmp(t, "OBEACON") == 0) { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_OBJECT; } else if (strcasecmp(t, "TBEACON") == 0) { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_TRACKER; } else if (strcasecmp(t, "IBEACON") == 0) { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_IGATE; } else { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_CUSTOM; } /* Save line number because some errors will be reported later. */ p_misc_config->beacon[p_misc_config->num_beacons].lineno = line; if (beacon_options(t + strlen("xBEACON") + 1, &(p_misc_config->beacon[p_misc_config->num_beacons]), line, p_audio_config)) { p_misc_config->num_beacons++; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Maximum number of beacons exceeded on line %d.\n", line); continue; } } /* * SMARTBEACONING [ fast_speed fast_rate slow_speed slow_rate turn_time turn_angle turn_slope ] * * Parameters must be all or nothing. */ else if (strcasecmp(t, "SMARTBEACON") == 0 || strcasecmp(t, "SMARTBEACONING") == 0) { int n; #define SB_NUM(name,sbvar,minn,maxx,unit) \ t = split(NULL,0); \ if (t == NULL) { \ if (strcmp(name, "fast speed") == 0) { \ p_misc_config->sb_configured = 1; \ continue; \ } \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name); \ continue; \ } \ n = atoi(t); \ if (n >= minn && n <= maxx) { \ p_misc_config->sbvar = n; \ } \ else { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("Line %d: Invalid %s for SmartBeaconing. Using default %d %s.\n", \ line, name, p_misc_config->sbvar, unit); \ } #define SB_TIME(name,sbvar,minn,maxx,unit) \ t = split(NULL,0); \ if (t == NULL) { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name); \ continue; \ } \ n = parse_interval(t,line); \ if (n >= minn && n <= maxx) { \ p_misc_config->sbvar = n; \ } \ else { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("Line %d: Invalid %s for SmartBeaconing. Using default %d %s.\n", \ line, name, p_misc_config->sbvar, unit); \ } SB_NUM ("fast speed", sb_fast_speed, 2, 90, "MPH") SB_TIME ("fast rate", sb_fast_rate, 10, 300, "seconds") SB_NUM ("slow speed", sb_slow_speed, 1, 30, "MPH") SB_TIME ("slow rate", sb_slow_rate, 30, 3600, "seconds") SB_TIME ("turn time", sb_turn_time, 5, 180, "seconds") SB_NUM ("turn angle", sb_turn_angle, 5, 90, "degrees") SB_NUM ("turn slope", sb_turn_slope, 1, 255, "deg*mph") /* If I was ambitious, I might allow optional */ /* unit at end for miles or km / hour. */ p_misc_config->sb_configured = 1; } /* * ==================== AX.25 connected mode ==================== */ /* * FRACK n - Number of seconds to wait for ack to transmission. */ else if (strcasecmp(t, "FRACK") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for FRACK.\n", line); continue; } n = atoi(t); if (n >= AX25_T1V_FRACK_MIN && n <= AX25_T1V_FRACK_MAX) { p_misc_config->frack = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid FRACK time. Using default %d.\n", line, p_misc_config->frack); } } /* * RETRY n - Number of times to retry before giving up. */ else if (strcasecmp(t, "RETRY") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for RETRY.\n", line); continue; } n = atoi(t); if (n >= AX25_N2_RETRY_MIN && n <= AX25_N2_RETRY_MAX) { p_misc_config->retry = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid RETRY number. Using default %d.\n", line, p_misc_config->retry); } } /* * PACLEN n - Maximum number of bytes in information part. */ else if (strcasecmp(t, "PACLEN") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for PACLEN.\n", line); continue; } n = atoi(t); if (n >= AX25_N1_PACLEN_MIN && n <= AX25_N1_PACLEN_MAX) { p_misc_config->paclen = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid PACLEN value. Using default %d.\n", line, p_misc_config->paclen); } } /* * MAXFRAME n - Max frames to send before ACK. mod 8 "Window" size. * * Window size would make more sense but everyone else calls it MAXFRAME. */ else if (strcasecmp(t, "MAXFRAME") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for MAXFRAME.\n", line); continue; } n = atoi(t); if (n >= AX25_K_MAXFRAME_BASIC_MIN && n <= AX25_K_MAXFRAME_BASIC_MAX) { p_misc_config->maxframe_basic = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid MAXFRAME value outside range of %d to %d. Using default %d.\n", line, AX25_K_MAXFRAME_BASIC_MIN, AX25_K_MAXFRAME_BASIC_MAX, p_misc_config->maxframe_basic); } } /* * EMAXFRAME n - Max frames to send before ACK. mod 128 "Window" size. */ else if (strcasecmp(t, "EMAXFRAME") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for EMAXFRAME.\n", line); continue; } n = atoi(t); if (n >= AX25_K_MAXFRAME_EXTENDED_MIN && n <= AX25_K_MAXFRAME_EXTENDED_MAX) { p_misc_config->maxframe_extended = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid EMAXFRAME value outside of range %d to %d. Using default %d.\n", line, AX25_K_MAXFRAME_EXTENDED_MIN, AX25_K_MAXFRAME_EXTENDED_MAX, p_misc_config->maxframe_extended); } } /* * MAXV22 n - Max number of SABME sent before trying SABM. */ else if (strcasecmp(t, "MAXV22") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for MAXV22.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= AX25_N2_RETRY_MAX) { p_misc_config->maxv22 = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid MAXV22 number. Will use half of RETRY.\n", line); } } /* * V20 address [ address ... ] - Stations known to support only AX.25 v2.0. * When connecting to these, skip SABME and go right to SABM. * Possible to have multiple and they are cummulative. */ else if (strcasecmp(t, "V20") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing address(es) for V20.\n", line); continue; } while (t != NULL) { int const strict = 2; char call_no_ssid[AX25_MAX_ADDR_LEN]; int ssid, heard; if (ax25_parse_addr (AX25_DESTINATION, t, strict, call_no_ssid, &ssid, &heard)) { p_misc_config->v20_addrs = (char**)realloc (p_misc_config->v20_addrs, sizeof(char*) * (p_misc_config->v20_count + 1)); p_misc_config->v20_addrs[p_misc_config->v20_count++] = strdup(t); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid station address for V20 command.\n", line); // continue processing any others following. } t = split(NULL,0); } } /* * NOXID address [ address ... ] - Stations known not to understand XID. * After connecting to these (with v2.2 obviously), don't try using XID commmand. * AX.25 for Linux is the one known case so far. * Possible to have multiple and they are cummulative. */ else if (strcasecmp(t, "NOXID") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing address(es) for NOXID.\n", line); continue; } while (t != NULL) { int const strict = 2; char call_no_ssid[AX25_MAX_ADDR_LEN]; int ssid, heard; if (ax25_parse_addr (AX25_DESTINATION, t, strict, call_no_ssid, &ssid, &heard)) { p_misc_config->noxid_addrs = (char**)realloc (p_misc_config->noxid_addrs, sizeof(char*) * (p_misc_config->noxid_count + 1)); p_misc_config->noxid_addrs[p_misc_config->noxid_count++] = strdup(t); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid station address for NOXID command.\n", line); // continue processing any others following. } t = split(NULL,0); } } /* * Invalid command. */ else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Unrecognized command '%s' on line %d.\n", t, line); } } fclose (fp); /* * A little error checking for option interactions. */ /* * Require that MYCALL be set when digipeating or IGating. * * Suggest that beaconing be enabled when digipeating. */ int i, j, k, b; for (i=0; ienabled[i][j]) { if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 || strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i); p_digi_config->enabled[i][j] = 0; } if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 || strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i); p_digi_config->enabled[i][j] = 0; } b = 0; for (k=0; knum_beacons; k++) { if (p_misc_config->beacon[k].sendto_chan == j) b++; } if (b == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Beaconing should be configured for channel %d when digipeating is enabled.\n", j); // It's a recommendation, not a requirement. // Was there some good reason to turn it off in earlier version? //p_digi_config->enabled[i][j] = 0; } } /* Connected mode digipeating. */ if (p_cdigi_config->enabled[i][j]) { if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 || strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i); p_cdigi_config->enabled[i][j] = 0; } if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 || strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i); p_cdigi_config->enabled[i][j] = 0; } b = 0; for (k=0; knum_beacons; k++) { if (p_misc_config->beacon[k].sendto_chan == j) b++; } if (b == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Beaconing should be configured for channel %d when digipeating is enabled.\n", j); // It's a recommendation, not a requirement. } } } if (p_audio_config->achan[i].valid && strlen(p_igate_config->t2_login) > 0) { if (strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for receive channel %d before Rx IGate is allowed.\n", i); strlcpy (p_igate_config->t2_login, "", sizeof(p_igate_config->t2_login)); } // Currently we can have only one transmit channel. // This might be generalized someday to allow more. if (p_igate_config->tx_chan >= 0 && ( strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "") == 0 || strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "N0CALL") == 0)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for transmit channel %d before Tx IGate is allowed.\n", i); p_igate_config->tx_chan = -1; } } } // Apply default IS>RF IGate filter if none specified. New in 1.4. // This will handle eventual case of multiple transmit channels. for (j=0; jachan[j].valid && strlen(p_igate_config->t2_login) > 0) { if (p_digi_config->filter_str[MAX_CHANS][j] == NULL) { p_digi_config->filter_str[MAX_CHANS][j] = strdup("i/30"); } } } // Terrible hack. But what can we do? if (p_misc_config->maxv22 < 0) { p_misc_config->maxv22 = p_misc_config->retry / 3; } } /* end config_init */ /* * Parse the PBEACON or OBEACON options. * Returns 1 for success, 0 for serious error. */ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config) { char *t; char temp_symbol[100]; int ok; char zone[8]; double easting = G_UNKNOWN; double northing = G_UNKNOWN; strlcpy (temp_symbol, "", sizeof(temp_symbol)); strlcpy (zone, "", sizeof(zone)); b->sendto_type = SENDTO_XMIT; b->sendto_chan = 0; b->delay = 60; b->slot = G_UNKNOWN; b->every = 600; //b->delay = 6; // temp test. //b->every = 3600; b->lat = G_UNKNOWN; b->lon = G_UNKNOWN; b->ambiguity = 0; b->alt_m = G_UNKNOWN; b->symtab = '/'; b->symbol = '-'; /* house */ b->freq = G_UNKNOWN; b->tone = G_UNKNOWN; b->offset = G_UNKNOWN; while ((t = split(NULL,0)) != NULL) { char keyword[20]; char value[200]; char *e; char *p; e = strchr(t, '='); if (e == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: No = found in, %s, on line %d.\n", t, line); return (0); } *e = '\0'; strlcpy (keyword, t, sizeof(keyword)); strlcpy (value, e+1, sizeof(value)); if (strcasecmp(keyword, "DELAY") == 0) { b->delay = parse_interval(value,line); } else if (strcasecmp(keyword, "SLOT") == 0) { int n = parse_interval(value,line); if ( n < 1 || n > 3600) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Beacon time slot, %d, must be in range of 1 to 3600 seconds.\n", line, n); continue; } b->slot = n; } else if (strcasecmp(keyword, "EVERY") == 0) { b->every = parse_interval(value,line); } else if (strcasecmp(keyword, "SENDTO") == 0) { if (value[0] == 'i' || value[0] == 'I') { b->sendto_type = SENDTO_IGATE; b->sendto_chan = 0; } else if (value[0] == 'r' || value[0] == 'R') { int n = atoi(value+1); if ( n < 0 || n >= MAX_CHANS || ! p_audio_config->achan[n].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Simulated receive on channel %d is not valid.\n", line, n); continue; } b->sendto_type = SENDTO_RECV; b->sendto_chan = n; } else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') { int n = atoi(value+1); if ( n < 0 || n >= MAX_CHANS || ! p_audio_config->achan[n].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); continue; } b->sendto_type = SENDTO_XMIT; b->sendto_chan = n; } else { int n = atoi(value); if ( n < 0 || n >= MAX_CHANS || ! p_audio_config->achan[n].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); continue; } b->sendto_type = SENDTO_XMIT; b->sendto_chan = n; } } else if (strcasecmp(keyword, "DEST") == 0) { b->dest = strdup(value); for (p = b->dest; *p != '\0'; p++) { if (islower(*p)) { *p = toupper(*p); /* silently force upper case. */ } } if (strlen(b->dest) > 9) { b->dest[9] = '\0'; } } else if (strcasecmp(keyword, "VIA") == 0) { #if 1 // proper checking if (check_via_path(value) >= 0) { b->via = strdup(value); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: invalid via path.\n", line); } #else // previously b->via = strdup(value); for (p = b->via; *p != '\0'; p++) { if (islower(*p)) { *p = toupper(*p); /* silently force upper case. */ } } #endif } else if (strcasecmp(keyword, "INFO") == 0) { b->custom_info = strdup(value); } else if (strcasecmp(keyword, "INFOCMD") == 0) { b->custom_infocmd = strdup(value); } else if (strcasecmp(keyword, "OBJNAME") == 0) { strlcpy(b->objname, value, sizeof(b->objname)); } else if (strcasecmp(keyword, "LAT") == 0) { b->lat = parse_ll (value, LAT, line); } else if (strcasecmp(keyword, "LONG") == 0 || strcasecmp(keyword, "LON") == 0) { b->lon = parse_ll (value, LON, line); } else if (strcasecmp(keyword, "AMBIGUITY") == 0 || strcasecmp(keyword, "AMBIG") == 0) { int n = atoi(value); if (n >= 0 && n <= 4) { b->ambiguity = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Location ambiguity, on line %d, must be in range of 0 to 4.\n", line); } } else if (strcasecmp(keyword, "ALT") == 0 || strcasecmp(keyword, "ALTITUDE") == 0) { b->alt_m = atof(value); } else if (strcasecmp(keyword, "ZONE") == 0) { strlcpy(zone, value, sizeof(zone)); } else if (strcasecmp(keyword, "EAST") == 0 || strcasecmp(keyword, "EASTING") == 0) { easting = atof(value); } else if (strcasecmp(keyword, "NORTH") == 0 || strcasecmp(keyword, "NORTHING") == 0) { northing = atof(value); } else if (strcasecmp(keyword, "SYMBOL") == 0) { /* Defer processing in case overlay appears later. */ strlcpy (temp_symbol, value, sizeof(temp_symbol)); } else if (strcasecmp(keyword, "OVERLAY") == 0) { if (strlen(value) == 1 && (isupper(value[0]) || isdigit(value[0]))) { b->symtab = value[0]; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Overlay must be one character in range of 0-9 or A-Z, upper case only, on line %d.\n", line); } } else if (strcasecmp(keyword, "POWER") == 0) { b->power = atoi(value); } else if (strcasecmp(keyword, "HEIGHT") == 0) { b->height = atoi(value); } else if (strcasecmp(keyword, "GAIN") == 0) { b->gain = atoi(value); } else if (strcasecmp(keyword, "DIR") == 0 || strcasecmp(keyword, "DIRECTION") == 0) { strlcpy(b->dir, value, sizeof(b->dir)); } else if (strcasecmp(keyword, "FREQ") == 0) { b->freq = atof(value); } else if (strcasecmp(keyword, "TONE") == 0) { b->tone = atof(value); } else if (strcasecmp(keyword, "OFFSET") == 0 || strcasecmp(keyword, "OFF") == 0) { b->offset = atof(value); } else if (strcasecmp(keyword, "COMMENT") == 0) { b->comment = strdup(value); } else if (strcasecmp(keyword, "COMMENTCMD") == 0) { b->commentcmd = strdup(value); } else if (strcasecmp(keyword, "COMPRESS") == 0 || strcasecmp(keyword, "COMPRESSED") == 0) { b->compress = atoi(value); } else if (strcasecmp(keyword, "MESSAGING") == 0) { b->messaging = atoi(value); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Invalid option keyword, %s.\n", line, keyword); return (0); } } if (b->custom_info != NULL && b->custom_infocmd != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Can't use both INFO and INFOCMD at the same time.\n", line); } if (b->compress && b->ambiguity != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Position ambiguity can't be used with compressed location format.\n", line); b->ambiguity = 0; } /* * Convert UTM coordintes to lat / long. */ if (strlen(zone) > 0 || easting != G_UNKNOWN || northing != G_UNKNOWN) { if (strlen(zone) > 0 && easting != G_UNKNOWN && northing != G_UNKNOWN) { long lzone; char latband, hemi; long lerr; double dlat, dlon; lzone = parse_utm_zone (zone, &latband, &hemi); lerr = Convert_UTM_To_Geodetic(lzone, hemi, easting, northing, &dlat, &dlon); if (lerr == 0) { b->lat = R2D(dlat); b->lon = R2D(dlon); } else { char message [300]; utm_error_string (lerr, message); text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: When any of ZONE, EASTING, NORTHING specified, they must all be specified.\n", line); } } /* * Process symbol now that we have any later overlay. */ if (strlen(temp_symbol) > 0) { if (strlen(temp_symbol) == 2 && (temp_symbol[0] == '/' || temp_symbol[0] == '\\' || isupper(temp_symbol[0]) || isdigit(temp_symbol[0])) && temp_symbol[1] >= '!' && temp_symbol[1] <= '~') { /* Explicit table and symbol. */ if (isupper(b->symtab) || isdigit(b->symtab)) { b->symbol = temp_symbol[1]; } else { b->symtab = temp_symbol[0]; b->symbol = temp_symbol[1]; } } else { /* Try to look up by description. */ ok = symbols_code_from_description (b->symtab, temp_symbol, &(b->symtab), &(b->symbol)); if (!ok) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Could not find symbol matching %s.\n", line, temp_symbol); } } } /* Check is here because could be using default channel when SENDTO= is not specified. */ if (b->sendto_type == SENDTO_XMIT) { if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || ! p_audio_config->achan[b->sendto_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan); return (0); } if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); return (0); } } return (1); } /* end config.c */ direwolf-1.5+dfsg/config.h000066400000000000000000000164361347750676600155660ustar00rootroot00000000000000 /*---------------------------------------------------------------------------- * * Name: config.h * * Purpose: * * Description: * *-----------------------------------------------------------------------------*/ #ifndef CONFIG_H #define CONFIG_H 1 #include "audio.h" /* for struct audio_s */ #include "digipeater.h" /* for struct digi_config_s */ #include "cdigipeater.h" /* for struct cdigi_config_s */ #include "aprs_tt.h" /* for struct tt_config_s */ #include "igate.h" /* for struct igate_config_s */ /* * All the leftovers. * This wasn't thought out. It just happened. */ enum beacon_type_e { BEACON_IGNORE, BEACON_POSITION, BEACON_OBJECT, BEACON_TRACKER, BEACON_CUSTOM, BEACON_IGATE }; enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV }; #define MAX_BEACONS 30 struct misc_config_s { int agwpe_port; /* Port number for the "AGW TCPIP Socket Interface" */ int kiss_port; /* Port number for the "TCP KISS" protocol. */ int enable_kiss_pt; /* Enable pseudo terminal for KISS. */ /* Want this to be off by default because it hangs */ /* after a while if nothing is reading from other end. */ char kiss_serial_port[20]; /* Serial port name for our end of the */ /* virtual null modem for native Windows apps. */ /* Version 1.5 add same capability for Linux. */ int kiss_serial_speed; /* Speed, in bps, for the KISS serial port. */ /* If 0, just leave what was already there. */ int kiss_serial_poll; /* When using Bluetooth KISS, the /dev/rfcomm0 device */ /* will appear and disappear as the remote application */ /* opens and closes the virtual COM port. */ /* When this is non-zero, we will check periodically to */ /* see if the device has appeared and we will open it. */ char gpsnmea_port[20]; /* Serial port name for reading NMEA sentences from GPS. */ /* e.g. COM22, /dev/ttyACM0 */ /* Currently no option for setting non-standard speed. */ char gpsd_host[20]; /* Host for gpsd server. */ /* e.g. localhost, 192.168.1.2 */ int gpsd_port; /* Port number for gpsd server. */ /* Default is 2947. */ char waypoint_port[20]; /* Serial port name for sending NMEA waypoint sentences */ /* to a GPS map display or other mapping application. */ /* e.g. COM22, /dev/ttyACM0 */ /* Currently no option for setting non-standard speed. */ int waypoint_formats; /* Which sentence formats should be generated? */ #define WPT_FORMAT_NMEA_GENERIC 0x01 /* N $GPWPT */ #define WPT_FORMAT_GARMIN 0x02 /* G $PGRMW */ #define WPT_FORMAT_MAGELLAN 0x04 /* M $PMGNWPL */ #define WPT_FORMAT_KENWOOD 0x08 /* K $PKWDWPL */ int log_daily_names; /* True to generate new log file each day. */ char log_path[80]; /* Either directory or full file name depending on above. */ int sb_configured; /* TRUE if SmartBeaconing is configured. */ int sb_fast_speed; /* MPH */ int sb_fast_rate; /* seconds */ int sb_slow_speed; /* MPH */ int sb_slow_rate; /* seconds */ int sb_turn_time; /* seconds */ int sb_turn_angle; /* degrees */ int sb_turn_slope; /* degrees * MPH */ // AX.25 connected mode. int frack; /* Number of seconds to wait for ack to transmission. */ int retry; /* Number of times to retry before giving up. */ int paclen; /* Max number of bytes in information part of frame. */ int maxframe_basic; /* Max frames to send before ACK. mod 8 "Window" size. */ int maxframe_extended; /* Max frames to send before ACK. mod 128 "Window" size. */ int maxv22; /* Maximum number of unanswered SABME frames sent before */ /* switching to SABM. This is to handle the case of an old */ /* TNC which simply ignores SABME rather than replying with FRMR. */ char **v20_addrs; /* Stations known to understand only AX.25 v2.0 so we don't */ /* waste time trying v2.2 first. */ int v20_count; /* Number of station addresses in array above. */ char **noxid_addrs; /* Stations known not to understand XID command so don't */ /* waste time sending it and eventually giving up. */ /* AX.25 for Linux is the one known case, so far, where */ /* SABME is implemented but XID is not. */ int noxid_count; /* Number of station addresses in array above. */ // Beacons. int num_beacons; /* Number of beacons defined. */ struct beacon_s { enum beacon_type_e btype; /* Position or object. */ int lineno; /* Line number from config file for later error messages. */ enum sendto_type_e sendto_type; /* SENDTO_XMIT - Usually beacons go to a radio transmitter. */ /* chan, below is the channel number. */ /* SENDTO_IGATE - Send to IGate, probably to announce my position */ /* rather than relying on someone else to hear */ /* me on the radio and report me. */ /* SENDTO_RECV - Pretend this was heard on the specified */ /* radio channel. Mostly for testing. It is a */ /* convenient way to send packets to attached apps. */ int sendto_chan; /* Transmit or simulated receive channel for above. Should be 0 for IGate. */ int delay; /* Seconds to delay before first transmission. */ int slot; /* Seconds after hour for slotted time beacons. */ /* If specified, it overrides any 'delay' value. */ int every; /* Time between transmissions, seconds. */ /* Remains fixed for PBEACON and OBEACON. */ /* Dynamically adjusted for TBEACON. */ time_t next; /* Unix time to transmit next one. */ char *dest; /* NULL or explicit AX.25 destination to use */ /* instead of the software version such as APDW11. */ int compress; /* Use more compact form? */ char objname[10]; /* Object name. Any printable characters. */ char *via; /* Path, e.g. "WIDE1-1,WIDE2-1" or NULL. */ char *custom_info; /* Info part for handcrafted custom beacon. */ /* Ignore the rest below if this is set. */ char *custom_infocmd; /* Command to generate info part. */ /* Again, other options below are then ignored. */ int messaging; /* Set messaging attribute for position report. */ /* i.e. Data Type Indicator of '=' rather than '!' */ double lat; /* Latitude and longitude. */ double lon; int ambiguity; /* Number of lower digits to trim from location. 0 (default), 1, 2, 3, 4. */ float alt_m; /* Altitude in meters. */ char symtab; /* Symbol table: / or \ or overlay character. */ char symbol; /* Symbol code. */ float power; /* For PHG. */ float height; float gain; /* Original protocol spec was unclear. */ /* Addendum 1.1 clarifies it is dBi not dBd. */ char dir[3]; /* 1 or 2 of N,E,W,S, or empty for omni. */ float freq; /* MHz. */ float tone; /* Hz. */ float offset; /* MHz. */ char *comment; /* Comment or NULL. */ char *commentcmd; /* Command to append more to Comment or NULL. */ } beacon[MAX_BEACONS]; }; #define MIN_IP_PORT_NUMBER 1024 #define MAX_IP_PORT_NUMBER 49151 #define DEFAULT_AGWPE_PORT 8000 /* Like everyone else. */ #define DEFAULT_KISS_PORT 8001 /* Above plus 1. */ #define DEFAULT_NULLMODEM "COM3" /* should be equiv. to /dev/ttyS2 on Cygwin */ extern void config_init (char *fname, struct audio_s *p_modem, struct digi_config_s *digi_config, struct cdigi_config_s *cdigi_config, struct tt_config_s *p_tt_config, struct igate_config_s *p_igate_config, struct misc_config_s *misc_config); #endif /* CONFIG_H */ /* end config.h */ direwolf-1.5+dfsg/decode_aprs.c000066400000000000000000004331631347750676600165640ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015, 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * File: decode_aprs.c * * Purpose: Decode the information part of APRS frame. * * Description: Present the packet contents in human readable format. * This is a fairly complete implementation with error messages * pointing out various specication violations. * * Assumptions: ax25_from_frame() has been called to * separate the header and information. * *------------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include /* for atof */ #include /* for strtok */ #include /* for pow */ #include /* for isdigit */ #include #include "regex.h" #include "ax25_pad.h" #include "textcolor.h" #include "symbols.h" #include "latlong.h" #include "dwgpsnmea.h" #include "decode_aprs.h" #include "telemetry.h" #define TRUE 1 #define FALSE 0 /* Position & symbol fields common to several message formats. */ typedef struct { char lat[8]; char sym_table_id; /* / \ 0-9 A-Z */ char lon[9]; char symbol_code; } position_t; typedef struct { char sym_table_id; /* / \ a-j A-Z */ /* "The presence of the leading Symbol Table Identifier */ /* instead of a digit indicates that this is a compressed */ /* Position Report and not a normal lat/long report." */ /* "a-j" is not a typographical error. */ /* The first 10 lower case letters represent the overlay */ /* characters of 0-9 in the compressed format. */ char y[4]; /* Compressed Latitude. */ char x[4]; /* Compressed Longitude. */ char symbol_code; char c; /* Course/speed or altitude. */ char s; char t ; /* Compression type. */ } compressed_position_t; /* Range of digits for Base 91 representation. */ #define B91_MIN '!' #define B91_MAX '{' #define isdigit91(c) ((c) >= B91_MIN && (c) <= B91_MAX) //static void print_decoded (decode_aprs_t *A); static void aprs_ll_pos (decode_aprs_t *A, unsigned char *, int); static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *, int); static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *, int); static void aprs_mic_e (decode_aprs_t *A, packet_t, unsigned char *, int); //static void aprs_compressed_pos (decode_aprs_t *A, unsigned char *, int); static void aprs_message (decode_aprs_t *A, unsigned char *, int, int quiet); static void aprs_object (decode_aprs_t *A, unsigned char *, int); static void aprs_item (decode_aprs_t *A, unsigned char *, int); static void aprs_station_capabilities (decode_aprs_t *A, char *, int); static void aprs_status_report (decode_aprs_t *A, char *, int); static void aprs_general_query (decode_aprs_t *A, char *, int, int quiet); static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet); static void aprs_telemetry (decode_aprs_t *A, char *info, int info_len, int quiet); static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int); static void aprs_morse_code (decode_aprs_t *A, char *, int); static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int); static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix); static void aprs_ultimeter (decode_aprs_t *A, char *, int); static void third_party_header (decode_aprs_t *A, char *, int); static void decode_position (decode_aprs_t *A, position_t *ppos); static void decode_compressed_position (decode_aprs_t *A, compressed_position_t *ppos); static double get_latitude_8 (char *p, int quiet); static double get_longitude_9 (char *p, int quiet); static time_t get_timestamp (decode_aprs_t *A, char *p); static int get_maidenhead (decode_aprs_t *A, char *p); static int data_extension_comment (decode_aprs_t *A, char *pdext); static void decode_tocall (decode_aprs_t *A, char *dest); //static void get_symbol (decode_aprs_t *A, char dti, char *src, char *dest); static void process_comment (decode_aprs_t *A, char *pstart, int clen); /*------------------------------------------------------------------ * * Function: decode_aprs * * Purpose: Split APRS packet into separate properties that it contains. * * Inputs: pp - APRS packet object. * * quiet - Suppress error messages. * * Outputs: A-> g_symbol_table, g_symbol_code, * g_lat, g_lon, * g_speed_mph, g_course, g_altitude_ft, * g_comment * ... and many others... * * Major Revisions: 1.1 Reorganized so parts are returned in a structure. * Print function is now called separately. * *------------------------------------------------------------------*/ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) { char dest[AX25_MAX_ADDR_LEN]; unsigned char *pinfo; int info_len; info_len = ax25_get_info (pp, &pinfo); memset (A, 0, sizeof (*A)); A->g_quiet = quiet; snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Unknown APRS Data Type Indicator \"%c\"", *pinfo); A->g_symbol_table = '/'; /* Default to primary table. */ A->g_symbol_code = ' '; /* What should we have for default symbol? */ A->g_lat = G_UNKNOWN; A->g_lon = G_UNKNOWN; A->g_speed_mph = G_UNKNOWN; A->g_course = G_UNKNOWN; A->g_power = G_UNKNOWN; A->g_height = G_UNKNOWN; A->g_gain = G_UNKNOWN; A->g_range = G_UNKNOWN; A->g_altitude_ft = G_UNKNOWN; A->g_freq = G_UNKNOWN; A->g_tone = G_UNKNOWN; A->g_dcs = G_UNKNOWN; A->g_offset = G_UNKNOWN; A->g_footprint_lat = G_UNKNOWN; A->g_footprint_lon = G_UNKNOWN; A->g_footprint_radius = G_UNKNOWN; /* * Extract source and destination including the SSID. */ ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src); ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest); /* * Report error if the information part contains a nul character. * There are two known cases where this can happen. * * - The Kenwood TM-D710A sometimes sends packets like this: * * VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast= * K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV = * * Notice that the data type indicator of "4" is not valid. If we remove * 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format. * This same thing has been observed from others and is intermittent. * * - AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes. * This is wrong, it should be using UTF-8. */ if ( ( ! A->g_quiet ) && ( (int)strlen((char*)pinfo) != info_len) ) { text_color_set(DW_COLOR_ERROR); dw_printf("'nul' character found in Information part. This should never happen.\n"); dw_printf("It seems that %s is transmitting with defective software.\n", A->g_src); if (strcmp((char*)pinfo, "4P") == 0) { dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\n"); } } switch (*pinfo) { /* "DTI" data type identifier. */ case '!': /* Position without timestamp (no APRS messaging). */ /* or Ultimeter 2000 WX Station */ case '=': /* Position without timestamp (with APRS messaging). */ if (strncmp((char*)pinfo, "!!", 2) == 0) { aprs_ultimeter (A, (char*)pinfo, info_len); } else { aprs_ll_pos (A, pinfo, info_len); } break; //case '#': /* Peet Bros U-II Weather station */ //case '*': /* Peet Bros U-II Weather station */ //break; case '$': /* Raw GPS data or Ultimeter 2000 */ if (strncmp((char*)pinfo, "$ULTW", 5) == 0) { aprs_ultimeter (A, (char*)pinfo, info_len); } else { aprs_raw_nmea (A, pinfo, info_len); } break; case '\'': /* Old Mic-E Data (but Current data for TM-D700) */ case '`': /* Current Mic-E Data (not used in TM-D700) */ aprs_mic_e (A, pp, pinfo, info_len); break; case ')': /* Item. */ aprs_item (A, pinfo, info_len); break; case '/': /* Position with timestamp (no APRS messaging) */ case '@': /* Position with timestamp (with APRS messaging) */ aprs_ll_pos_time (A, pinfo, info_len); break; case ':': /* Message: for one person, a group, or a bulletin. */ /* Directed Station Query */ /* Telemetry metadata. */ aprs_message (A, pinfo, info_len, quiet); break; case ';': /* Object */ aprs_object (A, pinfo, info_len); break; case '<': /* Station Capabilities */ aprs_station_capabilities (A, (char*)pinfo, info_len); break; case '>': /* Status Report */ aprs_status_report (A, (char*)pinfo, info_len); break; case '?': /* General Query */ aprs_general_query (A, (char*)pinfo, info_len, quiet); break; case 'T': /* Telemetry */ aprs_telemetry (A, (char*)pinfo, info_len, quiet); break; case '_': /* Positionless Weather Report */ aprs_positionless_weather_report (A, pinfo, info_len); break; case '{': /* user defined data */ /* http://www.aprs.org/aprs11/expfmts.txt */ if (strncmp((char*)pinfo, "{tt", 3) == 0) { aprs_raw_touch_tone (A, (char*)pinfo, info_len); } else if (strncmp((char*)pinfo, "{mc", 3) == 0) { aprs_morse_code (A, (char*)pinfo, info_len); } else { //aprs_user_defined (A, pinfo, info_len); } break; case 't': /* Raw touch tone data - NOT PART OF STANDARD */ /* Used to convey raw touch tone sequences to */ /* to an application that might want to interpret them. */ /* Might move into user defined data, above. */ aprs_raw_touch_tone (A, (char*)pinfo, info_len); break; case 'm': /* Morse Code data - NOT PART OF STANDARD */ /* Used by APRStt gateway to put audible responses */ /* into the transmit queue. Could potentially find */ /* other uses such as CW ID for station. */ /* Might move into user defined data, above. */ aprs_morse_code (A, (char*)pinfo, info_len); break; case '}': /* third party header */ third_party_header (A, (char*)pinfo, info_len); break; //case '\r': /* CR or LF? */ //case '\n': //break; default: break; } /* * Look in other locations if not found in information field. */ if (A->g_symbol_table == ' ' || A->g_symbol_code == ' ') { symbols_from_dest_or_src (*pinfo, A->g_src, dest, &A->g_symbol_table, &A->g_symbol_code); } /* * Application might be in the destination field for most message types. * MIC-E format has part of location in the destination field. */ switch (*pinfo) { /* "DTI" data type identifier. */ case '\'': /* Old Mic-E Data */ case '`': /* Current Mic-E Data */ break; default: decode_tocall (A, dest); break; } } /* end decode_aprs */ void decode_aprs_print (decode_aprs_t *A) { char stemp[200]; //char tmp2[2]; double absll; char news; int deg; double min; char s_lat[30]; char s_lon[30]; int n; char symbol_description[100]; /* * First line has: * - message type * - object name * - symbol * - manufacturer/application * - mic-e status * - power/height/gain, range */ strlcpy (stemp, A->g_msg_type, sizeof(stemp)); if (strlen(A->g_name) > 0) { strlcat (stemp, ", \"", sizeof(stemp)); strlcat (stemp, A->g_name, sizeof(stemp)); strlcat (stemp, "\"", sizeof(stemp)); } if (A->g_symbol_code != ' ') { symbols_get_description (A->g_symbol_table, A->g_symbol_code, symbol_description, sizeof(symbol_description)); strlcat (stemp, ", ", sizeof(stemp)); strlcat (stemp, symbol_description, sizeof(stemp)); } if (strlen(A->g_mfr) > 0) { strlcat (stemp, ", ", sizeof(stemp)); strlcat (stemp, A->g_mfr, sizeof(stemp)); } if (strlen(A->g_mic_e_status) > 0) { strlcat (stemp, ", ", sizeof(stemp)); strlcat (stemp, A->g_mic_e_status, sizeof(stemp)); } if (A->g_power > 0) { char phg[100]; /* Protcol spec doesn't mention whether this is dBd or dBi. */ /* Clarified later. */ /* http://eng.usna.navy.mil/~bruninga/aprs/aprs11.html */ /* "The Antenna Gain in the PHG format on page 28 is in dBi." */ snprintf (phg, sizeof(phg), ", %d W height=%d %ddBi %s", A->g_power, A->g_height, A->g_gain, A->g_directivity); strlcat (stemp, phg, sizeof(stemp)); } if (A->g_range > 0) { char rng[100]; snprintf (rng, sizeof(rng), ", range=%.1f", A->g_range); strlcat (stemp, rng, sizeof(stemp)); } text_color_set(DW_COLOR_DECODED); dw_printf("%s\n", stemp); /* * Second line has: * - Latitude * - Longitude * - speed * - direction * - altitude * - frequency */ /* * Convert Maidenhead locator to latitude and longitude. * * Any example was checked for each hemihemisphere using * http://www.amsat.org/cgi-bin/gridconv */ if (strlen(A->g_maidenhead) > 0) { if (A->g_lat == G_UNKNOWN && A->g_lon == G_UNKNOWN) { ll_from_grid_square (A->g_maidenhead, &(A->g_lat), &(A->g_lon)); } dw_printf("Grid square = %s, ", A->g_maidenhead); } strlcpy (stemp, "", sizeof(stemp)); if (A->g_lat != G_UNKNOWN || A->g_lon != G_UNKNOWN) { // Have location but it is posible one part is invalid. if (A->g_lat != G_UNKNOWN) { if (A->g_lat >= 0) { absll = A->g_lat; news = 'N'; } else { absll = - A->g_lat; news = 'S'; } deg = (int) absll; min = (absll - deg) * 60.0; snprintf (s_lat, sizeof(s_lat), "%c %02d%s%07.4f", news, deg, CH_DEGREE, min); } else { strlcpy (s_lat, "Invalid Latitude", sizeof(s_lat)); } if (A->g_lon != G_UNKNOWN) { if (A->g_lon >= 0) { absll = A->g_lon; news = 'E'; } else { absll = - A->g_lon; news = 'W'; } deg = (int) absll; min = (absll - deg) * 60.0; snprintf (s_lon, sizeof(s_lon), "%c %03d%s%07.4f", news, deg, CH_DEGREE, min); } else { strlcpy (s_lon, "Invalid Longitude", sizeof(s_lon)); } snprintf (stemp, sizeof(stemp), "%s, %s", s_lat, s_lon); } if (strlen(A->g_aprstt_loc) > 0) { if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp)); strlcat (stemp, A->g_aprstt_loc, sizeof(stemp)); }; if (A->g_speed_mph != G_UNKNOWN) { char spd[20]; if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp)); snprintf (spd, sizeof(spd), "%.0f MPH", A->g_speed_mph); strlcat (stemp, spd, sizeof(stemp)); }; if (A->g_course != G_UNKNOWN) { char cse[20]; if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp)); snprintf (cse, sizeof(cse), "course %.0f", A->g_course); strlcat (stemp, cse, sizeof(stemp)); }; if (A->g_altitude_ft != G_UNKNOWN) { char alt[20]; if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp)); snprintf (alt, sizeof(alt), "alt %.0f ft", A->g_altitude_ft); strlcat (stemp, alt, sizeof(stemp)); }; if (A->g_freq != G_UNKNOWN) { char ftemp[30]; snprintf (ftemp, sizeof(ftemp), ", %.3f MHz", A->g_freq); strlcat (stemp, ftemp, sizeof(stemp)); } if (A->g_offset != G_UNKNOWN) { char ftemp[30]; if (A->g_offset % 1000 == 0) { snprintf (ftemp, sizeof(ftemp), ", %+dM", A->g_offset/1000); } else { snprintf (ftemp, sizeof(ftemp), ", %+dk", A->g_offset); } strlcat (stemp, ftemp, sizeof(stemp)); } if (A->g_tone != G_UNKNOWN) { if (A->g_tone == 0) { strlcat (stemp, ", no PL", sizeof(stemp)); } else { char ftemp[30]; snprintf (ftemp, sizeof(ftemp), ", PL %.1f", A->g_tone); strlcat (stemp, ftemp, sizeof(stemp)); } } if (A->g_dcs != G_UNKNOWN) { char ftemp[30]; snprintf (ftemp, sizeof(ftemp), ", DCS %03o", A->g_dcs); strlcat (stemp, ftemp, sizeof(stemp)); } if (strlen (stemp) > 0) { text_color_set(DW_COLOR_DECODED); dw_printf("%s\n", stemp); } /* * Finally, any weather and/or comment. * * Non-printable characters are changed to safe hexadecimal representations. * For example, carriage return is displayed as <0x0d>. * * Drop annoying trailing CR LF. Anyone who cares can see it in the raw datA-> */ n = strlen(A->g_weather); if (n >= 1 && A->g_weather[n-1] == '\n') { A->g_weather[n-1] = '\0'; n--; } if (n >= 1 && A->g_weather[n-1] == '\r') { A->g_weather[n-1] = '\0'; n--; } if (n > 0) { ax25_safe_print (A->g_weather, -1, 0); dw_printf("\n"); } if (strlen(A->g_telemetry) > 0) { ax25_safe_print (A->g_telemetry, -1, 0); dw_printf("\n"); } n = strlen(A->g_comment); if (n >= 1 && A->g_comment[n-1] == '\n') { A->g_comment[n-1] = '\0'; n--; } if (n >= 1 && A->g_comment[n-1] == '\r') { A->g_comment[n-1] = '\0'; n--; } if (n > 0) { int j; ax25_safe_print (A->g_comment, -1, 0); dw_printf("\n"); /* * Point out incorrect attempts a degree symbol. * 0xb0 is degree in ISO Latin1. * To be part of a valid UTF-8 sequence, it would need to be preceded by 11xxxxxx or 10xxxxxx. * 0xf8 is degree in Microsoft code page 437. * To be part of a valid UTF-8 sequence, it would need to be followed by 10xxxxxx. */ if ( ! A->g_quiet) { for (j=0; jg_comment[j]) == 0xb0 && (j == 0 || ! (A->g_comment[j-1] & 0x80))) { text_color_set(DW_COLOR_ERROR); dw_printf("Character code 0xb0 is probably an attempt at a degree symbol.\n"); dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n"); } } for (j=0; jg_comment[j]) == 0xf8 && (j == n-1 || (A->g_comment[j+1] & 0xc0) != 0xc0)) { text_color_set(DW_COLOR_ERROR); dw_printf("Character code 0xf8 is probably an attempt at a degree symbol.\n"); dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n"); } } } } } /*------------------------------------------------------------------ * * Function: aprs_ll_pos * * Purpose: Decode "Lat/Long Position Report - without Timestamp" * * Reports without a timestamp can be regarded as real-time. * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft. * * Description: Type identifier '=' has APRS messaging. * Type identifier '!' does not have APRS messaging. * * The location can be in either compressed or human-readable form. * * When the symbol code is '_' this is a weather report. * * Examples: !4309.95NS07307.13W#PHG3320 W2,NY2 Mt Equinox VT k2lm@arrl.net * !4237.14NS07120.83W# * =4246.40N/07115.15W# {UIV32} * * TODO: (?) Special case, DF report when sym table id = '/' and symbol code = '\'. * * =4903.50N/07201.75W\088/036/270/729 * *------------------------------------------------------------------*/ static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen) { struct aprs_ll_pos_s { char dti; /* ! or = */ position_t pos; char comment[43]; /* Start of comment could be data extension(s). */ } *p; struct aprs_compressed_pos_s { char dti; /* ! or = */ compressed_position_t cpos; char comment[40]; /* No data extension allowed for compressed location. */ } *q; strlcpy (A->g_msg_type, "Position", sizeof(A->g_msg_type)); p = (struct aprs_ll_pos_s *)info; q = (struct aprs_compressed_pos_s *)info; if (isdigit((unsigned char)(p->pos.lat[0]))) /* Human-readable location. */ { decode_position (A, &(p->pos)); if (A->g_symbol_code == '_') { /* Symbol code indidates it is a weather report. */ /* In this case, we expect 7 byte "data extension" */ /* for the wind direction and speed. */ strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type)); weather_data (A, p->comment, TRUE); } else { /* Regular position report. */ data_extension_comment (A, p->comment); } } else /* Compressed location. */ { decode_compressed_position (A, &(q->cpos)); if (A->g_symbol_code == '_') { /* Symbol code indidates it is a weather report. */ /* In this case, the wind direction and speed are in the */ /* compressed data so we don't expect a 7 byte "data */ /* extension" for them. */ strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type)); weather_data (A, q->comment, FALSE); } else { /* Regular position report. */ process_comment (A, q->comment, -1); } } } /*------------------------------------------------------------------ * * Function: aprs_ll_pos_time * * Purpose: Decode "Lat/Long Position Report - with Timestamp" * * Reports sent with a timestamp might contain very old information. * * Otherwise, same as above. * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft. * * Description: Type identifier '@' has APRS messaging. * Type identifier '/' does not have APRS messaging. * * The location can be in either compressed or human-readable form. * * When the symbol code is '_' this is a weather report. * * Examples: @041025z4232.32N/07058.81W_124/000g000t036r000p000P000b10229h65/wx rpt * @281621z4237.55N/07120.20W_017/002g006t022r000p000P000h85b10195.Dvs * /092345z4903.50N/07201.75W>Test1234 * * I think the symbol code of "_" indicates weather report. * * (?) Special case, DF report when sym table id = '/' and symbol code = '\'. * * @092345z4903.50N/07201.75W\088/036/270/729 * /092345z4903.50N/07201.75W\000/000/270/729 * *------------------------------------------------------------------*/ static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen) { struct aprs_ll_pos_time_s { char dti; /* / or @ */ char time_stamp[7]; position_t pos; char comment[43]; /* First 7 bytes could be data extension. */ } *p; struct aprs_compressed_pos_time_s { char dti; /* / or @ */ char time_stamp[7]; compressed_position_t cpos; char comment[40]; /* No data extension in this case. */ } *q; strlcpy (A->g_msg_type, "Position with time", sizeof(A->g_msg_type)); time_t ts = 0; p = (struct aprs_ll_pos_time_s *)info; q = (struct aprs_compressed_pos_time_s *)info; if (isdigit((unsigned char)(p->pos.lat[0]))) /* Human-readable location. */ { ts = get_timestamp (A, p->time_stamp); decode_position (A, &(p->pos)); if (A->g_symbol_code == '_') { /* Symbol code indidates it is a weather report. */ /* In this case, we expect 7 byte "data extension" */ /* for the wind direction and speed. */ strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type)); weather_data (A, p->comment, TRUE); } else { /* Regular position report. */ data_extension_comment (A, p->comment); } } else /* Compressed location. */ { ts = get_timestamp (A, p->time_stamp); decode_compressed_position (A, &(q->cpos)); if (A->g_symbol_code == '_') { /* Symbol code indidates it is a weather report. */ /* In this case, the wind direction and speed are in the */ /* compressed data so we don't expect a 7 byte "data */ /* extension" for them. */ strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type)); weather_data (A, q->comment, FALSE); } else { /* Regular position report. */ process_comment (A, q->comment, -1); } } (void)(ts); // suppress 'set but not used' warning. } /*------------------------------------------------------------------ * * Function: aprs_raw_nmea * * Purpose: Decode "Raw NMEA Position Report" * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: A-> ... * * Description: APRS recognizes raw ASCII data strings conforming to the NMEA 0183 * Version 2.0 specification, originating from navigation equipment such * as GPS and LORAN receivers. It is recommended that APRS stations * interpret at least the following NMEA Received Sentence types: * * GGA Global Positioning System Fix Data * GLL Geographic Position, Latitude/Longitude Data * RMC Recommended Minimum Specific GPS/Transit Data * VTG Velocity and Track Data * WPL Way Point Location * * We presently recognize only RMC and GGA. * * Examples: $GPGGA,102705,5157.9762,N,00029.3256,W,1,04,2.0,75.7,M,47.6,M,,*62 * $GPGLL,2554.459,N,08020.187,W,154027.281,A * $GPRMC,063909,A,3349.4302,N,11700.3721,W,43.022,89.3,291099,13.6,E*52 * $GPVTG,318.7,T,,M,35.1,N,65.0,K*69 * *------------------------------------------------------------------*/ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) { if (strncmp((char*)info, "$GPRMC,", 7) == 0) { float speed_knots = G_UNKNOWN; (void) dwgpsnmea_gprmc ((char*)info, A->g_quiet, &(A->g_lat), &(A->g_lon), &speed_knots, &(A->g_course)); A->g_speed_mph = DW_KNOTS_TO_MPH(speed_knots); } else if (strncmp((char*)info, "$GPGGA,", 7) == 0) { float alt_meters = G_UNKNOWN; int num_sat = 0; (void) dwgpsnmea_gpgga ((char*)info, A->g_quiet, &(A->g_lat), &(A->g_lon), &alt_meters, &num_sat); A->g_altitude_ft = DW_METERS_TO_FEET(alt_meters); } // TODO (low): add a few other sentence types. } /* end aprs_raw_nmea */ /*------------------------------------------------------------------ * * Function: aprs_mic_e * * Purpose: Decode MIC-E (also Kenwood D7 & D700) packet. * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: * * Description: * * Destination Address Field - * * The 7-byte Destination Address field contains * the following encoded information: * * * The 6 latitude digits. * * A 3-bit Mic-E message identifier, specifying one of 7 Standard Mic-E * Message Codes or one of 7 Custom Message Codes or an Emergency * Message Code. * * The North/South and West/East Indicators. * * The Longitude Offset Indicator. * * The generic APRS digipeater path code. * * "Although the destination address appears to be quite unconventional, it is * still a valid AX.25 address, consisting only of printable 7-bit ASCII values." * * References: Mic-E TYPE CODES -- http://www.aprs.org/aprs12/mic-e-types.txt * * This is up to date with the 24 Aug 16 version mentioning the TH-D74. * * Mic-E TEST EXAMPLES -- http://www.aprs.org/aprs12/mic-e-examples.txt * * Examples: `b9Z!4y>/>"4N}Paul's_TH-D7 * * TODO: Destination SSID can contain generic digipeater path. * * Bugs: Doesn't handle ambiguous position. "space" treated as zero. * Invalid data results in a message but latitude is not set to unknown. * *------------------------------------------------------------------*/ /* a few test cases # example from http://www.aprs.org/aprs12/mic-e-examples.txt produces 4 errors. # TODO: Analyze all the bits someday and possibly report problem with document. N0CALL>ABCDEF:'abc123R/text # Let's use an actual valid location and concentrate on the manufacturers # as listed in http://www.aprs.org/aprs12/mic-e-types.txt N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Jeff Mobile_% N1ZZN-9>T2SP0W:`c_Vm6hk/ "49}Originl Mic-E (leading space) N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D7A walkie Talkie N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D72 walkie Talkie= W6GPS>S4PT3R:`p(1oR0K\>TH-D74A^ N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D700 MObile Radio N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D710 Mobile Radio= # Note: next line has trailing space character after _ N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu VX-8_ N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu FTM-350_" N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu VX-8G_# N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu FT1D_$ N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu FTM-400DR_% N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Byonics TinyTrack3|3 N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Byonics TinyTrack4|4 # The next group starts with metacharacter "T" which can be any of space > ] ` ' # But space is for original Mic-E, # > and ] are for Kenwood, # so ` ' would probably be less ambigous choices but any appear to be valid. N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Hamhud\9 N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Argent/9 N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}HinzTec anyfrog^9 N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO*9 N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}OTHER~9 # TODO: Why is manufacturer unknown? Should we explicitly say unknown? [0] VE2VL-9>TU3V0P,VE2PCQ-3,WIDE1,W1UWS-1,UNCAN,WIDE2*:`eB?l")v/"3y} MIC-E, VAN, En Route [0] VE2VL-9>TU3U5Q,VE2PCQ-3,WIDE1,W1UWS-1,N1NCI-3,WIDE2*:`eBgl"$v/"42}73 de Julien, Tinytrak 3 MIC-E, VAN, En Route [0] W1ERB-9>T1SW8P,KB1AEV-15,N1NCI-3,WIDE2*:`dI8l!#j/"3m} MIC-E, JEEP, In Service [0] W1ERB-9>T1SW8Q,KB1AEV-15,N1NCI-3,WIDE2*:`dI6l{^j/"4+}IntheJeep..try146.79(PVRA) "146.79" in comment looks like a frequency in non-standard format. For most systems to recognize it, use exactly this form "146.790MHz" at beginning of comment. MIC-E, JEEP, In Service */ static int mic_e_digit (decode_aprs_t *A, char c, int mask, int *std_msg, int *cust_msg) { if (c >= '0' && c <= '9') { return (c - '0'); } if (c >= 'A' && c <= 'J') { *cust_msg |= mask; return (c - 'A'); } if (c >= 'P' && c <= 'Y') { *std_msg |= mask; return (c - 'P'); } /* K, L, Z should be converted to space. */ /* others are invalid. */ /* But caller expects only values 0 - 9. */ if (c == 'K') { *cust_msg |= mask; return (0); } if (c == 'L') { return (0); } if (c == 'Z') { *std_msg |= mask; return (0); } if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character \"%c\" in MIC-E destination/latitude.\n", c); } return (0); } static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int ilen) { struct aprs_mic_e_s { char dti; /* ' or ` */ unsigned char lon[3]; /* "d+28", "m+28", "h+28" */ unsigned char speed_course[3]; char symbol_code; char sym_table_id; } *p; char dest[10]; int ch; int n; int offset; int std_msg = 0; int cust_msg = 0; const char *std_text[8] = {"Emergency", "Priority", "Special", "Committed", "Returning", "In Service", "En Route", "Off Duty" }; const char *cust_text[8] = {"Emergency", "Custom-6", "Custom-5", "Custom-4", "Custom-3", "Custom-2", "Custom-1", "Custom-0" }; unsigned char *pfirst, *plast; strlcpy (A->g_msg_type, "MIC-E", sizeof(A->g_msg_type)); p = (struct aprs_mic_e_s *)info; /* Destination is really latitude of form ddmmhh. */ /* Message codes are buried in the first 3 digits. */ ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest); A->g_lat = mic_e_digit(A, dest[0], 4, &std_msg, &cust_msg) * 10 + mic_e_digit(A, dest[1], 2, &std_msg, &cust_msg) + (mic_e_digit(A, dest[2], 1, &std_msg, &cust_msg) * 1000 + mic_e_digit(A, dest[3], 0, &std_msg, &cust_msg) * 100 + mic_e_digit(A, dest[4], 0, &std_msg, &cust_msg) * 10 + mic_e_digit(A, dest[5], 0, &std_msg, &cust_msg)) / 6000.0; /* 4th character of desination indicates north / south. */ if ((dest[3] >= '0' && dest[3] <= '9') || dest[3] == 'L') { /* South */ A->g_lat = ( - A->g_lat); } else if (dest[3] >= 'P' && dest[3] <= 'Z') { /* North */ } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid MIC-E N/S encoding in 4th character of destination.\n"); } } /* Longitude is mostly packed into 3 bytes of message but */ /* has a couple bits of information in the destination. */ if ((dest[4] >= '0' && dest[4] <= '9') || dest[4] == 'L') { offset = 0; } else if (dest[4] >= 'P' && dest[4] <= 'Z') { offset = 1; } else { offset = 0; if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid MIC-E Longitude Offset in 5th character of destination.\n"); } } /* First character of information field is longitude in degrees. */ /* It is possible for the unprintable DEL character to occur here. */ /* 5th character of desination indicates longitude offset of +100. */ /* Not quite that simple :-( */ ch = p->lon[0]; if (offset && ch >= 118 && ch <= 127) { A->g_lon = ch - 118; /* 0 - 9 degrees */ } else if ( ! offset && ch >= 38 && ch <= 127) { A->g_lon = (ch - 38) + 10; /* 10 - 99 degrees */ } else if (offset && ch >= 108 && ch <= 117) { A->g_lon = (ch - 108) + 100; /* 100 - 109 degrees */ } else if (offset && ch >= 38 && ch <= 107) { A->g_lon = (ch - 38) + 110; /* 110 - 179 degrees */ } else { A->g_lon = G_UNKNOWN; if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character 0x%02x for MIC-E Longitude Degrees.\n", ch); } } /* Second character of information field is A->g_longitude minutes. */ /* These are all printable characters. */ /* * More than once I've see the TH-D72A put <0x1a> here and flip between north and south. * * WB2OSZ>TRSW1R,WIDE1-1,WIDE2-2:`c0ol!O[/>=<0x0d> * N 42 37.1200, W 071 20.8300, 0 MPH, course 151 * * WB2OSZ>TRS7QR,WIDE1-1,WIDE2-2:`v<0x1a>n<0x1c>"P[/>=<0x0d> * Invalid character 0x1a for MIC-E Longitude Minutes. * S 42 37.1200, Invalid Longitude, 0 MPH, course 252 * * This was direct over the air with no opportunity for a digipeater * or anything else to corrupt the message. */ if (A->g_lon != G_UNKNOWN) { ch = p->lon[1]; if (ch >= 88 && ch <= 97) { A->g_lon += (ch - 88) / 60.0; /* 0 - 9 minutes*/ } else if (ch >= 38 && ch <= 87) { A->g_lon += ((ch - 38) + 10) / 60.0; /* 10 - 59 minutes */ } else { A->g_lon = G_UNKNOWN; if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character 0x%02x for MIC-E Longitude Minutes.\n", ch); } } /* Third character of information field is longitude hundredths of minutes. */ /* There are 100 possible values, from 0 to 99. */ /* Note that the range includes 4 unprintable control characters and DEL. */ if (A->g_lon != G_UNKNOWN) { ch = p->lon[2]; if (ch >= 28 && ch <= 127) { A->g_lon += ((ch - 28) + 0) / 6000.0; /* 0 - 99 hundredths of minutes*/ } else { A->g_lon = G_UNKNOWN; if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character 0x%02x for MIC-E Longitude hundredths of Minutes.\n", ch); } } } } /* 6th character of destintation indicates east / west. */ /* * Example of apparently invalid encoding. 6th character missing. * * [0] KB1HOZ-9>TTRW5,KQ1L-2,WIDE1,KQ1L-8,UNCAN,WIDE2*:`aFo"]|k/]"4m}<0x0d> * Invalid character "Invalid MIC-E E/W encoding in 6th character of destination. * MIC-E, truck, Kenwood TM-D700, Off Duty * N 44 27.5000, E 069 42.8300, 76 MPH, course 196, alt 282 ft */ if ((dest[5] >= '0' && dest[5] <= '9') || dest[5] == 'L') { /* East */ } else if (dest[5] >= 'P' && dest[5] <= 'Z') { /* West */ if (A->g_lon != G_UNKNOWN) { A->g_lon = ( - A->g_lon); } } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid MIC-E E/W encoding in 6th character of destination.\n"); } } /* Symbol table and codes like everyone else. */ A->g_symbol_table = p->sym_table_id; A->g_symbol_code = p->symbol_code; if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' && ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table)) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid symbol table code not one of / \\ A-Z 0-9\n"); } A->g_symbol_table = '/'; } /* Message type from two 3-bit codes. */ if (std_msg == 0 && cust_msg == 0) { strlcpy (A->g_mic_e_status, "Emergency", sizeof(A->g_mic_e_status)); } else if (std_msg == 0 && cust_msg != 0) { strlcpy (A->g_mic_e_status, cust_text[cust_msg], sizeof(A->g_mic_e_status)); } else if (std_msg != 0 && cust_msg == 0) { strlcpy (A->g_mic_e_status, std_text[std_msg], sizeof(A->g_mic_e_status)); } else { strlcpy (A->g_mic_e_status, "Unknown MIC-E Message Type", sizeof(A->g_mic_e_status)); } /* Speed and course from next 3 bytes. */ n = ((p->speed_course[0] - 28) * 10) + ((p->speed_course[1] - 28) / 10); if (n >= 800) n -= 800; A->g_speed_mph = DW_KNOTS_TO_MPH(n); n = ((p->speed_course[1] - 28) % 10) * 100 + (p->speed_course[2] - 28); if (n >= 400) n -= 400; /* Result is 0 for unknown and 1 - 360 where 360 is north. */ /* Convert to 0 - 360 and reserved value for unknown. */ if (n == 0) A->g_course = G_UNKNOWN; else if (n == 360) A->g_course = 0; else A->g_course = n; /* Now try to pick out manufacturer and other optional items. */ /* The telemetry field, in the original spec, is no longer used. */ strlcpy (A->g_mfr, "Unknown manufacturer", sizeof(A->g_mfr)); pfirst = info + sizeof(struct aprs_mic_e_s); plast = info + ilen - 1; /* Carriage return character at the end is not mentioned in spec. */ /* Remove if found because it messes up extraction of manufacturer. */ /* Don't drop trailing space because that is used for Yaesu VX-8. */ /* As I recall, the IGate function trims trailing spaces. */ /* That would be bad for this particular model. Maybe I'm mistaken? */ if (*plast == '\r') plast--; #define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'') // Last updated Sept. 2016 for TH-D74A if (isT(*pfirst)) { if (*pfirst == ' ' ) { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; } else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; } else if (*pfirst == '>' && *plast == '^') { strlcpy (A->g_mfr, "Kenwood TH-D74", sizeof(A->g_mfr)); pfirst++; plast--; } else if (*pfirst == '>' ) { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; } else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; } else if (*pfirst == ']' ) { strlcpy (A->g_mfr, "Kenwood TM-D700", sizeof(A->g_mfr)); pfirst++; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '$') { strlcpy (A->g_mfr, "Yaesu FT1D", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '%') { strlcpy (A->g_mfr, "Yaesu FTM-400DR", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == 'X') { strlcpy (A->g_mfr, "AP510", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; } else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strlcpy (A->g_mfr, "Byonics TinyTrack4", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '4') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7400 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '8') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7800 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '\'' ) { strlcpy (A->g_mfr, "McTrackr", sizeof(A->g_mfr)); pfirst++; } else if ( *(plast-1) == '\\' ) { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if ( *(plast-1) == '/' ) { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if ( *(plast-1) == '^' ) { strlcpy (A->g_mfr, "HinzTec anyfrog", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if ( *(plast-1) == '*' ) { strlcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if ( *(plast-1) == '~' ) { strlcpy (A->g_mfr, "OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; } } /* * An optional altitude is next. * It is three base-91 digits followed by "}". * The TM-D710A might have encoding bug. This was observed: * * KJ4ETP-9>SUUP9Q,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV$n6:>/]"7&}162.475MHz clintserman@gmail= * N 35 50.9100, W 083 58.0800, 25 MPH, course 230, alt 945 ft, 162.475MHz * * KJ4ETP-9>SUUP6Y,GRNTOP-3*,WIDE2-1,qAR,KI4HDU-2:`oU~nT >/]<0x9a>xt}162.475MHz clintserman@gmail= * Invalid character in MIC-E altitude. Must be in range of '!' to '{'. * N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 3280843 ft, 162.475MHz * * KJ4ETP-9>SUUP6Y,N4NEQ-3,K4EGA-1,WIDE2*,qAS,N5CWH-1:`oU~nT >/]?xt}162.475MHz clintserman@gmail= * N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 808497 ft, 162.475MHz * * KJ4ETP-9>SUUP2W,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV2o"J>/]"7)}162.475MHz clintserman@gmail= * N 35 50.2700, W 083 58.2200, 35 MPH, course 246, alt 955 ft, 162.475MHz * * Note the <0x9a> which is outside of the 7-bit ASCII range. Clearly very wrong. */ if (plast > pfirst && pfirst[3] == '}') { A->g_altitude_ft = DW_METERS_TO_FEET((pfirst[0]-33)*91*91 + (pfirst[1]-33)*91 + (pfirst[2]-33) - 10000); if ( ! isdigit91(pfirst[0]) || ! isdigit91(pfirst[1]) || ! isdigit91(pfirst[2])) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in MIC-E altitude. Must be in range of '!' to '{'.\n"); dw_printf("Bogus altitude of %.0f changed to unknown.\n", A->g_altitude_ft); } A->g_altitude_ft = G_UNKNOWN; } pfirst += 4; } process_comment (A, (char*)pfirst, (int)(plast - pfirst) + 1); } /*------------------------------------------------------------------ * * Function: aprs_message * * Purpose: Decode "Message Format." * The word message is used loosely all over the place, but it has a very specific meaning here. * * Inputs: info - Pointer to Information field. * ilen - Information field length. * quiet - supress error messages. * * Outputs: A->g_msg_type Text description for screen display. * * A->g_addressee To whom is it addressed. * Could be a specific station, alias, bulletin, etc. * For telemetry metadata is is about this station, * not being sent to it. * * A->g_message_subtype Subtype so caller might avoid replicating * all the code to distinguish them. * * A->g_message_number Message number if any. Required for ack/rej. * * Description: An APRS message is a text string with a specified addressee. * * It's a lot more complicated with different types of addressees * and replies with acknowledgement or rejection. * * There is even a special case for telemetry metadata. * * * Cases: :xxxxxxxxx:PARM. Telemetry metadata, parameter name * :xxxxxxxxx:UNIT. Telemetry metadata, unit/label * :xxxxxxxxx:EQNS. Telemetry metadata, Equation Coefficents * :xxxxxxxxx:BITS. Telemetry metadata, Bit Sense/Project Name * :xxxxxxxxx:? Directed Station Query * :xxxxxxxxx:ack Message acknowledged (received) * :xxxxxxxxx:rej Message rejected (unable to accept) * * :xxxxxxxxx: ... Message with no message number. * (Text may not contain the { character because * it indicates beginning of optional message number.) * :xxxxxxxxx: ... {num Message with message number. * *------------------------------------------------------------------*/ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int quiet) { struct aprs_message_s { char dti; /* : */ char addressee[9]; char colon; /* : */ char message[73]; /* 0-67 characters for message */ /* Optional { followed by 1-5 characters for message number */ /* If the first chracter is '?' it is a Directed Station Query. */ } *p; char addressee[AX25_MAX_ADDR_LEN]; int i; p = (struct aprs_message_s *)info; strlcpy (A->g_msg_type, "APRS Message", sizeof(A->g_msg_type)); A->g_message_subtype = message_subtype_message; /* until found otherwise */ if (ilen < 11) { if (! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("APRS Message must have a minimum of 11 characters for : 9 character addressee :\n"); } A->g_message_subtype = message_subtype_invalid; return; } if (p->colon != ':') { if (! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("APRS Message must begin with : 9 character addressee :\n"); } A->g_message_subtype = message_subtype_invalid; return; } memset (addressee, 0, sizeof(addressee)); memcpy (addressee, p->addressee, sizeof(p->addressee)); // copy exactly 9 bytes. /* Trim trailing spaces. */ i = strlen(addressee) - 1; while (i >= 0 && addressee[i] == ' ') { addressee[i--] = '\0'; } strlcpy (A->g_addressee, addressee, sizeof(A->g_addressee)); /* * Special message formats contain telemetry metadata. * It applies to the addressee, not the sender. * Makes no sense to me that it would not apply to sender instead. * Wouldn't the sender be describing his own data? * * I also don't understand the reasoning for putting this in a "message." * Telemetry data always starts with "#" after the "T" data type indicator. * Why not use other characters after the "T" for metadata? */ if (strncmp(p->message,"PARM.",5) == 0) { snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Parameter Name Message for \"%s\"", addressee); A->g_message_subtype = message_subtype_telem_parm; telemetry_name_message (addressee, p->message+5); } else if (strncmp(p->message,"UNIT.",5) == 0) { snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Unit/Label Message for \"%s\"", addressee); A->g_message_subtype = message_subtype_telem_unit; telemetry_unit_label_message (addressee, p->message+5); } else if (strncmp(p->message,"EQNS.",5) == 0) { snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Equation Coefficents Message for \"%s\"", addressee); A->g_message_subtype = message_subtype_telem_eqns; telemetry_coefficents_message (addressee, p->message+5, quiet); } else if (strncmp(p->message,"BITS.",5) == 0) { snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee); A->g_message_subtype = message_subtype_telem_bits; telemetry_bit_sense_message (addressee, p->message+5, quiet); } /* * If first character of message is "?" it is a query directed toward a specific station. */ else if (p->message[0] == '?') { strlcpy (A->g_msg_type, "Directed Station Query", sizeof(A->g_msg_type)); A->g_message_subtype = message_subtype_directed_query; aprs_directed_station_query (A, addressee, p->message+1, quiet); } /* ack or rej? Message number is required for these. */ else if (strncmp(p->message,"ack",3) == 0) { strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number)); snprintf (A->g_msg_type, sizeof(A->g_msg_type), "ACK message %s for \"%s\"", A->g_message_number, addressee); A->g_message_subtype = message_subtype_ack; } else if (strncmp(p->message,"rej",3) == 0) { strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number)); snprintf (A->g_msg_type, sizeof(A->g_msg_type), "REJ message %s for \"%s\"", A->g_message_number, addressee); A->g_message_subtype = message_subtype_ack; } /* message number is optional here. */ else { char *pno = strchr(p->message, '{'); if (pno != NULL) { strlcpy (A->g_message_number, pno+1, sizeof(A->g_message_number)); } snprintf (A->g_msg_type, sizeof(A->g_msg_type), "APRS Message %s for \"%s\"", A->g_message_number, addressee); A->g_message_subtype = message_subtype_message; /* No location so don't use process_comment () */ strlcpy (A->g_comment, p->message, sizeof(A->g_comment)); } } /*------------------------------------------------------------------ * * Function: aprs_object * * Purpose: Decode "Object Report Format" * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: A->g_object_name, A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft. * * Description: Message has a 9 character object name which could be quite different than * the source station. * * This can also be a weather report when the symbol id is '_'. * * Examples: ;WA2PNU *050457z4051.72N/07325.53W]BBS & FlexNet 145.070 MHz * * ;ActonEOC *070352z4229.20N/07125.95WoFire, EMS, Police, Heli-pad, Dial 911 * * ;IRLPC494@*012112zI9*n* * *------------------------------------------------------------------*/ static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen) { struct aprs_object_s { char dti; /* ; */ char name[9]; char live_killed; /* * for live or _ for killed */ char time_stamp[7]; position_t pos; char comment[43]; /* First 7 bytes could be data extension. */ } *p; struct aprs_compressed_object_s { char dti; /* ; */ char name[9]; char live_killed; /* * for live or _ for killed */ char time_stamp[7]; compressed_position_t cpos; char comment[40]; /* No data extension in this case. */ } *q; time_t ts = 0; int i; p = (struct aprs_object_s *)info; q = (struct aprs_compressed_object_s *)info; //assert (sizeof(A->g_name) > sizeof(p->name)); memset (A->g_name, 0, sizeof(A->g_name)); memcpy (A->g_name, p->name, sizeof(p->name)); // copy exactly 9 bytes. /* Trim trailing spaces. */ i = strlen(A->g_name) - 1; while (i >= 0 && A->g_name[i] == ' ') { A->g_name[i--] = '\0'; } if (p->live_killed == '*') strlcpy (A->g_msg_type, "Object", sizeof(A->g_msg_type)); else if (p->live_killed == '_') strlcpy (A->g_msg_type, "Killed Object", sizeof(A->g_msg_type)); else strlcpy (A->g_msg_type, "Object - invalid live/killed", sizeof(A->g_msg_type)); ts = get_timestamp (A, p->time_stamp); if (isdigit((unsigned char)(p->pos.lat[0]))) /* Human-readable location. */ { decode_position (A, &(p->pos)); if (A->g_symbol_code == '_') { /* Symbol code indidates it is a weather report. */ /* In this case, we expect 7 byte "data extension" */ /* for the wind direction and speed. */ strlcpy (A->g_msg_type, "Weather Report with Object", sizeof(A->g_msg_type)); weather_data (A, p->comment, TRUE); } else { /* Regular object. */ data_extension_comment (A, p->comment); } } else /* Compressed location. */ { decode_compressed_position (A, &(q->cpos)); if (A->g_symbol_code == '_') { /* Symbol code indidates it is a weather report. */ /* The spec doesn't explicitly mention the combination */ /* of weather report and object with compressed */ /* position. */ strlcpy (A->g_msg_type, "Weather Report with Object", sizeof(A->g_msg_type)); weather_data (A, q->comment, FALSE); } else { /* Regular position report. */ process_comment (A, q->comment, -1); } } (void)(ts); } /* end aprs_object */ /*------------------------------------------------------------------ * * Function: aprs_item * * Purpose: Decode "Item Report Format" * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: A->g_object_name, A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft. * * Description: An "item" is very much like an "object" except * * -- It doesn't have a time. * -- Name is a VARIABLE length 3 to 9 instead of fixed 9. * -- "live" indicator is ! rather than * * * Examples: * *------------------------------------------------------------------*/ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) { struct aprs_item_s { char dti; /* ')' */ char name[10]; /* Actually variable length 3 - 9 bytes. */ /* DON'T refer to the rest of this structure; */ /* the offsets will be wrong! */ /* We make it 10 here so we don't get subscript out of bounds */ /* warning when looking for following '!' or '_' character. */ char live_killed__; /* ! for live or _ for killed */ position_t pos__; char comment__[43]; /* First 7 bytes could be data extension. */ } *p; struct aprs_compressed_item_s { char dti; /* ')' */ char name[10]; /* Actually variable length 3 - 9 bytes. */ /* DON'T refer to the rest of this structure; */ /* the offsets will be wrong! */ char live_killed__; /* ! for live or _ for killed */ compressed_position_t cpos__; char comment__[40]; /* No data extension in this case. */ } *q; int i; char *ppos; p = (struct aprs_item_s *)info; q = (struct aprs_compressed_item_s *)info; (void)(q); memset (A->g_name, 0, sizeof(A->g_name)); i = 0; while (i < 9 && p->name[i] != '!' && p->name[i] != '_') { A->g_name[i] = p->name[i]; i++; A->g_name[i] = '\0'; } if (p->name[i] == '!') strlcpy (A->g_msg_type, "Item", sizeof(A->g_msg_type)); else if (p->name[i] == '_') strlcpy (A->g_msg_type, "Killed Item", sizeof(A->g_msg_type)); else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Item name too long or not followed by ! or _.\n"); } strlcpy (A->g_msg_type, "Object - invalid live/killed", sizeof(A->g_msg_type)); } ppos = p->name + i + 1; if (isdigit(*ppos)) /* Human-readable location. */ { decode_position (A, (position_t*) ppos); data_extension_comment (A, ppos + sizeof(position_t)); } else /* Compressed location. */ { decode_compressed_position (A, (compressed_position_t*)ppos); process_comment (A, ppos + sizeof(compressed_position_t), -1); } } /*------------------------------------------------------------------ * * Function: aprs_station_capabilities * * Purpose: Decode "Station Capabilities" * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: ??? * * Description: Each capability is a TOKEN or TOKEN=VALUE pair. * * * Example: * * Bugs: Not implemented yet. Treat whole thing as comment. * *------------------------------------------------------------------*/ static void aprs_station_capabilities (decode_aprs_t *A, char *info, int ilen) { strlcpy (A->g_msg_type, "Station Capabilities", sizeof(A->g_msg_type)); // process_comment() not applicable here because it // extracts information found in certain formats. strlcpy (A->g_comment, info+1, sizeof(A->g_comment)); } /* end aprs_station_capabilities */ /*------------------------------------------------------------------ * * Function: aprs_status_report * * Purpose: Decode "Status Report" * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: ??? * * Description: There are 3 different formats: * * (1) '>' * 7 char - timestamp, DHM z format * 0-55 char - status text * * (3) '>' * 4 or 6 char - Maidenhead Locator * 2 char - symbol table & code * ' ' character * 0-53 char - status text * * (2) '>' * 0-62 char - status text * * * In all cases, Beam heading and ERP can be at the * very end by using '^' and two other characters. * * * Examples from specification: * * * >Net Control Center without timestamp. * >092345zNet Control Center with timestamp. * >IO91SX/G * >IO91/G * >IO91SX/- My house (Note the space at the start of the status text). * >IO91SX/- ^B7 Meteor Scatter beam heading = 110 degrees, ERP = 490 watts. * *------------------------------------------------------------------*/ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) { struct aprs_status_time_s { char dti; /* > */ char ztime[7]; /* Time stamp ddhhmmz */ char comment[55]; } *pt; struct aprs_status_m4_s { char dti; /* > */ char mhead4[4]; /* 4 character Maidenhead locator. */ char sym_table_id; char symbol_code; char space; /* Should be space after symbol code. */ char comment[54]; } *pm4; struct aprs_status_m6_s { char dti; /* > */ char mhead6[6]; /* 6 character Maidenhead locator. */ char sym_table_id; char symbol_code; char space; /* Should be space after symbol code. */ char comment[54]; } *pm6; struct aprs_status_s { char dti; /* > */ char comment[62]; } *ps; strlcpy (A->g_msg_type, "Status Report", sizeof(A->g_msg_type)); pt = (struct aprs_status_time_s *)info; pm4 = (struct aprs_status_m4_s *)info; pm6 = (struct aprs_status_m6_s *)info; ps = (struct aprs_status_s *)info; /* * Do we have format with time? */ if (isdigit(pt->ztime[0]) && isdigit(pt->ztime[1]) && isdigit(pt->ztime[2]) && isdigit(pt->ztime[3]) && isdigit(pt->ztime[4]) && isdigit(pt->ztime[5]) && pt->ztime[6] == 'z') { // process_comment() not applicable here because it // extracts information found in certain formats. strlcpy (A->g_comment, pt->comment, sizeof(A->g_comment)); } /* * Do we have format with 6 character Maidenhead locator? */ else if (get_maidenhead (A, pm6->mhead6) == 6) { memset (A->g_maidenhead, 0, sizeof(A->g_maidenhead)); memcpy (A->g_maidenhead, pm6->mhead6, sizeof(pm6->mhead6)); A->g_symbol_table = pm6->sym_table_id; A->g_symbol_code = pm6->symbol_code; if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' && ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table)) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", A->g_symbol_table); } A->g_symbol_table = '/'; } if (pm6->space != ' ' && pm6->space != '\0') { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm6->space); } } // process_comment() not applicable here because it // extracts information found in certain formats. strlcpy (A->g_comment, pm6->comment, sizeof(A->g_comment)); } /* * Do we have format with 4 character Maidenhead locator? */ else if (get_maidenhead (A, pm4->mhead4) == 4) { memset (A->g_maidenhead, 0, sizeof(A->g_maidenhead)); memcpy (A->g_maidenhead, pm4->mhead4, sizeof(pm4->mhead4)); A->g_symbol_table = pm4->sym_table_id; A->g_symbol_code = pm4->symbol_code; if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' && ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table)) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", A->g_symbol_table); } A->g_symbol_table = '/'; } if (pm4->space != ' ' && pm4->space != '\0') { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm4->space); } } // process_comment() not applicable here because it // extracts information found in certain formats. strlcpy (A->g_comment, pm4->comment, sizeof(A->g_comment)); } /* * Whole thing is status text. */ else { strlcpy (A->g_comment, ps->comment, sizeof(A->g_comment)); } /* * Last 3 characters can represent beam heading and ERP. */ if (strlen(A->g_comment) >= 3) { char *hp = A->g_comment + strlen(A->g_comment) - 3; if (*hp == '^') { char h = hp[1]; char p = hp[2]; int beam = -1; int erp = -1; if (h >= '0' && h <= '9') { beam = (h - '0') * 10; } else if (h >= 'A' && h <= 'Z') { beam = (h - 'A') * 10 + 100; } if (p >= '1' && p <= 'K') { erp = (p - '0') * (p - '0') * 10; } // TODO (low): put result somewhere. // could use A->g_directivity and need new variable for erp. *hp = '\0'; (void)(beam); (void)(erp); } } } /* end aprs_status_report */ /*------------------------------------------------------------------ * * Function: aprs_general_query * * Purpose: Decode "General Query" for all stations. * * Inputs: info - Pointer to Information field. First character should be "?". * ilen - Information field length. * quiet - suppress error messages. * * Outputs: A - Decoded packet structure * A->g_query_type * A->g_query_lat (optional) * A->g_query_lon (optional) * A->g_query_radius (optional) * * Description: Formats are: * * ?query? * ?query?lat,long,radius * * 'query' is one of APRS, IGATE, WX, ... * optional footprint, in degrees and miles radius, means only * those in the specified circle should respond. * * Examples from specification, Chapter 15: * * ?APRS? * ?APRS? 34.02,-117.15,0200 * ?IGATE? * *------------------------------------------------------------------*/ static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quiet) { char *q2; char *p; char *tok; char stemp[256]; double lat, lon; float radius; strlcpy (A->g_msg_type, "General Query", sizeof(A->g_msg_type)); /* * First make a copy because we will modify it while parsing it. */ strlcpy (stemp, info, sizeof(stemp)); /* * There should be another "?" after the query type. */ q2 = strchr(stemp+1, '?'); if (q2 == NULL) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("General Query must have ? after the query type.\n"); } return; } *q2 = '\0'; strlcpy (A->g_query_type, stemp+1, sizeof(A->g_query_type)); // TODO: remove debug text_color_set(DW_COLOR_DEBUG); dw_printf("DEBUG: General Query type = \"%s\"\n", A->g_query_type); p = q2 + 1; if (strlen(p) == 0) { return; } /* * Try to extract footprint. * Spec says positive coordinate would be preceded by space * and radius must be exactly 4 digits. We are more forgiving. */ tok = strsep(&p, ","); if (tok != NULL) { lat = atof(tok); tok = strsep(&p, ","); if (tok != NULL) { lon = atof(tok); tok = strsep(&p, ","); if (tok != NULL) { radius = atof(tok); if (lat < -90 || lat > 90) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid latitude for General Query footprint.\n"); } return; } if (lon < -180 || lon > 180) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid longitude for General Query footprint.\n"); } return; } if (radius <= 0 || radius > 9999) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid radius for General Query footprint.\n"); } return; } A->g_footprint_lat = lat; A->g_footprint_lon = lon; A->g_footprint_radius = radius; } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Can't get radius for General Query footprint.\n"); } return; } } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Can't get longitude for General Query footprint.\n"); } return; } } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Can't get latitude for General Query footprint.\n"); } return; } // TODO: remove debug text_color_set(DW_COLOR_DEBUG); dw_printf("DEBUG: General Query footprint = %.6f %.6f %.2f\n", lat, lon, radius); } /* end aprs_general_query */ /*------------------------------------------------------------------ * * Function: aprs_directed_station_query * * Purpose: Decode "Directed Station Query" aimed at specific station. * This is actually a special format of the more general "message." * * Inputs: addressee - To whom it is directed. * Redundant because it is already in A->addressee. * * query - What's left over after ":addressee:?" in info part. * * quiet - suppress error messages. * * Outputs: A - Decoded packet structure * A->g_query_type * A->g_query_callsign (optional) * * Description: The caller has already removed the :addressee:? part so we are left * with a query type of exactly 5 characters and optional "callsign * of heard station." * * Examples from specification, Chapter 15. Our "query" argument. * * :KH2Z :?APRSD APRSD * :KH2Z :?APRSHVN0QBF APRSHVN0QBF * :KH2Z :?APRST APRST * :KH2Z :?PING? PING? * * "PING?" contains "?" only to pad it out to exactly 5 characters. * *------------------------------------------------------------------*/ static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet) { //char query_type[20]; /* Does the query type always need to be exactly 5 characters? */ /* If not, how would we know where the extra optional information starts? */ //char callsign[AX25_MAX_ADDR_LEN]; //if (strlen(query) < 5) ... } /* end aprs_directed_station_query */ /*------------------------------------------------------------------ * * Function: aprs_Telemetry * * Purpose: Decode "Telemetry" * * Inputs: info - Pointer to Information field. * ilen - Information field length. * quiet - suppress error messages. * * Outputs: A->g_telemetry * A->g_comment * * Description: TBD. * * Examples from specification: * * * TBD * *------------------------------------------------------------------*/ static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen, int quiet) { strlcpy (A->g_msg_type, "Telemetry", sizeof(A->g_msg_type)); telemetry_data_original (A->g_src, info, quiet, A->g_telemetry, sizeof(A->g_telemetry), A->g_comment, sizeof(A->g_comment)); } /* end aprs_telemetry */ /*------------------------------------------------------------------ * * Function: aprs_raw_touch_tone * * Purpose: Decode raw touch tone datA-> * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Description: Touch tone data is converted to a packet format * so it can be conveyed to an application for processing. * * This is not part of the APRS standard. * *------------------------------------------------------------------*/ static void aprs_raw_touch_tone (decode_aprs_t *A, char *info, int ilen) { strlcpy (A->g_msg_type, "Raw Touch Tone Data", sizeof(A->g_msg_type)); /* Just copy the info field without the message type. */ if (*info == '{') strlcpy (A->g_comment, info+3, sizeof(A->g_comment)); else strlcpy (A->g_comment, info+1, sizeof(A->g_comment)); } /* end aprs_raw_touch_tone */ /*------------------------------------------------------------------ * * Function: aprs_morse_code * * Purpose: Convey message in packet format to be transmitted as * Morse Code. * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Description: This is not part of the APRS standard. * *------------------------------------------------------------------*/ static void aprs_morse_code (decode_aprs_t *A, char *info, int ilen) { strlcpy (A->g_msg_type, "Morse Code Data", sizeof(A->g_msg_type)); /* Just copy the info field without the message type. */ if (*info == '{') strlcpy (A->g_comment, info+3, sizeof(A->g_comment)); else strlcpy (A->g_comment, info+1, sizeof(A->g_comment)); } /* end aprs_morse_code */ /*------------------------------------------------------------------ * * Function: aprs_ll_pos_time * * Purpose: Decode weather report without a position. * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: A->g_symbol_table, A->g_symbol_code. * * Description: Type identifier '_' is a weather report without a position. * *------------------------------------------------------------------*/ static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *info, int ilen) { struct aprs_positionless_weather_s { char dti; /* _ */ char time_stamp[8]; /* MDHM format */ char comment[99]; } *p; strlcpy (A->g_msg_type, "Positionless Weather Report", sizeof(A->g_msg_type)); //time_t ts = 0; p = (struct aprs_positionless_weather_s *)info; // not yet implemented for 8 character format // ts = get_timestamp (A, p->time_stamp); weather_data (A, p->comment, FALSE); } /*------------------------------------------------------------------ * * Function: weather_data * * Purpose: Decode weather data in position or object report. * * Inputs: info - Pointer to first byte after location * and symbol code. * * wind_prefix - Expecting leading wind info * for human-readable location. * (Currently ignored. We are very * forgiving in what is accepted.) * TODO: call this context instead and have 3 enumerated values. * * Global In: A->g_course - Wind info for compressed location. * A->g_speed_mph * * Outputs: A->g_weather * * Description: Extract weather details and format into a comment. * * For human-readable locations, we expect wind direction * and speed in a format like this: 999/999. * For compressed location, this has already been * processed and put in A->g_course and A->g_speed_mph. * Otherwise, for positionless weather data, the * wind is in the form c999s999. * * References: APRS Weather specification comments. * http://aprs.org/aprs11/spec-wx.txt * * Weather updates to the spec. * http://aprs.org/aprs12/weather-new.txt * * Examples: * * _10090556c220s004g005t077r000p000P000h50b09900wRSW * !4903.50N/07201.75W_220/004g005t077r000p000P000h50b09900wRSW * !4903.50N/07201.75W_220/004g005t077r000p000P000h50b.....wRSW * @092345z4903.50N/07201.75W_220/004g005t-07r000p000P000h50b09900wRSW * =/5L!!<*e7_7P[g005t077r000p000P000h50b09900wRSW * @092345z/5L!!<*e7_7P[g005t077r000p000P000h50b09900wRSW * ;BRENDA *092345z4903.50N/07201.75W_220/004g005b0990 * *------------------------------------------------------------------*/ static int getwdata (char **wpp, char ch, int dlen, float *val) { char stemp[8]; // larger than maximum dlen. int i; //dw_printf("debug: getwdata (wp=%p, ch=%c, dlen=%d)\n", *wpp, ch, dlen); *val = G_UNKNOWN; assert (dlen >= 2 && dlen <= 6); if (**wpp != ch) { /* Not specified element identifier. */ return (0); } if (strncmp((*wpp)+1, "......", dlen) == 0 || strncmp((*wpp)+1, " ", dlen) == 0) { /* Field present, unknown value */ *wpp += 1 + dlen; return (1); } /* Data field can contain digits, decimal point, leading negative. */ for (i=1; i<=dlen; i++) { if ( ! isdigit((*wpp)[i]) && (*wpp)[i] != '.' && (*wpp)[i] != '-' ) { return(0); } } memset (stemp, 0, sizeof(stemp)); memcpy (stemp, (*wpp)+1, dlen); *val = atof(stemp); //dw_printf("debug: getwdata returning %f\n", *val); *wpp += 1 + dlen; return (1); } static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) { int n; float fval; char *wp = wdata; int keep_going; if (wp[3] == '/') { if (sscanf (wp, "%3d", &n)) { // Data Extension format. // Fine point: Officially, should be values of 001-360. // "000" or "..." or " " means unknown. // In practice we see do see "000" here. A->g_course = n; } if (sscanf (wp+4, "%3d", &n)) { A->g_speed_mph = DW_KNOTS_TO_MPH(n); /* yes, in knots */ } wp += 7; } else if ( A->g_speed_mph == G_UNKNOWN) { if ( ! getwdata (&wp, 'c', 3, &A->g_course)) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Didn't find wind direction in form c999.\n"); } } if ( ! getwdata (&wp, 's', 3, &A->g_speed_mph)) { /* MPH here */ if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Didn't find wind speed in form s999.\n"); } } } // At this point, we should have the wind direction and speed // from one of three methods. if (A->g_speed_mph != G_UNKNOWN) { snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph", A->g_speed_mph); if (A->g_course != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", direction %.0f", A->g_course); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } /* We don't want this to show up on the location line. */ A->g_speed_mph = G_UNKNOWN; A->g_course = G_UNKNOWN; /* * After the mandatory wind direction and speed (in 1 of 3 formats), the * next two must be in fixed positions: * - gust (peak in mph last 5 minutes) * - temperature, degrees F, can be negative e.g. -01 */ if (getwdata (&wp, 'g', 3, &fval)) { if (fval != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", gust %.0f", fval); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Didn't find wind gust in form g999.\n"); } } if (getwdata (&wp, 't', 3, &fval)) { if (fval != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", temperature %.0f", fval); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Didn't find temperature in form t999.\n"); } } /* * Now pick out other optional fields in any order. */ keep_going = 1; while (keep_going) { if (getwdata (&wp, 'r', 3, &fval)) { /* r = rainfall, 1/100 inch, last hour */ if (fval != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", rain %.2f in last hour", fval / 100.); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'p', 3, &fval)) { /* p = rainfall, 1/100 inch, last 24 hours */ if (fval != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", rain %.2f in last 24 hours", fval / 100.); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'P', 3, &fval)) { /* P = rainfall, 1/100 inch, since midnight */ if (fval != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", rain %.2f since midnight", fval / 100.); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'h', 2, &fval)) { /* h = humidity %, 00 means 100% */ if (fval != G_UNKNOWN) { char ctemp[30]; if (fval == 0) fval = 100; snprintf (ctemp, sizeof(ctemp), ", humidity %.0f", fval); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'b', 5, &fval)) { /* b = barometric presure (tenths millibars / tenths of hPascal) */ /* Here, display as inches of mercury. */ if (fval != G_UNKNOWN) { char ctemp[40]; fval = DW_MBAR_TO_INHG(fval * 0.1); snprintf (ctemp, sizeof(ctemp), ", barometer %.2f", fval); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'L', 3, &fval)) { /* L = Luminosity, watts/ sq meter, 000-999 */ if (fval != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", %.0f watts/m^2", fval); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'l', 3, &fval)) { /* l = Luminosity, watts/ sq meter, 1000-1999 */ if (fval != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", %.0f watts/m^2", fval + 1000); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 's', 3, &fval)) { /* s = Snowfall in last 24 hours, inches */ /* Data can have decimal point so we don't have to worry about scaling. */ /* 's' is also used by wind speed but that must be in a fixed */ /* position in the message so there is no confusion. */ if (fval != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", %.1f snow in 24 hours", fval); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 's', 3, &fval)) { /* # = Raw rain counter */ if (fval != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", raw rain counter %.f", fval); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'X', 3, &fval)) { /* X = Nuclear Radiation. */ /* Encoded as two significant digits and order of magnitude */ /* like resistor color code. */ // TODO: decode this properly if (fval != G_UNKNOWN) { char ctemp[40]; snprintf (ctemp, sizeof(ctemp), ", nuclear Radiation %.f", fval); strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } // TODO: add new flood level, battery voltage, etc. else { keep_going = 0; } } /* * We should be left over with: * - one character for software. * - two to four characters for weather station type. * Examples: tU2k, wRSW * * But few people follow the protocol spec here. Instead more often we see things like: * sunny/WX * / {UIV32N} */ strlcat (A->g_weather, ", \"", sizeof(A->g_weather)); strlcat (A->g_weather, wp, sizeof(A->g_weather)); /* * Drop any CR / LF character at the end. */ n = strlen(A->g_weather); if (n >= 1 && A->g_weather[n-1] == '\n') { A->g_weather[n-1] = '\0'; } n = strlen(A->g_weather); if (n >= 1 && A->g_weather[n-1] == '\r') { A->g_weather[n-1] = '\0'; } strlcat (A->g_weather, "\"", sizeof(A->g_weather)); return; } /* end weather_data */ /*------------------------------------------------------------------ * * Function: aprs_ultimeter * * Purpose: Decode Peet Brothers ULTIMETER Weather Station Info. * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: A->g_weather * * Description: http://www.peetbros.com/shop/custom.aspx?recid=7 * * There are two different data formats in use. * One begins with $ULTW and is called "Packet Mode." Example: * * $ULTW009400DC00E21B8027730008890200010309001E02100000004C * * The other begins with !! and is called "logging mode." Example: * * !!000000A600B50000----------------001C01D500000017 * * * Bugs: Implementation is incomplete. * The example shown in the APRS protocol spec has a couple "----" * fields in the $ULTW message. This should be rewritten to handle * each field separately to deal with missing pieces. * *------------------------------------------------------------------*/ static void aprs_ultimeter (decode_aprs_t *A, char *info, int ilen) { // Header = $ULTW // Data Fields short h_windpeak; // 1. Wind Speed Peak over last 5 min. (0.1 kph) short h_wdir; // 2. Wind Direction of Wind Speed Peak (0-255) short h_otemp; // 3. Current Outdoor Temp (0.1 deg F) short h_totrain; // 4. Rain Long Term Total (0.01 in.) short h_baro; // 5. Current Barometer (0.1 mbar) short h_barodelta; // 6. Barometer Delta Value(0.1 mbar) short h_barocorrl; // 7. Barometer Corr. Factor(LSW) short h_barocorrm; // 8. Barometer Corr. Factor(MSW) short h_ohumid; // 9. Current Outdoor Humidity (0.1%) short h_date; // 10. Date (day of year) short h_time; // 11. Time (minute of day) short h_raintoday; // 12. Today's Rain Total (0.01 inches)* short h_windave; // 13. 5 Minute Wind Speed Average (0.1kph)* // Carriage Return & Line Feed // *Some instruments may not include field 13, some may // not include 12 or 13. // Total size: 44, 48 or 52 characters (hex digits) + // header, carriage return and line feed. int n; strlcpy (A->g_msg_type, "Ultimeter", sizeof(A->g_msg_type)); if (*info == '$') { n = sscanf (info+5, "%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx", &h_windpeak, &h_wdir, &h_otemp, &h_totrain, &h_baro, &h_barodelta, &h_barocorrl, &h_barocorrm, &h_ohumid, &h_date, &h_time, &h_raintoday, // not on some models. &h_windave); // not on some models. if (n >= 11 && n <= 13) { float windpeak, wdir, otemp, baro, ohumid; windpeak = DW_KM_TO_MILES(h_windpeak * 0.1); wdir = (h_wdir & 0xff) * 360. / 256.; otemp = h_otemp * 0.1; baro = DW_MBAR_TO_INHG(h_baro * 0.1); ohumid = h_ohumid * 0.1; snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph, direction %.0f, temperature %.1f, barometer %.2f, humidity %.0f", windpeak, wdir, otemp, baro, ohumid); } } // Header = !! // Data Fields // 1. Wind Speed (0.1 kph) // 2. Wind Direction (0-255) // 3. Outdoor Temp (0.1 deg F) // 4. Rain* Long Term Total (0.01 inches) // 5. Barometer (0.1 mbar) [ can be ---- ] // 6. Indoor Temp (0.1 deg F) [ can be ---- ] // 7. Outdoor Humidity (0.1%) [ can be ---- ] // 8. Indoor Humidity (0.1%) [ can be ---- ] // 9. Date (day of year) // 10. Time (minute of day) // 11. Today's Rain Total (0.01 inches)* // 12. 1 Minute Wind Speed Average (0.1kph)* // Carriage Return & Line Feed // // *Some instruments may not include field 12, some may not include 11 or 12. // Total size: 40, 44 or 48 characters (hex digits) + header, carriage return and line feed if (*info == '!') { n = sscanf (info+2, "%4hx%4hx%4hx%4hx", &h_windpeak, &h_wdir, &h_otemp, &h_totrain); if (n == 4) { float windpeak, wdir, otemp; windpeak = DW_KM_TO_MILES(h_windpeak * 0.1); wdir = (h_wdir & 0xff) * 360. / 256.; otemp = h_otemp * 0.1; snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph, direction %.0f, temperature %.1f\n", windpeak, wdir, otemp); } } } /* end aprs_ultimeter */ /*------------------------------------------------------------------ * * Function: third_party_header * * Purpose: Decode packet from a third party network. * * Inputs: info - Pointer to Information field. * ilen - Information field length. * * Outputs: A->g_comment * * Description: * *------------------------------------------------------------------*/ static void third_party_header (decode_aprs_t *A, char *info, int ilen) { strlcpy (A->g_msg_type, "Third Party Header", sizeof(A->g_msg_type)); /* more later? */ } /* end third_party_header */ /*------------------------------------------------------------------ * * Function: decode_position * * Purpose: Decode the position & symbol information common to many message formats. * * Inputs: ppos - Pointer to position & symbol fields. * * Returns: A->g_lat * A->g_lon * A->g_symbol_table * A->g_symbol_code * * Description: This provides resolution of about 60 feet. * This can be improved by using !DAO! in the comment. * *------------------------------------------------------------------*/ static void decode_position (decode_aprs_t *A, position_t *ppos) { A->g_lat = get_latitude_8 (ppos->lat, A->g_quiet); A->g_lon = get_longitude_9 (ppos->lon, A->g_quiet); A->g_symbol_table = ppos->sym_table_id; A->g_symbol_code = ppos->symbol_code; } /*------------------------------------------------------------------ * * Function: decode_compressed_position * * Purpose: Decode the compressed position & symbol information common to many message formats. * * Inputs: ppos - Pointer to compressed position & symbol fields. * * Returns: A->g_lat * A->g_lon * A->g_symbol_table * A->g_symbol_code * * One of the following: * A->g_course & A->g_speeed * A->g_altitude_ft * A->g_range * * Description: The compressed position provides resolution of around ??? * This also includes course/speed or altitude. * * It contains 13 bytes of the format: * * symbol table /, \, or overlay A-Z, a-j is mapped into 0-9 * * yyyy Latitude, base 91. * * xxxx Longitude, base 91. * * symbol code * * cs Course/Speed or altitude. * * t Various "type" info. * *------------------------------------------------------------------*/ static void decode_compressed_position (decode_aprs_t *A, compressed_position_t *pcpos) { if (isdigit91(pcpos->y[0]) && isdigit91(pcpos->y[1]) && isdigit91(pcpos->y[2]) && isdigit91(pcpos->y[3])) { A->g_lat = 90 - ((pcpos->y[0]-33)*91*91*91 + (pcpos->y[1]-33)*91*91 + (pcpos->y[2]-33)*91 + (pcpos->y[3]-33)) / 380926.0; } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in compressed latitude. Must be in range of '!' to '{'.\n"); } A->g_lat = G_UNKNOWN; } if (isdigit91(pcpos->x[0]) && isdigit91(pcpos->x[1]) && isdigit91(pcpos->x[2]) && isdigit91(pcpos->x[3])) { A->g_lon = -180 + ((pcpos->x[0]-33)*91*91*91 + (pcpos->x[1]-33)*91*91 + (pcpos->x[2]-33)*91 + (pcpos->x[3]-33)) / 190463.0; } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in compressed longitude. Must be in range of '!' to '{'.\n"); } A->g_lon = G_UNKNOWN; } if (pcpos->sym_table_id == '/' || pcpos->sym_table_id == '\\' || isupper((int)(pcpos->sym_table_id))) { /* primary or alternate or alternate with upper case overlay. */ A->g_symbol_table = pcpos->sym_table_id; } else if (pcpos->sym_table_id >= 'a' && pcpos->sym_table_id <= 'j') { /* Lower case a-j are used to represent overlay characters 0-9 */ /* because a digit here would mean normal (non-compressed) location. */ A->g_symbol_table = pcpos->sym_table_id - 'a' + '0'; } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid symbol table id for compressed position.\n"); } A->g_symbol_table = '/'; } A->g_symbol_code = pcpos->symbol_code; if (pcpos->c == ' ') { ; /* ignore other two bytes */ } else if (((pcpos->t - 33) & 0x18) == 0x10) { A->g_altitude_ft = pow(1.002, (pcpos->c - 33) * 91 + pcpos->s - 33); } else if (pcpos->c == '{') { A->g_range = 2.0 * pow(1.08, pcpos->s - 33); } else if (pcpos->c >= '!' && pcpos->c <= 'z') { /* For a weather station, this is wind information. */ A->g_course = (pcpos->c - 33) * 4; A->g_speed_mph = DW_KNOTS_TO_MPH(pow(1.08, pcpos->s - 33) - 1.0); } } /*------------------------------------------------------------------ * * Function: get_latitude_8 * * Purpose: Convert 8 byte latitude encoding to degrees. * * Inputs: plat - Pointer to first byte. * * Returns: Double precision value in degrees. Negative for South. * * Description: Latitude is expressed as a fixed 8-character field, in degrees * and decimal minutes (to two decimal places), followed by the * letter N for north or S for south. * The protocol spec specifies upper case but I've seen lower * case so this will accept either one. * Latitude degrees are in the range 00 to 90. Latitude minutes * are expressed as whole minutes and hundredths of a minute, * separated by a decimal point. * For example: * 4903.50N is 49 degrees 3 minutes 30 seconds north. * In generic format examples, the latitude is shown as the 8-character * string ddmm.hhN (i.e. degrees, minutes and hundredths of a minute north). * * Bug: We don't properly deal with position ambiguity where trailing * digits might be replaced by spaces. We simply treat them like zeros. * * Errors: Return G_UNKNOWN for any type of error. * * Should probably print an error message. * *------------------------------------------------------------------*/ double get_latitude_8 (char *p, int quiet) { struct lat_s { unsigned char deg[2]; unsigned char minn[2]; char dot; unsigned char hmin[2]; char ns; } *plat; double result = 0; plat = (void *)p; if (isdigit(plat->deg[0])) result += ((plat->deg[0]) - '0') * 10; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in latitude. Found '%c' when expecting 0-9 for tens of degrees.\n", plat->deg[0]); } return (G_UNKNOWN); } if (isdigit(plat->deg[1])) result += ((plat->deg[1]) - '0') * 1; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in latitude. Found '%c' when expecting 0-9 for degrees.\n", plat->deg[1]); } return (G_UNKNOWN); } if (plat->minn[0] >= '0' && plat->minn[0] <= '5') result += ((plat->minn[0]) - '0') * (10. / 60.); else if (plat->minn[0] == ' ') ; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in latitude. Found '%c' when expecting 0-5 for tens of minutes.\n", plat->minn[0]); } return (G_UNKNOWN); } if (isdigit(plat->minn[1])) result += ((plat->minn[1]) - '0') * (1. / 60.); else if (plat->minn[1] == ' ') ; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in latitude. Found '%c' when expecting 0-9 for minutes.\n", plat->minn[1]); } return (G_UNKNOWN); } if (plat->dot != '.') { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Unexpected character \"%c\" found where period expected in latitude.\n", plat->dot); } return (G_UNKNOWN); } if (isdigit(plat->hmin[0])) result += ((plat->hmin[0]) - '0') * (0.1 / 60.); else if (plat->hmin[0] == ' ') ; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in latitude. Found '%c' when expecting 0-9 for tenths of minutes.\n", plat->hmin[0]); } return (G_UNKNOWN); } if (isdigit(plat->hmin[1])) result += ((plat->hmin[1]) - '0') * (0.01 / 60.); else if (plat->hmin[1] == ' ') ; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in latitude. Found '%c' when expecting 0-9 for hundredths of minutes.\n", plat->hmin[1]); } return (G_UNKNOWN); } // The spec requires upper case for hemisphere. Accept lower case but warn. if (plat->ns == 'N') { return (result); } else if (plat->ns == 'n') { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Warning: Lower case n found for latitude hemisphere. Specification requires upper case N or S.\n"); } return (result); } else if (plat->ns == 'S') { return ( - result); } else if (plat->ns == 's') { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Warning: Lower case s found for latitude hemisphere. Specification requires upper case N or S.\n"); } return ( - result); } else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Error: '%c' found for latitude hemisphere. Specification requires upper case N or S.\n", plat->ns); } return (G_UNKNOWN); } } /*------------------------------------------------------------------ * * Function: get_longitude_9 * * Purpose: Convert 9 byte longitude encoding to degrees. * * Inputs: plat - Pointer to first byte. * * Returns: Double precision value in degrees. Negative for West. * * Description: Longitude is expressed as a fixed 9-character field, in degrees and * decimal minutes (to two decimal places), followed by the letter E * for east or W for west. * Longitude degrees are in the range 000 to 180. Longitude minutes are * expressed as whole minutes and hundredths of a minute, separated by a * decimal point. * For example: * 07201.75W is 72 degrees 1 minute 45 seconds west. * In generic format examples, the longitude is shown as the 9-character * string dddmm.hhW (i.e. degrees, minutes and hundredths of a minute west). * * Bug: We don't properly deal with position ambiguity where trailing * digits might be replaced by spaces. We simply treat them like zeros. * * Errors: Return G_UNKNOWN for any type of error. * * Example: * *------------------------------------------------------------------*/ double get_longitude_9 (char *p, int quiet) { struct lat_s { unsigned char deg[3]; unsigned char minn[2]; char dot; unsigned char hmin[2]; char ew; } *plon; double result = 0; plon = (void *)p; if (plon->deg[0] == '0' || plon->deg[0] == '1') result += ((plon->deg[0]) - '0') * 100; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in longitude. Found '%c' when expecting 0 or 1 for hundreds of degrees.\n", plon->deg[0]); } return (G_UNKNOWN); } if (isdigit(plon->deg[1])) result += ((plon->deg[1]) - '0') * 10; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in longitude. Found '%c' when expecting 0-9 for tens of degrees.\n", plon->deg[1]); } return (G_UNKNOWN); } if (isdigit(plon->deg[2])) result += ((plon->deg[2]) - '0') * 1; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in longitude. Found '%c' when expecting 0-9 for degrees.\n", plon->deg[2]); } return (G_UNKNOWN); } if (plon->minn[0] >= '0' && plon->minn[0] <= '5') result += ((plon->minn[0]) - '0') * (10. / 60.); else if (plon->minn[0] == ' ') ; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in longitude. Found '%c' when expecting 0-5 for tens of minutes.\n", plon->minn[0]); } return (G_UNKNOWN); } if (isdigit(plon->minn[1])) result += ((plon->minn[1]) - '0') * (1. / 60.); else if (plon->minn[1] == ' ') ; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in longitude. Found '%c' when expecting 0-9 for minutes.\n", plon->minn[1]); } return (G_UNKNOWN); } if (plon->dot != '.') { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Unexpected character \"%c\" found where period expected in longitude.\n", plon->dot); } return (G_UNKNOWN); } if (isdigit(plon->hmin[0])) result += ((plon->hmin[0]) - '0') * (0.1 / 60.); else if (plon->hmin[0] == ' ') ; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in longitude. Found '%c' when expecting 0-9 for tenths of minutes.\n", plon->hmin[0]); } return (G_UNKNOWN); } if (isdigit(plon->hmin[1])) result += ((plon->hmin[1]) - '0') * (0.01 / 60.); else if (plon->hmin[1] == ' ') ; else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Invalid character in longitude. Found '%c' when expecting 0-9 for hundredths of minutes.\n", plon->hmin[1]); } return (G_UNKNOWN); } // The spec requires upper case for hemisphere. Accept lower case but warn. if (plon->ew == 'E') { return (result); } else if (plon->ew == 'e') { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Warning: Lower case e found for longitude hemisphere. Specification requires upper case E or W.\n"); } return (result); } else if (plon->ew == 'W') { return ( - result); } else if (plon->ew == 'w') { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Warning: Lower case w found for longitude hemisphere. Specification requires upper case E or W.\n"); } return ( - result); } else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Error: '%c' found for longitude hemisphere. Specification requires upper case E or W.\n", plon->ew); } return (G_UNKNOWN); } } /*------------------------------------------------------------------ * * Function: get_timestamp * * Purpose: Convert 7 byte timestamp to unix time value. * * Inputs: p - Pointer to first byte. * * Returns: time_t data type. (UTC) * * Description: * * Day/Hours/Minutes (DHM) format is a fixed 7-character field, consisting of * a 6-digit day/time group followed by a single time indicator character (z or * /). The day/time group consists of a two-digit day-of-the-month (01-31) and * a four-digit time in hours and minutes. * Times can be expressed in zulu (UTC/GMT) or local time. For example: * * 092345z is 2345 hours zulu time on the 9th day of the month. * 092345/ is 2345 hours local time on the 9th day of the month. * * It is recommended that future APRS implementations only transmit zulu * format on the air. * * Note: The time in Status Reports may only be in zulu format. * * Hours/Minutes/Seconds (HMS) format is a fixed 7-character field, * consisting of a 6-digit time in hours, minutes and seconds, followed by the h * time-indicator character. For example: * * 234517h is 23 hours 45 minutes and 17 seconds zulu. * * Note: This format may not be used in Status Reports. * * Month/Day/Hours/Minutes (MDHM) format is a fixed 8-character field, * consisting of the month (01-12) and day-of-the-month (01-31), followed by * the time in hours and minutes zulu. For example: * * 10092345 is 23 hours 45 minutes zulu on October 9th. * * This format is only used in reports from stand-alone "positionless" weather * stations (i.e. reports that do not contain station position information). * * * Bugs: Local time not implemented yet. * 8 character form not implemented yet. * * Boundary conditions are not handled properly. * For example, suppose it is 00:00:03 on January 1. * We receive a timestamp of 23:59:58 (which was December 31). * If we simply replace the time, and leave the current date alone, * the result is about a day into the future. * * * Example: * *------------------------------------------------------------------*/ time_t get_timestamp (decode_aprs_t *A, char *p) { struct dhm_s { char day[2]; char hours[2]; char minutes[2]; char tic; /* Time indicator character. */ /* z = UTC. */ /* / = local - not implemented yet. */ } *pdhm; struct hms_s { char hours[2]; char minutes[2]; char seconds[2]; char tic; /* Time indicator character. */ /* h = UTC. */ } *phms; struct tm *ptm; time_t ts; ts = time(NULL); ptm = gmtime(&ts); pdhm = (void *)p; phms = (void *)p; if (pdhm->tic == 'z' || pdhm->tic == '/') /* Wrong! */ { int j; j = (pdhm->day[0] - '0') * 10 + pdhm->day[1] - '0'; //text_color_set(DW_COLOR_DECODED); //dw_printf("Changing day from %d to %d\n", ptm->tm_mday, j); ptm->tm_mday = j; j = (pdhm->hours[0] - '0') * 10 + pdhm->hours[1] - '0'; //dw_printf("Changing hours from %d to %d\n", ptm->tm_hour, j); ptm->tm_hour = j; j = (pdhm->minutes[0] - '0') * 10 + pdhm->minutes[1] - '0'; //dw_printf("Changing minutes from %d to %d\n", ptm->tm_min, j); ptm->tm_min = j; } else if (phms->tic == 'h') { int j; j = (phms->hours[0] - '0') * 10 + phms->hours[1] - '0'; //text_color_set(DW_COLOR_DECODED); //dw_printf("Changing hours from %d to %d\n", ptm->tm_hour, j); ptm->tm_hour = j; j = (phms->minutes[0] - '0') * 10 + phms->minutes[1] - '0'; //dw_printf("Changing minutes from %d to %d\n", ptm->tm_min, j); ptm->tm_min = j; j = (phms->seconds[0] - '0') * 10 + phms->seconds[1] - '0'; //dw_printf("%sChanging seconds from %d to %d\n", ptm->tm_sec, j); ptm->tm_sec = j; } return (mktime(ptm)); } /*------------------------------------------------------------------ * * Function: get_maidenhead * * Purpose: See if we have a maidenhead locator. * * Inputs: p - Pointer to first byte. * * Returns: 0 = not found. * 4 = possible 4 character locator found. * 6 = possible 6 character locator found. * * It is not stored anywhere or processed. * * Description: * * The maidenhead locator system is sometimes used as a more compact, * and less precise, alternative to numeric latitude and longitude. * * It is composed of: * a pair of letters in range A to R. * a pair of digits in range of 0 to 9. * a pair of letters in range of A to X. * * The APRS spec says that all letters must be transmitted in upper case. * * * Examples from APRS spec: * * IO91SX * IO91 * * *------------------------------------------------------------------*/ int get_maidenhead (decode_aprs_t *A, char *p) { if (toupper(p[0]) >= 'A' && toupper(p[0]) <= 'R' && toupper(p[1]) >= 'A' && toupper(p[1]) <= 'R' && isdigit(p[2]) && isdigit(p[3])) { /* We have 4 characters matching the rule. */ if (islower(p[0]) || islower(p[1])) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Warning: Lower case letter in Maidenhead locator. Specification requires upper case.\n"); } } if (toupper(p[4]) >= 'A' && toupper(p[4]) <= 'X' && toupper(p[5]) >= 'A' && toupper(p[5]) <= 'X') { /* We have 6 characters matching the rule. */ if (islower(p[4]) || islower(p[5])) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Warning: Lower case letter in Maidenhead locator. Specification requires upper case.\n"); } } return 6; } return 4; } return 0; } /*------------------------------------------------------------------ * * Function: data_extension_comment * * Purpose: A fixed length 7-byte field may follow APRS position datA-> * * Inputs: pdext - Pointer to optional data extension and comment. * * Returns: true if a data extension was found. * * Outputs: One or more of the following, depending the data found: * * A->g_course * A->g_speed_mph * A->g_power * A->g_height * A->g_gain * A->g_directivity * A->g_range * * Anything left over will be put in * * A->g_comment * * Description: * * * *------------------------------------------------------------------*/ const char *dir[9] = { "omni", "NE", "E", "SE", "S", "SW", "W", "NW", "N" }; static int data_extension_comment (decode_aprs_t *A, char *pdext) { int n; if (strlen(pdext) < 7) { strlcpy (A->g_comment, pdext, sizeof(A->g_comment)); return 0; } /* Tyy/Cxx - Area object descriptor. */ if (pdext[0] == 'T' && pdext[3] == '/' && pdext[4] == 'C') { /* not decoded at this time */ process_comment (A, pdext+7, -1); return 1; } /* CSE/SPD */ /* For a weather station (symbol code _) this is wind. */ /* For others, it would be course and speed. */ if (pdext[3] == '/') { if (sscanf (pdext, "%3d", &n)) { A->g_course = n; } if (sscanf (pdext+4, "%3d", &n)) { A->g_speed_mph = DW_KNOTS_TO_MPH(n); } /* Bearing and Number/Range/Quality? */ if (pdext[7] == '/' && pdext[11] == '/') { process_comment (A, pdext + 7 + 8, -1); } else { process_comment (A, pdext+7, -1); } return 1; } /* check for Station power, height, gain. */ if (strncmp(pdext, "PHG", 3) == 0) { A->g_power = (pdext[3] - '0') * (pdext[3] - '0'); A->g_height = (1 << (pdext[4] - '0')) * 10; A->g_gain = pdext[5] - '0'; if (pdext[6] >= '0' && pdext[6] <= '8') { strlcpy (A->g_directivity, dir[pdext[6]-'0'], sizeof(A->g_directivity)); } process_comment (A, pdext+7, -1); return 1; } /* check for precalculated radio range. */ if (strncmp(pdext, "RNG", 3) == 0) { if (sscanf (pdext+3, "%4d", &n)) { A->g_range = n; } process_comment (A, pdext+7, -1); return 1; } /* DF signal strength, */ if (strncmp(pdext, "DFS", 3) == 0) { //A->g_strength = pdext[3] - '0'; A->g_height = (1 << (pdext[4] - '0')) * 10; A->g_gain = pdext[5] - '0'; if (pdext[6] >= '0' && pdext[6] <= '8') { strlcpy (A->g_directivity, dir[pdext[6]-'0'], sizeof(A->g_directivity)); } process_comment (A, pdext+7, -1); return 1; } process_comment (A, pdext, -1); return 0; } /*------------------------------------------------------------------ * * Function: decode_tocall * * Purpose: Extract application from the destination. * * Inputs: dest - Destination address. * Don't care if SSID is present or not. * * Outputs: A->g_mfr * * Description: For maximum flexibility, we will read the * data file at run time rather than compiling it in. * * For the most recent version, download from: * * http://www.aprs.org/aprs11/tocalls.txt * * Windows version: File must be in current working directory. * * Linux version: Search order is current working directory then * /usr/local/share/direwolf * /usr/share/direwolf/tocalls.txt * * Mac: Like Linux and then * /opt/local/share/direwolf * *------------------------------------------------------------------*/ // If I was more ambitious, this would dynamically allocate enough // storage based on the file contents. Just stick in a constant for // now. This takes an insignificant amount of space and // I don't anticipate tocalls.txt growing that quickly. // Version 1.4 - add message if too small instead of silently ignoring the rest. // Dec. 2016 tocalls.txt has 153 destination addresses. #define MAX_TOCALLS 200 static struct tocalls_s { unsigned char len; char prefix[7]; char *description; } tocalls[MAX_TOCALLS]; static int num_tocalls = 0; // Make sure the array is null terminated. // If search order is changed, do the same in symbols.c static const char *search_locations[] = { (const char *) "tocalls.txt", #ifndef __WIN32__ (const char *) "/usr/local/share/direwolf/tocalls.txt", (const char *) "/usr/share/direwolf/tocalls.txt", #endif #if __APPLE__ // https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458 // Adding the /opt/local tree since macports typically installs there. Users might want their // INSTALLDIR (see Makefile.macosx) to mirror that. If so, then we need to search the /opt/local // path as well. (const char *) "/opt/local/share/direwolf/tocalls.txt", #endif (const char *) NULL }; static int tocall_cmp (const void *px, const void *py) { const struct tocalls_s *x = (struct tocalls_s *)px; const struct tocalls_s *y = (struct tocalls_s *)py; if (x->len != y->len) return (y->len - x->len); return (strcmp(x->prefix, y->prefix)); } static void decode_tocall (decode_aprs_t *A, char *dest) { FILE *fp = 0; int n = 0; static int first_time = 1; char stuff[100]; char *p = NULL; char *r = NULL; //dw_printf("debug: decode_tocall(\"%s\")\n", dest); /* * Extract the calls and descriptions from the file. * * Use only lines with exactly these formats: * * APN Network nodes, digis, etc * APWWxx APRSISCE win32 version * | | | * 00000000001111111111 * 01234567890123456789... * * Matching will be with only leading upper case and digits. */ // TODO: Look for this in multiple locations. // For example, if application was installed in /usr/local/bin, // we might want to put this in /usr/local/share/aprs // If search strategy changes, be sure to keep symbols_init in sync. if (first_time) { n = 0; fp = NULL; do { if(search_locations[n] == NULL) break; fp = fopen(search_locations[n++], "r"); } while (fp == NULL); if (fp != NULL) { while (fgets(stuff, sizeof(stuff), fp) != NULL && num_tocalls < MAX_TOCALLS) { p = stuff + strlen(stuff) - 1; while (p >= stuff && (*p == '\r' || *p == '\n')) { *p-- = '\0'; } // dw_printf("debug: %s\n", stuff); if (stuff[0] == ' ' && stuff[4] == ' ' && stuff[5] == ' ' && stuff[6] == 'A' && stuff[7] == 'P' && stuff[12] == ' ' && stuff[13] == ' ' ) { p = stuff + 6; r = tocalls[num_tocalls].prefix; while (isupper((int)(*p)) || isdigit((int)(*p))) { *r++ = *p++; } *r = '\0'; if (strlen(tocalls[num_tocalls].prefix) > 2) { tocalls[num_tocalls].description = strdup(stuff+14); tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix); // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); num_tocalls++; } } else if (stuff[0] == ' ' && stuff[1] == 'A' && stuff[2] == 'P' && isupper((int)(stuff[3])) && stuff[4] == ' ' && stuff[5] == ' ' && stuff[6] == ' ' && stuff[12] == ' ' && stuff[13] == ' ' ) { p = stuff + 1; r = tocalls[num_tocalls].prefix; while (isupper((int)(*p)) || isdigit((int)(*p))) { *r++ = *p++; } *r = '\0'; if (strlen(tocalls[num_tocalls].prefix) > 2) { tocalls[num_tocalls].description = strdup(stuff+14); tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix); // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); num_tocalls++; } } if (num_tocalls == MAX_TOCALLS) { // oops. might have discarded some. text_color_set(DW_COLOR_ERROR); dw_printf("MAX_TOCALLS needs to be larger than %d to handle contents of 'tocalls.txt'.\n", MAX_TOCALLS); } } fclose(fp); /* * Sort by decreasing length so the search will go * from most specific to least specific. * Example: APY350 or APY008 would match those specific * models before getting to the more generic APY. */ #if defined(__WIN32__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__) qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), tocall_cmp); #else qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), (__compar_fn_t)tocall_cmp); #endif } else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Warning: Could not open 'tocalls.txt'.\n"); dw_printf("System types in the destination field will not be decoded.\n"); } } first_time = 0; //for (n=0; n '%s'\n", n, tocalls[n].len, tocalls[n].prefix, tocalls[n].description); //} } for (n=0; ng_mfr, tocalls[n].description, sizeof(A->g_mfr)); return; } } } /* end decode_tocall */ /*------------------------------------------------------------------ * * Function: substr_se * * Purpose: Extract substring given start and end+1 offset. * * Inputs: src - Source string * * start - Start offset. * * endp1 - End offset+1 for ease of use with regexec result. * * Outputs: dest - Destination for substring. * *------------------------------------------------------------------*/ // TODO: potential for buffer overflow here. static void substr_se (char *dest, const char *src, int start, int endp1) { int len = endp1 - start; if (start < 0 || endp1 < 0 || len <= 0) { dest[0] = '\0'; return; } memcpy (dest, src + start, len); dest[len] = '\0'; } /* end substr_se */ /*------------------------------------------------------------------ * * Function: process_comment * * Purpose: Extract optional items from the comment. * * Inputs: pstart - Pointer to start of left over information field. * * clen - Length of comment or -1 to take it all. * * Outputs: A->g_telemetry - Base 91 telemetry |ss1122| * A->g_altitude_ft - from /A=123456 * A->g_lat - Might be adjusted from !DAO! * A->g_lon - Might be adjusted from !DAO! * A->g_aprstt_loc - Private extension to !DAO! * A->g_freq * A->g_tone * A->g_offset * A->g_comment - Anything left over after extracting above. * * Description: After processing fixed and possible optional parts * of the message, everything left over is a comment. * * Except!!! * * There are could be some other pieces of data, with * particular formats, buried in there. * Pull out those special items and put everything * else into A->g_comment. * * References: http://www.aprs.org/info/freqspec.txt * * 999.999MHz T100 +060 Voice frequency. * * http://www.aprs.org/datum.txt * * !DAO! APRS precision and Datum option. * * Protocol reference, end of chaper 6. * * /A=123456 Altitude * * What can appear in a comment? * * Chapter 5 of the APRS spec ( http://www.aprs.org/doc/APRS101.PDF ) says: * * "The comment may contain any printable ASCII characters (except | and ~, * which are reserved for TNC channel switching)." * * "Printable" would exclude character values less than space (00100000), e.g. * tab, carriage return, line feed, nul. Sometimes we see carriage return * (00001010) at the end of APRS packets. This would be in violation of the * specification. * * The base 91 telemetry format (http://he.fi/doc/aprs-base91-comment-telemetry.txt ), * which is not part of the APRS spec, uses the | character in the comment to delimit encoded * telemetry data. This would be in violation of the original spec. * * The APRS Spec Addendum 1.2 Proposals ( http://www.aprs.org/aprs12/datum.txt) * adds use of UTF-8 (https://en.wikipedia.org/wiki/UTF-8 )for the free form text in * messages and comments. It can't be used in the fixed width fields. * * Non-ASCII characters are represented by multi-byte sequences. All bytes in these * multi-byte sequences have the most significant bit set to 1. Using UTF-8 would not * add any nul (00000000) bytes to the stream. * * There are two known cases where we can have a nul character value. * * * The Kenwood TM-D710A sometimes sends packets like this: * * VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast= * K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV = * * Notice that the data type indicator of "4" is not valid. If we remove * 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format. * This same thing has been observed from others and is intermittent. * * * AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes. * This is wrong. It should be using UTF-8 and I'm not going to accomodate it here. * * * The digipeater and IGate functions should pass along anything exactly the * we received it, even if it is invalid. If different implementations try to fix it up * somehow, like changing unprintable characters to spaces, we will only make things * worse and thwart the duplicate detection. * *------------------------------------------------------------------*/ /* CTCSS tones in various formats to avoid conversions every time. */ #define NUM_CTCSS 50 static const int i_ctcss[NUM_CTCSS] = { 67, 69, 71, 74, 77, 79, 82, 85, 88, 91, 94, 97, 100, 103, 107, 110, 114, 118, 123, 127, 131, 136, 141, 146, 151, 156, 159, 162, 165, 167, 171, 173, 177, 179, 183, 186, 189, 192, 196, 199, 203, 206, 210, 218, 225, 229, 233, 241, 250, 254 }; static const float f_ctcss[NUM_CTCSS] = { 67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, 88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9, 114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2, 151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8, 177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5, 203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8, 250.3, 254.1 }; static const char * s_ctcss[NUM_CTCSS] = { "67.0", "69.3", "71.9", "74.4", "77.0", "79.7", "82.5", "85.4", "88.5", "91.5", "94.8", "97.4", "100.0", "103.5", "107.2", "110.9", "114.8", "118.8", "123.0", "127.3", "131.8", "136.5", "141.3", "146.2", "151.4", "156.7", "159.8", "162.2", "165.5", "167.9", "171.3", "173.8", "177.3", "179.9", "183.5", "186.2", "189.9", "192.8", "196.6", "199.5", "203.5", "206.5", "210.7", "218.1", "225.7", "229.1", "233.6", "241.8", "250.3", "254.1" }; #define sign(x) (((x)>=0)?1:(-1)) static void process_comment (decode_aprs_t *A, char *pstart, int clen) { static int first_time = 1; static regex_t std_freq_re; /* Frequency in standard format. */ static regex_t std_tone_re; /* Tone in standard format. */ static regex_t std_toff_re; /* Explicitly no tone. */ static regex_t std_dcs_re; /* Digital codes squelch in standard format. */ static regex_t std_offset_re; /* Xmit freq offset in standard format. */ static regex_t std_range_re; /* Range in standard format. */ static regex_t dao_re; /* DAO */ static regex_t alt_re; /* /A= altitude */ static regex_t bad_freq_re; /* Likely frequency, not standard format */ static regex_t bad_tone_re; /* Likely tone, not standard format */ static regex_t base91_tel_re; /* Base 91 compressed telemetry data. */ int e; char emsg[100]; #define MAXMATCH 4 regmatch_t match[MAXMATCH]; char temp[sizeof(A->g_comment)]; int keep_going; /* * No sense in recompiling the patterns and freeing every time. */ if (first_time) { /* * Frequency must be at the at the beginning. * Others can be anywhere in the comment. */ //e = regcomp (&freq_re, "^[0-9A-O][0-9][0-9]\\.[0-9][0-9][0-9 ]MHz( [TCDtcd][0-9][0-9][0-9]| Toff)?( [+-][0-9][0-9][0-9])?", REG_EXTENDED); // Freq optionally preceded by space or /. // Third fractional digit can be space instead. // "MHz" should be exactly that capitalization. // Print warning later it not. e = regcomp (&std_freq_re, "^[/ ]?([0-9A-O][0-9][0-9]\\.[0-9][0-9][0-9 ])([Mm][Hh][Zz])", REG_EXTENDED); if (e) { regerror (e, &std_freq_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } // If no tone, we might gobble up / after any data extension, // We could also have a space but it's not required. // I don't understand the difference between T and C so treat the same for now. // We can also have "off" instead of number to explicitly mean none. e = regcomp (&std_tone_re, "^[/ ]?([TtCc][012][0-9][0-9])", REG_EXTENDED); if (e) { regerror (e, &std_tone_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } e = regcomp (&std_toff_re, "^[/ ]?[TtCc][Oo][Ff][Ff]", REG_EXTENDED); if (e) { regerror (e, &std_toff_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } e = regcomp (&std_dcs_re, "^[/ ]?[Dd]([0-7][0-7][0-7])", REG_EXTENDED); if (e) { regerror (e, &std_dcs_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } e = regcomp (&std_offset_re, "^[/ ]?([+-][0-9][0-9][0-9])", REG_EXTENDED); if (e) { regerror (e, &std_offset_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } e = regcomp (&std_range_re, "^[/ ]?[Rr]([0-9][0-9])([mk])", REG_EXTENDED); if (e) { regerror (e, &std_range_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } e = regcomp (&dao_re, "!([A-Z][0-9 ][0-9 ]|[a-z][!-{ ][!-{ ]|T[0-9 B][0-9 ])!", REG_EXTENDED); if (e) { regerror (e, &dao_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } e = regcomp (&alt_re, "/A=[0-9][0-9][0-9][0-9][0-9][0-9]", REG_EXTENDED); if (e) { regerror (e, &alt_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } e = regcomp (&bad_freq_re, "[0-9][0-9][0-9]\\.[0-9][0-9][0-9]?", REG_EXTENDED); if (e) { regerror (e, &bad_freq_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } e = regcomp (&bad_tone_re, "(^|[^0-9.])([6789][0-9]\\.[0-9]|[12][0-9][0-9]\\.[0-9]|67|77|100|123)($|[^0-9.])", REG_EXTENDED); if (e) { regerror (e, &bad_tone_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } // TODO: Would like to restrict to even length something like this: ([!-{][!-{]){2,7} e = regcomp (&base91_tel_re, "\\|([!-{]{4,14})\\|", REG_EXTENDED); if (e) { regerror (e, &base91_tel_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } first_time = 0; } /* * If clen is >= 0, take only specified number of characters. * Otherwise, take it all. */ if (clen < 0) { clen = strlen(pstart); } /* * Watch out for buffer overflow. * KG6AZZ reports that there is a local digipeater that seems to * malfunction ocassionally. It corrupts the packet, as it is * digipeated, causing the comment to be hundreds of characters long. */ if (clen > (int)(sizeof(A->g_comment) - 1)) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Comment is extremely long, %d characters.\n", clen); dw_printf("Please report this, along with surrounding lines, so we can find the cause.\n"); } clen = sizeof(A->g_comment) - 1; } if (clen > 0) { memcpy (A->g_comment, pstart, (size_t)clen); A->g_comment[clen] = '\0'; } else { A->g_comment[0] = '\0'; } /* * Look for frequency in the standard format at start of comment. * If that fails, try to obtain from object name. */ if (regexec (&std_freq_re, A->g_comment, MAXMATCH, match, 0) == 0) { char sftemp[30]; char smtemp[10]; //dw_printf("matches= %d - %d, %d - %d, %d - %d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo), // (int)(match[1].rm_so), (int)(match[1].rm_eo), // (int)(match[2].rm_so), (int)(match[2].rm_eo) ); substr_se (sftemp, A->g_comment, match[1].rm_so, match[1].rm_eo); substr_se (smtemp, A->g_comment, match[2].rm_so, match[2].rm_eo); switch (sftemp[0]) { case 'A': A->g_freq = 1200 + atof(sftemp+1); break; case 'B': A->g_freq = 2300 + atof(sftemp+1); break; case 'C': A->g_freq = 2400 + atof(sftemp+1); break; case 'D': A->g_freq = 3400 + atof(sftemp+1); break; case 'E': A->g_freq = 5600 + atof(sftemp+1); break; case 'F': A->g_freq = 5700 + atof(sftemp+1); break; case 'G': A->g_freq = 5800 + atof(sftemp+1); break; case 'H': A->g_freq = 10100 + atof(sftemp+1); break; case 'I': A->g_freq = 10200 + atof(sftemp+1); break; case 'J': A->g_freq = 10300 + atof(sftemp+1); break; case 'K': A->g_freq = 10400 + atof(sftemp+1); break; case 'L': A->g_freq = 10500 + atof(sftemp+1); break; case 'M': A->g_freq = 24000 + atof(sftemp+1); break; case 'N': A->g_freq = 24100 + atof(sftemp+1); break; case 'O': A->g_freq = 24200 + atof(sftemp+1); break; default: A->g_freq = atof(sftemp); break; } if (strncmp(smtemp, "MHz", 3) != 0) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Warning: \"%s\" has non-standard capitalization and might not be recognized by some systems.\n", smtemp); dw_printf("For best compatibility, it should be exactly like this: \"MHz\" (upper,upper,lower case)\n"); } } strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)); } else if (strlen(A->g_name) > 0) { // Try to extract sensible number from object/item name. double x = atof (A->g_name); if ((x >= 144 && x <= 148) || (x >= 222 && x <= 225) || (x >= 420 && x <= 450) || (x >= 902 && x <= 928)) { A->g_freq = x; } } /* * Next, look for tone, DCS code, and range. * Examples always have them in same order but it's not clear * whether any order is allowed after possible frequency. * * TODO: Convert integer tone to original value for display. * TODO: samples in zfreq-test3.txt */ keep_going = 1; while (keep_going) { if (regexec (&std_tone_re, A->g_comment, MAXMATCH, match, 0) == 0) { char sttemp[10]; /* includes leading letter */ int f; int i; substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo); // Try to convert from integer to proper value. f = atoi(sttemp+1); for (i = 0; i < NUM_CTCSS; i++) { if (f == i_ctcss[i]) { A->g_tone = f_ctcss[i]; break; } } if (A->g_tone == G_UNKNOWN) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Bad CTCSS/PL specification: \"%s\"\n", sttemp); dw_printf("Integer does not correspond to standard tone.\n"); } } strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)); } else if (regexec (&std_toff_re, A->g_comment, MAXMATCH, match, 0) == 0) { dw_printf ("NO tone\n"); A->g_tone = 0; strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)); } else if (regexec (&std_dcs_re, A->g_comment, MAXMATCH, match, 0) == 0) { char sttemp[10]; /* three octal digits */ substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo); A->g_dcs = strtoul (sttemp, NULL, 8); strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } else if (regexec (&std_offset_re, A->g_comment, MAXMATCH, match, 0) == 0) { char sttemp[10]; /* includes leading sign */ substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo); A->g_offset = 10 * atoi(sttemp); strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } else if (regexec (&std_range_re, A->g_comment, MAXMATCH, match, 0) == 0) { char sttemp[10]; /* should be two digits */ char sutemp[10]; /* m for miles or k for km */ substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo); substr_se (sutemp, A->g_comment, match[2].rm_so, match[2].rm_eo); if (strcmp(sutemp, "m") == 0) { A->g_range = atoi(sttemp); } else { A->g_range = DW_KM_TO_MILES(atoi(sttemp)); } strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } else { keep_going = 0; } } /* * Telemetry data, in base 91 compressed format appears as 2 to 7 pairs * of base 91 digits, surrounded by | at start and end. */ if (regexec (&base91_tel_re, A->g_comment, MAXMATCH, match, 0) == 0) { char tdata[30]; /* Should be 4 to 14 characters. */ //dw_printf("compressed telemetry start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); substr_se (tdata, A->g_comment, match[1].rm_so, match[1].rm_eo); //dw_printf("compressed telemetry data = \"%s\"\n", tdata); telemetry_data_base91 (A->g_src, tdata, A->g_telemetry, sizeof(A->g_telemetry)); strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } /* * Latitude and Longitude in the form DD MM.HH has a resolution of about 60 feet. * The !DAO! option allows another digit or almost two for greater resolution. * * This would not make sense to use this with a compressed location which * already has much greater resolution. * * It surprized me to see this in a MIC-E message. * MIC-E has resolution of .01 minute so it would make sense to have it as an option. */ if (regexec (&dao_re, A->g_comment, MAXMATCH, match, 0) == 0) { int d = A->g_comment[match[0].rm_so+1]; int a = A->g_comment[match[0].rm_so+2]; int o = A->g_comment[match[0].rm_so+3]; //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); /* * Private extension for APRStt */ if (d == 'T') { if (a == ' ' && o == ' ') { snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt corral location"); } else if (isdigit(a) && o == ' ') { snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c of 10", a); } else if (isdigit(a) && isdigit(o)) { snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c%c of 100", a, o); } else if (a == 'B' && isdigit(o)) { snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c%c...", a, o); } } else if (isupper(d)) { /* * This adds one extra digit to each. Dao adds extra digit like: * * Lat: DD MM.HHa * Lon: DDD HH.HHo */ if (isdigit(a)) { A->g_lat += (a - '0') / 60000.0 * sign(A->g_lat); } if (isdigit(o)) { A->g_lon += (o - '0') / 60000.0 * sign(A->g_lon); } } else if (islower(d)) { /* * This adds almost two extra digits to each like this: * * Lat: DD MM.HHxx * Lon: DDD HH.HHxx * * The original character range '!' to '{' is first converted * to an integer in range of 0 to 90. It is multiplied by 1.1 * to stretch the numeric range to be 0 to 99. */ /* * Here is an interesting case. * * W8SAT-1>T2UV0P:`qC<0x1f>l!Xu\'"69}WMNI EDS Response Unit #1|+/%0'n|!w:X!|3 * * Let's break that down into pieces. * * W8SAT-1>T2UV0P:`qC<0x1f>l!Xu\'"69} MIC-E format * N 42 56.0000, W 085 39.0300, * 0 MPH, course 160, alt 709 ft * WMNI EDS Response Unit #1 comment * |+/%0'n| base 91 telemetry * !w:X! DAO * |3 Tiny Track 3 * * Comment earlier points out that MIC-E format has resolution of 0.01 minute, * same as non-compressed format, so the DAO does work out, after thinking * about it for a while. */ /* * The spec appears to be wrong. It says '}' is the maximum value when it should be '{'. */ if (isdigit91(a)) { A->g_lat += (a - B91_MIN) * 1.1 / 600000.0 * sign(A->g_lat); } if (isdigit91(o)) { A->g_lon += (o - B91_MIN) * 1.1 / 600000.0 * sign(A->g_lon); } } strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } /* * Altitude in feet. /A=123456 */ if (regexec (&alt_re, A->g_comment, MAXMATCH, match, 0) == 0) { //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); A->g_comment[match[0].rm_eo] = '\0'; A->g_altitude_ft = atoi(A->g_comment + match[0].rm_so + 3); strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } //dw_printf("Final comment='%s'\n", A->g_comment); /* * Finally look for something that looks like frequency or CTCSS tone * in the remaining comment. Point this out and suggest the * standardized format. * Don't complain if we have already found a valid value. */ if (A->g_freq == G_UNKNOWN && regexec (&bad_freq_re, A->g_comment, MAXMATCH, match, 0) == 0) { char bad[30]; char good[30]; double x; substr_se (bad, A->g_comment, match[0].rm_so, match[0].rm_eo); x = atof(bad); if ((x >= 144 && x <= 148) || (x >= 222 && x <= 225) || (x >= 420 && x <= 450) || (x >= 902 && x <= 928)) { if ( ! A->g_quiet) { snprintf (good, sizeof(good), "%07.3fMHz", x); text_color_set(DW_COLOR_ERROR); dw_printf("\"%s\" in comment looks like a frequency in non-standard format.\n", bad); dw_printf("For most systems to recognize it, use exactly this form \"%s\" at beginning of comment.\n", good); } if (A->g_freq == G_UNKNOWN) { A->g_freq = x; } } } if (A->g_tone == G_UNKNOWN && regexec (&bad_tone_re, A->g_comment, MAXMATCH, match, 0) == 0) { char bad1[30]; /* original 99.9 or 999.9 format or one of 67 77 100 123 */ char bad2[30]; /* 99.9 or 999.9 format. ".0" appended for special cases. */ char good[30]; int i; substr_se (bad1, A->g_comment, match[2].rm_so, match[2].rm_eo); strlcpy (bad2, bad1, sizeof(bad2)); if (strcmp(bad2, "67") == 0 || strcmp(bad2, "77") == 0 || strcmp(bad2, "100") == 0 || strcmp(bad2, "123") == 0) { strlcat (bad2, ".0", sizeof(bad2)); } // TODO: Why wasn't freq/PL recognized here? // Should we recognize some cases of single decimal place as frequency? //DECODED[194] N8VIM audio level = 27 [NONE] //[0] N8VIM>BEACON,WIDE2-2:!4240.85N/07133.99W_PHG72604/ Pepperell, MA-> WX. 442.9+ PL100<0x0d> //Didn't find wind direction in form c999. //Didn't find wind speed in form s999. //Didn't find wind gust in form g999. //Didn't find temperature in form t999. //Weather Report, WEATHER Station (blue) //N 42 40.8500, W 071 33.9900 //, "PHG72604/ Pepperell, MA-> WX. 442.9+ PL100" for (i = 0; i < NUM_CTCSS; i++) { if (strcmp (s_ctcss[i], bad2) == 0) { if ( ! A->g_quiet) { snprintf (good, sizeof(good), "T%03d", i_ctcss[i]); text_color_set(DW_COLOR_ERROR); dw_printf("\"%s\" in comment looks like it might be a CTCSS tone in non-standard format.\n", bad1); dw_printf("For most systems to recognize it, use exactly this form \"%s\" at near beginning of comment, after any frequency.\n", good); } if (A->g_tone == G_UNKNOWN) { A->g_tone = atof(bad2); } break; } } } if ((A->g_offset == 6000 || A->g_offset == -6000) && A->g_freq >= 144 && A->g_freq <= 148) { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("A transmit offset of 6 MHz on the 2 meter band doesn't seem right.\n"); dw_printf("Each unit is 10 kHz so you should probably be using \"-060\" or \"+060\"\n"); } } /* * TODO: samples in zfreq-test4.txt */ } /* end process_comment */ /*------------------------------------------------------------------ * * Function: main * * Purpose: Main program for standalone test program. * * Inputs: stdin for raw data to decode. * This is in the usual display format either from * a TNC, findu.com, aprs.fi, etc. e.g. * * N1EDF-9>T2QT8Y,W1CLA-1,WIDE1*,WIDE2-2,00000:`bSbl!Mv/`"4%}_ <0x0d> * * WB2OSZ-1>APN383,qAR,N1EDU-2:!4237.14NS07120.83W#PHG7130Chelmsford, MA * * New for 1.5: * * Also allow hexadecimal bytes for raw AX.25 or KISS. e.g. * * 00 82 a0 ae ae 62 60 e0 82 96 68 84 40 40 60 9c 68 b0 ae 86 40 e0 40 ae 92 88 8a 64 63 03 f0 3e 45 4d 36 34 6e 65 2f 23 20 45 63 68 6f 6c 69 6e 6b 20 31 34 35 2e 33 31 30 2f 31 30 30 68 7a 20 54 6f 6e 65 * * If it begins with 00 or C0 (which would be impossible for AX.25 address) process as KISS. * Also print these formats. * * Outputs: stdout * * Description: Compile like this to make a standalone test program. * * gcc -o decode_aprs -DDECAMAIN decode_aprs.c ax25_pad.c ... * * ./decode_aprs < decode_aprs.txt * * aprs.fi precedes raw data with a time stamp which you * would need to remove first. * * cut -c26-999 tmp/kj4etp-9.txt | decode_aprs.exe * * * Restriction: MIC-E message type can be problematic because it * it can use unprintable characters in the information field. * * Dire Wolf and aprs.fi print it in hexadecimal. Example: * * KB1KTR-8>TR3U6T,KB1KTR-9*,WB2OSZ-1*,WIDE2*,qAR,W1XM:`c1<0x1f>l!t>/>"4^} * ^^^^^^ * |||||| * What does findu.com do in this case? * * ax25_from_text recognizes this representation so it can be used * to decode raw data later. * * TODO: To make it more useful, * - Remove any leading timestamp. * - Remove any "qA*" and following from the path. * - Handle non-APRS frames properly. * *------------------------------------------------------------------*/ #if DECAMAIN #include "kiss_frame.h" /* Stub for stand-alone decoder. */ void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab, char symbol, float alt, float course, float speed, char *comment) { return; } // TODO: hex_dump is currently in server.c and we don't want to drag that in. // Someday put it in a more reasonable place, with other general utilities, and remove the private copy here. static void hex_dump (unsigned char *p, int len) { int n, i, offset; offset = 0; while (len > 0) { n = len < 16 ? len : 16; dw_printf (" %03x: ", offset); for (i=0; ichcp // Active code page: 437 //Restore on exit? oldcp = GetConsoleOutputCP(); SetConsoleOutputCP(CP_UTF8); #else /* * Default on Raspian & Ubuntu Linux is fine. Don't know about others. * * Should we look at LANG environment variable and issue a warning * if it doesn't look something like en_US.UTF-8 ? */ #endif if (argc >= 2) { if (freopen (argv[1], "r", stdin) == NULL) { fprintf(stderr, "Can't open %s for read.\n", argv[1]); exit(1); } } text_color_init(1); text_color_set(DW_COLOR_INFO); while (fgets(stuff, sizeof(stuff), stdin) != NULL) { p = stuff + strlen(stuff) - 1; while (p >= stuff && (*p == '\r' || *p == '\n')) { *p-- = '\0'; } if (strlen(stuff) == 0 || stuff[0] == '#') { /* comment or blank line */ text_color_set(DW_COLOR_INFO); dw_printf("%s\n", stuff); continue; } else { /* Try to process it. */ text_color_set(DW_COLOR_REC); dw_printf("\n%s\n", stuff); // Do we have monitor format, KISS, or AX.25 frame? p = stuff; while (isspace(*p)) p++; if (ISHEX2(p)) { // Collect a bunch of hexadecimal numbers. num_bytes = 0; while (ISHEX2(p) && num_bytes < MAXBYTES) { bytes[num_bytes++] = strtoul(p, NULL, 16); p += 2; while (isspace(*p)) p++; } if (num_bytes == 0 || *p != '\0') { text_color_set(DW_COLOR_ERROR); dw_printf("Parse error around column %d.\n", (int)(long)(p - stuff) + 1); dw_printf("Was expecting only space separated 2 digit hexadecimal numbers.\n\n"); continue; // next line } // If we have 0xC0 at start, remove it and expect same at end. if (bytes[0] == FEND) { if (num_bytes < 2 || bytes[1] != 0) { text_color_set(DW_COLOR_ERROR); dw_printf("Was expecting to find 00 after the initial C0.\n"); continue; } if (bytes[num_bytes-1] == FEND) { text_color_set(DW_COLOR_INFO); dw_printf("Removing KISS FEND characters at beginning and end.\n"); int n; for (n = 0; n < num_bytes-1; n++) { bytes[n] = bytes[n+1]; } num_bytes -= 2; } else { text_color_set(DW_COLOR_INFO); dw_printf("Removing KISS FEND character at beginning. Was expecting another at end.\n"); int n; for (n = 0; n < num_bytes-1; n++) { bytes[n] = bytes[n+1]; } num_bytes -= 1; } } if (bytes[0] == 0) { // Treat as KISS. Undo any KISS encoding. unsigned char kiss_frame[MAXBYTES]; int kiss_len = num_bytes; memcpy (kiss_frame, bytes, num_bytes); text_color_set(DW_COLOR_DEBUG); dw_printf ("--- KISS frame ---\n"); hex_dump (kiss_frame, kiss_len); // Put FEND at end to keep kiss_unwrap happy. // Having one at the begining is optional. kiss_frame[kiss_len++] = FEND; // In the more general case, we would need to include // the command byte because it could be escaped. // Here we know it is 0, so we take a short cut and // remove it before, rather than after, the conversion. num_bytes = kiss_unwrap (kiss_frame + 1, kiss_len - 1, bytes); } // Treat as AX.25. alevel_t alevel; memset (&alevel, 0, sizeof(alevel)); pp = ax25_from_frame(bytes, num_bytes, alevel); if (pp != NULL) { char addrs[120]; unsigned char *pinfo; int info_len; decode_aprs_t A; text_color_set(DW_COLOR_DEBUG); dw_printf ("--- AX.25 frame ---\n"); ax25_hex_dump (pp); dw_printf ("-------------------\n"); ax25_format_addrs (pp, addrs); text_color_set(DW_COLOR_DECODED); dw_printf ("%s", addrs); info_len = ax25_get_info (pp, &pinfo); ax25_safe_print ((char *)pinfo, info_len, 1); // Display non-ASCII to hexadecimal. dw_printf ("\n"); decode_aprs (&A, pp, 0); // Extract information into structure. decode_aprs_print (&A); // Now print it in human readable format. (void)ax25_check_addresses(pp); // Errors for invalid addresses. ax25_delete (pp); } else { text_color_set(DW_COLOR_ERROR); dw_printf("Could not construct AX.25 frame from bytes supplied!\n\n"); } } else { // Normal monitoring format. pp = ax25_from_text(stuff, 1); if (pp != NULL) { decode_aprs_t A; decode_aprs (&A, pp, 0); // Extract information into structure. decode_aprs_print (&A); // Now print it in human readable format. // This seems to be redundant because we used strict option // when parsing the monitoring format text. //(void)ax25_check_addresses(pp); // Errors for invalid addresses. // Future? Add -d option to include hex dump and maybe KISS? ax25_delete (pp); } else { text_color_set(DW_COLOR_ERROR); dw_printf("ERROR - Could not parse monitoring format input!\n\n"); } } } } return (0); } #endif /* DECAMAIN */ /* end decode_aprs.c */ direwolf-1.5+dfsg/decode_aprs.h000066400000000000000000000103071347750676600165600ustar00rootroot00000000000000 /* decode_aprs.h */ #ifndef DECODE_APRS_H #define DECODE_APRS_H 1 #ifndef G_UNKNOWN #include "latlong.h" #endif #ifndef AX25_MAX_ADDR_LEN #include "ax25_pad.h" #endif #ifndef APRSTT_LOC_DESC_LEN #include "aprs_tt.h" #endif typedef struct decode_aprs_s { int g_quiet; /* Suppress error messages when decoding. */ char g_src[AX25_MAX_ADDR_LEN]; char g_msg_type[60]; /* APRS data type. Telemetry descriptions get pretty long. */ /* Putting msg in the name was a poor choice because */ /* "message" has a specific meaning. Rename it someday. */ char g_symbol_table; /* The Symbol Table Identifier character selects one */ /* of the two Symbol Tables, or it may be used as */ /* single-character (alpha or numeric) overlay, as follows: */ /* / Primary Symbol Table (mostly stations) */ /* \ Alternate Symbol Table (mostly Objects) */ /* 0-9 Numeric overlay. Symbol from Alternate Symbol */ /* Table (uncompressed lat/long data format) */ /* a-j Numeric overlay. Symbol from Alternate */ /* Symbol Table (compressed lat/long data */ /* format only). i.e. a-j maps to 0-9 */ /* A-Z Alpha overlay. Symbol from Alternate Symbol Table */ char g_symbol_code; /* Where the Symbol Table Identifier is 0-9 or A-Z (or a-j */ /* with compressed position data only), the symbol comes from */ /* the Alternate Symbol Table, and is overlaid with the */ /* identifier (as a single digit or a capital letter). */ char g_aprstt_loc[APRSTT_LOC_DESC_LEN]; /* APRStt location from !DAO! */ double g_lat, g_lon; /* Location, degrees. Negative for South or West. */ /* Set to G_UNKNOWN if missing or error. */ char g_maidenhead[12]; /* 4 or 6 (or 8?) character maidenhead locator. */ char g_name[12]; /* Object or item name. Max. 9 characters. */ char g_addressee[12]; /* Addressee for a "message." Max. 9 characters. */ /* Also for Directed Station Query which is a */ /* special case of message. */ enum message_subtype_e { message_subtype_invalid = 0, message_subtype_message, message_subtype_ack, message_subtype_rej, message_subtype_telem_parm, message_subtype_telem_unit, message_subtype_telem_eqns, message_subtype_telem_bits, message_subtype_directed_query } g_message_subtype; /* Various cases of the overloaded "message." */ char g_message_number[8]; /* Message number. Should be 1 - 5 characters if used. */ float g_speed_mph; /* Speed in MPH. */ float g_course; /* 0 = North, 90 = East, etc. */ int g_power; /* Transmitter power in watts. */ int g_height; /* Antenna height above average terrain, feet. */ int g_gain; /* Antenna gain in dB. */ char g_directivity[12]; /* Direction of max signal strength */ float g_range; /* Precomputed radio range in miles. */ float g_altitude_ft; /* Feet above median sea level. */ char g_mfr[80]; /* Manufacturer or application. */ char g_mic_e_status[32]; /* MIC-E message. */ double g_freq; /* Frequency, MHz */ float g_tone; /* CTCSS tone, Hz, one fractional digit */ int g_dcs; /* Digital coded squelch, print as 3 octal digits. */ int g_offset; /* Transmit offset, kHz */ char g_query_type[12]; /* General Query: APRS, IGATE, WX, ... */ /* Addressee is NOT set. */ /* Directed Station Query: exactly 5 characters. */ /* APRSD, APRST, PING?, ... */ /* Addressee is set. */ double g_footprint_lat; /* A general query may contain a foot print. */ double g_footprint_lon; /* Set all to G_UNKNOWN if not used. */ float g_footprint_radius; /* Radius in miles. */ char g_query_callsign[12]; /* Directed query may contain callsign. */ /* e.g. tell me all objects from that callsign. */ char g_weather[500]; /* Weather. Can get quite long. Rethink max size. */ char g_telemetry[256]; /* Telemetry data. Rethink max size. */ char g_comment[256]; /* Comment. */ } decode_aprs_t; extern void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet); extern void decode_aprs_print (decode_aprs_t *A); #endifdirewolf-1.5+dfsg/dedupe.c000066400000000000000000000156301347750676600155550ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Name: dedupe.c * * Purpose: Avoid transmitting duplicate packets which are too * close together. * * * Description: We want to avoid digipeating duplicate packets to * to help reduce radio channel congestion with * redundant information. * Duplicate packets can occur in several ways: * * (1) A digipeated packet can loop between 2 or more * digipeaters. For example: * * W1ABC>APRS,WIDE3-3 * W1ABC>APRS,mycall*,WIDE3-2 * W1ABC>APRS,mycall,RPT1*,WIDE3-1 * W1ABC>APRS,mycall,RPT1,mycall* * * (2) We could hear our own original transmission * repeated by someone else. Example: * * mycall>APRS,WIDE3-3 * mycall>APRS,RPT1*,WIDE3-2 * mycall>APRS,RPT1*,mycall*,WIDE3-1 * * (3) We could hear the same packet from multiple * digipeaters (with or without the original). * * W1ABC>APRS,WIDE3-2 * W1ABC>APRS,RPT1*,WIDE3-2 * W1ABC>APRS,RPT2*,WIDE3-2 * W1ABC>APRS,RPT3*,WIDE3-2 * * (4) Someone could be sending the same thing over and * over with very little delay in between. * * W1ABC>APRS,WIDE3-3 * W1ABC>APRS,WIDE3-3 * W1ABC>APRS,WIDE3-3 * * We can catch the first two by looking for 'mycall' in * the source or digipeater fields. * * The other two cases require us to keep a record of what * we transmitted recently and test for duplicates that * should be dropped. * * Once we have the solution to catch cases (3) and (4) * there is no reason for the special case of looking for * mycall. The same technique catches all four situations. * * For detecting duplicates, we need to look * + source station * + destination * + information field * but NOT the changing list of digipeaters. * * Typically, only a checksum is kept to reduce memory * requirements and amount of compution for comparisons. * There is a very very small probability that two unrelated * packets will result in the same checksum, and the * undesired dropping of the packet. * * References: Original APRS specification: * * TBD... * * "The New n-N Paradigm" * * http://www.aprs.org/fix14439.html * *------------------------------------------------------------------*/ #define DEDUPE_C #include "direwolf.h" #include #include #include #include #include #include "ax25_pad.h" #include "dedupe.h" #include "fcs_calc.h" #include "textcolor.h" #ifndef DIGITEST #include "igate.h" #endif /*------------------------------------------------------------------------------ * * Name: dedupe_init * * Purpose: Initialize the duplicate detection subsystem. * * Input: ttl - Number of seconds to retain information * about recent transmissions. * * * Returns: None * * Description: This should be called at application startup. * * *------------------------------------------------------------------------------*/ static int history_time = 30; /* Number of seconds to keep information */ /* about recent transmissions. */ #define HISTORY_MAX 25 /* Maximum number of transmission */ /* records to keep. If we run out of */ /* room the oldest ones are overwritten */ /* before they expire. */ static int insert_next; /* Index, in array below, where next */ /* item should be stored. */ static struct { time_t time_stamp; /* When the packet was transmitted. */ unsigned short checksum; /* Some sort of checksum for the */ /* source, destination, and information. */ /* is is not used anywhere else. */ short xmit_channel; /* Radio channel number. */ } history[HISTORY_MAX]; void dedupe_init (int ttl) { history_time = ttl; insert_next = 0; memset (history, 0, sizeof(history)); } /*------------------------------------------------------------------------------ * * Name: dedupe_remember * * Purpose: Save information about a packet being transmitted so we * can detect, and avoid, duplicates later. * * Input: pp - Pointer to packet object. * * chan - Radio channel for transmission. * * Returns: None * * Rambling: At one time, my thinking is that we want to keep track of * ALL transmitted packets regardless of origin or type. * * + my beacons * + anything from a connected application * + anything digipeated * * The easiest way to catch all cases is to call dedup_remember() * from inside tq_append(). * * But I don't think that is the right approach. * When acting as a KISS TNC, we should just shovel everything * through and not question what the application is doing. * If the connected application has a digipeating function, * it's responsible for those decisions. * * My current thinking is that dedupe_remember() should be * called BEFORE tq_append() in the digipeater case. * * We should also capture our own beacon transmissions. * *------------------------------------------------------------------------------*/ void dedupe_remember (packet_t pp, int chan) { history[insert_next].time_stamp = time(NULL); history[insert_next].checksum = ax25_dedupe_crc(pp); history[insert_next].xmit_channel = chan; insert_next++; if (insert_next >= HISTORY_MAX) { insert_next = 0; } /* If we send something by digipeater, we don't */ /* want to do it again if it comes from APRS-IS. */ /* Not sure about the other way around. */ #ifndef DIGITEST ig_to_tx_remember (pp, chan, 1); #endif } /*------------------------------------------------------------------------------ * * Name: dedupe_check * * Purpose: Check whether this is a duplicate of another sent recently. * * Input: pp - Pointer to packet object. * * chan - Radio channel for transmission. * * Returns: True if it is a duplicate. * * *------------------------------------------------------------------------------*/ int dedupe_check (packet_t pp, int chan) { unsigned short crc = ax25_dedupe_crc(pp); time_t now = time(NULL); int j; for (j=0; j= now - history_time && history[j].checksum == crc && history[j].xmit_channel == chan) { return 1; } } return 0; } /* end dedupe.c */ direwolf-1.5+dfsg/dedupe.h000066400000000000000000000002151347750676600155530ustar00rootroot00000000000000 void dedupe_init (int ttl); void dedupe_remember (packet_t pp, int chan); int dedupe_check (packet_t pp, int chan); /* end dedupe.h */ direwolf-1.5+dfsg/demod.c000066400000000000000000001044651347750676600154040ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: demod.c * * Purpose: Common entry point for multiple types of demodulators. * * Input: Audio samples from either a file or the "sound card." * * Outputs: Calls hdlc_rec_bit() for each bit demodulated. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #include "audio.h" #include "demod.h" #include "tune.h" #include "fsk_demod_state.h" #include "fsk_gen_filter.h" #include "fsk_fast_filter.h" #include "hdlc_rec.h" #include "textcolor.h" #include "demod_9600.h" #include "demod_afsk.h" #include "demod_psk.h" // Properties of the radio channels. static struct audio_s *save_audio_config_p; // TODO: temp experiment. static int upsample = 2; // temp experiment. static int zerostuff = 1; // temp experiment. // Current state of all the decoders. static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS]; static int sample_sum[MAX_CHANS][MAX_SUBCHANS]; static int sample_count[MAX_CHANS][MAX_SUBCHANS]; /*------------------------------------------------------------------ * * Name: demod_init * * Purpose: Initialize the demodulator(s) used for reception. * * Inputs: pa - Pointer to audio_s structure with * various parameters for the modem(s). * * Returns: 0 for success, -1 for failure. * * * Bugs: This doesn't do much error checking so don't give it * anything crazy. * *----------------------------------------------------------------*/ int demod_init (struct audio_s *pa) { //int j; int chan; /* Loop index over number of radio channels. */ char profile; /* * Save audio configuration for later use. */ save_audio_config_p = pa; for (chan = 0; chan < MAX_CHANS; chan++) { if (save_audio_config_p->achan[chan].valid) { char *p; char just_letters[16]; int num_letters; int have_plus; /* * These are derived from config file parameters. * * num_subchan is number of demodulators. * This can be increased by: * Multiple frequencies. * Multiple letters (not sure if I will continue this). * New interleaved decoders. * * num_slicers is set to max by the "+" option. */ save_audio_config_p->achan[chan].num_subchan = 1; save_audio_config_p->achan[chan].num_slicers = 1; switch (save_audio_config_p->achan[chan].modem_type) { case MODEM_OFF: break; case MODEM_AFSK: /* * Tear apart the profile and put it back together in a normalized form: * - At least one letter, supply suitable default if necessary. * - Upper case only. * - Any plus will be at the end. */ num_letters = 0; just_letters[num_letters] = '\0'; have_plus = 0; for (p = save_audio_config_p->achan[chan].profiles; *p != '\0'; p++) { if (islower(*p)) { just_letters[num_letters] = toupper(*p); num_letters++; just_letters[num_letters] = '\0'; } else if (isupper(*p)) { just_letters[num_letters] = *p; num_letters++; just_letters[num_letters] = '\0'; } else if (*p == '+') { have_plus = 1; if (p[1] != '\0') { text_color_set(DW_COLOR_ERROR); dw_printf ("Channel %d: + option must appear at end of demodulator types \"%s\" \n", chan, save_audio_config_p->achan[chan].profiles); } } else if (*p == '-') { have_plus = -1; if (p[1] != '\0') { text_color_set(DW_COLOR_ERROR); dw_printf ("Channel %d: - option must appear at end of demodulator types \"%s\" \n", chan, save_audio_config_p->achan[chan].profiles); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Channel %d: Demodulator types \"%s\" can contain only letters and + - characters.\n", chan, save_audio_config_p->achan[chan].profiles); } } assert (num_letters == (int)(strlen(just_letters))); /* * Pick a good default demodulator if none specified. */ if (num_letters == 0) { if (save_audio_config_p->achan[chan].baud < 600) { /* This has been optimized for 300 baud. */ strlcpy (just_letters, "D", sizeof(just_letters)); } else { #if __arm__ /* We probably don't have a lot of CPU power available. */ /* Previously we would use F if possible otherwise fall back to A. */ /* In version 1.2, new default is E+ /3. */ strlcpy (just_letters, "E", sizeof(just_letters)); // version 1.2 now E. if (have_plus != -1) have_plus = 1; // Add as default for version 1.2 // If not explicitly turned off. if (save_audio_config_p->achan[chan].decimate == 0) { if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) { save_audio_config_p->achan[chan].decimate = 3; } } #else strlcpy (just_letters, "E", sizeof(just_letters)); // version 1.2 changed C to E. if (have_plus != -1) have_plus = 1; // Add as default for version 1.2 // If not explicitly turned off. #endif } num_letters = 1; } assert (num_letters == (int)(strlen(just_letters))); /* * Put it back together again. */ /* At this point, have_plus can have 3 values: */ /* 1 = turned on, either explicitly or by applied default */ /* -1 = explicitly turned off. change to 0 here so it is false. */ /* 0 = off by default. */ if (have_plus == -1) have_plus = 0; strlcpy (save_audio_config_p->achan[chan].profiles, just_letters, sizeof(save_audio_config_p->achan[chan].profiles)); assert (strlen(save_audio_config_p->achan[chan].profiles) >= 1); if (have_plus) { strlcat (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles)); } /* These can be increased later for the multi-frequency case. */ save_audio_config_p->achan[chan].num_subchan = num_letters; save_audio_config_p->achan[chan].num_slicers = 1; /* * Some error checking - Can use only one of these: * * - Multiple letters. * - New + multi-slicer. * - Multiple frequencies. */ if (have_plus && save_audio_config_p->achan[chan].num_freq > 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Channel %d: Demodulator + option can't be combined with multiple frequencies.\n", chan); save_audio_config_p->achan[chan].num_subchan = 1; // Will be set higher later. save_audio_config_p->achan[chan].num_freq = 1; } if (num_letters > 1 && save_audio_config_p->achan[chan].num_freq > 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Channel %d: Multiple demodulator types can't be combined with multiple frequencies.\n", chan); save_audio_config_p->achan[chan].profiles[1] = '\0'; num_letters = 1; } if (save_audio_config_p->achan[chan].decimate == 0) { save_audio_config_p->achan[chan].decimate = 1; if (strchr (just_letters, 'D') != NULL && save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) { save_audio_config_p->achan[chan].decimate = 3; } } text_color_set(DW_COLOR_DEBUG); dw_printf ("Channel %d: %d baud, AFSK %d & %d Hz, %s, %d sample rate", chan, save_audio_config_p->achan[chan].baud, save_audio_config_p->achan[chan].mark_freq, save_audio_config_p->achan[chan].space_freq, save_audio_config_p->achan[chan].profiles, save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec); if (save_audio_config_p->achan[chan].decimate != 1) dw_printf (" / %d", save_audio_config_p->achan[chan].decimate); if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); /* * Initialize the demodulator(s). * * We have 3 cases to consider. */ // TODO1.3: revisit this logic now that it is less restrictive. if (num_letters > 1) { int d; /* * Multiple letters, usually for 1200 baud. * Each one corresponds to a demodulator and subchannel. * * An interesting experiment but probably not too useful. * Can't have multiple frequency pairs. * In version 1.3 this can be combined with the + option. */ save_audio_config_p->achan[chan].num_subchan = num_letters; /* * Quick hack with special case for another experiment. * Do this in a more general way if it turns out to be useful. */ save_audio_config_p->achan[chan].interleave = 1; if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EE") == 0) { save_audio_config_p->achan[chan].interleave = 2; save_audio_config_p->achan[chan].decimate = 1; } else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEE") == 0) { save_audio_config_p->achan[chan].interleave = 3; save_audio_config_p->achan[chan].decimate = 1; } else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEEE") == 0) { save_audio_config_p->achan[chan].interleave = 4; save_audio_config_p->achan[chan].decimate = 1; } else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEEEE") == 0) { save_audio_config_p->achan[chan].interleave = 5; save_audio_config_p->achan[chan].decimate = 1; } else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GG") == 0) { save_audio_config_p->achan[chan].interleave = 2; save_audio_config_p->achan[chan].decimate = 1; } else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGG") == 0) { save_audio_config_p->achan[chan].interleave = 3; save_audio_config_p->achan[chan].decimate = 1; } else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGG+") == 0) { save_audio_config_p->achan[chan].interleave = 3; save_audio_config_p->achan[chan].decimate = 1; } else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGGG") == 0) { save_audio_config_p->achan[chan].interleave = 4; save_audio_config_p->achan[chan].decimate = 1; } else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGGGG") == 0) { save_audio_config_p->achan[chan].interleave = 5; save_audio_config_p->achan[chan].decimate = 1; } if (save_audio_config_p->achan[chan].num_subchan != num_letters) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_subchan(%d) != strlen(\"%s\")\n", __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_subchan, save_audio_config_p->achan[chan].profiles); } if (save_audio_config_p->achan[chan].num_freq != 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n", __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq); } for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) { int mark, space; assert (d >= 0 && d < MAX_SUBCHANS); struct demodulator_state_s *D; D = &demodulator_state[chan][d]; profile = save_audio_config_p->achan[chan].profiles[d]; mark = save_audio_config_p->achan[chan].mark_freq; space = save_audio_config_p->achan[chan].space_freq; if (save_audio_config_p->achan[chan].num_subchan != 1) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %d.%d: %c %d & %d\n", chan, d, profile, mark, space); } demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / (save_audio_config_p->achan[chan].decimate * save_audio_config_p->achan[chan].interleave), save_audio_config_p->achan[chan].baud, mark, space, profile, D); if (have_plus) { /* I'm not happy about putting this hack here. */ /* should pass in as a parameter rather than adding on later. */ save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; D->num_slicers = MAX_SLICERS; } /* For signal level reporting, we want a longer term view. */ // TODO: Should probably move this into the init functions. D->quick_attack = D->agc_fast_attack * 0.2f; D->sluggish_decay = D->agc_slow_decay * 0.2f; } } else if (have_plus) { /* * PLUS - which (formerly) implies we have only one letter and one frequency pair. * * One demodulator feeds multiple slicers, each a subchannel. */ if (num_letters != 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, strlen(\"%s\") != 1\n", __FILE__, __LINE__, chan, just_letters); } if (save_audio_config_p->achan[chan].num_freq != 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n", __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq); } if (save_audio_config_p->achan[chan].num_freq != save_audio_config_p->achan[chan].num_subchan) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != num_subchan(%d)\n", __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq, save_audio_config_p->achan[chan].num_subchan); } struct demodulator_state_s *D; D = &demodulator_state[chan][0]; /* I'm not happy about putting this hack here. */ /* This belongs in demod_afsk_init but it doesn't have access to the audio config. */ save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, save_audio_config_p->achan[chan].baud, save_audio_config_p->achan[chan].mark_freq, save_audio_config_p->achan[chan].space_freq, save_audio_config_p->achan[chan].profiles[0], D); if (have_plus) { /* I'm not happy about putting this hack here. */ /* should pass in as a parameter rather than adding on later. */ save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; D->num_slicers = MAX_SLICERS; } /* For signal level reporting, we want a longer term view. */ D->quick_attack = D->agc_fast_attack * 0.2f; D->sluggish_decay = D->agc_slow_decay * 0.2f; } else { int d; /* * One letter. * Can be combined with multiple frequencies. */ if (num_letters != 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, strlen(\"%s\") != 1\n", __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].profiles); } save_audio_config_p->achan[chan].num_subchan = save_audio_config_p->achan[chan].num_freq; for (d = 0; d < save_audio_config_p->achan[chan].num_freq; d++) { int mark, space, k; assert (d >= 0 && d < MAX_SUBCHANS); struct demodulator_state_s *D; D = &demodulator_state[chan][d]; profile = save_audio_config_p->achan[chan].profiles[0]; k = d * save_audio_config_p->achan[chan].offset - ((save_audio_config_p->achan[chan].num_freq - 1) * save_audio_config_p->achan[chan].offset) / 2; mark = save_audio_config_p->achan[chan].mark_freq + k; space = save_audio_config_p->achan[chan].space_freq + k; if (save_audio_config_p->achan[chan].num_freq != 1) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %d.%d: %c %d & %d\n", chan, d, profile, mark, space); } demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, save_audio_config_p->achan[chan].baud, mark, space, profile, D); if (have_plus) { /* I'm not happy about putting this hack here. */ /* should pass in as a parameter rather than adding on later. */ save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; D->num_slicers = MAX_SLICERS; } /* For signal level reporting, we want a longer term view. */ D->quick_attack = D->agc_fast_attack * 0.2f; D->sluggish_decay = D->agc_slow_decay * 0.2f; } /* for each freq pair */ } break; case MODEM_QPSK: // New for 1.4 // TODO: See how much CPU this takes on ARM and decide if we should have different defaults. if (strlen(save_audio_config_p->achan[chan].profiles) == 0) { //#if __arm__ // strlcpy (save_audio_config_p->achan[chan].profiles, "R", sizeof(save_audio_config_p->achan[chan].profiles)); //#else strlcpy (save_audio_config_p->achan[chan].profiles, "PQRS", sizeof(save_audio_config_p->achan[chan].profiles)); //#endif } save_audio_config_p->achan[chan].num_subchan = strlen(save_audio_config_p->achan[chan].profiles); save_audio_config_p->achan[chan].decimate = 1; // think about this later. text_color_set(DW_COLOR_DEBUG); dw_printf ("Channel %d: %d bps, QPSK, %s, %d sample rate", chan, save_audio_config_p->achan[chan].baud, save_audio_config_p->achan[chan].profiles, save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec); if (save_audio_config_p->achan[chan].decimate != 1) dw_printf (" / %d", save_audio_config_p->achan[chan].decimate); if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); int d; for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) { assert (d >= 0 && d < MAX_SUBCHANS); struct demodulator_state_s *D; D = &demodulator_state[chan][d]; profile = save_audio_config_p->achan[chan].profiles[d]; //text_color_set(DW_COLOR_DEBUG); //dw_printf ("About to call demod_psk_init for Q-PSK case, modem_type=%d, profile='%c'\n", // save_audio_config_p->achan[chan].modem_type, profile); demod_psk_init (save_audio_config_p->achan[chan].modem_type, save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, save_audio_config_p->achan[chan].baud, profile, D); //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Returned from demod_psk_init\n"); /* For signal level reporting, we want a longer term view. */ /* Guesses based on 9600. Maybe revisit someday. */ D->quick_attack = 0.080 * 0.2; D->sluggish_decay = 0.00012 * 0.2; } break; case MODEM_8PSK: // New for 1.4 // TODO: See how much CPU this takes on ARM and decide if we should have different defaults. if (strlen(save_audio_config_p->achan[chan].profiles) == 0) { //#if __arm__ // strlcpy (save_audio_config_p->achan[chan].profiles, "V", sizeof(save_audio_config_p->achan[chan].profiles)); //#else strlcpy (save_audio_config_p->achan[chan].profiles, "TUVW", sizeof(save_audio_config_p->achan[chan].profiles)); //#endif } save_audio_config_p->achan[chan].num_subchan = strlen(save_audio_config_p->achan[chan].profiles); save_audio_config_p->achan[chan].decimate = 1; // think about this later text_color_set(DW_COLOR_DEBUG); dw_printf ("Channel %d: %d bps, 8PSK, %s, %d sample rate", chan, save_audio_config_p->achan[chan].baud, save_audio_config_p->achan[chan].profiles, save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec); if (save_audio_config_p->achan[chan].decimate != 1) dw_printf (" / %d", save_audio_config_p->achan[chan].decimate); if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); //int d; for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) { assert (d >= 0 && d < MAX_SUBCHANS); struct demodulator_state_s *D; D = &demodulator_state[chan][d]; profile = save_audio_config_p->achan[chan].profiles[d]; //text_color_set(DW_COLOR_DEBUG); //dw_printf ("About to call demod_psk_init for 8-PSK case, modem_type=%d, profile='%c'\n", // save_audio_config_p->achan[chan].modem_type, profile); demod_psk_init (save_audio_config_p->achan[chan].modem_type, save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, save_audio_config_p->achan[chan].baud, profile, D); //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Returned from demod_psk_init\n"); /* For signal level reporting, we want a longer term view. */ /* Guesses based on 9600. Maybe revisit someday. */ D->quick_attack = 0.080 * 0.2; D->sluggish_decay = 0.00012 * 0.2; } break; //TODO: how about MODEM_OFF case? case MODEM_BASEBAND: case MODEM_SCRAMBLE: default: /* Not AFSK */ { if (strcmp(save_audio_config_p->achan[chan].profiles, "") == 0) { /* Apply default if not set earlier. */ /* Not sure if it should be on for ARM too. */ /* Need to take a look at CPU usage and performance difference. */ /* Version 1.5: Remove special case for ARM. */ /* We want higher performance to be the default. */ /* "MODEM 9600 -" can be used on very slow CPU if necessary. */ //#ifndef __arm__ strlcpy (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles)); //#endif } #ifdef TUNE_UPSAMPLE upsample = TUNE_UPSAMPLE; #endif #ifdef TUNE_ZEROSTUFF zerostuff = TUNE_ZEROSTUFF; #endif text_color_set(DW_COLOR_DEBUG); dw_printf ("Channel %d: %d baud, K9NG/G3RUH, %s, %d sample rate x %d", chan, save_audio_config_p->achan[chan].baud, save_audio_config_p->achan[chan].profiles, save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, upsample); if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); struct demodulator_state_s *D; D = &demodulator_state[chan][0]; // first subchannel save_audio_config_p->achan[chan].num_subchan = 1; save_audio_config_p->achan[chan].num_slicers = 1; if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) { /* I'm not happy about putting this hack here. */ /* This belongs in demod_9600_init but it doesn't have access to the audio config. */ save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; } /* We need a minimum number of audio samples per bit time for good performance. */ /* Easier to check here because demod_9600_init might have an adjusted sample rate. */ float ratio = (float)(save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec) / (float)(save_audio_config_p->achan[chan].baud); text_color_set(DW_COLOR_INFO); dw_printf ("The ratio of audio samples per sec (%d) to data rate in baud (%d) is %.1f\n", save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, (double)ratio); if (ratio < 3) { text_color_set(DW_COLOR_ERROR); dw_printf ("There is little hope of success with such a low ratio. Use a higher sample rate.\n"); } else if (ratio < 5) { dw_printf ("This is on the low side for best performance. Can you use a higher sample rate?\n"); } else if (ratio < 6) { dw_printf ("Increasing the sample rate should improve decoder performance.\n"); } else if (ratio > 15) { dw_printf ("Sample rate is more than adequate. You might lower it if CPU load is a concern.\n"); } else { dw_printf ("This is a suitable ratio for good performance.\n"); } demod_9600_init (upsample * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D); if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) { /* I'm not happy about putting this hack here. */ /* should pass in as a parameter rather than adding on later. */ save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; D->num_slicers = MAX_SLICERS; } /* For signal level reporting, we want a longer term view. */ D->quick_attack = D->agc_fast_attack * 0.2f; D->sluggish_decay = D->agc_slow_decay * 0.2f; } break; } /* switch on modulation type. */ } /* if channel number is valid */ } /* for chan ... */ return (0); } /* end demod_init */ /*------------------------------------------------------------------ * * Name: demod_get_sample * * Purpose: Get one audio sample fromt the specified sound input source. * * Inputs: a - Index for audio device. 0 = first. * * Returns: -32768 .. 32767 for a valid audio sample. * 256*256 for end of file or other error. * * Global In: save_audio_config_p->adev[ACHAN2ADEV(chan)].bits_per_sample - So we know whether to * read 1 or 2 bytes from audio stream. * * Description: Grab 1 or two btyes depending on data source. * * When processing stereo, the caller will call this * at twice the normal rate to obtain alternating left * and right samples. * *----------------------------------------------------------------*/ #define FSK_READ_ERR (256*256) __attribute__((hot)) int demod_get_sample (int a) { int x1, x2; signed short sam; /* short to force sign extention. */ assert (save_audio_config_p->adev[a].bits_per_sample == 8 || save_audio_config_p->adev[a].bits_per_sample == 16); if (save_audio_config_p->adev[a].bits_per_sample == 8) { x1 = audio_get(a); if (x1 < 0) return(FSK_READ_ERR); assert (x1 >= 0 && x1 <= 255); /* Scale 0..255 into -32k..+32k */ sam = (x1 - 128) * 256; } else { x1 = audio_get(a); /* lower byte first */ if (x1 < 0) return(FSK_READ_ERR); x2 = audio_get(a); if (x2 < 0) return(FSK_READ_ERR); assert (x1 >= 0 && x1 <= 255); assert (x2 >= 0 && x2 <= 255); sam = ( x2 << 8 ) | x1; } return (sam); } /*------------------------------------------------------------------- * * Name: demod_process_sample * * Purpose: (1) Demodulate the AFSK signal. * (2) Recover clock and data. * * Inputs: chan - Audio channel. 0 for left, 1 for right. * subchan - modem of the channel. * sam - One sample of audio. * Should be in range of -32768 .. 32767. * * Returns: None * * Descripion: We start off with two bandpass filters tuned to * the given frequencies. In the case of VHF packet * radio, this would be 1200 and 2200 Hz. * * The bandpass filter amplitudes are compared to * obtain the demodulated signal. * * We also have a digital phase locked loop (PLL) * to recover the clock and pick out data bits at * the proper rate. * * For each recovered data bit, we call: * * hdlc_rec (channel, demodulated_bit); * * to decode HDLC frames from the stream of bits. * * Future: This could be generalized by passing in the name * of the function to be called for each bit recovered * from the demodulator. For now, it's simply hard-coded. * *--------------------------------------------------------------------*/ __attribute__((hot)) void demod_process_sample (int chan, int subchan, int sam) { float fsam; int k; struct demodulator_state_s *D; assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); D = &demodulator_state[chan][subchan]; /* Scale to nice number, actually -2.0 to +2.0 for extra headroom */ fsam = sam / 16384.0f; /* * Accumulate measure of the input signal level. */ /* * Version 1.2: Try new approach to capturing the amplitude. * This is same as the later AGC without the normalization step. * We want decay to be substantially slower to get a longer * range idea of the received audio. */ if (fsam >= D->alevel_rec_peak) { D->alevel_rec_peak = fsam * D->quick_attack + D->alevel_rec_peak * (1.0f - D->quick_attack); } else { D->alevel_rec_peak = fsam * D->sluggish_decay + D->alevel_rec_peak * (1.0f - D->sluggish_decay); } if (fsam <= D->alevel_rec_valley) { D->alevel_rec_valley = fsam * D->quick_attack + D->alevel_rec_valley * (1.0f - D->quick_attack); } else { D->alevel_rec_valley = fsam * D->sluggish_decay + D->alevel_rec_valley * (1.0f - D->sluggish_decay); } /* * Select decoder based on modulation type. */ switch (save_audio_config_p->achan[chan].modem_type) { case MODEM_OFF: // Might have channel only listening to DTMF for APRStt gateway. // Don't waste CPU time running a demodulator here. break; case MODEM_AFSK: if (save_audio_config_p->achan[chan].decimate > 1) { sample_sum[chan][subchan] += sam; sample_count[chan][subchan]++; if (sample_count[chan][subchan] >= save_audio_config_p->achan[chan].decimate) { demod_afsk_process_sample (chan, subchan, sample_sum[chan][subchan] / save_audio_config_p->achan[chan].decimate, D); sample_sum[chan][subchan] = 0; sample_count[chan][subchan] = 0; } } else { demod_afsk_process_sample (chan, subchan, sam, D); } break; case MODEM_QPSK: case MODEM_8PSK: if (save_audio_config_p->achan[chan].decimate > 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid combination of options. Exiting.\n"); // Would probably work but haven't thought about it or tested yet. exit (1); } else { demod_psk_process_sample (chan, subchan, sam, D); } break; case MODEM_BASEBAND: case MODEM_SCRAMBLE: default: if (zerostuff) { /* Literature says this is better if followed */ /* by appropriate low pass filter. */ /* So far, both are same in tests with different */ /* optimal low pass filter parameters. */ for (k=1; k= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); /* We have to consider two different cases here. */ /* N demodulators, each with own slicer and HDLC decoder. */ /* Single demodulator, multiple slicers each with own HDLC decoder. */ if (demodulator_state[chan][0].num_slicers > 1) { subchan = 0; } D = &demodulator_state[chan][subchan]; // Take half of peak-to-peak for received audio level. alevel.rec = (int) (( D->alevel_rec_peak - D->alevel_rec_valley ) * 50.0f + 0.5f); if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK) { /* For AFSK, we have mark and space amplitudes. */ alevel.mark = (int) ((D->alevel_mark_peak ) * 100.0f + 0.5f); alevel.space = (int) ((D->alevel_space_peak ) * 100.0f + 0.5f); } else if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK || save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) { alevel.mark = -1; alevel.space = -1; } else { #if 1 /* Display the + and - peaks. */ /* Normally we'd expect them to be about the same. */ /* However, with SDR, or other DC coupling, we could have an offset. */ alevel.mark = (int) ((D->alevel_mark_peak) * 200.0f + 0.5f); alevel.space = (int) ((D->alevel_space_peak) * 200.0f - 0.5f); #else /* Here we have + and - peaks after filtering. */ /* Take half of the peak to peak. */ /* The "5/6" factor worked out right for the current low pass filter. */ /* Will it need to be different if the filter is tweaked? */ alevel.mark = (int) ((D->alevel_mark_peak - D->alevel_space_peak) * 100.0f * 5.0f/6.0f + 0.5f); alevel.space = -1; /* to print one number inside of ( ) */ #endif } return (alevel); } /* end demod.c */ direwolf-1.5+dfsg/demod.h000066400000000000000000000005211347750676600153750ustar00rootroot00000000000000 /* demod.h */ #include "audio.h" /* for struct audio_s */ #include "ax25_pad.h" /* for alevel_t */ int demod_init (struct audio_s *pa); int demod_get_sample (int a); void demod_process_sample (int chan, int subchan, int sam); void demod_print_agc (int chan, int subchan); alevel_t demod_get_audio_level (int chan, int subchan);direwolf-1.5+dfsg/demod_9600.c000066400000000000000000000402021347750676600160460ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2015 John Langner, WB2OSZ // // 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, see . // //#define DEBUG4 1 /* capture 9600 output to log files */ /*------------------------------------------------------------------ * * Module: demod_9600.c * * Purpose: Demodulator for scrambled baseband encoding. * * Input: Audio samples from either a file or the "sound card." * * Outputs: Calls hdlc_rec_bit() for each bit demodulated. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #include "tune.h" #include "fsk_demod_state.h" #include "hdlc_rec.h" #include "demod_9600.h" #include "textcolor.h" #include "dsp.h" static float slice_point[MAX_SUBCHANS]; /* Add sample to buffer and shift the rest down. */ __attribute__((hot)) __attribute__((always_inline)) static inline void push_sample (float val, float *buff, int size) { memmove(buff+1,buff,(size-1)*sizeof(float)); buff[0] = val; } /* FIR filter kernel. */ __attribute__((hot)) __attribute__((always_inline)) static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size) { float sum = 0.0f; int j; //#pragma GCC ivdep // ignored until gcc 4.9 for (j=0; j= *ppeak) { *ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack); } else { *ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay); } if (in <= *pvalley) { *pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack); } else { *pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay); } if (*ppeak > *pvalley) { return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); } return (0.0); } /*------------------------------------------------------------------ * * Name: demod_9600_init * * Purpose: Initialize the 9600 (or higher) baud demodulator. * * Inputs: samples_per_sec - Number of samples per second. * Might be upsampled in hopes of * reducing the PLL jitter. * * baud - Data rate in bits per second. * * D - Address of demodulator state. * * Returns: None * *----------------------------------------------------------------*/ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D) { float fc; int j; memset (D, 0, sizeof(struct demodulator_state_s)); D->num_slicers = 1; // Multiple profiles in future? // switch (profile) { // case 'J': // upsample x2 with filtering. // case 'K': // upsample x3 with filtering. // case 'L': // upsample x4 with filtering. D->lp_filter_len_bits = 76 * 9600.0 / (44100.0 * 2.0); // Works best with odd number in some tests. Even is better in others. //D->lp_filter_size = ((int) (0.5f * ( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud ))) * 2 + 1; D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5f); D->lp_window = BP_WINDOW_HAMMING; D->lpf_baud = 0.62; D->agc_fast_attack = 0.080; D->agc_slow_decay = 0.00012; D->pll_locked_inertia = 0.89; D->pll_searching_inertia = 0.67; // break; // } D->pll_step_per_sample = (int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec); #ifdef TUNE_LP_WINDOW D->lp_window = TUNE_LP_WINDOW; #endif #if TUNE_LP_FILTER_SIZE D->lp_filter_size = TUNE_LP_FILTER_SIZE; #endif #ifdef TUNE_LPF_BAUD D->lpf_baud = TUNE_LPF_BAUD; #endif #ifdef TUNE_AGC_FAST D->agc_fast_attack = TUNE_AGC_FAST; #endif #ifdef TUNE_AGC_SLOW D->agc_slow_decay = TUNE_AGC_SLOW; #endif #if defined(TUNE_PLL_LOCKED) D->pll_locked_inertia = TUNE_PLL_LOCKED; #endif #if defined(TUNE_PLL_SEARCHING) D->pll_searching_inertia = TUNE_PLL_SEARCHING; #endif fc = (float)baud * D->lpf_baud / (float)samples_per_sec; //dw_printf ("demod_9600_init: call gen_lowpass(fc=%.2f, , size=%d, )\n", fc, D->lp_filter_size); gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); /* Version 1.2: Experiment with different slicing levels. */ for (j = 0; j < MAX_SUBCHANS; j++) { slice_point[j] = 0.02f * (j - 0.5f * (MAX_SUBCHANS-1)); //dw_printf ("slice_point[%d] = %+5.2f\n", j, slice_point[j]); } } /* end fsk_demod_init */ /*------------------------------------------------------------------- * * Name: demod_9600_process_sample * * Purpose: (1) Filter & slice the signal. * (2) Descramble it. * (2) Recover clock and data. * * Inputs: chan - Audio channel. 0 for left, 1 for right. * * sam - One sample of audio. * Should be in range of -32768 .. 32767. * * Returns: None * * Descripion: "9600 baud" packet is FSK for an FM voice transceiver. * By the time it gets here, it's really a baseband signal. * At one extreme, we could have a 4800 Hz square wave. * A the other extreme, we could go a considerable number * of bit times without any transitions. * * The trick is to extract the digital data which has * been distorted by going thru voice transceivers not * intended to pass this sort of "audio" signal. * * Data is "scrambled" to reduce the amount of DC bias. * The data stream must be unscrambled at the receiving end. * * We also have a digital phase locked loop (PLL) * to recover the clock and pick out data bits at * the proper rate. * * For each recovered data bit, we call: * * hdlc_rec (channel, demodulated_bit); * * to decode HDLC frames from the stream of bits. * * Future: This could be generalized by passing in the name * of the function to be called for each bit recovered * from the demodulator. For now, it's simply hard-coded. * * References: 9600 Baud Packet Radio Modem Design * http://www.amsat.org/amsat/articles/g3ruh/109.html * * The KD2BD 9600 Baud Modem * http://www.amsat.org/amsat/articles/kd2bd/9k6modem/ * * 9600 Baud Packet Handbook * ftp://ftp.tapr.org/general/9600baud/96man2x0.txt * * *--------------------------------------------------------------------*/ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D); __attribute__((hot)) void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D) { float fsam; float amp; float demod_out; #if DEBUG4 static FILE *demod_log_fp = NULL; static int log_file_seq = 0; /* Part of log file name */ #endif int subchan = 0; int demod_data; /* Still scrambled. */ assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); /* * Filters use last 'filter_size' samples. * * First push the older samples down. * * Finally, put the most recent at the beginning. * * Future project? Rather than shifting the samples, * it might be faster to add another variable to keep * track of the most recent sample and change the * indexing in the later loops that multipy and add. */ /* Scale to nice number for convenience. */ /* Consistent with the AFSK demodulator, we'd like to use */ /* only half of the dynamic range to have some headroom. */ /* i.e. input range +-16k becomes +-1 here and is */ /* displayed in the heard line as audio level 100. */ fsam = sam / 16384.0; #if defined(TUNE_ZEROSTUFF) && TUNE_ZEROSTUFF == 0 // experiment - no filtering. amp = fsam; #else push_sample (fsam, D->raw_cb, D->lp_filter_size); /* * Low pass filter to reduce noise yet pass the data. */ amp = convolve (D->raw_cb, D->lp_filter, D->lp_filter_size); #endif /* * Version 1.2: Capture the post-filtering amplitude for display. * This is similar to the AGC without the normalization step. * We want decay to be substantially slower to get a longer * range idea of the received audio. * For AFSK, we keep mark and space amplitudes. * Here we keep + and - peaks because there could be a DC bias. */ // TODO: probably no need for this. Just use D->m_peak, D->m_valley if (amp >= D->alevel_mark_peak) { D->alevel_mark_peak = amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack); } else { D->alevel_mark_peak = amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay); } if (amp <= D->alevel_space_peak) { D->alevel_space_peak = amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack); } else { D->alevel_space_peak = amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay); } /* * The input level can vary greatly. * More importantly, there could be a DC bias which we need to remove. * * Normalize the signal with automatic gain control (AGC). * This works by looking at the minimum and maximum signal peaks * and scaling the results to be roughly in the -1.0 to +1.0 range. */ demod_out = agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley)); // TODO: There is potential for multiple decoders with one filter. //dw_printf ("peak=%.2f valley=%.2f amp=%.2f norm=%.2f\n", D->m_peak, D->m_valley, amp, norm); if (D->num_slicers <= 1) { /* Normal case of one demodulator to one HDLC decoder. */ /* Demodulator output is difference between response from two filters. */ /* AGC should generally keep this around -1 to +1 range. */ demod_data = demod_out > 0; nudge_pll (chan, subchan, 0, demod_out, D); } else { int slice; /* Multiple slicers each feeding its own HDLC decoder. */ for (slice=0; slicenum_slicers; slice++) { demod_data = demod_out - slice_point[slice] > 0; nudge_pll (chan, subchan, slice, demod_out - slice_point[slice], D); } } // demod_data is used only for debug out. // suppress compiler warning about it not being used. (void) demod_data; #if DEBUG4 if (chan == 0) { if (1) { //if (hdlc_rec_gathering (chan, subchan, slice)) { char fname[30]; int slice = 0; if (demod_log_fp == NULL) { log_file_seq++; snprintf (fname, sizeof(fname), "demod/%04d.csv", log_file_seq); //if (log_file_seq == 1) mkdir ("demod", 0777); if (log_file_seq == 1) mkdir ("demod"); demod_log_fp = fopen (fname, "w"); text_color_set(DW_COLOR_DEBUG); dw_printf ("Starting demodulator log file %s\n", fname); fprintf (demod_log_fp, "Audio, Filtered, Max, Min, Normalized, Sliced, Clock\n"); } fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %d, %.2f\n", fsam + 6, amp + 4, D->m_peak + 4, D->m_valley + 4, demod_out + 2, demod_data + 2, (D->slicer[slice].data_clock_pll & 0x80000000) ? .5 : .0); fflush (demod_log_fp); } else { if (demod_log_fp != NULL) { fclose (demod_log_fp); demod_log_fp = NULL; } } } #endif } /* end demod_9600_process_sample */ /*------------------------------------------------------------------- * * Name: nudge_pll * * Purpose: Update the PLL state for each audio sample. * * (2) Descramble it. * (2) Recover clock and data. * * Inputs: chan - Audio channel. 0 for left, 1 for right. * * subchan - Which demodulator. We could have several running in parallel. * * slice - Determines which Slicing level & HDLC decoder to use. * * demod_out_f - Demodulator output, possibly shifted by slicing level * It will be compared with 0.0 to bit binary value out. * * D - Demodulator state for this channel / subchannel. * * Returns: None * * Descripton: A PLL is used to sample near the centers of the data bits. * * D->data_clock_pll is a SIGNED 32 bit variable. * When it overflows from a large positive value to a negative value, we * sample a data bit from the demodulated signal. * * Ideally, the the demodulated signal transitions should be near * zero we we sample mid way between the transitions. * * Nudge the PLL by removing some small fraction from the value of * data_clock_pll, pushing it closer to zero. * * This adjustment will never change the sign so it won't cause * any erratic data bit sampling. * * If we adjust it too quickly, the clock will have too much jitter. * If we adjust it too slowly, it will take too long to lock on to a new signal. * * I don't think the optimal value will depend on the audio sample rate * because this happens for each transition from the demodulator. * * Version 1.4: Previously, we would always pull the PLL phase toward 0 after * after a zero crossing was detetected. This adds extra jitter, * especially when the ratio of audio sample rate to baud is low. * Now, we interpolate between the two samples to get an estimate * on when the zero crossing happened. The PLL is pulled toward * this point. * * Results??? TBD * *--------------------------------------------------------------------*/ __attribute__((hot)) inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_f, struct demodulator_state_s *D) { /* */ D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; // Perform the add as unsigned to avoid signed overflow error. D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample)); if ( D->slicer[slice].prev_d_c_pll > 1000000000 && D->slicer[slice].data_clock_pll < -1000000000) { /* Overflow. Was large positive, wrapped around, now large negative. */ hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, 1, D->slicer[slice].lfsr); } /* * Zero crossing? */ if ((D->slicer[slice].prev_demod_out_f < 0 && demod_out_f > 0) || (D->slicer[slice].prev_demod_out_f > 0 && demod_out_f < 0)) { // Note: Test for this demodulator, not overall for channel. float target = 0; target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f); if (hdlc_rec_gathering (chan, subchan, slice)) { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia + target * (1.0f - D->pll_locked_inertia) ); } else { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia + target * (1.0f - D->pll_searching_inertia) ); } } #if DEBUG5 //if (chan == 0) { if (hdlc_rec_gathering (chan,subchan,slice)) { char fname[30]; if (demod_log_fp == NULL) { seq++; snprintf (fname, sizeof(fname), "demod96/%04d.csv", seq); if (seq == 1) mkdir ("demod96" #ifndef __WIN32__ , 0777 #endif ); demod_log_fp = fopen (fname, "w"); text_color_set(DW_COLOR_DEBUG); dw_printf ("Starting 9600 decoder log file %s\n", fname); fprintf (demod_log_fp, "Audio, Peak, Valley, Demod, SData, Descram, Clock\n"); } fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n", 0.5f * fsam + 3.5, 0.5f * D->m_peak + 3.5, 0.5f * D->m_valley + 3.5, 0.5f * demod_out + 2.0, demod_data ? 1.35 : 1.0, descram ? .9 : .55, (D->data_clock_pll & 0x80000000) ? .1 : .45); } else { if (demod_log_fp != NULL) { fclose (demod_log_fp); demod_log_fp = NULL; } } //} #endif /* * Remember demodulator output (pre-descrambling) so we can compare next time * for the DPLL sync. */ D->slicer[slice].prev_demod_out_f = demod_out_f; } /* end nudge_pll */ /* end demod_9600.c */ direwolf-1.5+dfsg/demod_9600.h000066400000000000000000000006651347750676600160640ustar00rootroot00000000000000 /* demod_9600.h */ #include "fsk_demod_state.h" void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D); void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D); /* Undo data scrambling for 9600 baud. */ static inline int descramble (int in, int *state) { int out; out = (in ^ (*state >> 16) ^ (*state >> 11)) & 1; *state = (*state << 1) | (in & 1); return (out); } direwolf-1.5+dfsg/demod_afsk.c000066400000000000000000000773031347750676600164100ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // // #define DEBUG1 1 /* display debugging info */ // #define DEBUG3 1 /* print carrier detect changes. */ // #define DEBUG4 1 /* capture AFSK demodulator output to log files */ // #define DEBUG5 1 /* capture 9600 output to log files */ /*------------------------------------------------------------------ * * Module: demod_afsk.c * * Purpose: Demodulator for Audio Frequency Shift Keying (AFSK). * * Input: Audio samples from either a file or the "sound card." * * Outputs: Calls hdlc_rec_bit() for each bit demodulated. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #include "audio.h" #include "tune.h" #include "fsk_demod_state.h" #include "fsk_gen_filter.h" #include "hdlc_rec.h" #include "textcolor.h" #include "demod_afsk.h" #include "dsp.h" #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) #ifndef GEN_FFF /* Quick approximation to sqrt(x*x+y*y) */ /* No benefit for regular PC. */ /* Should help with microcomputer platform. */ __attribute__((hot)) __attribute__((always_inline)) static inline float z (float x, float y) { x = fabsf(x); y = fabsf(y); if (x > y) { return (x * .941246f + y * .41f); } else { return (y * .941246f + x * .41f); } } /* Add sample to buffer and shift the rest down. */ __attribute__((hot)) __attribute__((always_inline)) static inline void push_sample (float val, float *buff, int size) { memmove(buff+1,buff,(size-1)*sizeof(float)); buff[0] = val; } /* FIR filter kernel. */ __attribute__((hot)) __attribute__((always_inline)) static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size) { float sum = 0.0f; int j; //#pragma GCC ivdep // ignored until gcc 4.9 for (j=0; j= *ppeak) { *ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack); } else { *ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay); } if (in <= *pvalley) { *pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack); } else { *pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay); } if (*ppeak > *pvalley) { return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); } return (0.0f); } #endif // ifndef GEN_FFF /* * for multi-slicer experiment. */ #define MIN_G 0.5f #define MAX_G 4.0f /* TODO: static */ float space_gain[MAX_SUBCHANS]; /*------------------------------------------------------------------ * * Name: demod_afsk_init * * Purpose: Initialization for an AFSK demodulator. * Select appropriate parameters and set up filters. * * Inputs: samples_per_sec * baud * mark_freq * space_freq * * D - Pointer to demodulator state for given channel. * * Outputs: D->ms_filter_size * D->m_sin_table[] * D->m_cos_table[] * D->s_sin_table[] * D->s_cos_table[] * * Returns: None. * * Bugs: This doesn't do much error checking so don't give it * anything crazy. * *----------------------------------------------------------------*/ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, int space_freq, char profile, struct demodulator_state_s *D) { int j; memset (D, 0, sizeof(struct demodulator_state_s)); D->num_slicers = 1; #if DEBUG1 dw_printf ("demod_afsk_init (rate=%d, baud=%d, mark=%d, space=%d, profile=%c\n", samples_per_sec, baud, mark_freq, space_freq, profile); #endif #ifdef TUNE_PROFILE profile = TUNE_PROFILE; #endif if (profile == 'F') { if (baud != DEFAULT_BAUD || mark_freq != DEFAULT_MARK_FREQ || space_freq!= DEFAULT_SPACE_FREQ || samples_per_sec != DEFAULT_SAMPLES_PER_SEC) { text_color_set(DW_COLOR_INFO); dw_printf ("Note: Decoder 'F' works only for %d baud, %d/%d tones, %d samples/sec.\n", DEFAULT_BAUD, DEFAULT_MARK_FREQ, DEFAULT_SPACE_FREQ, DEFAULT_SAMPLES_PER_SEC); dw_printf ("Using Decoder 'A' instead.\n"); profile = 'A'; } } D->profile = profile; // so we know whether to take fast path later. switch (profile) { case 'A': case 'F': /* Original. 52 taps, truncated bandpass, IIR lowpass */ /* 'F' is the fast version for low end processors. */ /* It is a special case that works only for a particular */ /* baud rate, tone pair, and sampling rate. */ D->use_prefilter = 0; D->ms_filter_len_bits = 1.415; /* 52 @ 44100, 1200 */ D->ms_window = BP_WINDOW_TRUNCATED; //D->bp_window = BP_WINDOW_TRUNCATED; D->lpf_use_fir = 0; D->lpf_iir = 0.195; D->agc_fast_attack = 0.250; D->agc_slow_decay = 0.00012; D->hysteresis = 0.005; D->pll_locked_inertia = 0.700; D->pll_searching_inertia = 0.580; break; case 'B': /* Original bandpass. Use FIR lowpass instead. */ D->use_prefilter = 0; D->ms_filter_len_bits = 1.415; /* 52 @ 44100, 1200 */ D->ms_window = BP_WINDOW_TRUNCATED; //D->bp_window = BP_WINDOW_TRUNCATED; D->lpf_use_fir = 1; D->lpf_baud = 1.09; D->lp_filter_len_bits = D->ms_filter_len_bits; D->lp_window = BP_WINDOW_TRUNCATED; D->agc_fast_attack = 0.370; D->agc_slow_decay = 0.00014; D->hysteresis = 0.003; D->pll_locked_inertia = 0.620; D->pll_searching_inertia = 0.350; break; case 'C': /* Cosine window, 76 taps for bandpass, FIR lowpass. */ D->use_prefilter = 0; D->ms_filter_len_bits = 2.068; /* 76 @ 44100, 1200 */ D->ms_window = BP_WINDOW_COSINE; //D->bp_window = BP_WINDOW_COSINE; D->lpf_use_fir = 1; D->lpf_baud = 1.09; D->lp_filter_len_bits = D->ms_filter_len_bits; D->lp_window = BP_WINDOW_TRUNCATED; D->agc_fast_attack = 0.495; D->agc_slow_decay = 0.00022; D->hysteresis = 0.005; D->pll_locked_inertia = 0.620; D->pll_searching_inertia = 0.350; break; case 'D': /* Prefilter, Cosine window, FIR lowpass. Tweeked for 300 baud. */ D->use_prefilter = 1; /* first, a bandpass filter. */ D->prefilter_baud = 0.87; D->pre_filter_len_bits = 1.857; D->pre_window = BP_WINDOW_COSINE; D->ms_filter_len_bits = 1.857; /* 91 @ 44100/3, 300 */ D->ms_window = BP_WINDOW_COSINE; //D->bp_window = BP_WINDOW_COSINE; D->lpf_use_fir = 1; D->lpf_baud = 1.10; D->lp_filter_len_bits = D->ms_filter_len_bits; D->lp_window = BP_WINDOW_TRUNCATED; D->agc_fast_attack = 0.495; D->agc_slow_decay = 0.00022; D->hysteresis = 0.027; D->pll_locked_inertia = 0.620; D->pll_searching_inertia = 0.350; break; case 'E': /* 1200 baud - Started out similar to C but add prefilter. */ /* Version 1.2 */ /* Enhancements: */ /* + Add prefilter. Previously used for 300 baud D, but not 1200. */ /* + Prefilter length now independent of M/S filters. */ /* + Lowpass filter length now independent of M/S filters. */ /* + Allow mixed window types. */ //D->bp_window = BP_WINDOW_COSINE; /* The name says BP but it is used for all of them. */ D->use_prefilter = 1; /* first, a bandpass filter. */ D->prefilter_baud = 0.23; D->pre_filter_len_bits = 156 * 1200. / 44100.; D->pre_window = BP_WINDOW_TRUNCATED; D->ms_filter_len_bits = 74 * 1200. / 44100.; D->ms_window = BP_WINDOW_COSINE; D->lpf_use_fir = 1; D->lpf_baud = 1.18; D->lp_filter_len_bits = 63 * 1200. / 44100.; D->lp_window = BP_WINDOW_TRUNCATED; //D->agc_fast_attack = 0.300; //D->agc_slow_decay = 0.000185; D->agc_fast_attack = 0.820; D->agc_slow_decay = 0.000214; D->hysteresis = 0.01; //D->pll_locked_inertia = 0.57; //D->pll_searching_inertia = 0.33; D->pll_locked_inertia = 0.74; D->pll_searching_inertia = 0.50; break; case 'G': /* 1200 baud - Started out same as E but add 3 way interleave. */ /* Version 1.3 - EXPERIMENTAL - Needs more fine tuning. */ //D->bp_window = BP_WINDOW_COSINE; /* The name says BP but it is used for all of them. */ D->use_prefilter = 1; /* first, a bandpass filter. */ D->prefilter_baud = 0.15; D->pre_filter_len_bits = 128 * 1200. / (44100. / 3.); D->pre_window = BP_WINDOW_TRUNCATED; D->ms_filter_len_bits = 25 * 1200. / (44100. / 3.); D->ms_window = BP_WINDOW_COSINE; D->lpf_use_fir = 1; D->lpf_baud = 1.16; D->lp_filter_len_bits = 21 * 1200. / (44100. / 3.); D->lp_window = BP_WINDOW_TRUNCATED; D->agc_fast_attack = 0.130; D->agc_slow_decay = 0.00013; D->hysteresis = 0.01; D->pll_locked_inertia = 0.73; D->pll_searching_inertia = 0.64; break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid filter profile = %c\n", profile); exit (1); } #ifdef TUNE_PRE_WINDOW D->pre_window = TUNE_PRE_WINDOW; #endif #ifdef TUNE_MS_WINDOW D->ms_window = TUNE_MS_WINDOW; #endif #ifdef TUNE_LP_WINDOW D->lp_window = TUNE_LP_WINDOW; #endif #if defined(TUNE_AGC_FAST) && defined(TUNE_AGC_SLOW) D->agc_fast_attack = TUNE_AGC_FAST; D->agc_slow_decay = TUNE_AGC_SLOW; #endif #ifdef TUNE_HYST D->hysteresis = TUNE_HYST; #endif #if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING) D->pll_locked_inertia = TUNE_PLL_LOCKED; D->pll_searching_inertia = TUNE_PLL_SEARCHING; #endif #ifdef TUNE_LPF_BAUD D->lpf_baud = TUNE_LPF_BAUD; #endif #ifdef TUNE_PRE_BAUD D->prefilter_baud = TUNE_PRE_BAUD; #endif /* * Calculate constants used for timing. * The audio sample rate must be at least a few times the data rate. */ D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)baud) / ((double)samples_per_sec)); /* * Convert number of bit times to number of taps. */ D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)baud ); D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)baud ); D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud ); /* Experiment with other sizes. */ #ifdef TUNE_PRE_FILTER_SIZE D->pre_filter_size = TUNE_PRE_FILTER_SIZE; #endif #ifdef TUNE_MS_FILTER_SIZE D->ms_filter_size = TUNE_MS_FILTER_SIZE; #endif #ifdef TUNE_LP_FILTER_SIZE D->lp_filter_size = TUNE_LP_FILTER_SIZE; #endif //assert (D->pre_filter_size >= 4); assert (D->ms_filter_size >= 4); //assert (D->lp_filter_size >= 4); if (D->pre_filter_size > MAX_FILTER_SIZE) { text_color_set (DW_COLOR_ERROR); dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size); dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE); exit (1); } if (D->ms_filter_size > MAX_FILTER_SIZE) { text_color_set (DW_COLOR_ERROR); dw_printf ("Calculated filter size of %d is too large.\n", D->ms_filter_size); dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE); exit (1); } if (D->lp_filter_size > MAX_FILTER_SIZE) { text_color_set (DW_COLOR_ERROR); dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size); dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE); exit (1); } /* * Optionally apply a bandpass ("pre") filter to attenuate * frequencies outside the range of interest. * This was first used for the "D" profile for 300 baud * which uses narrow shift. We expect it to have significant * benefit for a narrow shift. * In version 1.2, we will also try it with 1200 baud "E" as * an experiment to see how much it actually helps. */ if (D->use_prefilter) { float f1, f2; f1 = MIN(mark_freq,space_freq) - D->prefilter_baud * baud; f2 = MAX(mark_freq,space_freq) + D->prefilter_baud * baud; #if 0 text_color_set(DW_COLOR_DEBUG); dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", f1, f2); #endif f1 = f1 / (float)samples_per_sec; f2 = f2 / (float)samples_per_sec; //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_HAMMING); //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_BLACKMAN); //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_COSINE); //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->bp_window); gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window); } /* * Filters for detecting mark and space tones. */ #if DEBUG1 text_color_set(DW_COLOR_DEBUG); dw_printf ("%s: \n", __FILE__); dw_printf ("%d baud, %d samples_per_sec\n", baud, samples_per_sec); dw_printf ("AFSK %d & %d Hz\n", mark_freq, space_freq); dw_printf ("spll_step_per_sample = %d = 0x%08x\n", D->pll_step_per_sample, D->pll_step_per_sample); dw_printf ("D->ms_filter_size = %d = 0x%08x\n", D->ms_filter_size, D->ms_filter_size); dw_printf ("\n"); dw_printf ("Mark\n"); dw_printf (" j shape M sin M cos \n"); #endif float Gs = 0, Gc = 0; for (j=0; jms_filter_size; j++) { float am; float center; float shape = 1.0f; /* Shape is an attempt to smooth out the */ /* abrupt edges in hopes of reducing */ /* overshoot and ringing. */ /* My first thought was to use a cosine shape. */ /* Should investigate Hamming and Blackman */ /* windows mentioned in the literature. */ /* http://en.wikipedia.org/wiki/Window_function */ center = 0.5f * (D->ms_filter_size - 1); am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2.0f * (float)M_PI); shape = window (D->ms_window, D->ms_filter_size, j); D->m_sin_table[j] = sinf(am) * shape; D->m_cos_table[j] = cosf(am) * shape; Gs += D->m_sin_table[j] * sinf(am); Gc += D->m_cos_table[j] * cosf(am); #if DEBUG1 dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->m_sin_table[j], D->m_cos_table[j]) ; #endif } /* Normalize for unity gain */ #if DEBUG1 dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ; #endif for (j=0; jms_filter_size; j++) { D->m_sin_table[j] = D->m_sin_table[j] / Gs; D->m_cos_table[j] = D->m_cos_table[j] / Gc; } #if DEBUG1 text_color_set(DW_COLOR_DEBUG); dw_printf ("Space\n"); dw_printf (" j shape S sin S cos\n"); #endif Gs = 0; Gc = 0; for (j=0; jms_filter_size; j++) { float as; float center; float shape = 1.0f; center = 0.5 * (D->ms_filter_size - 1); as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2.0f * (float)M_PI); shape = window (D->ms_window, D->ms_filter_size, j); D->s_sin_table[j] = sinf(as) * shape; D->s_cos_table[j] = cosf(as) * shape; Gs += D->s_sin_table[j] * sinf(as); Gc += D->s_cos_table[j] * cosf(as); #if DEBUG1 dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->s_sin_table[j], D->s_cos_table[j] ) ; #endif } /* Normalize for unity gain */ #if DEBUG1 dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ; #endif for (j=0; jms_filter_size; j++) { D->s_sin_table[j] = D->s_sin_table[j] / Gs; D->s_cos_table[j] = D->s_cos_table[j] / Gc; } /* * Now the lowpass filter. * I thought we'd want a cutoff of about 0.5 the baud rate * but it turns out about 1.1x is better. Still investigating... */ if (D->lpf_use_fir) { float fc; fc = baud * D->lpf_baud / (float)samples_per_sec; gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); } /* * A non-whole number of cycles results in a DC bias. * Let's see if it helps to take it out. * Actually makes things worse: 20 fewer decoded. * Might want to try again after EXPERIMENTC. */ #if 0 #ifndef AVOID_FLOATING_POINT failed experiment dc_bias = 0; for (j=0; jms_filter_size; j++) { dc_bias += D->m_sin_table[j]; } for (j=0; jms_filter_size; j++) { D->m_sin_table[j] -= dc_bias / D->ms_filter_size; } dc_bias = 0; for (j=0; jms_filter_size; j++) { dc_bias += D->m_cos_table[j]; } for (j=0; jms_filter_size; j++) { D->m_cos_table[j] -= dc_bias / D->ms_filter_size; } dc_bias = 0; for (j=0; jms_filter_size; j++) { dc_bias += D->s_sin_table[j]; } for (j=0; jms_filter_size; j++) { D->s_sin_table[j] -= dc_bias / D->ms_filter_size; } dc_bias = 0; for (j=0; jms_filter_size; j++) { dc_bias += D->s_cos_table[j]; } for (j=0; jms_filter_size; j++) { D->s_cos_table[j] -= dc_bias / D->ms_filter_size; } #endif #endif /* * In version 1.2 we try another experiment. * Try using multiple slicing points instead of the traditional AGC. */ space_gain[0] = MIN_G; float step = powf(10.0, log10f(MAX_G/MIN_G) / (MAX_SUBCHANS-1)); for (j=1; j= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); /* * Filters use last 'filter_size' samples. * * First push the older samples down. * * Finally, put the most recent at the beginning. * * Future project? Can we do better than shifting each time? */ /* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */ fsam = sam / 16384.0f; //abs_fsam = fsam >= 0.0f ? fsam : -fsam; /* * Optional bandpass filter before the mark/space discriminator. */ if (D->use_prefilter) { float cleaner; push_sample (fsam, D->raw_cb, D->pre_filter_size); cleaner = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size); push_sample (cleaner, D->ms_in_cb, D->ms_filter_size); } else { push_sample (fsam, D->ms_in_cb, D->ms_filter_size); } /* * Next we have bandpass filters for the mark and space tones. * * This takes a lot of computation. * It's not a problem on a typical (Intel x86 based) PC. * Dire Wolf takes only about 2 or 3% of the CPU time. * * It might be too much for a little microcomputer to handle. * * Here we have an optimized case for the default values. */ // TODO1.2: is this right or do we need to store profile in the modulator info? if (D->profile == toupper(FFF_PROFILE)) { /* ========== Faster for default values on slower processors. ========== */ m_sum1 = CALC_M_SUM1(D->ms_in_cb); m_sum2 = CALC_M_SUM2(D->ms_in_cb); m_amp = z(m_sum1,m_sum2); s_sum1 = CALC_S_SUM1(D->ms_in_cb); s_sum2 = CALC_S_SUM2(D->ms_in_cb); s_amp = z(s_sum1,s_sum2); } else { /* ========== General case to handle all situations. ========== */ /* * find amplitude of "Mark" tone. */ m_sum1 = convolve (D->ms_in_cb, D->m_sin_table, D->ms_filter_size); m_sum2 = convolve (D->ms_in_cb, D->m_cos_table, D->ms_filter_size); m_amp = sqrtf(m_sum1 * m_sum1 + m_sum2 * m_sum2); /* * Find amplitude of "Space" tone. */ s_sum1 = convolve (D->ms_in_cb, D->s_sin_table, D->ms_filter_size); s_sum2 = convolve (D->ms_in_cb, D->s_cos_table, D->ms_filter_size); s_amp = sqrtf(s_sum1 * s_sum1 + s_sum2 * s_sum2); /* ========== End of general case. ========== */ } /* * Apply some low pass filtering BEFORE the AGC to remove * overshoot, ringing, and other bad stuff. * * A simple IIR filter is faster but FIR produces better results. * * It is a balancing act between removing high frequency components * from the tone dectection while letting the data thru. */ if (D->lpf_use_fir) { push_sample (m_amp, D->m_amp_cb, D->lp_filter_size); m_amp = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size); push_sample (s_amp, D->s_amp_cb, D->lp_filter_size); s_amp = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size); } else { /* Original, but faster, IIR. */ m_amp = D->lpf_iir * m_amp + (1.0f - D->lpf_iir) * D->m_amp_prev; D->m_amp_prev = m_amp; s_amp = D->lpf_iir * s_amp + (1.0f - D->lpf_iir) * D->s_amp_prev; D->s_amp_prev = s_amp; } /* * Version 1.2: Try new approach to capturing the amplitude for display. * This is same as the AGC above without the normalization step. * We want decay to be substantially slower to get a longer * range idea of the received audio. */ if (m_amp >= D->alevel_mark_peak) { D->alevel_mark_peak = m_amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack); } else { D->alevel_mark_peak = m_amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay); } if (s_amp >= D->alevel_space_peak) { D->alevel_space_peak = s_amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack); } else { D->alevel_space_peak = s_amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay); } /* * Which tone is stronger? * * In an ideal world, simply compare. In my first naive attempt, that * worked perfectly with perfect signals. In the real world, we don't * have too many perfect signals. * * Here is an excellent explanation: * http://www.febo.com/packet/layer-one/transmit.html * * Under real conditions, we find that the higher tone has a * considerably smaller amplitude due to the passband characteristics * of the transmitter and receiver. To make matters worse, it * varies considerably from one station to another. * * The two filters also have different amounts of DC bias. * * My solution was to apply automatic gain control (AGC) to the mark and space * levels. This works by looking at the minimum and maximum outputs * for each filter and scaling the results to be roughly in the -0.5 to +0.5 range. * Results were excellent after tweaking the attack and decay times. * * 4X6IZ took a different approach. See QEX Jul-Aug 2012. * * He ran two different demodulators in parallel. One of them boosted the higher * frequency tone by 6 dB. Any duplicates were removed. This produced similar results. * He also used a bandpass filter before the mark/space filters. * I haven't tried this combination yet for 1200 baud. * * First, let's take a look at Track 1 of the TNC test CD. Here the receiver * has a flat response. We find the mark/space strength ratios very from 0.53 to 1.38 * with a median of 0.81. This in in line with expections because most * transmitters add pre-emphasis to boost the higher audio frequencies. * Track 2 should more closely resemble what comes out of the speaker on a typical * transceiver. Here we see a ratio from 1.73 to 3.81 with a median of 2.48. * * This is similar to my observations of local signals, from the speaker. * The amplitude ratio varies from 1.48 to 3.41 with a median of 2.70. * * Rather than only two filters, let's try slicing the data in more places. */ /* Fast attack and slow decay. */ /* Numbers were obtained by trial and error from actual */ /* recorded less-than-optimal signals. */ /* See fsk_demod_agc.h for more information. */ m_norm = agc (m_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley)); s_norm = agc (s_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->s_peak), &(D->s_valley)); if (D->num_slicers <= 1) { /* Normal case of one demodulator to one HDLC decoder. */ /* Demodulator output is difference between response from two filters. */ /* AGC should generally keep this around -1 to +1 range. */ demod_out = m_norm - s_norm; /* Try adding some Hysteresis. */ /* (Not to be confused with Hysteria.) */ if (demod_out > D->hysteresis) { demod_data = 1; } else if (demod_out < (- (D->hysteresis))) { demod_data = 0; } else { demod_data = D->slicer[subchan].prev_demod_data; } nudge_pll (chan, subchan, 0, demod_data, D); } else { int slice; for (slice=0; slicenum_slicers; slice++) { demod_data = m_amp > s_amp * space_gain[slice]; nudge_pll (chan, subchan, slice, demod_data, D); } } #if DEBUG4 if (chan == 0) { if (hdlc_rec_gathering (chan, subchan)) { char fname[30]; if (demod_log_fp == NULL) { seq++; snprintf (fname, sizeof(fname), "demod/%04d.csv", seq); if (seq == 1) mkdir ("demod", 0777); demod_log_fp = fopen (fname, "w"); text_color_set(DW_COLOR_DEBUG); dw_printf ("Starting demodulator log file %s\n", fname); fprintf (demod_log_fp, "Audio, Mark, Space, Demod, Data, Clock\n"); } fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f\n", fsam + 3.5, m_norm + 2, s_norm + 2, (m_norm - s_norm) / 2 + 1.5, demod_data ? .9 : .55, (D->data_clock_pll & 0x80000000) ? .1 : .45); } else { if (demod_log_fp != NULL) { fclose (demod_log_fp); demod_log_fp = NULL; } } } #endif } /* end demod_afsk_process_sample */ __attribute__((hot)) inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D) { /* * Finally, a PLL is used to sample near the centers of the data bits. * * D points to a demodulator for a channel/subchannel pair so we don't * have to keep recalculating it. * * D->data_clock_pll is a SIGNED 32 bit variable. * When it overflows from a large positive value to a negative value, we * sample a data bit from the demodulated signal. * * Ideally, the the demodulated signal transitions should be near * zero we we sample mid way between the transitions. * * Nudge the PLL by removing some small fraction from the value of * data_clock_pll, pushing it closer to zero. * * This adjustment will never change the sign so it won't cause * any erratic data bit sampling. * * If we adjust it too quickly, the clock will have too much jitter. * If we adjust it too slowly, it will take too long to lock on to a new signal. * * Be a little more agressive about adjusting the PLL * phase when searching for a signal. Don't change it as much when * locked on to a signal. * * I don't think the optimal value will depend on the audio sample rate * because this happens for each transition from the demodulator. */ D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; // Perform the add as unsigned to avoid signed overflow error. D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample)); //text_color_set(DW_COLOR_DEBUG); // dw_printf ("prev = %lx, new data clock pll = %lx\n" D->prev_d_c_pll, D->data_clock_pll); if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) { /* Overflow. */ hdlc_rec_bit (chan, subchan, slice, demod_data, 0, -1); } if (demod_data != D->slicer[slice].prev_demod_data) { if (hdlc_rec_gathering (chan, subchan, slice)) { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia); } else { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia); } } /* * Remember demodulator output so we can compare next time. */ D->slicer[slice].prev_demod_data = demod_data; } /* end nudge_pll */ #endif /* GEN_FFF */ /* end demod_afsk.c */ direwolf-1.5+dfsg/demod_afsk.h000066400000000000000000000003741347750676600164070ustar00rootroot00000000000000 /* demod_afsk.h */ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, int space_freq, char profile, struct demodulator_state_s *D); void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D); direwolf-1.5+dfsg/demod_psk.c000066400000000000000000000616321347750676600162570ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2016 John Langner, WB2OSZ // // 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, see . // //#define DEBUG1 1 /* display debugging info */ //#define DEBUG3 1 /* print carrier detect changes. */ //#define DEBUG4 1 /* capture PSK demodulator output to log files */ /*------------------------------------------------------------------ * * Module: demod_psk.c * * Purpose: Demodulator for Phase Shift Keying (PSK). * * This is my initial attempt at implementing a 2400 bps mode. * The MFJ-2400 & AEA PK232-2400 used V.26 / Bell 201 so I will follow that precedent. * * * Input: Audio samples from either a file or the "sound card." * * Outputs: Calls hdlc_rec_bit() for each bit demodulated. * * Current Status: New for Version 1.4. * * Don't know if this is correct and/or compatible with * other implementations. * There is a lot of stuff going on here with phase * shifting, gray code, bit order for the dibit, NRZI and * bit-stuffing for HDLC. Plenty of opportunity for * misinterpreting a protocol spec or just stupid mistakes. * * References: MFJ-2400 Product description and manual: * * http://www.mfjenterprises.com/Product.php?productid=MFJ-2400 * http://www.mfjenterprises.com/Downloads/index.php?productid=MFJ-2400&filename=MFJ-2400.pdf&company=mfj * * AEA had a 2400 bps packet modem, PK232-2400. * * http://www.repeater-builder.com/aea/pk232/pk232-2400-baud-dpsk-modem.pdf * * There was also a Kantronics KPC-2400 that had 2400 bps. * * http://www.brazoriacountyares.org/winlink-collection/TNC%20manuals/Kantronics/2400_modem_operators_guide@rgf.pdf * * * The MFJ and AEA both use the EXAR XR-2123 PSK modem chip. * The Kantronics has a P423 ??? * * Can't find the chip specs on the EXAR website so Google it. * * http://www.komponenten.es.aau.dk/fileadmin/komponenten/Data_Sheet/Linear/XR2123.pdf * * The XR-2123 implements the V.26 / Bell 201 standard: * * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.26-198811-I!!PDF-E&type=items * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.26bis-198811-I!!PDF-E&type=items * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.26ter-198811-I!!PDF-E&type=items * * "bis" and "ter" are from Latin for second and third. * I used the "ter" version which has phase shifts of 0, 90, 180, and 270 degrees. * * There are other references to an alternative B which uses other multiples of 45. * The XR-2123 data sheet mentions only multiples of 90. That's what I went with. * * The XR-2123 does not perform the scrambling as specified in V.26 so I wonder if * the vendors implemented it in software or just left it out. * I left out scrambling for now. Eventually, I'd like to get my hands on an old * 2400 bps TNC for compatibility testing. * * After getting QPSK working, it was not much more effort to add V.27 with 8 phases. * * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.27bis-198811-I!!PDF-E&type=items * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.27ter-198811-I!!PDF-E&type=items * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #include "audio.h" #include "tune.h" #include "fsk_demod_state.h" #include "fsk_gen_filter.h" #include "hdlc_rec.h" #include "textcolor.h" #include "demod_psk.h" #include "dsp.h" /* Add sample to buffer and shift the rest down. */ __attribute__((hot)) __attribute__((always_inline)) static inline void push_sample (float val, float *buff, int size) { memmove(buff+1,buff,(size-1)*sizeof(float)); buff[0] = val; } /* FIR filter kernel. */ __attribute__((hot)) __attribute__((always_inline)) static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size) { float sum = 0.0; int j; for (j=0; jms_filter_size * * Returns: None. * * Bugs: This doesn't do much error checking so don't give it * anything crazy. * *----------------------------------------------------------------*/ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char profile, struct demodulator_state_s *D) { int correct_baud; // baud is not same as bits/sec here! int carrier_freq; int j; memset (D, 0, sizeof(struct demodulator_state_s)); D->modem_type = modem_type; D->num_slicers = 1; // Haven't thought about this yet. Is it even applicable? #ifdef TUNE_PROFILE profile = TUNE_PROFILE; #endif if (modem_type == MODEM_QPSK) { correct_baud = bps / 2; // Originally I thought of scaling it to the data rate, // e.g. 2400 bps -> 1800 Hz, but decided to make it a // constant since it is the same for V.26 and V.27. carrier_freq = 1800; #if DEBUG1 dw_printf ("demod_psk_init QPSK (sample rate=%d, bps=%d, baud=%d, carrier=%d, profile=%c\n", samples_per_sec, bps, correct_baud, carrier_freq, profile); #endif switch (toupper(profile)) { case 'P': /* Self correlation technique. */ D->use_prefilter = 0; /* No bandpass filter. */ D->lpf_baud = 0.60; D->lp_filter_len_bits = 39. * 1200. / 44100.; D->lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.95; D->pll_searching_inertia = 0.50; break; case 'Q': /* Self correlation technique. */ D->use_prefilter = 1; /* Add a bandpass filter. */ D->prefilter_baud = 1.3; D->pre_filter_len_bits = 55. * 1200. / 44100.; D->pre_window = BP_WINDOW_COSINE; D->lpf_baud = 0.60; D->lp_filter_len_bits = 39. * 1200. / 44100.; D->lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.87; D->pll_searching_inertia = 0.50; break; default: text_color_set (DW_COLOR_ERROR); dw_printf ("Invalid demodulator profile %c for v.26 QPSK. Valid choices are P, Q, R, S. Using default.\n", profile); // fall thru. case 'R': /* Mix with local oscillator. */ D->psk_use_lo = 1; D->use_prefilter = 0; /* No bandpass filter. */ D->lpf_baud = 0.70; D->lp_filter_len_bits = 37. * 1200. / 44100.; D->lp_window = BP_WINDOW_TRUNCATED; D->pll_locked_inertia = 0.925; D->pll_searching_inertia = 0.50; break; case 'S': /* Mix with local oscillator. */ D->psk_use_lo = 1; D->use_prefilter = 1; /* Add a bandpass filter. */ D->prefilter_baud = 0.55; D->pre_filter_len_bits = 74. * 1200. / 44100.; D->pre_window = BP_WINDOW_FLATTOP; D->lpf_baud = 0.60; D->lp_filter_len_bits = 39. * 1200. / 44100.; D->lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.925; D->pll_searching_inertia = 0.50; break; } D->ms_filter_len_bits = 1.25; // Delay line > 13/12 * symbol period D->coffs = (int) round( (11.f / 12.f) * (float)samples_per_sec / (float)correct_baud ); D->boffs = (int) round( (float)samples_per_sec / (float)correct_baud ); D->soffs = (int) round( (13.f / 12.f) * (float)samples_per_sec / (float)correct_baud ); } else { correct_baud = bps / 3; carrier_freq = 1800; #if DEBUG1 dw_printf ("demod_psk_init 8-PSK (sample rate=%d, bps=%d, baud=%d, carrier=%d, profile=%c\n", samples_per_sec, bps, correct_baud, carrier_freq, profile); #endif switch (toupper(profile)) { case 'T': /* Self correlation technique. */ D->use_prefilter = 0; /* No bandpass filter. */ D->lpf_baud = 1.15; D->lp_filter_len_bits = 32. * 1200. / 44100.; D->lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.95; D->pll_searching_inertia = 0.50; break; case 'U': /* Self correlation technique. */ D->use_prefilter = 1; /* Add a bandpass filter. */ D->prefilter_baud = 0.9; D->pre_filter_len_bits = 21. * 1200. / 44100.; D->pre_window = BP_WINDOW_FLATTOP; D->lpf_baud = 1.15; D->lp_filter_len_bits = 32. * 1200. / 44100.; D->lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.87; D->pll_searching_inertia = 0.50; break; default: text_color_set (DW_COLOR_ERROR); dw_printf ("Invalid demodulator profile %c for v.27 8PSK. Valid choices are T, U, V, W. Using default.\n", profile); // fall thru. case 'V': /* Mix with local oscillator. */ D->psk_use_lo = 1; D->use_prefilter = 0; /* No bandpass filter. */ D->lpf_baud = 0.85; D->lp_filter_len_bits = 31. * 1200. / 44100.; D->lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.925; D->pll_searching_inertia = 0.50; break; case 'W': /* Mix with local oscillator. */ D->psk_use_lo = 1; D->use_prefilter = 1; /* Add a bandpass filter. */ D->prefilter_baud = 0.85; D->pre_filter_len_bits = 31. * 1200. / 44100.; D->pre_window = BP_WINDOW_COSINE; D->lpf_baud = 0.85; D->lp_filter_len_bits = 31. * 1200. / 44100.; D->lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.925; D->pll_searching_inertia = 0.50; break; } D->ms_filter_len_bits = 1.25; // Delay line > 10/9 * symbol period D->coffs = (int) round( (8.f / 9.f) * (float)samples_per_sec / (float)correct_baud ); D->boffs = (int) round( (float)samples_per_sec / (float)correct_baud ); D->soffs = (int) round( (10.f / 9.f) * (float)samples_per_sec / (float)correct_baud ); } if (D->psk_use_lo) { D->lo_step = (int) round( 256. * 256. * 256. * 256. * carrier_freq / (double)samples_per_sec); assert (MAX_FILTER_SIZE >= 256); for (j = 0; j < 256; j++) { D->m_sin_table[j] = sinf(2.f * (float)M_PI * j / 256.f); } } #ifdef TUNE_PRE_BAUD D->prefilter_baud = TUNE_PRE_BAUD; #endif #ifdef TUNE_PRE_WINDOW D->pre_window = TUNE_PRE_WINDOW; #endif #ifdef TUNE_LPF_BAUD D->lpf_baud = TUNE_LPF_BAUD; #endif #ifdef TUNE_LP_WINDOW D->lp_window = TUNE_LP_WINDOW; #endif #ifdef TUNE_HYST D->hysteresis = TUNE_HYST; #endif #if defined(TUNE_PLL_SEARCHING) D->pll_searching_inertia = TUNE_PLL_SEARCHING; #endif #if defined(TUNE_PLL_LOCKED) D->pll_locked_inertia = TUNE_PLL_LOCKED; #endif /* * Calculate constants used for timing. * The audio sample rate must be at least a few times the data rate. */ D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)correct_baud) / ((double)samples_per_sec)); /* * Convert number of symbol times to number of taps. */ D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)correct_baud ); D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)correct_baud ); D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)correct_baud ); #ifdef TUNE_PRE_FILTER_SIZE D->pre_filter_size = TUNE_PRE_FILTER_SIZE; #endif #ifdef TUNE_LP_FILTER_SIZE D->lp_filter_size = TUNE_LP_FILTER_SIZE; #endif if (D->pre_filter_size > MAX_FILTER_SIZE) { text_color_set (DW_COLOR_ERROR); dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size); dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE); exit (1); } if (D->ms_filter_size > MAX_FILTER_SIZE) { text_color_set (DW_COLOR_ERROR); dw_printf ("Calculated filter size of %d is too large.\n", D->ms_filter_size); dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE); exit (1); } if (D->lp_filter_size > MAX_FILTER_SIZE) { text_color_set (DW_COLOR_ERROR); dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size); dw_printf ("Decrease the audio sample rate or increase the baud rate or\n"); dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE); exit (1); } /* * Optionally apply a bandpass ("pre") filter to attenuate * frequencies outside the range of interest. */ if (D->use_prefilter) { float f1, f2; f1 = carrier_freq - D->prefilter_baud * correct_baud; f2 = carrier_freq + D->prefilter_baud * correct_baud; #if 0 text_color_set(DW_COLOR_DEBUG); dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", (double)f1, (double)f2); #endif if (f1 <= 0) { text_color_set (DW_COLOR_ERROR); dw_printf ("Prefilter of %.0f to %.0f Hz doesn't make sense.\n", (double)f1, (double)f2); f1 = 10; } f1 = f1 / (float)samples_per_sec; f2 = f2 / (float)samples_per_sec; gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window); } /* * Now the lowpass filter. */ float fc = correct_baud * D->lpf_baud / (float)samples_per_sec; gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); /* * No point in having multiple numbers for signal level. */ D->alevel_mark_peak = -1; D->alevel_space_peak = -1; } /* demod_psk_init */ /*------------------------------------------------------------------- * * Name: demod_psk_process_sample * * Purpose: (1) Demodulate the psk signal into I & Q components. * (2) Recover clock and sample data at the right time. * (3) Produce two bits per symbol based on phase change from previous. * * Inputs: chan - Audio channel. 0 for left, 1 for right. * subchan - modem of the channel. * sam - One sample of audio. * Should be in range of -32768 .. 32767. * * Outputs: For each recovered data bit, we call: * * hdlc_rec (channel, demodulated_bit); * * to decode HDLC frames from the stream of bits. * * Returns: None * * Descripion: All the literature, that I could find, described mixing * with a local oscillator. First we multiply the input by * cos and sin then low pass filter each. This gives us * correlation to the different phases. The signs of these two * results produces two data bits per symbol period. * * An 1800 Hz local oscillator was derived from the 1200 Hz * PLL used to sample the data. * This worked wonderfully for the ideal condition where * we start off with the proper phase and all the timing * is perfect. However, when random delays were added * before the frame, the PLL would lock on only about * half the time. * * Late one night, it dawned on me that there is no * need for a local oscillator (LO) at the carrier frequency. * Simply correlate the signal with the previous symbol, * phase shifted by + and - 45 degrees. * The code is much simpler and very reliable. * * Later, I realized it was not necessary to synchronize the LO * because we only care about the phase shift between symbols. * * This works better under noisy conditions because we are * including the noise from only the current symbol and not * the previous one. * * Finally, once we know how to distinguish 4 different phases, * it is not much effort to use 8 phases to double the bit rate. * *--------------------------------------------------------------------*/ inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct demodulator_state_s *D); __attribute__((hot)) void demod_psk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D) { float fsam; float sam_x_cos, sam_x_sin; float I, Q; int demod_phase_shift; // Phase shift relative to previous symbol. // range 0-3, 1 unit for each 90 degrees. int slice = 0; #if DEBUG4 static FILE *demod_log_fp = NULL; static int log_file_seq = 0; /* Part of log file name */ #endif assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); /* Scale to nice number for plotting during debug. */ fsam = sam / 16384.0f; /* * Optional bandpass filter before the phase detector. */ if (D->use_prefilter) { push_sample (fsam, D->raw_cb, D->pre_filter_size); fsam = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size); } if (D->psk_use_lo) { float a, delta; int id; /* * Mix with local oscillator to obtain phase. * The absolute phase doesn't matter. * We are just concerned with the change since the previous symbol. */ sam_x_cos = fsam * D->m_sin_table[((D->lo_phase >> 24) + 64) & 0xff]; sam_x_sin = fsam * D->m_sin_table[(D->lo_phase >> 24) & 0xff]; push_sample (sam_x_cos, D->m_amp_cb, D->lp_filter_size); I = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size); push_sample (sam_x_sin, D->s_amp_cb, D->lp_filter_size); Q = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size); a = my_atan2f(I,Q); push_sample (a, D->ms_in_cb, D->ms_filter_size); delta = a - D->ms_in_cb[D->boffs]; /* 256 units/cycle makes modulo processing easier. */ /* Make sure it is positive before truncating to integer. */ id = ((int)((delta / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff; if (D->modem_type == MODEM_QPSK) { demod_phase_shift = ((id + 32) >> 6) & 0x3; } else { demod_phase_shift = ((id + 16) >> 5) & 0x7; } nudge_pll (chan, subchan, slice, demod_phase_shift, D); D->lo_phase += D->lo_step; } else { /* * Correlate with previous symbol. We are looking for the phase shift. */ push_sample (fsam, D->ms_in_cb, D->ms_filter_size); sam_x_cos = fsam * D->ms_in_cb[D->coffs]; sam_x_sin = fsam * D->ms_in_cb[D->soffs]; push_sample (sam_x_cos, D->m_amp_cb, D->lp_filter_size); I = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size); push_sample (sam_x_sin, D->s_amp_cb, D->lp_filter_size); Q = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size); if (D->modem_type == MODEM_QPSK) { #if 1 // Speed up special case. if (I > 0) { if (Q > 0) demod_phase_shift = 0; /* 0 to 90 degrees, etc. */ else demod_phase_shift = 1; } else { if (Q > 0) demod_phase_shift = 3; else demod_phase_shift = 2; } #else a = my_atan2f(I,Q); int id = ((int)((a / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff; // 128 compensates for 180 degree phase shift due // to 1 1/2 carrier cycles per symbol period. demod_phase_shift = ((id + 128) >> 6) & 0x3; #endif } else { float a; int idelta; a = my_atan2f(I,Q); idelta = ((int)((a / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff; // 32 (90 degrees) compensates for 1800 carrier vs. 1800 baud. // 16 is to set threshold between constellation points. demod_phase_shift = ((idelta - 32 - 16) >> 5) & 0x7; } nudge_pll (chan, subchan, slice, demod_phase_shift, D); } #if DEBUG4 if (chan == 0) { if (1) { //if (hdlc_rec_gathering (chan, subchan, slice)) { char fname[30]; if (demod_log_fp == NULL) { log_file_seq++; snprintf (fname, sizeof(fname), "demod/%04d.csv", log_file_seq); //if (log_file_seq == 1) mkdir ("demod", 0777); if (log_file_seq == 1) mkdir ("demod"); demod_log_fp = fopen (fname, "w"); text_color_set(DW_COLOR_DEBUG); dw_printf ("Starting demodulator log file %s\n", fname); fprintf (demod_log_fp, "Audio, sin, cos, *cos, *sin, I, Q, phase, Clock\n"); } fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n", fsam + 2, - D->ms_in_cb[D->soffs] + 6, - D->ms_in_cb[D->coffs] + 6, sam_x_cos + 8, sam_x_sin + 10, 2 * I + 12, 2 * Q + 12, demod_phase_shift * 2. / 3. + 14., (D->slicer[slice].data_clock_pll & 0x80000000) ? .5 : .0); fflush (demod_log_fp); } else { if (demod_log_fp != NULL) { fclose (demod_log_fp); demod_log_fp = NULL; } } } #endif } /* end demod_psk_process_sample */ static const int phase_to_gray_v26[4] = {0, 1, 3, 2}; static const int phase_to_gray_v27[8] = {1, 0, 2, 3, 7, 6, 4, 5}; __attribute__((hot)) inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct demodulator_state_s *D) { /* * Finally, a PLL is used to sample near the centers of the data bits. * * D points to a demodulator for a channel/subchannel pair so we don't * have to keep recalculating it. * * D->data_clock_pll is a SIGNED 32 bit variable. * When it overflows from a large positive value to a negative value, we * sample a data bit from the demodulated signal. * * Ideally, the the demodulated signal transitions should be near * zero we we sample mid way between the transitions. * * Nudge the PLL by removing some small fraction from the value of * data_clock_pll, pushing it closer to zero. * * This adjustment will never change the sign so it won't cause * any erratic data bit sampling. * * If we adjust it too quickly, the clock will have too much jitter. * If we adjust it too slowly, it will take too long to lock on to a new signal. * * Be a little more agressive about adjusting the PLL * phase when searching for a signal. * Don't change it as much when locked on to a signal. * * I don't think the optimal value will depend on the audio sample rate * because this happens for each transition from the demodulator. */ D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; // Perform the add as unsigned to avoid signed overflow error. D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample)); if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll >= 0) { /* Overflow of PLL counter. */ /* This is where we sample the data. */ if (D->modem_type == MODEM_QPSK) { int gray = phase_to_gray_v26[ demod_bits ]; #if DEBUG4 text_color_set(DW_COLOR_DEBUG); dw_printf ("a=%.2f deg, delta=%.2f deg, phaseshift=%d, bits= %d %d \n", a * 360 / (2*M_PI), delta * 360 / (2*M_PI), demod_bits, (gray >> 1) & 1, gray & 1); //dw_printf ("phaseshift=%d, bits= %d %d \n", demod_bits, (gray >> 1) & 1, gray & 1); #endif hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, -1); hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, -1); } else { int gray = phase_to_gray_v27[ demod_bits ]; hdlc_rec_bit (chan, subchan, slice, (gray >> 2) & 1, 0, -1); hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, -1); hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, -1); } } /* * If demodulated data has changed, * Pull the PLL phase closer to zero. * Use "floor" instead of simply casting so the sign won't flip. * For example if we had -0.7 we want to end up with -1 rather than 0. */ // TODO: demod_9600 has an improved technique. Would it help us here? if (demod_bits != D->slicer[slice].prev_demod_data) { if (hdlc_rec_gathering (chan, subchan, slice)) { D->slicer[slice].data_clock_pll = (int)floorf((float)(D->slicer[slice].data_clock_pll) * D->pll_locked_inertia); } else { D->slicer[slice].data_clock_pll = (int)floorf((float)(D->slicer[slice].data_clock_pll) * D->pll_searching_inertia); } } /* * Remember demodulator output so we can compare next time. */ D->slicer[slice].prev_demod_data = demod_bits; } /* end nudge_pll */ /* end demod_psk.c */ direwolf-1.5+dfsg/demod_psk.h000066400000000000000000000003571347750676600162610ustar00rootroot00000000000000 /* demod_psk.h */ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char profile, struct demodulator_state_s *D); void demod_psk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D); direwolf-1.5+dfsg/digipeater.c000066400000000000000000000564631347750676600164350ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Name: digipeater.c * * Purpose: Act as an APRS digital repeater. * Similar cdigipeater.c is for connected mode. * * * Description: Decide whether the specified packet should * be digipeated and make necessary modifications. * * * References: APRS Protocol Reference, document version 1.0.1 * * http://www.aprs.org/doc/APRS101.PDF * * APRS SPEC Addendum 1.1 * * http://www.aprs.org/aprs11.html * * APRS SPEC Addendum 1.2 * * http://www.aprs.org/aprs12.html * * "The New n-N Paradigm" * * http://www.aprs.org/fix14439.html * * Preemptive Digipeating (new in version 0.8) * * http://www.aprs.org/aprs12/preemptive-digipeating.txt * *------------------------------------------------------------------*/ #define DIGIPEATER_C #include "direwolf.h" #include #include #include #include #include /* for isdigit, isupper */ #include "regex.h" #include #include "ax25_pad.h" #include "digipeater.h" #include "textcolor.h" #include "dedupe.h" #include "tq.h" #include "pfilter.h" static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt, char *type_filter); /* * Keep pointer to configuration options. * Set by digipeater_init and used later. */ static struct audio_s *save_audio_config_p; static struct digi_config_s *save_digi_config_p; /* * Maintain count of packets digipeated for each combination of from/to channel. */ static int digi_count[MAX_CHANS][MAX_CHANS]; int digipeater_get_count (int from_chan, int to_chan) { return (digi_count[from_chan][to_chan]); } /*------------------------------------------------------------------------------ * * Name: digipeater_init * * Purpose: Initialize with stuff from configuration file. * * Inputs: p_audio_config - Configuration for audio channels. * * p_digi_config - Digipeater configuration details. * * Outputs: Save pointers to configuration for later use. * * Description: Called once at application startup time. * *------------------------------------------------------------------------------*/ void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_digi_config) { save_audio_config_p = p_audio_config; save_digi_config_p = p_digi_config; dedupe_init (p_digi_config->dedupe_time); } /*------------------------------------------------------------------------------ * * Name: digipeater * * Purpose: Re-transmit packet if it matches the rules. * * Inputs: chan - Radio channel where it was received. * * pp - Packet object. * * Returns: None. * * *------------------------------------------------------------------------------*/ void digipeater (int from_chan, packet_t pp) { int to_chan; // dw_printf ("digipeater()\n"); assert (from_chan >= 0 && from_chan < MAX_CHANS); if ( ! save_audio_config_p->achan[from_chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("digipeater: Did not expect to receive on invalid channel %d.\n", from_chan); } /* * First pass: Look at packets being digipeated to same channel. * * We want these to get out quickly. */ for (to_chan=0; to_chanenabled[from_chan][to_chan]) { if (to_chan == from_chan) { packet_t result; result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, save_audio_config_p->achan[to_chan].mycall, &save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], to_chan, save_digi_config_p->preempt[from_chan][to_chan], save_digi_config_p->filter_str[from_chan][to_chan]); if (result != NULL) { dedupe_remember (pp, to_chan); tq_append (to_chan, TQ_PRIO_0_HI, result); digi_count[from_chan][to_chan]++; } } } } /* * Second pass: Look at packets being digipeated to different channel. * * These are lower priority */ for (to_chan=0; to_chanenabled[from_chan][to_chan]) { if (to_chan != from_chan) { packet_t result; result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, save_audio_config_p->achan[to_chan].mycall, &save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], to_chan, save_digi_config_p->preempt[from_chan][to_chan], save_digi_config_p->filter_str[from_chan][to_chan]); if (result != NULL) { dedupe_remember (pp, to_chan); tq_append (to_chan, TQ_PRIO_1_LO, result); digi_count[from_chan][to_chan]++; } } } } } /* end digipeater */ /*------------------------------------------------------------------------------ * * Name: digipeat_match * * Purpose: A simple digipeater for APRS. * * Input: pp - Pointer to a packet object. * * mycall_rec - Call of my station, with optional SSID, * associated with the radio channel where the * packet was received. * * mycall_xmit - Call of my station, with optional SSID, * associated with the radio channel where the * packet is to be transmitted. Could be the same as * mycall_rec or different. * * alias - Compiled pattern for my station aliases or * "trapping" (repeating only once). * * wide - Compiled pattern for normal WIDEn-n digipeating. * * to_chan - Channel number that we are transmitting to. * This is needed to maintain a history for * removing duplicates during specified time period. * * preempt - Option for "preemptive" digipeating. * * filter_str - Filter expression string or NULL. * * Returns: Packet object for transmission or NULL. * The original packet is not modified. (with one exception, probably obsolete) * We make a copy and return that modified copy! * This is very important because we could digipeat from one channel to many. * * Description: The packet will be digipeated if the next unused digipeater * field matches one of the following: * * - mycall_rec * - udigi list (only once) * - wide list (usual wideN-N rules) * *------------------------------------------------------------------------------*/ #define OBSOLETE14 1 #ifndef OBSOLETE14 static char *dest_ssid_path[16] = { "", /* Use VIA path */ "WIDE1-1", "WIDE2-2", "WIDE3-3", "WIDE4-4", "WIDE5-5", "WIDE6-6", "WIDE7-7", "WIDE1-1", /* North */ "WIDE1-1", /* South */ "WIDE1-1", /* East */ "WIDE1-1", /* West */ "WIDE2-2", /* North */ "WIDE2-2", /* South */ "WIDE2-2", /* East */ "WIDE2-2" }; /* West */ #endif static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt, char *filter_str) { char source[AX25_MAX_ADDR_LEN]; int ssid; int r; char repeater[AX25_MAX_ADDR_LEN]; int err; char err_msg[100]; /* * First check if filtering has been configured. */ if (filter_str != NULL) { if (pfilter(from_chan, to_chan, filter_str, pp, 1) != 1) { return(NULL); } } /* * The spec says: * * The SSID in the Destination Address field of all packets is coded to specify * the APRS digipeater path. * If the Destination Address SSID is -0, the packet follows the standard AX.25 * digipeater ("VIA") path contained in the Digipeater Addresses field of the * AX.25 frame. * If the Destination Address SSID is non-zero, the packet follows one of 15 * generic APRS digipeater paths. * * * What if this is non-zero but there is also a digipeater path? * I will ignore this if there is an explicit path. * * Note that this modifies the input. But only once! * Otherwise we don't want to modify the input because this could be called multiple times. */ #ifndef OBSOLETE14 // Took it out in 1.4 if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) { ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]); ax25_set_ssid(pp, AX25_DESTINATION, 0); /* Continue with general case, below. */ } #endif /* * Find the first repeater station which doesn't have "has been repeated" set. * * r = index of the address position in the frame. */ r = ax25_get_first_not_repeated(pp); if (r < AX25_REPEATER_1) { return (NULL); } ax25_get_addr_with_ssid(pp, r, repeater); ssid = ax25_get_ssid(pp, r); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("First unused digipeater is %s, ssid=%d\n", repeater, ssid); #endif /* * First check for explicit use of my call, including SSID. * Someone might explicitly specify a particular path for testing purposes. * This will bypass the usual checks for duplicates and my call in the source. * * In this case, we don't check the history so it would be possible * to have a loop (of limited size) if someone constructed the digipeater paths * correctly. I would expect it only for testing purposes. */ if (strcmp(repeater, mycall_rec) == 0) { packet_t result; result = ax25_dup (pp); assert (result != NULL); /* If using multiple radio channels, they */ /* could have different calls. */ ax25_set_addr (result, r, mycall_xmit); ax25_set_h (result, r); return (result); } /* * Don't digipeat my own. Fixed in 1.4 dev H. * Alternatively we might feed everything transmitted into * dedupe_remember rather than only frames out of digipeater. */ ax25_get_addr_with_ssid(pp, AX25_SOURCE, source); if (strcmp(source, mycall_rec) == 0) { return (NULL); } /* * Next try to avoid retransmitting redundant information. * Duplicates are detected by comparing only: * - source * - destination * - info part * - but not the via path. (digipeater addresses) * A history is kept for some amount of time, typically 30 seconds. * For efficiency, only a checksum, rather than the complete fields * might be kept but the result is the same. * Packets transmitted recently will not be transmitted again during * the specified time period. * */ if (dedupe_check(pp, to_chan)) { //#if DEBUG /* Might be useful if people are wondering why */ /* some are not repeated. Might also cause confusion. */ text_color_set(DW_COLOR_INFO); dw_printf ("Digipeater: Drop redundant packet to channel %d.\n", to_chan); //#endif return NULL; } /* * For the alias pattern, we unconditionally digipeat it once. * i.e. Just replace it with MYCALL. * * My call should be an implied member of this set. * In this implementation, we already caught it further up. */ err = regexec(alias,repeater,0,NULL,0); if (err == 0) { packet_t result; result = ax25_dup (pp); assert (result != NULL); ax25_set_addr (result, r, mycall_xmit); ax25_set_h (result, r); return (result); } else if (err != REG_NOMATCH) { regerror(err, alias, err_msg, sizeof(err_msg)); text_color_set (DW_COLOR_ERROR); dw_printf ("%s\n", err_msg); } /* * If preemptive digipeating is enabled, try matching my call * and aliases against all remaining unused digipeaters. */ if (preempt != PREEMPT_OFF) { int r2; for (r2 = r+1; r2 < ax25_get_num_addr(pp); r2++) { char repeater2[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid(pp, r2, repeater2); //text_color_set (DW_COLOR_DEBUG); //dw_printf ("test match %d %s\n", r2, repeater2); if (strcmp(repeater2, mycall_rec) == 0 || regexec(alias,repeater2,0,NULL,0) == 0) { packet_t result; result = ax25_dup (pp); assert (result != NULL); ax25_set_addr (result, r2, mycall_xmit); ax25_set_h (result, r2); switch (preempt) { case PREEMPT_DROP: /* remove all prior */ while (r2 > AX25_REPEATER_1) { ax25_remove_addr (result, r2-1); r2--; } break; case PREEMPT_MARK: r2--; while (r2 >= AX25_REPEATER_1 && ax25_get_h(result,r2) == 0) { ax25_set_h (result, r2); r2--; } break; case PREEMPT_TRACE: /* remove prior unused */ default: while (r2 > AX25_REPEATER_1 && ax25_get_h(result,r2-1) == 0) { ax25_remove_addr (result, r2-1); r2--; } break; } // Idea: Here is an interesting idea for a new option. REORDER? // The preemptive digipeater could move its call after the (formerly) last used digi field // and preserve all the unused fields after that. The list of used addresses would // accurately record the journey taken by the packet. // https://groups.yahoo.com/neo/groups/aprsisce/conversations/topics/31935 // > I was wishing for a non-marking preemptive digipeat so that the original packet would be left intact // > or maybe something like WIDE1-1,WIDE2-1,KJ4OVQ-9 becoming KJ4OVQ-9*,WIDE1-1,WIDE2-1. return (result); } } } /* * For the wide pattern, we check the ssid and decrement it. */ err = regexec(wide,repeater,0,NULL,0); if (err == 0) { /* * If ssid == 1, we simply replace the repeater with my call and * mark it as being used. * * Otherwise, if ssid in range of 2 to 7, * Decrement y and don't mark repeater as being used. * Insert own call ahead of this one for tracing if we don't already have the * maximum number of repeaters. */ if (ssid == 1) { packet_t result; result = ax25_dup (pp); assert (result != NULL); ax25_set_addr (result, r, mycall_xmit); ax25_set_h (result, r); return (result); } if (ssid >= 2 && ssid <= 7) { packet_t result; result = ax25_dup (pp); assert (result != NULL); ax25_set_ssid(result, r, ssid-1); // should be at least 1 if (ax25_get_num_repeaters(pp) < AX25_MAX_REPEATERS) { ax25_insert_addr (result, r, mycall_xmit); ax25_set_h (result, r); } return (result); } } else if (err != REG_NOMATCH) { regerror(err, wide, err_msg, sizeof(err_msg)); text_color_set (DW_COLOR_ERROR); dw_printf ("%s\n", err_msg); } /* * Don't repeat it if we get here. */ return (NULL); } /*------------------------------------------------------------------------------ * * Name: digi_regen * * Purpose: Send regenerated copy of what we received. * * Inputs: chan - Radio channel where it was received. * * pp - Packet object. * * Returns: None. * * Description: TODO... * * Initial reports were favorable. * Should document what this is all about if there is still interest... * *------------------------------------------------------------------------------*/ void digi_regen (int from_chan, packet_t pp) { int to_chan; packet_t result; // dw_printf ("digi_regen()\n"); assert (from_chan >= 0 && from_chan < MAX_CHANS); for (to_chan=0; to_chanregen[from_chan][to_chan]) { result = ax25_dup (pp); if (result != NULL) { // TODO: if AX.25 and has been digipeated, put in HI queue? tq_append (to_chan, TQ_PRIO_1_LO, result); } } } } /* end dig_regen */ /*------------------------------------------------------------------------- * * Name: main * * Purpose: Standalone test case for this funtionality. * * Usage: make -f Makefile. dtest * ./dtest * *------------------------------------------------------------------------*/ #if DIGITEST static char mycall[] = "WB2OSZ-9"; static regex_t alias_re; static regex_t wide_re; static int failed; static enum preempt_e preempt = PREEMPT_OFF; static void test (char *in, char *out) { packet_t pp, result; char rec[256]; char xmit[256]; unsigned char *pinfo; int info_len; unsigned char frame[AX25_MAX_PACKET_LEN]; int frame_len; alevel_t alevel; dw_printf ("\n"); /* * As an extra test, change text to internal format back to * text again to make sure it comes out the same. */ pp = ax25_from_text (in, 1); assert (pp != NULL); ax25_format_addrs (pp, rec); info_len = ax25_get_info (pp, &pinfo); (void)info_len; strlcat (rec, (char*)pinfo, sizeof(rec)); if (strcmp(in, rec) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Text/internal/text error-1 %s -> %s\n", in, rec); } /* * Just for more fun, write as the frame format, read it back * again, and make sure it is still the same. */ frame_len = ax25_pack (pp, frame); ax25_delete (pp); alevel.rec = 50; alevel.mark = 50; alevel.space = 50; pp = ax25_from_frame (frame, frame_len, alevel); assert (pp != NULL); ax25_format_addrs (pp, rec); info_len = ax25_get_info (pp, &pinfo); strlcat (rec, (char*)pinfo, sizeof(rec)); if (strcmp(in, rec) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("internal/frame/internal/text error-2 %s -> %s\n", in, rec); } /* * On with the digipeater test. */ text_color_set(DW_COLOR_REC); dw_printf ("Rec\t%s\n", rec); //TODO: Add filtering to test. // V result = digipeat_match (0, pp, mycall, mycall, &alias_re, &wide_re, 0, preempt, NULL); if (result != NULL) { dedupe_remember (result, 0); ax25_format_addrs (result, xmit); info_len = ax25_get_info (result, &pinfo); strlcat (xmit, (char*)pinfo, sizeof(xmit)); ax25_delete (result); } else { strlcpy (xmit, "", sizeof(xmit)); } text_color_set(DW_COLOR_XMIT); dw_printf ("Xmit\t%s\n", xmit); if (strcmp(xmit, out) == 0) { text_color_set(DW_COLOR_INFO); dw_printf ("OK\n"); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Expect\t%s\n", out); failed++; } dw_printf ("\n"); } int main (int argc, char *argv[]) { int e; failed = 0; char message[256]; dedupe_init (4); /* * Compile the patterns. */ e = regcomp (&alias_re, "^WIDE[4-7]-[1-7]|CITYD$", REG_EXTENDED|REG_NOSUB); if (e != 0) { regerror (e, &alias_re, message, sizeof(message)); text_color_set(DW_COLOR_ERROR); dw_printf ("\n%s\n\n", message); exit (1); } e = regcomp (&wide_re, "^WIDE[1-7]-[1-7]$|^TRACE[1-7]-[1-7]$|^MA[1-7]-[1-7]$", REG_EXTENDED|REG_NOSUB); if (e != 0) { regerror (e, &wide_re, message, sizeof(message)); text_color_set(DW_COLOR_ERROR); dw_printf ("\n%s\n\n", message); exit (1); } /* * Let's start with the most basic cases. */ test ( "W1ABC>TEST01,TRACE3-3:", "W1ABC>TEST01,WB2OSZ-9*,TRACE3-2:"); test ( "W1ABC>TEST02,WIDE3-3:", "W1ABC>TEST02,WB2OSZ-9*,WIDE3-2:"); test ( "W1ABC>TEST03,WIDE3-2:", "W1ABC>TEST03,WB2OSZ-9*,WIDE3-1:"); test ( "W1ABC>TEST04,WIDE3-1:", "W1ABC>TEST04,WB2OSZ-9*:"); /* * Look at edge case of maximum number of digipeaters. */ test ( "W1ABC>TEST11,R1,R2,R3,R4,R5,R6*,WIDE3-3:", "W1ABC>TEST11,R1,R2,R3,R4,R5,R6,WB2OSZ-9*,WIDE3-2:"); test ( "W1ABC>TEST12,R1,R2,R3,R4,R5,R6,R7*,WIDE3-3:", "W1ABC>TEST12,R1,R2,R3,R4,R5,R6,R7*,WIDE3-2:"); test ( "W1ABC>TEST13,R1,R2,R3,R4,R5,R6,R7*,WIDE3-1:", "W1ABC>TEST13,R1,R2,R3,R4,R5,R6,R7,WB2OSZ-9*:"); /* * "Trap" large values of "N" by repeating only once. */ test ( "W1ABC>TEST21,WIDE4-4:", "W1ABC>TEST21,WB2OSZ-9*:"); test ( "W1ABC>TEST22,WIDE7-7:", "W1ABC>TEST22,WB2OSZ-9*:"); /* * Only values in range of 1 thru 7 are valid. */ test ( "W1ABC>TEST31,WIDE0-4:", ""); test ( "W1ABC>TEST32,WIDE8-4:", ""); test ( "W1ABC>TEST33,WIDE2:", ""); /* * and a few cases actually heard. */ test ( "WA1ENO>FN42ND,W1MV-1*,WIDE3-2:", "WA1ENO>FN42ND,W1MV-1,WB2OSZ-9*,WIDE3-1:"); test ( "W1ON-3>BEACON:", ""); test ( "W1CMD-9>TQ3Y8P,N1RCW-2,W1CLA-1,N8VIM,WIDE2*:", ""); test ( "W1CLA-1>APX192,W1GLO-1,WIDE2*:", ""); test ( "AC1U-9>T2TX4S,AC1U,WIDE1,N8VIM*,WIDE2-1:", "AC1U-9>T2TX4S,AC1U,WIDE1,N8VIM,WB2OSZ-9*:"); /* * Someone is still using the old style and will probably be disappointed. */ test ( "K1CPD-1>T2SR5R,RELAY*,WIDE,WIDE,SGATE,WIDE:", ""); /* * Change destination SSID to normal digipeater if none specified. (Obsolete, removed.) */ test ( "W1ABC>TEST-3:", #ifndef OBSOLETE14 "W1ABC>TEST,WB2OSZ-9*,WIDE3-2:"); #else ""); #endif test ( "W1DEF>TEST-3,WIDE2-2:", "W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:"); /* * Drop duplicates within specified time interval. * Only the first 1 of 3 should be retransmitted. * The 4th case might be controversial. */ test ( "W1XYZ>TEST,R1*,WIDE3-2:info1", "W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info1"); test ( "W1XYZ>TEST,R2*,WIDE3-2:info1", ""); test ( "W1XYZ>TEST,R3*,WIDE3-2:info1", ""); test ( "W1XYZ>TEST,R1*,WB2OSZ-9:has explicit routing", "W1XYZ>TEST,R1,WB2OSZ-9*:has explicit routing"); /* * Allow same thing after adequate time. */ SLEEP_SEC (5); test ( "W1XYZ>TEST,R3*,WIDE3-2:info1", "W1XYZ>TEST,R3,WB2OSZ-9*,WIDE3-1:info1"); /* * Although source and destination match, the info field is different. */ test ( "W1XYZ>TEST,R1*,WIDE3-2:info4", "W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info4"); test ( "W1XYZ>TEST,R1*,WIDE3-2:info5", "W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info5"); test ( "W1XYZ>TEST,R1*,WIDE3-2:info6", "W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info6"); /* * New in version 0.8. * "Preemptive" digipeating looks ahead beyond the first unused digipeater. */ test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:off", ""); preempt = PREEMPT_DROP; test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:drop", "W1ABC>TEST11,WB2OSZ-9*,CITYE:drop"); preempt = PREEMPT_MARK; test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:mark1", "W1ABC>TEST11,CITYA,CITYB,CITYC,WB2OSZ-9*,CITYE:mark1"); test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,WB2OSZ-9,CITYE:mark2", "W1ABC>TEST11,CITYA,CITYB,CITYC,WB2OSZ-9*,CITYE:mark2"); preempt = PREEMPT_TRACE; test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:trace1", "W1ABC>TEST11,CITYA,WB2OSZ-9*,CITYE:trace1"); test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD:trace2", "W1ABC>TEST11,CITYA,WB2OSZ-9*:trace2"); test ( "W1ABC>TEST11,CITYB,CITYC,CITYD:trace3", "W1ABC>TEST11,WB2OSZ-9*:trace3"); test ( "W1ABC>TEST11,CITYA*,CITYW,CITYX,CITYY,CITYZ:nomatch", ""); /* * Did I miss any cases? * Yes. Don't retransmit my own. 1.4H */ test ( "WB2OSZ-7>TEST14,WIDE1-1,WIDE1-1:stuff", "WB2OSZ-7>TEST14,WB2OSZ-9*,WIDE1-1:stuff"); test ( "WB2OSZ-9>TEST14,WIDE1-1,WIDE1-1:from myself", ""); test ( "WB2OSZ-9>TEST14,WIDE1-1*,WB2OSZ-9:from myself but explicit routing", "WB2OSZ-9>TEST14,WIDE1-1,WB2OSZ-9*:from myself but explicit routing"); test ( "WB2OSZ-15>TEST14,WIDE1-1,WIDE1-1:stuff", "WB2OSZ-15>TEST14,WB2OSZ-9*,WIDE1-1:stuff"); if (failed == 0) { dw_printf ("SUCCESS -- All digipeater tests passed.\n"); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - %d digipeater tests failed.\n", failed); } return ( failed != 0 ); } /* end main */ #endif /* if DIGITEST */ /* end digipeater.c */ direwolf-1.5+dfsg/digipeater.h000066400000000000000000000031611347750676600164250ustar00rootroot00000000000000 #ifndef DIGIPEATER_H #define DIGIPEATER_H 1 #include "regex.h" #include "direwolf.h" /* for MAX_CHANS */ #include "ax25_pad.h" /* for packet_t */ #include "audio.h" /* for radio channel properties */ /* * Information required for digipeating. * * The configuration file reader fills in this information * and it is passed to digipeater_init at application start up time. */ struct digi_config_s { int dedupe_time; /* Don't digipeat duplicate packets */ /* within this number of seconds. */ #define DEFAULT_DEDUPE 30 /* * Rules for each of the [from_chan][to_chan] combinations. */ regex_t alias[MAX_CHANS][MAX_CHANS]; regex_t wide[MAX_CHANS][MAX_CHANS]; int enabled[MAX_CHANS][MAX_CHANS]; enum preempt_e { PREEMPT_OFF, PREEMPT_DROP, PREEMPT_MARK, PREEMPT_TRACE } preempt[MAX_CHANS][MAX_CHANS]; char *filter_str[MAX_CHANS+1][MAX_CHANS+1]; // NULL or optional Packet Filter strings such as "t/m". // Notice the size of arrays is one larger than normal. // That extra position is for the IGate. int regen[MAX_CHANS][MAX_CHANS]; // Regenerate packet. // Sort of like digipeating but passed along unchanged. }; /* * Call once at application start up time. */ extern void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_digi_config); /* * Call this for each packet received. * Suitable packets will be queued for transmission. */ extern void digipeater (int from_chan, packet_t pp); void digi_regen (int from_chan, packet_t pp); /* Make statistics available. */ int digipeater_get_count (int from_chan, int to_chan); #endif /* end digipeater.h */ direwolf-1.5+dfsg/direwolf-block-diagram.png000066400000000000000000000761641347750676600211670ustar00rootroot00000000000000‰PNG  IHDR[7 „•äsRGB®ÎégAMA± üa pHYsÃÃÇo¨d| IDATx^íÝïÏ4Ë]ç÷[Q)ÏŽ”y²òDà‡èŠÖÒ‚",…EQ„"/º¬ ‹5–`­;aaoŽ.ì ¢ñÙ‘Ln6>²7ö:6ë=[¶—sËØË‘mp„ ‹×A“þVWÏtUWw«¦jº«úý’Z÷=3==}U}»ê3=¿P a   Â@A„-€‚[¶ "lDØ(ˆ°Pa   Â@A„-€‚[¶ "lDØp(~á¥Ó[Ÿy|úž×ýâé»~ømÎòƒ?ó«§_xö§ßùȧNßøË¿¶÷Èë÷?ó'§_yïGO?ñó¿9yüW¿öéÓ›ßùþ¢àö[š÷­ûíÓ»~ëÃ&`ýƒ‡÷͆™/|ék§÷~ðLûŸúåÓoþ“aî{-Ù†,y| Yò ]>YïÿÌ9 ʺŸzòE{+€Z¶4íãøÇ§ùÙ_;ýúû/êl‘¬ûð܇ÌÙ.9–J‚7 X±g«$ýøÏ=gζq¦ ¨a @“ä,‘œ!zÃÛßcÎX¥úÒW^1gÃ$,Åp$AIö᫯|Ó^›FζI蓳^êCØÐ$ 9Rr‘°¥ \ÐälZΗ%° ïçP€›‘³MòÒ˜¿ä~‰L‚V‰P"aK^Ò["gÑä½V¸J(õ·(‡° ( r†I^Š“OÛIñy3¸¼ä&aæÚ³A¥ÃÈðú—¾ðgæï(´. .„-EHh’à!o—³Ako2—õ%lÉ}^óúw&½)=楾kÈßã¿D)/óÉK‡×¾?K‹ÀÔƒ° +yIPˆ„¦Ô³TXäLX̛ۇ³J9¾ªa<†„Èq°’3tò’è-mñ˜â¶d#gZ$„äzcúðµ òYk!JÖ“Àu+²o…|—|MÄ­ÉË•ò)Å[Lé[²—ï䥭Üov— !ß‘%gºæB…Ü.ìÖäïýû¿ðÛæ ÓVG‚íÚ›öl‹°àj·x¯”œI’— ý0'/åÉÙ´-ÂÎÿýÿ~úôw^÷‹W}W¼œìa ÀUä¬Ê-Þ”.äeB ãÀµÕY-që—.çÈ>ȾØ'€dò2Z®÷giɛídñߨ~+òiIyis/d_®ùY!å¶$ÙêMáBÞˆ/AO~¾F¾U} {9«5àì°_„-Ñä=J2±oõ¦p!AïÕ?öôé_þ«—í5·3|‡ØÞpv Ø'€(°äe¼ÔïÐÊåk¯|óôÝïi{é¶$èÉ™½½€ý lˆr‹Ojlõž©-ß'¶FöM¾}?÷×o¸a hŒ¼Ä'oZ—Oèɨa‘ß” g>$,Éûbßs$ëßê[Ú×luvIÚw«÷‰ið3>Àþ¶€ÈY W¦äÅåëä+ä»—†EÎvÈ7ŽËKM¶$0Èû®äLˆ¬«9S³§÷íí ê{±å‡„Õ¶þ¿íŸ][’€$g2$dI`’0kjºd’ž{/Öž>í&ÁQÎÔaJÎ:JÛìáì#€g¶€ ÉD*g§$dÉKF¹&V9+"/ÊKþû~öô¦ð½}ÇÕÞÈËÆ|£<°„- 2ræI^*”°Uêì…¼Ô(!ÿ 9û%Án/gK†—F¶×OKGEØ*"¨œµHy¹0–,9Ã%/SïCxß°/„- zäŒÅ­Ï.ÉË”ßûß¼ãô¯¿üu{ öŽo“ö…°T`ë—…dò¾Õ5äñŸüè?:}™þv°으7K–­¸êòŸþÄ/^÷Öß ¿€ l;&_N*ï—Ú ™¸åÓŠšïä¶̗×þÿÌ|ÐAÎŒò­òÀv[ÀNÉ{¥ä}Z{#Ÿ†”3\{ùd"†o’—~’— åÓ¤òÆy·GØvH¾rA¾Gj¯fogÜ0%ý#ý43[ò Åñunƒ°ìŒLŠòÒÏÞ_ªó'sìËÜ{ý¤ßöð@àH[ÀÎ /ÿìœu›ûilO^:œûn4 [œ™n‡°ìˆ|+º¼ Èaéeh c.à6[ÀNÈÄ(_Dù…/}Í^”UËYT v„-`'Þõ[æ'qpSòþ@ ø|Pa ØùÂPùþª½~úíâ¥k <°ò5~á%{ ¸-Ϊe¶€ÉW<¼æõï´—€Û“3ªò¥§ü2Pa ØØÖ?2 ©A©Eù¶€ qFá6ß?:=zÔ/÷ûû¤] r[À†öúû‡Myòpº{twzxb/ŸžœîºàEêšàìPa Ø|ì^>‰ˆrž<Ü=º?]¢akg·€2[ÀFä§näëPÖ4la g·€ü[ÀFÚÿöîǧ{û>)³Ü=œÎ¯äuä}TwOlZ^çüž«ál”yiÐ^×-á“Tö Öh½aûf{œÙ ’/:ýž×ý¢½ °y¹¦Ýoî– ³ü>©I€ÂY` \ýz—ÕºË^H Ù"l-“/9•/;a Ø€„, [GbÎ(Ïã{'ù÷1ìY-mV"lÅã¥D /°c| Ñ{QUغœ› Eæz³ÍñÙ³0ÂV¼#>J"lhÿçyú 5~ùÏœLakp]¼Œ˜Ýþ̯ž¾ð¥¯ÙK®AØ6 o@–7"7Ë MB¶ü`¤ E+/+¶ÒüÊ{?j×#l7&ß«%߯Õ4?™ð[rÝe¥ðäýP$Û_:6 W„­4ræUÎÀ¸a ¸±£œ1èCŽŸ>ü<ÈåÀ™­sèò‚–˜ EÎ}ºÅÍ^raëZò¢¼”àz„-àÆÞüÎ÷Ÿ>ðñÏØKÇEàÙ¿W¿öió­ò®CØnL¾5^¾=þè[ûÇÏIy¶€kûËLõ[û×þ§fÛ l7ö]?ü6û?`ßÞõ[>ýúûÏ^аÜ_‰šãËwò[À É{µä=[@ [@„-à†äSˆòiD |×a ¸!~à5ùýÏüÉé'~þ7í%©[À Õú†ãþ‹Bû%çùDâ¾¶€<[À Uùíñæ§wÆ¿søäôp—'$¶ö°äAØn¨Æ°5ý¹ÂÖQ¶€<[À µ¶ò!lía ȃ°ÜP]aËžÁýàóðCÒ~H’ËwOúë‡u!Ê¿=´ÿ~¡ë|Îv½`èþ v·¶/ûî¬gÿN纕}hŸžò l7ÔÊ™­`HꉄüÏkt¹Ó¯sÙÎ9 ùÛ „¡¥ cn·áÈx|~Üé¾?>݇s|Ý÷Ðu »Ñ$ù0‡|¨Àu[À ÕøÕê°å%÷º>äŒÃ—ˆßŽg1õ9¹­ cFoø×>æâ~4JjUjÀu[À ÕøÜYÂÖL(ŠÞŽÏ Nó˜Û¼}Ñ>æâ~4Š¢ò l7DØê/¢·ã#lõ#?ûk§—¾ðgö€T„-àF¾õo¿}zË»~çô_¿å³×Ô!çˈ~V‰ßŽg&Äs·™€vù{´¹¸úž×ýâéù×ö€T„- 0ùD—||þÕ¯}úô÷þûwŸ~ôÍ¿no©Cž°e/û!§ CãuúÇòÞOå­ãnÇ~bÒ{ƒü°¾ÿ˜Ý5á7È. íu-“%a Àõ[@!ŸzòÅÓüÔ/›ÎË÷‰¯¾òÍÓk^ÿNóÿZä [ç`$a§[Î_·ºŸ]Gnó×éoïÿ}=ß¶þ&}¡½®eü5a (@¾ÞáÇî¹Ó—¾òŠ½æ‚—fP>‰äCØ2ú—¾fBÖÒwiÉírÖ Ø³ÿê¿}–7Ç™¶€L$hɧ·Ö‚”¼¬(ïãöêCŸø£Óßþ±ÿÉ^p-¼,(A+ô²¡¯Æo‘DZÈ{µþó7þ2g¶€L[À•ä+Þðö÷œß¿FÎ|qf {%âvüŸÿô_V÷p-9Ú2ZGØ®ô Ï~àôÞþ½Ômø=Dy!¡KÂFì÷·ùŸzÍÍ [ö·EÃ×-ã¸[Àjü­C`‰¬áåp ]¾pÑÉ}øw73*¶‚¿¼@Ø*‰°$^n‘3@ >þ‡l^Ôø½peõDòHé—ùŠnég®Pa HÄ÷¡5¡žæÇ¨GÌKˆöåCïgŸ’Î_Ø;|™îø:ÚuÆa+¾ÌuÃý}±¿”ضó˜v6zŒÉú}¿ÅÙo¾Ý.·û7a HÀY-´føê¿¦åƒòsS°Áã\~ïÓ 3?¥Ygí²÷3Uý¯#ÈÙ·ñY«À˃3g¶üÇèƒV ÄùûÑ]wþe¬Î—'íÔ]ö‚å¶€œÕBk–¾l÷æWM;¶>´\BDø=N~`1¼³`)ë8—ç~d}†¹¯ÌÖÖ’,ᄀ¿åš}ma ˆÄY-´F>M+ŸªÃ“‹N  ôg~ÜëBáCPÖÖq.Ï„¥ {j¼Ä†-’á…§ÐßÜw³KûÜ6‰/%EK4OøQêq`˜.—³]áðÑ]Ù­7¤Œla«Z“}Ú0l ÌõÒf¼Œ` ¿mˆ–hÞ/¿ ? ~\Ó3ðƒL(høï}JYǹ¼ôÒ\ Hùû8¤Ta¶¿ü·„®;[Ú÷†¶€òýCrÛyú}Ÿ¶ÿÃµä— ä·:5äÍó‡ýùž¥3IÞm&hÈÙ›sš°/éÒ…zÙËöýbþÙ*¹Ý3fÿ¼ugOð1GÁª»fu?…sݰ_ƒ¥¶la ˆ°öÞ”÷}oyÞþ×’OÊY+#¿oË„‡Ù—¾Ü³^CÐ胊]fˆfù¸†ûBQ–ìrýƒ\ööüØÃf§á®'‹voö>þ¾¶á­~„- ‚|^ûˆ(ƒ°µ Þ·¥ >Í:h a P’7ËûVøâ¶[Ûà}[:„-„¶%y¿Š¼oÛ"lmçÐïÛR"l!„°(ż™å¶¶#¿›(¿Ÿ a Pz×o}øôëïÿ={ [!lmG>"‡°(ÉY-9»…m¶¶Ãúi[€ïWÙÂÖv~ç#Ÿ:½õÞkÄ"lJòe¦ò¥¦Øak;òµ'òõ'â¶%ÂÖ>¶¶ó…/}íôƒ?ó«ö- $ß1¤ý¶m”CØÚŽ|ÇÜ«_û´½@‹°(}׿Íþ["lm‹'@< DØÚÂÖ¶x9ˆGØ”[û@ØÚa ˆGØ”˜dö°µ­×¼þ§¯¾òM{ €a P’¼ËGß±-ÂÖ¶8à Ä#lJ|ƒü>¶¶EØâ¶¥‡ç>túÍò/ì%l…°µ-°(ÉPËQc[„­m¶€x„-@‰Ÿ*ÙÂÖ¶[@< $ßž-_è(ÿb;„­íȧåӈⶀoxû{NÿÃ?¶—°ÂÖv>õä‹§ÿ¹çì%Z„- ‚¼A^Þ(í¶¶óÞþÁéžý€½@‹°Dxé vú‘Ÿý5{ [h9lÉW‹ìùëEøD.†°DúÁŸùÕÓ¾ô5{ ·ÖrØ’÷Ê÷¹ýÊ{?j¯Ù^FÒ¶€H¼”¸­#¼Œ(aKB×Þ>ŒÁOõi[@¤oüå_›I‡O%nã(ïÙ’—åLÒ^H½¿úµOÛKb¶€o}æñéw>ò){ ·t¤7Èïé é¼_HGØ0ñlçHaKȯȯlMž\È“ ñ[@"9ã gp[G [â<¼ïôá^²—¶ÁÙ\ a H$ïÝ’O&ò†áÛ:bØÚC­É¯'È~ ÝãûG§G÷ùÏ®m·Ôãn­¦¿‹°\—VnïˆaKÈW.lõ†ùªôÉÃéîQ7)?º?m}¤¶–<9=ÜÅí'a+§ó2^܃æÉÃw{·L:ÀväyåÏtâx{Ýr÷ðÄÞZZ|Ña;2 Éd„Û8jØ[½”W󗙚ùáþþtßáÁ!õñ}7¾ß&ÃûÜõW l-!lm¦<Ób7ÏÝC×5áËÝ=Íu¹Îvâx9œû¸Ìco·¡ï6‹°UyiE—¼iå9lmõrâüÔ/Ÿ¾ô•W쥚ôc© ¥³3a«Z5ý]» [ýÙ*]¡OÃVg| Ø Ó'¦½m†®Ä8pñ®²þôÏÿÊþï˜ný¥ºòk ðªdÆ~û*†™Ö_‘ybîúž}2?,9ÁÌçu.9„gû‰iîþçöîþÃvçw´ßæöÉßÑÿ³›õ^uò×[úä69qq^ÇÞyØÏ‹å¶ž®¿_; [qgv‚aËÂÖl‡EtŠéÄPdž®;ÃÊA(¸ÅÛÆ¨8Ö¶*nä#Ïü_÷?üÆé¿|ÓÿÊïÇ¡ùrQy³ú­¾TW¾vB¾~¢FîÜ0¢ÎlÉÜ4¾n:WMÆën;Ã+"Ãø{~…$ðЉYga{ý6.ãû°Í¥1}ºŽ7'†þÖ@8½ðÛ²»ìïãêß0}¥È½Ÿ²­G—÷lŸa+òLT0lyÅsIôsÅãò‹£ß^ Üøï”· ç [»ïp»â@Pm/PÜÈK>¢ÿÁþYsæAÞÌ\çK/Ø»[¾‡J¾O®Î—ÈûÉù2æM'k#4¾Š¹ë=ÎX»2wùã´p®sNXË!e*x»3‡L·»¸Í¥¿sõo°ÛÏ–ê'w®š°eµ»î¼Œnœ„-{ÿI' ×›e9tMo¥xŒñu¿álí¾·ˆügÞíÚíŠùø_t*Ÿ“÷ºHãý\ÈI^ª–Ú*}vK¾ÛKê·J°Ô?éöÆÊè°ÕÇÎü0Œ­+Í×{Îuæþ£íž»Í™y%´Ý±àíÞ¾:óèÌ㌙mŽ÷m°ö7˜UºËO¯_hk¹uåïÞ“*Ïlù |9kuYÏàØí/.óçNµÏˆÆae­ –º„bt/{áëŠâF>s_<)×I“ßS”³]ò#ÃÃÂËHu‹O&J½Ê“†™1/8.zóÃÜX¼~æ PÖ°5ÿ¹¹qm|Þî?–Ùv¹ŸS#^’vU¶˜Û_÷ú•¶./üÝ{²Ó÷lÙ4;Óˆ~OÎlih] \]ê`¥ –nO)Fs[üòÿɾ¥7òðÏj…ÈÙ™¸ÆaKÞÃZ#EéïÝÒÔô~M'êÁdl… GèúÀXëlO3¯x7:×­Ü¿[Û}¢m­ï¡Û§jx™õÁ{ùUa¼ß«Ãüþ:ׯµõpyévd§ak(„p€ðø&aë\ˆökµt{R1ö/WÉmÎpEq#=üœ ŽGΖ–úôkÕ5½ôÔ¿mnü ]ï_g¶5ž+f^±wÃîuû›€u EfýÉåévǦë„Ohôó®¬;ÓvƒÑßd8mªüûë\¿ÚÖóÛ٣݆­s‡:ÝoàÕ°%äÝÞß8Õ»ÌíÞ}ÜgkµtÐ¥£y|ó¬Ão“ôâvÿ&¤¨ú£ñ¨Z©©®û¬–ï¼ñû¢“—§LÈpÃUèúK ‘åþô0™†¹ë²ÎÒ8<½Î¿ÿÌxoo—¿£ŸÜíŽÉcÈzã¿'¼~ß6ómwál«[ÜÍ-ÿ ¡vþõkm=·=Úqز†4;^¼B0²Rn§M·á3è¯c“öå ;(ÜP“PŒöñÃûžVÜ}»¶®QóGãQ·O=ùâéÇî9{)ÎÔÕ4ˆ"ý‡-ÔX•<1Ø-™ìdÒ¶û¢k?«…+8/"'ÂÖ­zÆ:y¿Œ¼oØJγPòa ZòÒ8ŽÆ¾:²òªÒ¶€+¼÷ƒpú…g?`/áüR÷ÌKÓ“—óeñ¿yÉÛ[gú²ÆÒKôaÓ—Òã·±G9¾g«ºZGØ®Pó÷•П-½Ÿ}yÚÜî+7l¥mc¯®y“ü-Ô8*ÂH&7™ä ú—'ï«òÒÖ$lÙ—óÆë™PdBаÌ|}¸¯YÖ“¶ŒÈmìUj`~6 @Y„­ ô¨_f:âý™£ÐuC¸éÿ“†î™­ÀÙ}çÀÔ‡¥ÉcØãnñô¶Q›”O$´Æ÷/–Ð\ØêÚÀ€x)¢ç?#/ƒ°ÕùÊùêØú·Œ­É™­7lufÝ––ÖY=ÛØ«˜³¬ò-©]‚ÖžÙ÷Î.î79þFEz‚³x˜ãí²}ÿ¸‰ÞŒöÎlÍ ˜ç™Þw€ <ë=ªÃw ú‰!ô„ÅNIa«û_è%IÇÁÃÖïæOÌ›Ü× ï3äIÂÍÌ ZóµkC™wLÉúÃñ8=æÂ÷øå_ŽÝz ¾Œ˜íuRþY//Å_°u„-ki"ñnK [ö~Ãq*Ûôn7÷Y9Žc0q{¥ [ò²·ü´ß¿¥cDanþpŽ‹ÁcnnO8üÇŽÚΚ|Ï–)§$yw…ðX É-ˆP÷ƒî°ø…lSü°Œ§/üÑmÝrÛ»ËÝgïqÅÒcËmòlå¼Nàþ¸ÂVÏÔ£?øžõÇËü³ì©àöì$àlç|œ,=þ…Ùîè˜IÙÆ^I€’¯‘—ål–¼¿ðšïâjÙ0¶:5¨‡IÍxcð°SkööÉ}º%vèök·ç[sÌã§i8 ­,ŒŸO×™£¯<óãÏ Fà,TH0÷g: ÿ±õÛ»¸ªíÑfز9ô£éh{Á)?‡ÒùJb7ÛÞRØ–XÜá]7y,lа…½_1øÏ~ò›÷bI¸’/Û}ó;ßOÈR ÏýX>wN9óÆûÙ1z%Œ¬ –½_øï]Ÿ»ÔÛ;»²íÑhØŸqòÒúèš¹­»ßd Mäãe²õ‚ÎuŠÇpØ ŸFÄžüûS“ò²!/Æ Ž­ã±<ôdXxgvŒž™´‚Û [Μ>{4·ïþõÚí]Ûvh4l 1¼t8êèQO:wõ€èƒÖ¸°Ì6²…­åƒ±õb¬MÊw¥|׿Íþ±‚cëÞÃV`> ™œTš9ûÔñ[·½–ÑlØ:wðƒùøµâáL×ô¸¼ÎŸ0¦@Æ…7SX¡Br®[{ìNëÅX›ÿáŸÞðö÷ØKÀvBïÙ‚^hl½Æîþ¬ØÚ›ìq=Âp Z¸€-ÉJóÃÒéZ Û#lWøÆ_þõé5¯'ïÛ2ü÷Žð>Â[á v}#lW’¯¯86´Æïý¢ð^›O"òÓÀ~¶€+É·ÈË7wÚÜwé 89»ú=¯ûE{ À¶€+ÉïÒÉJû¥Äé÷愸ÿv_f”Ûäºçuºm™ÿOÎŽM?Å»]ÃÄá~µE>‰ìa È@~¶G~¾çȨ̀«ag`ÂÎ(ÑøAjCÎ'£Ìwâyß´òÍÓªíN[w¹Ò—MÍžÏP¾‡ËœÝ¾œÁ^:–ûŸ6yè¶á×nâveÇ×Ux\ð…ºû2£§ãóR'+]»3Û/ò·4¬Š°enëž!ô‹ûòbè~këË3ãË'§ºÅ Îu}™Û®s?» wï|ßîóobêÖœ¼x,|ŸÎä@ð^žÝgi?—î‡8òå’òrâÑ&ÁI}MjÈN>çuÜÜÔøÜ`Ï<…ë2m»çcÒ.s½W|¿Û¾LÇè¾.ÇgbýZ ÕæìuçZÕÍ=br{ È—ÖYÚþ°ŸÎ:í£·û°å_o. :xûÚúã¢ñÀu£Í¬n·»¢ÛÆLúï¶å¼ôZ×\çñÉã œ}•ƒ{¼Ýé3«ð~*î‡(òÒ¿™ˆ’~üçž;ÞÔÓÌ ÁyÄg×ÖÑ<ŽèCÐxN±O¨GÛÒ¬3·}³ã¹ÍÎEÎ\‡³}‡-Óy^'{×9÷‹]ßZ½N±Ýîó9 I}A/=ãñ…·Ó1û1ÿ¬|r¿™ýôÍ>Ôä̃¼§†—y›œ=•ŸèÁ~˜1S‚ÊxñÆPœ÷/ çºä¹§Ÿc&ó‚³®fÀekußáØwØ2Ü]?Y.ïÜ/v}kõ:Åvûut)Ì3Šá@\ LÂlg6l×>3/ª°µr?$‘3\ò•®6H?îákøÝý™ŽÑö‚Ñ™#>ÍÎu©sO(¤‰ñ\£YGDÌm¡ëЫ lŠadZ˜ë[«×)¶;·Nðï£BŸžÊ2Û …ó¸Ã}gΘ­†-ÅýL~¤Z^òIýâIùÂT™X lÛ“Ošnýmíò#ROÔþÇL/¸øóY8×¥Î=š ¥YGDÌm¡ëЫàeÄQ§8÷‹]ßZ½N±Ý¹Â þ]Æð¾(ù„ÕúëÜf;“ðã½·*pPLîÚOÍýp™ åSŠò>. OÎä Ðò»‹ò’$¶'ý±u_ðå¹û3½¹ÃŸü˹.uî™»Ÿëí“sÍ:"´ýÎê¾Ã±ó7ÈÛ0á°œ…™;-»~oýºõíÎnøïê]>Å1-dŸÙŽóøvŸ–öÁ4ÞýBû©¹²øÀÇ?c“„.™´¿ô•Wì-röä»~øm&œÉ:œÁØ­‡Þ¯Ù1zt?ôsÀeü7·ËØ{^çʹg¼ž¹Ÿûøšu·ï]º½‡-1„Ša œqöºåí s{ûp×ж/la+BÍxÛç%°ÝK€“åþô0~o˜ÚOÍý‡„' ]ÃY+Yd?/ÿãoªÏ~ᶤ¯Bù¤näåC9KŠý ŽÑ¡±×·ûu·™±ØY'mîþ>…^A‰]gؾ¹.ô·xס·›°uL}ØZ{ m“É[Ζ ‹„°­ß„09ã¸yßž¼1@}[[ ¼W ³Zr…—÷EB±ôËxSÚ¿?rÖQ^æ½5 y´xi¨a Ø!Îní|XáÖ?Ãû´€6¶€â#þûò®ßú°ù¤è-½áíï1ßÓ n„-`§äå#ùIÎníï¾÷£§Ïüñ—í¥²ä[êåëAäÔ°ìïÝÚùÙ_3Ké—ôäwå¥Cùzm l;&g8äìoŽÞÖðIÄ!•:ã$K¶/ „-`ç¶xcv,Ù¿–_î”põÖg›ÿËß)ÿ—O&æ:Ë%aNÚP^:ÜÃ]È‹°T@&ö-&f„–ß_ú°‚ô‡¼¬(·}êÉíµqdr9k¶õo.(‡°T@^F”—÷zÖc«ï º9“õš×¿s¶í%„ÉK˜ä=vkg»¤­ä}xÒŸÒf|âha ¨Äø¥¬=’@Òâ{Ë´AR^ ”%g»äg}d‘&÷•åÕ¯}úüãÊx_p„- "ò¾ž½¾Ü´Å÷PÝÂ5—¼¼(aM¾Â8.–ÒÓïû´ý°™°å=>{ü¢KyùLÎê´æ_÷€ãªmna.LCØRú¾·Ä[Ç@ØR¢ÀÐyIPÎâÈKŒ~Ø’—sýx²lG‚ÛžÉOI(Ìñ÷1[Ç@ØR¢À€tCˆÛ# Xkz¹í laK‰®#ïùJy)²4y£¿¼ Øaë[Jpyß–¼T'/Ùí…¼Gk¯gÜp „­c l)Q`Àõ†ÏÞÃKvòÁyS<°%ÂÖ1¶”(0 9Ã%ï‘’7áoE‚–|0Øaë[J¼)]¾^âÖ?ž-KО¶Ž°¥DùÉgKø¹ÅïÊK—òž1‚ö„°u „-% (CÂüÆ¢|"°Ä÷\ÉË–ò‰Cy¯ØžÞœÂÖ1¶”(0 œ!É™§\_!ÁMÞÿ?õË|µv‹°u „-% (OÎ<É÷qÉ™. JòéÅXòr¡¼<)?G$_íPÃOá¸[Ç@ØR¢À€Û‘Ð%AIÎJÉÙ. Oòr㛞ùgæçüE‚™üæ£,y¹PÞxôõCoÿˆ·Xö¿<ÿb=¿^ û‹x„-% ØÆK_ø3žäôûþ›¯ð fò#Û·x£}-³êð¹/ÿ…ý_¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4„-% ØÇ¡m…¨«4ÙÃÖ«^õªÓ£Gš[¾ãû:x}í‹ôW-Z­­—RuUb oµ®þÖw~oðúÚƬmæÂ4ÙÖì4êQSQ[õ(ÕW%ÂuUÆ,”Pº¯²o⪠J(ÕW„-0f¡„Ò}•}ëW]¸PB©¾"l1 %”î«ì[§¸êÂÀ…Jõa ŒY(¡t_eß:ÅU.”Pª¯[`ÌB ¥û*ûÖ)®º0p¡„R}EØcJ(ÝWÙ·NqÕ… %”ê+³PBé¾Ê¾uŠ«. \(¡T_¶À˜…J÷Uö­S\uaàB ¥úаÆ,”Pº¯²o⪠J(ÕW„-0f¡„Ò}•}ëW]¸PB©¾"l1 %”î«ì[§¸êÂÀ…Jõa ŒY(¡t_eß:ÅU.”Pª¯[`ÌB ¥û*ûÖ)®º0p¡„R}EØcJ(ÝWÙ·NqÕ… %”ê+³PBé¾Ê¾õœ;üøþ‘ÙÞx¹{xbo­Å“ÓÃ]·ï÷íå}ÉÙ_¥Õ´¯GWª¯Ž¶ž<ÜÝ=t#É…¹îÑÝiÍXéŒ1vÜéþ¶~¹?¹#ÐÚíûµ÷þ+²¯ONwš>;¯î羆Ûx|oÖNY}ÍÌÍÁýöºÇ˜Üqßsà@ö½¤ì[ϹÃf2³E°g„­\jÚ×£+ÕW„­N`tÖsüûœ/¯Ý¾oG³L=Üߟî½ëOT\ÂøÀ©%Æüm̆¦™õçûMÖI˜MO÷¿¤Òu•}ë9wx¶R: ‹JXN5íëÑ•ê+ÂÖc3Éúc ¶V&ÅÕÛwîØcV?Jß¹û¢=š šOíuRcþY/€æÏ¦õuÚ…@¹ÿµó6ak]Ξ [þiÌ>Å‹_ vpg{½¥ûÓºY¼‚‘ûÊþ8Û¬3¾NsŸÉ~Û%÷˨²ÍZÔ´¯GWª¯Ž¶ì¤57†Çp »X»}ß=f™ lç¨`ø‰ 6¦nœz’ÚèBÎcy7ìøó˜ï\§“0Þ'gþý“9·[nQªò8%eßzΞB íúàÞG:y¼þ´ÓC1š¾ÓÇÅ<¤†‚9‡ [hãPÜÇÅûô39ŸÒ–SMûzt¥úêÈaëÁŒþäÚóǘ˄^íö=«iȽ¯C-ô3A?/Œºý<—8×-ñçÔs€óçœ~î\šƒÆûæÎ«3ó®Ù›‡9³µ"ç¡ÄYÆb ËëÐu#N§.f …W~ш`!-\Îu¿!tŸröWi5íëÑ•ê«Ã†­nÍ23Ç;¾õ÷ „ªµÛwjïý5–w_ýÀ3 1¡9ÍÔÆ¹Ÿ½õ½yÎÔš½°6'ùœ è쇷Ÿ¡mù×¶ÖåÜaÓÙãp5tÚ00˜‘Ëþ2˜œÛ‡m.uè\q9Ed÷q¸`ù×­]îuþ³ ÿr>Òµ¨i_®T_úeD;ö„ÆÐ˜rfï7¨ÖnߙÎYùªâ£ë¼ùÉ7­“qòœy¼¾&œ 5Ã_Ç<–¹¿¶4óöÒÜ\ˆìCIÙ·žs‡Mgù<.¦Õ™†g›K÷7¸Í+æÐ ç_·vY¸× ¡r´xëçRºÀrªi_®T_ý ò“ÉÕ )Ž•Ixõö9ê˜eú¸Û^hñŸ˜ÏÕB¨Núš’P$÷ÕÖ¨&Vë«3 döþwakiÞî¶ÖåÜaÓÁKakm€t˜³Í¥ûÏÝf¶yy*BÿºµË¹Î<ömže–.°œjÚ×£+ÕWG[Ý%ûDÌBcŠcm¬\»}GŽ9fÍ¿ºaú~4OÎr¬Ó÷ÝúîÜ6ÔÚÝÃyìµÚ˜„­Î°/wã°¥©µaŸC)¥ë*ûÖsî°_DCçOg)J[0~§š ´rYÇÞÁ<¾S|Óg ¡âõ¯[»,œëì~›}-¡íZ²ÝZäÚWÓÖ£v5‹SCÂÖ†×Oób×o[®¾ò¶ÄÊ8$c˜WÏ}ÍÛ±líöÛ{eÛW3wÍ„Émv, ¬ïÔÉÙ°þô¶>,éj#¶ºGìkÕÙv`Þ5ëc&•¬ÓÒu•}ë9w¸oXo™tæ¨PÌâØ¥Xd¹ïÒ»_þýÝŽô÷Á<¡âõ¯[»,Üë¤ð¼Eól üMµÈµ¯¦­ƒ5°Ô »~&Kƒð†JÕaË2ý~ü1Å÷ºÅ»ÿÚí{&û[‹\û:³Æú@3y2nkÄYf¶a¶¿´ E}„ÃÖ¨Öœ±qyÞÃ>ÉâÔ9a«Wz‡ \´3Ô•jê¯\û¸6 K×"l]1«.G³P^é¾Ê¾uŠëzý3w ]—îÙg‰^p1ë9áktJ|´ Ø__.Ëmçgv²×Üï¼=ÿY›÷˜£û;ÛµËxw—¶;ìÛyÌ!S¶Ya 5õµUÒ}•}ëWîD)K™³5õW®}5m [ÞKµf½sñÏ,NÏ4ºëÛËÝ:þ6&ëø—Ïû&gÛÆý8û6sfky»ö²·ÿ9•ª+Âjê/j«¥û*ûÖ)®ºqàòƒÇÙRØ2·¹¡&h.&Ù³Lm¯™ì{(l)¶;ÙNf¥êа…#ŽY(¯t_eß:ÅU—#\³A#HÎa)ñÌV0lÙÇ0ÿïÖ™,ã dφ—µ°¥Ønpß2’Ç+°…šú‹ÚªGé¾Ê¾uŠ«.G¸fÖ )—÷6¹ÄÿôÌø¶^ZØò‚’c&ЩÂÖÒvív[F©}E5õµUÒ}•}ëW]Ž8pM‹1}?”HÌY¯åû&hú7µ_FtßÔɾϾd¸°ÝNhßr*UW„-qÌBy¥û*ûÖ)®ºqàš–!h>±ç†-YÇ]®{ƒ¼}\g_d™0f—·~0X­lW.¶ÎJí+ʨ©¿¨­z”î«ì[/·Ãþ{W–Ï2l­ôd–KMƒA®}=‡ ñè+·¥þ–Ï ù}>\voò8þË“îc¸_ïúR^÷ï¹l~y»þ¾æ&Ya+]¸NêSS¡¶¨+ì[/³Ãvâ8÷dè™{ˆÐü¥\`+=™å"íP‹-÷Õ„žI½õõ5÷õ µÔ@ ¥úªÆ°µ‹:0O ¼' •bÌêQWy•®«ì[/²Ã—Jœ÷¾(ݲ8 [ùm¹¯}½…Î:Í4„­ü¶rˆ‘2^îcVºÊ«t]eßz™öÏ øgºt[S \z¦O»}¸,˃V-5PB©¾"l¥aRÜFÉ}¥®ò*]WÙ·^j‡/gìKƒ«/!Nͧ\/AÎÜ.Û>¯ã½ xÌó}Ìr):ÿ±úõÜ¢œ»¯ÛÂû”—l»5íëÑ•ê«ÂÖ䨖et{?Önë–ñáïÜouÜxðÞ»×-çq,}|K·•$W‹’ûjÚŸºÊF¯¤ì[/·Ã—7üJ§§08®.kè\w»òxãgÓ³iæ~ãBêž5 Û?V¿ý@A-l«¿OúߪUºÀrªi_®T_µ¶œcÛ¾MÂ9ÖgÎ@¤ŽÓ3ùÆ71Y· šÆ’ûìê*YéºÊ¾õ";l‹F¶m–QgHGiӯߑm‡:ëÞG66<Ö´;æ¾^Á{×ݪÈJXN5íëÑ•ê«f–7pL® MŠWŒÁqÈãÜwi|SìGIŒY=ê*¯Òu•}ëùwØ&㡳L±\ Ètv BBÅ(æ®ïnqO‡ÊâìÇ|˜mšûÖþ†ÉâwpŸò’Ç­EMûzt¥úêÐaëŠq#<)&ŽoŠý(I«%÷•ºÊK«¤ì[Ͼá$l;åîáÁtªjsÎ\Á„¯ï f¼m³^LØê¶iþõ‹qå¾bn_s+]`9Õ´¯GWª¯[iãÆtR¼b|SìGIŒY=ê*¯Òu•}ëùwxÚy†é˜®'©zÞ\Á¯t¼S4K§C;—mzgæÄÊ}ÅܾæÆÀ…JõÕaÂÖìK*iãÆdR¼f|SìGIŒY=ê*¯Òu•}ë%vØt–Ó6ÀteeïÌLðz¿ã‡pwM%ëØ;8Û´ÛºÆÀ}M¨¼íܾæVºÀrªi_®T_+lùOú¸1™¯ßÖ÷£$Ƭu•WéºÊ¾õR;l:[:mX.½Ùu†ßAas³\HöñºŸþTŠ-ŽÑ:ç½ò·i‹îr•ßÀ³À>å&]‹šöõèJõÕaÂVÇ\gƆëÇɤعf|[Û’äñjQr_©«¼äñJʾõÒ;Œ¼jê/j«¥úªÆ°…¼³PBé¾Ê¾uŠ«. \(¡T_¶À˜…J÷Uö­S\uaàB ¥úаÆ,”Pº¯²o⪠J(ÕW„-0f¡„Ò}•}ëW]¸PB©¾"l1 %”î«ì[§¸êÂÀ…Jõa ŒY(¡t_eß:ÅU.”Pª¯[`ÌB ¥û*ûÖ)®º0p¡„R}EØcJ(ÝWÙ·NqÕ… %”ê+³PBé¾Ê¾uŠ«. \(¡T_¶À˜…J÷Uö­S\uaàB ¥úаÆ,”Pº¯²o⪠J(ÕW„-0f¡„Ò}•}ëW]¸PB©¾"l1 %”î«ì[§¸êÂÀ…Jõa ŒY(¡t_eß:ÅU.”Pª¯[`ÌB ¥û*ûÖ)®º0p¡„R}EØcJ(ÝWÙ·NqÕ… %”ê+³PBé¾Ê¾õ§žzÊìtkËw|ÿO¯¯}‘þªE‹µE]Å)¶³êZ³¶]¨«4Än¥ƒ<@]Å¡½ôh«v}îËqúØgþÌ^º-ê* aK‰C ÔUÚK¶j×ó/~ñôôû>m/Ýu•†°¥D¡ê*í¥G[µ‹°U–†¨«8´—mÕ.ÂV}[JJ ®âÐ^z´U»[õ!l)Q`(ºŠC{éÑVí"lÕ‡°¥D¡ê*í¥G[µ‹°U–†¨«8´—mÕ.ÂV}[JJ ®âÐ^z´U»[õ!l)Q`(ºŠC{éÑVí"lÕ‡°¥D¡ê*í¥G[µ‹°U–†¨«8´—mÕ.ÂV}[JJ ®âÐ^z´U»[õ!l)Q`(ºŠC{éÑVí"lÕ‡°¥D¡ê*í¥G[µ‹°U–†¨«8´—mÕ.ÂV}[JJ ®âÐ^z´U»[õ!l)Q`(ºŠC{éÑVí"lÕ‡°¥D¡ê*í¥G[µ‹°U–†¨«8´—mÕ.ÂV}[JJ ®âÐ^z´U»[õ!l)Q`(ºŠC{éÑVí"lÕ‡°¥D¡ê*í¥G[Å{æwÿèô¦g_ØýrÿŽ~éýŸµ{}[ÔU–†¨«8´—mOÚ쓟ÿzË7ÿÍ·í^ßu•†°¥D¡ê*í¥G[Å£ÍÖÑFi[JJ ®âÐ^z´U<Úlm”†°¥D¡ê*í¥G[Å£ÍÖÑFi[JJ ®âÐ^z´U<Úlm”†°¥D¡ê*í¥G[Å£ÍÖÑFi[JJ ®âÐ^z´U<Úlm”†°¥D¡ê*í¥G[Å£ÍÖÑFi[JJ ®âÐ^z´U<Úlm”†°¥D¡ê*í¥G[Å£ÍÖÑFi[JJ ®âÐ^z´U<Úlm”†°¥D¡ê*í¥G[Å£ÍÖÑFi[JJ ®âÐ^z´U<Úlm”†°¥D¡ê*í¥G[Å£ÍÖÑFi[JJ ®âÐ^z´U<Úlm”†°¥D¡ê*í¥G[Å£ÍÖÑFi[JJ ®âÐ^z´U<Úlm”†°¥D¡ê*í¥G[Å£ÍÖÑFi[JJ ®âÐ^z´U<Úlm”†°¥D¡„RuõªW½êôèÑ£æ–ïþÉç‚××¼H_•P¢¶Z­«a‘6 ]_ëR¢¶˜ Ó¶”(0”Pª®d EJõU‰Új½®ZçKôsaFd¥Öú³ÄÖ–šž%Êþ¢¥úа°µŽ°•†Y‰ 5 \ÔV=Jõcj³ZÇ‘£ÄÀ…š.j«¥úŠ1 5Y­ãÈQbàBMµUR}Ř…šÆ¬Öqä(1p¡¦‹ÚªG©¾bÌBMcVë8r”¸PÓÀEmÕ£T_1f¡¦1«u9J \¨iࢶêQª¯³PÓ˜Õ:Ž%.Ô4pQ[õ(ÕWŒY¨iÌjGŽj¸¨­z”ê+Æ,Ô4fµŽ#G‰ 5 \ÔV=Jõcj³ZÇ‘£ÄÀ…š.j«¥úŠ1 5Y­ãÈQbàBMµUR}Ř…šÆ¬Öqä(1p¡¦‹ÚªG©¾bÌBMcVë8r”¸PÓÀEmÕ£T_1f¡¦1«u9J \¨iàºem=¾tztÿØ^B¬R}µû1ëÉÃé®ÛÞ£G÷§Iõ<¾75)+sýÝéች<ñøto¶9,ÓuŸ<Ün¿,וðúãN¥Ü'Žl77ÂVšÛÈ•#l¡¦ë–µ•¶V'Í l´O¥újïc– =÷÷&pLËçÉé᮫«»‡îNfk­¿Ïåf» /Ì™Çu¶{-ÝãºR1«u·‘+×lØZzvÙ > ô;3ÙzëÜMf¬a0–¼ƒÊ-È~çFز—÷€°µ*ß¾^ÂÆlýرiKú±(rÜ0}ꆹüa+ ð¸«Rî³¢Dm¶ÒÜnD®\«akùÙeh`²Ï.G×™Ár¼Ž7HvWôAk¼Ž ,þåÐD7wýj¸nY[þd)—¥ïÍõR+²Œnøqí9÷ó&×ɶí×s0·íµ}*I«„]YfŒ°íoŽñpˆêûKnëǢ¢dÇ?bÖهÉúýþÌÖHàqW¥ÜgE‰Ú"l¥¹Ýˆ\¹6ÃÖú³ËàÀä Ð`ä\gÃ×âäEØÊê–µå׎¹Ü=þyb´ýïL”3ýÜÖ¨¶&Û¶4¹¶íîŠà>•Vª¯öD»A.¬ýzr„êÂ\ˆ"ô¸kRî³Fþ¶Ü[in7"W®É°e&${pÏ î hyÏÀ&“VǽΘ3i+4øÉªs×oE?·fÖ×Q“ëB˜WW†¢ÖÄêc*¶ܧ(ÕWû³ú'y—€cÏ|{ý7ÆÅ@¤<fú}i½i ÕÛ…òq)÷YW¢¶[in7"W®Å°å©é€"‚aË›”& ¼ÆÛº§™gnsÝF`HM×-kËŸxVƒõ«­›éâÕš_¤ÕÇTl»_çöµ&ûQÂnǬ@;÷ãC íM îBÈÜsÖ'ðÖ…¦VÖuë+¦¦Ž6ªµR}µ×1Ëô‹¦‹fǬkÆŸ¸3?ýcèÃYöÖ7µao—ÿÏ£ØÇ)÷Ñ’6Ͱ•æv#råš [‰%ôìòrFê²ÌˆBñ,íºFÌÜD·ÑR¢¿¶ü—ð„­ ï®ŽÐ¶Åêc*¶ܧ(ÕWû³æC“é¯ñ¸áû³g¿<ÚõÆ&=ÑkR?²nhÿS7å>1j³Zw»¹r­…-3¸tZƉ ¡¹AR3±Ö!leUb_ç˜þu¶YL® †ŸPP—ÉùÊCÛë¹¾íð>•Wª¯v9f-ÏÎm¡3íÓëLúp=¼È6¼Û5ãUÇlûþazÿŽ&4Åïëõj³Zw»¹rm…-ý³Ë¤°'ü‰mî¬ÂÜõ¨iàºem¹¡fzYÌ^×í§,—›líØëýÉ(´¡{Ìåm‹ð>•%UÂÇ,Ó¾³ãÉe\š "^0êûk0ýý‡þsLsnWŽ/vÌšî¿îqSöõZ²½Ü[in7"W®©°¥~vi¦•ƒ?4€öšXd›ÞíîÀÓ™;«0wýj¸6«-D+ÕWMYHRÓ˜Õ:Ž¥–.í³K‘¶†äl§»|^ÛìX¿ŒÃÕÜõ·&Ÿa ¥úа…šÆ¬Öqä(1p¡¦‹ÚªG©¾bÌBMcVë8r”¸PÓÀEmÕ£T_1f¡¦1«u9J \¨iࢶêQª¯³PÓ˜Õ:Ž%.Ô4pQ[õ(ÕWŒY¨iÌjGŽj¸¨­z”ê+Æ,Ô4fµŽ#G‰ 5 \ÔV=Jõcj³ZÇ‘£ÄÀ…š.j«¥úŠ1 5Y­ãÈQbàBMµUR}uÄ1Ë|'ß¾y'j³ZLj¬DØBM×Íjkø gý2V•ê«Ƭù/XVüäW@lØšü6”¨-ÂVšÈõ#l¡¦ëµÕ³ÿôgŸ4¿:àXúù¨(ÕW„­u„­x„­4åGäFT¶8ûEMWéÚê~)S@"lÙÿåUwØJCØrÕ4fµ®ìˆÜZÖL8ûEMWÙÚŠ=ëÐÿÞ¦ì“YFu7ùÍÌno¶¯ßa¹aMœîä;¿áI·_?bîN"ûRBíaËí»þ²üÖª¹~èC¯sB÷YzbinŸyüîÖ«ëÅÙ×@½:OB“íæFØJSrDnJa‹³yÕ4p­-{¦T77H0×M ¨ÍÔ–™„ü‰s˜àÌ>Œï3LŒÓëúM¬ìGhÌuåÏ—ê«Ö<þðãöCž/wÆ÷é×_î¿ùÇ¿¾^‚ûï6oÿs+Q[„­4Gä¶Ô¶“Ú¢ùgqœ}èÉþäÖJØrk [:dÒ¡‰k¦:Îu}};ïÝýé¾»îüСmŒ¸ûÑ×ÑÜÄ]R©¾j2lyý1·Nhì Yz|Ÿ»îJ½¬ÖoÜc§*Q[„­4Gä¶T¶Ì­ !+ÏâDhBì8LÇ@&Τ¦×õ›XÙųɒj¸¶¬-¿&.ý>ZÆ“Ìl¿z÷1Ëe½ñã˜Éµûÿðïù:g2[Þgý¨ãç:²%6l™¾õêiÆÒãw·¦×Kdý–"™a+MÁ¹--„­Ë@d—…}2…&ÄI˜ê8×µsöAÔ4p­­ašiw·Ofúl­¶B×ùÌ:}Жmš‡<_çÕžf?Fµh&ÒŠC¼8lØê.›¯:³ue½(ê×ß÷j³ZWrDnJ aklz Û t¼¨&Dï>f¹¬7~3 uÿþ=_ç vËûᬿò7æ&û’[aËöÃÌdâÔV n–&­3UßöµrÿXþî?ü|\­Øîý™Ô/¨•Uª¯Ž¶Î}9³íÁìã_[/Šú ý=¹Õ4fµ®ìˆÜêÂÖZff÷@ï×]|7¶¼ë|fúÏ>ˆš®²µ%ìDè§¶üIÇô}¨Oý‰)4YJ}¸ý-uwº­g®»óB¼f?:}M…ÿ®RJõÕ±ÃVÇöùRhž}ü«ëe½~COn%j‹°•¦ôˆÜŒúÂÖ0„' ç@7‰»Þd2ƒ·­à$éëTígDMWéÚ:&¡ñ2;!ÉrzËÞ:¦Ÿí:—zݰxõ'ìã;uº®£ÙîΦ^§×—#ûSB5aëÜ'Ãryr6¤¬Õul-xw;[züëëe¹~COnò¸¹¶ÒÜhD®_aër°O')ç@×<‹ «õgoB«ö³¢¦«|mµªŸô9³`µ‡ÚÒ¡®Ò0+*Q`z„-=ê*¢µ‡ÚÒ¡®Ò0+*Q`z„-=ê*¢µ‡ÚÒ¡®Ò0+*Q`z„-=ê*¢µ‡ÚÒ¡®Ò0+*Q`z„-=ê*¢µ‡ÚÒ¡®Ò0+*Q`z„-=ê*¢µ‡ÚÒ¡®Ò0+*Q`z„-=ê*Ž|4_>¢uÔVùJùj,£®Ò0+*Q`z„-=ê*Ž|é¤|ù$ÖQ[q¨-ê* ³¢¦GØÒ£®â0!êQ[q¨-ê* ³¢¦GØÒ£®â0!êQ[q¨-ê* ³¢¦GØÒ£®â0!êQ[q¨-ê* ³¢¦GØÒ£®â0!êQ[q¨-ê* ³¢¦GØÒ£®â0!êQ[q¨-ê* ³¢¦GØÒ£®â|ì3vzëÿþ‡ö–P[q[:ÔUfE% L°¥G]Åùäç¿~zÓ³/ØKXBmÅ¡¶t¨«4ÌŠJ˜aKºŠÃ„¨GmÅ¡¶t¨«4ÌŠJ˜aKºŠÃ„¨GmÅ¡¶t¨«4ÌŠJ˜aKºŠÃ„¨GmÅ¡¶t¨«4ÌŠJ˜aKºŠÃ„¨GmÅ¡¶t¨«4ÌŠJ˜aKºŠÃ„¨GmÅ¡¶t¨«4ÌŠJ˜aKºŠóÙ—_9½ñ™OØKXBmÅ!léPWi˜•(0=–uçOÿü¯N?öôGí%,¡¶âP[:ÔUfE% L°¥G]ÅaBÔ£¶âP[:ÔUfE% L°¥G]ÅaBÔ£¶âP[:ÔUfE% L°¥G]ÅaBÔ£¶âP[:ÔUfE% L°¥G]ÅaBÔ£¶âP[:ÔUfE% L°¥G]ÅaBÔ£¶âP[:ÔUfE% L°¥G]Åùú7¾uú¡·}Ø^Âj+aK‡ºJì¨Dé¶ô¨«x´™í6[G¥aVT¢Àô[zÔU<ÚL‡vŠG›­£Ò0+*I±è [¡ëYXXXXê_°¥*8–ðò·¾ó{ƒ×³°°°ìyy;žå²ü/ÿ•ƒ°Õ 9 0OÞ+o†…õ‡úÒ£­âÐ^õ"l5ˆÉqVê)õ¥G[Å¡½êEØj“ã2¬8ÔSêK¶ŠC{Õ‹°Õ &Çe Xq¨§8Ô—m‡öªa«ALŽË°âPOq¨/=Ú*íU/ÂVƒ˜—1`Å¡žâP_z´UÚ«^„­19.cÀŠC=Å¡¾ôh«8´W½[ br\Æ€‡zŠóÆg>qúì˯ØKXò¦g_8}òó_·—°†Úªa«ALŽË[q¨§8=Ú*íU/ÂVƒ˜—¶âPOq˜õh«8´W½[ br\vÿŽ^þÊ7í%¬¡žâ0!êÑVqh¯z¶Ä丌+õ‡úÒ£­âÐ^õ"l5ˆÉqVê)õ¥G[Å¡½êEØj“ã2¬8ÔSêK¶ŠC{Õ‹°Õ &Çe Xq¨§8o~÷‹§^úª½„%¿ðžOþŸO~Ù^Âj«^„­19.#lÅ¡žâ<ý¾OŸžñ‹ö–ÐVqh¯z¶Ä丌°‡zŠÃ„¨G[Å¡½êEØj“ã2ÂVê)¢m‡öªa«ALŽË[q¨§8Lˆz´UÚ«^„­19.#lÅ¡žâ0!êÑVqh¯z¶Ä丌°‡zŠÃ„¨G[Å¡½êEØj“ã2>>‡zŠÃ„¨÷ÌïþÑé}û×öÖP[õ"l5ˆÉqVê)γÏ?9ýöG>o/aÉsúœY CmÕ‹°Õ &Çe„­8ÔS„m‡öªa«ALŽË[q¨§8Lˆz´UÚ«^„­19.#lÅ¡žâ0!êÑVqh¯z¶Ä丌°‡zŠÃ„¨G[Å¡½êEØj“ã2ÂVê)¢m‡öªa«ALŽË[q¨§8LˆzòÉ:ù„t¨­z¶Ä丌°‡zŠC€Ð“ãPŽGèP[õ"l5ˆÉqa+õ‡¡G[Å¡½êEØj“ã2ÂVê)¢m‡öªa«ALŽË[q¨§8Lˆz´UÚ«^„­19.ãM¦q¨§8Lˆz´UÚ«^„­19.#lÅ¡žâ0!êÑVqh¯z¶Ä丌°‡zŠÃ„¨G[Å¡½êEØj“ã2ÂVê)¢Þ'?ÿõÓ›ž}Á^Âj«^„­19.#lÅ¡žâ¼ðÒWOo~÷‹ö–¶âP[õ"l5ˆÉqa+õ‡¡G[Å¡½êEØj“ã2ÂVê)¢m‡öªa«ALŽË[q¨§8Lˆz´UÚ«^„­19.#lÅ¡žâ0!êÑVqh¯z¶Ä丌°‡zŠÃ„¨G[Å¡½êEØj“ã2ÂVê)¢Þç¾ü§×¿ë÷ì%¬¡¶êEØj“ã2¾«&õç³/¿rzã3Ÿ°—°äOÿü¯N?öôGí%¬¡¶êEØj“ã2ÂVê)B¶ŠC{Õ‹°Õ &Çe„­8ÔS&D=Ú*íU/ÂVƒ˜—¶âPOq˜õh«8´W½[ br\FØŠC=ÅaBÔ£­âÐ^õ"l5ˆÉqa+õ‡ Q¶ŠC{Õ‹°Õ &Çe„­8ÔS&D½¯ã[§zÛ‡í%¬¡¶êEØj“ã2ÂVê)bêKڪסÃÖ«^õªÓ£Gš[dð ]_û"ý•a+“aœo}ûoN÷~Ð^ÂêKڪסÖLà-j5Häê¯[„­–‚üw|ÿO¯oeÉâÇJˆŸ ¶úäP–šj e¶P\ýu‹°EmÕ£D_•š[¬«g~÷ìÿÚSSm¡¬CÏLˆuÉÕ_/¼ôÕÓ›ßý¢½TµUš&Dêª.5ÕÊ:ô‘ËÀU—\ýu‹s¥¶êQÓ„H]Õ¥¦ÚBY‡>r¸ê’«¿[«iB¤®êRSm¡¬C¹ \uÉÕ_„-ŒÕ4!RWu©©¶PÖ¡\®ºäê/ÂÆJô•|<_>¦ŸuU—šj eúÈeàªK®þ"la¬D_ÉOÊPæF]Õ¥¦ÚBY‡>r¸ê’«¿[«iB¤®êRSm¡¬C¹ \uÉÕ_„-ŒÕ4!RWu©©¶PÖ¡\®ºäê/ÂÆjš©«ºÔT[(ëÐG.W]rõa c5MˆÔU]jª-”uè#—«.¹ú‹°…±š&Dêª.5ÕÊ:ô‘ËÀU—\ýõòW¾yºÇÇì¥2¨­zÔ4!RWu©©¶PÖ¡Ü£\ïÝ?¶—ê‘«¿d ’«$&Åz”è«7>ó‰Óg_~Å^ʇºªKMµ…²}丞<œîºÇ›Ç§{¹í¼ÜžØ›Fž<ÜÖé–À¶4댶[¸(ÑWò2µ¼\u•_Éñ°¦ÚBY‡>rK\NšÈONwN—«ûËÝwì¢ßÆ8„Ù€6ÚžfaëaËô³ÔÂx¹{èªmà~éëñRËn}º†~tº=k˜<0Kø‰ÅVdŸrÛSغ¦ÿöfRÓÆ1ÂnaûaC%ãñ½Ù¶¿êytŸ^?N>³Þ0YiÖ™"l(l9áj˜Põ1Wf²¾»3gig'b[swÝö'aËÙ‡ý)ÑW» [‰ý·ö‰WKR³×îoÉñ°¦ÚBYÛϺń¨>ÍKŽ£Iп<°/MšMjÖ öÉ9ë03ˆ-­3<Îp{¡ñêL#‡ã†-aW ³æjuLgƒS¿Í»‡ÇößË„­¼RöõšþÛ‹~Z:3—N=F'¨©¶PÖö3†n1!jäÉ`2{vª†g6©Y'ÀìSwûe¿ì³ÆÑÖ×ñ£»\xRÍÕ_Ç[™º™«Õs`š ñçÐ?LÚ— ¶òJÙ×kúOôc“dñ6 u#÷™Œ+OÆÎë›e)Hõc&®í«p·»=T÷ú}[&÷ϰU§íg„ •8|¡y*0˜¬)³®f€à>™m]•Õuæî‚rõ×áÃÖLßÍÕê%0…ÏŠ]îGØì2l¥öŸ6ú±e¼ sÿî:w¼é×»¬Ö]Õ_kæò\(Çý¾zã\hí¾­¨©¶PÖö3†J>ÿÀ² 0¯)³IÍ:Á}ò¶¥^Gªà>ä—«¿[©a«3©¹q­ÍMÖCØ%qâ*¥D_½ùÝ/ž^xé«öR>)ûšÞ3ãHhðût) ™Û¼1#tÝ@¶4ûÚ¯ã†B»ÿÃc÷mEMµ…²¶Ÿ6Tâ@ðÍM`s»ólÌš;ÀÇf€à>…ЕuýßÐ-…'Ñ\ýEØ ×M°Ï;ÎdíMXîm3a«p]\«D_=ý¾OŸžñ‹öR>)ûšÜÊñe®nÎã‚¿ 3Ž ·—Àc‰•ñÌÐìëÌvœýÝ·rßÜJÕÊÚ~FØP‰Á77 3ÐÍÄágaý}†p¦Yg*´Oþ}4ë84â•rõïÙ’ eÚsµjú}´KøõGØì7lEôŸ&Àt–Æ8anïÖ?ïÃÌ“¶yáqΡÙ×™1ÊÙÿè}[VSm¡¬íg„ ÝbB\œÀVê~MŠv°:šu|çÁï¼_ý`6ÞÏÕudPÿ]™©\ýõÍóíÓñóÿÔ^*ãµµÆôá$èôªÓw–Y?pý$0 “Ö½Ú[ƒ=‡-uÿÍ„“þX¿Üo®nãmÍmwA¸–GTûÚaþ:Îþ'ìÛ’šj em?#l¨Ø„hðîž,îA\ÇPÌ@0º=¢4ëŒÉú²Žs?ot Õu†Ûº%×5G#—ï{ËóöeäÜ×T¦œz²AË™`/†>÷MÓ°}ÂÖ`×a+¢ÿúc|\/3OÌüºY|2fßÙ'Ùn¸.{öq½Z’ÇöW½¯£uúËãuRöm^Mµ…²¶Ÿ6Tâ@hEpÝXÎþ:LØ’‰d¼,ôé\Ÿ“3y[ƒ}‡­Ž²ÿ„_GÁÛuãßÏ]ņšóíš³âþ}ºÅ{ܵ}õ·!·›öq¶“²oarÿÜ[uÚ~FØP‰¡sè–rö×Âtjš©«ºÔT[(ëÐG.×<ÂÖu¨­zÔ4!RWu©©¶PÖ¡\®ºäì/Â%úêÙ矜~û#Ÿ·—ò¡®êRSm¡¬C¹ \uÉÙ_„- JôÕsúœYr£®êRSm¡¬C¹ \uÉÙ_„- jš©«ºÔT[(ëÐG.W]röa ƒš&Dêª.5ÕÊ:ô‘ËÀU—œýEØÂ ¦ ‘ºªKMµ…²}ä2pÕ%g¶0¨iB¤®êRSm¡¬C¹­\{üú†käì/ù¹ùÙžR˜ëQÓ„H]Õ¥¦ÚBY‡>ro9p™o)îoî§Rz—o.ž~óq<ÂÖ<ù!jùAêRnY[×±?ir^Ò~–¤f%úJ>š/ÑÏ­žº‚¨©¶PÖ¡Ü[\ýOfÜ™9 Rö'4îºÀEØšÊÙ_µ‡­<}kÃýy;öräÏë˜}éþÞËRW`+ÑWò¥“ò哹©+ûãË5÷á^ÕT[(«ìŒ°sE®Ãï“=ýN™ÑOtwí¿„-_Îþ"luì$;ÞLV;ÑÎÿ8pÒïɯ¯¼š&ÄÜû:×WÃxuã®H³QÝhÔT[(«ìŒ°s¥'ıóà˜à s½ CèrGŽ~”Ñ.‰Ö9ÃÐÝšu¼IUn“Çu˸ÎuǾyì\[¢K—ZóÏt-3û°ðÄ!zÿ[«rîkLï3¤D!l¡eg„+=!ŽÃÖÌDt™<§a«ÇÁÈžQm£Q—uúËuüË£ÉrrŸñË Þu£ÍÜLÎþj-lÉe©™sŽûlÁe Ÿ¥šç5—_³þþŠñuýúv¿ír«“ÇÊmÿa+>;µ5óDm­þ¢¶q¾¿­ÍaÕèZÝLn÷öiþ1ómæFتSÙaçJsÌA? “gbý`Òç~Øß6âl#<ñ™d¸ãùÌYÑð®sÖ·´×ÝBÎþj1lÉcžkÀ†b¿&¦ì¤«Zwd-t{5®ª#Îl­Ê¶¯‘Oš‚õx¢¶TÑÛ0¤>Ç5‰3uãþnÅ>´ùû0yÌ|jª-”UvFعÂ'lyáÈ½Í [^ :–3§3¸™©XÜ%rBì„®»Ùß\š [±ýdëæ\ ãuM½¸g35wFت.l™þ:–áF³®×/Þu«ý«ÝÆ(|Í™¬¬Í“Týc¦ª©¶PVÙaçJsÜ@e/›ÉÌ?+U2l¶3â¬oi¯»…œýEزg†š4õqY߯׉™š; Mjkû§¨Ñjš³íëJÿ9}3ÔÆd‰èßÄmôì©ñ²¶4ãfgþ1ó}ͰU§²3ÂΕ8æL&¯á ¿—b|!¶ƒ¢`†û…ŸÅ9ÉÊà*VL«ô5'g>l…êÁNˆwÞ€ÿI‚ëòd¢§Ú¿Ð¤y%úê…—¾zzó»_´—òÉ·¯6ÀÌÔ‡Ó7Š~YíßÄmt×Nê̬wà°Uª¶PVÙaçJOˆcÓ3öÌÂdÀóÂVÇ N ›”þ:ýåñ:Þ™ C¶9!vB×ÝBÎþâÌÖLX²Ë­·0³}§ž¶Öb÷on‚,¬D_}òó_?½éÙì¥|rîkˆÃííôMŽ'j‰Û讜ì£YoòÄ5¶g¶·<æåTSm¡¬²3ÂΕžÇ‚/ËŸíMÖ0ƒB·¿Ã2=£0 oövó˜Î@â®ļG{Ý-È>çrÿŽ^þÊ7í¥ürîkˆß)ýdnwjÀ«µ>¶š»Þ° 7¬ù{ÿØÞ}r %úª†°ué+ òkÇ®wÕµ´mLjbx20 [Óºékl\‡3ORý;fTSm¡¬²3ÂΕ8PNÎþ’ÁJ­RJ×–?I„& ÍDÒOH£å¼¾˜ü'v½à6\ÎcuëLŸ ¸ë¬ìz6òX¹Õ¶¬óÙÌÑ2éw/ˆ'=Q‹ß†èƒúpŸûÓCà‰ë\ÝŒ¯—%ø$¶`¡ÉcæFتSÙaçJ('gÕ¶OM"uU—šj eúÈeàªKÎþ"laPÓ„H]Õ¥¦ÚBY‡>r¸ê’³¿[Ô4!RWu©©¶PÖ¡\®ºäì/Â%úê³/¿rzã3Ÿ°—ò¡®êRSm¡¬C¹ \uÉÙ_„- Jô•|­ˆ|½HnÔU]jª-”uè#—«.9û‹°…AM"uU—šj eúÈeàªKÎþ"laPÓ„H]Õ¥¦ÚBY‡>r¸ê’³¿[Ô4!RWu©©¶PÖ¡\®ºäì/Â5MˆÔU]jª-”uè#—«.9ûK~ÈU~еj«5MˆÔU]jª-”uè#—«.9ûëé÷}úôü‹_´—ò£¶êQÓ„H]Õ¥¦ÚBY‡>r¸ê’³¿[”è«o}ûoN÷~Ð^ʇºªKMµ…²}ä2pÕ%g¶0(ÕWß÷–çíÿò¡®êRSm¡¬C¹ \uÉÙ_„- jš©«ºÔT[(ëÐG.W]röa ƒš&Dêª.5ÕÊ:ô‘ËÀU—œýEØÂ ¦ ‘ºªKMµ…²}ä>õÔSæ``©c‘þÊ¥tØ¢¶êYrÖÕX‰ ‘ºªk©©¶PO“$AËJ‡­–POi˜u¨¯xÔV}[ â@\GØÒ£žÒÈÇóåcúXF}Å£¶êCØjƒ×:–õ”F¾xR¾€˨¯xÔV}[ bðZGØÒ£žÒ0!êP_ñ¨­ú¶Äàµî¹}Î,XG=¥aBÔ¡¾âQ[õ!l5ˆÁkaKzJĨC}Å£¶êCØjƒ×:–õ”† Q‡úŠGmÕ‡°Õ ¯u„-=ê) ¢õÚªa«A ^ë[zÔS&Dê+µUÂVƒ¼Ö¶ô¨§4o|æ§Ï¾üн„9ÔW„­1x­#léQOiÞôì §O~þëöæP_ñ¨­ú¶Äൎ°¥G=¥aBÔ¡¾âQ[õ!l5ˆÁkaKzJĨC}Å£¶êCØjƒ×:–õ”† Q‡úŠGmÕ‡°Õ ¯u„-=ê) ¢õÚªa«A ^ëäwå÷±ŽzJĨC}Å£¶êCØjƒ×:–õ”æÍï~ñôÂK_µ—0‡úŠGmÕ‡°Õ ¯u„-=ê)Ô—Ô–Q_ñ¨­ú¶Äൎ°¥G=¥aBÔ¡¾âQ[õ!l5ˆÁkaKzJĨC}Å£¶êCØjƒ×:–õ”† Q‡úŠGmÕ‡°Õ ¯u„-=ê) ¢õÚªa«A ^ë[zÔS&Dê+µUÂVƒ¼Ö¶ô¨§4Lˆ:ÔW„­1x­#léQOiž}þÉé·?òy{ s¨¯xÔV}[ bðZGØÒ£žÒð“P:ÔW„­1x­“o_–oaÆ:ê) ¢õÚªa«A ^ëäwÅä÷ŰŽzJĨC}Å£¶êCØjƒ×:–õ”† Q‡úŠGmÕ‡°Õ ¯u„-=ê) ¢õÚªa«A ^ë[zÔS&Dê+µUÂVƒ¼Ö¶ô¨§4òÑ|ùˆ>–Q_ñ¨­ú¶Äൎ°¥G=¥áëEt¨¯xÔV}[ bðZGØÒ£žÒ0!êP_ñ¨­ú¶Äൎ°¥G=¥aBÔ¡¾âQ[õ!l5ˆÁkaKzJĨC}Å£¶êCØjƒ×:–õ”† Q‡úŠGmÕ‡°Õ ¯u/å›§ûw|Ì^Âê) ¢õÚªa«A ^ëþôÏÿêôcOÔ^Âê) ¢õÚªa«A ^ë[zÔS~ì\‡úŠGmÕ‡°Õ ¯u„-=ê) ï Ô¡¾âQ[õ!l5ˆÁkaKzJĨC}Å£¶êCØjP‰ÁëU¯zÕéÑ£GÍ,ÿþSÿáé»ò¹àm-,Ò_¹”ž [«­aùþ£ï<}×þ£àmµ.9ëjPª¾Z­+Y¨­ú¶Tb𒃡5rv«U9û«tØj±¶­ÕX‰¾*U_-ו ¶êÒö_wP„-äì/Â%úаÑzQ "l!g¶0(ÑW„-ˆÖû‹jla 9û‹°…A‰¾"lA´Þ_Tcƒ[ÈÙ_„- Jôa ¢õþ¢DØBÎþ"laP¢¯[­÷ÕØ Âröa ƒ}EØ‚h½¿¨Æ¶³¿[”è+ÂDëýE56ˆ°…œýEØÂ D_¶ Zï/ª±A„-äì/Â%úаÑzQ "l!g¶0(ÑW„-ˆÖû‹jla 9û‹°…A‰¾"lA´Þ_Tcƒ[ÈÙ_„- Jôa ¢õþ¢DØBÎþ"laP¢¯[­÷ÕØ ÂV>ïÝ?¶—ꑳ¿[ÛØcí•è+ÂDëýE56¨¶°e&•nûÃr÷ðÄÞâò×ó'¢'w‹· Í:c„­–ÃÖ“ÓÃݨÝŸöÔÓ„­ëlWWHÑzQ ª)lõj4É=¾7åÎ1vR¼{èþÖ‡¨»Ó%§=>ÝwÛOVšu|„­VÃV ¦¤öjìÖ[×Ù¦®ªõþ¢TMØzòpºë¶ëÏ'f’Mz&$-N‚}hšœ3ÁmWšu¦[†­™ÚÛÂÖuZŸ¼[ÓzQ ª+lMƒNj8ÛÕŸ˜{iјَ3¡jÖ &<çåG/øÉ:²Kë 3Ü^z•Çȥɰ¥<«¹Ú¯S#Ãí—"So7×ûµWºpVÈ>äFØ‚h½¿¨ÆÕ¶¦Áà [öŒÔý½VœIköìT_³}Í:ç‰ð¼Ât’^_ÇŒîò♺ëåì¯6ÃÖPgÒwÓ$Ô}ï_õ­êöq-tu:<±0·uw~¢a—Å'…•è+ÂDëýE56¨š°ÕM1Ó÷b oZvÃÖâ:+AÊLNšuüÉÒ0ÛºLЫ묜=+!gµ¶ 猣ºtýêÕÔø:Õí+Aß»1¸O7T¢¯[­÷ÕØ z–°aj´ÜßÃÌL‡§• eæ&Í:óîe[êuÌßÚ‡üröWÓak`ƒ.DÛ>4ÿúu¼ÄÜ>_¡ÇîÓ ÉþçFØ‚h½¿¨ÆÕ¶¦ÌË;ç3Y3ak|V tAÄ® R+ë ̺Ýã¹gêòËÙ_‡[«…Õ~] K×Þzüà>ÝP‰¾"lA´Þ_Tcƒê[þâíK†ÁIoùìWð½_‹ëL…&7ÿ>šu+/‡œýEغpúu­¯¼°uÖ'ïÖ´Þ_Tcƒê [¡÷puL°OJ}pO:f‡;‘Õf_ŸñcÍ=öÂ:²ÿ£õÍå¥3äì¯&ÖôWg~}¬÷}¨^ea ·jÅ<þ¸n:¡ën©D_Õ¶úàÝõ‡³”=®[Vº¿¶Öö_wPõ„-;ÙŒ«™‰ÄØBé<9^¹Î˜¬/ë8÷›™W×në–Òó¥ãÉлn´™›ÉÙ_û [CK»{!F„ÎH™ë¦}®!Ûgãš n§£¹ïr¹ÁÏ\ök,X<ýýôc/ûW‚kúja ¢õþ¢ÔNØêL&ºñå‡-ò²œmL'*áLVfÒô&WïºÐD¨½îröמÖaCŽl'´WCÉZŸ-…­¥û®Õ‘Ýoog¡í çøkÓ1Ûß'Q‰q€°ÑzQ j*ly¦{›¶B“ŽOj3œ3±™Éµ»\ïç¯Ó_¯cÏÙÙŽwoòÒ^w 9ûk·aK‡ÓG¶½þ·õ$ý›˜ÄL˜Y¿ïJÉþïï…)w[þYݱ´zM‘³®„-ˆÖû‹jlP{a«|V? [ÂL,ÝþËt‚²“Óèvó˜Îdä®ã?öúDÛË5ÉÅ’}Îe·a«s RvñëÆ°;p[T?ÚÇnÒÝWQGçÛÜ@çlËÔ¿»nè,ïå¶õzM!ÛΰÑzQ ª=láz9ûkÏaKgé¬b”è+ÂDëýE56ˆ°…œýU}Ø žEŠ}EØ‚h½¿¨Æ¶³¿ê[öåµàË‹ˆU¢¯[­÷ÕØ ÂröWÝa 9•è+ÂDëýE56ˆ°…œýEØÂ D_¶ Zï/ª±A„-äì/Â%úаÑzQ "l!g¶0(ÑW„-ˆÖû‹jla 9û‹°…A‰¾"lA´Þ_Tcƒ[ÈÙ_„- Jôa ¢õþ¢DØBÎþ"laP¢¯[­÷ÕØ Âröa ƒ}EØ‚h½¿¨Æ¶³¿[”è+ÂDëýE56ˆ°…œýEØÂ D_¶ Zï/ª±A„-äì/Â%úаÑzQ "l!g¶0(ÑW„-ˆÖû‹jla 9û‹°…A‰¾"lA´Þ_Tcƒ[ÈÙ_„- Jôa ¢õþ¢Tbðzê©§ÌÁÐÒòßÿÓÁë[X¤¿r)¶Z¬-YZ¬¯œu5(U_­ÖÕ°´V_%jkO[ *=9¶‚vÒ¡ÒÐn:´SÚ­.„­qêÐN:´SÚM‡vJC»Õ…°Õ BÚI‡vJC»éÐNih·º¶ÄA¨C;éÐNih7Ú) íVÂVƒ8uh'Ú) í¦C;¥¡ÝêBØj¡í¤C;¥¡Ýth§4´[][ â Ô¡th§4´›픆v« a«A„:´“픆vÓ¡ÒÐnu!l5ˆƒP‡vÒ¡ÒÐn:´SÚ­.„­qêÐN:´SÚM‡vJC»Õ…°Õ BÚI‡vJC»éÐNih·º¶ÄA¨C;éÐNih7Ú) íVÂVƒ8uh'Ú) í¦C;¥¡ÝêBØj¡í¤C;¥¡Ýth§4´[][ â Ô¡th§4´›픆v« a«A„:´“픆vÓ¡ÒÐnu!l5ˆƒP‡vÒ¡ÒÐn:´SÚ­.„­qêÐN:´SÚM‡vJC»Õ…°Õ BÚI‡vJC»éÐNih·º¶ÄA¨C;éÐNih7Ú) íVÂVƒ8uh'Ú) í¦C;¥¡ÝêBØj¡í¤C;¥¡Ýth§4´[][ â Ô¡th§4´›픆v« a«A„:´“픆vÓ¡ÒÐnu!l5ˆƒP‡vÒ¡ÒÐn:´SÚ­.„­qêÐN:´SÚM‡vJC»Õ…°Õ BÚI‡vJC»éÐNih·º¶ÄA¨C;éÐNih7Ú) íVÂVƒ8uh'Ú) í¦C;¥¡ÝêBØj¡í¤C;¥¡Ýth§4´[][ â Ô¡th§4´›픆v« a«A„:´“픆vÓ¡ÒÐnu!l5ˆƒP‡vÒ¡ÒÐn:´SÚ­.„­qêÐN:´SÚM‡vJC»Õ…°Õ BÚI‡vJC»éÐNih·º¶ÄA¨C;éÐNih7Ú) íVÂVƒ8uh'Ú) í¦C;¥¡ÝêBØj¡í¤C;¥¡Ýth§4´[][ â Ô¡th§4´›픆v« a«A„:´“픆vÓ¡ÒÐnu!l5ˆƒP‡vÒ¡ÒÐn:´SÚ­.„­qêÐN:´SÚM‡vJC»Õ…°Õ BÚI‡vJC»éÐNih·º¶ÄA¨C;éÐNih7Ú) íVÂVƒ8uh'Ú) í¦C;¥¡ÝêBØj¡í¤C;¥¡Ýth§4´[][ â Ô¡th§4´›픆v« a«A„:´“픆vÓ¡ÒÐnu!l5ˆƒP‡vÒ¡ÒÐn:´SÚ­.„­qêÐN:´SÚM‡vJC»Õ…°Õ BÚI‡vJC»éÐNih·º¶ÄA¨C;éÐNih7Ú) íVÂVƒ8uh'Ú) í¦C;¥¡ÝêBØj¡ÎÓïû´ý–POi¨/ê+ õUÂVƒ>ùù¯Ûÿ×£žPõ…# lDØ(ˆ°Pa   Â@A„-€‚[¶ "lDØ(ˆ°Pa   Â@A„-€‚[¶ "lDØ(ˆ°Pa   °;O÷îžØËONwNîºÿ-Ñ®WÒöaÚŽå=y¸;=êöí¼ì¢½”BØ&úIÚ™ 'Ë}·V)Ç[“Àa—ûl »Ï°ÕÿÝw§Ën=œîäo¿âO oïÝûœ}m—ïC  „-`”n7ø!!ÀìÓhÂÞØ°åþ Ãä§­íxs¶}¼?ðñ½®ÍBæÚqõïž©k³/Êv+߇@[ÀÂV„ëÂÖð·_s–çbÇaËiŸkþæ+î»P×ÚÀT¾6¶€5«“ÒýéñðRP·Œ'÷á,Á°'~»ý~‘‰Ë ã Úþÿ¼¾]ÌÎÍѾ ‹ÿ·˜¿ÃÜÏN”v íïòßT&låoÇEûœï{^'ýed7È íí·ƒ–½¿¢½'êz¶­<„-@‡°¬QœM–œPÚŽ]0´&>s?ÿ1çÖ[ßËßq n(è­ÿMºIZ„&ê>TM¯+ÛŽÊ¿Émˆ+„ݞÝ?M[-9ÑØý µãH¿ÝåP©íCàè[Àš…Ii$Îd=ËÆ˜Ä… ë!a-lÍl¿c&ÄÑöB“æ°¿ËÛÌߤCÛ9‹*xäjGmûhÚ!ÂøLšÿ÷ûuÝ9p…¶9g%l…k•އÀ±¶€5«aK;9ú“ûÜ$î_0š°5 þ~÷—ý³óaäÂ_'°¯3Âû0?ù_ø™ØŽêö±÷›Y7Š­%³ïÃÿGmî­Ë~ªö5[ØJéCàX[Àšä°åN~çeØÐìdŸ7l'>ïþáIÞnÏÙÀÊßäïÂiÛ ÛžÙ¹ÇLmLjöÎÙ£¤@h[[ÃßâŸqLÓÿÝóui-ÔµèÿÞå¿S߇À±¶€5 “Ò|Ø L¬Ãuç %ž‘00YO}æf¸¼¶4Sh°`ÛMÚZó˜‰íÑ>{¿ø@aCÐåë Ë.¡ý‰¶¤ŒÅuü6Óõ!°faò˜”ƒ¹?ÍLh“ûz!A˜}ò×_o~ÂôÏ ¨ÂVÌßä„£°pÛy¤h;Îܯ³z†)ØþkÚÆÖX®3B‹aq°Z×ëIÕ‡[Àª”°˜pÎ/C®›Lj6 Èu‹ak)„ŒÖ Mšó×­ÙÒüM Â3×vîËWeÛQÕ>ÒÿÎߣÿ}ö~ík¿D.S›ÞúÃö–ÚGÌÔup;ómµÖ‡3a X“¶:Þ$*“—™„¼ Q¿ÈÕOæK!A8÷3Ûœ vÿ/Ëtûmù“¼Ýž;»®üMú 2ÛvvÏávÔ·Ïh„ u6y¼qmÙ}œ«©Àö.soاÉ÷3ËÌßÚ†¶ƒa   Â@A„-€‚[¶ "lDØ(ˆ°Pa   Â@A„-€‚[¶ "lDØ(ˆ°Pa   Â@A„-€‚[¶ "lDØ(ˆ°Pa   Â@A„-€‚[¶ "lDØ(ætúÿWA p’npJIEND®B`‚direwolf-1.5+dfsg/direwolf.c000066400000000000000000001116541347750676600161250ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: direwolf.c * * Purpose: Main program for "Dire Wolf" which includes: * * AFSK modem using the "sound card." * AX.25 encoder/decoder. * APRS data encoder / decoder. * APRS digipeater. * KISS TNC emulator. * APRStt (touch tone input) gateway * Internet Gateway (IGate) * * *---------------------------------------------------------------*/ #define DIREWOLF_C 1 #include "direwolf.h" #include #include #include #include #include #include #include #include #if __ARM__ //#include //#include // Doesn't seem to be there. // We have libc 2.13. Looks like we might need 2.17 & gcc 4.8 #endif #if __WIN32__ #else #include #include #include #include #ifdef __OpenBSD__ #include #elif __APPLE__ #else #include #endif #include #include #include #endif #if USE_HAMLIB #include #endif #include "version.h" #include "audio.h" #include "config.h" #include "multi_modem.h" #include "demod.h" #include "hdlc_rec.h" #include "hdlc_rec2.h" #include "ax25_pad.h" #include "xid.h" #include "decode_aprs.h" #include "textcolor.h" #include "server.h" #include "kiss.h" #include "kissnet.h" #include "kissserial.h" #include "kiss_frame.h" #include "waypoint.h" #include "gen_tone.h" #include "digipeater.h" #include "cdigipeater.h" #include "tq.h" #include "xmit.h" #include "ptt.h" #include "beacon.h" #include "dtmf.h" #include "aprs_tt.h" #include "tt_user.h" #include "igate.h" #include "pfilter.h" #include "symbols.h" #include "dwgps.h" #include "waypoint.h" #include "log.h" #include "recv.h" #include "morse.h" #include "mheard.h" #include "ax25_link.h" #include "dtime_now.h" //static int idx_decoded = 0; #if __WIN32__ static BOOL cleanup_win (int); #else static void cleanup_linux (int); #endif static void usage (char **argv); #if defined(__SSE__) && !defined(__APPLE__) static void __cpuid(int cpuinfo[4], int infotype){ __asm__ __volatile__ ( "cpuid": "=a" (cpuinfo[0]), "=b" (cpuinfo[1]), "=c" (cpuinfo[2]), "=d" (cpuinfo[3]): "a" (infotype) ); } #endif /*------------------------------------------------------------------- * * Name: main * * Purpose: Main program for packet radio virtual TNC. * * Inputs: Command line arguments. * See usage message for details. * * Outputs: Decoded information is written to stdout. * * A socket and pseudo terminal are created for * for communication with other applications. * *--------------------------------------------------------------------*/ static struct audio_s audio_config; static struct tt_config_s tt_config; static struct misc_config_s misc_config; static const int audio_amplitude = 100; /* % of audio sample range. */ /* This translates to +-32k for 16 bit samples. */ /* Currently no option to change this. */ static int d_u_opt = 0; /* "-d u" command line option to print UTF-8 also in hexadecimal. */ static int d_p_opt = 0; /* "-d p" option for dumping packets over radio. */ static int q_h_opt = 0; /* "-q h" Quiet, suppress the "heard" line with audio level. */ static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of decoded of APRS packets. */ int main (int argc, char *argv[]) { int err; //int eof; int j; char config_file[100]; int xmit_calibrate_option = 0; int enable_pseudo_terminal = 0; struct digi_config_s digi_config; struct cdigi_config_s cdigi_config; struct igate_config_s igate_config; int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */ char P_opt[16]; char l_opt_logdir[80]; char L_opt_logfile[80]; char input_file[80]; char T_opt_timestamp[40]; int t_opt = 1; /* Text color option. */ int a_opt = 0; /* "-a n" interval, in seconds, for audio statistics report. 0 for none. */ int d_k_opt = 0; /* "-d k" option for serial port KISS. Can be repeated for more detail. */ int d_n_opt = 0; /* "-d n" option for Network KISS. Can be repeated for more detail. */ int d_t_opt = 0; /* "-d t" option for Tracker. Can be repeated for more detail. */ int d_g_opt = 0; /* "-d g" option for GPS. Can be repeated for more detail. */ int d_o_opt = 0; /* "-d o" option for output control such as PTT and DCD. */ int d_i_opt = 0; /* "-d i" option for IGate. Repeat for more detail */ int d_m_opt = 0; /* "-d m" option for mheard list. */ int d_f_opt = 0; /* "-d f" option for filtering. Repeat for more detail. */ #if USE_HAMLIB int d_h_opt = 0; /* "-d h" option for hamlib debugging. Repeat for more detail */ #endif int E_tx_opt = 0; /* "-E n" Error rate % for clobbering trasmit frames. */ int E_rx_opt = 0; /* "-E Rn" Error rate % for clobbering receive frames. */ strlcpy(l_opt_logdir, "", sizeof(l_opt_logdir)); strlcpy(L_opt_logfile, "", sizeof(L_opt_logfile)); strlcpy(P_opt, "", sizeof(P_opt)); strlcpy(T_opt_timestamp, "", sizeof(T_opt_timestamp)); #if __WIN32__ // Select UTF-8 code page for console output. // http://msdn.microsoft.com/en-us/library/windows/desktop/ms686036(v=vs.85).aspx // This is the default I see for windows terminal: // >chcp // Active code page: 437 //Restore on exit? oldcp = GetConsoleOutputCP(); SetConsoleOutputCP(CP_UTF8); #else /* * Default on Raspian & Ubuntu Linux is fine. Don't know about others. * * Should we look at LANG environment variable and issue a warning * if it doesn't look something like en_US.UTF-8 ? */ #endif /* * Pre-scan the command line options for the text color option. * We need to set this before any text output. */ t_opt = 1; /* 1 = normal, 0 = no text colors. */ for (j=1; j= 1) { __cpuid (cpuinfo, 1); //dw_printf ("debug: cpuinfo = %x, %x, %x, %x\n", cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]); if ( ! ( cpuinfo[3] & (1 << 25))) { text_color_set(DW_COLOR_ERROR); dw_printf ("------------------------------------------------------------------\n"); dw_printf ("This version requires a minimum of a Pentium 3 or equivalent.\n"); dw_printf ("If you are seeing this message, you are probably using a computer\n"); dw_printf ("from the previous century. See comments in Makefile.win for\n"); dw_printf ("information on how you can recompile it for use with your antique.\n"); dw_printf ("------------------------------------------------------------------\n"); } } text_color_set(DW_COLOR_INFO); #endif /* * Default location of configuration file is current directory. * Can be overridden by -c command line option. * TODO: Automatically search other places. */ strlcpy (config_file, "direwolf.conf", sizeof(config_file)); /* * Look at command line options. * So far, the only one is the configuration file location. */ strlcpy (input_file, "", sizeof(input_file)); while (1) { //int this_option_optind = optind ? optind : 1; int option_index = 0; int c; char *p; static struct option long_options[] = { {"future1", 1, 0, 0}, {"future2", 0, 0, 0}, {"future3", 1, 0, 'c'}, {0, 0, 0, 0} }; /* ':' following option character means arg is required. */ c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:L:Sa:E:T:", long_options, &option_index); if (c == -1) break; switch (c) { case 0: /* possible future use */ text_color_set(DW_COLOR_DEBUG); dw_printf("option %s", long_options[option_index].name); if (optarg) { dw_printf(" with arg %s", optarg); } dw_printf("\n"); break; case 'a': /* -a for audio statistics interval */ a_opt = atoi(optarg); if (a_opt < 0) a_opt = 0; if (a_opt < 10) { text_color_set(DW_COLOR_ERROR); dw_printf("Setting such a small audio statistics interval will produce inaccurate sample rate display.\n"); } break; case 'c': /* -c for configuration file name */ strlcpy (config_file, optarg, sizeof(config_file)); break; #if __WIN32__ #else case 'p': /* -p enable pseudo terminal */ /* We want this to be off by default because it hangs */ /* eventually when nothing is reading from other side. */ enable_pseudo_terminal = 1; break; #endif case 'B': /* -B baud rate and modem properties. */ B_opt = atoi(optarg); if (B_opt < MIN_BAUD || B_opt > MAX_BAUD) { text_color_set(DW_COLOR_ERROR); dw_printf ("Use a more reasonable data baud rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); exit (EXIT_FAILURE); } break; case 'P': /* -P for modem profile. */ //debug: dw_printf ("Demodulator profile set to \"%s\"\n", optarg); strlcpy (P_opt, optarg, sizeof(P_opt)); break; case 'D': /* -D decrease AFSK demodulator sample rate */ D_opt = atoi(optarg); if (D_opt < 1 || D_opt > 8) { text_color_set(DW_COLOR_ERROR); dw_printf ("Crazy value for -D. \n"); exit (EXIT_FAILURE); } break; case 'x': /* -x for transmit calibration tones. */ xmit_calibrate_option = 1; break; case 'r': /* -r audio samples/sec. e.g. 44100 */ r_opt = atoi(optarg); if (r_opt < MIN_SAMPLES_PER_SEC || r_opt > MAX_SAMPLES_PER_SEC) { text_color_set(DW_COLOR_ERROR); dw_printf("-r option, audio samples/sec, is out of range.\n"); r_opt = 0; } break; case 'n': /* -n number of audio channels for first audio device. 1 or 2. */ n_opt = atoi(optarg); if (n_opt < 1 || n_opt > 2) { text_color_set(DW_COLOR_ERROR); dw_printf("-n option, number of audio channels, is out of range.\n"); n_opt = 0; } break; case 'b': /* -b bits per sample. 8 or 16. */ b_opt = atoi(optarg); if (b_opt != 8 && b_opt != 16) { text_color_set(DW_COLOR_ERROR); dw_printf("-b option, bits per sample, must be 8 or 16.\n"); b_opt = 0; } break; case '?': /* Unknown option message was already printed. */ usage (argv); break; case 'd': /* Set debug option. */ /* New in 1.1. Can combine multiple such as "-d pkk" */ for (p=optarg; *p!='\0'; p++) { switch (*p) { case 'a': server_set_debug(1); break; case 'k': d_k_opt++; kissserial_set_debug (d_k_opt); kisspt_set_debug (d_k_opt); break; case 'n': d_n_opt++; kiss_net_set_debug (d_n_opt); break; case 'u': d_u_opt = 1; break; // separate out gps & waypoints. case 'g': d_g_opt++; break; case 'w': waypoint_set_debug (1); break; // not documented yet. case 't': d_t_opt++; beacon_tracker_set_debug (d_t_opt); break; case 'p': d_p_opt = 1; break; // TODO: packet dump for xmit side. case 'o': d_o_opt++; ptt_set_debug(d_o_opt); break; case 'i': d_i_opt++; break; case 'm': d_m_opt++; break; case 'f': d_f_opt++; break; #if AX25MEMDEBUG case 'l': ax25memdebug_set(); break; // Track down memory Leak. Not documented. #endif // Previously 'm' but that is now used for mheard. #if USE_HAMLIB case 'h': d_h_opt++; break; // Hamlib verbose level. #endif default: break; } } break; case 'q': /* Set quiet option. */ /* New in 1.2. Quiet option to suppress some types of printing. */ /* Can combine multiple such as "-q hd" */ for (p=optarg; *p!='\0'; p++) { switch (*p) { case 'h': q_h_opt = 1; break; case 'd': q_d_opt = 1; break; default: break; } } break; case 't': /* Was handled earlier. */ break; case 'U': /* Print UTF-8 test and exit. */ dw_printf ("\n UTF-8 test string: ma%c%cana %c%c F%c%c%c%ce\n\n", 0xc3, 0xb1, 0xc2, 0xb0, 0xc3, 0xbc, 0xc3, 0x9f); exit (0); break; case 'l': /* -l for log directory with daily files */ strlcpy (l_opt_logdir, optarg, sizeof(l_opt_logdir)); break; case 'L': /* -L for log file name with full path */ strlcpy (L_opt_logfile, optarg, sizeof(L_opt_logfile)); break; case 'S': /* Print symbol tables and exit. */ symbols_init (); symbols_list (); exit (0); break; case 'E': /* -E Error rate (%) for corrupting frames. */ /* Just a number is transmit. Precede by R for receive. */ if (*optarg == 'r' || *optarg == 'R') { E_rx_opt = atoi(optarg+1); if (E_rx_opt < 1 || E_rx_opt > 99) { text_color_set(DW_COLOR_ERROR); dw_printf("-ER must be in range of 1 to 99.\n"); E_rx_opt = 10; } } else { E_tx_opt = atoi(optarg); if (E_tx_opt < 1 || E_tx_opt > 99) { text_color_set(DW_COLOR_ERROR); dw_printf("-E must be in range of 1 to 99.\n"); E_tx_opt = 10; } } break; case 'T': /* -T for receive timestamp. */ strlcpy (T_opt_timestamp, optarg, sizeof(T_opt_timestamp)); break; default: /* Should not be here. */ text_color_set(DW_COLOR_DEBUG); dw_printf("?? getopt returned character code 0%o ??\n", c); usage (argv); } } /* end while(1) for options */ if (optind < argc) { if (optind < argc - 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Warning: File(s) beyond the first are ignored.\n"); } strlcpy (input_file, argv[optind], sizeof(input_file)); } /* * Get all types of configuration settings from configuration file. * * Possibly override some by command line options. */ #if USE_HAMLIB rig_set_debug(d_h_opt); #endif symbols_init (); config_init (config_file, &audio_config, &digi_config, &cdigi_config, &tt_config, &igate_config, &misc_config); if (r_opt != 0) { audio_config.adev[0].samples_per_sec = r_opt; } if (n_opt != 0) { audio_config.adev[0].num_channels = n_opt; if (n_opt == 2) { audio_config.achan[1].valid = 1; } } if (b_opt != 0) { audio_config.adev[0].bits_per_sample = b_opt; } if (B_opt != 0) { audio_config.achan[0].baud = B_opt; /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ /* that need to be kept in sync. Maybe it could be a common function someday. */ if (audio_config.achan[0].baud < 600) { audio_config.achan[0].modem_type = MODEM_AFSK; audio_config.achan[0].mark_freq = 1600; // Typical for HF SSB. audio_config.achan[0].space_freq = 1800; audio_config.achan[0].decimate = 3; // Reduce CPU load. } else if (audio_config.achan[0].baud < 1800) { audio_config.achan[0].modem_type = MODEM_AFSK; audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ; audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ; } else if (audio_config.achan[0].baud < 3600) { audio_config.achan[0].modem_type = MODEM_QPSK; audio_config.achan[0].mark_freq = 0; audio_config.achan[0].space_freq = 0; if (audio_config.achan[0].baud != 2400) { text_color_set(DW_COLOR_ERROR); dw_printf ("Bit rate should be standard 2400 rather than specified %d.\n", audio_config.achan[0].baud); } } else if (audio_config.achan[0].baud < 7200) { audio_config.achan[0].modem_type = MODEM_8PSK; audio_config.achan[0].mark_freq = 0; audio_config.achan[0].space_freq = 0; if (audio_config.achan[0].baud != 4800) { text_color_set(DW_COLOR_ERROR); dw_printf ("Bit rate should be standard 4800 rather than specified %d.\n", audio_config.achan[0].baud); } } else { audio_config.achan[0].modem_type = MODEM_SCRAMBLE; audio_config.achan[0].mark_freq = 0; audio_config.achan[0].space_freq = 0; } } audio_config.statistics_interval = a_opt; if (strlen(P_opt) > 0) { /* -P for modem profile. */ /* TODO: Not yet documented. Should probably since it is consistent with atest. */ strlcpy (audio_config.achan[0].profiles, P_opt, sizeof(audio_config.achan[0].profiles)); } if (D_opt != 0) { // Reduce audio sampling rate to reduce CPU requirements. audio_config.achan[0].decimate = D_opt; } strlcpy(audio_config.timestamp_format, T_opt_timestamp, sizeof(audio_config.timestamp_format)); // temp - only xmit errors. audio_config.xmit_error_rate = E_tx_opt; audio_config.recv_error_rate = E_rx_opt; if (strlen(l_opt_logdir) > 0 && strlen(L_opt_logfile) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Logging options -l and -L can't be used together. Pick one or the other.\n"); exit(1); } if (strlen(L_opt_logfile) > 0) { misc_config.log_daily_names = 0; strlcpy (misc_config.log_path, L_opt_logfile, sizeof(misc_config.log_path)); } else if (strlen(l_opt_logdir) > 0) { misc_config.log_daily_names = 1; strlcpy (misc_config.log_path, l_opt_logdir, sizeof(misc_config.log_path)); } misc_config.enable_kiss_pt = enable_pseudo_terminal; if (strlen(input_file) > 0) { strlcpy (audio_config.adev[0].adevice_in, input_file, sizeof(audio_config.adev[0].adevice_in)); } /* * Open the audio source * - soundcard * - stdin * - UDP * Files not supported at this time. * Can always "cat" the file and pipe it into stdin. */ err = audio_open (&audio_config); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Pointless to continue without audio device.\n"); SLEEP_SEC(5); exit (1); } /* * Initialize the demodulator(s) and HDLC decoder. */ multi_modem_init (&audio_config); /* * Initialize the touch tone decoder & APRStt gateway. */ dtmf_init (&audio_config, audio_amplitude); aprs_tt_init (&tt_config); tt_user_init (&audio_config, &tt_config); /* * Should there be an option for audio output level? * Note: This is not the same as a volume control you would see on the screen. * It is the range of the digital sound representation. */ gen_tone_init (&audio_config, audio_amplitude, 0); morse_init (&audio_config, audio_amplitude); assert (audio_config.adev[0].bits_per_sample == 8 || audio_config.adev[0].bits_per_sample == 16); assert (audio_config.adev[0].num_channels == 1 || audio_config.adev[0].num_channels == 2); assert (audio_config.adev[0].samples_per_sec >= MIN_SAMPLES_PER_SEC && audio_config.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC); /* * Initialize the transmit queue. */ xmit_init (&audio_config, d_p_opt); /* * If -x option specified, transmit alternating tones for transmitter * audio level adjustment, up to 1 minute then quit. * TODO: enhance for more than one channel. */ if (xmit_calibrate_option) { int max_duration = 60; /* seconds */ int n = audio_config.achan[0].baud * max_duration; int chan = 0; text_color_set(DW_COLOR_INFO); dw_printf ("\nSending transmit calibration tones. Press control-C to terminate.\n"); ptt_set (OCTYPE_PTT, chan, 1); while (n-- > 0) { tone_gen_put_bit (chan, n & 1); } ptt_set (OCTYPE_PTT, chan, 0); exit (0); } /* * Initialize the digipeater and IGate functions. */ digipeater_init (&audio_config, &digi_config); igate_init (&audio_config, &igate_config, &digi_config, d_i_opt); cdigipeater_init (&audio_config, &cdigi_config); pfilter_init (&igate_config, d_f_opt); ax25_link_init (&misc_config); /* * Provide the AGW & KISS socket interfaces for use by a client application. */ server_init (&audio_config, &misc_config); kissnet_init (&misc_config); /* * Create a pseudo terminal and KISS TNC emulator. */ kisspt_init (&misc_config); kissserial_init (&misc_config); kiss_frame_init (&audio_config); /* * Open port for communication with GPS. */ dwgps_init (&misc_config, d_g_opt); waypoint_init (&misc_config); /* * Enable beaconing. * Open log file first because "-dttt" (along with -l...) will * log the tracker beacon transmissions with fake channel 999. */ log_init(misc_config.log_daily_names, misc_config.log_path); mheard_init (d_m_opt); beacon_init (&audio_config, &misc_config, &igate_config); /* * Get sound samples and decode them. * Use hot attribute for all functions called for every audio sample. */ recv_init (&audio_config); recv_process (); exit (EXIT_SUCCESS); } /*------------------------------------------------------------------- * * Name: app_process_rec_frame * * Purpose: This is called when we receive a frame with a valid * FCS and acceptable size. * * Inputs: chan - Audio channel number, 0 or 1. * subchan - Which modem caught it. * Special case -1 for DTMF decoder. * slice - Slicer which caught it. * pp - Packet handle. * alevel - Audio level, range of 0 - 100. * (Special case, use negative to skip * display of audio level line. * Use -2 to indicate DTMF message.) * retries - Level of bit correction used. * spectrum - Display of how well multiple decoders did. * * * Description: Print decoded packet. * Optionally send to another application. * *--------------------------------------------------------------------*/ // TODO: Use only one printf per line so output doesn't get jumbled up with stuff from other threads. void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) { char stemp[500]; unsigned char *pinfo; int info_len; char heard[AX25_MAX_ADDR_LEN]; //int j; int h; char display_retries[32]; assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= -1 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); assert (pp != NULL); // 1.1J+ strlcpy (display_retries, "", sizeof(display_retries)); if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) { snprintf (display_retries, sizeof(display_retries), " [%s] ", retry_text[(int)retries]); } ax25_format_addrs (pp, stemp); info_len = ax25_get_info (pp, &pinfo); /* Print so we can see what is going on. */ /* Display audio input level. */ /* Who are we hearing? Original station or digipeater. */ if (ax25_get_num_addr(pp) == 0) { /* Not AX.25. No station to display below. */ h = -1; strlcpy (heard, "", sizeof(heard)); } else { h = ax25_get_heard(pp); ax25_get_addr_with_ssid(pp, h, heard); } text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); if (( ! q_h_opt ) && alevel.rec >= 0) { /* suppress if "-q h" option */ if (h != -1 && h != AX25_SOURCE) { dw_printf ("Digipeater "); } char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE]; ax25_alevel_to_text (alevel, alevel_text); // Experiment: try displaying the DC bias. // Should be 0 for soundcard but could show mistuning with SDR. #if 0 char bias[16]; snprintf (bias, sizeof(bias), " DC%+d", multi_modem_get_dc_average (chan)); strlcat (alevel_text, bias, sizeof(alevel_text)); #endif /* As suggested by KJ4ERJ, if we are receiving from */ /* WIDEn-0, it is quite likely (but not guaranteed), that */ /* we are actually hearing the preceding station in the path. */ if (h >= AX25_REPEATER_2 && strncmp(heard, "WIDE", 4) == 0 && isdigit(heard[4]) && heard[5] == '\0') { char probably_really[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid(pp, h-1, probably_really); dw_printf ("%s (probably %s) audio level = %s %s %s\n", heard, probably_really, alevel_text, display_retries, spectrum); } else if (strcmp(heard, "DTMF") == 0) { dw_printf ("%s audio level = %s tt\n", heard, alevel_text); } else { dw_printf ("%s audio level = %s %s %s\n", heard, alevel_text, display_retries, spectrum); } } /* Version 1.2: Cranking the input level way up produces 199. */ /* Keeping it under 100 gives us plenty of headroom to avoid saturation. */ // TODO: suppress this message if not using soundcard input. // i.e. we have no control over the situation when using SDR. if (alevel.rec > 110) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio input level is too high. Reduce so most stations are around 50.\n"); } // Display non-APRS packets in a different color. // Display subchannel only when multiple modems configured for channel. // -1 for APRStt DTMF decoder. char ts[100]; // optional time stamp if (strlen(audio_config.timestamp_format) > 0) { char tstmp[100]; timestamp_user_format (tstmp, sizeof(tstmp), audio_config.timestamp_format); strlcpy (ts, " ", sizeof(ts)); // space after channel. strlcat (ts, tstmp, sizeof(ts)); } else { strlcpy (ts, "", sizeof(ts)); } if (subchan == -1) { text_color_set(DW_COLOR_REC); dw_printf ("[%d.dtmf%s] ", chan, ts); } else { if (ax25_is_aprs(pp)) { text_color_set(DW_COLOR_REC); } else { text_color_set(DW_COLOR_DECODED); } if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers == 1) { dw_printf ("[%d.%d%s] ", chan, subchan, ts); } else if (audio_config.achan[chan].num_subchan == 1 && audio_config.achan[chan].num_slicers > 1) { dw_printf ("[%d.%d%s] ", chan, slice, ts); } else if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers > 1) { dw_printf ("[%d.%d.%d%s] ", chan, subchan, slice, ts); } else { dw_printf ("[%d%s] ", chan, ts); } } dw_printf ("%s", stemp); /* stations followed by : */ /* Demystify non-APRS. Use same format for transmitted frames in xmit.c. */ if ( ! ax25_is_aprs(pp)) { ax25_frame_type_t ftype; cmdres_t cr; char desc[80]; int pf; int nr; int ns; ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns); /* Could change by 1, since earlier call, if we guess at modulo 128. */ info_len = ax25_get_info (pp, &pinfo); dw_printf ("(%s)", desc); if (ftype == frame_type_U_XID) { struct xid_param_s param; char info2text[150]; xid_parse (pinfo, info_len, ¶m, info2text, sizeof(info2text)); dw_printf (" %s\n", info2text); } else { ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) ); dw_printf ("\n"); } } else { // for APRS we generally want to display non-ASCII to see UTF-8. // for other, probably want to restrict to ASCII only because we are // more likely to have compressed data than UTF-8 text. // TODO: Might want to use d_u_opt for transmitted frames too. ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) ); dw_printf ("\n"); } // Also display in pure ASCII if non-ASCII characters and "-d u" option specified. if (d_u_opt) { unsigned char *p; int n = 0; for (p = pinfo; *p != '\0'; p++) { if (*p >= 0x80) n++; } if (n > 0) { text_color_set(DW_COLOR_DEBUG); ax25_safe_print ((char *)pinfo, info_len, 1); dw_printf ("\n"); } } /* Optional hex dump of packet. */ if (d_p_opt) { text_color_set(DW_COLOR_DEBUG); dw_printf ("------\n"); ax25_hex_dump (pp); dw_printf ("------\n"); } /* * Decode the contents of UI frames and display in human-readable form. * Could be APRS or anything random for old fashioned packet beacons. * * Suppress printed decoding if "-q d" option used. */ if (ax25_is_aprs(pp)) { decode_aprs_t A; // we still want to decode it for logging and other processing. // Just be quiet about errors if "-qd" is set. decode_aprs (&A, pp, q_d_opt); if ( ! q_d_opt ) { // Print it all out in human readable format unless "-q d" option used. decode_aprs_print (&A); } /* * Perform validity check on each address. * This should print an error message if any issues. */ (void)ax25_check_addresses(pp); // Send to log file. log_write (chan, &A, pp, alevel, retries); // temp experiment. //log_rr_bits (&A, pp); // Add to list of stations heard over the radio. mheard_save_rf (chan, &A, pp, alevel, retries); // Convert to NMEA waypoint sentence if we have a location. if (A.g_lat != G_UNKNOWN && A.g_lon != G_UNKNOWN) { waypoint_send_sentence (strlen(A.g_name) > 0 ? A.g_name : A.g_src, A.g_lat, A.g_lon, A.g_symbol_table, A.g_symbol_code, DW_FEET_TO_METERS(A.g_altitude_ft), A.g_course, DW_MPH_TO_KNOTS(A.g_speed_mph), A.g_comment); } } /* Send to another application if connected. */ // TODO: Put a wrapper around this so we only call one function to send by all methods. // We see the same sequence in tt_user.c. int flen; unsigned char fbuf[AX25_MAX_PACKET_LEN]; flen = ax25_pack(pp, fbuf); server_send_rec_packet (chan, pp, fbuf, flen); // AGW net protocol kissnet_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS TCP kissserial_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS serial port kisspt_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS pseudo terminal /* * If it came from DTMF decoder, send it to APRStt gateway. * Otherwise, it is a candidate for IGate and digipeater. * * TODO: It might be useful to have some way to simulate touch tone * sequences with BEACON sendto=R... for testing. */ if (subchan == -1) { if (tt_config.gateway_enabled && info_len >= 2) { aprs_tt_sequence (chan, (char*)(pinfo+1)); } } else { /* Send to Internet server if option is enabled. */ /* Consider only those with correct CRC. */ if (ax25_is_aprs(pp) && retries == RETRY_NONE) { igate_send_rec_packet (chan, pp); } /* Send out a regenerated copy. Applies to all types, not just APRS. */ /* This was an experimental feature never documented in the User Guide. */ /* Initial feedback was positive but it fell by the wayside. */ /* Should follow up with testers and either document this or clean out the clutter. */ digi_regen (chan, pp); /* * APRS digipeater. * Use only those with correct CRC; We don't want to spread corrupted data! */ if (ax25_is_aprs(pp) && retries == RETRY_NONE) { digipeater (chan, pp); } /* * Connected mode digipeater. * Use only those with correct CRC. */ if (retries == RETRY_NONE) { cdigipeater (chan, pp); } } } /* end app_process_rec_packet */ /* Process control C and window close events. */ #if __WIN32__ static BOOL cleanup_win (int ctrltype) { if (ctrltype == CTRL_C_EVENT || ctrltype == CTRL_CLOSE_EVENT) { text_color_set(DW_COLOR_INFO); dw_printf ("\nQRT\n"); log_term (); ptt_term (); waypoint_term (); dwgps_term (); SLEEP_SEC(1); ExitProcess (0); } return (TRUE); } #else static void cleanup_linux (int x) { text_color_set(DW_COLOR_INFO); dw_printf ("\nQRT\n"); log_term (); ptt_term (); dwgps_term (); SLEEP_SEC(1); exit(0); } #endif static void usage (char **argv) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n"); dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); dw_printf ("\n"); dw_printf ("Usage: direwolf [options] [ - | stdin | UDP:nnnn ]\n"); dw_printf ("Options:\n"); dw_printf (" -c fname Configuration file name.\n"); dw_printf (" -l logdir Directory name for log files. Use . for current.\n"); dw_printf (" -r n Audio sample rate, per sec.\n"); dw_printf (" -n n Number of audio channels, 1 or 2.\n"); dw_printf (" -b n Bits per audio sample, 8 or 16.\n"); dw_printf (" -B n Data rate in bits/sec for channel 0. Standard values are 300, 1200, 2400, 4800, 9600.\n"); dw_printf (" 300 bps defaults to AFSK tones of 1600 & 1800.\n"); dw_printf (" 1200 bps uses AFSK tones of 1200 & 2200.\n"); dw_printf (" 2400 bps uses QPSK based on V.26 standard.\n"); dw_printf (" 4800 bps uses 8PSK based on V.27 standard.\n"); dw_printf (" 9600 bps and up uses K9NG/G3RUH standard.\n"); dw_printf (" -D n Divide audio sample rate by n for channel 0.\n"); dw_printf (" -d Debug options:\n"); dw_printf (" a a = AGWPE network protocol client.\n"); dw_printf (" k k = KISS serial port or pseudo terminal client.\n"); dw_printf (" n n = KISS network client.\n"); dw_printf (" u u = Display non-ASCII text in hexadecimal.\n"); dw_printf (" p p = dump Packets in hexadecimal.\n"); dw_printf (" g g = GPS interface.\n"); dw_printf (" w w = Waypoints for Position or Object Reports.\n"); dw_printf (" t t = Tracker beacon.\n"); dw_printf (" o o = output controls such as PTT and DCD.\n"); dw_printf (" i i = IGate.\n"); dw_printf (" m m = Monitor heard station list.\n"); dw_printf (" f f = packet Filtering.\n"); #if USE_HAMLIB dw_printf (" h h = hamlib increase verbose level.\n"); #endif dw_printf (" -q Quiet (suppress output) options:\n"); dw_printf (" h h = Heard line with the audio level.\n"); dw_printf (" d d = Decoding of APRS packets.\n"); dw_printf (" -t n Text colors. 1=normal, 0=disabled.\n"); dw_printf (" -a n Audio statistics interval in seconds. 0 to disable.\n"); #if __WIN32__ #else dw_printf (" -p Enable pseudo terminal for KISS protocol.\n"); #endif dw_printf (" -x Send Xmit level calibration tones.\n"); dw_printf (" -U Print UTF-8 test string and exit.\n"); dw_printf (" -S Print symbol tables and exit.\n"); dw_printf (" -T fmt Time stamp format for sent and received frames.\n"); dw_printf ("\n"); dw_printf ("After any options, there can be a single command line argument for the source of\n"); dw_printf ("received audio. This can overrides the audio input specified in the configuration file.\n"); dw_printf ("\n"); #if __WIN32__ #else dw_printf ("Complete documentation can be found in /usr/local/share/doc/direwolf.\n"); #endif exit (EXIT_FAILURE); } /* end direwolf.c */ direwolf-1.5+dfsg/direwolf.h000066400000000000000000000173171347750676600161330ustar00rootroot00000000000000 /* direwolf.h - Common stuff used many places. */ // TODO: include this file first before anything else in each .c file. #ifndef DIREWOLF_H #define DIREWOLF_H 1 /* * Support Windows XP and later. * * We need this before "#include ". * * Don't know what other impact it might have on others. */ #if __WIN32__ #ifdef _WIN32_WINNT #error Include "direwolf.h" before any windows system files. #endif #ifdef WINVER #error Include "direwolf.h" before any windows system files. #endif #define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */ #define WINVER 0x0501 /* Minimum OS version is XP. */ #include #endif /* * Previously, we could handle only a single audio device. * This meant we could have only two radio channels. * In version 1.2, we relax this restriction and allow more audio devices. * Three is probably adequate for standard version. * Larger reasonable numbers should also be fine. * * For example, if you wanted to use 4 audio devices at once, change this to 4. */ #define MAX_ADEVS 3 /* * Maximum number of radio channels. * Note that there could be gaps. * Suppose audio device 0 was in mono mode and audio device 1 was stereo. * The channels available would be: * * ADevice 0: channel 0 * ADevice 1: left = 2, right = 3 * * TODO1.2: Look for any places that have * for (ch=0; ch>1) #define ADEVFIRSTCHAN(n) ((n) * 2) /* * Maximum number of modems per channel. * I called them "subchannels" (in the code) because * it is short and unambiguous. * Nothing magic about the number. Could be larger * but CPU demands might be overwhelming. */ #define MAX_SUBCHANS 9 /* * Each one of these can have multiple slicers, at * different levels, to compensate for different * amplitudes of the AFSK tones. * Intially used same number as subchannels but * we could probably trim this down a little * without impacting performance. */ #define MAX_SLICERS 9 #if __WIN32__ #define SLEEP_SEC(n) Sleep((n)*1000) #define SLEEP_MS(n) Sleep(n) #else #define SLEEP_SEC(n) sleep(n) #define SLEEP_MS(n) usleep((n)*1000) #endif #if __WIN32__ #define PTW32_STATIC_LIB //#include "pthreads/pthread.h" #define gmtime_r( _clock, _result ) \ ( *(_result) = *gmtime( (_clock) ), \ (_result) ) #else #include #endif #ifdef __APPLE__ // https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2072 // The original suggestion was to add this to only ptt.c. // I thought it would make sense to put it here, so it will apply to all files, // consistently, rather than only one file ptt.c. // The placement of this is critical. Putting it earlier was a problem. // https://github.com/wb2osz/direwolf/issues/113 // It needs to be after the include pthread.h because // pthread.h pulls in , which redefines __DARWIN_C_LEVEL back to ansi, // which breaks things. // Maybe it should just go in ptt.c as originally suggested. // #define __DARWIN_C_LEVEL __DARWIN_C_FULL // There is a more involved patch here: // https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458 #ifndef _DARWIN_C_SOURCE #define _DARWIN_C_SOURCE #endif // Defining _DARWIN_C_SOURCE ensures that the definition for the cfmakeraw function (or similar) // are pulled in through the include file . #ifdef __DARWIN_C_LEVEL #undef __DARWIN_C_LEVEL #endif #define __DARWIN_C_LEVEL __DARWIN_C_FULL #endif /* Not sure where to put these. */ /* Prefix with DW_ because /usr/include/gps.h uses a couple of these names. */ #ifndef G_UNKNOWN #include "latlong.h" #endif #define DW_METERS_TO_FEET(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 3.2808399) #define DW_FEET_TO_METERS(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.3048) #define DW_KM_TO_MILES(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.621371192) #define DW_KNOTS_TO_MPH(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 1.15077945) #define DW_KNOTS_TO_METERS_PER_SEC(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.51444444444) #define DW_MPH_TO_KNOTS(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.868976) #define DW_MPH_TO_METERS_PER_SEC(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.44704) #define DW_MBAR_TO_INHG(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.0295333727) #if __WIN32__ typedef CRITICAL_SECTION dw_mutex_t; #define dw_mutex_init(x) \ InitializeCriticalSection (x) /* This one waits for lock. */ #define dw_mutex_lock(x) \ EnterCriticalSection (x) /* Returns non-zero if lock was obtained. */ #define dw_mutex_try_lock(x) \ TryEnterCriticalSection (x) #define dw_mutex_unlock(x) \ LeaveCriticalSection (x) #else typedef pthread_mutex_t dw_mutex_t; #define dw_mutex_init(x) pthread_mutex_init (x, NULL) /* this one will wait. */ #define dw_mutex_lock(x) \ { \ int err; \ err = pthread_mutex_lock (x); \ if (err != 0) { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("INTERNAL ERROR %s %d pthread_mutex_lock returned %d", __FILE__, __LINE__, err); \ exit (1); \ } \ } /* This one returns true if lock successful, false if not. */ /* pthread_mutex_trylock returns 0 for success. */ #define dw_mutex_try_lock(x) \ ({ \ int err; \ err = pthread_mutex_trylock (x); \ if (err != 0 && err != EBUSY) { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("INTERNAL ERROR %s %d pthread_mutex_trylock returned %d", __FILE__, __LINE__, err); \ exit (1); \ } ; \ ! err; \ }) #define dw_mutex_unlock(x) \ { \ int err; \ err = pthread_mutex_unlock (x); \ if (err != 0) { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("INTERNAL ERROR %s %d pthread_mutex_unlock returned %d", __FILE__, __LINE__, err); \ exit (1); \ } \ } #endif // Formerly used write/read on Linux, for some forgotten reason, // but always using send/recv makes more sense. // Need option to prevent a SIGPIPE signal on Linux. (added for 1.5 beta 2) #if __WIN32__ || __APPLE__ #define SOCK_SEND(s,data,size) send(s,data,size,0) #else #define SOCK_SEND(s,data,size) send(s,data,size, MSG_NOSIGNAL) #endif #define SOCK_RECV(s,data,size) recv(s,data,size,0) /* Platform differences for string functions. */ #if __WIN32__ char *strsep(char **stringp, const char *delim); char *strtok_r(char *str, const char *delim, char **saveptr); #endif // Don't recall why for everyone. char *strcasestr(const char *S, const char *FIND); #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__) // strlcpy and strlcat should be in string.h and the C library. #else // Use our own copy // These prevent /usr/include/gps.h from providing its own definition. #define HAVE_STRLCAT 1 #define HAVE_STRLCPY 1 #define DEBUG_STRL 1 #if DEBUG_STRL #define strlcpy(dst,src,siz) strlcpy_debug(dst,src,siz,__FILE__,__func__,__LINE__) #define strlcat(dst,src,siz) strlcat_debug(dst,src,siz,__FILE__,__func__,__LINE__) size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line); size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line); #else #define strlcpy(dst,src,siz) strlcpy_debug(dst,src,siz) #define strlcat(dst,src,siz) strlcat_debug(dst,src,siz) size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz); size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz); #endif /* DEBUG_STRL */ #endif /* BSD or Apple */ #endif /* ifndef DIREWOLF_H */ direwolf-1.5+dfsg/direwolf.spec000066400000000000000000000065501347750676600166330ustar00rootroot00000000000000#%global git_commit b2548ec58f44f4b651626757a166b9f4f18d8000 %global git_commit 37179479caf0bf36adf8c9bc0fde641884edaeac %global git_date 20171216 %global git_short_commit %(echo %{git_commit} | cut -c -8) %global git_suffix %{git_date}git%{git_short_commit} Name: direwolf Version: 1.5Beta Release: 1.%{git_suffix}%{?dist} Summary: Soundcard based AX.25 TNC Group: Applications/Communications License: GPLv2 URL: https://github.com/wb2osz/direwolf #Source0: https://github.com/wb2osz/direwolf/archive/%{name}-%{version}.tar.gz Source: %{name}-%{version}-%{git_suffix}.tgz Packager: David Ranch (KI6ZHD) Distribution: RedHat Linux Patch0: direwolf-1.5-makefile.patch BuildRequires: automake BuildRequires: alsa-lib-devel #If the gpsd and gpsd-devel packages are installed, Direwolf will add gps support %description Dire Wolf is a software "soundcard" modem/TNC and APRS encoder/decoder. It can be used stand-alone to receive APRS messages, as a digipeater, APRStt gateway, or Internet Gateway (IGate). It can also be used as a virtual TNC for other applications such as APRSIS32, UI-View32, Xastir, APRS-TW, YAAC, UISS, Linux AX25, SARTrack, RMS Express, and many others. %prep %setup -q -n %{name}-%{version} %patch0 -p0 %build make -f Makefile.linux #make -f Makefile.linux tocalls-symbols make %{?_smp_mflags} %install make install INSTALLDIR=$RPM_BUILD_ROOT/usr make install-conf INSTALLDIR=$RPM_BUILD_ROOT/usr # Install icon mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/direwolf/ cp dw-icon.png ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/direwolf/ mv symbols-new.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/ mv symbolsX.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/ mv tocalls.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/ desktop-file-install \ --dir=${RPM_BUILD_ROOT}%{_datadir}/applications direwolf.desktop #temp bug #non echo "fixing $RPM_BUILD_ROOT/%{_bindir}/bin" #non rm -f $RPM_BUILD_ROOT/usr/bin %files %{_sysconfdir}/ax25/direwolf.conf %{_sysconfdir}/ax25/sdr.conf %{_sysconfdir}/ax25/telemetry-toolkit/telem-balloon.conf %{_sysconfdir}/ax25/telemetry-toolkit/telem-m0xer-3.txt %{_sysconfdir}/ax25/telemetry-toolkit/telem-volts.conf %{_sysconfdir}/udev/rules.d/99-direwolf-cmedia.rules %{_bindir}/* %{_datadir}/pixmaps/direwolf/dw-icon.png %{_datadir}/applications/%{name}.desktop %{_datadir}/direwolf/* %{_docdir}/* %{_mandir}/man1/* %changelog * Sat Dec 16 2017 David Ranch - 1.5-1 - New 1.5-Beta version from Git * Sun Apr 2 2017 David Ranch - 1.4-1 - New 1.4-Beta1 version from Git * Sun Mar 5 2017 David Ranch - 1.4-1 - New 1.4-H Alpha version from Git version * Fri Aug 26 2016 David Ranch - 1.4-1 - New version * Fri May 06 2016 David Ranch - 1.3-1 - New version * Sat Sep 12 2015 David Ranch - 1.3F-1 - New version with new features * Sun May 10 2015 David Ranch - 1.2E-1 - New version that supports a PASSALL function - Updated the Makefile.linux patch * Sat Mar 21 2015 David Ranch - 1.2C-1 - changed to support different make installation variable * Sat Feb 14 2015 David Ranch - 1.2b-1 - new spec file * Sat Dec 20 2014 David Ranch - 1.1b1-1 - new spec file direwolf-1.5+dfsg/direwolf.txt000066400000000000000000000433061347750676600165200ustar00rootroot00000000000000C############################################################# C# # C# Configuration file for Dire Wolf # C# # L# Linux version # W# Windows version # C# # C############################################################# R R R The sample config file was getting pretty messy R with the Windows and Linux differences. R It would be a maintenance burden to keep most of R two different versions in sync. R This common source is now used to generate the R two different variations while having only a single R copy of the common parts. R R The first column contains one of the following: R R R remark which is discarded. R C common to both versions. R W Windows version only. R L Linux version only. R C# C# Consult the User Guide for more details on configuration options. C# C# C# These are the most likely settings you might change: C# C# (1) MYCALL - call sign and SSID for your station. C# C# Look for lines starting with MYCALL and C# change NOCALL to your own. C# C# (2) PBEACON - enable position beaconing. C# C# Look for lines starting with PBEACON and C# modify for your call, location, etc. C# C# (3) DIGIPEATER - configure digipeating rules. C# C# Look for lines starting with DIGIPEATER. C# Most people will probably use the given example. C# Just remove the "#" from the start of the line C# to enable it. C# C# (4) IGSERVER, IGLOGIN - IGate server and login C# C# Configure an IGate client to relay messages between C# radio and internet servers. C# C# C# The default location is "direwolf.conf" in the current working directory. L# On Linux, the user's home directory will also be searched. C# An alternate configuration file location can be specified with the "-c" command line option. C# C# As you probably guessed by now, # indicates a comment line. C# C# Remove the # at the beginning of a line if you want to use a sample C# configuration that is currently commented out. C# C# Commands are a keyword followed by parameters. C# C# Command key words are case insensitive. i.e. upper and lower case are equivalent. C# C# Command parameters are generally case sensitive. i.e. upper and lower case are different. C# C C C############################################################# C# # C# FIRST AUDIO DEVICE PROPERTIES # C# (Channel 0 + 1 if in stereo) # C# # C############################################################# C C# C# Many people will simply use the default sound device. C# Some might want to use an alternative device by chosing it here. C# W# When the Windows version starts up, it displays something like W# this with the available sound devices and capabilities: W# W# Available audio input devices for receive (*=selected): W# * 0: Microphone (C-Media USB Headpho (channel 2) W# 1: Microphone (Bluetooth SCO Audio W# 2: Microphone (Bluetooth AV Audio) W# * 3: Microphone (Realtek High Defini (channels 0 & 1) W# Available audio output devices for transmit (*=selected): W# * 0: Speakers (C-Media USB Headphone (channel 2) W# 1: Speakers (Bluetooth SCO Audio) W# 2: Realtek Digital Output(Optical) W# 3: Speakers (Bluetooth AV Audio) W# * 4: Speakers (Realtek High Definiti (channels 0 & 1) W# 5: Realtek Digital Output (Realtek W# W# Example: To use the microphone and speaker connections on the W# system board, either of these forms can be used: W W#ADEVICE High W#ADEVICE 3 4 W W W# Example: To use the USB Audio, use a command like this with W# the input and output device numbers. (Remove the # comment character.) W#ADEVICE USB W W# The position in the list can change when devices (e.g. USB) are added and removed. W# You can also specify devices by using part of the name. W# Here is an example of specifying the USB Audio device. W# This is case-sensitive. Upper and lower case are not treated the same. W W#ADEVICE USB W W L# Linux ALSA is complicated. See User Guide for discussion. L# To use something other than the default, generally use plughw L# and a card number reported by "arecord -l" command. Example: L L# ADEVICE plughw:1,0 L L# Starting with version 1.0, you can also use "-" or "stdin" to L# pipe stdout from some other application such as a software defined L# radio. You can also specify "UDP:" and an optional port for input. L# Something different must be specified for output. L W# ADEVICE - 0 W# ADEVICE UDP:7355 0 L# ADEVICE - plughw:1,0 L# ADEVICE UDP:7355 default L L C C# C# Number of audio channels for this souncard: 1 or 2. C# C CACHANNELS 1 C#ACHANNELS 2 C C C############################################################# C# # C# SECOND AUDIO DEVICE PROPERTIES # C# (Channel 2 + 3 if in stereo) # C# # C############################################################# C C#ADEVICE1 ... C C C############################################################# C# # C# THIRD AUDIO DEVICE PROPERTIES # C# (Channel 4 + 5 if in stereo) # C# # C############################################################# C C#ADEVICE2 ... C C C############################################################# C# # C# CHANNEL 0 PROPERTIES # C# # C############################################################# C CCHANNEL 0 C C# C# The following MYCALL, MODEM, PTT, etc. configuration items C# apply to the most recent CHANNEL. C# C C# C# Station identifier for this channel. C# Multiple channels can have the same or different names. C# C# It can be up to 6 letters and digits with an optional ssid. C# The APRS specification requires that it be upper case. C# C# Example (don't use this unless you are me): MYCALL WB2OSZ-5 C# C CMYCALL N0CALL C C# C# Pick a suitable modem speed based on your situation. C# 1200 Most common for VHF/UHF. Default if not specified. C# 300 Low speed for HF SSB. C# 9600 High speed - Can't use Microphone and Speaker connections. C# C# In the simplest form, just specify the speed. C# C CMODEM 1200 C#MODEM 300 C#MODEM 9600 C C# C# These are the defaults should be fine for most cases. In special situations, C# you might want to specify different AFSK tones or the baseband mode which does C# not use AFSK. C# C#MODEM 1200 1200:2200 C#MODEM 300 1600:1800 C#MODEM 9600 0:0 C# C# C# On HF SSB, you might want to use multiple demodulators on slightly different C# frequencies to compensate for stations off frequency. Here we have 7 different C# demodulators at 30 Hz intervals. This takes a lot of CPU power so you will C# probably need to reduce the audio sampling rate with the /n option. C C#MODEM 300 1600:1800 7@30 /4 C C C# C# Uncomment line below to enable the DTMF decoder for this channel. C# C C#DTMF C C# C# If not using a VOX circuit, the transmitter Push to Talk (PTT) C# control is usually wired to a serial port with a suitable interface circuit. C# DON'T connect it directly! C# C# For the PTT command, specify the device and either RTS or DTR. C# RTS or DTR may be preceded by "-" to invert the signal. C# Both can be used for interfaces that want them driven with opposite polarity. C# L# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on. L# C C#PTT COM1 RTS C#PTT COM1 RTS -DTR L#PTT /dev/ttyUSB0 RTS C L# L# On Linux, you can also use general purpose I/O pins if L# your system is configured for user access to them. L# This would apply mostly to microprocessor boards, not a regular PC. L# See separate Raspberry Pi document for more details. L# The number may be preceded by "-" to invert the signal. L# L L#PTT GPIO 25 L C# The Data Carrier Detect (DCD) signal can be sent to the same places C# as the PTT signal. This could be used to light up an LED like a normal TNC. C C#DCD COM1 -DTR L#DCD GPIO 24 C C C############################################################# C# # C# CHANNEL 1 PROPERTIES # C# # C############################################################# C C#CHANNEL 1 C C# C# Specify MYCALL, MODEM, PTT, etc. configuration items for C# CHANNEL 1. Repeat for any other channels. C C C############################################################# C# # C# TEXT TO SPEECH COMMAND FILE # C# # C############################################################# C W#SPEECH dwespeak.bat L#SPEECH dwespeak.sh C C C############################################################# C# # C# VIRTUAL TNC SERVER PROPERTIES # C# # C############################################################# C C# C# Dire Wolf acts as a virtual TNC and can communicate with C# client applications by different protocols: C# C# - the "AGW TCPIP Socket Interface" - default port 8000 C# - KISS protocol over TCP socket - default port 8001 W# - KISS TNC via serial port L# - KISS TNC via pseudo terminal (-p command line option) C# C CAGWPORT 8000 CKISSPORT 8001 C W# W# Some applications are designed to operate with only a physical W# TNC attached to a serial port. For these, we provide a virtual serial W# port that appears to be connected to a TNC. W# W# Take a look at the User Guide for instructions to set up W# two virtual serial ports named COM3 and COM4 connected by W# a null modem. W# W# Using the configuration described, Dire Wolf will connect to W# COM3 and the client application will use COM4. W# W# Uncomment following line to use this feature. W W#SERIALKISS COM3 W W C# C# It is sometimes possible to recover frames with a bad FCS. C# This applies to all channels. C# C# 0 [NONE] - Don't try to repair. C# 1 [SINGLE] - Attempt to fix single bit error. (default) C# 2 [DOUBLE] - Also attempt to fix two adjacent bits. C# ... see User Guide for more values and in-depth discussion. C# C C#FIX_BITS 0 C C# C############################################################# C# # C# BEACONING PROPERTIES # C# # C############################################################# C C C# C# Beaconing is configured with these two commands: C# C# PBEACON - for a position report (usually yourself) C# OBEACON - for an object report (usually some other entity) C# C# Each has a series of keywords and values for options. C# See User Guide for details. C# C# Example: C# C# This results in a broadcast once every 10 minutes. C# Every half hour, it can travel via two digipeater hops. C# The others are kept local. C# C C#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 C#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" C#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" C C C# With UTM coordinates instead of latitude and longitude. C C#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 C C C# C# When the destination field is set to "SPEECH" the information part is C# converted to speech rather than transmitted as a data frame. C# C C#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm." C C C# C# Modify for your particular situation before removing C# the # comment character from the beginning of appropriate lines above. C# C C C############################################################# C# # C# DIGIPEATER PROPERTIES # C# # C############################################################# C C# C# For most common situations, use something like this by removing C# the "#" from the beginning of the line below. C# C C#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE C C# See User Guide for more explanation of what this means and how C# it can be customized for your particular needs. C C# Filtering can be used to limit was is digipeated. C# For example, only weather weather reports, received on channel 0, C# will be retransmitted on channel 1. C# C C#FILTER 0 1 t/wn C C C############################################################# C# # C# INTERNET GATEWAY # C# # C############################################################# C C# First you need to specify the name of a Tier 2 server. C# The current preferred way is to use one of these regional rotate addresses: C C# noam.aprs2.net - for North America C# soam.aprs2.net - for South America C# euro.aprs2.net - for Europe and Africa C# asia.aprs2.net - for Asia C# aunz.aprs2.net - for Oceania C C#IGSERVER noam.aprs2.net C C# You also need to specify your login name and passcode. C# Contact the author if you can't figure out how to generate the passcode. C C#IGLOGIN WB2OSZ-5 123456 C C# That's all you need for a receive only IGate which relays C# messages from the local radio channel to the global servers. C C# Some might want to send an IGate client position directly to a server C# without sending it over the air and relying on someone else to C# forward it to an IGate server. This is done by using sendto=IG rather C# than a radio channel number. Overlay R for receive only, T for two way. C C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W C C C# To relay messages from the Internet to radio, you need to add C# one more options with the transmit channel number and a VIA path. C C#IGTXVIA 0 WIDE1-1 C C# The APRS Internet Server (APRS-IS) has its own idea about what you C# should be transmitting. This includes "messages" addressed to stations C# recently heard in your area. For special situations, you can subscribe C# to C# decrease what you are already subscribed to. This is known as a server C# side filter. Read here: http://www.aprs-is.net/javaprsfilter.aspx C# Example, positions and objects within 50 km of my location: C C#IGFILTER m/50 C C# Sometimes the server will send you more than you want. You can also apply C# local filtering to limit what will be transmitted on the RF side. C# For example, transmit only "messages" (which is the default) on channel 0 C# and weather reports on channel 1. C C#FILTER IG 0 i/30 C#FILTER IG 1 t/wn C C# Finally, we don't want to flood the radio channel. C# The IGate function will limit the number of packets transmitted C# during 1 minute and 5 minute intervals. If a limit would C# be exceeded, the packet is dropped and message is displayed in red. C CIGTXLIMIT 6 10 C C C############################################################# C# # C# APRStt GATEWAY # C# # C############################################################# C C# C# Dire Wolf can receive DTMF (commonly known as Touch Tone) C# messages and convert them to packet objects. C# C# See separate "APRStt-Implementation-Notes" document for details. C# C C# C# Sample gateway configuration based on: C# C# http://www.aprs.org/aprstt/aprstt-coding24.txt C# http://www.aprs.org/aprs-jamboree-2013.html C# C C# Define specific points. C CTTPOINT B01 37^55.37N 81^7.86W CTTPOINT B7495088 42.605237 -71.34456 CTTPOINT B934 42.605237 -71.34456 C CTTPOINT B901 42.661279 -71.364452 CTTPOINT B902 42.660411 -71.364419 CTTPOINT B903 42.659046 -71.364452 CTTPOINT B904 42.657578 -71.364602 C C C# For location at given bearing and distance from starting point. C CTTVECTOR B5bbbddd 37^55.37N 81^7.86W 0.01 mi C C# For location specified by x, y coordinates. C CTTGRID Byyyxxx 37^50.00N 81^00.00W 37^59.99N 81^09.99W C C# UTM location for Lowell-Dracut-Tyngsborough State Forest. C CTTUTM B6xxxyyy 19T 10 300000 4720000 C C C C# Location for the corral. C CTTCORRAL 37^55.50N 81^7.00W 0^0.02N C C# Compact messages - Fixed locations xx and object yyy where C# Object numbers 100 - 199 = bicycle C# Object numbers 200 - 299 = fire truck C# Others = dog C CTTMACRO xx1yy B9xx*AB166*AA2B4C5B3B0A1yy CTTMACRO xx2yy B9xx*AB170*AA3C4C7C3B0A2yy CTTMACRO xxyyy B9xx*AB180*AA3A6C4A0Ayyy C CTTMACRO z Cz C C# Receive on channel 0, Transmit object reports on channel 1 with optional via path. C C#TTOBJ 0 1 WIDE1-1 C C# Advertise gateway position with beacon. C C# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway" C C direwolf-1.5+dfsg/dlq.c000066400000000000000000000737121347750676600150740ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2014, 2015, 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: dlq.c * * Purpose: Received frame queue. * * Description: In earlier versions, the main thread read from the * audio device and performed the receive demodulation/decoding. * * Since version 1.2 we have a separate receive thread * for each audio device. This queue is used to collect * received frames from all channels and process them * serially. * * In version 1.4, other types of events also go into this * queue and we use it to drive the data link state machine. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #if __WIN32__ #else #include #endif #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "dlq.h" #include "dedupe.h" #include "dtime_now.h" /* The queue is a linked list of these. */ static struct dlq_item_s *queue_head = NULL; /* Head of linked list for queue. */ #if __WIN32__ // TODO1.2: use dw_mutex_t static CRITICAL_SECTION dlq_cs; /* Critical section for updating queues. */ static HANDLE wake_up_event; /* Notify received packet processing thread when queue not empty. */ #else static pthread_mutex_t dlq_mutex; /* Critical section for updating queues. */ static pthread_cond_t wake_up_cond; /* Notify received packet processing thread when queue not empty. */ static pthread_mutex_t wake_up_mutex; /* Required by cond_wait. */ static volatile int recv_thread_is_waiting = 0; #endif static int was_init = 0; /* was initialization performed? */ static void append_to_queue (struct dlq_item_s *pnew); static volatile int s_new_count = 0; /* To detect memory leak for queue items. */ static volatile int s_delete_count = 0; // TODO: need to test. static volatile int s_cdata_new_count = 0; /* To detect memory leak for connected mode data. */ static volatile int s_cdata_delete_count = 0; // TODO: need to test. /*------------------------------------------------------------------- * * Name: dlq_init * * Purpose: Initialize the queue. * * Inputs: None. * * Outputs: * * Description: Initialize the queue to be empty and set up other * mechanisms for sharing it between different threads. * *--------------------------------------------------------------------*/ void dlq_init (void) { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_init ( )\n"); #endif queue_head = NULL; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_init: pthread_mutex_init...\n"); #endif #if __WIN32__ InitializeCriticalSection (&dlq_cs); #else int err; err = pthread_mutex_init (&wake_up_mutex, NULL); err = pthread_mutex_init (&dlq_mutex, NULL); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq_init: pthread_mutex_init err=%d", err); perror (""); exit (1); } #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_init: pthread_cond_init...\n"); #endif #if __WIN32__ wake_up_event = CreateEvent (NULL, 0, 0, NULL); if (wake_up_event == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq_init: pthread_cond_init: can't create receive wake up event"); exit (1); } #else err = pthread_cond_init (&wake_up_cond, NULL); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_init: pthread_cond_init returns %d\n", err); #endif if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq_init: pthread_cond_init err=%d", err); perror (""); exit (1); } recv_thread_is_waiting = 0; #endif was_init = 1; } /* end dlq_init */ /*------------------------------------------------------------------- * * Name: dlq_rec_frame * * Purpose: Add a received packet to the end of the queue. * Normally this was received over the radio but we can create * our own from APRStt or beaconing. * * This would correspond to PH-DATA Indication in the AX.25 protocol spec. * * Inputs: chan - Channel, 0 is first. * * subchan - Which modem caught it. * Special case -1 for APRStt gateway. * * slice - Which slice we picked. * * pp - Address of packet object. * Caller should NOT make any references to * it after this point because it could * be deleted at any time. * * alevel - Audio level, range of 0 - 100. * (Special case, use negative to skip * display of audio level line. * Use -2 to indicate DTMF message.) * * retries - Level of bit correction used. * * spectrum - Display of how well multiple decoders did. * * * IMPORTANT! Don't make an further references to the packet object after * giving it to dlq_append. * *--------------------------------------------------------------------*/ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) { struct dlq_item_s *pnew; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_rec_frame (chan=%d, pp=%p, ...)\n", chan, pp); #endif assert (chan >= 0 && chan < MAX_CHANS); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: dlq_rec_frame NULL packet pointer. Please report this!\n"); return; } #if AX25MEMDEBUG if (ax25memdebug_get()) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_rec_frame (chan=%d.%d, seq=%d, ...)\n", chan, subchan, ax25memdebug_seq(pp)); } #endif /* Allocate a new queue item. */ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); s_new_count++; if (s_new_count > s_delete_count + 50) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: DLQ memory leak, new=%d, delete=%d\n", s_new_count, s_delete_count); } pnew->nextp = NULL; pnew->type = DLQ_REC_FRAME; pnew->chan = chan; pnew->slice = slice; pnew->subchan = subchan; pnew->pp = pp; pnew->alevel = alevel; pnew->retries = retries; if (spectrum == NULL) strlcpy(pnew->spectrum, "", sizeof(pnew->spectrum)); else strlcpy(pnew->spectrum, spectrum, sizeof(pnew->spectrum)); /* Put it into queue. */ append_to_queue (pnew); } /* end dlq_rec_frame */ /*------------------------------------------------------------------- * * Name: append_to_queue * * Purpose: Append some type of event to queue. * This includes frames received over the radio, * requests from client applications, and notifications * from the frame transmission process. * * * Inputs: pnew - Pointer to queue element structure. * * Outputs: Information is appended to queue. * * Description: Add item to end of linked list. * Signal the receive processing thread if the queue was formerly empty. * *--------------------------------------------------------------------*/ static void append_to_queue (struct dlq_item_s *pnew) { struct dlq_item_s *plast; int queue_length = 0; if ( ! was_init) { dlq_init (); } pnew->nextp = NULL; #if DEBUG1 text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq append_to_queue: enter critical section\n"); #endif #if __WIN32__ EnterCriticalSection (&dlq_cs); #else int err; err = pthread_mutex_lock (&dlq_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq append_to_queue: pthread_mutex_lock err=%d", err); perror (""); exit (1); } #endif if (queue_head == NULL) { queue_head = pnew; queue_length = 1; } else { queue_length = 2; /* head + new one */ plast = queue_head; while (plast->nextp != NULL) { plast = plast->nextp; queue_length++; } plast->nextp = pnew; } #if __WIN32__ LeaveCriticalSection (&dlq_cs); #else err = pthread_mutex_unlock (&dlq_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq append_to_queue: pthread_mutex_unlock err=%d", err); perror (""); exit (1); } #endif #if DEBUG1 text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq append_to_queue: left critical section\n"); dw_printf ("dlq append_to_queue (): about to wake up recv processing thread.\n"); #endif /* * Bug: June 2015, version 1.2 * * It has long been known that we will eventually block trying to write to a * pseudo terminal if nothing is reading from the other end. There is even * a warning at start up time: * * Virtual KISS TNC is available on /dev/pts/2 * WARNING - Dire Wolf will hang eventually if nothing is reading from it. * Created symlink /tmp/kisstnc -> /dev/pts/2 * * In earlier versions, where the audio input and demodulation was in the main * thread, that would stop and it was pretty obvious something was wrong. * In version 1.2, the audio in / demodulating was moved to a device specific * thread. Packet objects are appended to this queue. * * The main thread should wake up and process them which includes printing and * forwarding to clients over multiple protocols and transport methods. * Just before the 1.2 release someone reported a memory leak which only showed * up after about 20 hours. It happened to be on a Cubie Board 2, which shouldn't * make a difference unless there was some operating system difference. * (cubieez 2.0 is based on Debian wheezy, just like Raspian.) * * The debug output revealed: * * It was using AX.25 for Linux (not APRS). * The pseudo terminal KISS interface was being used. * Transmitting was continuing fine. (So something must be writing to the other end.) * Frames were being received and appended to this queue. * They were not coming out of the queue. * * My theory is that writing to the the pseudo terminal is blocking so the * main thread is stopped. It's not taking anything from this queue and we detect * it as a memory leak. * * Add a new check here and complain if the queue is growing too large. * That will get us a step closer to the root cause. * This has been documented in the User Guide and the CHANGES.txt file which is * a minimal version of Release Notes. * The proper fix will be somehow avoiding or detecting the pseudo terminal filling up * and blocking on a write. */ if (queue_length > 10) { text_color_set(DW_COLOR_ERROR); dw_printf ("Received frame queue is out of control. Length=%d.\n", queue_length); dw_printf ("Reader thread is probably frozen.\n"); dw_printf ("This can be caused by using a pseudo terminal (direwolf -p) where another\n"); dw_printf ("application is not reading the frames from the other side.\n"); } #if __WIN32__ SetEvent (wake_up_event); #else if (recv_thread_is_waiting) { err = pthread_mutex_lock (&wake_up_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq append_to_queue: pthread_mutex_lock wu err=%d", err); perror (""); exit (1); } err = pthread_cond_signal (&wake_up_cond); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq append_to_queue: pthread_cond_signal err=%d", err); perror (""); exit (1); } err = pthread_mutex_unlock (&wake_up_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq append_to_queue: pthread_mutex_unlock wu err=%d", err); perror (""); exit (1); } } #endif } /* end append_to_queue */ /*------------------------------------------------------------------- * * Name: dlq_connect_request * * Purpose: Client application has requested connection to another station. * * Inputs: addrs - Source (owncall), destination (peercall), * and possibly digipeaters. * * num_addr - Number of addresses. 2 to 10. * * chan - Channel, 0 is first. * * client - Client application instance. We could have multiple * applications, all on the same channel, connecting * to different stations. We need to know which one * should get the results. * * pid - Protocol ID for data. Normally 0xf0 but the API * allows the client app to use something non-standard * for special situations. * TODO: remove this. PID is only for I and UI frames. * * Outputs: Request is appended to queue for processing by * the data link state machine. * *--------------------------------------------------------------------*/ void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid) { struct dlq_item_s *pnew; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_connect_request (...)\n"); #endif assert (chan >= 0 && chan < MAX_CHANS); /* Allocate a new queue item. */ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); s_new_count++; pnew->type = DLQ_CONNECT_REQUEST; pnew->chan = chan; memcpy (pnew->addrs, addrs, sizeof(pnew->addrs)); pnew->num_addr = num_addr; pnew->client = client; /* Put it into queue. */ append_to_queue (pnew); } /* end dlq_connect_request */ /*------------------------------------------------------------------- * * Name: dlq_disconnect_request * * Purpose: Client application has requested to disconnect. * * Inputs: addrs - Source (owncall), destination (peercall), * and possibly digipeaters. * * num_addr - Number of addresses. 2 to 10. * Only first two matter in this case. * * chan - Channel, 0 is first. * * client - Client application instance. We could have multiple * applications, all on the same channel, connecting * to different stations. We need to know which one * should get the results. * * Outputs: Request is appended to queue for processing by * the data link state machine. * *--------------------------------------------------------------------*/ void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client) { struct dlq_item_s *pnew; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_disconnect_request (...)\n"); #endif assert (chan >= 0 && chan < MAX_CHANS); /* Allocate a new queue item. */ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); s_new_count++; pnew->type = DLQ_DISCONNECT_REQUEST; pnew->chan = chan; memcpy (pnew->addrs, addrs, sizeof(pnew->addrs)); pnew->num_addr = num_addr; pnew->client = client; /* Put it into queue. */ append_to_queue (pnew); } /* end dlq_connect_request */ /*------------------------------------------------------------------- * * Name: dlq_xmit_data_request * * Purpose: Client application has requested transmission of connected * data over an established link. * * Inputs: addrs - Source (owncall), destination (peercall), * and possibly digipeaters. * * num_addr - Number of addresses. 2 to 10. * First two are used to uniquely identify link. * Any digipeaters involved are remembered * from when the link was established. * * chan - Channel, 0 is first. * * client - Client application instance. * * pid - Protocol ID for data. Normally 0xf0 but the API * allows the client app to use something non-standard * for special situations. * * xdata_ptr - Pointer to block of data. * * xdata_len - Length of data in bytes. * * Outputs: Request is appended to queue for processing by * the data link state machine. * *--------------------------------------------------------------------*/ void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid, char *xdata_ptr, int xdata_len) { struct dlq_item_s *pnew; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_xmit_data_request (...)\n"); #endif assert (chan >= 0 && chan < MAX_CHANS); /* Allocate a new queue item. */ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); s_new_count++; pnew->type = DLQ_XMIT_DATA_REQUEST; pnew->chan = chan; memcpy (pnew->addrs, addrs, sizeof(pnew->addrs)); pnew->num_addr = num_addr; pnew->client = client; /* Attach the transmit data. */ pnew->txdata = cdata_new(pid,xdata_ptr,xdata_len); /* Put it into queue. */ append_to_queue (pnew); } /* end dlq_xmit_data_request */ /*------------------------------------------------------------------- * * Name: dlq_register_callsign * dlq_unregister_callsign * * Purpose: Register callsigns that we will recognize for incoming connection requests. * * Inputs: addrs - Source (owncall), destination (peercall), * and possibly digipeaters. * * chan - Channel, 0 is first. * * client - Client application instance. * * Outputs: Request is appended to queue for processing by * the data link state machine. * * Description: The data link state machine does not use MYCALL from the APRS configuration. * For outgoing frames, the client supplies the source callsign. * For incoming connection requests, we need to know what address(es) to respond to. * * Note that one client application can register multiple callsigns for * multiple channels. * Different clients can register different different addresses on the same channel. * *--------------------------------------------------------------------*/ void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client) { struct dlq_item_s *pnew; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_register_callsign (%s, chan=%d, client=%d)\n", addr, chan, client); #endif assert (chan >= 0 && chan < MAX_CHANS); /* Allocate a new queue item. */ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); s_new_count++; pnew->type = DLQ_REGISTER_CALLSIGN; pnew->chan = chan; strlcpy (pnew->addrs[0], addr, AX25_MAX_ADDR_LEN); pnew->num_addr = 1; pnew->client = client; /* Put it into queue. */ append_to_queue (pnew); } /* end dlq_register_callsign */ void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client) { struct dlq_item_s *pnew; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_unregister_callsign (%s, chan=%d, client=%d)\n", addr, chan, client); #endif assert (chan >= 0 && chan < MAX_CHANS); /* Allocate a new queue item. */ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); s_new_count++; pnew->type = DLQ_UNREGISTER_CALLSIGN; pnew->chan = chan; strlcpy (pnew->addrs[0], addr, AX25_MAX_ADDR_LEN); pnew->num_addr = 1; pnew->client = client; /* Put it into queue. */ append_to_queue (pnew); } /* end dlq_unregister_callsign */ /*------------------------------------------------------------------- * * Name: dlq_channel_busy * * Purpose: Inform data link state machine about activity on the radio channel. * * Inputs: chan - Radio channel number. * * activity - OCTYPE_PTT or OCTYPE_DCD, as defined in audio.h. * Other values will be discarded. * * status - 1 for active or 0 for quiet. * * Outputs: Request is appended to queue for processing by * the data link state machine. * * Description: Notify the link state machine about changes in carrier detect * and our transmitter. * This is needed for pausing some of our timers. For example, * if we transmit a frame and expect a response in 3 seconds, that * might be delayed because someone else is using the channel. * *--------------------------------------------------------------------*/ void dlq_channel_busy (int chan, int activity, int status) { struct dlq_item_s *pnew; if (activity == OCTYPE_PTT || activity == OCTYPE_DCD) { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_channel_busy (...)\n"); #endif /* Allocate a new queue item. */ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); s_new_count++; pnew->type = DLQ_CHANNEL_BUSY; pnew->chan = chan; pnew->activity = activity; pnew->status = status; /* Put it into queue. */ append_to_queue (pnew); } } /* end dlq_channel_busy */ /*------------------------------------------------------------------- * * Name: dlq_seize_confirm * * Purpose: Inform data link state machine that the transmitter is on. * This is in reponse to lm_seize_request. * * Inputs: chan - Radio channel number. * * Outputs: Request is appended to queue for processing by * the data link state machine. * * Description: When removed from the data link state machine queue, this * becomes lm_seize_confirm. * *--------------------------------------------------------------------*/ void dlq_seize_confirm (int chan) { struct dlq_item_s *pnew; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_seize_confirm (chan=%d)\n", chan); #endif /* Allocate a new queue item. */ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); s_new_count++; pnew->type = DLQ_SEIZE_CONFIRM; pnew->chan = chan; /* Put it into queue. */ append_to_queue (pnew); } /* end dlq_seize_confirm */ /*------------------------------------------------------------------- * * Name: dlq_client_cleanup * * Purpose: Client application has disappeared. * i.e. The TCP connection has been broken. * * Inputs: client - Client application instance. * * Outputs: Request is appended to queue for processing by * the data link state machine. * * Description: Notify the link state machine that given client has gone away. * Clean up all information related to that client application. * *--------------------------------------------------------------------*/ void dlq_client_cleanup (int client) { struct dlq_item_s *pnew; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_client_cleanup (...)\n"); #endif // assert (client >= 0 && client < MAX_NET_CLIENTS); /* Allocate a new queue item. */ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); s_new_count++; // All we care about is the client number. pnew->type = DLQ_CLIENT_CLEANUP; pnew->client = client; /* Put it into queue. */ append_to_queue (pnew); } /* end dlq_client_cleanup */ /*------------------------------------------------------------------- * * Name: dlq_wait_while_empty * * Purpose: Sleep while the received data queue is empty rather than * polling periodically. * * Inputs: timeout - Return at this time even if queue is empty. * Zero for no timeout. * * Returns: True if timed out before any event arrived. * * Description: In version 1.4, we add timeout option so we can continue after * some amount of time even if no events are in the queue. * *--------------------------------------------------------------------*/ int dlq_wait_while_empty (double timeout) { int timed_out_result = 0; #if DEBUG1 text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_wait_while_empty (%.3f)\n", timeout); #endif if ( ! was_init) { dlq_init (); } if (queue_head == NULL) { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_wait_while_empty (): prepare to SLEEP...\n"); #endif #if __WIN32__ if (timeout != 0.0) { DWORD ms = (timeout - dtime_now()) * 1000; if (ms <= 0) ms = 1; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("WaitForSingleObject: timeout after %d ms\n", ms); #endif if (WaitForSingleObject (wake_up_event, ms) == WAIT_TIMEOUT) { timed_out_result = 1; } } else { WaitForSingleObject (wake_up_event, INFINITE); } #else int err; err = pthread_mutex_lock (&wake_up_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq_wait_while_empty: pthread_mutex_lock wu err=%d", err); perror (""); exit (1); } recv_thread_is_waiting = 1; if (timeout != 0.0) { struct timespec abstime; abstime.tv_sec = (time_t)(long)timeout; abstime.tv_nsec = (long)((timeout - (long)abstime.tv_sec) * 1000000000.0); err = pthread_cond_timedwait (&wake_up_cond, &wake_up_mutex, &abstime); if (err == ETIMEDOUT) { timed_out_result = 1; } } else { err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); } recv_thread_is_waiting = 0; err = pthread_mutex_unlock (&wake_up_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq_wait_while_empty: pthread_mutex_unlock wu err=%d", err); perror (""); exit (1); } #endif } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_wait_while_empty () returns timedout=%d\n", timed_out_result); #endif return (timed_out_result); } /* end dlq_wait_while_empty */ /*------------------------------------------------------------------- * * Name: dlq_remove * * Purpose: Remove an item from the head of the queue. * * Inputs: None. * * Returns: Pointer to a queue item. Caller is responsible for deleting it. * NULL if queue is empty. * *--------------------------------------------------------------------*/ struct dlq_item_s *dlq_remove (void) { struct dlq_item_s *result = NULL; //int err; #if DEBUG1 text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_remove() enter critical section\n"); #endif if ( ! was_init) { dlq_init (); } #if __WIN32__ EnterCriticalSection (&dlq_cs); #else int err; err = pthread_mutex_lock (&dlq_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq_remove: pthread_mutex_lock err=%d", err); perror (""); exit (1); } #endif if (queue_head != NULL) { result = queue_head; queue_head = queue_head->nextp; } #if __WIN32__ LeaveCriticalSection (&dlq_cs); #else err = pthread_mutex_unlock (&dlq_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("dlq_remove: pthread_mutex_unlock err=%d", err); perror (""); exit (1); } #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_remove() returns \n"); #endif #if AX25MEMDEBUG if (ax25memdebug_get() && result != NULL) { text_color_set(DW_COLOR_DEBUG); if (result->pp != NULL) { // TODO: mnemonics for type. dw_printf ("dlq_remove (type=%d, chan=%d.%d, seq=%d, ...)\n", result->type, result->chan, result->subchan, ax25memdebug_seq(result->pp)); } else { dw_printf ("dlq_remove (type=%d, chan=%d, ...)\n", result->type, result->chan); } } #endif return (result); } /*------------------------------------------------------------------- * * Name: dlq_delete * * Purpose: Release storage used by a queue item. * * Inputs: pitem - Pointer to a queue item. * *--------------------------------------------------------------------*/ void dlq_delete (struct dlq_item_s *pitem) { if (pitem == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: dlq_delete() given NULL pointer.\n"); return; } s_delete_count++; if (pitem->pp != NULL) { ax25_delete (pitem->pp); pitem->pp = NULL; } if (pitem->txdata != NULL) { cdata_delete (pitem->txdata); pitem->txdata = NULL; } free (pitem); } /* end dlq_delete */ /*------------------------------------------------------------------- * * Name: cdata_new * * Purpose: Allocate blocks of data for sending and receiving connected data. * * Inputs: pid - protocol id. * data - pointer to data. Can be NULL for segment reassembler. * len - length of data. * * Returns: Structure with a copy of the data. * * Description: The flow goes like this: * * Client application extablishes a connection with another station. * Client application calls "dlq_xmit_data_request." * A copy of the data is made with this function and attached to the queue item. * The txdata block is attached to the appropriate link state machine. * At the proper time, it is transmitted in an I frame. * It needs to be kept around in case it needs to be retransmitted. * When no longer needed, it is freed with cdata_delete. * *--------------------------------------------------------------------*/ cdata_t *cdata_new (int pid, char *data, int len) { int size; cdata_t *cdata; s_cdata_new_count++; /* Round up the size to the next 128 bytes. */ /* The theory is that a smaller number of unique sizes might be */ /* beneficial for memory fragmentation and garbage collection. */ size = ( len + 127 ) & ~0x7f; cdata = malloc ( sizeof(cdata_t) + size ); cdata->magic = TXDATA_MAGIC; cdata->next = NULL; cdata->pid = pid; cdata->size = size; cdata->len = len; assert (len >= 0 && len <= size); if (data == NULL) { memset (cdata->data, '?', size); } else { memcpy (cdata->data, data, len); } return (cdata); } /* end cdata_new */ /*------------------------------------------------------------------- * * Name: cdata_delete * * Purpose: Release storage used by a connected data block. * * Inputs: cdata - Pointer to a data block. * *--------------------------------------------------------------------*/ void cdata_delete (cdata_t *cdata) { if (cdata == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: cdata_delete() given NULL pointer.\n"); return; } if (cdata->magic != TXDATA_MAGIC) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: cdata_delete() given corrupted data.\n"); return; } s_cdata_delete_count++; cdata->magic = 0; free (cdata); } /* end cdata_delete */ /*------------------------------------------------------------------- * * Name: cdata_check_leak * * Purpose: Check for memory leak of cdata items. * * Description: This is called when we expect no outstanding allocations. * *--------------------------------------------------------------------*/ void cdata_check_leak (void) { if (s_cdata_delete_count != s_cdata_new_count) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal Error, %s, new=%d, delete=%d\n", __func__, s_cdata_new_count, s_cdata_delete_count); } } /* end cdata_check_leak */ /* end dlq.c */ direwolf-1.5+dfsg/dlq.h000066400000000000000000000067661347750676600151060ustar00rootroot00000000000000 /*------------------------------------------------------------------ * * Module: dlq.h * *---------------------------------------------------------------*/ #ifndef DLQ_H #define DLQ_H 1 #include "ax25_pad.h" #include "audio.h" /* A transmit or receive data block for connected mode. */ typedef struct cdata_s { int magic; /* For integrity checking. */ #define TXDATA_MAGIC 0x09110911 struct cdata_s *next; /* Pointer to next when part of a list. */ int pid; /* Protocol id. */ int size; /* Number of bytes allocated. */ int len; /* Number of bytes actually used. */ char data[]; /* Variable length data. */ } cdata_t; /* Types of things that can be in queue. */ typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST, DLQ_REGISTER_CALLSIGN, DLQ_UNREGISTER_CALLSIGN, DLQ_CHANNEL_BUSY, DLQ_SEIZE_CONFIRM, DLQ_CLIENT_CLEANUP} dlq_type_t; /* A queue item. */ // TODO: call this event rather than item. // TODO: should add fences. typedef struct dlq_item_s { struct dlq_item_s *nextp; /* Next item in queue. */ dlq_type_t type; /* Type of item. */ /* See enum definition above. */ int chan; /* Radio channel of origin. */ // I'm not worried about amount of memory used but this might be a // little clearer if a union was used for the different event types. // Used for received frame. int subchan; /* Winning "subchannel" when using multiple */ /* decoders on one channel. */ /* Special case, -1 means DTMF decoder. */ /* Maybe we should have a different type in this case? */ int slice; /* Winning slicer. */ packet_t pp; /* Pointer to frame structure. */ alevel_t alevel; /* Audio level. */ retry_t retries; /* Effort expended to get a valid CRC. */ char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */ // Used by requests from a client application, connect, etc. char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN]; int num_addr; /* Range 2 .. 10. */ int client; // Used only by client request to transmit connected data. cdata_t *txdata; // Used for channel activity change. // It is useful to know when the channel is busy either for carrier detect // or when we are transmitting. int activity; /* OCTYPE_PTT for my transmission start/end. */ /* OCTYPE_DCD if we hear someone else. */ int status; /* 1 for active or 0 for quiet. */ } dlq_item_t; void dlq_init (void); void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum); void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid); void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client); void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid, char *xdata_ptr, int xdata_len); void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client); void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client); void dlq_channel_busy (int chan, int activity, int status); void dlq_seize_confirm (int chan); void dlq_client_cleanup (int client); int dlq_wait_while_empty (double timeout_val); struct dlq_item_s *dlq_remove (void); void dlq_delete (struct dlq_item_s *pitem); cdata_t *cdata_new (int pid, char *data, int len); void cdata_delete (cdata_t *txdata); void cdata_check_leak (void); #endif /* end dlq.h */ direwolf-1.5+dfsg/doc/000077500000000000000000000000001347750676600147035ustar00rootroot00000000000000direwolf-1.5+dfsg/doc/README.md000066400000000000000000000151111347750676600161610ustar00rootroot00000000000000# Documentation for Dire Wolf # Click on the document name to view in your web browser or the link following to download the PDF file. ## Essential Reading ## - [**User Guide**](User-Guide.pdf) [ [*download*](../../../raw/master/doc/User-Guide.pdf) ] This is your primary source of information about installation, operation, and configuration. - [**Raspberry Pi APRS**](Raspberry-Pi-APRS.pdf) [ [*download*](../../../raw/master/doc/Raspberry-Pi-APRS.pdf) ] The Raspberry Pi has some special considerations that make it different from other generic Linux systems. Start here if using the Raspberry Pi, Beaglebone Black, cubieboard2, or similar single board computers. ## Application Notes ## These dive into more detail for specialized topics or typical usage scenarios. - [**Successful APRS IGate Operation**](Successful-APRS-IGate-Operation.pdf) [ [*download*](../../../raw/master/doc/Successful-APRS-IGate-Operation.pdf) ] Dire Wolf can serve as a gateway between the APRS radio network and APRS-IS servers on the Internet. This explains how it all works, proper configuration, and troubleshooting. - [**Bluetooth KISS TNC**](Bluetooth-KISS-TNC.pdf) [ [*download*](../../../raw/master/doc/Bluetooth-KISS-TNC.pdf) ] Eliminate the cable between your TNC and application. Use Bluetooth instead. - [**APRStt Implementation Notes**](APRStt-Implementation-Notes.pdf) [ [*download*](../../../raw/master/doc/APRStt-Implementation-Notes.pdf) ] Very few hams have portable equipment for APRS but nearly everyone has a handheld radio that can send DTMF tones. APRStt allows a user, equipped with only DTMF (commonly known as Touch Tone) generation capability, to enter information into the global APRS data network. This document explains how the APRStt concept was implemented in the Dire Wolf application. - [**APRStt Interface for SARTrack**](APRStt-interface-for-SARTrack.pdf) [ [*download*](../../../raw/master/doc/APRStt-interface-for-SARTrack.pdf) ] This example illustrates how APRStt can be integrated with other applications such as SARTrack, APRSISCE/32, YAAC, or Xastir. - [**APRStt Listening Example**](APRStt-Listening-Example.pdf) [ [*download*](../../../raw/master/doc/APRStt-Listening-Example.pdf) ] WB4APR described a useful application for the [QIKCOM-2 Satallite Transponder](http://www.tapr.org/pipermail/aprssig/2015-November/045035.html). Don’t have your own QIKCOM-2 Satellite Transponder? No Problem. You can do the same thing with an ordinary computer and the APRStt gateway built into Dire Wolf. Here’s how. - [**Raspberry Pi APRS Tracker**](Raspberry-Pi-APRS-Tracker.pdf) [ [*download*](../../../raw/master/doc/Raspberry-Pi-APRS-Tracker.pdf) ] Build a tracking device which transmits position from a GPS receiver. - [**Raspberry Pi SDR IGate**](Raspberry-Pi-SDR-IGate.pdf) [ [*download*](../../../raw/master/doc/Raspberry-Pi-SDR-IGate.pdf) ] It's easy to build a receive-only APRS Internet Gateway (IGate) with only a Raspberry Pi and a software defined radio (RTL-SDR) dongle. Here’s how. - [**APRS Telemetry Toolkit**](APRS-Telemetry-Toolkit.pdf) [ [*download*](../../../raw/master/doc/APRS-Telemetry-Toolkit.pdf) ] Describes scripts and methods to generate telemetry. Includes a complete example of attaching an analog to digital converter to a Raspberry Pi and transmitting a measured voltage. - [**2400 & 4800 bps PSK for APRS / Packet Radio**](2400-4800-PSK-for-APRS-Packet-Radio.pdf) [ [*download*](../../../raw/master/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf) ] Double or quadruple your data rate by sending multiple bits at the same time. - [**Going beyond 9600 baud**](Going-beyond-9600-baud.pdf) [ [*download*](../../../raw/master/doc/Going-beyond-9600-baud.pdf) ] Why stop at 9600 baud? Go faster if your soundcard and radio can handle it. ## Miscellaneous ## - [**A Better APRS Packet Demodulator, part 1, 1200 baud**](A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf) [ [*download*](../../../raw/master/doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf) ] Sometimes it's a little mystifying why an APRS / AX.25 Packet TNC will decode some signals and not others. A weak signal, buried in static, might be fine while a nice strong clean sounding signal is not decoded. Here we will take a brief look at what could cause this perplexing situation and a couple things that can be done about it. - [**A Better APRS Packet Demodulator, part 2, 9600 baud**](A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf) [ [*download*](../../../raw/master/doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf) ] In the first part of this series we discussed 1200 baud audio frequency shift keying (AFSK). The mismatch between FM transmitter pre-emphasis and the receiver de-emphasis will cause the amplitudes of the two tones to be different. This makes it more difficult to demodulate them accurately. 9600 baud operation is an entirely different animal. ... - [**WA8LMF TNC Test CD Results a.k.a. Battle of the TNCs**](WA8LMF-TNC-Test-CD-Results.pdf) [ [*download*](../../../raw/master/doc/WA8LMF-TNC-Test-CD-Results.pdf) ] How can we compare how well the TNCs perform under real world conditions? The de facto standard of measurement is the number of packets decoded from [WA8LMF’s TNC Test CD](http://wa8lmf.net/TNCtest/index.htm). Many have published the number of packets they have been able to decode from this test. Here they are, all gathered in one place, for your reading pleasure. - [**A Closer Look at the WA8LMF TNC Test CD**](A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf) [ [*download*](../../../raw/master/doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf) ] Here, we take a closer look at some of the frames on the TNC Test CD in hopes of gaining some insights into why some are easily decoded and others are more difficult. There are a lot of ugly signals out there. Many can be improved by decreasing the transmit volume. Others are just plain weird and you have to wonder how they are being generated. ## Questions? Experiences to share? ## Here are some good places to ask questions and share your experiences: - [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) - [Raspberry Pi 4 Ham Radio](https://groups.yahoo.com/neo/groups/Raspberry_Pi_4-Ham_RADIO/info) - [linuxham](https://groups.yahoo.com/neo/groups/linuxham/info) - [TAPR aprssig](http://www.tapr.org/pipermail/aprssig/) The github "issues" section is for reporting software defects and enhancement requests. It is NOT a place to ask questions or have general discussions. Please use one of the locations above. direwolf-1.5+dfsg/dsp.c000066400000000000000000000142371347750676600150770ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Name: dsp.c * * Purpose: Generate the filters used by the demodulators. * *----------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include "audio.h" #include "fsk_demod_state.h" #include "fsk_gen_filter.h" #include "textcolor.h" #include "dsp.h" //#include "fsk_demod_agc.h" /* for M_FILTER_SIZE, etc. */ #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) // Don't remove this. It serves as a reminder that an experiment is underway. #if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE) #define DEBUG1 1 // Don't remove this. #endif /*------------------------------------------------------------------ * * Name: window * * Purpose: Filter window shape functions. * * Inputs: type - BP_WINDOW_HAMMING, etc. * size - Number of filter taps. * j - Index in range of 0 to size-1. * * Returns: Multiplier for the window shape. * *----------------------------------------------------------------*/ float window (bp_window_t type, int size, int j) { float center; float w; center = 0.5 * (size - 1); switch (type) { case BP_WINDOW_COSINE: w = cos((j - center) / size * M_PI); //w = sin(j * M_PI / (size - 1)); break; case BP_WINDOW_HAMMING: w = 0.53836 - 0.46164 * cos((j * 2 * M_PI) / (size - 1)); break; case BP_WINDOW_BLACKMAN: w = 0.42659 - 0.49656 * cos((j * 2 * M_PI) / (size - 1)) + 0.076849 * cos((j * 4 * M_PI) / (size - 1)); break; case BP_WINDOW_FLATTOP: w = 1.0 - 1.93 * cos((j * 2 * M_PI) / (size - 1)) + 1.29 * cos((j * 4 * M_PI) / (size - 1)) - 0.388 * cos((j * 6 * M_PI) / (size - 1)) + 0.028 * cos((j * 8 * M_PI) / (size - 1)); break; case BP_WINDOW_TRUNCATED: default: w = 1.0; break; } return (w); } /*------------------------------------------------------------------ * * Name: gen_lowpass * * Purpose: Generate low pass filter kernel. * * Inputs: fc - Cutoff frequency as fraction of sampling frequency. * filter_size - Number of filter taps. * wtype - Window type, BP_WINDOW_HAMMING, etc. * * Outputs: lp_filter * *----------------------------------------------------------------*/ void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype) { int j; float G; #if DEBUG1 text_color_set(DW_COLOR_DEBUG); dw_printf ("Lowpass, size=%d, fc=%.2f\n", filter_size, fc); dw_printf (" j shape sinc final\n"); #endif assert (filter_size >= 3 && filter_size <= MAX_FILTER_SIZE); for (j=0; j= 3 && filter_size <= MAX_FILTER_SIZE); for (j=0; j #include "textcolor.h" #include "dtime_now.h" /* Current time in seconds but more resolution than time(). */ /* We don't care what date a 0 value represents because we */ /* only use this to calculate elapsed real time. */ #include #ifdef __APPLE__ #include #endif #include // needed for Mac. /*------------------------------------------------------------------ * * Name: dtime_now * * Purpose: Return current time as double precision. * * Input: none * * Returns: Unix time, as double precision, so we can get resolution * finer than one second. * * Description: Normal unix time is in seconds since 1/1/1970 00:00:00 UTC. * Sometimes we want resolution finer than a second. * Rather than having a separate variable for the fractional * part of a second, and having extra calculations everywhere, * simply use double precision floating point to make usage * easier. * *---------------------------------------------------------------*/ double dtime_now (void) { double result; #if __WIN32__ /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ FILETIME ft; GetSystemTimeAsFileTime (&ft); result = ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + (double)ft.dwLowDateTime ) / 10000000.) - 11644473600.); #else /* tv_sec is seconds from Jan 1, 1970. */ struct timespec ts; #ifdef __APPLE__ struct timeval tp; gettimeofday(&tp, NULL); ts.tv_nsec = tp.tv_usec * 1000; ts.tv_sec = tp.tv_sec; #else clock_gettime (CLOCK_REALTIME, &ts); #endif result = ((double)(ts.tv_sec) + (double)(ts.tv_nsec) * 0.000000001); #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dtime_now() returns %.3f\n", result ); #endif return (result); } #if __WIN32__ /* * Windows doesn't have localtime_r. * It should have the equivalent localtime_s, with opposite parameter * order, but I get undefined reference when trying to use it. */ static struct tm *localtime_r(time_t *clock, struct tm *res) { struct tm *tm; tm = localtime (clock); memcpy (res, tm, sizeof(struct tm)); return (res); } #endif /*------------------------------------------------------------------ * * Name: timestamp_now * * Purpose: Convert local time to one of these formats for debug output. * * HH:MM:SS * HH:MM:SS.mmm * * Input: result_size - Size of result location. * Should be at least 9 or 13. * * show_ms - True to display milliseconds. * * Output: result - Result is placed here. * *---------------------------------------------------------------*/ void timestamp_now (char *result, int result_size, int show_ms) { double now = dtime_now(); time_t t = (int)now; struct tm tm; localtime_r (&t, &tm); strftime (result, result_size, "%H:%M:%S", &tm); if (show_ms) { int ms = (now - (int)t) * 1000; char strms[16]; if (ms == 1000) ms = 999; sprintf (strms, ".%03d", ms); strlcat (result, strms, result_size); } } /* end timestamp_now */ /*------------------------------------------------------------------ * * Name: timestamp_user_format * * Purpose: Convert local time user-specified format. e.g. * * HH:MM:SS * mm/dd/YYYY HH:MM:SS * dd/mm/YYYY HH:MM:SS * * Input: result_size - Size of result location. * * user_format - See strftime documentation. * * https://linux.die.net/man/3/strftime * https://msdn.microsoft.com/en-us/library/aa272978(v=vs.60).aspx * * Note that Windows does not support all of the Linux formats. * For example, Linux has %T which is equivalent to %H:%M:%S * * Output: result - Result is placed here. * *---------------------------------------------------------------*/ void timestamp_user_format (char *result, int result_size, char *user_format) { double now = dtime_now(); time_t t = (int)now; struct tm tm; localtime_r (&t, &tm); strftime (result, result_size, user_format, &tm); } /* end timestamp_user_format */ /*------------------------------------------------------------------ * * Name: timestamp_filename * * Purpose: Generate unique file name based on the current time. * The format will be: * * YYYYMMDD-HHMMSS-mmm * * Input: result_size - Size of result location. * Should be at least 20. * * Output: result - Result is placed here. * * Description: This is for the kissutil "-r" option which places * each received frame in a new file. It is possible to * have two packets arrive in less than a second so we * need more than one second resolution. * * What if someone wants UTC, rather than local time? * You can simply set an environment variable like this: * * TZ=UTC direwolf * * so it's probably not worth the effort to add another * option. * *---------------------------------------------------------------*/ void timestamp_filename (char *result, int result_size) { double now = dtime_now(); time_t t = (int)now; struct tm tm; localtime_r (&t, &tm); strftime (result, result_size, "%Y%m%d-%H%M%S", &tm); int ms = (now - (int)t) * 1000; char strms[16]; if (ms == 1000) ms = 999; sprintf (strms, "-%03d", ms); strlcat (result, strms, result_size); } /* end timestamp_filename */ direwolf-1.5+dfsg/dtime_now.h000066400000000000000000000003551347750676600162770ustar00rootroot00000000000000 extern double dtime_now (void); void timestamp_now (char *result, int result_size, int show_ms); void timestamp_user_format (char *result, int result_size, char *user_format); void timestamp_filename (char *result, int result_size);direwolf-1.5+dfsg/dtmf.c000066400000000000000000000402151347750676600152360ustar00rootroot00000000000000 //#define DEBUG 1 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: dtmf.c * * Purpose: Decoder for DTMF, commonly known as "touch tones." * * Description: This uses the Goertzel Algorithm for tone detection. * * References: http://eetimes.com/design/embedded/4024443/The-Goertzel-Algorithm * http://www.ti.com/ww/cn/uprogram/share/ppt/c5000/17dtmf_v13.ppt * * Revisions: 1.4 - Added transmit capability. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include "dtmf.h" #include "hdlc_rec.h" // for dcd_change #include "textcolor.h" #include "gen_tone.h" #if DTMF_TEST #define TIMEOUT_SEC 1 /* short for unit test below. */ #define DEBUG 1 // Don't remove this. We want more output for test. #else #define TIMEOUT_SEC 5 /* for normal operation. */ #endif #define NUM_TONES 8 static int const dtmf_tones[NUM_TONES] = { 697, 770, 852, 941, 1209, 1336, 1477, 1633 }; /* * Current state of the DTMF decoding. */ static struct dd_s { /* Separate for each audio channel. */ int sample_rate; /* Samples per sec. Typ. 44100, 8000, etc. */ int block_size; /* Number of samples to process in one block. */ float coef[NUM_TONES]; int n; /* Samples processed in this block. */ float Q1[NUM_TONES]; float Q2[NUM_TONES]; char prev_dec; char debounced; char prev_debounced; int timeout; } dd[MAX_CHANS]; static int s_amplitude = 100; // range of 0 .. 100 static void push_button (int chan, char button, int ms); /*------------------------------------------------------------------ * * Name: dtmf_init * * Purpose: Initialize the DTMF decoder. * This should be called once at application start up time. * * Inputs: p_audio_config - Configuration for audio interfaces. * * All we care about is: * * samples_per_sec - Audio sample frequency, typically * 44100, 22050, 8000, etc. * * This is a associated with the soundcard. * In version 1.2, we can have multiple soundcards * with potentially different sample rates. * * amp - Signal amplitude, for transmit, on scale of 0 .. 100. * * 100 will produce maximum amplitude of +-32k samples. * * Returns: None. * *----------------------------------------------------------------*/ void dtmf_init (struct audio_s *p_audio_config, int amp) { int j; /* Loop over all tones frequencies. */ int c; /* Loop over all audio channels. */ s_amplitude = amp; /* * Pick a suitable processing block size. * Larger = narrower bandwidth, slower response. */ for (c=0; csample_rate = p_audio_config->adev[a].samples_per_sec; if (p_audio_config->achan[c].dtmf_decode != DTMF_DECODE_OFF) { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("channel %d:\n", c); #endif D->block_size = (205 * D->sample_rate) / 8000; #if DEBUG dw_printf (" freq k coef \n"); #endif for (j=0; jblock_size * (float)(dtmf_tones[j]) / (float)(D->sample_rate); D->coef[j] = 2.0f * cosf(2.0f * (float)M_PI * (float)k / (float)(D->block_size)); assert (D->coef[j] > 0.0f && D->coef[j] < 2.0f); #if DEBUG dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D->coef[j]); #endif } } } for (c=0; cn = 0; for (j=0; jQ1[j] = 0; D->Q2[j] = 0; } D->prev_dec = ' '; D->debounced = ' '; D->prev_debounced = ' '; D->timeout = 0; } } /*------------------------------------------------------------------ * * Name: dtmf_sample * * Purpose: Process one audio sample from the sound input source. * * Inputs: c - Audio channel number. * This can process multiple channels in parallel. * input - Audio sample. * * Returns: 0123456789ABCD*# for a button push. * . for nothing happening during sample interval. * $ after several seconds of inactivity. * space between sample intervals. * * *----------------------------------------------------------------*/ __attribute__((hot)) char dtmf_sample (int c, float input) { int i; float Q0; float output[NUM_TONES]; char decoded; char ret; struct dd_s *D; static const char rc2char[16] = { '1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D' }; D = &(dd[c]); for (i=0; iQ1[i] * D->coef[i] - D->Q2[i]; D->Q2[i] = D->Q1[i]; D->Q1[i] = Q0; } /* * Is it time to process the block? */ D->n++; if (D->n == D->block_size) { int row, col; for (i=0; iQ1[i] * D->Q1[i] + D->Q2[i] * D->Q2[i] - D->Q1[i] * D->Q2[i] * D->coef[i]); D->Q1[i] = 0; D->Q2[i] = 0; } D->n = 0; /* * The input signal can vary over a couple orders of * magnitude so we can't set some absolute threshold. * * See if one tone is stronger than the sum of the * others in the same group multiplied by some factor. * * For perfect synthetic signals this needs to be in * the range of about 1.33 (very senstive) to 2.15 (very fussy). * * Too low will cause false triggers on random noise. * Too high will won't decode less than perfect signals. * * Use the mid point 1.74 as our initial guess. * It might need some fine tuning for imperfect real world signals. */ #define THRESHOLD 1.74f if (output[0] > THRESHOLD * ( output[1] + output[2] + output[3])) row = 0; else if (output[1] > THRESHOLD * (output[0] + output[2] + output[3])) row = 1; else if (output[2] > THRESHOLD * (output[0] + output[1] + output[3])) row = 2; else if (output[3] > THRESHOLD * (output[0] + output[1] + output[2] )) row = 3; else row = -1; if (output[4] > THRESHOLD * ( output[5] + output[6] + output[7])) col = 0; else if (output[5] > THRESHOLD * (output[4] + output[6] + output[7])) col = 1; else if (output[6] > THRESHOLD * (output[4] + output[5] + output[7])) col = 2; else if (output[7] > THRESHOLD * (output[4] + output[5] + output[6] )) col = 3; else col = -1; for (i=0; i= 0 && col >= 0) { decoded = rc2char[row*4+col]; } else { decoded = ' '; } // Consider valid only if we get same twice in a row. if (decoded == D->prev_dec) { D->debounced = decoded; // Update Data Carrier Detect Indicator. #ifndef DTMF_TEST dcd_change (c, MAX_SUBCHANS, 0, decoded != ' '); #endif /* Reset timeout timer. */ if (decoded != ' ') { D->timeout = ((TIMEOUT_SEC) * D->sample_rate) / D->block_size; } } D->prev_dec = decoded; // Return only new button pushes. // Also report timeout after period of inactivity. ret = '.'; if (D->debounced != D->prev_debounced) { if (D->debounced != ' ') { ret = D->debounced; } } if (ret == '.') { if (D->timeout > 0) { D->timeout--; if (D->timeout == 0) { ret = '$'; } } } D->prev_debounced = D->debounced; #if DEBUG dw_printf (" dec=%c, deb=%c, ret=%c, to=%d \n", decoded, D->debounced, ret, D->timeout); #endif return (ret); } return (' '); } /*------------------------------------------------------------------- * * Name: dtmf_send * * Purpose: Generate DTMF tones from text string. * * Inputs: chan - Radio channel number. * str - Character string to send. 0-9, A-D, *, # * speed - Number of tones per second. Range 1 to 10. * txdelay - Delay (ms) from PTT to start. * txtail - Delay (ms) from end to PTT off. * * Returns: Total number of milliseconds to activate PTT. * This includes delays before the first tone * and after the last to avoid chopping off part of it. * * Description: xmit_thread calls this instead of the usual hdlc_send * when we have a special packet that means send DTMF. * *--------------------------------------------------------------------*/ int dtmf_send (int chan, char *str, int speed, int txdelay, int txtail) { char *p; int len_ms; // Length of tone or gap between. len_ms = (int) ( ( 500.0f / (float)speed ) + 0.5f); push_button (chan, ' ', txdelay); for (p = str; *p != '\0'; p++) { push_button (chan, *p, len_ms); push_button (chan, ' ', len_ms); } push_button (chan, ' ', txtail); #ifndef DTMF_TEST audio_flush(ACHAN2ADEV(chan)); #endif return (txdelay + (int) (1000.0f * (float)strlen(str) / (float)speed + 0.5f) + txtail); } /* end dtmf_send */ /*------------------------------------------------------------------ * * Name: push_button * * Purpose: Generate DTMF tone for a button push. * * Inputs: chan - Radio channel number. * * button - One of 0-9, A-D, *, #. Others result in silence. * '?' is a special case used only for unit testing. * * ms - Duration in milliseconds. * Use 50 ms for tone and 50 ms of silence for max rate of 10 per second. * * Outputs: Audio is sent to radio. * *----------------------------------------------------------------*/ static void push_button (int chan, char button, int ms) { float phasea = 0; float phaseb = 0; float fa = 0; float fb = 0; int i; float dtmf; // Audio. Sum of two sine waves. #if DTMF_TEST char x; static char result[100]; static int result_len = 0; #endif switch (button) { case '1': fa = dtmf_tones[0]; fb = dtmf_tones[4]; break; case '2': fa = dtmf_tones[0]; fb = dtmf_tones[5]; break; case '3': fa = dtmf_tones[0]; fb = dtmf_tones[6]; break; case 'a': case 'A': fa = dtmf_tones[0]; fb = dtmf_tones[7]; break; case '4': fa = dtmf_tones[1]; fb = dtmf_tones[4]; break; case '5': fa = dtmf_tones[1]; fb = dtmf_tones[5]; break; case '6': fa = dtmf_tones[1]; fb = dtmf_tones[6]; break; case 'b': case 'B': fa = dtmf_tones[1]; fb = dtmf_tones[7]; break; case '7': fa = dtmf_tones[2]; fb = dtmf_tones[4]; break; case '8': fa = dtmf_tones[2]; fb = dtmf_tones[5]; break; case '9': fa = dtmf_tones[2]; fb = dtmf_tones[6]; break; case 'c': case 'C': fa = dtmf_tones[2]; fb = dtmf_tones[7]; break; case '*': fa = dtmf_tones[3]; fb = dtmf_tones[4]; break; case '0': fa = dtmf_tones[3]; fb = dtmf_tones[5]; break; case '#': fa = dtmf_tones[3]; fb = dtmf_tones[6]; break; case 'd': case 'D': fa = dtmf_tones[3]; fb = dtmf_tones[7]; break; #if DTMF_TEST case '?': /* check result */ if (strcmp(result, "123A456B789C*0#D123$789$") == 0) { text_color_set(DW_COLOR_REC); dw_printf ("\nSuccess!\n"); } else if (strcmp(result, "123A456B789C*0#D123789") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n * Time-out failed, otherwise OK *\n"); dw_printf ("\"%s\"\n", result); exit (EXIT_FAILURE); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("\n *** TEST FAILED ***\n"); dw_printf ("\"%s\"\n", result); exit (EXIT_FAILURE); } break; #endif } //dw_printf ("push_button (%d, '%c', %d), fa=%.0f, fb=%.0f. %d samples\n", chan, button, ms, fa, fb, (ms*dd[chan].sample_rate)/1000); for (i = 0; i < (ms*dd[chan].sample_rate)/1000; i++) { // This could be more efficient with a precomputed sine wave table // but I'm not that worried about it. // With a Raspberry Pi, model 2, default 1200 receiving takes about 14% of one CPU core. // When transmitting tones, it briefly shoots up to about 33%. if (fa > 0 && fb > 0) { dtmf = sinf(phasea) + sinf(phaseb); phasea += 2.0f * (float)M_PI * fa / dd[chan].sample_rate; phaseb += 2.0f * (float)M_PI * fb / dd[chan].sample_rate; } else { dtmf = 0; } #if DTMF_TEST /* Make sure it is insensitive to signal amplitude. */ /* (Uncomment each of below when testing.) */ x = dtmf_sample (0, dtmf); //x = dtmf_sample (0, dtmf * 1000); //x = dtmf_sample (0, dtmf * 0.001); if (x != ' ' && x != '.') { result[result_len] = x; result_len++; result[result_len] = '\0'; } #else // 'dtmf' can be in range of +-2.0 because it is sum of two sine waves. // Amplitude of 100 would use full +-32k range. int sam = (int)(dtmf * 16383.0f * (float)s_amplitude / 100.0f); gen_tone_put_sample (chan, ACHAN2ADEV(chan), sam); #endif } } /*------------------------------------------------------------------ * * Name: main * * Purpose: Unit test for functions above. * * Usage: rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe * or * make dtmftest * *----------------------------------------------------------------*/ #if DTMF_TEST static struct audio_s my_audio_config; int main () { int c = 0; // radio channel. memset (&my_audio_config, 0, sizeof(my_audio_config)); my_audio_config.adev[ACHAN2ADEV(c)].defined = 1; my_audio_config.adev[ACHAN2ADEV(c)].samples_per_sec = 44100; my_audio_config.achan[c].valid = 1; my_audio_config.achan[c].dtmf_decode = DTMF_DECODE_ON; dtmf_init(&my_audio_config, 50); text_color_set(DW_COLOR_INFO); dw_printf ("\nFirst, check all button tone pairs. \n\n"); /* Max auto dialing rate is 10 per second. */ push_button (c, '1', 50); push_button (c, ' ', 50); push_button (c, '2', 50); push_button (c, ' ', 50); push_button (c, '3', 50); push_button (c, ' ', 50); push_button (c, 'A', 50); push_button (c, ' ', 50); push_button (c, '4', 50); push_button (c, ' ', 50); push_button (c, '5', 50); push_button (c, ' ', 50); push_button (c, '6', 50); push_button (c, ' ', 50); push_button (c, 'B', 50); push_button (c, ' ', 50); push_button (c, '7', 50); push_button (c, ' ', 50); push_button (c, '8', 50); push_button (c, ' ', 50); push_button (c, '9', 50); push_button (c, ' ', 50); push_button (c, 'C', 50); push_button (c, ' ', 50); push_button (c, '*', 50); push_button (c, ' ', 50); push_button (c, '0', 50); push_button (c, ' ', 50); push_button (c, '#', 50); push_button (c, ' ', 50); push_button (c, 'D', 50); push_button (c, ' ', 50); text_color_set(DW_COLOR_INFO); dw_printf ("\nShould reject very short pulses.\n\n"); push_button (c, '1', 20); push_button (c, ' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50); text_color_set(DW_COLOR_INFO); dw_printf ("\nTest timeout after inactivity.\n\n"); /* For this test we use 1 second. */ /* In practice, it will probably more like 5. */ push_button (c, '1', 250); push_button (c, ' ', 500); push_button (c, '2', 250); push_button (c, ' ', 500); push_button (c, '3', 250); push_button (c, ' ', 1200); push_button (c, '7', 250); push_button (c, ' ', 500); push_button (c, '8', 250); push_button (c, ' ', 500); push_button (c, '9', 250); push_button (c, ' ', 1200); /* Check for expected results. */ push_button (c, '?', 0); exit (EXIT_SUCCESS); } /* end main */ #endif /* end dtmf.c */ direwolf-1.5+dfsg/dtmf.h000066400000000000000000000003431347750676600152410ustar00rootroot00000000000000/* dtmf.h */ #include "audio.h" void dtmf_init (struct audio_s *p_audio_config, int amp); char dtmf_sample (int c, float input); int dtmf_send (int chan, char *str, int speed, int txdelay, int txtail); /* end dtmf.h */ direwolf-1.5+dfsg/dw-icon.ico000066400000000000000000013226261347750676600162060ustar00rootroot00000000000000 hf ¨Î00 ¨%v@@ (B;€€ (F} ( n…(  @~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ(((ÿ===ÿnnnÿ~~~ÿpppÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿžœ£ÿÔÐßÿ•–—ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ³®½ÿηýÿ¿·Òÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿƒ‚…ÿÒ¾ùÿÊ·ðÿCCEÿÿFFFÿsssÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ€„ÿ­ëÿ~tžÿ ÿVWWÿ••–ÿPPPÿ,--ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿŽÿ{~‚ÿ§©ªÿÿ~~~ÿSSSÿÿÿzzzÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ{{|ÿ555ÿÿÿÿEEEÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿ:::ÿÿÿÿÿÿaabÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿCB\ÿ/,sÿsssÿ|||ÿ ÿÿÿÿÿÿ ÿllnÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿccdÿZZ\ÿ~~ÿQQQÿÿÿÿÿÿÿÿMMMÿllmÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿrrrÿ ÿÿÿÿÿÿÿ//4ÿJJKÿ[[[ÿ<<<ÿPPPÿccdÿWWWÿrrrÿ“““ÿÿÿÿÿÿÿ$$$ÿiikÿLLNÿ ÿÿÿÿÿÿ‘‘‘ÿ"""ÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿGGGÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ( @ €~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~ÿÿÿ~~ÿ~~~ÿ333ÿÿÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ ÿÿÿÿIIIÿqqqÿ~~~ÿ~~~ÿÿ]]]ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿNNNÿEEEÿ```ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿgggÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ‡‡‡ÿ§¤°ÿÑÔÖÿŸ££ÿfggÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ£¢¤ÿÑÈåÿîæÿÿéáùÿŸ ÿ±±±ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÊÉÎÿ͸÷ÿÒ¼ÿÿÔÁûÿ²¶·ÿNOPÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ¡œªÿâÔþÿʰÿÿÏ·ÿÿÒÃôÿ¢¢£ÿÿÿÿÿÿ ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ†††ÿÍ¿êÿйÿÿÉ®ÿÿåØÿÿÑÐÖÿ ÿÿÿÿYYYÿÿ€€ÿ ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~ÿ‹ˆÿâÔÿÿÉ®ÿÿÈ®þÿ²ªÄÿ---ÿÿÿÿÿ¿¿¿ÿ¶¶¶ÿ’”•ÿBFFÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿІ“ÿѽúÿÉ®ÿÿÉ®ÿÿ‚€ÿ ÿÿÿ888ÿœžÿÅÆÇÿoppÿRRRÿWZZÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~ÿ~~~ÿ£™¸ÿɰýÿ¡Îÿÿÿÿ‘‘ÿŒÿ………ÿmmmÿ@@@ÿ@@@ÿSSSÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ¼·ÆÿquÿenqÿÆÉÊÿÀÄÄÿ¸¹¹ÿ‡‡‡ÿ~~~ÿ|||ÿOOOÿIIIÿCCCÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ„……ÿ‘‘’ÿÿ‰‰‰ÿÿÿÿÿxxxÿ:::ÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~ÿ~~ÿÿÿÿÿÿÿ}}€ÿtttÿ;;;ÿ ÿÿÿÿÿÿmmmÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~ÿqqqÿÿÿÿÿÿÿÿÿ999ÿ}}}ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿÿwwwÿ(((ÿÿÿÿÿÿÿÿÿÿVVVÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿÿÿ>>>ÿ ÿÿÿÿÿÿÿÿÿÿÿiiiÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ{zÿ[[_ÿKKXÿED]ÿoooÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!!"ÿ|||ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ}}ÿ))+ÿ fÿ"ãÿ4ÿ```ÿ~~ÿÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿÿ779ÿ}}ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~ÿ~~~ÿeefÿ**+ÿ''/ÿCCCÿ~~ÿ~~ÿ~~ÿAAAÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ76=ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿtttÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿoooÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ'''ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿHHHÿ{{{ÿDDDÿpprÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿPPPÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ006ÿÿ669ÿ%%%ÿzzzÿyyyÿnnoÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ“““ÿ¦¦¦ÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxx|ÿBBDÿ222ÿ{{{ÿPPPÿ ÿÿÿ++,ÿ@@@ÿRRSÿAABÿÿ777ÿÿ­­­ÿÿ&&&ÿÿÿÿÿÿÿÿÿÿÿÿÿÿjjlÿ{{}ÿutwÿwwzÿ000ÿÿÿÿÿÿÿÿÿÿÿQQQÿÿŸŸŸÿ>>>ÿÿÿÿÿÿÿÿÿÿÿÿ ÿsssÿqqsÿNNOÿ777ÿÿÿÿÿÿÿÿÿÿÿÿÿÿzzzÿšššÿLLLÿÿÿÿÿÿÿÿÿÿÿÿ ÿ***ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ;;;ÿÿEEEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ___ÿ+++ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(0` €%}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~ÿ~~ÿÿ~~ÿÿÿ~~ÿ~~~ÿGGGÿÿÿÿÿÿÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ}}}ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ;;;ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ***ÿ///ÿ///ÿ222ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ###ÿLLLÿvvvÿ~~~ÿ~~~ÿÿÿsssÿ>>>ÿÿÿÿÿÿÿÿÿÿÿÿÿ}}~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ>>>ÿÿÿÿ111ÿhhhÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿgggÿQQQÿlllÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ}}}ÿJJJÿÿÿÿÿÿÿÿÿÿÿÿÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ„……ÿ±°³ÿ»ÃÃÿŸ¤¤ÿŒŒŒÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}}}ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ}}}ÿ}}}ÿ~~~ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿƒƒ„ÿ¾½¿ÿñïóÿÙÆþÿûûýÿ·»»ÿïïïÿœœœÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ¤£¦ÿØÕßÿÚÈýÿðéÿÿÝÍÿÿñðôÿ¸º»ÿ‚„…ÿ‚‚‚ÿÿÿÿÿÿÿÿÿÿÿÿÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ……†ÿ×ÕÙÿÇ´îÿåØþÿ͵ÿÿÍ´ÿÿîìõÿ™Ÿ¡ÿqstÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}}}ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿÿ©£ºÿøõþÿ˲þÿÈ­þÿÈ­þÿÈ­þÿìçöÿŠŽÿ>>>ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿŽŠ–ÿîæþÿÔ¿ÿÿÉ®ÿÿÉ®ÿÿ×ÃÿÿÔÀýÿÓÓÖÿz{{ÿÿÿÿÿÿÿÿÿHHHÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿƒƒƒÿĽÑÿ̳þÿÖÂÿÿÉ®ÿÿɯþÿåÙÿÿáÝêÿgggÿÿÿÿÿÿ```ÿ ÿÿÿ®®®ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿŽŽÿæÛþÿÈ­þÿÈ­þÿÉ­þÿиþÿÛÚÝÿ—˜˜ÿwwwÿÿÿÿÿÿjjjÿMMMÿ ÿ899ÿÎÏÏÿNPPÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿÿ~~ÿ~~~ÿ–¢ÿíåÿÿϸÿÿÉ®ÿÿÈ®þÿ¼£ïÿª©¬ÿÿÿÿÿÿÿÿbbbÿðððÿëëëÿ±³´ÿž ¡ÿ]bbÿ ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿ~~ÿ–¨ÿ͵ÿÿεþÿÉ®ÿÿÉ®ÿÿ¢ŽÐÿ­®°ÿÿÿÿÿÿ ÿ   ÿŽ’“ÿåææÿÿcddÿZZZÿtttÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ}}}ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ€‚ÿ´¡Úÿä×þÿÈ­þÿÉ­þÿsg•ÿ,15ÿÿ ÿ ÿÿ:::ÿ‡‰Šÿ¦¨©ÿ¬¬¬ÿ{{{ÿGGGÿ@@@ÿAAAÿ]]]ÿ(,,ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~ÿ~~ÿ~~ÿŠ…”ÿ¸¤áÿÉ®ÿÿ¾¥ñÿ'*:ÿÿÿ"ÿ&+-ÿ···ÿÓÔÕÿˆŒŽÿÿÿ]]]ÿ@@@ÿ@@@ÿ@@@ÿ]]]ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿˆ‡ŠÿÛÒîÿ<=Sÿ‡‹Žÿ2=Aÿœ¡£ÿÚÜÝÿ¤ª¬ÿìììÿÿ“““ÿÿ~~~ÿzzzÿEEEÿBBBÿ@@@ÿfffÿ ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ}}}ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ£££ÿ”šœÿÁÂÃÿŽ”•ÿÉËËÿØØØÿª¬¬ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿvvvÿjjjÿYYYÿ###ÿÿÿÿÿÿÿÿÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}}}ÿXXXÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ}}~ÿ}}ÿ~~ÿ~~ÿ~~~ÿÿÿ~~~ÿÿÿ~~~ÿÿÿ{{‚ÿÿÿtttÿCCCÿÿÿÿÿÿÿÿÿÿxxxÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ}}~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ]]]ÿÿÿÿÿÿÿÿÿÿÿÿ]\]ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ}}€ÿ```ÿÿÿÿÿÿÿÿÿÿÿÿÿ***ÿ{{|ÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿÿÿ~~~ÿÿÿlllÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿMMNÿ~~~ÿ~~ÿ~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ}}~ÿ~~~ÿ}}~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿvvvÿ&&&ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿÿÿÿÿÿ~~ÿÿÿÿÿÿAAAÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿlllÿ~~ÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ}}~ÿ~~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~~ÿww~ÿnnnÿZZZÿlllÿ|||ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ223ÿ|||ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ}}~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿeehÿ'&2ÿ 6ÿmÿ»ÿÿ:::ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿSSUÿ~~ÿ~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ}}ÿ669ÿ ÿ¬ÿ#ìÿ§ÿÿ]^^ÿÿ~~ÿÿÿeeeÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿeegÿ~~€ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ}}~ÿ~~ÿ~~ÿ}}~ÿmnoÿÿÿ ÿ ÿ222ÿ~~ÿ}}ÿ~~ÿ~~ÿ~~~ÿ///ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿcbjÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~ÿvwxÿvwwÿvvvÿ~~~ÿ~~~ÿ}}~ÿ~}ÿ~~ÿlllÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿkkqÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ}}}ÿ222ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ##$ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿMMMÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿPPPÿ~~~ÿ~~ÿ~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿSSSÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿKKLÿÿwwwÿ***ÿ111ÿ}}ÿ~}~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ===ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ..0ÿÿ}}ÿ,,,ÿÿCCCÿÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿœœœÿ¶¶¶ÿ~~~ÿwwwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)ÿzzÿ~~~ÿKKPÿÿGGGÿ|||ÿ{{{ÿTTTÿ%%%ÿ++-ÿYYZÿqqrÿrrsÿ||}ÿ~~~ÿ~~~ÿ}}~ÿzzzÿ]]]ÿ[[\ÿ}}}ÿ‘‘‘ÿÂÂÂÿ„„„ÿ~~~ÿ---ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿgfvÿ{{{ÿ214ÿÿ^^^ÿÿuuuÿ###ÿÿÿÿÿÿÿÿ444ÿ===ÿ---ÿ ÿÿ ÿtttÿÿ§§§ÿÿ{{{ÿ>>>ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿPOUÿ}}€ÿxxyÿiimÿzy„ÿ}}}ÿ^^^ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwwwÿ„„„ÿ···ÿ‡‡‡ÿ\\\ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcccÿ}}€ÿ~~ÿ~~~ÿ~~~ÿrrrÿ@@@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿNNNÿ~~~ÿ¨¨¨ÿ¦¦¦ÿdddÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWWWÿ~~~ÿvvvÿMLOÿ000ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!!!ÿtttÿ‹‹‹ÿˆˆˆÿwwwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ333ÿ@@@ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ%%%ÿ~~~ÿÿnnnÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ```ÿ~~~ÿTTTÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿpppÿ<<<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(@€ B}}ÿ~~ÿ~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿ~~ÿ~~~ÿ^^^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ~~ÿ~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~ÿ___ÿ ÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ000ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ777ÿ???ÿ???ÿ???ÿHHHÿ"##ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ+++ÿMMMÿ{{{ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ111ÿÿÿÿÿÿÿDDDÿ{{{ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿgggÿÿÿÿ<<<ÿFFFÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ|||ÿÿxxxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿqqqÿ\\\ÿzzzÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿxxxÿ---ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ………ÿ¹¹ºÿ›§¨ÿ˜žŸÿyzzÿbccÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ Ÿ ÿÅÅÈÿÔÇóÿøôÿÿùúúÿŽ••ÿÝÞÞÿöööÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ|||ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ€€€ÿˆˆˆÿŸž¢ÿñðõÿÿÿÿÿãÖÿÿÚÈÿÿûüüÿÆÈÉÿ¾¿Àÿÿÿÿÿ§§§ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿŸ¢ÿäääÿçÜÿÿεÿÿæÙÿÿòìÿÿÝÐúÿðñòÿÍÎÏÿ$()ÿ«««ÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ’ÿÖÔÙÿȹçÿÚËùÿïçÿÿÉ®ÿÿ̳ÿÿêßÿÿàåæÿ•™›ÿ{€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÆÄÎÿÿÿÿÿɯÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÑÂñÿÿÿÿÿUZ]ÿ»»»ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ¯®±ÿÕÁþÿîæÿÿζÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÑÅîÿüüüÿ‘’’ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ€ƒÿÕÅöÿöòÿÿÏ·ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿâÔÿÿÔ¿ÿÿÜÚæÿˆˆˆÿtttÿÿÿÿÿÿÿÿÿÿÿÿ€€€ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}€ÿÿÿ¬ª°ÿʰÿÿÙÆÿÿ×ÄÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿêàÿÿïçÿÿÐÑÑÿÿÿÿÿÿÿÿÿ€€€ÿÿÿÿÿÖÖÖÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ›››ÿóîüÿ̳ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿãÕÿÿüûÿÿÌÌÌÿ»»»ÿ&&&ÿÿÿÿÿÿÿÿÕÕÕÿÿÿÿÿÿÿÿÿ566ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~ÿÿÿ’•ÿòëÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÈ®þÿÉ®ÿÿ×Äÿÿ«««ÿÿ233ÿÿÿÿÿÿÿÿÿÆÆÆÿ^__ÿFFFÿ¥¥¥ÿ=BBÿÿÿÿÿ†ŠŠÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿÿœ’¯ÿìâÿÿãÖÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿˆx§ÿ¿¿¿ÿÿÿÿÿÿÿÿÿÿÿßßßÿøøùÿýýýÿîîîÿV[[ÿµ¶¶ÿinnÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ£•¾ÿйÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿƒz ÿÉÉÊÿÿÿÿÿÿÿÿÿ˜˜˜ÿhjkÿÉËÌÿéêëÿ¯²²ÿxyyÿabbÿbbbÿyyyÿFKKÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ‰…ÿ¾§íÿîåÿÿ˱ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ>>>ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ???ÿÿ***ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿ???ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ777ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ666ÿYYYÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿOOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ777ÿuuuÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿOOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ:::ÿrrrÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿOOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ777ÿrrrÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ///ÿÿÿÿÿÿÿÿ ÿÿÿÿ???ÿYYYÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿyyyÿ888ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿoooÿ ÿÿÿÿÿXXXÿtttÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿvvvÿÿÿÿfffÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿIIIÿ222ÿ???ÿnnnÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿ'((ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcccÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿÿÿ™™™ÿ ¨ªÿ¨¶¹ÿ›¨«ÿsxyÿÿÿ?@@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿÿÿ˜˜˜ÿÏÐÐÿÿÿÿÿ•£¢ÿ›šÿ`ihÿòòòÿ™™™ÿPRRÿåååÿ^``ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ€€€ÿº»»ÿ»¹ÑÿäÖÿÿÿÿÿÿÿÿÿÿñòòÿûûûÿP\[ÿÞààÿãääÿžžÿÿÿÿÿÞÞÞÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿœœœÿæåèÿÞÚçÿÿÿÿÿéßÿÿÉ®ÿÿãÕÿÿÿÿÿÿüüüÿÿÿÿÿ§­¬ÿdmlÿÿÿÿÿöööÿÿÿÿÿÿÿÿÿÛÛÛÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿzzzÿ}}}ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿ„ƒ…ÿ‰•ÿØ×ØÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿÉ®ÿÿεÿÿ÷óÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿipqÿÿÿÿÿÜÝÝÿÿÿÿÿÿÿÿÿÿÿÿÿ%%&ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿƒƒƒÿ‰‰‰ÿ›šœÿŸžŸÿÎÍÏÿñêÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÜËÿÿüýýÿýýýÿÿÿÿÿ±´µÿÊÌÍÿSVWÿÿÿÿÿÿÿÿÿÿÿÿÿzzzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ€‚ÿ—–˜ÿÈÈÈÿÏÏÏÿþýÿÿôïÿÿζÿÿØÅÿÿÑ»ÿÿÿÿÿÿÿÿÿÿÓ¾ÿÿÉ®ÿÿäÖÿÿÿÿÿÿëììÿÿÿÿÿÿÿÿÿIQTÿ ÿÿÿÿÿŸŸŸÿýýýÿxxxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ‚‚‚ÿ³°¸ÿ³¯¹ÿûûüÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿþþÿÿÿÿÿÿøôÿÿàÐÿÿêëìÿÛÞßÿýýýÿgjnÿÎÏÐÿ:<=ÿÿÅÅÅÿKKKÿGGGÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ‚‚‚ÿ£¢§ÿÚÚÚÿÈÅÏÿ­œÏÿÀ¬çÿþþÿÿòëÿÿÜËÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿØÄÿÿéÞÿÿÿÿÿÿÿÿÿÿÀÌÏÿƒ‰‹ÿÿÿÿÿ£§©ÿ"%ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ|}}ÿ~~~ÿÿÿÀ¼ÉÿÜ×çÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿâÔÿÿýüÿÿñëÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ÷óÿÿÿÿÿÿÄÌÍÿ(15ÿ«®¯ÿýýýÿ256ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿþþÿÿÿÿÿÿÿÿÿÿ̳ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ¿¿Ëÿÿÿÿÿÿÿÿÿiorÿiorÿ÷÷÷ÿáááÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ€€ÿ±·½ÿéÞÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿôðýÿÿÿÿÿÿÿÿÿw|ÿ ÿYYZÿ»»»ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ”––ÿÒÀüÿÛÉÿÿþþÿÿÿÿÿÿÛÉÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ¿»Ñÿÿÿÿÿûûûÿäååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿÿÿÿÿÿÿÿ²²²ÿ÷ôÿÿßÏÿÿÉ®ÿÿÚÈÿÿâÔÿÿ̳ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÀ­êÿÿÿÿÿÿÿÿÿøùùÿÿÿÿÿ\\\ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ}}~ÿ~~~ÿÿÿÿÿÿÿÿȽÞÿýüÿÿÿÿÿÿÝÌÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿùöÿÿÜËÿÿÉ®ÿÿñêÿÿÿÿÿÿÿÿÿÿbbbÿýýýÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeeeÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿˆ…ÿÉ®ÿÿÉ®ÿÿþþÿÿÿÿÿÿáÒÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿäÖÿÿÉ®ÿÿÛÕêÿ¦«¯ÿ¼¼¼ÿÿGHHÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ©¡¹ÿÉ®ÿÿÉ®ÿÿÙÇÿÿôîÿÿöóÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÞÍÿÿÿÿÿÿêàÿÿÖÂÿÿÿÿÿÿijlÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeeeÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ{z„ÿÿÿÿÿŠŠŠÿþþÿÿÏ·ÿÿÉ®ÿÿÉ®ÿÿεÿÿÖÂÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿεÿÿÿÿÿÿþþÿÿÿÿÿÿÿÿÿÿÛÛÛÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}}}ÿÿÿÿÿ666ÿÿÿ)))ÿÿÿÿÿÛÛÛÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ²²²ÿöööÿûúÿÿØÅÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿѺÿÿäÖÿÿùöÿÿþþÿÿÿÿÿÿWWWÿ˜™™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ344ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿ¾¾¾ÿÿÿÿÿÜËÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÛÉÿÿþþÿÿÿÿÿÿüûÿÿÿÿÿÿÛÛÛÿVWWÿÿÿÿÿ}}}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýýýÿÛÛÛÿÿ999ÿÿÿÿÿÿ\^^ÿÿÿÿÿÿÿÿÿžŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿˆˆˆÿþþÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿêàÿÿÿÿÿÿKKKÿÿùùùÿÿÅÅÅÿMMMÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ———ÿÿÿÿÿ444ÿÿÿÿ222ÿÿKLLÿÿÿÿÿÿÿÿÿÿýýýÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿ~~ÿÿÿÿÿÿüÐÿÿÿÿÿèÝÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿűüÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿãÕÿÿÿÿÿÿeeeÿ%%%ÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿƒƒƒÿÿÿÿÿåååÿabcÿÿ›››ÿæææÿÿÿÿÿŠ‹‹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ||}ÿÿÿÿ»ªßÿïçÿÿÿÿÿÿêàÿÿÏ·ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿRJbÿÿÿÿÿ………ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿãããÿÿÿÿÿþþþÿþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠ‹‹ÿÿýýýÿÒÔÔÿÀÂÂÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿ·¢ßÿεÿÿôïÿÿýüÿÿØÅÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ;Xÿ#ÿÿÿ ÿ ÿ ÿÿ*47ÿ#.1ÿÿ#ÿ ÿ778ÿåååÿÿÿÿÿÿÿÿÿÄÇÈÿÔÖ×ÿÿÿÿÿ±µ·ÿÿÿÿÿÿÿÿ```ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿBBBÿ@@@ÿ@@@ÿ@@@ÿlllÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ’‹ ÿùöþÿçÛÿÿÉ®ÿÿœŠÈÿoe‘ÿ!ÿquzÿ.5;ÿÿ ÿ ÿ ÿ5>Aÿçèéÿÿÿÿÿ«°²ÿ7BFÿ,9<ÿ¯¶·ÿÿÿÿÿÿÿÿÿÿÿÿÿÁÄÆÿgikÿ¼¼¼ÿÜÜÜÿÿÿÿÿÿÿÿyyyÿaaaÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿCCCÿEEEÿAAAÿ@@@ÿ@@@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÎÎÎÿþþÿÿÉ®ÿÿBA[ÿ$ÿ"ÿÿÿÿÿ £¥ÿ-5<ÿ +/ÿ$(ÿGQTÿêëìÿÿÿÿÿïññÿÿÿÿÿ´¹»ÿéêêÿÖÚÛÿýþþÿÿÿÿÿßßßÿÿÿÿÿÿÿÿÿÿÿÿÿÿJJJÿ@@@ÿHHHÿFFFÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿMMMÿÿiiiÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ}}~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ­­­ÿÿÿÿÿšž¡ÿ=FKÿ!ÿ ÿ×ÙÚÿýýýÿ‡‘—ÿŽœÿo}~ÿÛÞÞÿÿÿÿÿÿÿÿÿ}ƒ„ÿýýýÿÿÿÿÿÿÿÿÿs}~ÿ™  ÿßßßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿiiiÿBBBÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿAAAÿzzzÿfffÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¼¼¼ÿõõõÿèìíÿ©·½ÿ›¥©ÿ¿ÂÃÿÿÿÿÿ”œžÿ™  ÿNZZÿÍÑÑÿÿÿÿÿÿÿÿÿÿÿÿÿõõõÿÿÿÿÿÿÿÿÿmtvÿv{ÿÿÿÿÿÿÿÿÿÿ}}}ÿÿÿÿÿÿÿyyyÿaaaÿ___ÿ```ÿaaaÿ___ÿlllÿ{{{ÿ)))ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ­°±ÿ„ˆ‰ÿÿªªªÿûûûÿúúúÿßßßÿKTYÿ§¨©ÿßßßÿŽ—™ÿçèèÿÖÖÖÿ½½½ÿŽŽŽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿ}}}ÿ'''ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}}}ÿÿ}}}ÿ<<<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿ555ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfffÿ***ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ{{ÿ||ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿmmmÿAAAÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}ÿ~~~ÿ}}ÿ~~ÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXXXÿ===ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhfšÿÿÿÿÿÿÿmmmÿ888ÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿsssÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿOOOÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgghÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ777ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ777ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿCCCÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvu‰ÿÿÿÿ777ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿOOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿCCCÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿÿÿÿ~~ÿ~~ÿÿ~~ÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgggÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgggÿ777ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ111ÿmmnÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿÿ~~ÿÿÿÿ~~ÿÿÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgggÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿUUUÿmmmÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~ÿ~~ÿÿÿ~~ÿÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~}€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUVÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~ÿÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ777ÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ112ÿhhhÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgggÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ111ÿmmmÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿOOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿ%%%ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿIIIÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿÿ~~ÿÿÿÿÿ~~ÿÿÿÿÿÿ___ÿ(&Zÿ___ÿÿÿÿÿÿÿ???ÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿCCCÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿÿÿ~~ÿ~~ÿÿ~~ÿ~~ÿ}|€ÿssŒÿNMsÿ44Mÿÿÿÿÿÿ ^ÿ™ÿ$íÿËÿÿÿÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿTTTÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿÿÿ~~ÿ~~ÿ~~ÿ}}ÿ}}ÿ]]_ÿÿÿÿÿ(ÿvÿvÿ Õÿ$íÿ$íÿ$íÿzÿÿÿÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ..4ÿmmmÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿÿÿ~~ÿ}}ÿ}}ÿ}|€ÿxx„ÿ ÿÿÿÿÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ :ÿÿÿÿ___ÿÿÿÿÿyy…ÿÿÿÿÿÿÿÿÿgggÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿSSWÿsstÿ~~€ÿÿÿ~~€ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿ~~ÿÿ~~ÿ}}ÿ}}€ÿ}}ÿyyÿÿÿÿÿ¾ÿ$íÿ$íÿ$íÿ$íÿ$íÿ$ìÿµÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿOOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿffgÿxxzÿÿ~~ÿ~~€ÿ~~€ÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿ~~ÿ}}ÿ}}ÿ||~ÿuwyÿ79:ÿÿÿÿÿÿÀÿ#èÿ$íÿ$íÿ#æÿÿÿÿÿYYYÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ&%,ÿmmmÿ~~ÿ~~€ÿ~~€ÿ~~€ÿ~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ}}~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~}ÿ~~ÿÿÿ~~ÿÿ~~ÿ~~ÿ}}~ÿz{{ÿXY[ÿÿÿÿÿÿÿÿÿÿ =ÿÿÿÿÿÿÿÿÿ}}€ÿÿÿÿÿ}}€ÿÿÿÿÿOOOÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ''.ÿrrtÿ~~ÿ~~ÿ~~€ÿ~~€ÿÿÿÿÿÿÿ~~~ÿÿ~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ}}ÿ}}~ÿ{||ÿ{|}ÿ[]^ÿÿÿÿÿÿÿÿÿÿÿÿÿYYYÿÿÿÿ~~ÿzz‚ÿ||€ÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ((*ÿxxzÿ~~€ÿ~~€ÿ~~€ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿ}}}ÿ|}}ÿ}}~ÿ~€ÿ‚ÿ>?@ÿ>>ÿÿÿÿÿÿÂÂÂÿÃÃÃÿ³³³ÿÿÿÿ>>>ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿMMMÿÿÿ~~~ÿÿÿÿÿ{{{ÿiiwÿ111ÿ...ÿ(((ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿfffÿÿÿÿÿ”””ÿ’’’ÿÿÿÿÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿiiiÿÿÿÿÿÿrrrÿ666ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ666ÿ{{{ÿÿÿÿÿÿÿÿÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdddÿÿÿÿÿPPPÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>>>ÿÿÿÿÿÿÿÿÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$$$ÿ...ÿ...ÿ...ÿ(((ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ~~~ÿÿÿÿÿÿÿÿ)))ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿKKKÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ***ÿ{{{ÿÿÿÿÿÿ|||ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ***ÿ~~~ÿÿÿÿÿLLLÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ999ÿÿÿÿÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿNNNÿÿ~~~ÿÿ...ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿoooÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ***ÿ{{{ÿaaaÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ( ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ~~ÿ€ÿ€ÿÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿ€ÿÿ€ÿ€ÿ€ÿ€ÿ€ÿÿÿÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ€ÿ}}€ÿ~~ÿÿÿÿÿ€€€ÿ€€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ???ÿ???ÿ???ÿ@@@ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ???ÿ???ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ€ÿ€ÿ€ÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿ€ÿÿ€ÿ€ÿ€ÿÿÿÿÿÿÿ€ÿ€ÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ}}ÿ~~ÿ~~ÿ~~ÿ}}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ€ÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿVVWÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿÿÿÿÿxyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿÿÿÿÿ111ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$)ÿ#(ÿ#(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ444ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,,,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ444ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿZZZÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ;;;ÿÿÿÿÿÿÿÿÿÿZZZÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿZZZÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿOOOÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿOOOÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿZZZÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿOOOÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ}}}ÿÿOOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿOOOÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ}}}ÿÿÿÿ\\\ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿZZZÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿÿÿÿ~~~ÿ```ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$$$ÿÿÿÿÿÿOOOÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿyyyÿ```ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzzzÿvvvÿ|||ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿZZZÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ|||ÿMMMÿÿÿÿÿÿÿÿÿÿÿ777ÿ...ÿSSSÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿsttÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ|||ÿxxxÿ+++ÿÿÿÿÿÿÿÿÿOOOÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ^^^ÿÿÿÿÿÿÿÿ())ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ(((ÿÿÿÿÿ???ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿsttÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿKKKÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿpppÿ())ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwwwÿ344ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿpppÿ())ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ…‡‡ÿŸª¬ÿª¹¼ÿ«º½ÿ—¦©ÿix{ÿhkkÿÿÿÿÿÿ899ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÚÚÚÿÏÞáÿ°¿Âÿ±ÀÃÿ§¶¹ÿ ¯²ÿ“”ÿÿÿÿÿÿÿBCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ‡ˆ‰ÿÛÛÛÿÿÿÿÿÿÿÿÿ¬º¹ÿžÿp~}ÿ‡•”ÿ^lkÿ}~~ÿËËËÿÿÿÿÿ»»»ÿÿpppÿ344ÿ™™™ÿÿÿÿÿz{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿˆˆ‰ÿÛÛÛÿÛÞßÿÿÿÿÿÿÿÿÿÿÿÿÿ\jiÿ¿ÍÌÿºÈÇÿ‹““ÿ[eeÿMUUÿÿÿÿÿÿÿÿÿûûûÿ/11ÿ ÿ™››ÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ›Ÿ¢ÿ€‹“ÿÎÓÖÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈÎÎÿÿÿÿÿïòòÿÿÿÿÿlvuÿ,:9ÿøøøÿÿÿÿÿÿÿÿÿ’““ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{||ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿƒƒƒÿÏÏÏÿÿÿÿÿØÚÝÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‹’‘ÿ .-ÿ„‹‹ÿÿÿÿÿÿÿÿÿÿÿÿÿ{||ÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡‡‡ÿÛÛÛÿÅÂÌÿ“ŸÿÿÿÿÿÿÿÿÿÿÿÿÿýüÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÿÿÿÿÿÿÿÿÿÿÿÿÿøùùÿ!/.ÿ'&ÿøøøÿÿÿÿÿÿÿÿÿ÷÷÷ÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÛÛÛÿÿÿÿÿüûüÿêßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿôõõÿÿÿÿÿÿÿÿÿÿÿÿÿ…Œÿ ÿpvvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿvvvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ€ƒÿ‘Ž–ÿÆÆÆÿüüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýüÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÝÌÿÿýüÿÿÿÿÿÿÿÿÿÿàââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ„‹‹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿúúúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿvvvÿzzzÿ~~~ÿÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿƒƒƒÿ‘Ž–ÿ’‹Ÿÿ“ŸÿŸžŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿâÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøøøÿ#ÿÿÿÿÿÿÿÿÿÿÿÿÿ{|}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ|||ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ‚‚‚ÿ€€€ÿÿ‚‚ƒÿ“““ÿŸŸŸÿŸŸŸÿŸžŸÿ›™ŸÿŸŸŸÿîæÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿâÔÿÿýþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ'+ÿøøøÿÿÿÿÿéêêÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ€€€ÿ†††ÿ†††ÿ†††ÿŸŸŸÿ›šŸÿŸŸŸÿŸŸŸÿŸŸŸÿÿÿÿÿÿÿÿÿøõÿÿâÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿ÷ùùÿÿÿÿÿ÷øøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¨«­ÿouwÿÅÇÈÿY_bÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôôôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ‚€ƒÿ„ƒ†ÿ†…†ÿ†††ÿŸŸŸÿŸŸŸÿŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÏÿÿÉ®ÿÿßÏÿÿñëÿÿØÅÿÿÝÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ˱ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿøøøÿøøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿDLOÿ"&ÿ!%ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿ€€€ÿÿÿÿÿÿÿÿÿùùùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ€ƒÿƒ†ÿ†††ÿÌÌÎÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüúÿÿÿÿÿÿøõÿÿÝÌÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòìÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÃÄÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ°³µÿ#'ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿçççÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ½¼¿ÿƒ†ÿƒ†ÿüûüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûúÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿãÖÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿûüüÿÿÿÿÿÿÿÿÿøøøÿz}€ÿÓÓÔÿÿÿÿÿÿÿÿÿÆÈÉÿ ÿ ÿ ÿ÷÷÷ÿÿÿÿÿçççÿÿ```ÿ¿¿¿ÿHHHÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ‹‹‹ÿüûüÿ’‹Ÿÿ’‹Ÿÿ»·ÃÿñðóÿÿÿÿÿÿÿÿÿÿÿÿÿþýÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðèÿÿÿÿÿÿÿÿÿÿ«°´ÿv€‚ÿÿÿÿÿÿÿÿÿÿÿÿÿ+16ÿ%).ÿvx{ÿÇÉÊÿÿ ÿÿÿaaaÿ¿¿¿ÿHHHÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÛÛÛÿÿÿÿÿ’‹Ÿÿ’‹Ÿÿ’‹Ÿÿ’‹ŸÿÝÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿ˱ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÝÌÿÿñëÿÿòìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÓÝßÿ³ÂÅÿ;DHÿøøøÿÿÿÿÿÿÿÿÿÁÃÅÿ19=ÿ'04ÿ )-ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿŽŽŽÿ’‹Ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ’‹ŸÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿëáÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈÐÒÿ´ÃÆÿNW[ÿŒ‘“ÿÿÿÿÿÿÿÿÿÿÿÿÿž¢¤ÿ&*ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿˆˆˆÿ’‹Ÿÿ’‹ŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿâÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøõÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿþýÿÿÿÿÿÿÿÿÿÿÿÿÿÿùúúÿª¹¼ÿCLPÿ!*.ÿ‘•—ÿÿÿÿÿÿÿÿÿøøùÿ'+ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿy{}ÿÿÿÿÿÿÿéÞÿÿÿÿÿÿâÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿú÷ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿßÏÿÿøõÿÿÿÿÿÿøõÿÿØÅÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿâÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÿp‚ÿ)26ÿ!ÿ&*ÿÿÿÿÿÿÿÿÿÿÿÿÿ   ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿØÅÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ#/ÿþýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‘”ÿ!%ÿ(,ÿøøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿéÞÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåææÿ%)ÿ&*ÿsx{ÿààáÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚„…ÿÿ©³½ÿÝÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿøøùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¬¯±ÿ"&ÿ#'ÿ ÿ ÿ÷÷÷ÿÿÿÿÿçççÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¿ÉÓÿßãçÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÝÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøøøÿ!*.ÿ ÿ ÿÿaaaÿÀÀÀÿHIIÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðöôÿÉ®ÿÿÉ®ÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ6BHÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ›Ÿ¡ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÖÜÜÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÝÌÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿøõÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿïññÿÿÿÿÿÿÿÿÿ÷÷÷ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿËËËÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÝÌÿÿûùÿÿÿÿÿÿúøÿÿØÅÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ¦ª­ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæêìÿÿÿÿÿÿÿÿÿÿÿÿÿyyzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿëáÿÿ˱ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüýýÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ}}€ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿçÛÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿéÞÿÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿjjjÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÝÌÿÿøõÿÿÿÿÿÿÿÿÿÿÿÿÿÿýüÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿýüÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿçççÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ———ÿÿÿÿÿxxxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€€€ÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéÞÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿ—Ÿ£ÿúûûÿÿÿÿÿèèèÿ ÿÿ`aaÿ¿¿¿ÿHIIÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿˆˆˆÿÿÿÿÿ¤—¿ÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÓ½ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ§ª®ÿdlsÿ£¨­ÿÀÀÁÿIJJÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€€€ÿÿÿÿÿ¯ÒÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÝÌÿÿøõÿÿÿÿÿÿÿÿÿÿÿÿÿÿìãÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿéÞÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿ¦ª­ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”””ÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÓ½ÿÿÿÿÿÿÿÿÿÿñëÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿâÔÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ———ÿÿÿÿÿxxxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚‚‚ÿÿÿÿÿýüÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÝÌÿÿñëÿÿ×ÂÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÝÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿ[[[ÿsssÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿxxxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿon“ÿÿÿÿÿÿÿÿÿÿÿªªªÿÿÿÿÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿ‡‡‡ÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿËËËÿÿÿÿÿÞÞÞÿÿÿÿÿÿÿÿÿøõÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿéÞÿÿÿÿÿÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ???ÿdddÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýüýÿøõÿÿøõÿÿØÅÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿéÞÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxxxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÄÄÄÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿyyyÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxxxÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ···ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûÿÿÿÿÿÿÿÿÿýüÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÝÌÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷óÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿaaaÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿXXXÿÿÿÿÿÿÿÿÿÿÿÿ ÿ§ªªÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿz||ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿâÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿoooÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿçççÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ```ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿsuvÿýüÿÿÿÿÿÿú÷ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿçççÿÿÿaaaÿÀÀÀÿIJJÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÌÌÿÿÿÿÿÿÿÿÿ¶¶¶ÿÿÿÿÿÿÿÿÿøøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚ƒÿéÞÿÿÿÿÿÿÿÿÿÿñéÿÿ˱ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿiiiÿÀÀÀÿIIIÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøøøÿÿÿÿÿÿÿÿÿÿÿÿÿšššÿÿÿÿÿÿgggÿÛÛÛÿõõõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿ¤—¿ÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿçÛÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ¶ùÿôûÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿýüÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ†‹ÿÿÿÿÿÿÿÿÿÿÿÿÿöööÿïïïÿÚÚÚÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿyz{ÿÿÿÿÿÿÿÿ¤—¿ÿÝÌÿÿøõÿÿÿÿÿÿÿÿÿÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿhgjÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ•••ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøøøÿÿÿÿÿéêêÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ||}ÿÿÿÿÿÿÿ¤—¿ÿÉ®ÿÿÉ®ÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâÔÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúúúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøøøÿÿÿÿÿéééÿkppÿÄÆÆÿU\\ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~€ÿÿÿÿÿÿÿÿ¤—¿ÿÉ®ÿÿÉ®ÿÿÝÌÿÿýüÿÿÿÿÿÿÿÿÿÿÿÿÿÿøõÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿaaaÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿjnqÿÃÅÆÿŠ‹ÿhijÿbdeÿcffÿcffÿ2::ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿ¦—ÁÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÝÌÿÿøõÿÿÿÿÿÿøõÿÿØÅÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿBÿÂÇÈÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿêëìÿøøøÿÿÿÿÿéêêÿÿ>>ÿ???ÿ™™™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ†Œÿøùùÿÿÿÿÿÿÿÿÿÿÿÿÿ¿ÃÅÿ?MSÿÿÿÿÿÿÿÿÿÿÿÿÿ‚‚‚ÿÿÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿFFFÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿÿÿÿÉ®ÿÿÉ®ÿÿζÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ!ÿ ÿ!(ÿ ÿ"ÿ ÿÿ"ÿÿÿÿ ÿÿ ÿÿÿÿw}ÿMUXÿ%)ÿ ÿÿ#ÿ ÿ ÿ ÿ!#$ÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ’•ÿv{€ÿåæçÿÿÿÿÿÿÿÿÿÿÿÿÿÇÇÇÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBBBÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿHHHÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿmmmÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿ~~~ÿÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ®ÿÿëáÿÿÿÿÿÿìãÿÿ˱ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ"ÿÿ#ÿ!ÿ%ÿ!ÿ%ÿ!ÿ ÿÿ ÿ"ÿÿÿ ÿÿ¡¥§ÿÿÿÿÿÿÿÿÿÿÿÿÿ¦¬®ÿ4?Cÿ'26ÿ ÿ ÿ"%ÿETWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ…ŠŽÿ '0ÿÿ¿¿¿ÿÿÿÿÿÿÿÿÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgggÿHHHÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿDDDÿVVVÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿƒƒƒÿûûûÿÿÿÿÿÿÿÿÿçÛÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ%ÿ%ÿ$ÿ#ÿ!ÿŸ¢¥ÿÿÿÿÿƒ‡‹ÿÿÿ ÿ ÿ ÿ#ÿ#ÿÿÿ ÿ£§©ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÔÙÚÿs}ÿ9DHÿ"ÿ‰‹ÿz†ˆÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿƒ‰Œÿÿÿÿ³³³ÿßßßÿ£££ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿDDDÿFFFÿ@@@ÿ@@@ÿ@@@ÿDDDÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ»»»ÿÿÿÿÿÿÿÿÿýüÿÿÉ®ÿÿÉ®ÿÿÉ®ÿÿ%ÿ%ÿ$ÿ$ÿ#ÿÿÿÿÿÿÿÿÿ÷øøÿÿ%ÿ ÿÿÿ ÿ ÿÿ*.ÿ­±³ÿÿÿÿÿÿÿÿÿÿÿÿÿýýýÿÿÿÿÿÿÿÿÿÿÿÿÿËÐÑÿXcgÿ§¬­ÿÿÿÿÿ®¶¸ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBBBÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿFFFÿÿÿÿvvvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ˱ÿÿÉ®ÿÿ$ÿ%ÿ%ÿ$ÿ$ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿ‚†‰ÿBJQÿPX_ÿÿ)48ÿ&15ÿ6AEÿÂÆÈÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÖÚÛÿíïïÿÿÿÿÿÿÿÿÿùúúÿ¶º»ÿÿÿÿÿÿÿÿÿ±¹ºÿûûûÿÿÿÿÿúûûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgggÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿaaaÿZZZÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿDDDÿmmmÿÿÿyyyÿ777ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûÿÿÿÿÿÿÿÿÿ¼¿Áÿ#*ÿ$ÿ$ÿ$ÿ$ÿ#ÿ ÿøøøÿÿÿÿÿÿÿÿÿøøùÿw†ÿž¦­ÿœ§«ÿŒ—›ÿz…‰ÿfquÿ­³µÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈÌÍÿ"-1ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŸ¦¨ÿ€ŠŒÿÍÑÒÿ¹¾¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBBBÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿmmmÿÿÿxxxÿ:::ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ»»»ÿÿÿÿÿÿÿÿÿÿÿÿÿ“šœÿ}…ˆÿP[_ÿ ÿ ÿ  ÿ ÿipsÿÿÿÿÿÿÿÿÿÿÿÿÿƒ“ÿ†’–ÿ“¤¡ÿ€‘Žÿp~ÿo€}ÿÂÊÈÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûÿÿøøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ•”ÿ#10ÿ.<;ÿ°¶µÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgggÿHHHÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿFFFÿÿÿwwwÿ***ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¿¿¿ÿÿÿÿÿÿÿÿÿ÷÷øÿÿÿÿÿÑÙÛÿ²ÀÆÿ©·½ÿ¢°¶ÿ˜¤¨ÿ‘ÿÿÿÿÿÿÿÿÿÿÿÿÿiuyÿZfjÿ5EDÿˆ‘ÿVbaÿ%$ÿ¤£ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÚÝÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠ‘“ÿ2ADÿ7FIÿª°±ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgggÿHHHÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿEEEÿFFFÿ@@@ÿ@@@ÿ@@@ÿHHHÿmmmÿÿÿpppÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ³³³ÿßßßÿÿÿÿÿÿÿÿÿÑÙÛÿ¥³¹ÿ¦´ºÿ´ÂÈÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‘—™ÿ¨­®ÿÿÿÿÿ­³´ÿ .0ÿœ£¤ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡‰Šÿswxÿyy‚ÿ~~€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿoooÿ%%%ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¹ÃÇÿ–¤ªÿÿÿÿÿûûûÿÿÿÿÿÿÿÿÿÿÿÿÿõõöÿÿÿÿÿÿÿÿÿs{ÿ(.ÿŸ¤§ÿÿÿÿÿÿÿÿÿÿÿÿÿ¦®°ÿãææÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôôôÿ¼¼¼ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyyyÿÿÿÿÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿïïïÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿ#08ÿÿÿÿÿÿÿÿÿ2JPÿÿùúúÿ¨¨¨ÿ¡¡¡ÿ»»»ÿ………ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvvvÿÿÿÿÿwwwÿ%%%ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~ÿÿÿ~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿƒƒƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyyyÿÿÿÿÿÿ555ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxxxÿ555ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~ÿÿ~~ÿÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~}€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxxxÿ***ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿpppÿ***ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿÿ~~~ÿÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿpppÿ***ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿpppÿ***ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿ~~ÿ~~ÿÿ~~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿpppÿ***ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿÿ~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿxx…ÿxx…ÿ~~~ÿxx…ÿ}}€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿsssÿDDDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿ~~ÿ~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}€ÿ}|€ÿ~~~ÿ~~~ÿ}}€ÿ}}€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿsssÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿÿÿÿÿ~~ÿ~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿFFFÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿÿÿ~~ÿÿ~~ÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ%íÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ%%%ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿÿÿÿÿ~~ÿ~~ÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ:::ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿÿÿÿ€ÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿ~~ÿÿÿ~~ÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿÿÿÿÿÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿ~~ÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿhhhÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgghÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhghÿhhhÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿ~~~ÿÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿgggÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgggÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿÿ~~~ÿÿ~~~ÿÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ]Z¨ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~~ÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿÿÿ~~~ÿÿ~~~ÿÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿÿÿÿÿÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~~ÿ~~~ÿÿ~~ÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~ÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿhhhÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿÿÿ~~ÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿ ÿÿhhhÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿÿÿ~~ÿÿ~~ÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿhhhÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿÿÿ~~ÿÿ~~ÿÿÿÿÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿhhhÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿ~~ÿ~~ÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿhhhÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~ÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿhhhÿgggÿhhhÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿ~~~ÿ~~ÿ~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿhhhÿhhhÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿÿÿ~~~ÿ~~ÿ~~ÿ~~ÿÿ~~~ÿ~~ÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿ~~ÿÿÿÿÿ~~ÿ~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ}|€ÿ~~€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿhhhÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿÿ~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿÿ~~~ÿ~~ÿ~~ÿÿÿ~~ÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿ~~ÿÿÿ~~ÿÿ~~ÿÿÿÿÿ}|€ÿ~~€ÿ~~€ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿhhhÿhhhÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿhhhÿhhhÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ||ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿ ÿ ÿhhhÿhhhÿhhhÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿÿ~~ÿ~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿgggÿhhhÿÿÿÿÿÿ€ÿÿÿÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿ ÿÿhhhÿhhhÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿ~~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿhhhÿÿÿÿÿÿÿÿ€ÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿ ÿÿÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿ~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ&ÿÿÿÿÿÿhhhÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!ÿÿÿÿÿhhhÿÿÿÿÿÿÿÿÿÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿ~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$ìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}|€ÿ}|€ÿÿihšÿigšÿihšÿjhšÿihšÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$íÿ$íÿ$íÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgggÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿ~~ÿÿ~~ÿ~~ÿÿÿ~~ÿ~~ÿÿ}}€ÿ}|€ÿ}|€ÿ}}€ÿji™ÿÿigšÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿÈÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ}}ÿ}}ÿ}}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$íÿ$íÿ$îÿ$íÿ$íÿ$íÿ$íÿ$íÿ `ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿbbbÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ}|€ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ}}ÿ}}ÿ}|€ÿ}|€ÿÿÿÿÿÿÿÿÿÿÿœÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿžÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿgghÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ}|€ÿ}}ÿ}|€ÿxx…ÿxx…ÿyyƒÿÿÿÿÿÿÿÿÿÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ Ïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ$ÿ&ÿhhhÿgghÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿÿÿ~~ÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿ~~ÿ}}€ÿ}}€ÿ}}€ÿ}|€ÿ}}ÿyyƒÿyyƒÿÿÿÿÿÿÿÿÿÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿigšÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgghÿgghÿhhhÿÿ~~ÿ€ÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿ~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿ~~ÿ}}€ÿ}}€ÿ}}€ÿ}}€ÿ}}ÿyyƒÿyyƒÿÿÿÿÿÿÿÿÿ#æÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ%íÿ$íÿ$íÿ!×ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ$ÿgghÿhhhÿ~~ÿ~~ÿÿ€ÿ€ÿÿ€ÿ~~ÿ~~ÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿ~~ÿÿÿÿ~~ÿ~~~ÿ~~~ÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿ~~ÿÿÿÿ~~ÿÿÿÿÿ~~ÿÿ}}€ÿ}}€ÿ}}€ÿ}}€ÿ}}ÿyyƒÿz{|ÿÿÿÿÿÿÿÿÿ Yÿ Ïÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$ìÿ Ïÿ Aÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ$ÿgghÿhhhÿ~~ÿ~~ÿÿ€ÿ€ÿ~~ÿ~~ÿ}}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~ÿÿÿ~~~ÿ~~ÿÿ~~~ÿ~~~ÿÿ~~ÿ~~~ÿÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿÿÿ~~ÿ~~ÿÿÿÿÿ~~ÿÿ~~ÿ}}ÿ~~ÿ}}€ÿ}}ÿ|}}ÿ{|}ÿquxÿosuÿÿÿÿÿÿÿÿÿÿÿ Yÿ$ìÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ$íÿ#éÿÿÿÿÿ ÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeefÿgghÿgghÿ~~ÿÿÿÿÿÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~ÿ~~ÿÿ~~~ÿÿ~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿ~~ÿÿÿÿÿÿÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ}}ÿ{|}ÿz{|ÿosuÿnrtÿÿÿÿÿÿÿÿÿÿÿÿ Yÿ Ïÿ"Úÿ$ìÿ$íÿ$íÿ$íÿ$íÿ$íÿ!×ÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿgghÿgggÿÿ~~ÿ~~ÿÿÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~~ÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿÿÿ~~ÿ~~~ÿ~~ÿÿÿ~~~ÿÿ~~~ÿÿ~~ÿ~~ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ}}ÿ~~~ÿ~~~ÿ}|€ÿÿÿÿÿÿÿÿ~~ÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ}}}ÿz{|ÿz{{ÿmqtÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ Yÿ Tÿÿÿ Yÿ²ÿ Cÿÿÿ ÿOOOÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxx…ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ&ÿgghÿhghÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿ~~ÿÿ~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿÿÿ}|€ÿÿÿÿÿÿ~~ÿÿÿ~~ÿÿ~~ÿ~~ÿ~~ÿ}}}ÿz{|ÿz{|ÿz{|ÿz{|ÿz{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿxx…ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgghÿgghÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ}|€ÿÿÿÿÿ~~ÿÿÿ~~ÿÿÿÿÿ~~ÿ~~ÿ~~ÿ}}}ÿz{|ÿz||ÿ{|}ÿ{|}ÿ{||ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ~~ÿÿxx…ÿxx…ÿxx…ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ&ÿgggÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿÿ~~ÿÿÿÿÿÿÿ~~ÿÿ}|€ÿ}}ÿ~~ÿ~~~ÿ}}~ÿ}}~ÿ{||ÿ{|}ÿ|}}ÿ|}~ÿx|ÿx|~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿ~~ÿ}|€ÿ}|€ÿÿ~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~ÿÿ~~ÿÿ~~~ÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ}|€ÿÿÿÿÿÿÿ}}€ÿ{||ÿ{||ÿ}}~ÿ{|}ÿ{|}ÿ|}}ÿÿ}‚„ÿÿƒ…ÿÿÿÿÿÿÿÿÿ ÿ&49ÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ}|€ÿ~~€ÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿgghÿ~~ÿ~~ÿ€ÿ€ÿ~~ÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿ~~ÿÿÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ}|€ÿÿ}|€ÿÿÿÿÿÿÿÿÿÿÿÿÿ{|}ÿÿÿÿ}‚„ÿ‚†ˆÿÿÿ|€‚ÿÿt…ŽÿBLQÿ ÿ ÿGUZÿ\owÿÿMY^ÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~€ÿÿ~}€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzy†ÿ€ÿ€ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿÿÿÿ}|€ÿÿÿÿÿÿÿÿÿÿÿÿÿw{}ÿx}ÿÿÿÿÿ„‰‹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~}€ÿ~~€ÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!!"ÿXWZÿ~~ÿ~~ÿ~}ÿ~~ÿ€ÿ€ÿ€ÿÿÿÿ€ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ}|€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~€ÿ~~€ÿ~~€ÿ~€ÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJHmÿ~~ÿ~~ÿ~~ÿ€ÿ€ÿ€ÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ}|€ÿ}|€ÿ}|€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJHmÿyy†ÿyy†ÿ€ÿ€ÿ€ÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~ÿÿ~~ÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ}|€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJHmÿzy†ÿzy†ÿ~~ÿ€ÿ€ÿÿÿÿ~~ÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~ÿÿ~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJHmÿigšÿ~~ÿ€ÿ€ÿ€ÿÿÿÿÿ~~ÿ€ÿ€ÿÿÿÿÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~ÿÿ~~~ÿÿ~~~ÿÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿig›ÿyy†ÿÿ€ÿ~~ÿÿÿ~~ÿ~~ÿ€ÿÿÿÿÿÿÿÿ~~ÿÿÿ~~~ÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$ÿzy†ÿ~~ÿ}}€ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ€ÿÿ~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿ~~ÿÿÿÿÿÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿxx…ÿÿÿÿÿÿÿÿÿ}|€ÿÿxx…ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"ÿ~~ÿ}}€ÿ~~ÿ~~ÿ~~ÿÿxx…ÿÿ}|€ÿÿÿÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿ}|€ÿÿ~~~ÿÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ}|€ÿÿÿÿÿÿÿxx…ÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿoopÿ}}€ÿxx…ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~ÿÿÿ~~~ÿÿÿ~~ÿÿÿ~~~ÿÿ~~~ÿÿÿÿÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿ~~~ÿ~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿxx…ÿÿÿÿÿÿÿÿÿxx…ÿÿÿÿxx…ÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿTT^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿÿÿ~~~ÿÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿ~~ÿ~~~ÿ~~ÿÿ~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~ÿ~~~ÿ~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿ~~ÿ~~ÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿYYYÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿÿ~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~ÿÿ~~~ÿÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿAAAÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿ~~ÿ~~~ÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿ~~ÿÿÿ~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿ~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿAAAÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿAAAÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿ~~ÿÿÿ~~ÿÿÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRRRÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿÿÿ~~ÿÿ~~ÿÿ~~ÿ~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿ~~ÿ~~ÿÿ~~ÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿ~~ÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ~~ÿÿ~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxx…ÿ~~~ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿÿ~~~ÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿxx…ÿ~~~ÿxx…ÿ~~~ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿ~~ÿÿÿ~~ÿ~~ÿÿ~~ÿÿ~~ÿ~~ÿ~~ÿÿ~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ~~~ÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿxx…ÿ~~~ÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿ~~ÿÿÿÿ~~ÿÿÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿ~~~ÿÿ~~~ÿÿÿÿÿ~~~ÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿLLLÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿ~~~ÿxx…ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿÿ~~ÿ~~ÿ~~ÿÿ~~ÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~~ÿÿÿ~~~ÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ444ÿÿÿÿÿÿÿÿCCCÿxx…ÿ~~~ÿxx…ÿxx…ÿ~~~ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿÿ~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿKKKÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ~~~ÿ~~~ÿ~~~ÿxx…ÿÿÿ~~~ÿ~~~ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@@@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ444ÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿ~~~ÿ~~~ÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿKKKÿÿÿÿÿÿÿÿÿÿ}|€ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ===ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,,,ÿÿÿÿÿÿWWWÿ,,,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}|€ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿ¨¨¨ÿÃÃÃÿÃÃÃÿžžžÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿ,,,ÿÿÿÿÿ[[[ÿÿ,,,ÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿ}|€ÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÃÃÃÿÃÃÃÿÃÃÃÿÁÁÁÿÿÿÿÿÿÿÿÿÿÿ<<<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,,,ÿÿÿÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¨¨¨ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÿÿÿÿÿÿÿÿÿÿ{{{ÿ(((ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ444ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¨¨¨ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÿÿÿÿÿÿÿÿÿÿÿÿGGGÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿ,,,ÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ}|€ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÁÁÁÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¼ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ%íÿ,,,ÿÿÿÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿ~~~ÿ}|€ÿ}|€ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿÿÿÿÿÿ~~ÿÿÿÿÿ˜˜˜ÿ»»»ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÿÿÿÿÿÿÿÿÿÿÿÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ“ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ444ÿÿÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿÿ~~~ÿÿxx„ÿxx…ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿÿÿÿÿUUUÿ|||ÿÿÿÿÿÿÿÿžžžÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿžžžÿXXXÿÿÿÿÿÿÿÿÿÿÿOOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿlÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿ&ÿ&ÿ!ÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿiiiÿÿÿÿÿÿÿÿÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÁÁÁÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ“ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿCCCÿÿÿÿÿÿÿÿÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿ|||ÿoooÿ\\\ÿTTTÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿ ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~~ÿ~~ÿ~~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿLLLÿÿÿÿ{{{ÿÿÿÿÿ{{{ÿÿÿÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿkkkÿÿÿÿÿÿÿÿÿKKKÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿDDDÿÿÿÿ{{{ÿÿÿÿ{{{ÿÿÿÿÁÁÁÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿjÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿ,,,ÿÿÿÿÿÿÿÿÿOOOÿÿÿÿÿÿÿÿÿÿÿÿ|||ÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿ ÿCCCÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ ÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿ˜˜˜ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÿÿÿÿÿÿÿwwwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,,,ÿÿÿÿÿÿÿÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvvvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿ(((ÿUUUÿsssÿÿÿÿÿÿÿÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿDDDÿÿÿÿÿÿÿÿÿÿÿÿÁÁÁÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿžžžÿÿÿÿÿÿwwwÿ$$$ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‘ÿÿÿÿÿÿÿÿÿÿÿÿ444ÿÿÿÿÿÿÿÿÿÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿCCCÿsssÿÿsssÿ:::ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ˜˜˜ÿ»»»ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÁÁÁÿÿÿÿÿÿÿÿÿÿÿÿ<<<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ Fÿ½ÿÿÿÿÿÿÿÿÿÿÿÿ _ÿÿÿÿÿÿÿÿÿÿ???ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿNNNÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ444ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÁÁÁÿÃÃÃÿÃÃÃÿÃÃÃÿ°°°ÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿ,,,ÿÿÿÿÿÿÿÿÿÿKKKÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿžžžÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿ¦¦¦ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ’ÿÿÿÿÿÿÿÿÿÿÿ,,,ÿÿÿÿÿ$$$ÿ???ÿWWWÿgggÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<<<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿžžžÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿOOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888ÿsssÿÿÿÿÿÿÿÿÿÿÿÿÁÁÁÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÁÁÁÿÿÿÿÿÿÿÿÿÿ<<<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhf›ÿÿÿÿÿOOOÿ ÿOOOÿÿÿÿÿÿÿÿ%íÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ444ÿÿÿÿÿÿÿÿÿÿÿ˜˜˜ÿÁÁÁÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿtÿÿÿÿÿÿc`¢ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUQ³ÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿ˜˜˜ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿžžžÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXXXÿggsÿfd‘ÿÿÿÿÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÁÁÁÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿžžžÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿmmmÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUQ³ÿÿÿÿÿÿÿÿÿ^^^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿÿÿÿÿÿÿÿÿÿÿ˜˜˜ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿžžžÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿNNNÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÁÁÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}}}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWWWÿÿÿÿÿÿÿÿÿÿÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿ¦¦¦ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿlllÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÁÁÁÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÁÁÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}}}ÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhhhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ˜˜˜ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿWT¯ÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿqqqÿ\\\ÿ___ÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÿÿÿÿÿÿGGGÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿ{{{ÿÿ{{ƒÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿmmmÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿ½½½ÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|||ÿÿÿÿÿÿÿÿ~~~ÿ~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿqqqÿÿ\\\ÿ\\\ÿ\\\ÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÁÁÁÿÃÃÃÿÃÃÃÿÃÃÃÿ»»»ÿ’’’ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿpppÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿkkkÿ\\\ÿ\\\ÿ\\\ÿ___ÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿ{{{ÿÿÿÿÿÿÿÿÿÿ˜˜˜ÿ»»»ÿ»»»ÿ’’’ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿFFFÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿqqqÿfdžÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿoooÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ]]]ÿÿÿÿÿÿÿÿÿÿÿÿÿÿzzzÿ___ÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJJJÿÿÿÿÿÿÿÿÿÿÿÿÿNNNÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿoooÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJJJÿÿÿÿÿÿÿÿÿÿÿsssÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJJJÿÿÿÿÿÿÿÿÿÿQQQÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ333ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ___ÿCCCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ444ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿoooÿÿÿÿÿÿÿÿÿÿÿÿÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿ444ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<<<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@@@ÿÿÿÿÿÿÿÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿ{{{ÿÿÿGGGÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<<<ÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@@@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿsssÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ000ÿoooÿÿoooÿ$$$ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdirewolf-1.5+dfsg/dw-icon.png000066400000000000000000000571161347750676600162160ustar00rootroot00000000000000‰PNG  IHDR\r¨fsRGB®ÎégAMA± üa pHYsttÞfx]ãIDATx^í]¼EµŸäÞ´›ÞB‡DB©>>}ŠŠ€ â£‰Šúh „ÞQ@êC%RDˆ>z•&½'ÔPÒ{»ÉMÞüg÷ìwv¾™Ý™Ýýʽ÷~Ë—»;åÌ™9uÎÌtéÒ¥ËÑH 40Ð)1 é¿Á:ËȯYS ¯—s¦³ ÌÚO\¶¡îJK­ðÖá@%'‚ë Uz²T²EÖ튯"Û,ª®ZŽa5ñÖ`E͘F=©¨æÄNÆ’¡–„ï søl0_¬7ò‚"&o!€h•´'ÀAϊϨÄ,jÔÙÀ@0à˺ÖÎF³ 40Ph0€:„ Ô  P+Ì7Úm` 0Ð`u0 ¨  V˜o´ÛÀ@` Áê` 40P+ 4@­0ßh·:À@# ¡³°å–[Š]wÝU 0@ÌŸ?_Ü{ï½bêÔ© …÷×744€Â‡¡Qa¾ò•¯(âGÂïĉÓŠ4¾W P!Ä6ª5c`·ÝvÍÍͱ}úôi «Fh0€!¾36»Ë.»(Õß”L 63¢Ájƒ÷NÙªNü«V­Šð°óÎ;wJœÔºÓ Pëè$í›Tn Œ?¾“`¢¾ºÙ`õ5š$ÕŸ:Ü0j3ô^ €öJûì™öÉ[4Z­$hɯ’m4êÎŽ/€f8À¿mο%åËz£d½cÄ%?Ýë_ïpw&øœ@ëß]˜‚ É ¡ãL?_âßvÛm;NçÛIOœ@–£¤þ› 'üh'³Ç&$ÿOúSoÉo["lߨ¨ôDWi›C”È|m~Ÿ†Éœ 2Eç]=t7Z²a€Gúù`©áôÁVz^Îl¹S5€jIc½,Ü,%ÕÀ@Ãæ¯–‹iÃʪEø.Ýеƒz‚ÍþΔê#µ X@Ñv”˜L_s#Oû²î€úŸ'5y°ç_Öºxë­·ãÆ³ÖØÔÔ$¦OŸ.|ðAÿV%:$ ýóîì[¼x±¸øâ‹;$~jÕ©$and®ûï¿¿Øn»íRa~å•WÄ”)SRó52t| üêW¿òöü›°rúé§w|dU±‡I Õ ˜çæ›o.~ñ‹_ˆ=öØ#-kã{Ç@Ãù×þ¸Œdq°aùñÞ&Lhh@œ´îŸ»¢FUÇ@“T&™ZÝa‡ÄÀ½9r¤À¶Îõ×__ 2D¼ÿþû^å™Û(Ü·¥¥¥0à}ôÑÂêjT$„³ÒŸ–ÜöÞ{o±ãŽ;–áò¸ váÂ…b³EKÄMïO«©³# ‹†ãÒ×Ζ‡ŸëWtß>€â0š¶šWæäò³ŸýLôïß? Â³gÏ ,ˆêªÅ*B#Ú°˜‰å³ÉZál ~óÍ7Åœ9sRh0€T9gpfœð9¡üú׿ V[R>vQÿ÷NÐúõë'¦M›•ýì³Ïjª1xw¢“ðñöï¾ûî¢k×’«iùò婌 Ö  #iŒi @Œ) —æôYgeÝòÐ}±ª­Meo–G<ù²?¤=p({ì±]YœiÈédtíÜ]o?'~4гgO±é¦›ZÛâÇ„9TpF=†¾#Ï“Ôe@tþÌ3ÏLd4!VÉÓ^·xý­Øpø2*Xzè¡…2„5ÆWðù¤ú€7Z4b U@)bX À¿Ï9çÑÚÚZS”ÂïЈ>¬þŒ;6W£ï¾ûn®:…‹Ã€ &°råJÕr›ôúŸ{î¹bîܹÅAÖ´í+o¤ÖÉ# ·Új+qÚi§¥–idÈŸ½þ6û˽µÖ óc¢þkHÓì©ÖP`—.>ûì³ÝS.ÿX°H,ë×7VLzÅÏø€O‡MmÚ¢ mÞå¢Â=ÁN…¹½g àŸ$ßïã¿øÅ²eVHþ?ü00àbfŠ+P2»ÐÁ»å–[Ä%×^+>¾ýÎ2ÈWËz7øpzæ¹hT9™E­HÞÕH >'üB(èŒÂ…ø¯>b&@V&¿ÀEóg¡Ÿ¶Îhõ~ôg3ʾ¿ôÒKbòäÉ¿¦ôüæ_P¯ò‡›1ƒU‚¢MW©ç d;ˈSz\ƒ@ü&ïšäJ+ÕŸ‰{²€ãJ|ìóÏ??tܳ™êÿ׿þÕ)Î2ˆaìÅîNŸÓzõÐ_T›ÿ¡‡J…1O<ñDj¾F7 ¸ô2r)l㯾%–mŸ~Á¤ê¬Y³ÄÌ™æ€ñ·ß~[`‰ÎEü "ö™|Ô?fÌ%òF—I$‰ZZ ô‡Lµ´à-·é‘œ >•½öÚKùU|müáe¦NÛf«ï†nðiª‘7.4ltæÙ5÷÷eKn‚uIo¼±˜7o^ÄPÄuñõ×_/¼ð‚€SŒi­µÖr©6–‡+Éã ¬ñAXÝãúw# z8I+CÈ;äCÊìø4䯳Î:1<}üñDZ^Iu4€iöûîÂÊL½ × CåöîÕ[œÐoxìÈ8CûÁÅ¿“—¶ Û ÂDpe.¦:^|ñEq÷Ýw;Õ'#læfì¢4,'ÀO‘7&÷FœÁªÄd˜–Öø0M”1½ßf›mÔ~¾IWÿ±õ÷å—_vZöƒ¶wöÙgû6ÙÈŸ€š0€nݺ©8ßô"æþä(çº÷Þ{Tõ¼L`ÆŒâꫯv†Ë7#úoÛ>m"ü$mkà 7T>h18-÷é§Ÿ{í¹§$0S€–Ï!.h °\í÷Ç.?žÀ\yä'âG¹†ýïŠq÷|…04ç£`òƒ =2|ŒxøÈï»Cæüë_ÿ*^{í5k¹¼L`Ù²eâ‚Pzzg)€~Cꛞ$ñ9èÕ«—Øl³Í”ï"Ñ÷á°FIÚK0€&%Øþpü¹&“ÓemË~ðí`|:è Xµ>Àµ¿í)_! €K_FpDŸbèÏü7î@¼ë®»™@–U0&’ÎèËgœQÈx¢NH~žlDÏ ÿ†ÓŒ¤¼0L€ê»^.µ"ñ¶mŒ'<û,ùÙöö?ðÀÖî€ èCƒx¾SæB€¯ CvÁÀab‰‡)ÀËã±k®¹&q}¶jžÛd°ìdÚ‚ ‡–ËÚ5$5w|Á6wNy =€Î8|œèÒŒ˜3ýo›ôGýI À„Ÿpž5^Ó˜@ªZã†ú@€ ÀìáS,Êk#PÊIòo|CÀ^vMúD†f°hÑ"u^¤Ó}÷Ý'–.]j­®Ÿ<waø}<9¹Ÿ<7ñÐï}ϵù _ñ{7V(pè ýúAšÊÀô€ó’Ž#¼×ßí™àh0Ÿ‘¨\ÞÂ@ô1x—~Œ \OÇOcRRÚÊnÍbAøíجg˜€áGŒ9#VËNjÒàA|ưaÃk·1×ÀÎl+2¥X¡ ¸|ð0©¸¯ ð~¹®dÑ’ð?”O=õTe?w[±BIÛVÏà˜¨‚¤>‡9oŒ‚é@×7ÞxCl²É&‰‡ÁÂDÚh£ŒèƒðŸÿü§ªÃ%ÇÀu#B@V €ºõÀ°Ñâñ£~èÝKìصD§ ¯´hm :iu@nkº¦œtèkÛ{Jþ¼DÏqeœ €Gëƒm¹Ï4˜¾êãBPo’p.ÆœÏ@‹i•¥AµÏÌé"‹³ç²|WÝ:;~üø²&@츅ˆ§ùóç*QTD]Ø@*ñ§!¾ƒÈèq(£1 [vùçúoc]†jÝsï¶S–JÕëÔx'ÏäÅò2 : [‚ÝgŸž@ìpÜé+ºýž­Õx©Âˆ?@¤ß“Ð=t¸ˆ¾êuஆJ¥¼{5*Wg¨×yÀ„Œ<&ë®A½][ÕÇ5Œ¢M%±]’¯ àRgæf0jÔ¨t®&¹Ï·ÞŠ%_§]m—`¥iíÞÇBÉ“Å@Û6S;θ™ÅèÒf>î\UNR;i*Ô˜øéR —ÙÊ/ÀÐÿíRypÝO>Ç}aóOƒø]1]™|¹Ž#Ò¸Œ tLßÍ=ØE÷…/|A­ÙÓVbÔÿꫯª'¸ÐðÐ7ÄÆû´ÑEcV‚ØxíY’ú^™qr®Õ‡)s?ÎØõ«â’èÑ£‡ºÞ Éuë/¤ÿM7ÝäܯFÆr ¸Ð]Zžš™Ô¬¦€Ïî1×hAHØùº¢Ÿ†ÄÆäôÇyüñÇý v¢ùä\ÄΉ-K…ä„QõèÄt*¨–3FËŸÚ·«×Ý„O^/g|z{:“ñm7 © ÎxÄNKLh]ò7ˆ¿„YÜX!纶\Cª àJ¸úÄ0•sœë¢‹.Ê3ÏD«Ô$(‘½Oª¿®âÛ*ƒ•ˆœ~9ñÓ¿õ_ðˆ°Ws( Gܼ²1ªCg icBßùoÒ;ÆŽ¼Xª…„ÂF"žˆøæ@€•¬4Çqš6ØÜéb oË ˆIú¸Rö AƒÄ±Çëš=5iºŠ¯tBœnÿs¦ÀTü@9HÙ8”9—ÖV©nظcªÒÄ8h²Ùü iæJžþ]ýõe®€44òS¢}L›P°M3k P^â7âC °_>øàƒÔ‹B\íæU8¡ÜÕ ŸÍäüãþ,72©ï _š&ÂqãCøXèðØÆÄÚž§FcëŽAÃøâ ÀDÒ¿¨y—*ê<ƒ®½éà’Ì!M³Kêjª P4ž|˜ŽäJÛ'žTŸ®¾*‰•Ò!+ñÇõ«r»Ì…¿/'ÖÁòµë’§H¢ÒëÊS÷a‡¦.ᪿÏü(znÖS}¦¹Ëáƒí‡;³ÀodyÕe"û 2‹€í˜%ma0lïÕ$§þŽ3‚h€M uqÕ˜3<%™[¼o>urœ›Lˆ¼Ú ê?á„b·Qg–¹RoeФA/  Z û ·åI¼Oºj›æœ‹òsâçÌÃÀHòÀj,[Á˜ƒ4I£k26\æé3˜œƒt Y=ÎÁ<ýËZVi”{ÀU ’y1Ý"õ)’ùR …[Öô³HÙD>|a@~'ˆ¬ = Ïð àá ï)v€à®ö\Í3ÿê¹lU4>ƒˆG'ú¿ÕßÔ$¹¶àË„~@nІºùшÈ:)Jå"Pä@šÀrµ5yÙ1ò¬¿½åUU¸E8OÒûæ$ñK¢;Þ´%ÄY·×bŒG ŠÎ40–˜D[ÖW.\lC—mó9ç¥té‚ì$×ðoþ·Bôdýõֻ챧ˆˆ?…ÚÑ27ÐbƒH‚J!‚$~Âd6)iÄD qØ\áL¼™Èŕϭû[TL§/«Çm ¦1Jî€I`sºJFÇ®´ûlnØ»GY´¦E®d¼ô¾Òæ?ü;ŠÇnR:¤#Ô¥yͼ&¼÷QýG'E5MšXÐKØm½Ÿu3A½Ó¼þŠ¡–òœûü¬’G ¯.{Neû´tó©&[^ÛŠ «ÎŸ‚–<Ùš²•â„ÏW Î@3Æ<5hÓh¹Ê¶€ž’øûË+½¿þ_ÿ%FÊMjÒ“c-´íIÊW„ØSæ[b Sû£Øx>¹ÑÏùù p+‘$~u;c.Õ­n[%º65GY»5­ÍÍÝIJÁ}ÌM]ÚDÛš&Ñ¿ûñøÓÿ?þœR|öÛk'±öZCEψ÷ßù·¸ûï ܧH)óåªÖX‹€à¡þg¹iY7ºà4G &B㸱ÒlâA\s–ø…'ßÛm–Çr5KpÒñǧÎëZ¿bFáäU"‚y” Ž¾¾ŸÚ9Î4,R3‰`â¯Z\bÚUšekƒª»vm`+ÛºŠî’'üê”ÓÁéÕ³»ØrüºâÙOm²^SÊÄô¥X\¼‚k×x9Cu@ZÄ>ÿ\™n> Õó bšëÐY22€ì8éßÒ"†÷ ¾<á«b­PòÛj«ñGð0‚Ç;NÜ&Ög"þ$ÉÙ¨j“3RBxSPvFxaHd«% g¦ú¼f•ÒDÎ8ë¼ì–PÒ‹!ÀGÞºDn2„f€L€;}˜9 š@Éüôòd/Ujé˜Ù]¿ÍäËKôFçN/µª‡I)"Ö(üUC‚þ>»)ÂЖ%cAL˜G™K²!åY 5?l &”|N—„Ÿ‡ø{öè–x½”Óµë!ìÝ¥êÝmÕJõÌ.ë,*•ã1®&J“ á(iºgüc† _=à5úd÷ëÓ@÷îûNRibŒ@S¯=À¤ú³†i•€"# S&†3¢B[?æù·9Ò’˜™¬œœ’Ex¹—¯X)fϞأŋ««À­)ì…"’,s H›? &`¨â  ›´û!ùAÃGN?$ éå’ßïŸ6šø. ĸ^¯>ägòº«(þ’„å›}(òÙ.០9}e€;£Ýƒ®N? @ÛË—.g}Ž8[>¾éÀÞýÄ7[úFÅð÷ÝCG‰=z¶Dïz·ô'NŒUvb3ev%~Ÿmß¶È@Ÿ¾ƒ ¤-/úÔ×óLU:1bEQHGþßÃWbªÿ÷ä®.â¶þ6U?¯çßFüú Äò1;zOÌ ‰a„•š.ñq’Ý9M3ˆ;"ùw¥îg¯X¾Dý^tÉeÎsD~|¿A¢·$‚AÒ‘hÒÌg¶µ‰qŸ½«³gÏÒ™VZfµó¼`~–cÁé²òØøyMñh¹³O¿lˆ«þ$iWé}Î%ý5b5=Iñ2S5ƒd¥UõÍdÈ MW‰›l= ŒØ‚‘"-}eŒ p6yJ2ÿ凉1rép°…øÑÞ ÃÚâÉU hç´ÒaÁ70} ûËþ~Òî*óØó«_ ˆØ0…@9÷˜GKBšÓ­hlÇ–ô Óhεؖ^À¨û ÆÄ“\3ˆáDøè0#é̈­þМ‘ÿ†Üî.Wš»÷^|i"Ú@ô/Ž\W̽¡ø$~_œ¼6J•ù23–,]k'ÕPô`†õÁç¬H«l/H…à­×jË€œA ûÊ~Dùõ•Eüa"µ__.S^lÙ7 |ÛKC,'vr”%Ù}<ÂO­5“ž›¬Ñ2?üfZ°ùb}099£0t¸G¸Ì–„‹‹ Uß9…Ìe®XòݘÊßuççj‹Ìˆ±‚:ÏUz—ú}Îp©¯=ç‰1€ˆè•CÍŸø¡ö¯-ÕþÞ=zFËU䬑MÜΈ'!'\Qéªÿ‰ #d’åzSÐXª¤µyýÓ:ç‰Ì?/]˜Vcü;ÓD†IGî¨æ±xÉRçºG}>Ó9¯KFßÃALuvfG`\H­æ!ÔÒ·ŸŠïÇAðú# 7çí9o]æ€=œÄ<@¦,£…øÊ¶kj¹©&Ï?Úãy3÷=lŸâçQ©²ä/ùQÎ<é¤D\ýbÞ,qý’™ñyϰщeO?ýt§º?˜ƒE¤¢–‹€¥½Ö¡] ¢ÍX‡^ÁÞ±ÖZ¢7;Ä£uåJu¹ƒ‘Û:ÔYXFà >_4m¥e@îØ3ñÍÈÎgÉÄ_ JÄÑ+JVØ*îJ÷ßæ Þƒ ¬õÉ41eébo¿ØZ¾ÁÈ»’:.ÐYµlÈ”›EºÈøñ&%Q|S³´÷yÚo¿ýÏkËЄ/H¥üIÁ3)µšˆ¸"Ï?õƒ 'xÞGî (¼ï¡ãŒÏi§œ’Š«'‡Ïý\ ž>5öpíÀĨ&öê-ëÝ?µþjg(" Ú0×S{]G-Zz·È m^kü}û–HöÝw_Õ·º"OFs8Z™4´ÏMZ- ¸Bg˜7žI’ \sÔÑêëµGå =´00£:²oý1€Î*¹6%c×&)ù›¤ éàHŽªR{¤Ú߯_¿XõŒº!~‚ ~G›qï?hÐãi¾ì» \”Ç@øQÔ"[›Gl<– ñK ¯=òhùø3b¼ÛO®ˆ/: ¤âY0^E,VÐ:m Ë AƒÖ`-×g)E–‰I~êÔÿzbÖŸ”Á0– ËèDÌ×÷õjõoF&â ž<ÜawÚi§ W^¨§iòäÉ*®ÃvTXL¶9ßY¢Õr¿DPšàR8DænRíï'—øúPÒëJüœœ.hV¹ôaÛL£p<—2s3 NÂKKùc“¸ü‡E_uF`bô®V¯w…ÎÀ{_&ÐÙ€¢kWÐ[®ñê×_¬'ö꩞‰_'hîÜÓ7™ÔW=ˆÉ—/%š5Ð\á'¦P/ob4nE1´Ñi´ÐÜÜ, ö÷–,?Ñ\Õ÷YÍKH®—oú±íŒ@å—…˜¤¼ÉLp‚»‰wü9HÈÂ’ÌÞÎÂR·#Ò ÄßÒ«W4Ã@ü`xLÄŸ$í²¬Çg™Ú<„—bý¹6 û$¿’ \ƒzÞ¾„¨þF,VÓ4Ê‚Ûz,c3Ý’`m„;œÐ"=þ½$ñÓ¤$°IþzšÀœÐ]VÒˆÚäÃHzÇqaû·>I]à¬G"¬L_E;¡IxÐo­úX©vS5Þ° E ×ÔÓì~)W)à]ë5.íY¶ga^¦26!-‚ìÏ΢‚ºŽ¡-Ÿéþ@&@{ ô_Áw4æœÈô>Äá“7ï$p*¯|QÉ# [h°Iý·ÁNð§Ž:P¦JÆt¤1±®ôéÓG <8š”’É…ËVÃá;áÇ2u' ^Fý²LÃ1U•Ôg }EL$m… Ç2 <õžÎ@Ýã»*ÀûHÂ$ßB¿C½áѪp q¼¤P_]ê¥ÙÒE "«š¬OÕ¿âçðÚ$|Ì\Ò:ˆ2üI«bR'Yƒ(Ñí¤Ñ»)[×\„A02HŠñ§É¾¯”üIÖU~ú»LîH÷û§™Ø×¸×_¯²ŒAÐÉ? wÀ~ü´`>žy˜€SÃí<“‘èÜ$¿«êO8©&ñ»q,_Ù rPÓòžM3pцb¸bp9‚S7ÙšåA¢µJúª_±ªLí] ˆ1>ßá§O\nê2Ù‹Œ$Ï~´žÎD䱘²ýY¾,L‹¤·­lZÊ‘Hfˆüw3Kû^~u8ìª\%7—Õ:ñŠÀR!‡rE`M©4æ»Â•OýåažH\õ÷•üÕ舉Êl{SH\ÖÈ@‡(@޽oœ¸­UBx^î€,sþ%8ù\ ¶±H,ËÛ¤àP÷¨%‚ù’÷v§¢æ ßÀëÌãt…­½Ý?h4hrÂî¯Çd“€4©#)O’5ìiQŸÈÎvº6<¤i<&ç(GÄ_V?†$q|%¥\ÄиæÃëÖÏPB¨C¿ ?SV/ÄPmj.lžùÞÞˆ}@lÍ_6–ûˆÐ|GêpD&•µälÓˆ„ˆ>ÍVK#ä$˜¸£/‰QcÕãb÷RŒ)L8v0ˆ ‚ùYz~ý|ý;g!ÑÇà„äo8(cXë)÷ÊàiO)bòœ@±ÑŒ!¿>ÄÁ'wUA'ã„“Ò¶öŸ%¤3µÿaÛ¼¿:SHÄ [‚ŒV Â}$­U}6xa8Sb Ò|…Á’¡¢Zi@­iÅôˆ? öñ«yWSK§eDKŒ™17×nYû¯IQ"úT†¡5¬ÃÈá+ó+4N>‰QX¾E’É0˜Ñ¥.®H¬A¾$&àsŽ/èíI P ëþØðÃÕä,Äï;ñ}ëâùæžþ@`–HW·ÿÕ7M…M‚߸†¯ÛØÐX&sˆ3 ún,Ã}&DqBÕ‰:I5çÚFRIƒÓŽT0Ýüƒ3Ð%ÐÇg~ÚòÖû-Ä]$ñ¯AÈ/¤V⯖¦´[Óçv´œìe~Ýà µÛaÔ­Ž;*ËOøŽ$9ÿUŸHÕNh×´ß *ÇsEfŠÚºÍÂ/¥-í•¶=8»â°’–¤ú«¿=1ã›ß§z¾®Ÿ¤ ˜6ДyþÆËêñµ¯MùéE Èj"5“Ô÷íC0ðñ¡à ÅgÚQ^rjû8·ÛQ÷¼@5ßìUEå3§y𹺯«ý¼¬•y„‘wÎLL÷ ß+¼l Î=ô&âMc 6‚Ï‹ì4]óîÜÿ*f„9 }%™@{ðH%ÎHý¯â¸$6ʼn6‰pûßäT ´mí*pmÅÀµÏL6ÂwU£ù2rF Õ8sÌL›ð‰ò‘ôwa^:I`*õ`O“c°33®´é‡Ôg)èJ-äsqš…Ññ§´Þ —.õÚÀWu[>ª÷i’—òhL)‚­¼EËx!S‰¨îw æ£3‘î8Ôi°và:ÈÌ®Ç.*ñ_w7ú0Lñu{k›à&FÀ}z@&PÈ€2o´ŒVl\ypi”™4¦@(b^¼~ 6R0´²0ë<šŠKræ÷î{ÖŠÖÒæŽÔ¯Ç¤"¹1¸•TƒòvܦÒS½\¢sb7 Œ‰ KQHN­Rþ·NxŠæ€H[^52U§oŒAÿ‰ šL*ýéï´ ŸŸE”s£õ«)’ ¸ø¨pW=. *  ®TSiºÂÉW aØýºPÄ$ã0‘äŒÌЀ Èã’L¯2Œ^Ÿ©OºS”çáxqÅGR}®uÔK>ÓöáJ Âzc]÷Ý78è£;¥\ÞÕñÇë‹Óè6o˜Qgt.œ; fnNè*².ñÄiÐ\qäš/Éü!fZ>5Õ¯¯¨äÅŸk*™3š‰ H ˜ª•î´Ó kº#/ibê©ç-[pÊž‰›&&3ÅEBç!µ¸MƒrÕ¬t‰oŠ^L¢Î3i¸vµ^4y•¬ÝÂL³=];ë”Ï1 /©®4'!Êr&×ôáö5ÿwÞzð嘉úKóÀ4NB¼˜V.Ê4+=ÂŽzÏV &À>ê (€IúWøCçYžåG_ÇgE©±iŽJ"“©b#Žˆ8‰Hµ_^ΖDù˜0*ÛA : ÃáL€Æ ´QIí¸ÖL ,д% 2Ê8¬•'hÇžnR!­¹é‘űVæi ݆CoÓ‚¹ ç.Á@NÚþ3 TCà˜ª%èZIîæ3²j®ÜÅ—à/ååõr3À©®P:+Ó„=¼¬iÀTw"SàDÏM>S@à?Ó¡‡*FëhnZIx®ó¸hÌ—iyTñ¬ÀyK´°!’ÞúÒ_RÝ¥/Ñ’_¸Ô˜´ÊÀ5+¥1Ĩ<ŒL”ï8.  Oæ“Ð5dÙušç'¸93d{íµ—ÒøÖá\L É߆9Qƒà*£ I·Ì¨s(È"àòHN„:9ƒp€(S–4Í""|›­¾O ʘ•©š4¾'!Kcu\æ²|77tßýîwÅ&›lÕ“‹ ä†¦Ø švÛm·I¼JNŒY ÓÄ$ çà ɩWô2_Z?t‡÷ lYˆÐ˜l}µá?ÉOƒÝx]d¥tbúÀh›¾¢¾Õ@ê¤áµ¿¯½öÚbË-·n¸¡ÀiBx–,Y’Ô¶ÞŸÀ.cjâ†9 UAY«Jj§ ÎD¶wYo™G.,¨; M0ÄúHö¶.3’„»î+WЦիE› =í!'éjù»ÆFüÚîìE`Œ3F1‚=z¨º,Äl*ã#ôЇ.§vZWƒ£11"\ÖøQgµìÓRb`ÞõˆéiŽ7&‹ñPš‚6êNe¾(Z±Ö³`ÁñÏþS¼÷ž}’^Øv&ašIYtgŒ  èFx}®Ê™b©¶š¯ãÂÔ¾Í)Ó«’6ÿBDO™õ¿m ´R¤’ó¥½ÕíÊ ê…M€Z#Ýus5¼š0“­o‹CˆÞÛ€bRÙ¤ÎÓ;ý›)o×Õò\Mʻ⯚8ë,má M7ÝTl¶ÙfbîܹbÞ¼yÆ®Û̆j]U5W鯤›Ôiåj¥pÉOæGÌ4Hbš#Kz½X’f`Òª=‰: qgí§I+H:–¼šf€ñLÀ¬ÍZÎãRŸN„.eŠÎÃÀËüú›|ã —ü6_@WùA_…p¨º‘¥ À}›&L믟|FA$ªP5 î’cIŸÔ|ÝÚU‚qi›¤%¸Öç;x›œkGï9±ëÑxÚú?1A[<€Ž¯2-!¼¤Ó·üÕÁV À`ÔSªHRk˾iŽ2NÊ“fBTr@"‚¦@ vrPŒH (/ÿU¦EøØ$¼ ~*ÙÇJÔ]Ïçè¹öWWóÁößÿºbUs®I[ø×°êê ¢HЍ•jÌ„iaÂI“JiÜqþ»Výr%Î|ç;ßQgNî°ÃbÏ=÷2ðL<òÈ#âwÝ(6»yTÕ¤I“Ôû—Ÿû›1jqÒI'ŠÙ³g‹~ýú)§Ú矞i½ÝÖJäÃ1`Ë–-+;AEˆ°9 Ê®–ñ•NUa®Ø4ÉyYÓw (IÒ·Z ÅL=þß`™"üü†µüz‘ú ܞͭ¢GS«˜57wøá‡ \:‹‰¾léRE `Ó§$¦üe²øú׿!~ü“Ÿ©(vÜéKbóÍ·Žò·™èÛ·_ô7¶Ë‚8À vÝuWñܳÿ‹Í= $éXPÀS%'~êú‚~à“›3gNZŒq‡ôï ÀµÑJSo,5—;‹˜àyë ˜ù¯ z„_l¹¯  ·/zyHiÿ€ÄŠåË•dŸ;g†XÕºLI­Å‹‹~}{ŠÕ«–ŠíwÜ5*~òñG‹?˜&îà©úÎ?ò€˜:í±ën{I°•Ìd™—}ñ…§ÅÃÞ#>ñ‘øê„¯‰Ï>ŸUHWW"”šIkŸJa¦¼þúëbÚ´iâÃ?ŒžO?ýTi-ˆ¤÷0 áR^Ä ¼øùçŸ5çJ;>0êykæTÄ-bdAŠ_†=ÓiˆHûžAyÊÒʀɌá‘}úνz•ü»ì²‹RÓ)ÝxÃuâüsNgqšøö·¿-~ðƒˆ£>Züò—¿}üy uk­µ–Ré9ô{bµX.Ö^g-qàAßm¼‘Å«V­#F”{òÇ¿win³ç/ —.ÍRšÂ)"e¹5„ÿÌ3ψ·ß~[jCËËÀSÑ‚ȇü”à ¤eB0R”©Æ¼®(Hë"ØV‡ËW6aZE zuèD««’º/‚¤½nÔ+ñGœø{äAqÉùç‰ë®¾Z<ùØ“¢G÷>¢[s‹hê*¥ÿÊ5âìó~¡µuÅ ±hþb1|ÈÑÒÔKô•y{˼Ë,sf˜%÷+/¿ Ö:Hm¸1¥!‰ƒ¼ûî»FÂO«LÑ‚HаL¸õÖ[G~“´òE|¯(à¦-¸ll)¢ÃYê°Ùô¼.£2)ɯ/sâ|Š þ4¨¼½{÷Vyá;ö˜ÃÅm·ÜÕËÊ|÷×Å’ðá@ûŸ_%.ºà ñÒKq‡™˜@ß–æØk¬^àˆ/ìå/*¥­Õ£¯:1ëANºŽÁ‘#G*üè©Gbï¬H#ØD䜀º ÿíÚž¡ÙÔ~þž«ø¼O‰Z‹îݯSo?ïϯ7Ý[4:?x5}ŽoL á;¿ý›Àm€À©'ÿXÔ`qíU—+‚ & ~©Œ @^xÂÉ\øŸ_üXÜróŸbuOùëŸÅQÿ}˜¸÷Þ{”¶Ùµ×\&^üwÉ<°Í§®]ºŠ»n™¬>ƒýçþ§XwÝuÕožDN¿G}4µHx0‰aÆEyñÎ%™.]±b¥KQç<aέk“$qMj¹ a»ä!px^_ɯ3.báÁY‘UÁr“¶Ø¿¼öäaQˆç¦E꿱oáݘ¼3òh¯Ž@Â%~ñŒÄ‡_N<þ§bÒ©'Š¿5A|aãQ☣—Òþ%µNŽ„²¨ë…§Æµ0=¤–ñµo~KyÄÇò¾õÖ[¹°çëôÁ/\¸0±MròL&ÁÖµà=…3Wi#”ð}9Ì6ÁhyMÿžFä®°éõ'1W?A™ÉÐ$?ðcËÓ¥Ï{¿ uÀ ³àÄŽ¥-w’ ü0ç–›'Ë@Ÿ§UhƒV¿0ðÌœ½L¬5jmÕε×^k%¬ÖÖâ¤SÏˉ%‡ÜSO<".:çDoÀýij¿©rÓò ÏÇý&A”pVçoá ÐD‰èP˜ÔkW_wÄ9TeÑUý2Ï}HÀ&_UbZÿOÊï_Qy»‚0僴nŸ àfÒË÷¿&MÀ¡á÷¯¸_:ØòÐð‹ÄCö=p¢ðW²…Q{è!5ñnÑ¢E*nïa: Œöàü~J>ø  R‚º?åŽ[EÏ=eÝ¥eD8$—>Wïj–sÿPe1 Ï¹¢í4^ @GTlë+?8Ãòo”·ÙìºéÄ@lœ×dÓsBN2OP§‰øLŽJäm–^üî­+ÅŠ0“ˆŸ?+ñ¬+VÊÞæîbyë*ѺJªùrª©w]º‹¶UR;hý´Íêß+–·‰UR¹è"º©÷ìÑGÆôkVK§aSOõÿnêÚM:ÈF‹—_~Y\rÉ%‘ÿfÁÁ¬–ñíÙgÿ-;ü¨êpÃòe+¤¹Ge[%ÐZ±:{to–ÚQà;(s#åhµ¦ À¤¶$©2º=nrÈq\aê*½Í„ ²>¦‚©ŒM[È1N)ºDÛpØÛg–ú:€^hÄœ£½ûlÖ2±`á"Uâæ›K—~œwÞyQ0Îñ?mÒéÊ\@"éú׿Ü™&]»–6çx4¯²º®ñûÖ«ç§ù âÏk)¹ 2! e®„Ã;ígKÁÕϳ³«¿€êOò Ø.ïÈZw¤¥U þáóæÎÏN}-^dÌ!¾$R†žû,‰k[E0€Of΋ݨûóŸÿ\ßþö·ˆÐ ˆøñròäÉÊ,8êèc•6Sâý©ofé’*³ÑFæðåÌ b¹ÒFOä,‚ ÊŠB€¾)†ˆUwÆemO×$lõÐðÉ«3![˜µIåV;H}§ù•ûü0^å–ë 1DîÊÛÚí+<]ù÷(JvÓµõ „§IJm—‹4o¼±Opúé§‹ã?^U µÿí÷>S>„µG¯%~ô£ªðáqãÆ99«¥”ÙýOy:Ë7w±ÐCAMªoQX—ý$¬6õÞ•xG4%£­=ݹ˜džK‘õ ’®MÄFGªj¡þGicùnÓÀË®"þîNº±Á5æèýʤ˜ “äcÿéÌù1ÕA2ݺu·¢"X^\¡¤<ßZ¼H:{öì&?üGâ¼ó/Œi ŸLÿXì·Ç.âëß:Xtir¶Á’`§¢©æé…žÿ÷ª£äW®j“{-°J÷]¬vÐö’æMa À4°iÄjbÑ&)Ÿæö|‘„`«Ë»iE!é]5àÔÛà» “ÚX¿)8%Æ&l+¤ •érWß«åêÊÚGí•÷aŠø‘´‰Ò«WKÙ);ŠWiÄob'œp‚8ûœsÊúóۋϳgΧœqnäX™úîÛâÖÛnOÜ™÷øãg>½‡æVBvÜqÇ®Y³fŠ_|VŸ0{Ö‚è}Ñ  ¢&@™F2Lbt;«™esÕŸº®Ô“—ßUÜ«o„’ñã-?þ­EýeÁ£>¾õLù›Øm÷=ŒÕð#â×sÈâ‚¶¶x4â–.]I~C»´œˆåC,žrꩱO;õ×bó±£Åyü'~dzýµWÄØq› y{VY8/¯$K\•'Ü "}.aZ°`ž¸óÎ[ñ# Œ•NüYÆB/Spáê®À*IoÉLܲ‡’ <\¡ü¼3ù'\ê®užî5qÕ_ŠmúÉ ï˜#÷Ó¯þ põ4ÉvϽàyÐ.â7—^©Ö¡éÁùŸ1i§ÚŒM†ð mù± œ ˜ˆp5.M ÷&`%`¢<ÁˆœƒÌé#cí/ºè"1í£ÙâדJ’Í^~é…òp—£nŸ|òÉÊW0tèÐ˜é€ ¾þ€Ñ£G <ÜŸz)½ôÒsî6‰.„¯3æÈUÝ[Ng˜°\! + ea„q3¯¸ã«æ>Ú€O^ÞÓʃ^—βâ£Zå@,ýºõ”Ä¿ƒ¹IÚìóÖ'¹AúH?˜%à“bÿ‡²øøO$á:[†ËZ—‚°Š0„ð_J@pOo_¾®¯W àþûïW œyÆjÙ±õß‘ ß¾¼páñ毉÷¦Nõ‡žsÌ1âÄO”œî¡ˆó×^Ëåqhýû·¨­¼.̧ááL ',Qê!˜ôd“þ=z4KFÒ%z|¬€«@bRSãŒ>°ÿMŽ5×¶i~ÂLíÒnV&ã;hyóìÞ"~:n·¼Õx•_ûˆ½Uþc~zœ8ñW§©#xg£1#ˆ?JºCþ½FJr%-ñË'\Q€C‹eÕåLN2lJB”!-£-–×|óeCÀòÉ'‹·ßL\vÅubÂÄo–õuñâEâç?;Z è7Hü×ßW]}™xøáÅ÷¿Lk€ðÞ™3í;'©Rœ€#Á(Á\¹é¦ÿþž3gqLýïÖ-ÃÀ™´Ÿ”[p!  $çŸN¸\Ý'¦àÓA^Ÿk9.õõ¥@‘·â¦ÏኊBò‘p€”´Çýò„¨Î«¯º*NüÑ`ÊÃ'$øHò‡ÎaµÏùuâΖ³Ÿ' ¿€2 ÖÈEùèÄjÿ|Ódåh¼ô’sÅ3ÿ*?°¤OŸ¾âÜó.gž{ØaÇ¥)q¹‚þ‰'Š$;LœfÌwRûôé£B™¡5pâÇw:I™òƒðuâ í«¢¹@–bÛ¯¨®îg‘榥I˜m~bBô½Zþ˜]ól4lt)ë&r£ÌèÒÁ®uøäƒ€tî…Ý Ô[¨ß°«£”`Ó#r’` "ÆLØÖÇ–ÉèŽ2 PÕ­·Þ*}=Å©§!fÌ^¢TúO>þP--ê©Y.GFæÌÐ`Ee§vSÚ L“[o¬þ F@'Óï6Ûl#¸ãO¯›æ¤*áû †–·ª @ð,¦wê=dú·¯¶a"N×:ô|®årŒEáEmæô§KˆWç…'Ö|A29á«‘¸m{Ë-·8ž£êýá\‘¤Uýe‘|mŽkì·ß~êœ~0‚óå9‡Kå~ÖV¹î.5€GŸxALøúwb›Š€k®º,Ën»í(¼ãŽ;‰?üáFyBðÒG‘¾‹’W”yã‘Õb]dÏŲ…Zê³Ì¶g—ªiÎg¢êLe]%vþX‹Î‹pà$pž<Æ[¥)Ï ñ¶dýÝg !¾þÅà7G°©/›ŽßL\wÝuÑ'Ü%>!b@“»/î=*Ÿ?ð”ªá»?•úàƒÔ-F8{ ¦Á¼ù‹”:oº¦ì©'ß=ø°XW/¾80pèÈŽÒ4¸ä’ßÉPä«å‘èïÉH!ÎSëÿ®ó4ï¼ÉåÌÂ`Wu^7²¶É{.uMÏó¶'o?–Ôpê²-í6b#±Ûð0¦ÄÐ_¾þ¿Tª¼ÿ(-ƒåhÙS¬”K[p¸Qj‚cIgÑß,ƒrø«1öFREþú¾­_Ô.bòÿð‡?IJaÕÁçîµ×,Ö´Á׈8àÛЧŸzZL“÷à°Ó.á!¯½öb´ö¼Ÿ~:/Ùæ¡ÊêÌ­äiåML"¯³Í•øMÚH'dZ_‹þŽé/¹SÒ‰…ÎýsªÓ©[—¸÷º›Œ~S‰9î83(ñ…0CäÕf÷·É˜…¢ˆ_ú®¹æš²žøÎÁU+›v?¯n*Å@ü½CE÷Mâûò.…¥KŠûî›#þY³‚„@ÜxÀôlÿùŽw·ä2|¦üiäÒ—;Û8QfmÛ¥÷üëù]ˆK•ÊC;Û¤ÝÙ5í†Rû¹äýãÀ,xç³BAœwÃRe>(V'âúûôí+šä™Ñ"†V‹$|½ùÿýßÒr}£ AYlurbeà¼ó.ŽÌS·±¤GËz­ò,z2 (*¢ë„™@¥Á¦òçm/Ky’øÄxÒWž)¢,ˆŸvÂCš ù[ØÅˆû÷$üÖžrãM÷àD¡´tÅWDYèˆ,xÉÁ¢E~^IŠS+‹­Ÿ£þýÌ3Ï´¹ô7ľáÔãùóç ÓQá¦JôÊéÏÕzÄ3à¡T3@}™€/"õü6‚L’¾yÛlOå³h;.Û€ OÌ”‡|ÊØ(Í”*g†¸ÿîË{ˆæVfƒ†˜¦qî¹çFMtÐA⡇ûìSŠ´ŽU¨öCå¯F ÀXâ Z×/þçäÈY‡3 Ž;î8u¥ù¨Q£2ƒ6kVp؉) ‡3Î (äÙ¹QÆXkÆ’€59à()œ¥,_÷ç3²3fä8È¢½¤5»J†Îžp•”`DHý¤®Q¶Ë.+-¡á¬sgÛ¡‡&|1…PVCêë¸ãaÂ/¼ð‚ºÿPH~ø ¶ß~{Á ×™GÒ¸vr4iüuÂgáHNSÉêŽp"5-f%×UÓéÒ8 #I#HÓwÞÿ,AZ›ØIe¹O¾¢ g °„C>‘ŸO‘x#FŒˆr\y啊)üþ÷%|µ¤>‡«<ár‘¤sýq¼÷þûïi#GŽ+ÂCWmøMcÜGj‰wrº Ø®”Ý›p{˜;èœ!È‘1ËÚ¨®M¸ÖaÊGL$+ÊÚuÎpŠl;vAF%¥?œcËÕây;Ê‹üþœ)íá÷‡«d¸2%~´7b°M8m9pÞ¼ùbÁü…jÓÑ¥r÷£)uÖ‚¼þ®°åa<8Ò›ØäÌ È’&1}ÓÑ®M ÚˆÕ´nï*IMù|ý®m¹ zšÖ‘…1Ä&o;ÿ¬ý0-3"sKÜwp†Ü¡‡¥.Hѹà©è<|ŸŽ£„HÆK/½4Q ¼Ø´lY›ˆT Od×S>_âáj¶îÐ%°# ·™—ð©>^K’V8ˆäͤùŽ ò?ÿ|pÖ߇},â´.:½ôA[€‡Œ´%>xÛwØañƒÄ'Õ‰®hЊ®Ï´³Ú01Œ½÷ÞK}vvà °s(°>a]A‡ÕgÒ›$9× t»X‡Ç§-§Þ†O¿³¶©·ï:Æ&“„4¥!Òöœ=p€SUØ–zÀyä àÔˆž {R¤f#P»äËü)¶ú.^¼¸ h,}â0Jè_ïÞ- ã¾«É R56 Áëp©C·çMj¯^‰øÓòئTšÆcó Aü€ÉGC°ù>P+ñ#otY&L$â§»p\xž„zx²8 •OÀs‡[°’ʦÝíGeÓpÄG¾¢Aßô°bøˆØ‹¾ôÓUÕ(±¤1'Ö}ôwQiCd¥<ó¦öÒpã:ØIùp&Ý1»M°3y䕨}|PŽîz<Ç »üÔaÔGZÓ @pýd˜ð*ìg7å<;š° pä‘GŠ“N:I†îÆ}Y*äó²IFp⯦ôüÎ@–ÎU&Éh’ô•&þ¢úåZOQNDÞžŽ·Y³f‰WŸy¡$Ú/@Ä·'öMw ’ÖA˜îûÛm׌ý ¡P mਣŽRkúY‰>0[ñW›è9,í‚èÈK"p“]œFh&U:­L{ÿnrVÍi-mÕú§ïÀ‡¼>8mI2)¹E<õØCâæ?ý^<ðÀvtíLÕZ0·d¾S_áÅ_°´È®Åë’fÀ×øŽØR™5áßÿ¡B„ÿ(Z¤³ 7!2ïᇖL(R9h ©gê Œ!ˆá©¶ËCn ÎWóq>‚V‰/ƒ1Ùù\Šë+œqT‚à+Q§+Žy_³àÑØ?ˆ2ÀQ=Iz»ÍóY€v¤Œ² “‡.ÚjV+W^y•ºèL¡=¦«¯¾ZMAEÝÕ®'F  Ö Á„®Ú›¼þº$ÔMZiÛĠÑ7:À— è°Mzù>±j yD5ß&L’¿ˆŽP¶»˜€7‡½ð°›ö  V¥ë2ùjÉœL€J#%­þ$Ͼ‰1PþJ}^BKësÒw“Ö¶F:ÌpÞN}.‹o‡Ó.ÃVáÄ>RpÉŸ`a—_^ÚAþ€öæ°áDi¸ç¿šf@fP4$Õ§û’˜‡°ÚsYà€1.!¨–ç@Œ/ȳHU‡yð•­ƒÃC÷ߪðÃC1w¬šV6sf}&WJGŠáfJí… ÀÙgƒµÆeþv9õÔS3[ Yv!v›€6må³ÂCmùO5c|7/.ÐÞ!‡|[lðJp@…JU8<4ÖO°pá|ѯßõÒð j‡Nïå©–ö´Ë%yüÕ—J Ì“Y(†²ªÒ±ˆÉ]Iø;JÝ7Üp«X²ï&¥îTéðP4¸ß‘ˆ/o»åFõ­§<À–Û Îmkþµ ~à¬î¿ ˜–½:ã:~Ú„Oc¢iåéûû¯¾(>ß.¸õ&vl8eÈ`äï˜×ÎqxÆÅž£Tiêãa‡}_. >"p×@{1âZK ÷ùi½®ã“7_Ý1…ˆpëq5"/«Y>/è.ý¾GÄ/I?€¿_ᎴŽ7Ÿ«÷¿ß£L€Ñ#«yÒÒÒS)öG=ˆÛzLt‹oŒøé°OFýí ˆN˜¤qˆ÷™¸ s ã>¸Ôkj•‡S =öü•Ù&œz÷O˃g}»íwT×oáùÙO~,^{íhi_±]oš€ÉöWc£S;4›"ˆÈ±ŽÜ¬²–X˶ñ[µl6©a{ïͼmRµà+Äa[îµ9îç*Ãß2LgòAÒŸ2él1fu¢<¦K<ª6ž ™3ÒáYU®ì¹@®ÖsnytЫŽREB‰7ä–¢@W¯J8ߟw°4ÈÓÖëÛG‘NÊpßàKw='=Tìn Ù‡3''þ4ù:Åpg?­wòäÉâW¿ nÆ7¤zÒ¸óÏt¼w­V/ªÎ\UR×µÿ<$wQÞ—zfHêIm†™àíÒÔͺÄzþm×_t¹¸þïwÆñf;M§óD¾§ cW˜ËqÃîN;í¤®ëî+· ûÓ_Fp´JÇà…çžêxÛpîa/¦¦ÜÔŠøÑ‘\  “ž×Ikþz;•h7˨Ö[@šíèÂ…!Ð xÂæ¡yMæÃ*¯ŸöL9ú §;ß7¸Ô*˜ôGåo½ñ¢tøµÈtz‹qŒ8òèŸDmþå¶›¼={‡ŒÂ<¨Çįú"øjIü€¡IzO'eEV¥ÑÄÆJµ™õWRtüÚ`×ßc n¾¼Vü‘GQ×h2D 4H¼ôÔ³â¥yŸDÏ–[n)ĨA%î–g"”xNù1YQ&¬2l.÷ ¬;Tˆ‘ËÀ_¶t¾X$ƒî¿ÿÿÄ…—\ÇýÔ‰óÏ Nê×§—X´x™:DD]¤©l€4¶˜uäó•«5dÀMssS> À.ê¿Q5•ÀVz­ß6ßþÖS~Ze²: Cs˜“p‚.ï¼óNѶªtñ†ãljoþèP! Á)Á‘çrçÌ56¸om3‚Õ€u×/æÍ].vÚeç˜íÜOŽ–çî/ûì;Qüþú?«ËF{tv‚ôƒ‡UŒ@=¥zaKU v!²ZIRØL¨ÞÌ€DÉ~TÙ ¶ü»}é?ÄÓO>¥j>þä“äM¿ñ+À…¼ÕVÜóïtÚ3ú¦ÔÞú4(~ok[¥¶#A#9ïì3Ä7¿u Øt|p\Ù³Ï<-¾üå/+ò‡z=wÞµLˆS‡GŽ©ÎÀmD矾Ô&îO‡«ƒæÀx6ÉëÊëŠÔŠø•´ÈhŒÕ30©™Æwfà²ç¢YžÔÓ„ÓzdÐо{ï-¶’yÄb ä7±T^°ùæ'æ;GeçÈ=½¯KcO>ñ¨Øy—]cyî»ç.ñóŸ->ú$¸øté²ÒÅž+ø28C¨}«ÜÝ—qŽUFK)­°š›\ˆ¬VLÀ6Ó@Ô30«dcy²áÝ 0G28ß䬂¤¥¤ß­§Á’"–GK?B‰{H•› ( ¿n:0@÷éñ¥@ûÚW÷o¾ñºbèÓ?›c8„×y!µÊûúô²• 4EduÊÐßút—Vb$u* P¼€î °¹0ä6!×ÝÁx¥PýeºþúëÕ%,Ò!lþäf#ø‘Ùâ7hm]Vµ¦Œ€ï¸ýV1vܦâO7Ý.¾ô0≈oW®\Y•A̶héŸû,„0ê¨j€„u™tEž\¶6‹ÖÈRI§'Ñ©Íåª ð|ÍÜÀ'Nò0p º #â:`Ý›ÅaûLbXóÆ"Ž`Ó¡ ’q¬ùÂ(‰³Ü?vñÂËï(Æ0kÖL±Î˜ÑR07ÕÃ¥#Yæ0Yk]±-^VõDa MÎ¥A<&$ðM@YäRÆ;Ö¥Ò‚ò@=mÃyø2‘ñß$IŒ#ÑÉj6p“Ô~[½€—ÊanöìÙâƒ>Ó§OËäí¸bá2é‘[,®¿ÿ®tâ7­aÓµ­Ä“DMjýСÃÄ/y¼uDèl¾‚†ÌZMÑ{@øDüE¸r1€J#¯õ×3áóþvëÖÍHü”‡ºèMy\qš„'b.a»3ÌÓ—Ì˺¬›ŒÝ$bnVXtõ_»^üã?,+zùå‰Ušwã”S{ß”N–}h·ÿ…ýïrì7©ôü×VUIssÁZ; h/„ƒ—ºˆÁ5 ¾¯y•äð©k•<¹Œà™gžï¿ÿ¾MÅ|0«Lýªrí ’ÁƒãW‹]|É9âwW\"¾ýíƒbu‰š4…­+R÷ßûôíÀΧh?úEÜ¿«š®3lìEÏ‹L À1i\¨ß+ 3”"Û)z°mÚƒ þ‹‚D·ß~»˜:uªxéégÄõwÜoÞt?!Û‰8ZZzGeZ%!ÿéO×)ÕÿŒ3JWwÈ?ý4Œ!Ð:ˆ;‘ Í+"Üô 699Ýdeº`›åNØÊm¯·o·æ®ÙWŠœè.0ož¢&p^8|ËWÃb3'ô1.J‹qÞxãâoþS|°p¶8묳Th1Ò{ïi—“hkÿ:LW_s¹Ò–~tø1±U³Ï>K…)ëéä“O–A@%FÁ#$ÍDÎw$úŽž9Z° ÏýCµÚp챥ݿûÝåââ‹/Œ !|žrʯcÄ÷j§dø”þŸ}úººoZþ "1}g!/?§=al3mÒ'|%µJÖí;Iùu/}œ\ÕÂk;®ùL}œ'W –ÊU»×ßx[†ô>#†!J¿Á€%I~æY'‰#‰Ïg|,™ÆñᇈîÝ[Ä¢ª39úè£Ô*nÚzë 2ZÇÄ ÿ)à â§+x%=•©äŠHí'P'‘:è':Ö®<'¿‰ ¸2†6Ÿ™ÏÛ,Õ¤Ü €ªÍ3ð.„•½«Õ)éÒ““ÇTÕ¥>Þ3ßüY°bÒlõ¸ÀCšƒ)ïJIȯ¼òŠxçw# u¾[÷5âÝwßÏ=÷œÜøÓ"æÍ›#ÆŒ«ÎÀY=ô¼B¬EŒ¿¹˜2eŠožðéyÓ&‰Þ>I”´ry`ªfY~‰ùÕ²Ÿ¸¢ûÿ¸_®2׎™´€À¹YŽ1·ÏRÂM.5Æ´ ÀèMÚ@“¿¬ÕîÝm(×*€mrù ÉTGšVuRWz¦õ›Ú×Í'ï¾Éó^%B\›|˜x-áíß¿¿˜0a¢Øl3yÚ°%™æGz{烎(V¡»IÉä€,Ë/3( £ê39©´p pø‚þM,«OÒ&¼F F™“ gœ`8¨Y\°$gX=$—~¸ä)ª/¸õxã7VLIsæÌ)§3¹Ñ(n¾ùµnVõKGYôÙŠ~èÉÍLèɪª˜$5'ˆßP'çÎ ·^ÞV÷-̘1C<ýôÓâÑG@¹Nxv~^˜²àG× èo)œ¥Ý¤2µ^wÝu®ôÄñ™_&G=´í9I|X¦‘†3ô‡§fö¨Ž¨5Ô¹'9òÒÔãjMˆ¬p¤!ÔôÝä q±û“–ÇÐŽ©pjÁÓý·¿ýM¼þúëVpáMÇ5Z[l±…÷Rcò8:Mþ‘´öÒ¾cÏÁzë­gÍvÛm·Š·ß~;ú^‚“\|V§­½›ìw4@AAT{VA™Öo—ï´AÉKà*~ãœ?/ÑeáÄÕb(iÈæ°ñó2f»“N´5×n2€cxÉŸþyÅl ÞtLzòªcï°aáåŸiIùîÂÜ’ÆÅ6ÎYÇ <“ )Ù´€r]ª%«:®pVcc9ÑïU<2E±  «ZbÀP=1›õžgfÓÖ— =«Œ²ãd<–ÃÎ;ï¼Dâ÷išÂþûï/6ÜpèXVBôi·ÚyÏ;ïœhE@ÝØ8®40TðnøÎ¼RPm¸‹hOm\¢Šô.ëÒžÿ­ˆÈðTÄMYDOë°NLäÏ&[pÁÁI’ß·n8Éî¾ûîX±¼ŒÜ†jåﮎ‡›ÝÖbéCš P-˜‹jG1€h3DXkšÓÁȲêF=iïÏFàºCÌe9P›…¨Ã¶ïݵQh\ÚãÃâÅ‹Åe—]&^~ùå(_-—ò|úS|ÞZZìÅ÷†j4úÒ@ œpe%œ8¼Ž¢~Úl~Ó'ÙÄtn Ê!–´;Ï4Wæ _¤>G†ºÈg@+ `ƒ6nµµÊÏ ‡¼¤0a¤6u WõyÍÜzÏÛb}”„!È›ø-ýé¼’"y€“´½¤ ñbý¥—^ªö×뉲˴S¡ã¶mùÁ$î¸ãŽ˜VV·)ˆÉT&)”ƒË÷®e‡Æuc—ñpi§žò¨+uû>ÀPê§æ+0CGC¾i²§9 9:éÌ{ï½WIîj&hwÝuW™yàCS¨# ŸIk«M®ø œÍÑW -Ò ¨¼®x5Å PÙ´>㲋zI¦•ƒ"`KÃA–6pWöĘgèìHÄ®ã& þÒŠFÓn>·ר–«ÄDÈ2y*QFˆÖiSpÛ¼^ÅÀO€h»r©ÈqáÃlñ/X0¿,ªÊm(rª#NüA7 Àð«!Q‹œÕ€×iTX¦,ý˪à€¹sçú‚˜)?>t©@˜Ê‚½~;Q@ü d–@ùå¿4Õ³äÒ¿Zä±á0}7`0P~ÖJ-§Íjö¯hâOZŠ„šM>œœ–ŠÄÃäÉ“ãW“ÉÆ±¼™Düð¯ HF]  ³€ž4ÀÛÙwã*@Ô‡Ž¬ Aõq`lµÏ<0„¶Ö*!"qÈ!bçw.;Œ“`#¸ù曕óÐ…äé ÝI|ÂÙ·re«ºpÙ²¥^‚D1ËUdyà«VYNƦ>³ àIErlÄE¼Õ†ÛÖ·<ýá&¢‹Œt‹´<ðÀ7`K.ŽCüpUùòeR’7)¢Çý¨gùòÒuáú©@< €VYbpË úy‚iý¯å÷r2Öþm:ø-gžÄTV]ÕÜgB$ @µá&XŠ‚õeõÔrbêmƒIàɺœIç>ñÄj.ñ Ë¥ª¿(ºd8_¶ Œ¨ä@°FŸÂJ=Æuç*6òÈ9^c˜|H­4ÏJL:(ùÚ‘Ê_áIˆ.¥‡þº”é y !d%~à‡L œû7{öLéÜœ-Ï\¢˜žeË @OÉeÀ2‹RÄ/Ój+PËäCüf8ƒ¾ªcËà !‹óŠÿ+«›&¶þ[KdÕ6ï“©N¢vÉ“Þm·Ý6kÑU„¼bE«Tñ[Ã_ÜÜž˜ ”œÝ8ùô\ï´„àž¿Ú¿ï`Åû„úÄ_âv%'`~Öâ _îü•ÔlDobtÔ“t©$áS»8*{üøñ¹ñÙ*À¡|â¯^Ý&Í€%’xÛBB¦_õ§$?~Ë{‚‡­“øúŽ9„å« $f Lžø|TÝ‘ÿ+™3¢4/\˧Iz—zô`—2Eä™8qbÕ´Û:‚åºàÀ ~;/I½ cúFŸ€8Ü\^LgNÀR0‡ÚÓãê¸?E1™"}ÕúúÀûß™M›zNó#.ÛÜ)T/çêꦽ5¦#`^TgޏAgkuÌ;Ú7häÉa½€)%ÛpŒ»ÁÚê>d@ÝÆGØú–$qc*oDº&P ô 4‚8ƒ(mr#¨à`‘pSªê§z-!xˆd¹´2JÌX®4˜@fëç{cõæÔÄ[Ó>VÏŒÙ󫾤[ d–/‡•3¡²(­²¼_k¢ÈBòÁ—ÞŠÕ c xOjªˆ P9$ÐZ¤b)%ƒ+²}Ûq£rf•Oè­¶ÚÊ·¹v™ŸKhÓcYYÊ8ÀøS|çíäGÒŸÚäJGQ«ñ®>K3¨¢*~@*Sc)¬¤øú•c+$ðˆÈÇfß}÷) Y#n•Ž|ôÀ—5áô¡Î’Œ~¦û¥ÎÄf9vöÑsœhõ0€%gYê0º¨Fiýª”Ô/ƒ5e&ì×µ€÷¦½—ÖeƒöïÛ;52 o·nMJåGÈ477ÉcÄšc9o«u L—.kÄÈ‘#£‡‚vŠtÊ:uÎ!“m¼é}0Lº&–<ÓTYU_j©(mÀÞ=y9HôQ'ø  ˆ%5‡1‰e¸xWùêhŒÀ4ì6س¶„6&|ºé àÖÛnKE!Ê,X´$1_¿~½"â6ÆÇJ›˜1‰ "jž è&X£÷I%§Û¹9ÚÊneù}cyÍãY»ÞìK£pÒ—yÆCyhÑs L º¥[×Òs¥í¤+ùaƒºÊýÁémdÉÁÇ.Ms°JÝ;­â8?¼ÁÇqY¾“Ê»‘B ¤aÀÎT\ØMjc†ðejáñ”÷§Aa‚¤¡/WO«/é»m¦M‘Ä:a‹‡DÈ‡ËØ’ÖajŸ—O…ÒÑ0 ¨¿“&Mʃ6cY\#F‰|…7Vøä“OVªêÂêå»-‘–pYk%Rp¦AyrmWÃ!ý?´ƒ—ÆÖ¶IEND®B`‚direwolf-1.5+dfsg/dw-icon.rc000066400000000000000000000000331347750676600160200ustar00rootroot00000000000000MAINICON ICON "dw-icon.ico"direwolf-1.5+dfsg/dw-start.sh000077500000000000000000000125651347750676600162530ustar00rootroot00000000000000#!/bin/bash # Run this from crontab periodically to start up # Dire Wolf automatically. # See User Guide for more discussion. # For release 1.4 it is section 5.7 "Automatic Start Up After Reboot" # but it could change in the future as more information is added. # Versioning (this file, not direwolf version) #----------- # v1.3 - KI6ZHD - added variable support for direwolf binary location # v1.2 - KI6ZHD - support different versions of VNC # v1.1 - KI6ZHD - expanded version to support running on text-only displays with # auto support; log placement change # v1.0 - WB2OSZ - original version for Xwindow displays only #How are you running Direwolf : within a GUI (Xwindows / VNC) or CLI mode # # AUTO mode is design to try starting direwolf with GUI support and then # if no GUI environment is available, it reverts to CLI support with screen # # GUI mode is suited for users with the machine running LXDE/Gnome/KDE or VNC # which auto-logs on (sitting at a login prompt won't work) # # CLI mode is suited for say a Raspberry Pi running the Jessie LITE version # where it will run from the CLI w/o requiring Xwindows - uses screen RUNMODE=AUTO # Location of the direwolf binary. Depends on $PATH as shown. # change this if you want to use some other specific location. # e.g. DIREWOLF="/usr/local/bin/direwolf" DIREWOLF="direwolf" #Direwolf start up command :: two examples where example one is enabled # # 1. For normal operation as TNC, digipeater, IGate, etc. # Print audio statistics each 100 seconds for troubleshooting. # Change this command to however you wish to start Direwolf DWCMD="$DIREWOLF -a 100" #--------------------------------------------------------------- # # 2. Alternative for running with SDR receiver. # Piping one application into another makes it a little more complicated. # We need to use bash for the | to be recognized. #DWCMD="bash -c 'rtl_fm -f 144.39M - | direwolf -c sdr.conf -r 24000 -D 1 -'" #Where will logs go - needs to be writable by non-root users LOGFILE=/var/tmp/dw-start.log #------------------------------------- # Main functions of the script #------------------------------------- #Status variables SUCCESS=0 function CLI { SCREEN=`which screen` if [ $? -ne 0 ]; then echo -e "Error: screen is not installed but is required for CLI mode. Aborting" exit 1 fi echo "Direwolf in CLI mode start up" echo "Direwolf in CLI mode start up" >> $LOGFILE # Screen commands # -d m :: starts the command in detached mode # -S :: name the session $SCREEN -d -m -S direwolf $DWCMD >> $LOGFILE SUCCESS=1 $SCREEN -list direwolf $SCREEN -list direwolf >> $LOGFILE echo "-----------------------" echo "-----------------------" >> $LOGFILE } function GUI { # In this case # In my case, the Raspberry Pi is not connected to a monitor. # I access it remotely using VNC as described here: # http://learn.adafruit.com/adafruit-raspberry-pi-lesson-7-remote-control-with-vnc # # If VNC server is running, use its display number. # Otherwise default to :0 (the Xwindows on the HDMI display) # export DISPLAY=":0" #Reviewing for RealVNC sessions (stock in Raspbian Pixel) if [ -n "`ps -ef | grep vncserver-x11-serviced | grep -v grep`" ]; then sleep 0.1 echo -e "\nRealVNC found - defaults to connecting to the :0 root window" elif [ -n "`ps -ef | grep Xtightvnc | grep -v grep`" ]; then #Reviewing for TightVNC sessions echo -e "\nTightVNC found - defaults to connecting to the :1 root window" v=`ps -ef | grep Xtightvnc | grep -v grep` d=`echo "$v" | sed 's/.*tightvnc *\(:[0-9]\).*/\1/'` export DISPLAY="$d" fi echo "Direwolf in GUI mode start up" echo "Direwolf in GUI mode start up" >> $LOGFILE echo "DISPLAY=$DISPLAY" echo "DISPLAY=$DISPLAY" >> $LOGFILE # # Auto adjust the startup for your particular environment: gnome-terminal, xterm, etc. # if [ -x /usr/bin/lxterminal ]; then /usr/bin/lxterminal -t "Dire Wolf" -e "$DWCMD" & SUCCESS=1 elif [ -x /usr/bin/xterm ]; then /usr/bin/xterm -bg white -fg black -e "$DWCMD" & SUCCESS=1 elif [ -x /usr/bin/x-terminal-emulator ]; then /usr/bin/x-terminal-emulator -e "$DWCMD" & SUCCESS=1 else echo "Did not find an X terminal emulator. Reverting to CLI mode" SUCCESS=0 fi echo "-----------------------" echo "-----------------------" >> $LOGFILE } # ----------------------------------------------------------- # Main Script start # ----------------------------------------------------------- # When running from cron, we have a very minimal environment # including PATH=/usr/bin:/bin. # export PATH=/usr/local/bin:$PATH #Log the start of the script run and re-run date >> $LOGFILE # First wait a little while in case we just rebooted # and the desktop hasn't started up yet. # sleep 30 # # Nothing to do if Direwolf is already running. # a=`ps ax | grep direwolf | grep -vi -e bash -e screen -e grep | awk '{print $1}'` if [ -n "$a" ] then #date >> /tmp/dw-start.log #echo "Direwolf already running." >> $LOGFILE exit fi # Main execution of the script if [ $RUNMODE == "AUTO" ];then GUI if [ $SUCCESS -eq 0 ]; then CLI fi elif [ $RUNMODE == "GUI" ];then GUI elif [ $RUNMODE == "CLI" ];then CLI else echo -e "ERROR: illegal run mode given. Giving up" exit 1 fi direwolf-1.5+dfsg/dwespeak.bat000066400000000000000000000001621347750676600164300ustar00rootroot00000000000000echo off set chan=%1 set msg=%2 sleep 1 "C:\Program Files (x86)\eSpeak\command_line\espeak.exe" -v en-sc %msg%direwolf-1.5+dfsg/dwespeak.sh000066400000000000000000000000721347750676600162740ustar00rootroot00000000000000#!/bin/bash chan=$1 msg=$2 sleep 1 espeak -v en-sc "$msg" direwolf-1.5+dfsg/dwgps.c000066400000000000000000000150741347750676600154350ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: dwgps.c * * Purpose: Interface for obtaining location from GPS. * * Description: This is a wrapper for two different implementations: * * (1) Read NMEA sentences from a serial port (or USB * that looks line one). Available for all platforms. * * (2) Read from gpsd. Not available for Windows. * Including this is optional because it depends * on another external software component. * * * API: dwgps_init Connect to data stream at start up time. * * dwgps_read Return most recent location to application. * * dwgps_print Print contents of structure for debugging. * * dwgps_term Shutdown on exit. * * * from below: dwgps_set_data Called from other two implementations to * save data until it is needed. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include "textcolor.h" #include "dwgps.h" #include "dwgpsnmea.h" #include "dwgpsd.h" static int s_dwgps_debug = 0; /* Enable debug output. */ /* >= 2 show updates from GPS. */ /* >= 1 show results from dwgps_read. */ /* * The GPS reader threads deposit current data here when it becomes available. * dwgps_read returns it to the requesting application. * * A critical region to avoid inconsistency between fields. */ static dwgps_info_t s_dwgps_info = { .timestamp = 0, .fix = DWFIX_NOT_INIT, /* to detect read without init. */ .dlat = G_UNKNOWN, .dlon = G_UNKNOWN, .speed_knots = G_UNKNOWN, .track = G_UNKNOWN, .altitude = G_UNKNOWN }; static dw_mutex_t s_gps_mutex; /*------------------------------------------------------------------- * * Name: dwgps_init * * Purpose: Intialize the GPS interface. * * Inputs: pconfig Configuration settings. This might include * serial port name for direct connect and host * name or address for network connection. * * debug - If >= 1, print results when dwgps_read is called. * (In this file.) * * If >= 2, location updates are also printed. * (In other two related files.) * * Returns: none * * Description: Call corresponding functions for implementations. * Normally we would expect someone to use either GPSNMEA or * GPSD but there is nothing to prevent use of both at the * same time. * *--------------------------------------------------------------------*/ void dwgps_init (struct misc_config_s *pconfig, int debug) { s_dwgps_debug = debug; dw_mutex_init (&s_gps_mutex); dwgpsnmea_init (pconfig, debug); #if ENABLE_GPSD dwgpsd_init (pconfig, debug); #endif SLEEP_MS(500); /* So receive thread(s) can clear the */ /* not init status before it gets checked. */ } /* end dwgps_init */ /*------------------------------------------------------------------- * * Name: dwgps_clear * * Purpose: Clear the gps info structure. * *--------------------------------------------------------------------*/ void dwgps_clear (dwgps_info_t *gpsinfo) { gpsinfo->timestamp = 0; gpsinfo->fix = DWFIX_NOT_SEEN; gpsinfo->dlat = G_UNKNOWN; gpsinfo->dlon = G_UNKNOWN; gpsinfo->speed_knots = G_UNKNOWN; gpsinfo->track = G_UNKNOWN; gpsinfo->altitude = G_UNKNOWN; } /*------------------------------------------------------------------- * * Name: dwgps_read * * Purpose: Return most recent location data available. * * Outputs: gpsinfo - Structure with latitude, longitude, etc. * * Returns: Position fix quality. Same as in structure. * * *--------------------------------------------------------------------*/ dwfix_t dwgps_read (dwgps_info_t *gpsinfo) { dw_mutex_lock (&s_gps_mutex); memcpy (gpsinfo, &s_dwgps_info, sizeof(*gpsinfo)); dw_mutex_unlock (&s_gps_mutex); if (s_dwgps_debug >= 1) { text_color_set (DW_COLOR_DEBUG); dwgps_print ("gps_read: ", gpsinfo); } // TODO: Should we check timestamp and complain if very stale? // or should we leave that up to the caller? return (s_dwgps_info.fix); } /*------------------------------------------------------------------- * * Name: dwgps_print * * Purpose: Print gps information for debugging. * * Inputs: msg - Message for prefix on line. * gpsinfo - Structure with latitude, longitude, etc. * * Description: Caller is responsible for setting text color. * *--------------------------------------------------------------------*/ void dwgps_print (char *msg, dwgps_info_t *gpsinfo) { dw_printf ("%stime=%d fix=%d lat=%.6f lon=%.6f trk=%.0f spd=%.1f alt=%.0f\n", msg, (int)gpsinfo->timestamp, (int)gpsinfo->fix, gpsinfo->dlat, gpsinfo->dlon, gpsinfo->track, gpsinfo->speed_knots, gpsinfo->altitude); } /* end dwgps_set_data */ /*------------------------------------------------------------------- * * Name: dwgps_term * * Purpose: Shut down GPS interface before exiting from application. * * Inputs: none. * * Returns: none. * *--------------------------------------------------------------------*/ void dwgps_term (void) { dwgpsnmea_term (); #if ENABLE_GPSD dwgpsd_term (); #endif } /* end dwgps_term */ /*------------------------------------------------------------------- * * Name: dwgps_set_data * * Purpose: Called by the GPS interfaces when new data is available. * * Inputs: gpsinfo - Structure with latitude, longitude, etc. * *--------------------------------------------------------------------*/ void dwgps_set_data (dwgps_info_t *gpsinfo) { /* Debug print is handled by the two callers so */ /* we can distinguish the source. */ dw_mutex_lock (&s_gps_mutex); memcpy (&s_dwgps_info, gpsinfo, sizeof(s_dwgps_info)); dw_mutex_unlock (&s_gps_mutex); } /* end dwgps_set_data */ /* end dwgps.c */ direwolf-1.5+dfsg/dwgps.h000066400000000000000000000024661347750676600154430ustar00rootroot00000000000000 /* dwgps.h */ #ifndef DWGPS_H #define DWGPS_H 1 #include #include "config.h" /* for struct misc_config_s */ /* * Values for fix, equivalent to values from libgps. * -2 = not initialized. * -1 = error communicating with GPS receiver. * 0 = nothing heard yet. * 1 = had signal but lost it. * 2 = 2D. * 3 = 3D. * * Undefined float & double values are set to G_UNKNOWN. * */ enum dwfix_e { DWFIX_NOT_INIT= -2, DWFIX_ERROR= -1, DWFIX_NOT_SEEN=0, DWFIX_NO_FIX=1, DWFIX_2D=2, DWFIX_3D=3 }; typedef enum dwfix_e dwfix_t; typedef struct dwgps_info_s { time_t timestamp; /* When last updated. System time. */ dwfix_t fix; /* Quality of position fix. */ double dlat; /* Latitude. Valid if fix >= 2. */ double dlon; /* Longitude. Valid if fix >= 2. */ float speed_knots; /* libgps uses meters/sec but we use GPS usual knots. */ float track; /* What is difference between track and course? */ float altitude; /* meters above mean sea level. Valid if fix == 3. */ } dwgps_info_t; void dwgps_init (struct misc_config_s *pconfig, int debug); void dwgps_clear (dwgps_info_t *gpsinfo); dwfix_t dwgps_read (dwgps_info_t *gpsinfo); void dwgps_print (char *msg, dwgps_info_t *gpsinfo); void dwgps_term (void); void dwgps_set_data (dwgps_info_t *gpsinfo); #endif /* DWGPS_H 1 */ /* end dwgps.h */ direwolf-1.5+dfsg/dwgpsd.c000066400000000000000000000252411347750676600155760ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: dwgps.c * * Purpose: Interface to location data, i.e. GPS receiver. * * Description: For Linux, we would normally want to use gpsd and libgps. * This allows multiple applications to access the GPS data, * without fighting over the same serial port, and has the * extra benefit that the system clock can be set from the GPS signal. * * Reference: http://www.catb.org/gpsd/ * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #if __WIN32__ #error Not for Windows #endif #if ENABLE_GPSD #include // Debian bug report: direwolf (1.2-1) FTBFS with libgps22 as part of the gpsd transition (#803605): // dwgps.c claims to only support GPSD_API_MAJOR_VERSION 5, but also builds successfully with // GPSD_API_MAJOR_VERSION 6 provided by libgps22 when the attached patch is applied. #if GPSD_API_MAJOR_VERSION < 5 || GPSD_API_MAJOR_VERSION > 6 #error libgps API version might be incompatible. #endif /* * Information for interface to gpsd daemon. */ static struct gps_data_t gpsdata; #endif /* ENABLE_GPSD */ #include "textcolor.h" #include "dwgps.h" #include "dwgpsd.h" #if ENABLE_GPSD static int s_debug = 0; /* Enable debug output. */ /* >= 1 show results from dwgps_read. */ /* >= 2 show updates from GPS. */ static void * read_gpsd_thread (void *arg); #endif /*------------------------------------------------------------------- * * Name: dwgpsd_init * * Purpose: Intialize the GPS interface. * * Inputs: pconfig Configuration settings. This includes * host name or address for network connection. * * debug - If >= 1, print results when dwgps_read is called. * (In different file.) * * If >= 2, location updates are also printed. * (In this file.) * * Returns: 1 = success * 0 = nothing to do (no host specified in config) * -1 = failure * * Description: - Establish socket connection with gpsd. * - Start up thread to process incoming data. * It reads from the daemon and deposits into * shared region via dwgps_put_data. * * The application calls dwgps_read to get the most 8 recent information. * *--------------------------------------------------------------------*/ /* * Historical notes: * * Originally, I wanted to use the shared memory interface to gpsd * because it is simpler and more efficient. Just access it when we * actually need the data and we don't have a lot of extra unnecessary * busy work going on. * * The current version of gpsd, supplied with Raspian (Wheezy), is 3.6 from back in * May 2012, is missing support for the shared memory interface. * * I tried to download a newer source and build with shared memory support * but ran into a couple other issues. * * sudo apt-get install libncurses5-dev * sudo apt-get install scons * cd ~ * wget http://download-mirror.savannah.gnu.org/releases/gpsd/gpsd-3.11.tar.gz * tar xfz gpsd-3.11.tar.gz * cd gpsd-3.11 * scons prefix=/usr libdir=lib/arm-linux-gnueabihf shm_export=True python=False * sudo scons udev-install * * For now, we will use the socket interface. Maybe get back to this again someday. * * Update: January 2016. * * I'm told that the shared memory interface might work in Raspian, Jessie version. * Haven't tried it yet. */ int dwgpsd_init (struct misc_config_s *pconfig, int debug) { #if ENABLE_GPSD pthread_t read_gps_tid; int e; int err; int arg = 0; char sport[12]; s_debug = debug; if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dwgpsd_init()\n"); } /* * Socket interface to gpsd. */ if (strlen(pconfig->gpsd_host) == 0) { /* Nothing to do. Leave initial fix value of errror. */ return (0); } snprintf (sport, sizeof(sport), "%d", pconfig->gpsd_port); err = gps_open (pconfig->gpsd_host, sport, &gpsdata); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Unable to connect to GPSD stream at %s:%s.\n", pconfig->gpsd_host, sport); dw_printf ("%s\n", gps_errstr(errno)); return (-1); } gps_stream(&gpsdata, WATCH_ENABLE | WATCH_JSON, NULL); e = pthread_create (&read_gps_tid, NULL, read_gpsd_thread, (void *)(long)arg); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create GPS reader thread"); return (-1); } /* success */ return (1); #else /* end ENABLE_GPSD */ // Shouldn't be here. text_color_set(DW_COLOR_ERROR); dw_printf ("GPSD interface not enabled in this version.\n"); dw_printf ("See documentation on how to rebuild with ENABLE_GPSD.\n"); return (-1); #endif } /* end dwgps_init */ /*------------------------------------------------------------------- * * Name: read_gpsd_thread * * Purpose: Read information from GPSD, as it becomes available, and * store in memory shared with dwgps_read. * * Inputs: arg - not used * *--------------------------------------------------------------------*/ #define TIMEOUT 30 #if ENABLE_GPSD static void * read_gpsd_thread (void *arg) { dwgps_info_t info; if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("starting read_gpsd_thread (%d)\n", (int)(long)arg); } dwgps_clear (&info); info.fix = DWFIX_NOT_SEEN; /* clear not init state. */ if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dwgps_print ("GPSD: ", &info); } dwgps_set_data (&info); while (1) { if ( ! gps_waiting(&gpsdata, TIMEOUT * 1000000)) { text_color_set(DW_COLOR_ERROR); dw_printf ("GPSD: Timeout waiting for GPS data.\n"); /* Fall thru to read which should get error and bail out. */ } if (gps_read (&gpsdata) == -1) { text_color_set(DW_COLOR_ERROR); dw_printf ("------------------------------------------\n"); dw_printf ("GPSD: Lost communication with gpsd server.\n"); dw_printf ("------------------------------------------\n"); info.fix = DWFIX_ERROR; if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dwgps_print ("GPSD: ", &info); } dwgps_set_data (&info); break; // Jump out of loop and terminate thread. } switch (gpsdata.fix.mode) { default: case MODE_NOT_SEEN: if (info.fix >= DWFIX_2D) { text_color_set(DW_COLOR_INFO); dw_printf ("GPSD: Lost location fix.\n"); } info.fix = DWFIX_NOT_SEEN; break; case MODE_NO_FIX: if (info.fix >= DWFIX_2D) { text_color_set(DW_COLOR_INFO); dw_printf ("GPSD: Lost location fix.\n"); } info.fix = DWFIX_NO_FIX; break; case MODE_2D: if (info.fix != DWFIX_2D) { text_color_set(DW_COLOR_INFO); dw_printf ("GPSD: Location fix is now 2D.\n"); } info.fix = DWFIX_2D; break; case MODE_3D: if (info.fix != DWFIX_3D) { text_color_set(DW_COLOR_INFO); dw_printf ("GPSD: Location fix is now 3D.\n"); } info.fix = DWFIX_3D; break; } /* Data is available. */ // TODO: what is gpsdata.status? if (gpsdata.status >= STATUS_FIX && gpsdata.fix.mode >= MODE_2D) { info.dlat = isnan(gpsdata.fix.latitude) ? G_UNKNOWN : gpsdata.fix.latitude; info.dlon = isnan(gpsdata.fix.longitude) ? G_UNKNOWN : gpsdata.fix.longitude; info.track = isnan(gpsdata.fix.track) ? G_UNKNOWN : gpsdata.fix.track; info.speed_knots = isnan(gpsdata.fix.speed) ? G_UNKNOWN : (MPS_TO_KNOTS * gpsdata.fix.speed); if (gpsdata.fix.mode >= MODE_3D) { info.altitude = isnan(gpsdata.fix.altitude) ? G_UNKNOWN : gpsdata.fix.altitude; } } info.timestamp = time(NULL); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dwgps_print ("GPSD: ", &info); } dwgps_set_data (&info); } return(0); // Terminate thread on serious error. } /* end read_gpsd_thread */ #endif /*------------------------------------------------------------------- * * Name: dwgpsd_term * * Purpose: Shut down GPS interface before exiting from application. * * Inputs: none. * * Returns: none. * *--------------------------------------------------------------------*/ void dwgpsd_term (void) { #if ENABLE_GPSD gps_close (&gpsdata); #endif } /* end dwgpsd_term */ /*------------------------------------------------------------------- * * Name: main * * Purpose: Simple unit test for other functions in this file. * * Description: Compile with -DGPSTEST option. * * gcc -DGPSTEST -DENABLE_GPSD dwgpsd.c dwgps.c textcolor.o latlong.o misc.a -lm -lpthread -lgps * ./a.out * *--------------------------------------------------------------------*/ #if GPSTEST int dwgpsnmea_init (struct misc_config_s *pconfig, int debug) { return (0); } void dwgpsnmea_term (void) { return; } int main (int argc, char *argv[]) { struct misc_config_s config; dwgps_info_t info; memset (&config, 0, sizeof(config)); strlcpy (config.gpsd_host, "localhost", sizeof(config.gpsd_host)); config.gpsd_port = atoi(DEFAULT_GPSD_PORT); dwgps_init (&config, 3); while (1) { dwfix_t fix; fix = dwgps_read (&info) ; text_color_set (DW_COLOR_INFO); switch (fix) { case DWFIX_2D: case DWFIX_3D: dw_printf ("%.6f %.6f", info.dlat, info.dlon); dw_printf (" %.1f knots %.0f degrees", info.speed_knots, info.track); if (fix==3) dw_printf (" altitude = %.1f meters", info.altitude); dw_printf ("\n"); break; case DWFIX_NOT_SEEN: case DWFIX_NO_FIX: dw_printf ("Location currently not available.\n"); break; case DWFIX_NOT_INIT: dw_printf ("GPS Init failed.\n"); exit (1); case DWFIX_ERROR: default: dw_printf ("ERROR getting GPS information.\n"); break; } SLEEP_SEC (3); } } /* end main */ #endif /* end dwgpsd.c */ direwolf-1.5+dfsg/dwgpsd.h000066400000000000000000000003501347750676600155750ustar00rootroot00000000000000 /* dwgpsd.h - For communicating with daemon */ #ifndef DWGPSD_H #define DWGPSD_H 1 #include "config.h" int dwgpsd_init (struct misc_config_s *pconfig, int debug); void dwgpsd_term (void); #endif /* end dwgpsd.h */ direwolf-1.5+dfsg/dwgpsnmea.c000066400000000000000000000505521347750676600162760ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: dwgpsnmea.c * * Purpose: process NMEA sentences from a GPS receiver. * * Description: This version is available for all operating systems. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include "textcolor.h" #include "dwgps.h" #include "dwgpsnmea.h" #include "serial_port.h" static int s_debug = 0; /* Enable debug output. */ /* See dwgpsnmea_init description for values. */ static struct misc_config_s *s_save_configp; #if __WIN32__ static unsigned __stdcall read_gpsnmea_thread (void *arg); #else static void * read_gpsnmea_thread (void *arg); #endif /*------------------------------------------------------------------- * * Name: dwgpsnmea_init * * Purpose: Open serial port for the GPS receiver. * * Inputs: pconfig Configuration settings. This includes * serial port name for direct connect. * * debug - If >= 1, print results when dwgps_read is called. * (In different file.) * * If >= 2, location updates are also printed. * (In this file.) * Why not do it in dwgps_set_data() ? * Here, we can prefix it with GPSNMEA to * distinguish it from GPSD. * * If >= 3, Also the NMEA sentences. * (In this file.) * * Returns: 1 = success * 0 = nothing to do (no serial port specified in config) * -1 = failure * * Description: When talking directly to GPS receiver (any operating system): * * - Open the appropriate serial port. * - Start up thread to process incoming data. * It reads from the serial port and deposits into * dwgps_info, above. * * The application calls dwgps_read to get the most recent information. * *--------------------------------------------------------------------*/ /* Make this static and available to all functions so term function can access it. */ static MYFDTYPE s_gpsnmea_port_fd = MYFDERROR; /* Handle for serial port. */ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug) { //dwgps_info_t info; #if __WIN32__ HANDLE read_gps_th; #else pthread_t read_gps_tid; //int e; #endif s_debug = debug; s_save_configp = pconfig; if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("dwgpsnmea_init()\n"); } if (strlen(pconfig->gpsnmea_port) == 0) { /* Nothing to do. Leave initial fix value for not init. */ return (0); } /* * Open serial port connection. * 4800 baud is standard for GPS. * Should add an option to allow changing someday. */ s_gpsnmea_port_fd = serial_port_open (pconfig->gpsnmea_port, 4800); if (s_gpsnmea_port_fd != MYFDERROR) { #if __WIN32__ read_gps_th = (HANDLE)_beginthreadex (NULL, 0, read_gpsnmea_thread, (void*)(long)s_gpsnmea_port_fd, 0, NULL); if (read_gps_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create GPS NMEA listening thread.\n"); return (-1); } #else int e; e = pthread_create (&read_gps_tid, NULL, read_gpsnmea_thread, (void*)(long)s_gpsnmea_port_fd); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create GPS NMEA listening thread."); return (-1); } #endif } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not open serial port %s for GPS receiver.\n", pconfig->gpsnmea_port); return (-1); } /* success */ return (1); } /* end dwgpsnmea_init */ /* Return fd to share if waypoint wants same device. */ /* Currently both are fixed speed at 4800. */ /* If that ever becomes configurable, that needs to be compared too. */ MYFDTYPE dwgpsnmea_get_fd(char *wp_port_name, int speed) { if (strcmp(s_save_configp->gpsnmea_port, wp_port_name) == 0 && speed == 4800) { return (s_gpsnmea_port_fd); } return (MYFDERROR); } /*------------------------------------------------------------------- * * Name: read_gpsnmea_thread * * Purpose: Read information from GPS, as it becomes available, and * store it for later retrieval by dwgps_read. * * Inputs: fd - File descriptor for serial port. * * Description: This version reads from serial port and parses the * NMEA sentences. * *--------------------------------------------------------------------*/ #define TIMEOUT 5 #if __WIN32__ static unsigned __stdcall read_gpsnmea_thread (void *arg) #else static void * read_gpsnmea_thread (void *arg) #endif { MYFDTYPE fd = (MYFDTYPE)(long)arg; // Maximum length of message from GPS receiver is 82 according to some people. // Make buffer considerably larger to be safe. #define NMEA_MAX_LEN 160 char gps_msg[NMEA_MAX_LEN]; int gps_msg_len = 0; dwgps_info_t info; if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("read_gpsnmea_thread (%d)\n", (int)(long)arg); } dwgps_clear (&info); info.fix = DWFIX_NOT_SEEN; /* clear not init state. */ if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dwgps_print ("GPSNMEA: ", &info); } dwgps_set_data (&info); while (1) { int ch; ch = serial_port_get1(fd); if (ch < 0) { /* This might happen if a USB device is unplugged. */ /* I can't imagine anything that would cause it with */ /* a normal serial port. */ text_color_set(DW_COLOR_ERROR); dw_printf ("----------------------------------------------\n"); dw_printf ("GPSNMEA: Lost communication with GPS receiver.\n"); dw_printf ("----------------------------------------------\n"); info.fix = DWFIX_ERROR; if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dwgps_print ("GPSNMEA: ", &info); } dwgps_set_data (&info); // TODO: doesn't exist yet - serial_port_close(fd); s_gpsnmea_port_fd = MYFDERROR; break; /* terminate thread. */ } if (ch == '$') { // Start of new sentence. gps_msg_len = 0; gps_msg[gps_msg_len++] = ch; gps_msg[gps_msg_len] = '\0'; } else if (ch == '\r' || ch == '\n') { if (gps_msg_len >= 6 && gps_msg[0] == '$') { dwfix_t f; if (s_debug >= 3) { text_color_set(DW_COLOR_DEBUG); dw_printf ("%s\n", gps_msg); } /* Process sentence. */ if (strncmp(gps_msg, "$GPRMC", 6) == 0) { f = dwgpsnmea_gprmc (gps_msg, 0, &info.dlat, &info.dlon, &info.speed_knots, &info.track); if (f == DWFIX_ERROR) { /* Parse error. Shouldn't happen. Better luck next time. */ text_color_set(DW_COLOR_INFO); dw_printf ("GPSNMEA: Error parsing $GPRMC sentence.\n"); dw_printf ("%s\n", gps_msg); } else if (f == DWFIX_2D) { if (info.fix != DWFIX_2D && info.fix != DWFIX_3D) { text_color_set(DW_COLOR_INFO); dw_printf ("GPSNMEA: Location fix is now available.\n"); info.fix = DWFIX_2D; // Don't know if 2D or 3D. Take minimum. } info.timestamp = time(NULL); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dwgps_print ("GPSNMEA: ", &info); } dwgps_set_data (&info); } else { if (info.fix == DWFIX_2D || info.fix == DWFIX_3D) { text_color_set(DW_COLOR_INFO); dw_printf ("GPSNMEA: Lost location fix.\n"); } info.fix = f; /* lost it. */ info.timestamp = time(NULL); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dwgps_print ("GPSNMEA: ", &info); } dwgps_set_data (&info); } } else if (strncmp(gps_msg, "$GPGGA", 6) == 0) { int nsat; f = dwgpsnmea_gpgga (gps_msg, 0, &info.dlat, &info.dlon, &info.altitude, &nsat); /* Only switch between 2D & 3D. */ /* Let GPRMC handle other changes in fix state and data transfer. */ if (f == DWFIX_ERROR) { /* Parse error. Shouldn't happen. Better luck next time. */ text_color_set(DW_COLOR_INFO); dw_printf ("GPSNMEA: Error parsing $GPGGA sentence.\n"); dw_printf ("%s\n", gps_msg); } else if ((f == DWFIX_3D && info.fix == DWFIX_2D) || (f == DWFIX_2D && info.fix == DWFIX_3D)) { text_color_set(DW_COLOR_INFO); dw_printf ("GPSNMEA: Location fix is now %dD.\n", (int)f); info.fix = f; } } } gps_msg_len = 0; gps_msg[gps_msg_len] = '\0'; } else { if (gps_msg_len < NMEA_MAX_LEN-1) { gps_msg[gps_msg_len++] = ch; gps_msg[gps_msg_len] = '\0'; } } } /* while (1) */ #if __WIN32__ return (0); #else return (NULL); #endif } /* end read_gpsnmea_thread */ /*------------------------------------------------------------------- * * Name: remove_checksum * * Purpose: Validate checksum and remove before further processing. * * Inputs: sentence * quiet suppress printing of error messages. * * Outputs: sentence modified in place. * * Returns: 0 = good checksum. * -1 = error. missing or wrong. * *--------------------------------------------------------------------*/ static int remove_checksum (char *sent, int quiet) { char *p; unsigned char cs; // Do we have valid checksum? cs = 0; for (p = sent+1; *p != '*' && *p != '\0'; p++) { cs ^= *p; } p = strchr (sent, '*'); if (p == NULL) { if ( ! quiet) { text_color_set (DW_COLOR_INFO); dw_printf("Missing GPS checksum.\n"); } return (-1); } if (cs != strtoul(p+1, NULL, 16)) { if ( ! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf("GPS checksum error. Expected %02x but found %s.\n", cs, p+1); } return (-1); } *p = '\0'; // Remove the checksum. return (0); } /*------------------------------------------------------------------- * * Name: dwgpsnmea_gprmc * * Purpose: Parse $GPRMC sentence and extract interesing parts. * * Inputs: sentence NMEA sentence. * * quiet suppress printing of error messages. * * Outputs: odlat latitude * odlon longitude * oknots speed * ocourse direction of travel. * * Left undefined if not valid. * * Returns: DWFIX_ERROR Parse error. * DWFIX_NO_FIX GPS is there but Position unknown. Could be temporary. * DWFIX_2D Valid position. We don't know if it is really 2D or 3D. * * Examples: $GPRMC,001431.00,V,,,,,,,121015,,,N*7C * $GPRMC,212404.000,V,4237.1505,N,07120.8602,W,,,150614,,*0B * $GPRMC,000029.020,V,,,,,,,080810,,,N*45 * $GPRMC,003413.710,A,4237.1240,N,07120.8333,W,5.07,291.42,160614,,,A*7F * *--------------------------------------------------------------------*/ dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon, float *oknots, float *ocourse) { char stemp[NMEA_MAX_LEN]; /* Make copy because parsing is destructive. */ char *next; char *ptype; /* Should be $GPRMC */ char *ptime; /* Time, hhmmss[.sss] */ char *pstatus; /* Status, A=Active (valid position), V=Void */ char *plat; /* Latitude */ char *pns; /* North/South */ char *plon; /* Longitude */ char *pew; /* East/West */ char *pknots; /* Speed over ground, knots. */ char *pcourse; /* True course, degrees. */ char *pdate; /* Date, ddmmyy */ /* Magnetic variation */ /* In version 3.00, mode is added: A D E N (see below) */ /* Checksum */ strlcpy (stemp, sentence, sizeof(stemp)); if (remove_checksum (stemp, quiet) < 0) { return (DWFIX_ERROR); } next = stemp; ptype = strsep(&next, ","); ptime = strsep(&next, ","); pstatus = strsep(&next, ","); plat = strsep(&next, ","); pns = strsep(&next, ","); plon = strsep(&next, ","); pew = strsep(&next, ","); pknots = strsep(&next, ","); pcourse = strsep(&next, ","); pdate = strsep(&next, ","); /* Suppress the 'set but not used' warnings. */ /* Alternatively, we might use __attribute__((unused)) */ (void)(ptype); (void)(ptime); (void)(pdate); if (pstatus != NULL && strlen(pstatus) == 1) { if (*pstatus != 'A') { return (DWFIX_NO_FIX); /* Not "Active." Don't parse. */ } } else { if ( ! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf("No status in GPRMC sentence.\n"); } return (DWFIX_ERROR); } if (plat != NULL && strlen(plat) > 0 && pns != NULL && strlen(pns) > 0) { *odlat = latitude_from_nmea(plat, pns); } else { if ( ! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf("Can't get latitude from GPRMC sentence.\n"); } return (DWFIX_ERROR); } if (plon != NULL && strlen(plon) > 0 && pew != NULL && strlen(pew) > 0) { *odlon = longitude_from_nmea(plon, pew); } else { if ( ! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf("Can't get longitude from GPRMC sentence.\n"); } return (DWFIX_ERROR); } if (pknots != NULL && strlen(pknots) > 0) { *oknots = atof(pknots); } else { if ( ! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf("Can't get speed from GPRMC sentence.\n"); } return (DWFIX_ERROR); } if (pcourse != NULL) { if (strlen(pcourse) > 0) { *ocourse = atof(pcourse); } else { /* When stationary, this field might be empty. */ *ocourse = G_UNKNOWN; } } else { if ( ! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf("Can't get course from GPRMC sentence.\n"); } return (DWFIX_ERROR); } //text_color_set (DW_COLOR_INFO); //dw_printf("%.6f %.6f %.1f %.0f\n", *odlat, *odlon, *oknots, *ocourse); return (DWFIX_2D); } /* end dwgpsnmea_gprmc */ /*------------------------------------------------------------------- * * Name: dwgpsnmea_gpgga * * Purpose: Parse $GPGGA sentence and extract interesing parts. * * Inputs: sentence NMEA sentence. * * quiet suppress printing of error messages. * * Outputs: odlat latitude * odlon longitude * oalt altitude in meters * onsat number of satellites. * * Left undefined if not valid. * * Returns: DWFIX_ERROR Parse error. * DWFIX_NO_FIX GPS is there but Position unknown. Could be temporary. * DWFIX_2D Valid position. We don't know if it is really 2D or 3D. * Take more cautious value so we don't try using altitude. * * Examples: $GPGGA,001429.00,,,,,0,00,99.99,,,,,,*68 * $GPGGA,212407.000,4237.1505,N,07120.8602,W,0,00,,,M,,M,,*58 * $GPGGA,000409.392,,,,,0,00,,,M,0.0,M,,0000*53 * $GPGGA,003518.710,4237.1250,N,07120.8327,W,1,03,5.9,33.5,M,-33.5,M,,0000*5B * *--------------------------------------------------------------------*/ // TODO: in progress... dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon, float *oalt, int *onsat) { char stemp[NMEA_MAX_LEN]; /* Make copy because parsing is destructive. */ char *next; char *ptype; /* Should be $GPGGA */ char *ptime; /* Time, hhmmss[.sss] */ char *plat; /* Latitude */ char *pns; /* North/South */ char *plon; /* Longitude */ char *pew; /* East/West */ char *pfix; /* 0=invalid, 1=GPS fix, 2=DGPS fix */ char *pnum_sat; /* Number of satellites */ char *phdop; /* Horiz. Dilution fo Precision */ char *paltitude; /* Altitude, above mean sea level */ char *palt_u; /* Units for Altitude, typically M for meters. */ char *pheight; /* Height above ellipsoid */ char *pheight_u; /* Units for height, typically M for meters. */ char *psince; /* Time since last DGPS update. */ char *pdsta; /* DGPS reference station id. */ strlcpy (stemp, sentence, sizeof(stemp)); if (remove_checksum (stemp, quiet) < 0) { return (DWFIX_ERROR); } next = stemp; ptype = strsep(&next, ","); ptime = strsep(&next, ","); plat = strsep(&next, ","); pns = strsep(&next, ","); plon = strsep(&next, ","); pew = strsep(&next, ","); pfix = strsep(&next, ","); pnum_sat = strsep(&next, ","); phdop = strsep(&next, ","); paltitude = strsep(&next, ","); palt_u = strsep(&next, ","); pheight = strsep(&next, ","); pheight_u = strsep(&next, ","); psince = strsep(&next, ","); pdsta = strsep(&next, ","); /* Suppress the 'set but not used' warnings. */ /* Alternatively, we might use __attribute__((unused)) */ (void)(ptype); (void)(ptime); (void)(pnum_sat); (void)(phdop); (void)(palt_u); (void)(pheight); (void)(pheight_u); (void)(psince); (void)(pdsta); if (pfix != NULL && strlen(pfix) == 1) { if (*pfix == '0') { return (DWFIX_NO_FIX); /* No Fix. Don't parse the rest. */ } } else { if ( ! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf("No fix in GPGGA sentence.\n"); } return (DWFIX_ERROR); } if (plat != NULL && strlen(plat) > 0 && pns != NULL && strlen(pns) > 0) { *odlat = latitude_from_nmea(plat, pns); } else { if ( ! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf("Can't get latitude from GPGGA sentence.\n"); } return (DWFIX_ERROR); } if (plon != NULL && strlen(plon) > 0 && pew != NULL && strlen(pew) > 0) { *odlon = longitude_from_nmea(plon, pew); } else { if ( ! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf("Can't get longitude from GPGGA sentence.\n"); } return (DWFIX_ERROR); } // TODO: num sat... /* * We can distinguish between 2D & 3D fix by presence * of altitude or an empty field. */ if (paltitude != NULL) { if (strlen(paltitude) > 0) { *oalt = atof(paltitude); return (DWFIX_3D); } else { return (DWFIX_2D); } } else { if ( ! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf("Can't get altitude from GPGGA sentence.\n"); } return (DWFIX_ERROR); } } /* end dwgpsnmea_gpgga */ /*------------------------------------------------------------------- * * Name: dwgpsnmea_term * * Purpose: Shut down GPS interface before exiting from application. * * Inputs: none. * * Returns: none. * *--------------------------------------------------------------------*/ void dwgpsnmea_term (void) { // Should probably kill reader thread before closing device to avoid // message about read error. // serial_port_close (s_gpsnmea_port_fd); } /* end dwgps_term */ /*------------------------------------------------------------------- * * Name: main * * Purpose: Simple unit test for other functions in this file. * * Description: Compile with -DGPSTEST option. * * Windows: * gcc -DGPSTEST -Iregex dwgpsnmea.c dwgps.c textcolor.o serial_port.o latlong.o misc.a * a.exe * * Linux: * gcc -DGPSTEST dwgpsnmea.c dwgps.c textcolor.o serial_port.o latlong.o misc.a -lm -lpthread * ./a.out * *--------------------------------------------------------------------*/ #if GPSTEST int main (int argc, char *argv[]) { struct misc_config_s config; dwgps_info_t info; memset (&config, 0, sizeof(config)); strlcpy (config.gpsnmea_port, "COM22", sizeof(config.gpsnmea_port)); dwgps_init (&config, 3); while (1) { dwfix_t fix; fix = dwgps_read (&info) ; text_color_set (DW_COLOR_INFO); switch (fix) { case DWFIX_2D: case DWFIX_3D: dw_printf ("%.6f %.6f", info.dlat, info.dlon); dw_printf (" %.1f knots %.0f degrees", info.speed_knots, info.track); if (fix==3) dw_printf (" altitude = %.1f meters", info.altitude); dw_printf ("\n"); break; case DWFIX_NOT_SEEN: case DWFIX_NO_FIX: dw_printf ("Location currently not available.\n"); break; case DWFIX_NOT_INIT: dw_printf ("GPS Init failed.\n"); exit (1); case DWFIX_ERROR: default: dw_printf ("ERROR getting GPS information.\n"); break; } SLEEP_SEC (3); } } /* end main */ #endif /* end dwgpsnmea.c */ direwolf-1.5+dfsg/dwgpsnmea.h000066400000000000000000000011671347750676600163010ustar00rootroot00000000000000 /* dwgpsnmea.h - For reading NMEA sentences over serial port */ #ifndef DWGPSNMEA_H #define DWGPSNMEA_H 1 #include "dwgps.h" /* for dwfix_t */ #include "config.h" #include "serial_port.h" /* for MYFDTYPE */ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug); MYFDTYPE dwgpsnmea_get_fd(char *wp_port_name, int speed); void dwgpsnmea_term (void); dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon, float *oknots, float *ocourse); dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon, float *oalt, int *onsat); #endif /* end dwgpsnmea.h */ direwolf-1.5+dfsg/encode_aprs.c000066400000000000000000000614141347750676600165720ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2014 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: encode_aprs.c * * Purpose: Construct APRS packets from components. * * Description: * * References: APRS Protocol Reference. * * Frequency spec. * http://www.aprs.org/info/freqspec.txt * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #include "encode_aprs.h" #include "latlong.h" #include "textcolor.h" /*------------------------------------------------------------------ * * Name: set_norm_position * * Purpose: Fill in the human-readable latitude, longitude, * symbol part which is common to multiple data formats. * * Inputs: symtab - Symbol table id or overlay. * symbol - Symbol id. * dlat - Latitude. * dlong - Longitude. * ambiguity - Blank out least significant digits. * * Outputs: presult - Stored here. * * Returns: Number of characters in result. * *----------------------------------------------------------------*/ /* Position & symbol fields common to several message formats. */ typedef struct position_s { char lat[8]; char sym_table_id; /* / \ 0-9 A-Z */ char lon[9]; char symbol_code; } position_t; static int set_norm_position (char symtab, char symbol, double dlat, double dlong, int ambiguity, position_t *presult) { latitude_to_str (dlat, ambiguity, presult->lat); if (symtab != '/' && symtab != '\\' && ! isdigit(symtab) && ! isupper(symtab)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Symbol table identifier is not one of / \\ 0-9 A-Z\n"); } presult->sym_table_id = symtab; longitude_to_str (dlong, ambiguity, presult->lon); if (symbol < '!' || symbol > '~') { text_color_set(DW_COLOR_ERROR); dw_printf ("Symbol code is not in range of ! to ~\n"); } presult->symbol_code = symbol; return (sizeof(position_t)); } /*------------------------------------------------------------------ * * Name: set_comp_position * * Purpose: Fill in the compressed latitude, longitude, * symbol part which is common to multiple data formats. * * Inputs: symtab - Symbol table id or overlay. * symbol - Symbol id. * dlat - Latitude. * dlong - Longitude. * * power - Watts. * height - Feet. * gain - dBi. * * course - Degress, 0 - 360 (360 equiv. to 0). * Use G_UNKNOWN for none or unknown. * speed - knots. * * * Outputs: presult - Stored here. * * Returns: Number of characters in result. * * Description: The cst field can have only one of * * course/speed - takes priority (this implementation) * radio range - calculated from PHG * altitude - not implemented yet. * * Some conversion must be performed for course from * the API definition to what is sent over the air. * *----------------------------------------------------------------*/ /* Compressed position & symbol fields common to several message formats. */ typedef struct compressed_position_s { char sym_table_id; /* / \ a-j A-Z */ /* "The presence of the leading Symbol Table Identifier */ /* instead of a digit indicates that this is a compressed */ /* Position Report and not a normal lat/long report." */ char y[4]; /* Compressed Latitude. */ char x[4]; /* Compressed Longitude. */ char symbol_code; char c; /* Course/speed or radio range or altitude. */ char s; char t ; /* Compression type. */ } compressed_position_t; static int set_comp_position (char symtab, char symbol, double dlat, double dlong, int power, int height, int gain, int course, int speed, compressed_position_t *presult) { if (symtab != '/' && symtab != '\\' && ! isdigit(symtab) && ! isupper(symtab)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Symbol table identifier is not one of / \\ 0-9 A-Z\n"); } /* * In compressed format, the characters a-j are used for a numeric overlay. * This allows the receiver to distinguish between compressed and normal formats. */ if (isdigit(symtab)) { symtab = symtab - '0' + 'a'; } presult->sym_table_id = symtab; latitude_to_comp_str (dlat, presult->y); longitude_to_comp_str (dlong, presult->x); if (symbol < '!' || symbol > '~') { text_color_set(DW_COLOR_ERROR); dw_printf ("Symbol code is not in range of ! to ~\n"); } presult->symbol_code = symbol; /* * The cst field is complicated. * * When c is ' ', the cst field is not used. * * When the t byte has a certain pattern, c & s represent altitude. * * Otherwise, c & s can be either course/speed or radio range. * * When c is in range of '!' to 'z', * * ('!' - 33) * 4 = 0 degrees. * ... * ('z' - 33) * 4 = 356 degrees. * * In this case, s represents speed ... * * When c is '{', s is range ... */ if (speed > 0) { int c; int s; if (course != G_UNKNOWN) { c = (course + 2) / 4; if (c < 0) c += 90; if (c >= 90) c -= 90; } else { c = 0; } presult->c = c + '!'; s = (int)round(log(speed+1.0) / log(1.08)); presult->s = s + '!'; presult->t = 0x26 + '!'; /* current, other tracker. */ } else if (power || height || gain) { int s; float range; presult->c = '{'; /* radio range. */ if (power == 0) power = 10; if (height == 0) height = 20; if (gain == 0) gain = 3; // from protocol reference page 29. range = sqrt(2.0*height * sqrt((power/10.0) * (gain/2.0))); s = (int)round(log(range/2.) / log(1.08)); if (s < 0) s = 0; if (s > 93) s = 93; presult->s = s + '!'; presult->t = 0x26 + '!'; /* current, other tracker. */ } else { presult->c = ' '; /* cst field not used. */ presult->s = ' '; presult->t = '!'; /* avoid space. */ } return (sizeof(compressed_position_t)); } /*------------------------------------------------------------------ * * Name: phg_data_extension * * Purpose: Fill in parts of the power/height/gain data extension. * * Inputs: power - Watts. * height - Feet. * gain - dB. Protocol spec doesn't mention whether it is dBi or dBd. * This says dBi: * http://www.tapr.org/pipermail/aprssig/2008-September/027034.html * dir - Directivity: N, NE, etc., omni. * * Outputs: presult - Stored here. * * Returns: Number of characters in result. * *----------------------------------------------------------------*/ // TODO (bug): Doesn't check for G_UNKNOWN. // could have a case where some, but not all, values were specified. // Callers originally checked for any not zero. // now they check for any > 0. typedef struct phg_s { char P; char H; char G; char p; char h; char g; char d; } phg_t; static int phg_data_extension (int power, int height, int gain, char *dir, char *presult) { phg_t *r = (phg_t*)presult; int x; r->P = 'P'; r->H = 'H'; r->G = 'G'; x = (int)round(sqrt((float)power)) + '0'; if (x < '0') x = '0'; else if (x > '9') x = '9'; r->p = x; x = (int)round(log2(height/10.0)) + '0'; if (x < '0') x = '0'; /* Result can go beyond '9'. */ r->h = x; x = gain + '0'; if (x < '0') x = '0'; else if (x > '9') x = '0'; r->g = x; r->d = '0'; if (dir != NULL) { if (strcasecmp(dir,"NE") == 0) r->d = '1'; if (strcasecmp(dir,"E") == 0) r->d = '2'; if (strcasecmp(dir,"SE") == 0) r->d = '3'; if (strcasecmp(dir,"S") == 0) r->d = '4'; if (strcasecmp(dir,"SW") == 0) r->d = '5'; if (strcasecmp(dir,"W") == 0) r->d = '6'; if (strcasecmp(dir,"NW") == 0) r->d = '7'; if (strcasecmp(dir,"N") == 0) r->d = '8'; } return (sizeof(phg_t)); } /*------------------------------------------------------------------ * * Name: cse_spd_data_extension * * Purpose: Fill in parts of the course & speed data extension. * * Inputs: course - Degress, 0 - 360 (360 equiv. to 0). * Use G_UNKNOWN for none or unknown. * * speed - knots. * * Outputs: presult - Stored here. * * Returns: Number of characters in result. * * Description: Over the air we use: * 0 for unknown or not relevant. * 1 - 360 for valid course. (360 for north) * *----------------------------------------------------------------*/ typedef struct cs_s { char cse[3]; char slash; char spd[3]; } cs_t; static int cse_spd_data_extension (int course, int speed, char *presult) { cs_t *r = (cs_t*)presult; char stemp[8]; int x; if (course != G_UNKNOWN) { x = course; while (x < 1) x += 360; while (x > 360) x -= 360; // Should now be in range of 1 - 360. */ // Original value of 0 for north is transmitted as 360. */ } else { x = 0; } snprintf (stemp, sizeof(stemp), "%03d", x); memcpy (r->cse, stemp, 3); r->slash = '/'; x = speed; if (x < 0) x = 0; // would include G_UNKNOWN if (x > 999) x = 999; snprintf (stemp, sizeof(stemp), "%03d", x); memcpy (r->spd, stemp, 3); return (sizeof(cs_t)); } /*------------------------------------------------------------------ * * Name: frequency_spec * * Purpose: Put frequency specification in beginning of comment field. * * Inputs: freq - MHz. * tone - Hz. * offset - MHz. * * Outputs: presult - Stored here. * * Returns: Number of characters in result. * * Description: There are several valid variations. * * The frequency could be missing here if it is in the * object name. In this case we could have tone & offset. * * Offset must always be preceded by tone. * * Resulting formats are all fixed width and have a trailing space: * * "999.999MHz " * "T999 " * "+999 " (10 kHz units) * * Reference: http://www.aprs.org/info/freqspec.txt * *----------------------------------------------------------------*/ static int frequency_spec (float freq, float tone, float offset, char *presult) { int result_size = 24; // TODO: add as parameter. *presult = '\0'; if (freq > 0) { char stemp[16]; /* TODO: Should use letters for > 999.999. */ /* For now, just be sure we have proper field width. */ if (freq > 999.999) freq = 999.999; snprintf (stemp, sizeof(stemp), "%07.3fMHz ", freq); strlcpy (presult, stemp, result_size); } if (tone != G_UNKNOWN) { char stemp[12]; if (tone == 0) { strlcpy (stemp, "Toff ", sizeof (stemp)); } else { snprintf (stemp, sizeof(stemp), "T%03d ", (int)tone); } strlcat (presult, stemp, result_size); } if (offset != G_UNKNOWN) { char stemp[12]; snprintf (stemp, sizeof(stemp), "%+04d ", (int)round(offset * 100)); strlcat (presult, stemp, result_size); } return (strlen(presult)); } /*------------------------------------------------------------------ * * Name: encode_position * * Purpose: Construct info part for position report format. * * Inputs: messaging - This determines whether the data type indicator * is set to '!' (false) or '=' (true). * compressed - Send in compressed form? * lat - Latitude. * lon - Longitude. * ambiguity - Number of digits to omit from location. * alt_ft - Altitude in feet. * symtab - Symbol table id or overlay. * symbol - Symbol id. * * power - Watts. * height - Feet. * gain - dB. Not clear if it is dBi or dBd. * dir - Directivity: N, NE, etc., omni. * * course - Degress, 0 - 360 (360 equiv. to 0). * Use G_UNKNOWN for none or unknown. * speed - knots. // TODO: should distinguish unknown(not revevant) vs. known zero. * * freq - MHz. * tone - Hz. * offset - MHz. * * comment - Additional comment text. * * result_size - Ammount of space for result, provideed by * caller, to avoid buffer overflow. * * Outputs: presult - Stored here. Should be at least ??? bytes. * Could get into hundreds of characters * because it includes the comment. * * Returns: Number of characters in result. * * Description: There can be a single optional "data extension" * following the position so there is a choice * between: * Power/height/gain/directivity or * Course/speed. * * Afer that, * *----------------------------------------------------------------*/ typedef struct aprs_ll_pos_s { char dti; /* ! or = */ position_t pos; /* Comment up to 43 characters. */ /* Start of comment could be data extension(s). */ } aprs_ll_pos_t; typedef struct aprs_compressed_pos_s { char dti; /* ! or = */ compressed_position_t cpos; /* Comment up to 40 characters. */ /* No data extension allowed for compressed location. */ } aprs_compressed_pos_t; int encode_position (int messaging, int compressed, double lat, double lon, int ambiguity, int alt_ft, char symtab, char symbol, int power, int height, int gain, char *dir, int course, int speed, float freq, float tone, float offset, char *comment, char *presult, size_t result_size) { int result_len = 0; if (compressed) { aprs_compressed_pos_t *p = (aprs_compressed_pos_t *)presult; p->dti = messaging ? '=' : '!'; set_comp_position (symtab, symbol, lat, lon, power, height, gain, course, speed, &(p->cpos)); result_len = 1 + sizeof (p->cpos); } else { aprs_ll_pos_t *p = (aprs_ll_pos_t *)presult; p->dti = messaging ? '=' : '!'; set_norm_position (symtab, symbol, lat, lon, ambiguity, &(p->pos)); result_len = 1 + sizeof (p->pos); /* Optional data extension. (singular) */ /* Can't have both course/speed and PHG. Former gets priority. */ if (course != G_UNKNOWN || speed > 0) { result_len += cse_spd_data_extension (course, speed, presult + result_len); } else if (power > 0 || height > 0 || gain > 0) { result_len += phg_data_extension (power, height, gain, dir, presult + result_len); } } /* Optional frequency spec. */ if (freq != 0 || tone != 0 || offset != 0) { result_len += frequency_spec (freq, tone, offset, presult + result_len); } presult[result_len] = '\0'; /* Altitude. Can be anywhere in comment. */ if (alt_ft != G_UNKNOWN) { char salt[12]; /* Not clear if altitude can be negative. */ /* Be sure it will be converted to 6 digits. */ if (alt_ft < 0) alt_ft = 0; if (alt_ft > 999999) alt_ft = 999999; snprintf (salt, sizeof(salt), "/A=%06d", alt_ft); strlcat (presult, salt, result_size); result_len += strlen(salt); } /* Finally, comment text. */ if (comment != NULL) { strlcat (presult, comment, result_size); result_len += strlen(comment); } if (result_len >= (int)result_size) { text_color_set(DW_COLOR_ERROR); dw_printf ("encode_position result of %d characters won't fit into space provided.\n", result_len); } return (result_len); } /* end encode_position */ /*------------------------------------------------------------------ * * Name: encode_object * * Purpose: Construct info part for object report format. * * Inputs: name - Name, up to 9 characters. * compressed - Send in compressed form? * thyme - Time stamp or 0 for none. * lat - Latitude. * lon - Longitude. * ambiguity - Number of digits to omit from location. * symtab - Symbol table id or overlay. * symbol - Symbol id. * * power - Watts. * height - Feet. * gain - dB. Not clear if it is dBi or dBd. * dir - Direction: N, NE, etc., omni. * * course - Degress, 0 - 360 (360 equiv. to 0). * Use G_UNKNOWN for none or unknown. * speed - knots. * * freq - MHz. * tone - Hz. * offset - MHz. * * comment - Additional comment text. * * result_size - Ammount of space for result, provideed by * caller, to avoid buffer overflow. * * Outputs: presult - Stored here. Should be at least ??? bytes. * 36 for fixed part, * 7 for optional extended data, * ~20 for freq, etc., * comment could be very long... * * Returns: Number of characters in result. * * Description: * *----------------------------------------------------------------*/ typedef struct aprs_object_s { struct { char dti; /* ; */ char name[9]; char live_killed; /* * for live or _ for killed */ char time_stamp[7]; } o; union { position_t pos; /* Up to 43 char comment. First 7 bytes could be data extension. */ compressed_position_t cpos; /* Up to 40 char comment. No PHG data extension in this case. */ } u; } aprs_object_t; int encode_object (char *name, int compressed, time_t thyme, double lat, double lon, int ambiguity, char symtab, char symbol, int power, int height, int gain, char *dir, int course, int speed, float freq, float tone, float offset, char *comment, char *presult, size_t result_size) { aprs_object_t *p = (aprs_object_t *) presult; int result_len = 0; int n; p->o.dti = ';'; memset (p->o.name, ' ', sizeof(p->o.name)); n = strlen(name); if (n > (int)(sizeof(p->o.name))) n = sizeof(p->o.name); memcpy (p->o.name, name, n); p->o.live_killed = '*'; if (thyme != 0) { struct tm tm; #define XMIT_UTC 1 #if XMIT_UTC (void)gmtime_r (&thyme, &tm); #else /* Using local time, for this application, would make more sense to me. */ /* On Windows, localtime_r produces UTC. */ /* How do we set the time zone? Google for mingw time zone. */ localtime_r (thyme, &tm); #endif snprintf (p->o.time_stamp, sizeof(p->o.time_stamp), "%02d%02d%02d", tm.tm_mday, tm.tm_hour, tm.tm_min); #if XMIT_UTC p->o.time_stamp[6] = 'z'; #else p->o.time_stamp[6] = '/'; #endif } else { memcpy (p->o.time_stamp, "111111z", sizeof(p->o.time_stamp)); } if (compressed) { set_comp_position (symtab, symbol, lat, lon, power, height, gain, course, speed, &(p->u.cpos)); result_len = sizeof(p->o) + sizeof (p->u.cpos); } else { set_norm_position (symtab, symbol, lat, lon, ambiguity, &(p->u.pos)); result_len = sizeof(p->o) + sizeof (p->u.pos); /* Optional data extension. (singular) */ /* Can't have both course/speed and PHG. Former gets priority. */ if (course != G_UNKNOWN || speed > 0) { result_len += cse_spd_data_extension (course, speed, presult + result_len); } else if (power > 0 || height > 0 || gain > 0) { result_len += phg_data_extension (power, height, gain, dir, presult + result_len); } } /* Optional frequency spec. */ if (freq != 0 || tone != 0 || offset != 0) { result_len += frequency_spec (freq, tone, offset, presult + result_len); } presult[result_len] = '\0'; /* Finally, comment text. */ if (comment != NULL) { strlcat (presult, comment, result_size); result_len += strlen(comment); } if (result_len >= (int)result_size) { text_color_set(DW_COLOR_ERROR); dw_printf ("encode_object result of %d characters won't fit into space provided.\n", result_len); } return (result_len); } /* end encode_object */ /*------------------------------------------------------------------ * * Name: main * * Purpose: Quick test for some functions in this file. * * Description: Just a smattering, not an organized test. * * $ rm a.exe ; gcc -DEN_MAIN encode_aprs.c latlong.c textcolor.c ; ./a.exe * *----------------------------------------------------------------*/ #if EN_MAIN int main (int argc, char *argv[]) { char result[100]; int errors = 0; /*********** Position ***********/ encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', 0, 0, 0, NULL, G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } /* with PHG. */ // TODO: Need to test specifying some but not all. encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', 50, 100, 6, "N", G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&PHG7368") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } /* with freq & tone. minus offset, no offset, explict simplex. */ encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, -0.6, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 -060 ") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, G_UNKNOWN, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 ") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, 0, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 +000 ") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } /* with course/speed, freq, and comment! */ encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', 0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz T074 -060 River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } /* Course speed, no tone, + offset */ encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', 0, 0, 0, NULL, 180, 55, 146.955, G_UNKNOWN, 0.6, "River flooding", result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz +060 River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } /* Course speed, no tone, + offset + altitude */ encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, 12345, 'D', '&', 0, 0, 0, NULL, 180, 55, 146.955, G_UNKNOWN, 0.6, "River flooding", result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz +060 /A=012345River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, 12345, 'D', '&', 0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz Toff +060 /A=012345River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } // TODO: try boundary conditions of course = 0, 359, 360 /*********** Compressed position. ***********/ encode_position (0, 1, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', 0, 0, 0, NULL, G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!D8yKC 0) { text_color_set (DW_COLOR_ERROR); dw_printf ("Encode APRS test FAILED with %d errors.\n", errors); exit (EXIT_FAILURE); } text_color_set (DW_COLOR_REC); dw_printf ("Encode APRS test PASSED with no errors.\n"); exit (EXIT_SUCCESS); } /* end main */ #endif /* unit test */ /* end encode_aprs.c */ direwolf-1.5+dfsg/encode_aprs.h000066400000000000000000000011361347750676600165720ustar00rootroot00000000000000 int encode_position (int messaging, int compressed, double lat, double lon, int ambiguity, int alt_ft, char symtab, char symbol, int power, int height, int gain, char *dir, int course, int speed_knots, float freq, float tone, float offset, char *comment, char *presult, size_t result_size); int encode_object (char *name, int compressed, time_t thyme, double lat, double lon, int ambiguity, char symtab, char symbol, int power, int height, int gain, char *dir, int course, int speed_knots, float freq, float tone, float offset, char *comment, char *presult, size_t result_size); direwolf-1.5+dfsg/fcs_calc.c000066400000000000000000000107741347750676600160500ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011 John Langner, WB2OSZ // // 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, see . // #include "direwolf.h" #include /* * Calculate the FCS for an AX.25 frame. */ #include "fcs_calc.h" static const unsigned short ccitt_table[256] = { // from http://www.ietf.org/rfc/rfc1549.txt 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 }; /* * Use this for an AX.25 frame. */ unsigned short fcs_calc (unsigned char *data, int len) { unsigned short crc = 0xffff; int j; for (j=0; j> 8) ^ ccitt_table[((crc) ^ data[j]) & 0xff]; } return ( crc ^ 0xffff ); } /* * CRC is also used for duplicate checking for the digipeater and IGate. * A packet is considered a duplicate if the source, destination, and * information parts match. In other words, we ignore the via path * which changes along the way. * Rather than keeping a variable length string we just keep a 16 bit * CRC which takes less memory and processing to compare. * * This can result in occasional false matches. If we had a random * 16 bit number, there is a 1/65536 ( = 0.0015 % ) chance that it will * match and we will drop something that should be passed along. * * Looking at it another way, there is a 0.9999847412109375 (out of 1) * probability of doing the right thing. */ /* * This can be used when we want to calculate a single CRC over disjoint data. * * crc = crc16 (region1, sizeof(region1), 0xffff); * crc = crc16 (region2, sizeof(region2), crc); * crc = crc16 (region3, sizeof(region3), crc); */ unsigned short crc16 (unsigned char *data, int len, unsigned short seed) { unsigned short crc = seed; int j; for (j=0; j> 8) ^ ccitt_table[((crc) ^ data[j]) & 0xff]; } return ( crc ^ 0xffff ); } direwolf-1.5+dfsg/fcs_calc.h000066400000000000000000000002571347750676600160500ustar00rootroot00000000000000 /* fcs_calc.h */ unsigned short fcs_calc (unsigned char *data, int len); unsigned short crc16 (unsigned char *data, int len, unsigned short seed); /* end fcs_calc.h */ direwolf-1.5+dfsg/fsk_demod_agc.h000066400000000000000000000001011347750676600170440ustar00rootroot00000000000000#define TUNE_MS_FILTER_SIZE 140 #define TUNE_PRE_BAUD 1.080 direwolf-1.5+dfsg/fsk_demod_state.h000066400000000000000000000177651347750676600174620ustar00rootroot00000000000000/* fsk_demod_state.h */ #ifndef FSK_DEMOD_STATE_H #include "rpack.h" #include "audio.h" // for enum modem_t /* * Demodulator state. * The name of the file is from we only had FSK. Now we have other techniques. * Different copy is required for each channel & subchannel being processed concurrently. */ // TODO1.2: change prefix from BP_ to DSP_ typedef enum bp_window_e { BP_WINDOW_TRUNCATED, BP_WINDOW_COSINE, BP_WINDOW_HAMMING, BP_WINDOW_BLACKMAN, BP_WINDOW_FLATTOP } bp_window_t; struct demodulator_state_s { /* * These are set once during initialization. */ enum modem_t modem_type; // MODEM_AFSK, MODEM_8PSK, etc. char profile; // 'A', 'B', etc. Upper case. // Only needed to see if we are using 'F' to take fast path. #define TICKS_PER_PLL_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 ) int pll_step_per_sample; // PLL is advanced by this much each audio sample. // Data is sampled when it overflows. int ms_filter_size; /* Size of mark & space filters, in audio samples. */ /* Started off as a guess of one bit length */ /* but somewhat longer turned out to be better. */ /* Currently using same size for any prefilter. */ #define MAX_FILTER_SIZE 320 /* 304 is needed for profile C, 300 baud & 44100. */ /* * Filter length for Mark & Space in bit times. * e.g. 1 means 1/1200 second for 1200 baud. */ float ms_filter_len_bits; /* * Window type for the various filters. */ bp_window_t pre_window; bp_window_t ms_window; bp_window_t lp_window; /* * Alternate Low pass filters. * First is arbitrary number for quick IIR. * Second is frequency as ratio to baud rate for FIR. */ int lpf_use_fir; /* 0 for IIR, 1 for FIR. */ float lpf_iir; /* Only if using IIR. */ float lpf_baud; /* Cutoff frequency as fraction of baud. */ /* Intuitively we'd expect this to be somewhere */ /* in the range of 0.5 to 1. */ /* In practice, it turned out a little larger */ /* for profiles B, C, D. */ float lp_filter_len_bits; /* Length in number of bit times. */ int lp_filter_size; /* Size of Low Pass filter, in audio samples. */ /* Previously it was always the same as the M/S */ /* filters but in version 1.2 it's now independent. */ /* * Automatic gain control. Fast attack and slow decay factors. */ float agc_fast_attack; float agc_slow_decay; /* * Use a longer term view for reporting signal levels. */ float quick_attack; float sluggish_decay; /* * Hysteresis before final demodulator 0 / 1 decision. */ float hysteresis; int num_slicers; /* >1 for multiple slicers. */ /* * Phase Locked Loop (PLL) inertia. * Larger number means less influence by signal transitions. */ float pll_locked_inertia; float pll_searching_inertia; /* * Optional band pass pre-filter before mark/space detector. */ int use_prefilter; /* True to enable it. */ float prefilter_baud; /* Cutoff frequencies, as fraction of */ /* baud rate, beyond tones used. */ /* Example, if we used 1600/1800 tones at */ /* 300 baud, and this was 0.5, the cutoff */ /* frequencies would be: */ /* lower = min(1600,1800) - 0.5 * 300 = 1450 */ /* upper = max(1600,1800) + 0.5 * 300 = 1950 */ float pre_filter_len_bits; /* Length in number of bit times. */ int pre_filter_size; /* Size of pre filter, in audio samples. */ float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); /* * Kernel for the mark and space detection filters. */ float m_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); float m_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); /* * These are for PSK only. * They are number of delay line taps into previous symbol. * They are one symbol period and + or - 45 degrees of the carrier frequency. */ int boffs; /* symbol length based on sample rate and baud. */ int coffs; /* to get cos component of previous symbol. */ int soffs; /* to get sin component of previous symbol. */ unsigned int lo_step; /* How much to advance the local oscillator */ /* phase for each audio sample. */ int psk_use_lo; /* Use local oscillator rather than self correlation. */ /* * The rest are continuously updated. */ unsigned int lo_phase; /* Local oscillator for PSK. */ /* * Most recent raw audio samples, before/after prefiltering. */ float raw_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); /* * Use half of the AGC code to get a measure of input audio amplitude. * These use "quick" attack and "sluggish" decay while the * AGC uses "fast" attack and "slow" decay. */ float alevel_rec_peak; float alevel_rec_valley; float alevel_mark_peak; float alevel_space_peak; /* * Input to the mark/space detector. * Could be prefiltered or raw audio. */ float ms_in_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); /* * Outputs from the mark and space amplitude detection, * used as inputs to the FIR lowpass filters. * Kernel for the lowpass filters. */ float m_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); float s_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); float lp_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); float m_peak, s_peak; float m_valley, s_valley; float m_amp_prev, s_amp_prev; /* * For the PLL and data bit timing. * starting in version 1.2 we can have multiple slicers for one demodulator. * Each slicer has its own PLL and HDLC decoder. */ /* * Version 1.3: Clean up subchan vs. slicer. * * Originally some number of CHANNELS (originally 2, later 6) * which can have multiple parallel demodulators called SUB-CHANNELS. * This was originally for staggered frequencies for HF SSB. * It can also be used for multiple demodulators with the same * frequency but other differing parameters. * Each subchannel has its own demodulator and HDLC decoder. * * In version 1.2 we added multiple SLICERS. * The data structure, here, has multiple slicers per * demodulator (subchannel). Due to fuzzy thinking or * expediency, the multiple slicers got mapped into subchannels. * This means we can't use both multiple decoders and * multiple slicers at the same time. * * Clean this up in 1.3 and keep the concepts separate. * This means adding a third variable many places * we are passing around the origin. * */ struct { signed int data_clock_pll; // PLL for data clock recovery. // It is incremented by pll_step_per_sample // for each audio sample. signed int prev_d_c_pll; // Previous value of above, before // incrementing, to detect overflows. int prev_demod_data; // Previous data bit detected. // Used to look for transitions. float prev_demod_out_f; /* This is used only for "9600" baud data. */ int lfsr; // Descrambler shift register. } slicer [MAX_SLICERS]; // Actual number in use is num_slicers. // Should be in range 1 .. MAX_SLICERS, /* * Special for Rino decoder only. * One for each possible signal polarity. * The project showed promise but fell by the wayside. */ #if 0 struct gr_state_s { signed int data_clock_pll; // PLL for data clock recovery. // It is incremented by pll_step_per_sample // for each audio sample. signed int prev_d_c_pll; // Previous value of above, before // incrementing, to detect overflows. float gr_minus_peak; // For automatic gain control. float gr_plus_peak; int gr_sync; // Is sync pulse present? int gr_prev_sync; // Previous state to detect leading edge. int gr_first_sample; // Index of starting sample index for debugging. int gr_dcd; // Data carrier detect. i.e. are we // currently decoding a message. float gr_early_sum; // For averaging bit values in two regions. int gr_early_count; float gr_late_sum; int gr_late_count; float gr_sync_sum; int gr_sync_count; int gr_bit_count; // Bit index into message. struct rpack_s rpack; // Collection of bits. } gr_state[2]; #endif }; #define FSK_DEMOD_STATE_H 1 #endifdirewolf-1.5+dfsg/fsk_filters.h000066400000000000000000000010521347750676600166200ustar00rootroot00000000000000/* 1200 bits/sec with Audio sample rate = 11025 */ /* Mark freq = 1200, Space freq = 2200 */ static const signed short m_sin_table[9] = { 0 , 7347 , 11257 , 9899 , 3909 , -3909 , -9899 , -11257 , -7347 }; static const signed short m_cos_table[9] = { 11431 , 8756 , 1984 , -5715 , -10741 , -10741 , -5715 , 1984 , 8756 }; static const signed short s_sin_table[9] = { 0 , 10950 , 6281 , -7347 , -10496 , 1327 , 11257 , 5130 , -8314 }; static const signed short s_cos_table[9] = { 11431 , 3278 , -9550 , -8756 , 4527 , 11353 , 1984 , -10215 , -7844 }; direwolf-1.5+dfsg/fsk_gen_filter.h000066400000000000000000000003771347750676600172770ustar00rootroot00000000000000 #ifndef FSK_GEN_FILTER_H #define FSK_GEN_FILTER_H 1 #include "audio.h" #include "fsk_demod_state.h" void fsk_gen_filter (int samples_per_sec, int baud, int mark_freq, int space_freq, char profile, struct demodulator_state_s *D); #endifdirewolf-1.5+dfsg/gen_packets.c000066400000000000000000000722671347750676600166030ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 2014, 2015, 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Name: gen_packets.c * * Purpose: Test program for generating AX.25 frames. * * Description: Given messages are converted to audio and written * to a .WAV type audio file. * * Bugs: Most options are implemented for only one audio channel. * * Examples: Different speeds: * * gen_packets -o z1.wav * atest z1.wav * * gen_packets -B 300 -o z3.wav * atest -B 300 z3.wav * * gen_packets -B 9600 -o z9.wav * atest -B 300 z9.wav * * User-defined content: * * echo "WB2OSZ>APDW12:This is a test" | gen_packets -o z.wav - * atest z.wav * * echo "WB2OSZ>APDW12:Test line 1" > z.txt * echo "WB2OSZ>APDW12:Test line 2" >> z.txt * echo "WB2OSZ>APDW12:Test line 3" >> z.txt * gen_packets -o z.wav z.txt * atest z.wav * * With artificial noise added: * * gen_packets -n 100 -o z2.wav * atest z2.wav * * *------------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include "audio.h" #include "ax25_pad.h" #include "hdlc_send.h" #include "gen_tone.h" #include "textcolor.h" #include "morse.h" #include "dtmf.h" /* Own random number generator so we can get */ /* same results on Windows and Linux. */ #define MY_RAND_MAX 0x7fffffff static int seed = 1; static int my_rand (void) { // Perform the calculation as unsigned to avoid signed overflow error. seed = (int)(((unsigned)seed * 1103515245) + 12345) & MY_RAND_MAX; return (seed); } static void usage (char **argv); static int audio_file_open (char *fname, struct audio_s *pa); static int audio_file_close (void); static int g_add_noise = 0; static float g_noise_level = 0; static int g_morse_wpm = 0; /* Send morse code at this speed. */ static struct audio_s modem; static void send_packet (char *str) { packet_t pp; unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; int flen; int c; if (g_morse_wpm > 0) { // TODO: Why not use the destination field instead of command line option? morse_send (0, str, g_morse_wpm, 100, 100); } else { pp = ax25_from_text (str, 1); flen = ax25_pack (pp, fbuf); for (c=0; c MAX_BAUD) { text_color_set(DW_COLOR_ERROR); dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); exit (EXIT_FAILURE); } break; case 'B': /* -B for data Bit rate */ /* 300 implies 1600/1800 AFSK. */ /* 1200 implies 1200/2200 AFSK. */ /* 9600 implies scrambled. */ /* If you want something else, specify -B first */ /* then anything to override these defaults. */ modem.achan[0].baud = atoi(optarg); text_color_set(DW_COLOR_INFO); dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud); if (modem.achan[0].baud != 100 && (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); exit (EXIT_FAILURE); } /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ /* that need to be kept in sync. Maybe it could be a common function someday. */ if (modem.achan[0].baud == 100) { modem.achan[0].modem_type = MODEM_AFSK; modem.achan[0].mark_freq = 1615; modem.achan[0].space_freq = 1785; } else if (modem.achan[0].baud < 600) { modem.achan[0].modem_type = MODEM_AFSK; modem.achan[0].mark_freq = 1600; // Typical for HF SSB modem.achan[0].space_freq = 1800; } else if (modem.achan[0].baud < 1800) { modem.achan[0].modem_type = MODEM_AFSK; modem.achan[0].mark_freq = DEFAULT_MARK_FREQ; modem.achan[0].space_freq = DEFAULT_SPACE_FREQ; } else if (modem.achan[0].baud < 3600) { modem.achan[0].modem_type = MODEM_QPSK; modem.achan[0].mark_freq = 0; modem.achan[0].space_freq = 0; dw_printf ("Using V.26 QPSK rather than AFSK.\n"); if (modem.achan[0].baud != 2400) { text_color_set(DW_COLOR_ERROR); dw_printf ("Bit rate should be standard 2400 rather than specified %d.\n", modem.achan[0].baud); } } else if (modem.achan[0].baud < 7200) { modem.achan[0].modem_type = MODEM_8PSK; modem.achan[0].mark_freq = 0; modem.achan[0].space_freq = 0; dw_printf ("Using V.27 8PSK rather than AFSK.\n"); if (modem.achan[0].baud != 4800) { text_color_set(DW_COLOR_ERROR); dw_printf ("Bit rate should be standard 4800 rather than specified %d.\n", modem.achan[0].baud); } } else { modem.achan[0].modem_type = MODEM_SCRAMBLE; text_color_set(DW_COLOR_INFO); dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); } break; case 'g': /* -g for g3ruh scrambling */ modem.achan[0].modem_type = MODEM_SCRAMBLE; text_color_set(DW_COLOR_INFO); dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); break; case 'm': /* -m for Mark freq */ modem.achan[0].mark_freq = atoi(optarg); text_color_set(DW_COLOR_INFO); dw_printf ("Mark frequency set to %d Hz.\n", modem.achan[0].mark_freq); if (modem.achan[0].mark_freq < 300 || modem.achan[0].mark_freq > 3000) { text_color_set(DW_COLOR_ERROR); dw_printf ("Use a more reasonable value in range of 300 - 3000.\n"); exit (EXIT_FAILURE); } break; case 's': /* -s for Space freq */ modem.achan[0].space_freq = atoi(optarg); text_color_set(DW_COLOR_INFO); dw_printf ("Space frequency set to %d Hz.\n", modem.achan[0].space_freq); if (modem.achan[0].space_freq < 300 || modem.achan[0].space_freq > 3000) { text_color_set(DW_COLOR_ERROR); dw_printf ("Use a more reasonable value in range of 300 - 3000.\n"); exit (EXIT_FAILURE); } break; case 'n': /* -n number of packets with increasing noise. */ packet_count = atoi(optarg); g_add_noise = 1; break; case 'a': /* -a for amplitude */ amplitude = atoi(optarg); text_color_set(DW_COLOR_INFO); dw_printf ("Amplitude set to %d%%.\n", amplitude); if (amplitude < 0 || amplitude > 200) { text_color_set(DW_COLOR_ERROR); dw_printf ("Amplitude must be in range of 0 to 200.\n"); exit (EXIT_FAILURE); } break; case 'r': /* -r for audio sample Rate */ modem.adev[0].samples_per_sec = atoi(optarg); text_color_set(DW_COLOR_INFO); dw_printf ("Audio sample rate set to %d samples / second.\n", modem.adev[0].samples_per_sec); if (modem.adev[0].samples_per_sec < MIN_SAMPLES_PER_SEC || modem.adev[0].samples_per_sec > MAX_SAMPLES_PER_SEC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Use a more reasonable audio sample rate in range of %d - %d.\n", MIN_SAMPLES_PER_SEC, MAX_SAMPLES_PER_SEC); exit (EXIT_FAILURE); } break; case 'z': /* -z leading zeros before frame flag */ leading_zeros = atoi(optarg); text_color_set(DW_COLOR_INFO); dw_printf ("Send %d zero bits before frame flag.\n", leading_zeros); /* The demodulator needs a few for the clock recovery PLL. */ /* We don't want to be here all day either. */ /* We can't translate to time yet because the data bit rate */ /* could be changed later. */ if (leading_zeros < 8 || leading_zeros > 12000) { text_color_set(DW_COLOR_ERROR); dw_printf ("Use a more reasonable value.\n"); exit (EXIT_FAILURE); } break; case '8': /* -8 for 8 bit samples */ modem.adev[0].bits_per_sample = 8; text_color_set(DW_COLOR_INFO); dw_printf("8 bits per audio sample rather than 16.\n"); break; case '2': /* -2 for 2 channels of sound */ modem.adev[0].num_channels = 2; modem.achan[1].valid = 1; text_color_set(DW_COLOR_INFO); dw_printf("2 channels of sound rather than 1.\n"); break; case 'o': /* -o for Output file */ strlcpy (output_file, optarg, sizeof(output_file)); text_color_set(DW_COLOR_INFO); dw_printf ("Output file set to %s\n", output_file); break; case 'M': /* -M for morse code speed */ //TODO: document this. // Why not base it on the destination field instead? g_morse_wpm = atoi(optarg); text_color_set(DW_COLOR_INFO); dw_printf ("Morse code speed set to %d WPM.\n", g_morse_wpm); if (g_morse_wpm < 5 || g_morse_wpm > 50) { text_color_set(DW_COLOR_ERROR); dw_printf ("Morse code speed must be in range of 5 to 50 WPM.\n"); exit (EXIT_FAILURE); } break; case 'X': experiment = 1; break; case '?': /* Unknown option message was already printed. */ usage (argv); break; default: /* Should not be here. */ text_color_set(DW_COLOR_ERROR); dw_printf("?? getopt returned character code 0%o ??\n", c); usage (argv); } } /* * Open the output file. */ if (strlen(output_file) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: The -o output file option must be specified.\n"); usage (argv); exit (1); } err = audio_file_open (output_file, &modem); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Can't open output file.\n"); exit (1); } if (experiment) { modem.achan[0].modem_type = MODEM_QPSK; modem.achan[0].baud = 2400; // really bps not baud. amplitude = 100; } gen_tone_init (&modem, amplitude/2, 1); morse_init (&modem, amplitude/2); dtmf_init (&modem, amplitude/2); assert (modem.adev[0].bits_per_sample == 8 || modem.adev[0].bits_per_sample == 16); assert (modem.adev[0].num_channels == 1 || modem.adev[0].num_channels == 2); assert (modem.adev[0].samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC); if (experiment) { int chan = 0; int n; // 6 cycles of 1800 Hz. for (n=0; n<8; n++) { tone_gen_put_bit (chan, 0); } // Shift 90 tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 1); // Shift 90 tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 1); // Shift 90 tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 1); // Shift 90 tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 1); // Shift 180 tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); // Shift 270 tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 0); // Shift 0 tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 0); // Shift 0 tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 0); // HDLC flag - six 1 in a row. tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 0); // reverse even/odd position tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 1); tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 0); // Shift 0 tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 0); // Shift 0 tone_gen_put_bit (chan, 0); tone_gen_put_bit (chan, 0); audio_file_close (); return (EXIT_SUCCESS); } /* * Get user packets(s) from file or stdin if specified. * "-n" option is ignored in this case. */ if (optind < argc) { char str[400]; // dw_printf("non-option ARGV-elements: "); // while (optind < argc) // dw_printf("%s ", argv[optind++]); //dw_printf("\n"); if (optind < argc - 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Warning: File(s) beyond the first are ignored.\n"); } if (strcmp(argv[optind], "-") == 0) { text_color_set(DW_COLOR_INFO); dw_printf ("Reading from stdin ...\n"); input_fp = stdin; } else { input_fp = fopen(argv[optind], "r"); if (input_fp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't open %s for read.\n", argv[optind]); exit (EXIT_FAILURE); } text_color_set(DW_COLOR_INFO); dw_printf ("Reading from %s ...\n", argv[optind]); } while (fgets (str, sizeof(str), input_fp) != NULL) { text_color_set(DW_COLOR_REC); dw_printf ("%s", str); send_packet (str); } if (input_fp != stdin) { fclose (input_fp); } audio_file_close(); return EXIT_SUCCESS; } /* * Otherwise, use the built in packets. */ text_color_set(DW_COLOR_INFO); dw_printf ("built in message...\n"); if (packet_count > 0) { /* * Generate packets with increasing noise level. * Would probably be better to record real noise from a radio but * for now just use a random number generator. */ for (i = 1; i <= packet_count; i++) { char stemp[88]; if (modem.achan[0].baud < 600) { /* e.g. 300 bps AFSK - About 2/3 should be decoded properly. */ g_noise_level = amplitude *.0048 * ((float)i / packet_count); } else if (modem.achan[0].baud < 1800) { /* e.g. 1200 bps AFSK - About 2/3 should be decoded properly. */ g_noise_level = amplitude *.0023 * ((float)i / packet_count); } else if (modem.achan[0].baud < 3600) { /* e.g. 2400 bps QPSK - T.B.D. */ g_noise_level = amplitude *.0015 * ((float)i / packet_count); } else if (modem.achan[0].baud < 7200) { /* e.g. 4800 bps - T.B.D. */ g_noise_level = amplitude *.0007 * ((float)i / packet_count); } else { /* e.g. 9600 */ g_noise_level = 0.33 * (amplitude / 200.0) * ((float)i / packet_count); // temp test //g_noise_level = 0.20 * (amplitude / 200.0) * ((float)i / packet_count); } snprintf (stemp, sizeof(stemp), "WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! %04d of %04d", i, packet_count); send_packet (stemp); } } else { /* * Builtin default 4 packets. */ send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 1 of 4"); send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 2 of 4"); send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 3 of 4"); send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 4 of 4"); } audio_file_close(); return EXIT_SUCCESS; } static void usage (char **argv) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n"); dw_printf ("Usage: gen_packets [options] [file]\n"); dw_printf ("Options:\n"); dw_printf (" -a Signal amplitude in range of 0 - 200%%. Default 50.\n"); dw_printf (" -b Bits / second for data. Default is %d.\n", DEFAULT_BAUD); dw_printf (" -B Bits / second for data. Proper modem selected for 300, 1200, 2400, 4800, 9600.\n"); dw_printf (" -g Scrambled baseband rather than AFSK.\n"); dw_printf (" -m Mark frequency. Default is %d.\n", DEFAULT_MARK_FREQ); dw_printf (" -s Space frequency. Default is %d.\n", DEFAULT_SPACE_FREQ); dw_printf (" -r Audio sample Rate. Default is %d.\n", DEFAULT_SAMPLES_PER_SEC); dw_printf (" -n Generate specified number of frames with increasing noise.\n"); dw_printf (" -o Send output to .wav file.\n"); dw_printf (" -8 8 bit audio rather than 16.\n"); dw_printf (" -2 2 channels (stereo) audio rather than one channel.\n"); // dw_printf (" -z Number of leading zero bits before frame.\n"); // dw_printf (" Default is 12 which is .01 seconds at 1200 bits/sec.\n"); dw_printf ("\n"); dw_printf ("An optional file may be specified to provide messages other than\n"); dw_printf ("the default built-in message. The format should correspond to\n"); dw_printf ("the standard packet monitoring representation such as,\n\n"); dw_printf (" WB2OSZ-1>APDW12,WIDE2-2:!4237.14NS07120.83W#\n"); dw_printf ("\n"); dw_printf ("Example: gen_packets -o x.wav \n"); dw_printf ("\n"); dw_printf (" With all defaults, a built-in test message is generated\n"); dw_printf (" with standard Bell 202 tones used for packet radio on ordinary\n"); dw_printf (" VHF FM transceivers.\n"); dw_printf ("\n"); dw_printf ("Example: gen_packets -o x.wav -g -b 9600\n"); dw_printf ("Shortcut: gen_packets -o x.wav -B 9600\n"); dw_printf ("\n"); dw_printf (" 9600 baud mode.\n"); dw_printf ("\n"); dw_printf ("Example: gen_packets -o x.wav -m 1600 -s 1800 -b 300\n"); dw_printf ("Shortcut: gen_packets -o x.wav -B 300\n"); dw_printf ("\n"); dw_printf (" 200 Hz shift, 300 baud, suitable for HF SSB transceiver.\n"); dw_printf ("\n"); dw_printf ("Example: echo -n \"WB2OSZ>WORLD:Hello, world!\" | gen_packets -a 25 -o x.wav -\n"); dw_printf ("\n"); dw_printf (" Read message from stdin and put quarter volume sound into the file x.wav.\n"); exit (EXIT_FAILURE); } /*------------------------------------------------------------------ * * Name: audio_file_open * * Purpose: Open a .WAV format file for output. * * Inputs: fname - Name of .WAV file to create. * * pa - Address of structure of type audio_s. * * The fields that we care about are: * num_channels * samples_per_sec * bits_per_sample * If zero, reasonable defaults will be provided. * * Returns: 0 for success, -1 for failure. * *----------------------------------------------------------------*/ struct wav_header { /* .WAV file header. */ char riff[4]; /* "RIFF" */ int filesize; /* file length - 8 */ char wave[4]; /* "WAVE" */ char fmt[4]; /* "fmt " */ int fmtsize; /* 16. */ short wformattag; /* 1 for PCM. */ short nchannels; /* 1 for mono, 2 for stereo. */ int nsamplespersec; /* sampling freq, Hz. */ int navgbytespersec; /* = nblockalign * nsamplespersec. */ short nblockalign; /* = wbitspersample / 8 * nchannels. */ short wbitspersample; /* 16 or 8. */ char data[4]; /* "data" */ int datasize; /* number of bytes following. */ } ; /* 8 bit samples are unsigned bytes */ /* in range of 0 .. 255. */ /* 16 bit samples are signed short */ /* in range of -32768 .. +32767. */ static FILE *out_fp = NULL; static struct wav_header header; static int byte_count; /* Number of data bytes written to file. */ /* Will be written to header when file is closed. */ static int audio_file_open (char *fname, struct audio_s *pa) { int n; /* * Fill in defaults for any missing values. */ if (pa -> adev[0].num_channels == 0) pa -> adev[0].num_channels = DEFAULT_NUM_CHANNELS; if (pa -> adev[0].samples_per_sec == 0) pa -> adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; if (pa -> adev[0].bits_per_sample == 0) pa -> adev[0].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* * Write the file header. Don't know length yet. */ out_fp = fopen (fname, "wb"); if (out_fp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't open file for write: %s\n", fname); perror (""); return (-1); } memset (&header, 0, sizeof(header)); memcpy (header.riff, "RIFF", (size_t)4); header.filesize = 0; memcpy (header.wave, "WAVE", (size_t)4); memcpy (header.fmt, "fmt ", (size_t)4); header.fmtsize = 16; // Always 16. header.wformattag = 1; // 1 for PCM. header.nchannels = pa -> adev[0].num_channels; header.nsamplespersec = pa -> adev[0].samples_per_sec; header.wbitspersample = pa -> adev[0].bits_per_sample; header.nblockalign = header.wbitspersample / 8 * header.nchannels; header.navgbytespersec = header.nblockalign * header.nsamplespersec; memcpy (header.data, "data", (size_t)4); header.datasize = 0; assert (header.nchannels == 1 || header.nchannels == 2); n = fwrite (&header, sizeof(header), (size_t)1, out_fp); if (n != 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't write header to: %s\n", fname); perror (""); fclose (out_fp); out_fp = NULL; return (-1); } /* * Number of bytes written will be filled in later. */ byte_count = 0; return (0); } /* end audio_open */ /*------------------------------------------------------------------ * * Name: audio_put * * Purpose: Send one byte to the audio output file. * * Inputs: c - One byte in range of 0 - 255. * * Returns: Normally non-negative. * -1 for any type of error. * * Description: The caller must deal with the details of mono/stereo * and number of bytes per sample. * *----------------------------------------------------------------*/ int audio_put (int a, int c) { static short sample16; int s; if (g_add_noise) { if ((byte_count & 1) == 0) { sample16 = c & 0xff; /* save lower byte. */ byte_count++; return c; } else { float r; sample16 |= (c << 8) & 0xff00; /* insert upper byte. */ byte_count++; s = sample16; // sign extend. /* Add random noise to the signal. */ /* r should be in range of -1 .. +1. */ /* Use own function instead of rand() from the C library. */ /* Windows and Linux have different results, messing up my self test procedure. */ /* No idea what Mac OSX and BSD might do. */ r = (my_rand() - MY_RAND_MAX/2.0) / (MY_RAND_MAX/2.0); s += 5 * r * g_noise_level * 32767; if (s > 32767) s = 32767; if (s < -32767) s = -32767; putc(s & 0xff, out_fp); return (putc((s >> 8) & 0xff, out_fp)); } } else { byte_count++; return (putc(c, out_fp)); } } /* end audio_put */ int audio_flush (int a) { return 0; } /*------------------------------------------------------------------ * * Name: audio_file_close * * Purpose: Close the audio output file. * * Returns: Normally non-negative. * -1 for any type of error. * * * Description: Must go back to beginning of file and fill in the * size of the data. * *----------------------------------------------------------------*/ static int audio_file_close (void) { int n; //text_color_set(DW_COLOR_DEBUG); //dw_printf ("audio_close()\n"); /* * Go back and fix up lengths in header. */ header.filesize = byte_count + sizeof(header) - 8; header.datasize = byte_count; if (out_fp == NULL) { return (-1); } fflush (out_fp); fseek (out_fp, 0L, SEEK_SET); n = fwrite (&header, sizeof(header), (size_t)1, out_fp); if (n != 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't write header to audio file.\n"); perror (""); // TODO: remove perror. fclose (out_fp); out_fp = NULL; return (-1); } fclose (out_fp); out_fp = NULL; return (0); } /* end audio_close */ // To keep dtmf.c happy. #include "hdlc_rec.h" // for dcd_change void dcd_change (int chan, int subchan, int slice, int state) { } direwolf-1.5+dfsg/gen_tone.c000066400000000000000000000466141347750676600161130ustar00rootroot00000000000000//#define DEBUG 1 //#define DEBUG2 1 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2014, 2015, 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: gen_tone.c * * Purpose: Convert bits to AFSK for writing to .WAV sound file * or a sound device. * * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "audio.h" #include "gen_tone.h" #include "textcolor.h" #include "fsk_demod_state.h" /* for MAX_FILTER_SIZE which might be overly generous for here. */ /* but safe if we use same size as for receive. */ #include "dsp.h" // Properties of the digitized sound stream & modem. static struct audio_s *save_audio_config_p = NULL; /* * 8 bit samples are unsigned bytes in range of 0 .. 255. * * 16 bit samples are signed short in range of -32768 .. +32767. */ /* Constants after initialization. */ #define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 ) static int ticks_per_sample[MAX_CHANS]; /* Same for both channels of same soundcard */ /* because they have same sample rate */ /* but less confusing to have for each channel. */ static int ticks_per_bit[MAX_CHANS]; static int f1_change_per_sample[MAX_CHANS]; static int f2_change_per_sample[MAX_CHANS]; static short sine_table[256]; /* Accumulators. */ static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation. // Upper bits are used as index into sine table. #define PHASE_SHIFT_180 ( 128u << 24 ) #define PHASE_SHIFT_90 ( 64u << 24 ) #define PHASE_SHIFT_45 ( 32u << 24 ) static int bit_len_acc[MAX_CHANS]; // To accumulate fractional samples per bit. static int lfsr[MAX_CHANS]; // Shift register for scrambler. static int bit_count[MAX_CHANS]; // Counter incremented for each bit transmitted // on the channel. This is only used for QPSK. // The LSB determines if we save the bit until // next time, or send this one with the previously saved. // The LSB+1 position determines if we add an // extra 180 degrees to the phase to compensate // for having 1.5 carrier cycles per symbol time. // For 8PSK, it has a different meaning. It is the // number of bits in 'save_bit' so we can accumulate // three for each symbol. static int save_bit[MAX_CHANS]; /* * The K9NG/G3RUH output originally took a very simple and lazy approach. * We simply generated a square wave with + or - the desired amplitude. * This has a couple undesirable properties. * * - Transmitting a square wave would splatter into adjacent * channels of the transmitter doesn't limit the bandwidth. * * - The usual sample rate of 44100 is not a multiple of the * baud rate so jitter would be added to the zero crossings. * * Starting in version 1.2, we try to overcome these issues by using * a higher sample rate, low pass filtering, and down sampling. * * What sort of low pass filter would be appropriate? Intuitively, * we would expect a cutoff frequency somewhere between baud/2 and baud. * The current values were found with a small amount of trial and * error for best results. Future improvement is certainly possible. */ /* * For low pass filtering of 9600 baud data. */ /* Add sample to buffer and shift the rest down. */ // TODO: Can we have one copy of these in dsp.h? static inline void push_sample (float val, float *buff, int size) { memmove(buff+1,buff,(size-1)*sizeof(float)); buff[0] = val; } /* FIR filter kernel. */ static inline float convolve (const float *data, const float *filter, int filter_size) { float sum = 0; int j; for (j=0; jachan[chan].valid) { int a = ACHAN2ADEV(chan); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("gen_tone_init: chan=%d, modem_type=%d, bps=%d, samples_per_sec=%d\n", chan, save_audio_config_p->achan[chan].modem_type, audio_config_p->achan[chan].baud, audio_config_p->adev[a].samples_per_sec); #endif tone_phase[chan] = 0; bit_len_acc[chan] = 0; lfsr[chan] = 0; ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); // The terminology is all wrong here. Didn't matter with 1200 and 9600. // The config speed should be bits per second rather than baud. // ticks_per_bit should be ticks_per_symbol. switch (save_audio_config_p->achan[chan].modem_type) { case MODEM_QPSK: audio_config_p->achan[chan].mark_freq = 1800; audio_config_p->achan[chan].space_freq = audio_config_p->achan[chan].mark_freq; // Not Used. // symbol time is 1 / (half of bps) ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / ((double)audio_config_p->achan[chan].baud * 0.5)) + 0.5); f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used. tone_phase[chan] = PHASE_SHIFT_45; // Just to mimic first attempt. break; case MODEM_8PSK: audio_config_p->achan[chan].mark_freq = 1800; audio_config_p->achan[chan].space_freq = audio_config_p->achan[chan].mark_freq; // Not Used. // symbol time is 1 / (third of bps) ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / ((double)audio_config_p->achan[chan].baud / 3.)) + 0.5); f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used. break; default: ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); f2_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].space_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); break; } } } for (j=0; j<256; j++) { double a; int s; a = ((double)(j) / 256.0) * (2 * M_PI); s = (int) (sin(a) * 32767 * amp / 100.0); /* 16 bit sound sample must fit in range of -32768 .. +32767. */ if (s < -32768) { text_color_set(DW_COLOR_ERROR); dw_printf ("gen_tone_init: Excessive amplitude is being clipped.\n"); s = -32768; } else if (s > 32767) { text_color_set(DW_COLOR_ERROR); dw_printf ("gen_tone_init: Excessive amplitude is being clipped.\n"); s = 32767; } sine_table[j] = s; } /* * Low pass filter for 9600 baud. */ for (chan = 0; chan < MAX_CHANS; chan++) { if (audio_config_p->achan[chan].valid && (audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE || audio_config_p->achan[chan].modem_type == MODEM_BASEBAND)) { int a = ACHAN2ADEV(chan); int samples_per_sec; /* Might be scaled up! */ int baud; /* These numbers were by trial and error. Need more investigation here. */ float filter_len_bits = 88 * 9600.0 / (44100.0 * 2.0); /* Filter length in number of data bits. */ /* Currently 9.58 */ float lpf_baud = 0.8; /* Lowpass cutoff freq as fraction of baud rate */ float fc; /* Cutoff frequency as fraction of sampling frequency. */ /* * Normally, we want to generate the same thing whether sending over the air * or putting it into a file for other testing. * (There is an important exception. gen_packets can introduce random noise.) * In this case, we want more aggressive low pass filtering so it looks more like * what we see coming out of a receiver. * Specifically, single bits of the same state have considerably reduced amplitude * below several same values in a row. */ if (gen_packets) { filter_len_bits = 4; lpf_baud = 0.55; /* Lowpass cutoff freq as fraction of baud rate */ } samples_per_sec = audio_config_p->adev[a].samples_per_sec * UPSAMPLE; baud = audio_config_p->achan[chan].baud; ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)samples_per_sec ) + 0.5); ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)baud ) + 0.5); lp_filter_size[chan] = (int) (( filter_len_bits * (float)samples_per_sec / baud) + 0.5); if (lp_filter_size[chan] < 10) { text_color_set(DW_COLOR_DEBUG); dw_printf ("gen_tone_init: unexpected, chan %d, lp_filter_size %d < 10\n", chan, lp_filter_size[chan]); lp_filter_size[chan] = 10; } else if (lp_filter_size[chan] > MAX_FILTER_SIZE) { text_color_set(DW_COLOR_DEBUG); dw_printf ("gen_tone_init: unexpected, chan %d, lp_filter_size %d > %d\n", chan, lp_filter_size[chan], MAX_FILTER_SIZE); lp_filter_size[chan] = MAX_FILTER_SIZE; } fc = (float)baud * lpf_baud / (float)samples_per_sec; //text_color_set(DW_COLOR_DEBUG); //dw_printf ("gen_tone_init: chan %d, call gen_lowpass(fc=%.2f, , size=%d, )\n", chan, fc, lp_filter_size[chan]); gen_lowpass (fc, lp_filter[chan], lp_filter_size[chan], BP_WINDOW_HAMMING); } } return (0); } /* end gen_tone_init */ /*------------------------------------------------------------------- * * Name: tone_gen_put_bit * * Purpose: Generate tone of proper duration for one data bit. * * Inputs: chan - Audio channel, 0 = first. * * dat - 0 for f1, 1 for f2. * * -1 inserts half bit to test data * recovery PLL. * * Assumption: fp is open to a file for write. * * Version 1.4: Attempt to implement 2400 and 4800 bps PSK modes. * *--------------------------------------------------------------------*/ static const int gray2phase_v26[4] = {0, 1, 3, 2}; static const int gray2phase_v27[8] = {1, 0, 2, 3, 6, 7, 5, 4}; void tone_gen_put_bit (int chan, int dat) { int a = ACHAN2ADEV(chan); /* device for channel. */ assert (save_audio_config_p != NULL); assert (save_audio_config_p->achan[chan].valid); if (dat < 0) { /* Hack to test receive PLL recovery. */ bit_len_acc[chan] -= ticks_per_bit[chan]; dat = 0; } if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK) { int dibit; int symbol; dat &= 1; // Keep only LSB to be extra safe. if ( ! (bit_count[chan] & 1)) { save_bit[chan] = dat; bit_count[chan]++; return; } // All zero bits should give us steady 1800 Hz. // All one bits should flip phase by 180 degrees each time. dibit = (save_bit[chan] << 1) | dat; symbol = gray2phase_v26[dibit]; tone_phase[chan] += symbol * PHASE_SHIFT_90; bit_count[chan]++; } if (save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) { int tribit; int symbol; dat &= 1; // Keep only LSB to be extra safe. if (bit_count[chan] < 2) { save_bit[chan] = (save_bit[chan] << 1) | dat; bit_count[chan]++; return; } // The bit pattern 001 should give us steady 1800 Hz. // All one bits should flip phase by 180 degrees each time. tribit = (save_bit[chan] << 1) | dat; symbol = gray2phase_v27[tribit]; tone_phase[chan] += symbol * PHASE_SHIFT_45; save_bit[chan] = 0; bit_count[chan] = 0; } if (save_audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE) { int x; x = (dat ^ (lfsr[chan] >> 16) ^ (lfsr[chan] >> 11)) & 1; lfsr[chan] = (lfsr[chan] << 1) | (x & 1); dat = x; } do { /* until enough audio samples for this symbol. */ int sam; float fsam; switch (save_audio_config_p->achan[chan].modem_type) { case MODEM_AFSK: #if DEBUG2 text_color_set(DW_COLOR_DEBUG); dw_printf ("tone_gen_put_bit %d AFSK\n", __LINE__); #endif tone_phase[chan] += dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan]; sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; gen_tone_put_sample (chan, a, sam); break; case MODEM_QPSK: case MODEM_8PSK: #if DEBUG2 text_color_set(DW_COLOR_DEBUG); dw_printf ("tone_gen_put_bit %d PSK\n", __LINE__); #endif tone_phase[chan] += f1_change_per_sample[chan]; sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; gen_tone_put_sample (chan, a, sam); break; case MODEM_BASEBAND: case MODEM_SCRAMBLE: #if DEBUG2 text_color_set(DW_COLOR_DEBUG); dw_printf ("tone_gen_put_bit %d SCR\n", __LINE__); #endif fsam = dat ? amp16bit : (-amp16bit); /* version 1.2 - added a low pass filter instead of square wave out. */ push_sample (fsam, raw[chan], lp_filter_size[chan]); resample[chan]++; if (resample[chan] >= UPSAMPLE) { sam = (int) convolve (raw[chan], lp_filter[chan], lp_filter_size[chan]); resample[chan] = 0; gen_tone_put_sample (chan, a, sam); } break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR: %s %d achan[%d].modem_type = %d\n", __FILE__, __LINE__, chan, save_audio_config_p->achan[chan].modem_type); exit (EXIT_FAILURE); } /* Enough for the bit time? */ bit_len_acc[chan] += ticks_per_sample[chan]; } while (bit_len_acc[chan] < ticks_per_bit[chan]); bit_len_acc[chan] -= ticks_per_bit[chan]; } void gen_tone_put_sample (int chan, int a, int sam) { /* Ship out an audio sample. */ /* 16 bit is signed, little endian, range -32768 .. +32767 */ /* 8 bit is unsigned, range 0 .. 255 */ assert (save_audio_config_p != NULL); assert (save_audio_config_p->adev[a].num_channels == 1 || save_audio_config_p->adev[a].num_channels == 2); assert (save_audio_config_p->adev[a].bits_per_sample == 16 || save_audio_config_p->adev[a].bits_per_sample == 8); // TODO: Should print message telling user to reduce output level. if (sam < -32767) sam = -32767; else if (sam > 32767) sam = 32767; if (save_audio_config_p->adev[a].num_channels == 1) { /* Mono */ if (save_audio_config_p->adev[a].bits_per_sample == 8) { audio_put (a, ((sam+32768) >> 8) & 0xff); } else { audio_put (a, sam & 0xff); audio_put (a, (sam >> 8) & 0xff); } } else { if (chan == ADEVFIRSTCHAN(a)) { /* Stereo, left channel. */ if (save_audio_config_p->adev[a].bits_per_sample == 8) { audio_put (a, ((sam+32768) >> 8) & 0xff); audio_put (a, 0); } else { audio_put (a, sam & 0xff); audio_put (a, (sam >> 8) & 0xff); audio_put (a, 0); audio_put (a, 0); } } else { /* Stereo, right channel. */ if (save_audio_config_p->adev[a].bits_per_sample == 8) { audio_put (a, 0); audio_put (a, ((sam+32768) >> 8) & 0xff); } else { audio_put (a, 0); audio_put (a, 0); audio_put (a, sam & 0xff); audio_put (a, (sam >> 8) & 0xff); } } } } /*------------------------------------------------------------------- * * Name: main * * Purpose: Quick test program for above. * * Description: Compile like this for unit test: * * gcc -Wall -DMAIN -o gen_tone_test gen_tone.c audio.c textcolor.c * * gcc -Wall -DMAIN -o gen_tone_test.exe gen_tone.c audio_win.c textcolor.c -lwinmm * *--------------------------------------------------------------------*/ #if MAIN int main () { int n; int chan1 = 0; int chan2 = 1; int r; struct audio_s my_audio_config; /* to sound card */ /* one channel. 2 times: one second of each tone. */ memset (&my_audio_config, 0, sizeof(my_audio_config)); strlcpy (my_audio_config.adev[0].adevice_in, DEFAULT_ADEVICE, sizeof(my_audio_config.adev[0].adevice_in)); strlcpy (my_audio_config.adev[0].adevice_out, DEFAULT_ADEVICE, sizeof(my_audio_config.adev[0].adevice_out)); audio_open (&my_audio_config); gen_tone_init (&my_audio_config, 100); for (r=0; r<2; r++) { for (n=0; n #include #include "utm.h" #include "mgrs.h" #include "usng.h" #include "error_string.h" // Convert error codes to text. // Note that the code is a bit mask so it is possible to have multiple messages. // Caller should probably provide space for a couple hundred characters to be safe. static const struct { long mask; char *msg; } utm_err [] = { { UTM_NO_ERROR, "No errors occurred in function" }, { UTM_LAT_ERROR, "Latitude outside of valid range (-80.5 to 84.5 degrees)" }, { UTM_LON_ERROR, "Longitude outside of valid range (-180 to 360 degrees)" }, { UTM_EASTING_ERROR, "Easting outside of valid range (100,000 to 900,000 meters)" }, { UTM_NORTHING_ERROR, "Northing outside of valid range (0 to 10,000,000 meters)" }, { UTM_ZONE_ERROR, "Zone outside of valid range (1 to 60)" }, { UTM_HEMISPHERE_ERROR, "Invalid hemisphere ('N' or 'S')" }, { UTM_ZONE_OVERRIDE_ERROR,"Zone outside of valid range (1 to 60) and within 1 of 'natural' zone" }, { UTM_A_ERROR, "Semi-major axis less than or equal to zero" }, { UTM_INV_F_ERROR, "Inverse flattening outside of valid range (250 to 350)" }, { 0, NULL } }; static const struct { long mask; char *msg; } mgrs_err [] = { { MGRS_NO_ERROR, "No errors occurred in function" }, { MGRS_LAT_ERROR, "Latitude outside of valid range (-90 to 90 degrees)" }, { MGRS_LON_ERROR, "Longitude outside of valid range (-180 to 360 degrees)" }, { MGRS_STRING_ERROR, "An MGRS string error: string too long, too short, or badly formed" }, { MGRS_PRECISION_ERROR, "The precision must be between 0 and 5 inclusive." }, { MGRS_A_ERROR, "Inverse flattening outside of valid range (250 to 350)" }, { MGRS_INV_F_ERROR, "Invalid hemisphere ('N' or 'S')" }, { MGRS_EASTING_ERROR, "Easting outside of valid range (100,000 to 900,000 meters for UTM) (0 to 4,000,000 meters for UPS)" }, { MGRS_NORTHING_ERROR, "Northing outside of valid range (0 to 10,000,000 meters for UTM) (0 to 4,000,000 meters for UPS)" }, { MGRS_ZONE_ERROR, "Zone outside of valid range (1 to 60)" }, { MGRS_HEMISPHERE_ERROR, "Invalid hemisphere ('N' or 'S')" }, { MGRS_LAT_WARNING, "Latitude warning ???" }, { 0, NULL } }; static const struct { long mask; char *msg; } usng_err [] = { { USNG_NO_ERROR, "No errors occurred in function" }, { USNG_LAT_ERROR, "Latitude outside of valid range (-90 to 90 degrees)" }, { USNG_LON_ERROR, "Longitude outside of valid range (-180 to 360 degrees)" }, { USNG_STRING_ERROR, "A USNG string error: string too long, too short, or badly formed" }, { USNG_PRECISION_ERROR, "The precision must be between 0 and 5 inclusive." }, { USNG_A_ERROR, "Inverse flattening outside of valid range (250 to 350)" }, { USNG_INV_F_ERROR, "Invalid hemisphere ('N' or 'S')" }, { USNG_EASTING_ERROR, "Easting outside of valid range (100,000 to 900,000 meters for UTM) (0 to 4,000,000 meters for UPS)" }, { USNG_NORTHING_ERROR, "Northing outside of valid range (0 to 10,000,000 meters for UTM) (0 to 4,000,000 meters for UPS)" }, { USNG_ZONE_ERROR, "Zone outside of valid range (1 to 60)" }, { USNG_HEMISPHERE_ERROR, "Invalid hemisphere ('N' or 'S')" }, { USNG_LAT_WARNING, "Latitude warning ???" }, { 0, NULL } }; void utm_error_string (long err, char *str) { int n; strcpy (str, ""); for (n = 1; utm_err[n].mask != 0; n++) { if (err & utm_err[n].mask) { if (strlen(str) > 0) strcat(str, "\n"); strcat (str, utm_err[n].msg); } } if (strlen(str) == 0) { strcpy (str, utm_err[0].msg); } } void mgrs_error_string (long err, char *str) { int n; strcpy (str, ""); for (n = 1; mgrs_err[n].mask != 0; n++) { if (err & mgrs_err[n].mask) { if (strlen(str) > 0) strcat(str, "\n"); strcat (str, mgrs_err[n].msg); } } if (strlen(str) == 0) { strcpy (str, mgrs_err[0].msg); } } void usng_error_string (long err, char *str) { int n; strcpy (str, ""); for (n = 1; usng_err[n].mask != 0; n++) { if (err & usng_err[n].mask) { if (strlen(str) > 0) strcat(str, "\n"); strcat (str, usng_err[n].msg); } } if (strlen(str) == 0) { strcpy (str, usng_err[0].msg); } } direwolf-1.5+dfsg/geotranz/error_string.h000066400000000000000000000002151347750676600206550ustar00rootroot00000000000000 void utm_error_string (long err, char *str); void mgrs_error_string (long err, char *str); void usng_error_string (long err, char *str); direwolf-1.5+dfsg/geotranz/mgrs.c000066400000000000000000001347501347750676600171150ustar00rootroot00000000000000/***************************************************************************/ /* RSC IDENTIFIER: MGRS * * ABSTRACT * * This component converts between geodetic coordinates (latitude and * longitude) and Military Grid Reference System (MGRS) coordinates. * * ERROR HANDLING * * This component checks parameters for valid values. If an invalid value * is found, the error code is combined with the current error code using * the bitwise or. This combining allows multiple error codes to be * returned. The possible error codes are: * * MGRS_NO_ERROR : No errors occurred in function * MGRS_LAT_ERROR : Latitude outside of valid range * (-90 to 90 degrees) * MGRS_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees) * MGRS_STR_ERROR : An MGRS string error: string too long, * too short, or badly formed * MGRS_PRECISION_ERROR : The precision must be between 0 and 5 * inclusive. * MGRS_A_ERROR : Semi-major axis less than or equal to zero * MGRS_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * MGRS_EASTING_ERROR : Easting outside of valid range * (100,000 to 900,000 meters for UTM) * (0 to 4,000,000 meters for UPS) * MGRS_NORTHING_ERROR : Northing outside of valid range * (0 to 10,000,000 meters for UTM) * (0 to 4,000,000 meters for UPS) * MGRS_ZONE_ERROR : Zone outside of valid range (1 to 60) * MGRS_HEMISPHERE_ERROR : Invalid hemisphere ('N' or 'S') * * REUSE NOTES * * MGRS is intended for reuse by any application that does conversions * between geodetic coordinates and MGRS coordinates. * * REFERENCES * * Further information on MGRS can be found in the Reuse Manual. * * MGRS originated from : U.S. Army Topographic Engineering Center * Geospatial Information Division * 7701 Telegraph Road * Alexandria, VA 22310-3864 * * LICENSES * * None apply to this component. * * RESTRICTIONS * * * ENVIRONMENT * * MGRS was tested and certified in the following environments: * * 1. Solaris 2.5 with GCC version 2.8.1 * 2. Windows 95 with MS Visual C++ version 6 * * MODIFICATIONS * * Date Description * ---- ----------- * 16-11-94 Original Code * 15-09-99 Reengineered upper layers * 02-05-03 Corrected latitude band bug in GRID_UTM * 08-20-03 Reengineered lower layers */ /***************************************************************************/ /* * INCLUDES */ #include #include #include #include #include "ups.h" #include "utm.h" #include "mgrs.h" /* * ctype.h - Standard C character handling library * math.h - Standard C math library * stdio.h - Standard C input/output library * string.h - Standard C string handling library * ups.h - Universal Polar Stereographic (UPS) projection * utm.h - Universal Transverse Mercator (UTM) projection * mgrs.h - function prototype error checking */ /***************************************************************************/ /* * GLOBAL DECLARATIONS */ #define DEG_TO_RAD 0.017453292519943295 /* PI/180 */ #define RAD_TO_DEG 57.29577951308232087 /* 180/PI */ #define LETTER_A 0 /* ARRAY INDEX FOR LETTER A */ #define LETTER_B 1 /* ARRAY INDEX FOR LETTER B */ #define LETTER_C 2 /* ARRAY INDEX FOR LETTER C */ #define LETTER_D 3 /* ARRAY INDEX FOR LETTER D */ #define LETTER_E 4 /* ARRAY INDEX FOR LETTER E */ #define LETTER_F 5 /* ARRAY INDEX FOR LETTER F */ #define LETTER_G 6 /* ARRAY INDEX FOR LETTER G */ #define LETTER_H 7 /* ARRAY INDEX FOR LETTER H */ #define LETTER_I 8 /* ARRAY INDEX FOR LETTER I */ #define LETTER_J 9 /* ARRAY INDEX FOR LETTER J */ #define LETTER_K 10 /* ARRAY INDEX FOR LETTER K */ #define LETTER_L 11 /* ARRAY INDEX FOR LETTER L */ #define LETTER_M 12 /* ARRAY INDEX FOR LETTER M */ #define LETTER_N 13 /* ARRAY INDEX FOR LETTER N */ #define LETTER_O 14 /* ARRAY INDEX FOR LETTER O */ #define LETTER_P 15 /* ARRAY INDEX FOR LETTER P */ #define LETTER_Q 16 /* ARRAY INDEX FOR LETTER Q */ #define LETTER_R 17 /* ARRAY INDEX FOR LETTER R */ #define LETTER_S 18 /* ARRAY INDEX FOR LETTER S */ #define LETTER_T 19 /* ARRAY INDEX FOR LETTER T */ #define LETTER_U 20 /* ARRAY INDEX FOR LETTER U */ #define LETTER_V 21 /* ARRAY INDEX FOR LETTER V */ #define LETTER_W 22 /* ARRAY INDEX FOR LETTER W */ #define LETTER_X 23 /* ARRAY INDEX FOR LETTER X */ #define LETTER_Y 24 /* ARRAY INDEX FOR LETTER Y */ #define LETTER_Z 25 /* ARRAY INDEX FOR LETTER Z */ #define MGRS_LETTERS 3 /* NUMBER OF LETTERS IN MGRS */ #define ONEHT 100000.e0 /* ONE HUNDRED THOUSAND */ #define TWOMIL 2000000.e0 /* TWO MILLION */ #define TRUE 1 /* CONSTANT VALUE FOR TRUE VALUE */ #define FALSE 0 /* CONSTANT VALUE FOR FALSE VALUE */ #define PI 3.14159265358979323e0 /* PI */ #define PI_OVER_2 (PI / 2.0e0) #define MIN_EASTING 100000 #define MAX_EASTING 900000 #define MIN_NORTHING 0 #define MAX_NORTHING 10000000 #define MAX_PRECISION 5 /* Maximum precision of easting & northing */ #define MIN_UTM_LAT ( (-80 * PI) / 180.0 ) /* -80 degrees in radians */ #define MAX_UTM_LAT ( (84 * PI) / 180.0 ) /* 84 degrees in radians */ #define MIN_EAST_NORTH 0 #define MAX_EAST_NORTH 4000000 /* Ellipsoid parameters, default to WGS 84 */ double MGRS_a = 6378137.0; /* Semi-major axis of ellipsoid in meters */ double MGRS_f = 1 / 298.257223563; /* Flattening of ellipsoid */ char MGRS_Ellipsoid_Code[3] = {'W','E',0}; /* * CLARKE_1866 : Ellipsoid code for CLARKE_1866 * CLARKE_1880 : Ellipsoid code for CLARKE_1880 * BESSEL_1841 : Ellipsoid code for BESSEL_1841 * BESSEL_1841_NAMIBIA : Ellipsoid code for BESSEL 1841 (NAMIBIA) */ const char* CLARKE_1866 = "CC"; const char* CLARKE_1880 = "CD"; const char* BESSEL_1841 = "BR"; const char* BESSEL_1841_NAMIBIA = "BN"; typedef struct Latitude_Band_Value { long letter; /* letter representing latitude band */ double min_northing; /* minimum northing for latitude band */ double north; /* upper latitude for latitude band */ double south; /* lower latitude for latitude band */ double northing_offset; /* latitude band northing offset */ } Latitude_Band; static const Latitude_Band Latitude_Band_Table[20] = {{LETTER_C, 1100000.0, -72.0, -80.5, 0.0}, {LETTER_D, 2000000.0, -64.0, -72.0, 2000000.0}, {LETTER_E, 2800000.0, -56.0, -64.0, 2000000.0}, {LETTER_F, 3700000.0, -48.0, -56.0, 2000000.0}, {LETTER_G, 4600000.0, -40.0, -48.0, 4000000.0}, {LETTER_H, 5500000.0, -32.0, -40.0, 4000000.0}, {LETTER_J, 6400000.0, -24.0, -32.0, 6000000.0}, {LETTER_K, 7300000.0, -16.0, -24.0, 6000000.0}, {LETTER_L, 8200000.0, -8.0, -16.0, 8000000.0}, {LETTER_M, 9100000.0, 0.0, -8.0, 8000000.0}, {LETTER_N, 0.0, 8.0, 0.0, 0.0}, {LETTER_P, 800000.0, 16.0, 8.0, 0.0}, {LETTER_Q, 1700000.0, 24.0, 16.0, 0.0}, {LETTER_R, 2600000.0, 32.0, 24.0, 2000000.0}, {LETTER_S, 3500000.0, 40.0, 32.0, 2000000.0}, {LETTER_T, 4400000.0, 48.0, 40.0, 4000000.0}, {LETTER_U, 5300000.0, 56.0, 48.0, 4000000.0}, {LETTER_V, 6200000.0, 64.0, 56.0, 6000000.0}, {LETTER_W, 7000000.0, 72.0, 64.0, 6000000.0}, {LETTER_X, 7900000.0, 84.5, 72.0, 6000000.0}}; typedef struct UPS_Constant_Value { long letter; /* letter representing latitude band */ long ltr2_low_value; /* 2nd letter range - low number */ long ltr2_high_value; /* 2nd letter range - high number */ long ltr3_high_value; /* 3rd letter range - high number (UPS) */ double false_easting; /* False easting based on 2nd letter */ double false_northing; /* False northing based on 3rd letter */ } UPS_Constant; static const UPS_Constant UPS_Constant_Table[4] = {{LETTER_A, LETTER_J, LETTER_Z, LETTER_Z, 800000.0, 800000.0}, {LETTER_B, LETTER_A, LETTER_R, LETTER_Z, 2000000.0, 800000.0}, {LETTER_Y, LETTER_J, LETTER_Z, LETTER_P, 800000.0, 1300000.0}, {LETTER_Z, LETTER_A, LETTER_J, LETTER_P, 2000000.0, 1300000.0}}; /***************************************************************************/ /* * FUNCTIONS */ long Get_Latitude_Band_Min_Northing(long letter, double* min_northing, double* northing_offset) /* * The function Get_Latitude_Band_Min_Northing receives a latitude band letter * and uses the Latitude_Band_Table to determine the minimum northing and northing offset * for that latitude band letter. * * letter : Latitude band letter (input) * min_northing : Minimum northing for that letter (output) */ { /* Get_Latitude_Band_Min_Northing */ long error_code = MGRS_NO_ERROR; if ((letter >= LETTER_C) && (letter <= LETTER_H)) { *min_northing = Latitude_Band_Table[letter-2].min_northing; *northing_offset = Latitude_Band_Table[letter-2].northing_offset; } else if ((letter >= LETTER_J) && (letter <= LETTER_N)) { *min_northing = Latitude_Band_Table[letter-3].min_northing; *northing_offset = Latitude_Band_Table[letter-3].northing_offset; } else if ((letter >= LETTER_P) && (letter <= LETTER_X)) { *min_northing = Latitude_Band_Table[letter-4].min_northing; *northing_offset = Latitude_Band_Table[letter-4].northing_offset; } else error_code |= MGRS_STRING_ERROR; return error_code; } /* Get_Latitude_Band_Min_Northing */ long Get_Latitude_Range(long letter, double* north, double* south) /* * The function Get_Latitude_Range receives a latitude band letter * and uses the Latitude_Band_Table to determine the latitude band * boundaries for that latitude band letter. * * letter : Latitude band letter (input) * north : Northern latitude boundary for that letter (output) * north : Southern latitude boundary for that letter (output) */ { /* Get_Latitude_Range */ long error_code = MGRS_NO_ERROR; if ((letter >= LETTER_C) && (letter <= LETTER_H)) { *north = Latitude_Band_Table[letter-2].north * DEG_TO_RAD; *south = Latitude_Band_Table[letter-2].south * DEG_TO_RAD; } else if ((letter >= LETTER_J) && (letter <= LETTER_N)) { *north = Latitude_Band_Table[letter-3].north * DEG_TO_RAD; *south = Latitude_Band_Table[letter-3].south * DEG_TO_RAD; } else if ((letter >= LETTER_P) && (letter <= LETTER_X)) { *north = Latitude_Band_Table[letter-4].north * DEG_TO_RAD; *south = Latitude_Band_Table[letter-4].south * DEG_TO_RAD; } else error_code |= MGRS_STRING_ERROR; return error_code; } /* Get_Latitude_Range */ long Get_Latitude_Letter(double latitude, int* letter) /* * The function Get_Latitude_Letter receives a latitude value * and uses the Latitude_Band_Table to determine the latitude band * letter for that latitude. * * latitude : Latitude (input) * letter : Latitude band letter (output) */ { /* Get_Latitude_Letter */ double temp = 0.0; long error_code = MGRS_NO_ERROR; double lat_deg = latitude * RAD_TO_DEG; if (lat_deg >= 72 && lat_deg < 84.5) *letter = LETTER_X; else if (lat_deg > -80.5 && lat_deg < 72) { temp = ((latitude + (80.0 * DEG_TO_RAD)) / (8.0 * DEG_TO_RAD)) + 1.0e-12; *letter = Latitude_Band_Table[(int)temp].letter; } else error_code |= MGRS_LAT_ERROR; return error_code; } /* Get_Latitude_Letter */ long Check_Zone(char* MGRS, long* zone_exists) /* * The function Check_Zone receives an MGRS coordinate string. * If a zone is given, TRUE is returned. Otherwise, FALSE * is returned. * * MGRS : MGRS coordinate string (input) * zone_exists : TRUE if a zone is given, * FALSE if a zone is not given (output) */ { /* Check_Zone */ int i = 0; int j = 0; int num_digits = 0; long error_code = MGRS_NO_ERROR; /* skip any leading blanks */ while (MGRS[i] == ' ') i++; j = i; while (isdigit(MGRS[i])) i++; num_digits = i - j; if (num_digits <= 2) if (num_digits > 0) *zone_exists = TRUE; else *zone_exists = FALSE; else error_code |= MGRS_STRING_ERROR; return error_code; } /* Check_Zone */ long Round_MGRS (double value) /* * The function Round_MGRS rounds the input value to the * nearest integer, using the standard engineering rule. * The rounded integer value is then returned. * * value : Value to be rounded (input) */ { /* Round_MGRS */ double ivalue; long ival; double fraction = modf (value, &ivalue); ival = (long)(ivalue); if ((fraction > 0.5) || ((fraction == 0.5) && (ival%2 == 1))) ival++; return (ival); } /* Round_MGRS */ long Make_MGRS_String (char* MGRS, long Zone, int Letters[MGRS_LETTERS], double Easting, double Northing, long Precision) /* * The function Make_MGRS_String constructs an MGRS string * from its component parts. * * MGRS : MGRS coordinate string (output) * Zone : UTM Zone (input) * Letters : MGRS coordinate string letters (input) * Easting : Easting value (input) * Northing : Northing value (input) * Precision : Precision level of MGRS string (input) */ { /* Make_MGRS_String */ long i; long j; double divisor; long east; long north; char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; long error_code = MGRS_NO_ERROR; i = 0; if (Zone) i = sprintf (MGRS+i,"%2.2ld",Zone); else strncpy(MGRS, " ", 2); // 2 spaces for (j=0;j<3;j++) MGRS[i++] = alphabet[Letters[j]]; divisor = pow (10.0, (5 - Precision)); Easting = fmod (Easting, 100000.0); if (Easting >= 99999.5) Easting = 99999.0; east = (long)(Easting/divisor); i += sprintf (MGRS+i, "%*.*ld", (int)Precision, (int)Precision, east); Northing = fmod (Northing, 100000.0); if (Northing >= 99999.5) Northing = 99999.0; north = (long)(Northing/divisor); i += sprintf (MGRS+i, "%*.*ld", (int)Precision, (int)Precision, north); return (error_code); } /* Make_MGRS_String */ long Break_MGRS_String (char* MGRS, long* Zone, long Letters[MGRS_LETTERS], double* Easting, double* Northing, long* Precision) /* * The function Break_MGRS_String breaks down an MGRS * coordinate string into its component parts. * * MGRS : MGRS coordinate string (input) * Zone : UTM Zone (output) * Letters : MGRS coordinate string letters (output) * Easting : Easting value (output) * Northing : Northing value (output) * Precision : Precision level of MGRS string (output) */ { /* Break_MGRS_String */ long num_digits; long num_letters; long i = 0; long j = 0; long error_code = MGRS_NO_ERROR; while (MGRS[i] == ' ') i++; /* skip any leading blanks */ j = i; while (isdigit(MGRS[i])) i++; num_digits = i - j; if (num_digits <= 2) if (num_digits > 0) { char zone_string[3]; /* get zone */ strncpy (zone_string, MGRS+j, 2); zone_string[2] = 0; sscanf (zone_string, "%ld", Zone); if ((*Zone < 1) || (*Zone > 60)) error_code |= MGRS_STRING_ERROR; } else *Zone = 0; else error_code |= MGRS_STRING_ERROR; j = i; while (isalpha(MGRS[i])) i++; num_letters = i - j; if (num_letters == 3) { /* get letters */ Letters[0] = (toupper(MGRS[j]) - (long)'A'); if ((Letters[0] == LETTER_I) || (Letters[0] == LETTER_O)) error_code |= MGRS_STRING_ERROR; Letters[1] = (toupper(MGRS[j+1]) - (long)'A'); if ((Letters[1] == LETTER_I) || (Letters[1] == LETTER_O)) error_code |= MGRS_STRING_ERROR; Letters[2] = (toupper(MGRS[j+2]) - (long)'A'); if ((Letters[2] == LETTER_I) || (Letters[2] == LETTER_O)) error_code |= MGRS_STRING_ERROR; } else error_code |= MGRS_STRING_ERROR; j = i; while (isdigit(MGRS[i])) i++; num_digits = i - j; if ((num_digits <= 10) && (num_digits%2 == 0)) { long n; char east_string[6]; char north_string[6]; long east; long north; double multiplier; /* get easting & northing */ n = num_digits/2; *Precision = n; if (n > 0) { strncpy (east_string, MGRS+j, n); east_string[n] = 0; sscanf (east_string, "%ld", &east); strncpy (north_string, MGRS+j+n, n); north_string[n] = 0; sscanf (north_string, "%ld", &north); multiplier = pow (10.0, 5 - n); *Easting = east * multiplier; *Northing = north * multiplier; } else { *Easting = 0.0; *Northing = 0.0; } } else error_code |= MGRS_STRING_ERROR; return (error_code); } /* Break_MGRS_String */ void Get_Grid_Values (long zone, long* ltr2_low_value, long* ltr2_high_value, double *pattern_offset) /* * The function getGridValues sets the letter range used for * the 2nd letter in the MGRS coordinate string, based on the set * number of the utm zone. It also sets the pattern offset using a * value of A for the second letter of the grid square, based on * the grid pattern and set number of the utm zone. * * zone : Zone number (input) * ltr2_low_value : 2nd letter low number (output) * ltr2_high_value : 2nd letter high number (output) * pattern_offset : Pattern offset (output) */ { /* BEGIN Get_Grid_Values */ long set_number; /* Set number (1-6) based on UTM zone number */ long aa_pattern; /* Pattern based on ellipsoid code */ set_number = zone % 6; if (!set_number) set_number = 6; if (!strcmp(MGRS_Ellipsoid_Code,CLARKE_1866) || !strcmp(MGRS_Ellipsoid_Code, CLARKE_1880) || !strcmp(MGRS_Ellipsoid_Code,BESSEL_1841) || !strcmp(MGRS_Ellipsoid_Code,BESSEL_1841_NAMIBIA)) aa_pattern = FALSE; else aa_pattern = TRUE; if ((set_number == 1) || (set_number == 4)) { *ltr2_low_value = LETTER_A; *ltr2_high_value = LETTER_H; } else if ((set_number == 2) || (set_number == 5)) { *ltr2_low_value = LETTER_J; *ltr2_high_value = LETTER_R; } else if ((set_number == 3) || (set_number == 6)) { *ltr2_low_value = LETTER_S; *ltr2_high_value = LETTER_Z; } /* False northing at A for second letter of grid square */ if (aa_pattern) { if ((set_number % 2) == 0) *pattern_offset = 500000.0; else *pattern_offset = 0.0; } else { if ((set_number % 2) == 0) *pattern_offset = 1500000.0; else *pattern_offset = 1000000.00; } } /* END OF Get_Grid_Values */ long UTM_To_MGRS (long Zone, char Hemisphere, double Longitude, double Latitude, double Easting, double Northing, long Precision, char *MGRS) /* * The function UTM_To_MGRS calculates an MGRS coordinate string * based on the zone, latitude, easting and northing. * * Zone : Zone number (input) * Hemisphere: Hemisphere (input) * Longitude : Longitude in radians (input) * Latitude : Latitude in radians (input) * Easting : Easting (input) * Northing : Northing (input) * Precision : Precision (input) * MGRS : MGRS coordinate string (output) */ { /* BEGIN UTM_To_MGRS */ double pattern_offset; /* Northing offset for 3rd letter */ double grid_easting; /* Easting used to derive 2nd letter of MGRS */ double grid_northing; /* Northing used to derive 3rd letter of MGRS */ long ltr2_low_value; /* 2nd letter range - low number */ long ltr2_high_value; /* 2nd letter range - high number */ int letters[MGRS_LETTERS]; /* Number location of 3 letters in alphabet */ double divisor; double rounded_easting; long temp_error_code = MGRS_NO_ERROR; long error_code = MGRS_NO_ERROR; divisor = pow (10.0, (5 - Precision)); rounded_easting = Round_MGRS (Easting/divisor) * divisor; /* Special check for rounding to (truncated) eastern edge of zone 31V */ if ((Zone == 31) && (((Latitude >= 56.0 * DEG_TO_RAD) && (Latitude < 64.0 * DEG_TO_RAD)) && ((Longitude >= 3.0 * DEG_TO_RAD) || (rounded_easting >= 500000.0)))) { /* Reconvert to UTM zone 32 */ Set_UTM_Parameters (MGRS_a, MGRS_f, 32); temp_error_code = Convert_Geodetic_To_UTM (Latitude, Longitude, &Zone, &Hemisphere, &Easting, &Northing); if(temp_error_code) { if(temp_error_code & UTM_LAT_ERROR) error_code |= MGRS_LAT_ERROR; if(temp_error_code & UTM_LON_ERROR) error_code |= MGRS_LON_ERROR; if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR) error_code |= MGRS_ZONE_ERROR; if(temp_error_code & UTM_EASTING_ERROR) error_code |= MGRS_EASTING_ERROR; if(temp_error_code & UTM_NORTHING_ERROR) error_code |= MGRS_NORTHING_ERROR; return error_code; } else /* Round easting value using new easting */ Easting = Round_MGRS (Easting/divisor) * divisor; } else Easting = rounded_easting; /* Round northing values */ Northing = Round_MGRS (Northing/divisor) * divisor; if( Latitude <= 0.0 && Northing == 1.0e7) { Latitude = 0.0; Northing = 0.0; } Get_Grid_Values(Zone, <r2_low_value, <r2_high_value, &pattern_offset); error_code = Get_Latitude_Letter(Latitude, &letters[0]); if (!error_code) { grid_northing = Northing; while (grid_northing >= TWOMIL) { grid_northing = grid_northing - TWOMIL; } grid_northing = grid_northing + pattern_offset; if(grid_northing >= TWOMIL) grid_northing = grid_northing - TWOMIL; letters[2] = (long)(grid_northing / ONEHT); if (letters[2] > LETTER_H) letters[2] = letters[2] + 1; if (letters[2] > LETTER_N) letters[2] = letters[2] + 1; grid_easting = Easting; if (((letters[0] == LETTER_V) && (Zone == 31)) && (grid_easting == 500000.0)) grid_easting = grid_easting - 1.0; /* SUBTRACT 1 METER */ letters[1] = ltr2_low_value + ((long)(grid_easting / ONEHT) -1); if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_N)) letters[1] = letters[1] + 1; Make_MGRS_String (MGRS, Zone, letters, grid_easting, Northing, Precision); } return error_code; } /* END UTM_To_MGRS */ long Set_MGRS_Parameters (double a, double f, char *Ellipsoid_Code) /* * The function SET_MGRS_PARAMETERS receives the ellipsoid parameters and sets * the corresponding state variables. If any errors occur, the error code(s) * are returned by the function, otherwise MGRS_NO_ERROR is returned. * * a : Semi-major axis of ellipsoid in meters (input) * f : Flattening of ellipsoid (input) * Ellipsoid_Code : 2-letter code for ellipsoid (input) */ { /* Set_MGRS_Parameters */ double inv_f = 1 / f; long Error_Code = MGRS_NO_ERROR; if (a <= 0.0) { /* Semi-major axis must be greater than zero */ Error_Code |= MGRS_A_ERROR; } if ((inv_f < 250) || (inv_f > 350)) { /* Inverse flattening must be between 250 and 350 */ Error_Code |= MGRS_INV_F_ERROR; } if (!Error_Code) { /* no errors */ MGRS_a = a; MGRS_f = f; strcpy (MGRS_Ellipsoid_Code, Ellipsoid_Code); } return (Error_Code); } /* Set_MGRS_Parameters */ void Get_MGRS_Parameters (double *a, double *f, char* Ellipsoid_Code) /* * The function Get_MGRS_Parameters returns the current ellipsoid * parameters. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) * Ellipsoid_Code : 2-letter code for ellipsoid (output) */ { /* Get_MGRS_Parameters */ *a = MGRS_a; *f = MGRS_f; strcpy (Ellipsoid_Code, MGRS_Ellipsoid_Code); return; } /* Get_MGRS_Parameters */ long Convert_Geodetic_To_MGRS (double Latitude, double Longitude, long Precision, char* MGRS) /* * The function Convert_Geodetic_To_MGRS converts Geodetic (latitude and * longitude) coordinates to an MGRS coordinate string, according to the * current ellipsoid parameters. If any errors occur, the error code(s) * are returned by the function, otherwise MGRS_NO_ERROR is returned. * * Latitude : Latitude in radians (input) * Longitude : Longitude in radians (input) * Precision : Precision level of MGRS string (input) * MGRS : MGRS coordinate string (output) * */ { /* Convert_Geodetic_To_MGRS */ long zone; char hemisphere; double easting; double northing; long temp_error_code = MGRS_NO_ERROR; long error_code = MGRS_NO_ERROR; if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2)) { /* Latitude out of range */ error_code |= MGRS_LAT_ERROR; } if ((Longitude < -PI) || (Longitude > (2*PI))) { /* Longitude out of range */ error_code |= MGRS_LON_ERROR; } if ((Precision < 0) || (Precision > MAX_PRECISION)) error_code |= MGRS_PRECISION_ERROR; if (!error_code) { if ((Latitude < MIN_UTM_LAT) || (Latitude > MAX_UTM_LAT)) { temp_error_code = Set_UPS_Parameters (MGRS_a, MGRS_f); if(!temp_error_code) { temp_error_code = Convert_Geodetic_To_UPS (Latitude, Longitude, &hemisphere, &easting, &northing); if(!temp_error_code) { error_code |= Convert_UPS_To_MGRS (hemisphere, easting, northing, Precision, MGRS); } else { if(temp_error_code & UPS_LAT_ERROR) error_code |= MGRS_LAT_ERROR; if(temp_error_code & UPS_LON_ERROR) error_code |= MGRS_LON_ERROR; } } else { if(temp_error_code & UPS_A_ERROR) error_code |= MGRS_A_ERROR; if(temp_error_code & UPS_INV_F_ERROR) error_code |= MGRS_INV_F_ERROR; } } else { temp_error_code = Set_UTM_Parameters (MGRS_a, MGRS_f, 0); if(!temp_error_code) { temp_error_code = Convert_Geodetic_To_UTM (Latitude, Longitude, &zone, &hemisphere, &easting, &northing); if(!temp_error_code) error_code |= UTM_To_MGRS (zone, hemisphere, Longitude, Latitude, easting, northing, Precision, MGRS); else { if(temp_error_code & UTM_LAT_ERROR) error_code |= MGRS_LAT_ERROR; if(temp_error_code & UTM_LON_ERROR) error_code |= MGRS_LON_ERROR; if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR) error_code |= MGRS_ZONE_ERROR; if(temp_error_code & UTM_EASTING_ERROR) error_code |= MGRS_EASTING_ERROR; if(temp_error_code & UTM_NORTHING_ERROR) error_code |= MGRS_NORTHING_ERROR; } } else { if(temp_error_code & UTM_A_ERROR) error_code |= MGRS_A_ERROR; if(temp_error_code & UTM_INV_F_ERROR) error_code |= MGRS_INV_F_ERROR; if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR) error_code |= MGRS_ZONE_ERROR; } } } return (error_code); } /* Convert_Geodetic_To_MGRS */ long Convert_MGRS_To_Geodetic (char* MGRS, double *Latitude, double *Longitude) /* * The function Convert_MGRS_To_Geodetic converts an MGRS coordinate string * to Geodetic (latitude and longitude) coordinates * according to the current ellipsoid parameters. If any errors occur, * the error code(s) are returned by the function, otherwise UTM_NO_ERROR * is returned. * * MGRS : MGRS coordinate string (input) * Latitude : Latitude in radians (output) * Longitude : Longitude in radians (output) * */ { /* Convert_MGRS_To_Geodetic */ long zone; char hemisphere = '?'; double easting; double northing; long zone_exists; long temp_error_code = MGRS_NO_ERROR; long error_code = MGRS_NO_ERROR; error_code = Check_Zone(MGRS, &zone_exists); if (!error_code) { if (zone_exists) { error_code |= Convert_MGRS_To_UTM (MGRS, &zone, &hemisphere, &easting, &northing); if(!error_code || (error_code & MGRS_LAT_WARNING)) { temp_error_code = Set_UTM_Parameters (MGRS_a, MGRS_f, 0); if(!temp_error_code) { temp_error_code = Convert_UTM_To_Geodetic (zone, hemisphere, easting, northing, Latitude, Longitude); if(temp_error_code) { if((temp_error_code & UTM_ZONE_ERROR) || (temp_error_code & UTM_HEMISPHERE_ERROR)) error_code |= MGRS_STRING_ERROR; if(temp_error_code & UTM_EASTING_ERROR) error_code |= MGRS_EASTING_ERROR; if(temp_error_code & UTM_NORTHING_ERROR) error_code |= MGRS_NORTHING_ERROR; } } else { if(temp_error_code & UTM_A_ERROR) error_code |= MGRS_A_ERROR; if(temp_error_code & UTM_INV_F_ERROR) error_code |= MGRS_INV_F_ERROR; if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR) error_code |= MGRS_ZONE_ERROR; } } } else { error_code |= Convert_MGRS_To_UPS (MGRS, &hemisphere, &easting, &northing); if(!error_code) { temp_error_code = Set_UPS_Parameters (MGRS_a, MGRS_f); if(!temp_error_code) { temp_error_code = Convert_UPS_To_Geodetic (hemisphere, easting, northing, Latitude, Longitude); if(temp_error_code) { if(temp_error_code & UPS_HEMISPHERE_ERROR) error_code |= MGRS_STRING_ERROR; if(temp_error_code & UPS_EASTING_ERROR) error_code |= MGRS_EASTING_ERROR; if(temp_error_code & UPS_LAT_ERROR) error_code |= MGRS_NORTHING_ERROR; } } else { if(temp_error_code & UPS_A_ERROR) error_code |= MGRS_A_ERROR; if(temp_error_code & UPS_INV_F_ERROR) error_code |= MGRS_INV_F_ERROR; } } } } return (error_code); } /* END OF Convert_MGRS_To_Geodetic */ long Convert_UTM_To_MGRS (long Zone, char Hemisphere, double Easting, double Northing, long Precision, char* MGRS) /* * The function Convert_UTM_To_MGRS converts UTM (zone, easting, and * northing) coordinates to an MGRS coordinate string, according to the * current ellipsoid parameters. If any errors occur, the error code(s) * are returned by the function, otherwise MGRS_NO_ERROR is returned. * * Zone : UTM zone (input) * Hemisphere : North or South hemisphere (input) * Easting : Easting (X) in meters (input) * Northing : Northing (Y) in meters (input) * Precision : Precision level of MGRS string (input) * MGRS : MGRS coordinate string (output) */ { /* Convert_UTM_To_MGRS */ double latitude; /* Latitude of UTM point */ double longitude; /* Longitude of UTM point */ long utm_error_code = MGRS_NO_ERROR; long error_code = MGRS_NO_ERROR; if ((Zone < 1) || (Zone > 60)) error_code |= MGRS_ZONE_ERROR; if ((Hemisphere != 'S') && (Hemisphere != 'N')) error_code |= MGRS_HEMISPHERE_ERROR; if ((Easting < MIN_EASTING) || (Easting > MAX_EASTING)) error_code |= MGRS_EASTING_ERROR; if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING)) error_code |= MGRS_NORTHING_ERROR; if ((Precision < 0) || (Precision > MAX_PRECISION)) error_code |= MGRS_PRECISION_ERROR; if (!error_code) { Set_UTM_Parameters (MGRS_a, MGRS_f, 0); utm_error_code = Convert_UTM_To_Geodetic (Zone, Hemisphere, Easting, Northing, &latitude, &longitude); if(utm_error_code) { if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR)) error_code |= MGRS_STRING_ERROR; if(utm_error_code & UTM_EASTING_ERROR) error_code |= MGRS_EASTING_ERROR; if(utm_error_code & UTM_NORTHING_ERROR) error_code |= MGRS_NORTHING_ERROR; } error_code = UTM_To_MGRS (Zone, Hemisphere, longitude, latitude, Easting, Northing, Precision, MGRS); } return (error_code); } /* Convert_UTM_To_MGRS */ long Convert_MGRS_To_UTM (char *MGRS, long *Zone, char *Hemisphere, double *Easting, double *Northing) /* * The function Convert_MGRS_To_UTM converts an MGRS coordinate string * to UTM projection (zone, hemisphere, easting and northing) coordinates * according to the current ellipsoid parameters. If any errors occur, * the error code(s) are returned by the function, otherwise UTM_NO_ERROR * is returned. * * MGRS : MGRS coordinate string (input) * Zone : UTM zone (output) * Hemisphere : North or South hemisphere (output) * Easting : Easting (X) in meters (output) * Northing : Northing (Y) in meters (output) */ { /* Convert_MGRS_To_UTM */ double min_northing; double northing_offset; long ltr2_low_value; long ltr2_high_value; double pattern_offset; double upper_lat_limit; /* North latitude limits based on 1st letter */ double lower_lat_limit; /* South latitude limits based on 1st letter */ double grid_easting; /* Easting for 100,000 meter grid square */ double grid_northing; /* Northing for 100,000 meter grid square */ long letters[MGRS_LETTERS]; long in_precision; double latitude = 0.0; double longitude = 0.0; double divisor = 1.0; long utm_error_code = MGRS_NO_ERROR; long error_code = MGRS_NO_ERROR; error_code = Break_MGRS_String (MGRS, Zone, letters, Easting, Northing, &in_precision); if (!*Zone) error_code |= MGRS_STRING_ERROR; else { if (!error_code) { if ((letters[0] == LETTER_X) && ((*Zone == 32) || (*Zone == 34) || (*Zone == 36))) error_code |= MGRS_STRING_ERROR; else { if (letters[0] < LETTER_N) *Hemisphere = 'S'; else *Hemisphere = 'N'; Get_Grid_Values(*Zone, <r2_low_value, <r2_high_value, &pattern_offset); /* Check that the second letter of the MGRS string is within * the range of valid second letter values * Also check that the third letter is valid */ if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) || (letters[2] > LETTER_V)) error_code |= MGRS_STRING_ERROR; if (!error_code) { double row_letter_northing = (double)(letters[2]) * ONEHT; grid_easting = (double)((letters[1]) - ltr2_low_value + 1) * ONEHT; if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_O)) grid_easting = grid_easting - ONEHT; if (letters[2] > LETTER_O) row_letter_northing = row_letter_northing - ONEHT; if (letters[2] > LETTER_I) row_letter_northing = row_letter_northing - ONEHT; if (row_letter_northing >= TWOMIL) row_letter_northing = row_letter_northing - TWOMIL; error_code = Get_Latitude_Band_Min_Northing(letters[0], &min_northing, &northing_offset); if (!error_code) { grid_northing = row_letter_northing - pattern_offset; if(grid_northing < 0) grid_northing += TWOMIL; grid_northing += northing_offset; if(grid_northing < min_northing) grid_northing += TWOMIL; *Easting = grid_easting + *Easting; *Northing = grid_northing + *Northing; /* check that point is within Zone Letter bounds */ utm_error_code = Set_UTM_Parameters(MGRS_a,MGRS_f,0); if (!utm_error_code) { utm_error_code = Convert_UTM_To_Geodetic(*Zone,*Hemisphere,*Easting,*Northing,&latitude,&longitude); if (!utm_error_code) { divisor = pow (10.0, in_precision); error_code = Get_Latitude_Range(letters[0], &upper_lat_limit, &lower_lat_limit); if (!error_code) { if (!(((lower_lat_limit - DEG_TO_RAD/divisor) <= latitude) && (latitude <= (upper_lat_limit + DEG_TO_RAD/divisor)))) error_code |= MGRS_LAT_WARNING; } } else { if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR)) error_code |= MGRS_STRING_ERROR; if(utm_error_code & UTM_EASTING_ERROR) error_code |= MGRS_EASTING_ERROR; if(utm_error_code & UTM_NORTHING_ERROR) error_code |= MGRS_NORTHING_ERROR; } } else { if(utm_error_code & UTM_A_ERROR) error_code |= MGRS_A_ERROR; if(utm_error_code & UTM_INV_F_ERROR) error_code |= MGRS_INV_F_ERROR; if(utm_error_code & UTM_ZONE_OVERRIDE_ERROR) error_code |= MGRS_ZONE_ERROR; } } } } } } return (error_code); } /* Convert_MGRS_To_UTM */ long Convert_UPS_To_MGRS (char Hemisphere, double Easting, double Northing, long Precision, char* MGRS) /* * The function Convert_UPS_To_MGRS converts UPS (hemisphere, easting, * and northing) coordinates to an MGRS coordinate string according to * the current ellipsoid parameters. If any errors occur, the error * code(s) are returned by the function, otherwise UPS_NO_ERROR is * returned. * * Hemisphere : Hemisphere either 'N' or 'S' (input) * Easting : Easting/X in meters (input) * Northing : Northing/Y in meters (input) * Precision : Precision level of MGRS string (input) * MGRS : MGRS coordinate string (output) */ { /* Convert_UPS_To_MGRS */ double false_easting; /* False easting for 2nd letter */ double false_northing; /* False northing for 3rd letter */ double grid_easting; /* Easting used to derive 2nd letter of MGRS */ double grid_northing; /* Northing used to derive 3rd letter of MGRS */ long ltr2_low_value; /* 2nd letter range - low number */ int letters[MGRS_LETTERS]; /* Number location of 3 letters in alphabet */ double divisor; int index = 0; long error_code = MGRS_NO_ERROR; if ((Hemisphere != 'N') && (Hemisphere != 'S')) error_code |= MGRS_HEMISPHERE_ERROR; if ((Easting < MIN_EAST_NORTH) || (Easting > MAX_EAST_NORTH)) error_code |= MGRS_EASTING_ERROR; if ((Northing < MIN_EAST_NORTH) || (Northing > MAX_EAST_NORTH)) error_code |= MGRS_NORTHING_ERROR; if ((Precision < 0) || (Precision > MAX_PRECISION)) error_code |= MGRS_PRECISION_ERROR; if (!error_code) { divisor = pow (10.0, (5 - Precision)); Easting = Round_MGRS (Easting/divisor) * divisor; Northing = Round_MGRS (Northing/divisor) * divisor; if (Hemisphere == 'N') { if (Easting >= TWOMIL) letters[0] = LETTER_Z; else letters[0] = LETTER_Y; index = letters[0] - 22; ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value; false_easting = UPS_Constant_Table[index].false_easting; false_northing = UPS_Constant_Table[index].false_northing; } else { if (Easting >= TWOMIL) letters[0] = LETTER_B; else letters[0] = LETTER_A; ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value; false_easting = UPS_Constant_Table[letters[0]].false_easting; false_northing = UPS_Constant_Table[letters[0]].false_northing; } grid_northing = Northing; grid_northing = grid_northing - false_northing; letters[2] = (long)(grid_northing / ONEHT); if (letters[2] > LETTER_H) letters[2] = letters[2] + 1; if (letters[2] > LETTER_N) letters[2] = letters[2] + 1; grid_easting = Easting; grid_easting = grid_easting - false_easting; letters[1] = ltr2_low_value + ((long)(grid_easting / ONEHT)); if (Easting < TWOMIL) { if (letters[1] > LETTER_L) letters[1] = letters[1] + 3; if (letters[1] > LETTER_U) letters[1] = letters[1] + 2; } else { if (letters[1] > LETTER_C) letters[1] = letters[1] + 2; if (letters[1] > LETTER_H) letters[1] = letters[1] + 1; if (letters[1] > LETTER_L) letters[1] = letters[1] + 3; } Make_MGRS_String (MGRS, 0, letters, Easting, Northing, Precision); } return (error_code); } /* Convert_UPS_To_MGRS */ long Convert_MGRS_To_UPS ( char *MGRS, char *Hemisphere, double *Easting, double *Northing) /* * The function Convert_MGRS_To_UPS converts an MGRS coordinate string * to UPS (hemisphere, easting, and northing) coordinates, according * to the current ellipsoid parameters. If any errors occur, the error * code(s) are returned by the function, otherwide UPS_NO_ERROR is returned. * * MGRS : MGRS coordinate string (input) * Hemisphere : Hemisphere either 'N' or 'S' (output) * Easting : Easting/X in meters (output) * Northing : Northing/Y in meters (output) */ { /* Convert_MGRS_To_UPS */ long ltr2_high_value; /* 2nd letter range - high number */ long ltr3_high_value; /* 3rd letter range - high number (UPS) */ long ltr2_low_value; /* 2nd letter range - low number */ double false_easting; /* False easting for 2nd letter */ double false_northing; /* False northing for 3rd letter */ double grid_easting; /* easting for 100,000 meter grid square */ double grid_northing; /* northing for 100,000 meter grid square */ long zone = 0; long letters[MGRS_LETTERS]; long in_precision = 0; int index = 0; long error_code = MGRS_NO_ERROR; error_code = Break_MGRS_String (MGRS, &zone, letters, Easting, Northing, &in_precision); if (zone) error_code |= MGRS_STRING_ERROR; else { if (!error_code) { if (letters[0] >= LETTER_Y) { *Hemisphere = 'N'; index = letters[0] - 22; ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value; ltr2_high_value = UPS_Constant_Table[index].ltr2_high_value; ltr3_high_value = UPS_Constant_Table[index].ltr3_high_value; false_easting = UPS_Constant_Table[index].false_easting; false_northing = UPS_Constant_Table[index].false_northing; } else { *Hemisphere = 'S'; ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value; ltr2_high_value = UPS_Constant_Table[letters[0]].ltr2_high_value; ltr3_high_value = UPS_Constant_Table[letters[0]].ltr3_high_value; false_easting = UPS_Constant_Table[letters[0]].false_easting; false_northing = UPS_Constant_Table[letters[0]].false_northing; } /* Check that the second letter of the MGRS string is within * the range of valid second letter values * Also check that the third letter is valid */ if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) || ((letters[1] == LETTER_D) || (letters[1] == LETTER_E) || (letters[1] == LETTER_M) || (letters[1] == LETTER_N) || (letters[1] == LETTER_V) || (letters[1] == LETTER_W)) || (letters[2] > ltr3_high_value)) error_code |= MGRS_STRING_ERROR; if (!error_code) { grid_northing = (double)letters[2] * ONEHT + false_northing; if (letters[2] > LETTER_I) grid_northing = grid_northing - ONEHT; if (letters[2] > LETTER_O) grid_northing = grid_northing - ONEHT; grid_easting = (double)((letters[1]) - ltr2_low_value) * ONEHT + false_easting; if (ltr2_low_value != LETTER_A) { if (letters[1] > LETTER_L) grid_easting = grid_easting - 300000.0; if (letters[1] > LETTER_U) grid_easting = grid_easting - 200000.0; } else { if (letters[1] > LETTER_C) grid_easting = grid_easting - 200000.0; if (letters[1] > LETTER_I) grid_easting = grid_easting - ONEHT; if (letters[1] > LETTER_L) grid_easting = grid_easting - 300000.0; } *Easting = grid_easting + *Easting; *Northing = grid_northing + *Northing; } } } return (error_code); } /* Convert_MGRS_To_UPS */ direwolf-1.5+dfsg/geotranz/mgrs.h000066400000000000000000000225231347750676600171140ustar00rootroot00000000000000#ifndef MGRS_H #define MGRS_H /***************************************************************************/ /* RSC IDENTIFIER: MGRS * * ABSTRACT * * This component converts between geodetic coordinates (latitude and * longitude) and Military Grid Reference System (MGRS) coordinates. * * ERROR HANDLING * * This component checks parameters for valid values. If an invalid value * is found, the error code is combined with the current error code using * the bitwise or. This combining allows multiple error codes to be * returned. The possible error codes are: * * MGRS_NO_ERROR : No errors occurred in function * MGRS_LAT_ERROR : Latitude outside of valid range * (-90 to 90 degrees) * MGRS_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees) * MGRS_STR_ERROR : An MGRS string error: string too long, * too short, or badly formed * MGRS_PRECISION_ERROR : The precision must be between 0 and 5 * inclusive. * MGRS_A_ERROR : Semi-major axis less than or equal to zero * MGRS_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * MGRS_EASTING_ERROR : Easting outside of valid range * (100,000 to 900,000 meters for UTM) * (0 to 4,000,000 meters for UPS) * MGRS_NORTHING_ERROR : Northing outside of valid range * (0 to 10,000,000 meters for UTM) * (0 to 4,000,000 meters for UPS) * MGRS_ZONE_ERROR : Zone outside of valid range (1 to 60) * MGRS_HEMISPHERE_ERROR : Invalid hemisphere ('N' or 'S') * * REUSE NOTES * * MGRS is intended for reuse by any application that does conversions * between geodetic coordinates and MGRS coordinates. * * REFERENCES * * Further information on MGRS can be found in the Reuse Manual. * * MGRS originated from : U.S. Army Topographic Engineering Center * Geospatial Information Division * 7701 Telegraph Road * Alexandria, VA 22310-3864 * * LICENSES * * None apply to this component. * * RESTRICTIONS * * * ENVIRONMENT * * MGRS was tested and certified in the following environments: * * 1. Solaris 2.5 with GCC version 2.8.1 * 2. Windows 95 with MS Visual C++ version 6 * * MODIFICATIONS * * Date Description * ---- ----------- * 16-11-94 Original Code * 15-09-99 Reengineered upper layers * */ /***************************************************************************/ /* * DEFINES */ #define MGRS_NO_ERROR 0x0000 #define MGRS_LAT_ERROR 0x0001 #define MGRS_LON_ERROR 0x0002 #define MGRS_STRING_ERROR 0x0004 #define MGRS_PRECISION_ERROR 0x0008 #define MGRS_A_ERROR 0x0010 #define MGRS_INV_F_ERROR 0x0020 #define MGRS_EASTING_ERROR 0x0040 #define MGRS_NORTHING_ERROR 0x0080 #define MGRS_ZONE_ERROR 0x0100 #define MGRS_HEMISPHERE_ERROR 0x0200 #define MGRS_LAT_WARNING 0x0400 /***************************************************************************/ /* * FUNCTION PROTOTYPES */ /* ensure proper linkage to c++ programs */ #ifdef __cplusplus extern "C" { #endif long Set_MGRS_Parameters(double a, double f, char *Ellipsoid_Code); /* * The function Set_MGRS_Parameters receives the ellipsoid parameters and sets * the corresponding state variables. If any errors occur, the error code(s) * are returned by the function, otherwise MGRS_NO_ERROR is returned. * * a : Semi-major axis of ellipsoid in meters (input) * f : Flattening of ellipsoid (input) * Ellipsoid_Code : 2-letter code for ellipsoid (input) */ void Get_MGRS_Parameters(double *a, double *f, char *Ellipsoid_Code); /* * The function Get_MGRS_Parameters returns the current ellipsoid * parameters. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) * Ellipsoid_Code : 2-letter code for ellipsoid (output) */ long Convert_Geodetic_To_MGRS (double Latitude, double Longitude, long Precision, char *MGRS); /* * The function Convert_Geodetic_To_MGRS converts geodetic (latitude and * longitude) coordinates to an MGRS coordinate string, according to the * current ellipsoid parameters. If any errors occur, the error code(s) * are returned by the function, otherwise MGRS_NO_ERROR is returned. * * Latitude : Latitude in radians (input) * Longitude : Longitude in radians (input) * Precision : Precision level of MGRS string (input) * MGRS : MGRS coordinate string (output) * */ long Convert_MGRS_To_Geodetic (char *MGRS, double *Latitude, double *Longitude); /* * This function converts an MGRS coordinate string to Geodetic (latitude * and longitude in radians) coordinates. If any errors occur, the error * code(s) are returned by the function, otherwise MGRS_NO_ERROR is returned. * * MGRS : MGRS coordinate string (input) * Latitude : Latitude in radians (output) * Longitude : Longitude in radians (output) * */ long Convert_UTM_To_MGRS (long Zone, char Hemisphere, double Easting, double Northing, long Precision, char *MGRS); /* * The function Convert_UTM_To_MGRS converts UTM (zone, easting, and * northing) coordinates to an MGRS coordinate string, according to the * current ellipsoid parameters. If any errors occur, the error code(s) * are returned by the function, otherwise MGRS_NO_ERROR is returned. * * Zone : UTM zone (input) * Hemisphere : North or South hemisphere (input) * Easting : Easting (X) in meters (input) * Northing : Northing (Y) in meters (input) * Precision : Precision level of MGRS string (input) * MGRS : MGRS coordinate string (output) */ long Convert_MGRS_To_UTM (char *MGRS, long *Zone, char *Hemisphere, double *Easting, double *Northing); /* * The function Convert_MGRS_To_UTM converts an MGRS coordinate string * to UTM projection (zone, hemisphere, easting and northing) coordinates * according to the current ellipsoid parameters. If any errors occur, * the error code(s) are returned by the function, otherwise UTM_NO_ERROR * is returned. * * MGRS : MGRS coordinate string (input) * Zone : UTM zone (output) * Hemisphere : North or South hemisphere (output) * Easting : Easting (X) in meters (output) * Northing : Northing (Y) in meters (output) */ long Convert_UPS_To_MGRS ( char Hemisphere, double Easting, double Northing, long Precision, char *MGRS); /* * The function Convert_UPS_To_MGRS converts UPS (hemisphere, easting, * and northing) coordinates to an MGRS coordinate string according to * the current ellipsoid parameters. If any errors occur, the error * code(s) are returned by the function, otherwise UPS_NO_ERROR is * returned. * * Hemisphere : Hemisphere either 'N' or 'S' (input) * Easting : Easting/X in meters (input) * Northing : Northing/Y in meters (input) * Precision : Precision level of MGRS string (input) * MGRS : MGRS coordinate string (output) */ long Convert_MGRS_To_UPS ( char *MGRS, char *Hemisphere, double *Easting, double *Northing); /* * The function Convert_MGRS_To_UPS converts an MGRS coordinate string * to UPS (hemisphere, easting, and northing) coordinates, according * to the current ellipsoid parameters. If any errors occur, the error * code(s) are returned by the function, otherwide UPS_NO_ERROR is returned. * * MGRS : MGRS coordinate string (input) * Hemisphere : Hemisphere either 'N' or 'S' (output) * Easting : Easting/X in meters (output) * Northing : Northing/Y in meters (output) */ #ifdef __cplusplus } #endif #endif /* MGRS_H */ direwolf-1.5+dfsg/geotranz/polarst.c000066400000000000000000000416701347750676600176270ustar00rootroot00000000000000/***************************************************************************/ /* RSC IDENTIFIER: POLAR STEREOGRAPHIC * * * ABSTRACT * * This component provides conversions between geodetic (latitude and * longitude) coordinates and Polar Stereographic (easting and northing) * coordinates. * * ERROR HANDLING * * This component checks parameters for valid values. If an invalid * value is found the error code is combined with the current error code * using the bitwise or. This combining allows multiple error codes to * be returned. The possible error codes are: * * POLAR_NO_ERROR : No errors occurred in function * POLAR_LAT_ERROR : Latitude outside of valid range * (-90 to 90 degrees) * POLAR_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees) * POLAR_ORIGIN_LAT_ERROR : Latitude of true scale outside of valid * range (-90 to 90 degrees) * POLAR_ORIGIN_LON_ERROR : Longitude down from pole outside of valid * range (-180 to 360 degrees) * POLAR_EASTING_ERROR : Easting outside of valid range, * depending on ellipsoid and * projection parameters * POLAR_NORTHING_ERROR : Northing outside of valid range, * depending on ellipsoid and * projection parameters * POLAR_RADIUS_ERROR : Coordinates too far from pole, * depending on ellipsoid and * projection parameters * POLAR_A_ERROR : Semi-major axis less than or equal to zero * POLAR_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * * * REUSE NOTES * * POLAR STEREOGRAPHIC is intended for reuse by any application that * performs a Polar Stereographic projection. * * * REFERENCES * * Further information on POLAR STEREOGRAPHIC can be found in the * Reuse Manual. * * * POLAR STEREOGRAPHIC originated from : * U.S. Army Topographic Engineering Center * Geospatial Information Division * 7701 Telegraph Road * Alexandria, VA 22310-3864 * * * LICENSES * * None apply to this component. * * * RESTRICTIONS * * POLAR STEREOGRAPHIC has no restrictions. * * * ENVIRONMENT * * POLAR STEREOGRAPHIC was tested and certified in the following * environments: * * 1. Solaris 2.5 with GCC, version 2.8.1 * 2. Window 95 with MS Visual C++, version 6 * * * MODIFICATIONS * * Date Description * ---- ----------- * 06-11-95 Original Code * 03-01-97 Original Code * * */ /************************************************************************/ /* * INCLUDES */ #include #include "polarst.h" /* * math.h - Standard C math library * polarst.h - Is for prototype error checking */ /************************************************************************/ /* DEFINES * */ #define PI 3.14159265358979323e0 /* PI */ #define PI_OVER_2 (PI / 2.0) #define TWO_PI (2.0 * PI) #define POLAR_POW(EsSin) pow((1.0 - EsSin) / (1.0 + EsSin), es_OVER_2) /************************************************************************/ /* GLOBAL DECLARATIONS * */ const double PI_Over_4 = (PI / 4.0); /* Ellipsoid Parameters, default to WGS 84 */ static double Polar_a = 6378137.0; /* Semi-major axis of ellipsoid in meters */ static double Polar_f = 1 / 298.257223563; /* Flattening of ellipsoid */ static double es = 0.08181919084262188000; /* Eccentricity of ellipsoid */ static double es_OVER_2 = .040909595421311; /* es / 2.0 */ static double Southern_Hemisphere = 0; /* Flag variable */ static double tc = 1.0; static double e4 = 1.0033565552493; static double Polar_a_mc = 6378137.0; /* Polar_a * mc */ static double two_Polar_a = 12756274.0; /* 2.0 * Polar_a */ /* Polar Stereographic projection Parameters */ static double Polar_Origin_Lat = ((PI * 90) / 180); /* Latitude of origin in radians */ static double Polar_Origin_Long = 0.0; /* Longitude of origin in radians */ static double Polar_False_Easting = 0.0; /* False easting in meters */ static double Polar_False_Northing = 0.0; /* False northing in meters */ /* Maximum variance for easting and northing values for WGS 84. */ static double Polar_Delta_Easting = 12713601.0; static double Polar_Delta_Northing = 12713601.0; /* These state variables are for optimization purposes. The only function * that should modify them is Set_Polar_Stereographic_Parameters. */ /************************************************************************/ /* FUNCTIONS * */ long Set_Polar_Stereographic_Parameters (double a, double f, double Latitude_of_True_Scale, double Longitude_Down_from_Pole, double False_Easting, double False_Northing) { /* BEGIN Set_Polar_Stereographic_Parameters */ /* * The function Set_Polar_Stereographic_Parameters receives the ellipsoid * parameters and Polar Stereograpic projection parameters as inputs, and * sets the corresponding state variables. If any errors occur, error * code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned. * * a : Semi-major axis of ellipsoid, in meters (input) * f : Flattening of ellipsoid (input) * Latitude_of_True_Scale : Latitude of true scale, in radians (input) * Longitude_Down_from_Pole : Longitude down from pole, in radians (input) * False_Easting : Easting (X) at center of projection, in meters (input) * False_Northing : Northing (Y) at center of projection, in meters (input) */ double es2; double slat, clat; double essin; double one_PLUS_es, one_MINUS_es; double pow_es; double temp, temp_northing = 0; double inv_f = 1 / f; double mc; // const double epsilon = 1.0e-2; long Error_Code = POLAR_NO_ERROR; if (a <= 0.0) { /* Semi-major axis must be greater than zero */ Error_Code |= POLAR_A_ERROR; } if ((inv_f < 250) || (inv_f > 350)) { /* Inverse flattening must be between 250 and 350 */ Error_Code |= POLAR_INV_F_ERROR; } if ((Latitude_of_True_Scale < -PI_OVER_2) || (Latitude_of_True_Scale > PI_OVER_2)) { /* Origin Latitude out of range */ Error_Code |= POLAR_ORIGIN_LAT_ERROR; } if ((Longitude_Down_from_Pole < -PI) || (Longitude_Down_from_Pole > TWO_PI)) { /* Origin Longitude out of range */ Error_Code |= POLAR_ORIGIN_LON_ERROR; } if (!Error_Code) { /* no errors */ Polar_a = a; two_Polar_a = 2.0 * Polar_a; Polar_f = f; if (Longitude_Down_from_Pole > PI) Longitude_Down_from_Pole -= TWO_PI; if (Latitude_of_True_Scale < 0) { Southern_Hemisphere = 1; Polar_Origin_Lat = -Latitude_of_True_Scale; Polar_Origin_Long = -Longitude_Down_from_Pole; } else { Southern_Hemisphere = 0; Polar_Origin_Lat = Latitude_of_True_Scale; Polar_Origin_Long = Longitude_Down_from_Pole; } Polar_False_Easting = False_Easting; Polar_False_Northing = False_Northing; es2 = 2 * Polar_f - Polar_f * Polar_f; es = sqrt(es2); es_OVER_2 = es / 2.0; if (fabs(fabs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10) { slat = sin(Polar_Origin_Lat); essin = es * slat; pow_es = POLAR_POW(essin); clat = cos(Polar_Origin_Lat); mc = clat / sqrt(1.0 - essin * essin); Polar_a_mc = Polar_a * mc; tc = tan(PI_Over_4 - Polar_Origin_Lat / 2.0) / pow_es; } else { one_PLUS_es = 1.0 + es; one_MINUS_es = 1.0 - es; e4 = sqrt(pow(one_PLUS_es, one_PLUS_es) * pow(one_MINUS_es, one_MINUS_es)); } /* Calculate Radius */ Convert_Geodetic_To_Polar_Stereographic(0, Longitude_Down_from_Pole, &temp, &temp_northing); Polar_Delta_Northing = temp_northing; if(Polar_False_Northing) Polar_Delta_Northing -= Polar_False_Northing; if (Polar_Delta_Northing < 0) Polar_Delta_Northing = -Polar_Delta_Northing; Polar_Delta_Northing *= 1.01; Polar_Delta_Easting = Polar_Delta_Northing; /* Polar_Delta_Easting = temp_northing; if(Polar_False_Easting) Polar_Delta_Easting -= Polar_False_Easting; if (Polar_Delta_Easting < 0) Polar_Delta_Easting = -Polar_Delta_Easting; Polar_Delta_Easting *= 1.01;*/ } return (Error_Code); } /* END OF Set_Polar_Stereographic_Parameters */ void Get_Polar_Stereographic_Parameters (double *a, double *f, double *Latitude_of_True_Scale, double *Longitude_Down_from_Pole, double *False_Easting, double *False_Northing) { /* BEGIN Get_Polar_Stereographic_Parameters */ /* * The function Get_Polar_Stereographic_Parameters returns the current * ellipsoid parameters and Polar projection parameters. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) * Latitude_of_True_Scale : Latitude of true scale, in radians (output) * Longitude_Down_from_Pole : Longitude down from pole, in radians (output) * False_Easting : Easting (X) at center of projection, in meters (output) * False_Northing : Northing (Y) at center of projection, in meters (output) */ *a = Polar_a; *f = Polar_f; *Latitude_of_True_Scale = Polar_Origin_Lat; *Longitude_Down_from_Pole = Polar_Origin_Long; *False_Easting = Polar_False_Easting; *False_Northing = Polar_False_Northing; return; } /* END OF Get_Polar_Stereographic_Parameters */ long Convert_Geodetic_To_Polar_Stereographic (double Latitude, double Longitude, double *Easting, double *Northing) { /* BEGIN Convert_Geodetic_To_Polar_Stereographic */ /* * The function Convert_Geodetic_To_Polar_Stereographic converts geodetic * coordinates (latitude and longitude) to Polar Stereographic coordinates * (easting and northing), according to the current ellipsoid * and Polar Stereographic projection parameters. If any errors occur, error * code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned. * * Latitude : Latitude, in radians (input) * Longitude : Longitude, in radians (input) * Easting : Easting (X), in meters (output) * Northing : Northing (Y), in meters (output) */ double dlam; double slat; double essin; double t; double rho; double pow_es; long Error_Code = POLAR_NO_ERROR; if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2)) { /* Latitude out of range */ Error_Code |= POLAR_LAT_ERROR; } if ((Latitude < 0) && (Southern_Hemisphere == 0)) { /* Latitude and Origin Latitude in different hemispheres */ Error_Code |= POLAR_LAT_ERROR; } if ((Latitude > 0) && (Southern_Hemisphere == 1)) { /* Latitude and Origin Latitude in different hemispheres */ Error_Code |= POLAR_LAT_ERROR; } if ((Longitude < -PI) || (Longitude > TWO_PI)) { /* Longitude out of range */ Error_Code |= POLAR_LON_ERROR; } if (!Error_Code) { /* no errors */ if (fabs(fabs(Latitude) - PI_OVER_2) < 1.0e-10) { *Easting = Polar_False_Easting; *Northing = Polar_False_Northing; } else { if (Southern_Hemisphere != 0) { Longitude *= -1.0; Latitude *= -1.0; } dlam = Longitude - Polar_Origin_Long; if (dlam > PI) { dlam -= TWO_PI; } if (dlam < -PI) { dlam += TWO_PI; } slat = sin(Latitude); essin = es * slat; pow_es = POLAR_POW(essin); t = tan(PI_Over_4 - Latitude / 2.0) / pow_es; if (fabs(fabs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10) rho = Polar_a_mc * t / tc; else rho = two_Polar_a * t / e4; if (Southern_Hemisphere != 0) { *Easting = -(rho * sin(dlam) - Polar_False_Easting); // *Easting *= -1.0; *Northing = rho * cos(dlam) + Polar_False_Northing; } else { *Easting = rho * sin(dlam) + Polar_False_Easting; *Northing = -rho * cos(dlam) + Polar_False_Northing; } } } return (Error_Code); } /* END OF Convert_Geodetic_To_Polar_Stereographic */ long Convert_Polar_Stereographic_To_Geodetic (double Easting, double Northing, double *Latitude, double *Longitude) { /* BEGIN Convert_Polar_Stereographic_To_Geodetic */ /* * The function Convert_Polar_Stereographic_To_Geodetic converts Polar * Stereographic coordinates (easting and northing) to geodetic * coordinates (latitude and longitude) according to the current ellipsoid * and Polar Stereographic projection Parameters. If any errors occur, the * code(s) are returned by the function, otherwise POLAR_NO_ERROR * is returned. * * Easting : Easting (X), in meters (input) * Northing : Northing (Y), in meters (input) * Latitude : Latitude, in radians (output) * Longitude : Longitude, in radians (output) * */ double dy = 0, dx = 0; double rho = 0; double t; double PHI, sin_PHI; double tempPHI = 0.0; double essin; double pow_es; double delta_radius; long Error_Code = POLAR_NO_ERROR; double min_easting = Polar_False_Easting - Polar_Delta_Easting; double max_easting = Polar_False_Easting + Polar_Delta_Easting; double min_northing = Polar_False_Northing - Polar_Delta_Northing; double max_northing = Polar_False_Northing + Polar_Delta_Northing; if (Easting > max_easting || Easting < min_easting) { /* Easting out of range */ Error_Code |= POLAR_EASTING_ERROR; } if (Northing > max_northing || Northing < min_northing) { /* Northing out of range */ Error_Code |= POLAR_NORTHING_ERROR; } if (!Error_Code) { dy = Northing - Polar_False_Northing; dx = Easting - Polar_False_Easting; /* Radius of point with origin of false easting, false northing */ rho = sqrt(dx * dx + dy * dy); delta_radius = sqrt(Polar_Delta_Easting * Polar_Delta_Easting + Polar_Delta_Northing * Polar_Delta_Northing); if(rho > delta_radius) { /* Point is outside of projection area */ Error_Code |= POLAR_RADIUS_ERROR; } if (!Error_Code) { /* no errors */ if ((dy == 0.0) && (dx == 0.0)) { *Latitude = PI_OVER_2; *Longitude = Polar_Origin_Long; } else { if (Southern_Hemisphere != 0) { dy *= -1.0; dx *= -1.0; } if (fabs(fabs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10) t = rho * tc / (Polar_a_mc); else t = rho * e4 / (two_Polar_a); PHI = PI_OVER_2 - 2.0 * atan(t); while (fabs(PHI - tempPHI) > 1.0e-10) { tempPHI = PHI; sin_PHI = sin(PHI); essin = es * sin_PHI; pow_es = POLAR_POW(essin); PHI = PI_OVER_2 - 2.0 * atan(t * pow_es); } *Latitude = PHI; *Longitude = Polar_Origin_Long + atan2(dx, -dy); if (*Longitude > PI) *Longitude -= TWO_PI; else if (*Longitude < -PI) *Longitude += TWO_PI; if (*Latitude > PI_OVER_2) /* force distorted values to 90, -90 degrees */ *Latitude = PI_OVER_2; else if (*Latitude < -PI_OVER_2) *Latitude = -PI_OVER_2; if (*Longitude > PI) /* force distorted values to 180, -180 degrees */ *Longitude = PI; else if (*Longitude < -PI) *Longitude = -PI; } if (Southern_Hemisphere != 0) { *Latitude *= -1.0; *Longitude *= -1.0; } } } return (Error_Code); } /* END OF Convert_Polar_Stereographic_To_Geodetic */ direwolf-1.5+dfsg/geotranz/polarst.h000066400000000000000000000176201347750676600176320ustar00rootroot00000000000000#ifndef POLARST_H #define POLARST_H /***************************************************************************/ /* RSC IDENTIFIER: POLAR STEREOGRAPHIC * * * ABSTRACT * * This component provides conversions between geodetic (latitude and * longitude) coordinates and Polar Stereographic (easting and northing) * coordinates. * * ERROR HANDLING * * This component checks parameters for valid values. If an invalid * value is found the error code is combined with the current error code * using the bitwise or. This combining allows multiple error codes to * be returned. The possible error codes are: * * POLAR_NO_ERROR : No errors occurred in function * POLAR_LAT_ERROR : Latitude outside of valid range * (-90 to 90 degrees) * POLAR_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees) * POLAR_ORIGIN_LAT_ERROR : Latitude of true scale outside of valid * range (-90 to 90 degrees) * POLAR_ORIGIN_LON_ERROR : Longitude down from pole outside of valid * range (-180 to 360 degrees) * POLAR_EASTING_ERROR : Easting outside of valid range, * depending on ellipsoid and * projection parameters * POLAR_NORTHING_ERROR : Northing outside of valid range, * depending on ellipsoid and * projection parameters * POLAR_RADIUS_ERROR : Coordinates too far from pole, * depending on ellipsoid and * projection parameters * POLAR_A_ERROR : Semi-major axis less than or equal to zero * POLAR_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * * * REUSE NOTES * * POLAR STEREOGRAPHIC is intended for reuse by any application that * performs a Polar Stereographic projection. * * * REFERENCES * * Further information on POLAR STEREOGRAPHIC can be found in the * Reuse Manual. * * * POLAR STEREOGRAPHIC originated from : * U.S. Army Topographic Engineering Center * Geospatial Information Division * 7701 Telegraph Road * Alexandria, VA 22310-3864 * * * LICENSES * * None apply to this component. * * * RESTRICTIONS * * POLAR STEREOGRAPHIC has no restrictions. * * * ENVIRONMENT * * POLAR STEREOGRAPHIC was tested and certified in the following * environments: * * 1. Solaris 2.5 with GCC, version 2.8.1 * 2. Window 95 with MS Visual C++, version 6 * * * MODIFICATIONS * * Date Description * ---- ----------- * 06-11-95 Original Code * 03-01-97 Original Code * * */ /**********************************************************************/ /* * DEFINES */ #define POLAR_NO_ERROR 0x0000 #define POLAR_LAT_ERROR 0x0001 #define POLAR_LON_ERROR 0x0002 #define POLAR_ORIGIN_LAT_ERROR 0x0004 #define POLAR_ORIGIN_LON_ERROR 0x0008 #define POLAR_EASTING_ERROR 0x0010 #define POLAR_NORTHING_ERROR 0x0020 #define POLAR_A_ERROR 0x0040 #define POLAR_INV_F_ERROR 0x0080 #define POLAR_RADIUS_ERROR 0x0100 /**********************************************************************/ /* * FUNCTION PROTOTYPES */ /* ensure proper linkage to c++ programs */ #ifdef __cplusplus extern "C" { #endif long Set_Polar_Stereographic_Parameters (double a, double f, double Latitude_of_True_Scale, double Longitude_Down_from_Pole, double False_Easting, double False_Northing); /* * The function Set_Polar_Stereographic_Parameters receives the ellipsoid * parameters and Polar Stereograpic projection parameters as inputs, and * sets the corresponding state variables. If any errors occur, error * code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned. * * a : Semi-major axis of ellipsoid, in meters (input) * f : Flattening of ellipsoid (input) * Latitude_of_True_Scale : Latitude of true scale, in radians (input) * Longitude_Down_from_Pole : Longitude down from pole, in radians (input) * False_Easting : Easting (X) at center of projection, in meters (input) * False_Northing : Northing (Y) at center of projection, in meters (input) */ void Get_Polar_Stereographic_Parameters (double *a, double *f, double *Latitude_of_True_Scale, double *Longitude_Down_from_Pole, double *False_Easting, double *False_Northing); /* * The function Get_Polar_Stereographic_Parameters returns the current * ellipsoid parameters and Polar projection parameters. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) * Latitude_of_True_Scale : Latitude of true scale, in radians (output) * Longitude_Down_from_Pole : Longitude down from pole, in radians (output) * False_Easting : Easting (X) at center of projection, in meters (output) * False_Northing : Northing (Y) at center of projection, in meters (output) */ long Convert_Geodetic_To_Polar_Stereographic (double Latitude, double Longitude, double *Easting, double *Northing); /* * The function Convert_Geodetic_To_Polar_Stereographic converts geodetic * coordinates (latitude and longitude) to Polar Stereographic coordinates * (easting and northing), according to the current ellipsoid * and Polar Stereographic projection parameters. If any errors occur, error * code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned. * * Latitude : Latitude, in radians (input) * Longitude : Longitude, in radians (input) * Easting : Easting (X), in meters (output) * Northing : Northing (Y), in meters (output) */ long Convert_Polar_Stereographic_To_Geodetic (double Easting, double Northing, double *Latitude, double *Longitude); /* * The function Convert_Polar_Stereographic_To_Geodetic converts Polar * Stereographic coordinates (easting and northing) to geodetic * coordinates (latitude and longitude) according to the current ellipsoid * and Polar Stereographic projection Parameters. If any errors occur, the * code(s) are returned by the function, otherwise POLAR_NO_ERROR * is returned. * * Easting : Easting (X), in meters (input) * Northing : Northing (Y), in meters (input) * Latitude : Latitude, in radians (output) * Longitude : Longitude, in radians (output) * */ #ifdef __cplusplus } #endif #endif /* POLARST_H */ direwolf-1.5+dfsg/geotranz/readme.txt000066400000000000000000000077631347750676600200020ustar00rootroot00000000000000GEOTRANS 2.4.2 The GEOTRANS 2.4.2 software was developed and tested on Windows XP, Solaris 7, Red Hat Linux Professional 9, and SuSE Linux 9.3 platforms, and should function correctly on more recent versions of those operating systems. There are eight different GEOTRANS 2.4.2 distribution files - four for Windows, in zip format, and four for UNIX/Linux, in tar/gzip format: win_user.zip - Windows end-user's package win_dev.zip - Windows developer's package master.zip - master copy (everything) in zip format mgrs.zip - MGRS & supporting projections in zip format unix_user.tgz - UNIX/Linux end-user's package unix_dev.tgz - UNIX/Linux developer's package master.tgz - master copy (everything) in tar/gzip format mgrs.tgz - MGRS & supporting projections in tar/gzip The Windows packages omit the UNIX/Linux-specific directories, while the UNIX/Linux packages omit the Windows-specific directories. The end-user packages contain only executables, DLLs or shared object libraries, documentation, and supporting data. The developer packages also include source code, as well as makefiles, MS Visual C++ workspace and project files, and everything else necessary to build the libraries and executables. The master packages can be considered to be cross-platform developer packages. They both contain the union of the Windows and UNIX/Linux developer packages. Only their format is different. The MGRS packages contain only the source code for the MGRS, UTM, UPS, Transverse Mercator, and Polar Stereographic conversion modules, and are intended for developers who only want to do MGRS conversions. Their content is identical. Only their format is different. You should only need to copy one of these packages, depending on your platform and your intended usage. GEOTRANS Terms of Use: 1. The GEOTRANS source code ("the software") is provided free of charge by the National Geospatial-Intelligence Agency (NGA) of the United States Department of Defense. Although NGA makes no copyright claim under Title 17 U.S.C., NGA claims copyrights in the source code under other legal regimes. NGA hereby grants to each user of the software a license to use and distribute the software, and develop derivative works. 2. NGA requests that products developed using the software credit the source of the software with the following statement, "The product was developed using GEOTRANS, a product of the National Geospatial-Intelligence Agency (NGA) and U.S. Army Engineering Research and Development Center." Do not use the name GEOTRANS for any derived work. 3. Warranty Disclaimer: The software was developed to meet only the internal requirements of the National Geospatial-Intelligence Agency (NGA). The software is provided "as is," and no warranty, express or implied, including but not limited to the implied warranties of merchantability and fitness for particular purpose or arising by statute or otherwise in law or from a course of dealing or usage in trade, is made by NGA as to the accuracy and functioning of the software. 4. NGA and its personnel are not required to provide technical support or general assistance with respect to public use of the software. Government customers may contact NGA. 5. Neither NGA nor its personnel will be liable for any claims, losses, or damages arising from or connected with the use of the software. The user agrees to hold harmless the United States National Geospatial-Intelligence Agency (NGA). The user's sole and exclusive remedy is to stop using the software. 6. Please be advised that pursuant to the United States Code, 10 U.S.C. 425, the name of the National Geospatial-Intelligence Agency, the initials "NGA", the seal of the National Geospatial-Intelligence Agency, or any colorable imitation thereof shall not be used to imply approval, endorsement, or authorization of a product without prior written permission from United States Secretary of Defense. Do not create the impression that NGA, the Secretary of Defense or the Director of National Intelligence has endorsed any product derived from GEOTRANS. direwolf-1.5+dfsg/geotranz/releasenotes.txt000066400000000000000000000635301347750676600212300ustar00rootroot00000000000000GEOTRANS Release Notes Release 2.0.2 - November 1999 1. The datum parameter file 3_param.dat was changed to correct an error in the latitude bounds for the NAD 27 Canada datum. 2. The MGRS module was changed to make the final latitude check on MGRS to UTM conversions sensitive to the precision of the input MGRS coordinate string. The lower the input precision, the more "slop" is allowed in the final check on the latitude zone letter. This is to handle an issue raised by some F-16 pilots, who truncate MGRS strings that they receive from the Army. This truncation can put them on the wrong side of a latitude zone boundary, causing the truncated MGRS string to be considered invalid. The correction causes truncated strings to be considered valid if any part of the square which they denote lies within the latitude zone specified by the third letter of the string. Release 2.0.3 - April 2000 1. Problems with the GEOTRANS file processing capability, including problems reading coordinate system/projection parameters, and problems with some coordinates being skipped. Note that spaces must separate individual coordinate values. 2. The Bonne projection module has been changed to return an error when the Origin Latitude parameter is set to zero. In the next release, the Sinusoidal projection will be used in this situation. 3. Reported errors in certain cases of conversions between geodetic and MGRS have been corrected. The error actually occurred in the formatting of the geodetic output. 4. The Equidistant Cylindrical projection parameter that was previously called Origin Latitude has been renamed to Standard Parallel, which more correctly reflects its role in the projection. The Origin Latitude for the Equidistant Cylindrical projection is always zero (i.e., the Equator). Error messages and documentation were updated accordingly. Note that the renaming of this parameter is the only change to the external interface of the GEOTRANS Engine in this release. 5. An error in the method selection logic for datum transformations, in the Datum module, has been corrected. This error caused the Molodensky method to be used when transforming between the two 7-parameter datums (EUR-7 and OGB-7) and WGS 84. 6. The datum parameter file 3_param.dat was changed to correct the names of several South American (S-42) datums. 7. A leftover debug printf statement in the Geoid module was removed. 8. Several multiple declaration errors that prevented the Motif GUI source code from being compiled successfully using the Gnu g++ compiler were corrected. Additional comments were added to the make file for the GEOTRANS application to facilitate switching between Sun and Gnu compilers. 9. Comments were also added to the make file for the GEOTRANS application concerning locating the libXpm shared object library. This library, which supports the use of X Window pixmaps, was moved to /usr/local/opt/xpm/lib under Solaris 2.6. 10. Documentation for the Local Cartesian module was corrected so that this coordinate system is no longer referred to as a projection. 11. The usage example in the GEOTRANS Engine Reuse Manual was corrected so that it now compiles successfully. Release 2.1 - June 2000 1. The geoid separation file has been converted from text to binary form and renamed to egm96.grd to better reflect its implementation of the Earth Gravity Model 1996. The new binary file is less than half the size of the text file (~4MB vs ~10MB), and is loaded much more quickly when GEOTRANS is started. 2. Inverse flattening is now used as a primary ellipsoid parameter, along with the semi-major axis, instead of the semi-minor axis. Previously, the inverse flattening was computed from the semi-major and semi-minor axes. This is a more correct approach and improves overall accuracy slightly. 3. User-defined datums and ellipsoids can now be deleted. A user-defined ellipsoid can only be deleted if it is not used by any user-defined datum. 4. Additional datum and ellipsoid parameter functions have been added to the external interface of the GEOTRANS Engine, for use by applications. 5. For Windows, a GEOTRANS dynamically linked library (DLL) is now provided which includes the GEOTRANS Engine and all of the DT&CC modules. A version of the GEOTRANS application, geotrans2d.exe, which uses the DLL is also provided. Similarly, for UNIX, a GEOTRANS shared object (.so) library is provided, along with a version of the GEOTRANS application, geotrans2d, which uses the shared object library. 6. The Bonne projection now defaults to the Sinusoidal projection when the origin latitude is zero. 7. A "No Height" option has been added for Geodetic coordinates, as an alternative to "Ellipsoid Height" and "Geoid/MSL Height". When "No Height" is selected on input, the contents of the Height field is ignored. When "No Height" is selected on output, no Height value is output. 8. Three new projections have been added: Azimuthal Equidistant, (Oblique) Gnomonic, and Oblique Mercator. The only difference between Gnomonic and Oblique Gnomonic is the value of the original latitude parameter. 9. The Windows and Motif GUIs have been updated to make the screen layouts more consistent. Bidirectional conversion between the upper and lower panels is now supported, using two Convert buttons (Upper-to-Lower and Lower-to-Upper). Error values are shown separately for each panel. 10. A bug in the MGRS module that occasionally caused 100km errors was corrected. Easting and northing values greater than 99999.5 (i.e., less then 1/2m from the eastern or northern boundary of a 100km square) were being set to zero, but not moved into the adjacent 100km square. These values are now rounded to 99999. 11. Documentation and on-line help has been updated to reflect all of the above enhancements. Release 2.2 - September 2000 1. The datum code for WGS 72 has been corrected from "WGD" to "WGC". 2. The default initial output coordinate system type has been changed from Mercator to UTM. 3. A bug in the Windows GUI that prevented degrees/minutes and decimal degrees formats from being selected has been corrected. 4. In the Windows GUI, the initial default value for inverse flattening and the associated label in the Create Ellipsoid dialog box have been corrected. 5. A bug in the Windows GUI that allowed multiple Geodetic Height type radio buttons to be selected in the File Processing dialog box has been corrected. 6. Diagrams showing MGRS grid zone, band, and 100,000m square layouts have been added to the Users' Guide and the on-line help. 7. An error in the implementation of Oblique Mercator has been corrected. 8. Four new projections have been added: Ney's (Modified Lambert Conformal Conic), Stereographic, British National Grid, and New Zealand Map Grid. 9. A prototype Java GUI has been added which runs on both Windows and UNIX platforms. It requires a Java run-time environment. To run it on a Windows platform, go to the /geotrans2/win95 directory and double click on the file geo_22.jar. To run it on a Solaris platform, cd to the /geotrans2/unix directory and enter the command: make -f javamake. It may be necessary to edit the javamake file to point to the location of the Java run-time environment on your system. Release 2.2.1 - June 2001 1. Fixed problem(s) in Local Cartesian conversions. 2. Corrected a rounding problem in MGRS coordinates when the output precision was set coarser than 1m (10m, 100m, etc.), and the point being converted rounded to the eastern or northern edge of a 100,000m square. An illegal MGRS string could be produced, with an odd number of digits including a "1" followed by one or more zeros. This was corrected by rounding the UTM easting or northing BEFORE determining the correct 100,000m square. 3. Corrected a very old error in the determination of the 100,000m square from a UPS easting in the easternmost part of the south polar zone. 4. Added more flexible support for delimiters in input files (commas, tabs, spaces) 5. Corrected a problem in reporting invalid northing errors in UTM. 6. Correct an example in the Polar Stereo reuse manual with Latitude of True Scale erroneously set to 0.0. 7. Removed an invalid Central Meridian line from the header of the Mollweide example input file. 8. Added a special F-16 Grid Reference System, a special version of MGRS. 9. Allowed 90% CE, 90% LE, and 90% SE accuracy values for input coordinates to be specified. These are used, along with datum transformation accuracy information, in deriving the output coordinate accuracy values. 10. Added a pull-down menu of coordinate sources, including GPS, maps, and digital geospatial data which can be selected to automatically set input accuracy values. 11. Improved the Java GUI to be fully functional, including support for file processsing and improvements in its appearance. Release 2.2.2 - February 2002 1. Added two new datums from Amendment 1 to NIMA TR8350.2 (Jan 2001) to 3_param.dat file: - Korean Geodetic Datum 1995 (KGS) - SIRGAS, South America (SIR) Corrected ellipsoid code errors in 3_param.dat file: - Ellipsoid used with TIL datum changed from EA to EB, - Ellipsoid used with IND-P datum changed from EA to EF. 2. Corrected an "off-by-one" error in the datum index validity check function in the GEOTRANS engine, which prevented the last datum in the pull-down list from being used. The Java GUI reported this error, but the Windows and Motif GUIs did not. 3. Processing of input coordinate files was made more flexible and forgiving: - Case sensitivity of keywords and name strings was eliminated. - Height values with geodetic coordinates were made optional, defaulting to zero. - Coordinate reference system names were made consistent with the GUI pull-down menus. - File processing error messages were improved. 4. A warnings count was added to the file processing GUI. 5. Geodetic height fields are grayed out and the No Height selection is forced whenever 3D conversion is not feasible. For 3D conversion to be feasible, Geodetic, Geocentric, or Local Cartesian must be selected on both panels. For file processing, the output Geodetic height field is grayed out and the No Height selection is forced whenever the input coordinate reference system is not a 3D system. 6. File header generation, using a modified version of the File Processing GUI, was added. (Java GUI Only) 7. Some results of the review of GEOTRANS by NIMA G&G were implemented: - In the User’s Guide (and on-line help), the description of the use of 3-step method, rather than Molodenski, in polar regions was reworded. - In the User's Guide (and on-line help), the description of how to specify Lambert Conformal Conic projection with one standard parallel was clarified. - UTM zone fields were enabled independent of the state of the Override buttons, with default values of zero, and the valid range of zone numbers (1-60) was added to the zone field labels. - In the Sources pull-down menus, the values for GPS PPS and GPS SPS were corrected to be the same, reflecting the shutting off of GPS selective availability (SA). - The words “Warning:” or “Error:”, as appropriate, were explicitly included in all messages output by the GUIs. Release 2.2.3 - February 2003 There were no changes made to the external interfaces of the GEOTRANS libraries. 1. The ellipsoid (ellipse.dat) and datum (3_param.dat) files were updated to correct several typos. Dates were added to all ellipsoid names. 2. A problem in the MGRS module (mgrs.c) was corrected. This problem occurred only when converting from geodetic to MGRS coordinates that round to the centerline of zone 31V. This zone is “cut in half”, such that its centerline is also its eastern boundary. Points that are rounded up (eastward) to this boundary are considered to lie in zone 32V. 3. An error in the Local Cartesian module (loccart.c) was corrected to properly take into account the longitude of the local Cartesian coordinate system origin in converting between geocentric and local Cartesian coordinates. 4. A possible problem in the Transverse Mercator module (tranmerc.c) concerning projections at the poles was investigated. Points at the poles are projected when the Transverse Mercator module is initialized in order to determine the range of valid inputs for the inverse projection. The tangent of the latitude is calculated, which should be infinite at the poles. Investigation determined that the tangent functions for both Windows and Solaris actually return very large values in this case, which result in the expected behavior. However, to avoid this problem on other platforms, the maximum valid latitude for the Transverse Mercator projection was reduced from 90 to 89.99 degrees. 5. A reported incompatibility between GEOTRANS 2.2.2 and version 4 of the Boeing Autometric EDGE Viewer on Windows platforms was investigated. This version of the EDGE Viewer includes the GEOTRANS 2.0.3 libraries. When the EDGE Viewer is installed, it sets the GEOTRANS environment variables to reference the directory C:/Program Files/Autometric/EDGEViewer/Data/GeoTrans. This overrides the default setting in the GEOTRANS application, causing it to look for its required data files in the EDGE Viewer directory. The incompatibility arises from the fact that the Earth Gravity Model 1996 geoid separation file was renamed and changed from text to binary form in GEOTRANS 2.1, which reduced its size from 10MB to 4MB. When the newer binary file is not found in the EDGE data directory, the GEOTRANS application fails to initialize successfully. Placing a copy of the binary geoid separation file (egm96.grd) into the EDGE Viewer data directory eliminates the problem. A recent update to the EDGE Viewer eliminates the problem by using a more recent version of the GEOTRANS libraries. Therefore no changes to the GEOTRANS software were necessary. 6. The source data accuracy values for GPS PPS and SPS modes were updated from 10m to 20m. 7. The Linear (i.e., vertical) Error (LE) and Spherical Error (SE) fields are now grayed out whenever a conversion is not three-dimensional. Release 2.2.4 - August 2003 There were no changes made to the external interfaces of the GEOTRANS libraries. 1. Minor changes were made to source code to eliminate all compilation warnings. These changes involved the Ellisoid module, the GEOTRANS engine, GEOTRANS application Windows GUI, and GEOTRANS application support source code file. 2. A bug in the MGRS module was corrected. This bug caused MGRS coordinates located in small triangular areas north of 64S latitude, and south of the 2,900,000 northing line, to fail to convert correctly to UTM. 3. The MGRS module was corrected so that the MGRS "AL" 100,000m square pattern is used with the Bessel 1841 Namibia (BN) ellipsoid. On-line help was corrected to be consistent with this change. 4. The MGRS module was updated to eliminate inconsistencies in the conversion of points located on UTM zone and MGRS latitude band boundaries. 5. The MGRS module was reorganized internally to improve the clarity and efficiency of the source code. The external interface of the MGRS module was not changed. Release 2.2.4 - August 2003 There were no changes made to the external interfaces of the GEOTRANS libraries. 1. Minor changes were made to source code to eliminate all compilation warnings. These changes involved the Ellisoid module, the GEOTRANS engine, GEOTRANS application Windows GUI, and GEOTRANS application support source code file. 2. A bug in the MGRS module was corrected. This bug caused MGRS coordinates located in small triangular areas north of 64S latitude, and south of the 2,900,000 northing line, to fail to convert correctly to UTM. 3. The MGRS module was corrected so that the MGRS "AL" 100,000m square pattern is used with the Bessel 1841 Namibia (BN) ellipsoid. On-line help was corrected to be consistent with this change. 4. The MGRS module was updated to eliminate inconsistencies in the conversion of points located on UTM zone and MGRS latitude band boundaries. 5. The MGRS module was reorganized internally to improve the clarity and efficiency of the source code. The external interface of the MGRS module was not changed. Release 2.2.4 - August 2003 There were no changes made to the external interfaces of the GEOTRANS libraries. 1. Minor changes were made to source code to eliminate all compilation warnings. These changes involved the Ellisoid module, the GEOTRANS engine, GEOTRANS application Windows GUI, and GEOTRANS application support source code file. 2. A bug in the MGRS module was corrected. This bug caused MGRS coordinates located in small triangular areas north of 64S latitude, and south of the 2,900,000 northing line, to fail to convert correctly to UTM. 3. The MGRS module was corrected so that the MGRS "AL" 100,000m square pattern is used with the Bessel 1841 Namibia (BN) ellipsoid. On-line help was corrected to be consistent with this change. 4. The MGRS module was updated to eliminate inconsistencies in the conversion of points located on UTM zone and MGRS latitude band boundaries. 5. The MGRS module was reorganized internally to improve the clarity and efficiency of the source code. The external interface of the MGRS module was not changed. Release 2.2.5 - June 2004 There were no changes made to the external interfaces of the GEOTRANS libraries. 1. A minor correction was made in the Datum module, to correct an "off-by-one" error in the Valid_Datum function, which caused it to return a warning when the last 3-parameter datum was accessed. 2. A minor correction was made in the Round_DMS function, in the common application GUI support software, which caused incorrect geodetic coordinate values to be displayed when converting to degrees with a precision of .0001. 3 The 3-parameter datum file, 3_param.dat, was updated to reflect new parameter values for the MID (MIDWAY ASTRO 1961, Midway Is.) datum, which went into effect in June 2003. The longitude limits for the NAS-U (North American 1927, Greenland), the DAL (Dabola, Guinea) and the TRN (Astro Tern Island (Frig) 1961 datums were also corrected. Release 2.2.6 - June 2005 1. A minor correction was made in the Get_Geoid_Height function in the GEOID module, which does bilinear interpolation of EGM96 geoid separation values. The error caused the specified point to be shifted in longitude, mirrored around the east-west midline of the 15-minute grid cell that contains it. 2. The 3-parameter datum file, 3_param.dat, was updated to correct the latitude and longitude limits for the DID (Deception Island), EUR-S (European 1950, Israel and Iraq), and KEG (Kerguelen Island) datums. 3. A minor correction was made in the Convert_Orthographic_to_Geodetic function in the ORTHOGR module to use the two-argument arctangent function (atan2) rather than the single-argument arctangent function (atan). This avoids sign errors in results near the poles. 4. A minor correction was made in the Convert_Albers_to_Geodetic function in the ALBERS module to avoid infinite loops when the iterative solution for latitude fails to converge. After 30 iterations, the function now returns an error status. 5. Support was added for the Lambert Conformal Conic projection with one standard parallel. A new LAMBERT_1 module was added, and the LAMBERT_2 module was renamed (from LAMBERT) and reengineered to use the new LAMBERT_1 module. Backward compatibility was maintained at the DT&CC and GEOTRANS Engine levels. However, all existing functions that include "Lambert", rather than "Lambert1" or "Lambert2", in their names are now considered to be deprecated, and will be removed in a future update. 6. The GEOTRANS application GUI was enhanced to help users avoid incompatible combinations of coordinate systems and datums by color coding the conversion buttons. Red indicates that the selected coordinate systems and datums are not compatible with one another, and that an error message will result from any attempted conversion operation. Yellow indicates that the selected datums have disjoint areas of validity, adn that a warning message will result from any attempted conversion operation. 7. A correction was made to the Geodetic_Shift_WGS72_To_WGS84 and Geodetic_Shift_WGS72_To_WGS84 functions in the DATUM module to wrap longitude values across the 180 degree line and wrap latitude values over the poles. 8. File processing examples in the online help, and in the /examples subdirectory, were improved to use more realistic coordinates, and additional examples were added. 9. The GEOTRANS application GUI was enhanced to provide an option to display leading zeroes on output geodetic coordinate values, including degrees (three digits for longitude, two digits for latitude), minutes (two digits), and seconds (two digits). Release 2.3 - March 2006 1. The 3-parameter datum file, 3_param.dat, was updated to correct the latitude and longitude limits for DID (Deception Island), switching longitude order, and JOH (Johnston Island) datums. 2. An “off-by-one” error in datum indexing in the JNI interface was corrected. 3. Support for Red Hat Linux (Red Hat Professional 9.3 and later) and for SuSE Linux (SuSE Linux 9 and later) was added. 4. A reported potential error in file name string underflow/overflow in the Ellipsoid, Datum, and Geoid modules was corrected. 5. Support for multithreading was improved by adding mutual exclusion zones around code that opens and reads data files in the Ellipsoid, Datum, and Geoid modules. 6. Support for three additional gravity-related height models was added, based on requirements from the US military services: a. EGM96 with variable grid spacing and natural spline interpolation b. EGM84 with 10 degree by 10 degree grid spacing and bilinear interpolation c. EGM84 with 10 degree by 10 degree grid spacing and natural spline interpolation These are in addition to EGM96 with 15-minute grid spacing and bilinear interpolation, which was previously supported. Release 2.4 - September 2006 1. The 3-parameter datum file, 3_param.dat, was updated to correct two spelling errors (Columbia -> Colombia, Phillipines -> Philippines). 2. The 3-parameter datum file, 3_param.dat, was updated to adjust the limits for several local datums in the 3-parameter datum file (ADI-E, CAC, CAI, COA, HJO, ING-A, KEG, LCF, NDS, SAE, SAN-A, SAN-C, VOI, and VOR). 3. All required ellipsoid, datum, and geoid data files to a /data subdirectory to eliminate the need for duplicate copies. 4. Error reporting was improved when required ellipsoid, datum, and/or geoid data files cannot be located at initialization. 5. In the Geoid module, problems were corrected in the interpolation of geoid separation values using a variable-resolution grid when converting to or from MSL-EGM96-VG-NS Height. 6. In the MGRS module, a problem was corrected in rounding up to the equator when converting to MGRS. 7. Support was added for the U.S. National Grid (USNG). 8. Support was added for the Global Area Reference System (USNG). Release 2.4.1 - March 2007 1. Corrected two minor errors (6cm and 1cm) in the values contained in the EGM84 geoid separation file. 2. Improved error handling and reporting in the Transverse Mercator, UTM, and MGRS modules at extreme northern and southern latitudes, for points that are more than 9 degrees, but less than 400km, from the central meridian. 3. Modified the US National Grid (USNG) module to truncate, rather than round, USNG coordinates as required by the USNG specification. 4. Corrected an error in the calculation of valid ranges for input easting and northing coordinates in the Mercator module, and several other map projection modules. This caused valid inputs to be rejected when extremely large (e.g., 20,000,000m) false easting or false northing values were specified for those map projections. 5. Improved error handling and reporting in the Lambert Conformal Conic modules in cases of extremely small scale factor values. 6. Corrected an error in the MGRS module that occurred when a point rounded up to the eastern boundary of the non-standard zone 31V in the northern Atlantic, which is considered to be part of zone 32V. Release 2.4.2 - August 2008 1. Corrected an error in the MGRS and USNG modules that incorrectly mapped 100,000m square row letters to northing values in the northern portion of the X latitude band (northings > 9,000,000m). 2. Revised the handling of warnings reported by the Transverse Mercator (TM) module for points located more than 9 degrees from the central meridian, when the TM module is invoked by the UTM and MGRS modules, so that UTM or MGRS error checking takes precedence. 3. Added datum domain checks for those cases where no datum transformations are performed. Previously, coordinates were not checked against the valid domain of the datum when the input datum and output datum were identical. 4. The default accuracy estimate values for DTED Level 1 and DTED Level 2 were updated to be consistent with MIL-PRF-89020B, Performance Specification, Digital Terrain Elevation Data (DTED), 23 May 2000, replacing the values from MIL-PRF-89020A, 19 Apr 1996. Default spherical accuracy estimate values for all relevant data sources were updated to reflect a more accurate relationship to the corresponding circular (horizontal) accuracy estimates. 5. In the GEOTRANS application, added commands to save the current selections and options settings as the defaults, and to reset the current selections and options settings from the defaults. 6. In the GEOTRANS application, added capabilities to create and delete user-defined 7-parameter datums. 7. Corrected a problem with the checking of input coordinates against the valid region for a local datum when a longitude value greater than +180 degrees was entered. 8. Corrected the valid regions for the PUK and NAR-E datums to use a range of longitudes that span the +180/-180 degree line. direwolf-1.5+dfsg/geotranz/tranmerc.c000066400000000000000000000602301347750676600177470ustar00rootroot00000000000000/***************************************************************************/ /* RSC IDENTIFIER: TRANSVERSE MERCATOR * * ABSTRACT * * This component provides conversions between Geodetic coordinates * (latitude and longitude) and Transverse Mercator projection coordinates * (easting and northing). * * ERROR HANDLING * * This component checks parameters for valid values. If an invalid value * is found the error code is combined with the current error code using * the bitwise or. This combining allows multiple error codes to be * returned. The possible error codes are: * * TRANMERC_NO_ERROR : No errors occurred in function * TRANMERC_LAT_ERROR : Latitude outside of valid range * (-90 to 90 degrees) * TRANMERC_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees, and within * +/-90 of Central Meridian) * TRANMERC_EASTING_ERROR : Easting outside of valid range * (depending on ellipsoid and * projection parameters) * TRANMERC_NORTHING_ERROR : Northing outside of valid range * (depending on ellipsoid and * projection parameters) * TRANMERC_ORIGIN_LAT_ERROR : Origin latitude outside of valid range * (-90 to 90 degrees) * TRANMERC_CENT_MER_ERROR : Central meridian outside of valid range * (-180 to 360 degrees) * TRANMERC_A_ERROR : Semi-major axis less than or equal to zero * TRANMERC_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * TRANMERC_SCALE_FACTOR_ERROR : Scale factor outside of valid * range (0.3 to 3.0) * TM_LON_WARNING : Distortion will result if longitude is more * than 9 degrees from the Central Meridian * * REUSE NOTES * * TRANSVERSE MERCATOR is intended for reuse by any application that * performs a Transverse Mercator projection or its inverse. * * REFERENCES * * Further information on TRANSVERSE MERCATOR can be found in the * Reuse Manual. * * TRANSVERSE MERCATOR originated from : * U.S. Army Topographic Engineering Center * Geospatial Information Division * 7701 Telegraph Road * Alexandria, VA 22310-3864 * * LICENSES * * None apply to this component. * * RESTRICTIONS * * TRANSVERSE MERCATOR has no restrictions. * * ENVIRONMENT * * TRANSVERSE MERCATOR was tested and certified in the following * environments: * * 1. Solaris 2.5 with GCC, version 2.8.1 * 2. Windows 95 with MS Visual C++, version 6 * * MODIFICATIONS * * Date Description * ---- ----------- * 10-02-97 Original Code * 03-02-97 Re-engineered Code * */ /***************************************************************************/ /* * INCLUDES */ #include #include "tranmerc.h" /* * math.h - Standard C math library * tranmerc.h - Is for prototype error checking */ /***************************************************************************/ /* DEFINES * */ #define PI 3.14159265358979323e0 /* PI */ #define PI_OVER_2 (PI/2.0e0) /* PI over 2 */ #define MAX_LAT ((PI * 89.99)/180.0) /* 89.99 degrees in radians */ #define MAX_DELTA_LONG ((PI * 90)/180.0) /* 90 degrees in radians */ #define MIN_SCALE_FACTOR 0.3 #define MAX_SCALE_FACTOR 3.0 #define SPHTMD(Latitude) ((double) (TranMerc_ap * Latitude \ - TranMerc_bp * sin(2.e0 * Latitude) + TranMerc_cp * sin(4.e0 * Latitude) \ - TranMerc_dp * sin(6.e0 * Latitude) + TranMerc_ep * sin(8.e0 * Latitude) ) ) #define SPHSN(Latitude) ((double) (TranMerc_a / sqrt( 1.e0 - TranMerc_es * \ pow(sin(Latitude), 2)))) #define SPHSR(Latitude) ((double) (TranMerc_a * (1.e0 - TranMerc_es) / \ pow(DENOM(Latitude), 3))) #define DENOM(Latitude) ((double) (sqrt(1.e0 - TranMerc_es * pow(sin(Latitude),2)))) /**************************************************************************/ /* GLOBAL DECLARATIONS * */ /* Ellipsoid Parameters, default to WGS 84 */ static double TranMerc_a = 6378137.0; /* Semi-major axis of ellipsoid in meters */ static double TranMerc_f = 1 / 298.257223563; /* Flattening of ellipsoid */ static double TranMerc_es = 0.0066943799901413800; /* Eccentricity (0.08181919084262188000) squared */ static double TranMerc_ebs = 0.0067394967565869; /* Second Eccentricity squared */ /* Transverse_Mercator projection Parameters */ static double TranMerc_Origin_Lat = 0.0; /* Latitude of origin in radians */ static double TranMerc_Origin_Long = 0.0; /* Longitude of origin in radians */ static double TranMerc_False_Northing = 0.0; /* False northing in meters */ static double TranMerc_False_Easting = 0.0; /* False easting in meters */ static double TranMerc_Scale_Factor = 1.0; /* Scale factor */ /* Isometeric to geodetic latitude parameters, default to WGS 84 */ static double TranMerc_ap = 6367449.1458008; static double TranMerc_bp = 16038.508696861; static double TranMerc_cp = 16.832613334334; static double TranMerc_dp = 0.021984404273757; static double TranMerc_ep = 3.1148371319283e-005; /* Maximum variance for easting and northing values for WGS 84. */ static double TranMerc_Delta_Easting = 40000000.0; static double TranMerc_Delta_Northing = 40000000.0; /* These state variables are for optimization purposes. The only function * that should modify them is Set_Tranverse_Mercator_Parameters. */ /************************************************************************/ /* FUNCTIONS * */ long Set_Transverse_Mercator_Parameters(double a, double f, double Origin_Latitude, double Central_Meridian, double False_Easting, double False_Northing, double Scale_Factor) { /* BEGIN Set_Tranverse_Mercator_Parameters */ /* * The function Set_Tranverse_Mercator_Parameters receives the ellipsoid * parameters and Tranverse Mercator projection parameters as inputs, and * sets the corresponding state variables. If any errors occur, the error * code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is * returned. * * a : Semi-major axis of ellipsoid, in meters (input) * f : Flattening of ellipsoid (input) * Origin_Latitude : Latitude in radians at the origin of the (input) * projection * Central_Meridian : Longitude in radians at the center of the (input) * projection * False_Easting : Easting/X at the center of the projection (input) * False_Northing : Northing/Y at the center of the projection (input) * Scale_Factor : Projection scale factor (input) */ double tn; /* True Meridianal distance constant */ double tn2; double tn3; double tn4; double tn5; double dummy_northing; double TranMerc_b; /* Semi-minor axis of ellipsoid, in meters */ double inv_f = 1 / f; long Error_Code = TRANMERC_NO_ERROR; if (a <= 0.0) { /* Semi-major axis must be greater than zero */ Error_Code |= TRANMERC_A_ERROR; } if ((inv_f < 250) || (inv_f > 350)) { /* Inverse flattening must be between 250 and 350 */ Error_Code |= TRANMERC_INV_F_ERROR; } if ((Origin_Latitude < -PI_OVER_2) || (Origin_Latitude > PI_OVER_2)) { /* origin latitude out of range */ Error_Code |= TRANMERC_ORIGIN_LAT_ERROR; } if ((Central_Meridian < -PI) || (Central_Meridian > (2*PI))) { /* origin longitude out of range */ Error_Code |= TRANMERC_CENT_MER_ERROR; } if ((Scale_Factor < MIN_SCALE_FACTOR) || (Scale_Factor > MAX_SCALE_FACTOR)) { Error_Code |= TRANMERC_SCALE_FACTOR_ERROR; } if (!Error_Code) { /* no errors */ TranMerc_a = a; TranMerc_f = f; TranMerc_Origin_Lat = Origin_Latitude; if (Central_Meridian > PI) Central_Meridian -= (2*PI); TranMerc_Origin_Long = Central_Meridian; TranMerc_False_Northing = False_Northing; TranMerc_False_Easting = False_Easting; TranMerc_Scale_Factor = Scale_Factor; /* Eccentricity Squared */ TranMerc_es = 2 * TranMerc_f - TranMerc_f * TranMerc_f; /* Second Eccentricity Squared */ TranMerc_ebs = (1 / (1 - TranMerc_es)) - 1; TranMerc_b = TranMerc_a * (1 - TranMerc_f); /*True meridianal constants */ tn = (TranMerc_a - TranMerc_b) / (TranMerc_a + TranMerc_b); tn2 = tn * tn; tn3 = tn2 * tn; tn4 = tn3 * tn; tn5 = tn4 * tn; TranMerc_ap = TranMerc_a * (1.e0 - tn + 5.e0 * (tn2 - tn3)/4.e0 + 81.e0 * (tn4 - tn5)/64.e0 ); TranMerc_bp = 3.e0 * TranMerc_a * (tn - tn2 + 7.e0 * (tn3 - tn4) /8.e0 + 55.e0 * tn5/64.e0 )/2.e0; TranMerc_cp = 15.e0 * TranMerc_a * (tn2 - tn3 + 3.e0 * (tn4 - tn5 )/4.e0) /16.0; TranMerc_dp = 35.e0 * TranMerc_a * (tn3 - tn4 + 11.e0 * tn5 / 16.e0) / 48.e0; TranMerc_ep = 315.e0 * TranMerc_a * (tn4 - tn5) / 512.e0; Convert_Geodetic_To_Transverse_Mercator(MAX_LAT, MAX_DELTA_LONG + Central_Meridian, &TranMerc_Delta_Easting, &TranMerc_Delta_Northing); Convert_Geodetic_To_Transverse_Mercator(0, MAX_DELTA_LONG + Central_Meridian, &TranMerc_Delta_Easting, &dummy_northing); TranMerc_Delta_Northing++; TranMerc_Delta_Easting++; } /* END OF if(!Error_Code) */ return (Error_Code); } /* END of Set_Transverse_Mercator_Parameters */ void Get_Transverse_Mercator_Parameters(double *a, double *f, double *Origin_Latitude, double *Central_Meridian, double *False_Easting, double *False_Northing, double *Scale_Factor) { /* BEGIN Get_Tranverse_Mercator_Parameters */ /* * The function Get_Transverse_Mercator_Parameters returns the current * ellipsoid and Transverse Mercator projection parameters. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) * Origin_Latitude : Latitude in radians at the origin of the (output) * projection * Central_Meridian : Longitude in radians at the center of the (output) * projection * False_Easting : Easting/X at the center of the projection (output) * False_Northing : Northing/Y at the center of the projection (output) * Scale_Factor : Projection scale factor (output) */ *a = TranMerc_a; *f = TranMerc_f; *Origin_Latitude = TranMerc_Origin_Lat; *Central_Meridian = TranMerc_Origin_Long; *False_Easting = TranMerc_False_Easting; *False_Northing = TranMerc_False_Northing; *Scale_Factor = TranMerc_Scale_Factor; return; } /* END OF Get_Tranverse_Mercator_Parameters */ long Convert_Geodetic_To_Transverse_Mercator (double Latitude, double Longitude, double *Easting, double *Northing) { /* BEGIN Convert_Geodetic_To_Transverse_Mercator */ /* * The function Convert_Geodetic_To_Transverse_Mercator converts geodetic * (latitude and longitude) coordinates to Transverse Mercator projection * (easting and northing) coordinates, according to the current ellipsoid * and Transverse Mercator projection coordinates. If any errors occur, the * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is * returned. * * Latitude : Latitude in radians (input) * Longitude : Longitude in radians (input) * Easting : Easting/X in meters (output) * Northing : Northing/Y in meters (output) */ double c; /* Cosine of latitude */ double c2; double c3; double c5; double c7; double dlam; /* Delta longitude - Difference in Longitude */ double eta; /* constant - TranMerc_ebs *c *c */ double eta2; double eta3; double eta4; double s; /* Sine of latitude */ double sn; /* Radius of curvature in the prime vertical */ double t; /* Tangent of latitude */ double tan2; double tan3; double tan4; double tan5; double tan6; double t1; /* Term in coordinate conversion formula - GP to Y */ double t2; /* Term in coordinate conversion formula - GP to Y */ double t3; /* Term in coordinate conversion formula - GP to Y */ double t4; /* Term in coordinate conversion formula - GP to Y */ double t5; /* Term in coordinate conversion formula - GP to Y */ double t6; /* Term in coordinate conversion formula - GP to Y */ double t7; /* Term in coordinate conversion formula - GP to Y */ double t8; /* Term in coordinate conversion formula - GP to Y */ double t9; /* Term in coordinate conversion formula - GP to Y */ double tmd; /* True Meridional distance */ double tmdo; /* True Meridional distance for latitude of origin */ long Error_Code = TRANMERC_NO_ERROR; double temp_Origin; double temp_Long; if ((Latitude < -MAX_LAT) || (Latitude > MAX_LAT)) { /* Latitude out of range */ Error_Code|= TRANMERC_LAT_ERROR; } if (Longitude > PI) Longitude -= (2 * PI); if ((Longitude < (TranMerc_Origin_Long - MAX_DELTA_LONG)) || (Longitude > (TranMerc_Origin_Long + MAX_DELTA_LONG))) { if (Longitude < 0) temp_Long = Longitude + 2 * PI; else temp_Long = Longitude; if (TranMerc_Origin_Long < 0) temp_Origin = TranMerc_Origin_Long + 2 * PI; else temp_Origin = TranMerc_Origin_Long; if ((temp_Long < (temp_Origin - MAX_DELTA_LONG)) || (temp_Long > (temp_Origin + MAX_DELTA_LONG))) Error_Code|= TRANMERC_LON_ERROR; } if (!Error_Code) { /* no errors */ /* * Delta Longitude */ dlam = Longitude - TranMerc_Origin_Long; if (fabs(dlam) > (9.0 * PI / 180)) { /* Distortion will result if Longitude is more than 9 degrees from the Central Meridian */ Error_Code |= TRANMERC_LON_WARNING; } if (dlam > PI) dlam -= (2 * PI); if (dlam < -PI) dlam += (2 * PI); if (fabs(dlam) < 2.e-10) dlam = 0.0; s = sin(Latitude); c = cos(Latitude); c2 = c * c; c3 = c2 * c; c5 = c3 * c2; c7 = c5 * c2; t = tan (Latitude); tan2 = t * t; tan3 = tan2 * t; tan4 = tan3 * t; tan5 = tan4 * t; tan6 = tan5 * t; eta = TranMerc_ebs * c2; eta2 = eta * eta; eta3 = eta2 * eta; eta4 = eta3 * eta; /* radius of curvature in prime vertical */ sn = SPHSN(Latitude); /* True Meridianal Distances */ tmd = SPHTMD(Latitude); /* Origin */ tmdo = SPHTMD (TranMerc_Origin_Lat); /* northing */ t1 = (tmd - tmdo) * TranMerc_Scale_Factor; t2 = sn * s * c * TranMerc_Scale_Factor/ 2.e0; t3 = sn * s * c3 * TranMerc_Scale_Factor * (5.e0 - tan2 + 9.e0 * eta + 4.e0 * eta2) /24.e0; t4 = sn * s * c5 * TranMerc_Scale_Factor * (61.e0 - 58.e0 * tan2 + tan4 + 270.e0 * eta - 330.e0 * tan2 * eta + 445.e0 * eta2 + 324.e0 * eta3 -680.e0 * tan2 * eta2 + 88.e0 * eta4 -600.e0 * tan2 * eta3 - 192.e0 * tan2 * eta4) / 720.e0; t5 = sn * s * c7 * TranMerc_Scale_Factor * (1385.e0 - 3111.e0 * tan2 + 543.e0 * tan4 - tan6) / 40320.e0; *Northing = TranMerc_False_Northing + t1 + pow(dlam,2.e0) * t2 + pow(dlam,4.e0) * t3 + pow(dlam,6.e0) * t4 + pow(dlam,8.e0) * t5; /* Easting */ t6 = sn * c * TranMerc_Scale_Factor; t7 = sn * c3 * TranMerc_Scale_Factor * (1.e0 - tan2 + eta ) /6.e0; t8 = sn * c5 * TranMerc_Scale_Factor * (5.e0 - 18.e0 * tan2 + tan4 + 14.e0 * eta - 58.e0 * tan2 * eta + 13.e0 * eta2 + 4.e0 * eta3 - 64.e0 * tan2 * eta2 - 24.e0 * tan2 * eta3 )/ 120.e0; t9 = sn * c7 * TranMerc_Scale_Factor * ( 61.e0 - 479.e0 * tan2 + 179.e0 * tan4 - tan6 ) /5040.e0; *Easting = TranMerc_False_Easting + dlam * t6 + pow(dlam,3.e0) * t7 + pow(dlam,5.e0) * t8 + pow(dlam,7.e0) * t9; } return (Error_Code); } /* END OF Convert_Geodetic_To_Transverse_Mercator */ long Convert_Transverse_Mercator_To_Geodetic ( double Easting, double Northing, double *Latitude, double *Longitude) { /* BEGIN Convert_Transverse_Mercator_To_Geodetic */ /* * The function Convert_Transverse_Mercator_To_Geodetic converts Transverse * Mercator projection (easting and northing) coordinates to geodetic * (latitude and longitude) coordinates, according to the current ellipsoid * and Transverse Mercator projection parameters. If any errors occur, the * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is * returned. * * Easting : Easting/X in meters (input) * Northing : Northing/Y in meters (input) * Latitude : Latitude in radians (output) * Longitude : Longitude in radians (output) */ double c; /* Cosine of latitude */ double de; /* Delta easting - Difference in Easting (Easting-Fe) */ double dlam; /* Delta longitude - Difference in Longitude */ double eta; /* constant - TranMerc_ebs *c *c */ double eta2; double eta3; double eta4; double ftphi; /* Footpoint latitude */ int i; /* Loop iterator */ //double s; /* Sine of latitude */ double sn; /* Radius of curvature in the prime vertical */ double sr; /* Radius of curvature in the meridian */ double t; /* Tangent of latitude */ double tan2; double tan4; double t10; /* Term in coordinate conversion formula - GP to Y */ double t11; /* Term in coordinate conversion formula - GP to Y */ double t12; /* Term in coordinate conversion formula - GP to Y */ double t13; /* Term in coordinate conversion formula - GP to Y */ double t14; /* Term in coordinate conversion formula - GP to Y */ double t15; /* Term in coordinate conversion formula - GP to Y */ double t16; /* Term in coordinate conversion formula - GP to Y */ double t17; /* Term in coordinate conversion formula - GP to Y */ double tmd; /* True Meridional distance */ double tmdo; /* True Meridional distance for latitude of origin */ long Error_Code = TRANMERC_NO_ERROR; if ((Easting < (TranMerc_False_Easting - TranMerc_Delta_Easting)) ||(Easting > (TranMerc_False_Easting + TranMerc_Delta_Easting))) { /* Easting out of range */ Error_Code |= TRANMERC_EASTING_ERROR; } if ((Northing < (TranMerc_False_Northing - TranMerc_Delta_Northing)) || (Northing > (TranMerc_False_Northing + TranMerc_Delta_Northing))) { /* Northing out of range */ Error_Code |= TRANMERC_NORTHING_ERROR; } if (!Error_Code) { /* True Meridional Distances for latitude of origin */ tmdo = SPHTMD(TranMerc_Origin_Lat); /* Origin */ tmd = tmdo + (Northing - TranMerc_False_Northing) / TranMerc_Scale_Factor; /* First Estimate */ sr = SPHSR(0.e0); ftphi = tmd/sr; for (i = 0; i < 5 ; i++) { t10 = SPHTMD (ftphi); sr = SPHSR(ftphi); ftphi = ftphi + (tmd - t10) / sr; } /* Radius of Curvature in the meridian */ sr = SPHSR(ftphi); /* Radius of Curvature in the meridian */ sn = SPHSN(ftphi); /* Sine Cosine terms */ //s = sin(ftphi); c = cos(ftphi); /* Tangent Value */ t = tan(ftphi); tan2 = t * t; tan4 = tan2 * tan2; eta = TranMerc_ebs * pow(c,2); eta2 = eta * eta; eta3 = eta2 * eta; eta4 = eta3 * eta; de = Easting - TranMerc_False_Easting; if (fabs(de) < 0.0001) de = 0.0; /* Latitude */ t10 = t / (2.e0 * sr * sn * pow(TranMerc_Scale_Factor, 2)); t11 = t * (5.e0 + 3.e0 * tan2 + eta - 4.e0 * pow(eta,2) - 9.e0 * tan2 * eta) / (24.e0 * sr * pow(sn,3) * pow(TranMerc_Scale_Factor,4)); t12 = t * (61.e0 + 90.e0 * tan2 + 46.e0 * eta + 45.E0 * tan4 - 252.e0 * tan2 * eta - 3.e0 * eta2 + 100.e0 * eta3 - 66.e0 * tan2 * eta2 - 90.e0 * tan4 * eta + 88.e0 * eta4 + 225.e0 * tan4 * eta2 + 84.e0 * tan2* eta3 - 192.e0 * tan2 * eta4) / ( 720.e0 * sr * pow(sn,5) * pow(TranMerc_Scale_Factor, 6) ); t13 = t * ( 1385.e0 + 3633.e0 * tan2 + 4095.e0 * tan4 + 1575.e0 * pow(t,6))/ (40320.e0 * sr * pow(sn,7) * pow(TranMerc_Scale_Factor,8)); *Latitude = ftphi - pow(de,2) * t10 + pow(de,4) * t11 - pow(de,6) * t12 + pow(de,8) * t13; t14 = 1.e0 / (sn * c * TranMerc_Scale_Factor); t15 = (1.e0 + 2.e0 * tan2 + eta) / (6.e0 * pow(sn,3) * c * pow(TranMerc_Scale_Factor,3)); t16 = (5.e0 + 6.e0 * eta + 28.e0 * tan2 - 3.e0 * eta2 + 8.e0 * tan2 * eta + 24.e0 * tan4 - 4.e0 * eta3 + 4.e0 * tan2 * eta2 + 24.e0 * tan2 * eta3) / (120.e0 * pow(sn,5) * c * pow(TranMerc_Scale_Factor,5)); t17 = (61.e0 + 662.e0 * tan2 + 1320.e0 * tan4 + 720.e0 * pow(t,6)) / (5040.e0 * pow(sn,7) * c * pow(TranMerc_Scale_Factor,7)); /* Difference in Longitude */ dlam = de * t14 - pow(de,3) * t15 + pow(de,5) * t16 - pow(de,7) * t17; /* Longitude */ (*Longitude) = TranMerc_Origin_Long + dlam; if((fabs)(*Latitude) > (90.0 * PI / 180.0)) Error_Code |= TRANMERC_NORTHING_ERROR; if((*Longitude) > (PI)) { *Longitude -= (2 * PI); if((fabs)(*Longitude) > PI) Error_Code |= TRANMERC_EASTING_ERROR; } else if((*Longitude) < (-PI)) { *Longitude += (2 * PI); if((fabs)(*Longitude) > PI) Error_Code |= TRANMERC_EASTING_ERROR; } if (fabs(dlam) > (9.0 * PI / 180) * cos(*Latitude)) { /* Distortion will result if Longitude is more than 9 degrees from the Central Meridian at the equator */ /* and decreases to 0 degrees at the poles */ /* As you move towards the poles, distortion will become more significant */ Error_Code |= TRANMERC_LON_WARNING; } } return (Error_Code); } /* END OF Convert_Transverse_Mercator_To_Geodetic */ direwolf-1.5+dfsg/geotranz/tranmerc.h000066400000000000000000000210661347750676600177600ustar00rootroot00000000000000#ifndef TRANMERC_H #define TRANMERC_H /***************************************************************************/ /* RSC IDENTIFIER: TRANSVERSE MERCATOR * * ABSTRACT * * This component provides conversions between Geodetic coordinates * (latitude and longitude) and Transverse Mercator projection coordinates * (easting and northing). * * ERROR HANDLING * * This component checks parameters for valid values. If an invalid value * is found the error code is combined with the current error code using * the bitwise or. This combining allows multiple error codes to be * returned. The possible error codes are: * * TRANMERC_NO_ERROR : No errors occurred in function * TRANMERC_LAT_ERROR : Latitude outside of valid range * (-90 to 90 degrees) * TRANMERC_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees, and within * +/-90 of Central Meridian) * TRANMERC_EASTING_ERROR : Easting outside of valid range * (depending on ellipsoid and * projection parameters) * TRANMERC_NORTHING_ERROR : Northing outside of valid range * (depending on ellipsoid and * projection parameters) * TRANMERC_ORIGIN_LAT_ERROR : Origin latitude outside of valid range * (-90 to 90 degrees) * TRANMERC_CENT_MER_ERROR : Central meridian outside of valid range * (-180 to 360 degrees) * TRANMERC_A_ERROR : Semi-major axis less than or equal to zero * TRANMERC_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * TRANMERC_SCALE_FACTOR_ERROR : Scale factor outside of valid * range (0.3 to 3.0) * TM_LON_WARNING : Distortion will result if longitude is more * than 9 degrees from the Central Meridian * * REUSE NOTES * * TRANSVERSE MERCATOR is intended for reuse by any application that * performs a Transverse Mercator projection or its inverse. * * REFERENCES * * Further information on TRANSVERSE MERCATOR can be found in the * Reuse Manual. * * TRANSVERSE MERCATOR originated from : * U.S. Army Topographic Engineering Center * Geospatial Information Division * 7701 Telegraph Road * Alexandria, VA 22310-3864 * * LICENSES * * None apply to this component. * * RESTRICTIONS * * TRANSVERSE MERCATOR has no restrictions. * * ENVIRONMENT * * TRANSVERSE MERCATOR was tested and certified in the following * environments: * * 1. Solaris 2.5 with GCC, version 2.8.1 * 2. Windows 95 with MS Visual C++, version 6 * * MODIFICATIONS * * Date Description * ---- ----------- * 10-02-97 Original Code * 03-02-97 Re-engineered Code * */ /***************************************************************************/ /* * DEFINES */ #define TRANMERC_NO_ERROR 0x0000 #define TRANMERC_LAT_ERROR 0x0001 #define TRANMERC_LON_ERROR 0x0002 #define TRANMERC_EASTING_ERROR 0x0004 #define TRANMERC_NORTHING_ERROR 0x0008 #define TRANMERC_ORIGIN_LAT_ERROR 0x0010 #define TRANMERC_CENT_MER_ERROR 0x0020 #define TRANMERC_A_ERROR 0x0040 #define TRANMERC_INV_F_ERROR 0x0080 #define TRANMERC_SCALE_FACTOR_ERROR 0x0100 #define TRANMERC_LON_WARNING 0x0200 /***************************************************************************/ /* * FUNCTION PROTOTYPES * for TRANMERC.C */ /* ensure proper linkage to c++ programs */ #ifdef __cplusplus extern "C" { #endif long Set_Transverse_Mercator_Parameters(double a, double f, double Origin_Latitude, double Central_Meridian, double False_Easting, double False_Northing, double Scale_Factor); /* * The function Set_Tranverse_Mercator_Parameters receives the ellipsoid * parameters and Tranverse Mercator projection parameters as inputs, and * sets the corresponding state variables. If any errors occur, the error * code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is * returned. * * a : Semi-major axis of ellipsoid, in meters (input) * f : Flattening of ellipsoid (input) * Origin_Latitude : Latitude in radians at the origin of the (input) * projection * Central_Meridian : Longitude in radians at the center of the (input) * projection * False_Easting : Easting/X at the center of the projection (input) * False_Northing : Northing/Y at the center of the projection (input) * Scale_Factor : Projection scale factor (input) */ void Get_Transverse_Mercator_Parameters(double *a, double *f, double *Origin_Latitude, double *Central_Meridian, double *False_Easting, double *False_Northing, double *Scale_Factor); /* * The function Get_Transverse_Mercator_Parameters returns the current * ellipsoid and Transverse Mercator projection parameters. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) * Origin_Latitude : Latitude in radians at the origin of the (output) * projection * Central_Meridian : Longitude in radians at the center of the (output) * projection * False_Easting : Easting/X at the center of the projection (output) * False_Northing : Northing/Y at the center of the projection (output) * Scale_Factor : Projection scale factor (output) */ long Convert_Geodetic_To_Transverse_Mercator (double Latitude, double Longitude, double *Easting, double *Northing); /* * The function Convert_Geodetic_To_Transverse_Mercator converts geodetic * (latitude and longitude) coordinates to Transverse Mercator projection * (easting and northing) coordinates, according to the current ellipsoid * and Transverse Mercator projection coordinates. If any errors occur, the * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is * returned. * * Latitude : Latitude in radians (input) * Longitude : Longitude in radians (input) * Easting : Easting/X in meters (output) * Northing : Northing/Y in meters (output) */ long Convert_Transverse_Mercator_To_Geodetic (double Easting, double Northing, double *Latitude, double *Longitude); /* * The function Convert_Transverse_Mercator_To_Geodetic converts Transverse * Mercator projection (easting and northing) coordinates to geodetic * (latitude and longitude) coordinates, according to the current ellipsoid * and Transverse Mercator projection parameters. If any errors occur, the * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is * returned. * * Easting : Easting/X in meters (input) * Northing : Northing/Y in meters (input) * Latitude : Latitude in radians (output) * Longitude : Longitude in radians (output) */ #ifdef __cplusplus } #endif #endif /* TRANMERC_H */ direwolf-1.5+dfsg/geotranz/ups.c000066400000000000000000000231401347750676600167420ustar00rootroot00000000000000/********************************************************************/ /* RSC IDENTIFIER: UPS * * * ABSTRACT * * This component provides conversions between geodetic (latitude * and longitude) coordinates and Universal Polar Stereographic (UPS) * projection (hemisphere, easting, and northing) coordinates. * * * ERROR HANDLING * * This component checks parameters for valid values. If an * invalid value is found the error code is combined with the * current error code using the bitwise or. This combining allows * multiple error codes to be returned. The possible error codes * are: * * UPS_NO_ERROR : No errors occurred in function * UPS_LAT_ERROR : Latitude outside of valid range * (North Pole: 83.5 to 90, * South Pole: -79.5 to -90) * UPS_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees) * UPS_HEMISPHERE_ERROR : Invalid hemisphere ('N' or 'S') * UPS_EASTING_ERROR : Easting outside of valid range, * (0 to 4,000,000m) * UPS_NORTHING_ERROR : Northing outside of valid range, * (0 to 4,000,000m) * UPS_A_ERROR : Semi-major axis less than or equal to zero * UPS_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * * * REUSE NOTES * * UPS is intended for reuse by any application that performs a Universal * Polar Stereographic (UPS) projection. * * * REFERENCES * * Further information on UPS can be found in the Reuse Manual. * * UPS originated from : U.S. Army Topographic Engineering Center * Geospatial Information Division * 7701 Telegraph Road * Alexandria, VA 22310-3864 * * * LICENSES * * None apply to this component. * * * RESTRICTIONS * * UPS has no restrictions. * * * ENVIRONMENT * * UPS was tested and certified in the following environments: * * 1. Solaris 2.5 with GCC version 2.8.1 * 2. Windows 95 with MS Visual C++ version 6 * * * MODIFICATIONS * * Date Description * ---- ----------- * 06-11-95 Original Code * 03-01-97 Original Code * * */ /************************************************************************/ /* * INCLUDES */ #include #include "polarst.h" #include "ups.h" /* * math.h - Is needed to call the math functions. * polar.h - Is used to convert polar stereographic coordinates * ups.h - Defines the function prototypes for the ups module. */ /************************************************************************/ /* GLOBAL DECLARATIONS * */ #define PI 3.14159265358979323e0 /* PI */ #define PI_OVER (PI/2.0e0) /* PI over 2 */ #define MAX_LAT ((PI * 90)/180.0) /* 90 degrees in radians */ #define MAX_ORIGIN_LAT ((81.114528 * PI) / 180.0) #define MIN_NORTH_LAT (83.5*PI/180.0) #define MIN_SOUTH_LAT (-79.5*PI/180.0) #define MIN_EAST_NORTH 0 #define MAX_EAST_NORTH 4000000 /* Ellipsoid Parameters, default to WGS 84 */ static double UPS_a = 6378137.0; /* Semi-major axis of ellipsoid in meters */ static double UPS_f = 1 / 298.257223563; /* Flattening of ellipsoid */ const double UPS_False_Easting = 2000000; const double UPS_False_Northing = 2000000; static double UPS_Origin_Latitude = MAX_ORIGIN_LAT; /*set default = North Hemisphere */ static double UPS_Origin_Longitude = 0.0; /************************************************************************/ /* FUNCTIONS * */ long Set_UPS_Parameters( double a, double f) { /* * The function SET_UPS_PARAMETERS receives the ellipsoid parameters and sets * the corresponding state variables. If any errors occur, the error code(s) * are returned by the function, otherwise UPS_NO_ERROR is returned. * * a : Semi-major axis of ellipsoid in meters (input) * f : Flattening of ellipsoid (input) */ double inv_f = 1 / f; long Error_Code = UPS_NO_ERROR; if (a <= 0.0) { /* Semi-major axis must be greater than zero */ Error_Code |= UPS_A_ERROR; } if ((inv_f < 250) || (inv_f > 350)) { /* Inverse flattening must be between 250 and 350 */ Error_Code |= UPS_INV_F_ERROR; } if (!Error_Code) { /* no errors */ UPS_a = a; UPS_f = f; } return (Error_Code); } /* END of Set_UPS_Parameters */ void Get_UPS_Parameters( double *a, double *f) { /* * The function Get_UPS_Parameters returns the current ellipsoid parameters. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) */ *a = UPS_a; *f = UPS_f; return; } /* END OF Get_UPS_Parameters */ long Convert_Geodetic_To_UPS ( double Latitude, double Longitude, char *Hemisphere, double *Easting, double *Northing) { /* * The function Convert_Geodetic_To_UPS converts geodetic (latitude and * longitude) coordinates to UPS (hemisphere, easting, and northing) * coordinates, according to the current ellipsoid parameters. If any * errors occur, the error code(s) are returned by the function, * otherwide UPS_NO_ERROR is returned. * * Latitude : Latitude in radians (input) * Longitude : Longitude in radians (input) * Hemisphere : Hemisphere either 'N' or 'S' (output) * Easting : Easting/X in meters (output) * Northing : Northing/Y in meters (output) */ double tempEasting, tempNorthing; long Error_Code = UPS_NO_ERROR; if ((Latitude < -MAX_LAT) || (Latitude > MAX_LAT)) { /* latitude out of range */ Error_Code |= UPS_LAT_ERROR; } if ((Latitude < 0) && (Latitude > MIN_SOUTH_LAT)) Error_Code |= UPS_LAT_ERROR; if ((Latitude >= 0) && (Latitude < MIN_NORTH_LAT)) Error_Code |= UPS_LAT_ERROR; if ((Longitude < -PI) || (Longitude > (2 * PI))) { /* slam out of range */ Error_Code |= UPS_LON_ERROR; } if (!Error_Code) { /* no errors */ if (Latitude < 0) { UPS_Origin_Latitude = -MAX_ORIGIN_LAT; *Hemisphere = 'S'; } else { UPS_Origin_Latitude = MAX_ORIGIN_LAT; *Hemisphere = 'N'; } Set_Polar_Stereographic_Parameters( UPS_a, UPS_f, UPS_Origin_Latitude, UPS_Origin_Longitude, UPS_False_Easting, UPS_False_Northing); Convert_Geodetic_To_Polar_Stereographic(Latitude, Longitude, &tempEasting, &tempNorthing); *Easting = tempEasting; *Northing = tempNorthing; } /* END of if(!Error_Code) */ return Error_Code; } /* END OF Convert_Geodetic_To_UPS */ long Convert_UPS_To_Geodetic(char Hemisphere, double Easting, double Northing, double *Latitude, double *Longitude) { /* * The function Convert_UPS_To_Geodetic converts UPS (hemisphere, easting, * and northing) coordinates to geodetic (latitude and longitude) coordinates * according to the current ellipsoid parameters. If any errors occur, the * error code(s) are returned by the function, otherwise UPS_NO_ERROR is * returned. * * Hemisphere : Hemisphere either 'N' or 'S' (input) * Easting : Easting/X in meters (input) * Northing : Northing/Y in meters (input) * Latitude : Latitude in radians (output) * Longitude : Longitude in radians (output) */ long Error_Code = UPS_NO_ERROR; if ((Hemisphere != 'N') && (Hemisphere != 'S')) Error_Code |= UPS_HEMISPHERE_ERROR; if ((Easting < MIN_EAST_NORTH) || (Easting > MAX_EAST_NORTH)) Error_Code |= UPS_EASTING_ERROR; if ((Northing < MIN_EAST_NORTH) || (Northing > MAX_EAST_NORTH)) Error_Code |= UPS_NORTHING_ERROR; if (Hemisphere =='N') {UPS_Origin_Latitude = MAX_ORIGIN_LAT;} if (Hemisphere =='S') {UPS_Origin_Latitude = -MAX_ORIGIN_LAT;} if (!Error_Code) { /* no errors */ Set_Polar_Stereographic_Parameters( UPS_a, UPS_f, UPS_Origin_Latitude, UPS_Origin_Longitude, UPS_False_Easting, UPS_False_Northing); Convert_Polar_Stereographic_To_Geodetic( Easting, Northing, Latitude, Longitude); if ((*Latitude < 0) && (*Latitude > MIN_SOUTH_LAT)) Error_Code |= UPS_LAT_ERROR; if ((*Latitude >= 0) && (*Latitude < MIN_NORTH_LAT)) Error_Code |= UPS_LAT_ERROR; } /* END OF if(!Error_Code) */ return (Error_Code); } /* END OF Convert_UPS_To_Geodetic */ direwolf-1.5+dfsg/geotranz/ups.h000066400000000000000000000134471347750676600167600ustar00rootroot00000000000000#ifndef UPS_H #define UPS_H /********************************************************************/ /* RSC IDENTIFIER: UPS * * * ABSTRACT * * This component provides conversions between geodetic (latitude * and longitude) coordinates and Universal Polar Stereographic (UPS) * projection (hemisphere, easting, and northing) coordinates. * * * ERROR HANDLING * * This component checks parameters for valid values. If an * invalid value is found the error code is combined with the * current error code using the bitwise or. This combining allows * multiple error codes to be returned. The possible error codes * are: * * UPS_NO_ERROR : No errors occurred in function * UPS_LAT_ERROR : Latitude outside of valid range * (North Pole: 83.5 to 90, * South Pole: -79.5 to -90) * UPS_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees) * UPS_HEMISPHERE_ERROR : Invalid hemisphere ('N' or 'S') * UPS_EASTING_ERROR : Easting outside of valid range, * (0 to 4,000,000m) * UPS_NORTHING_ERROR : Northing outside of valid range, * (0 to 4,000,000m) * UPS_A_ERROR : Semi-major axis less than or equal to zero * UPS_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * * * REUSE NOTES * * UPS is intended for reuse by any application that performs a Universal * Polar Stereographic (UPS) projection. * * * REFERENCES * * Further information on UPS can be found in the Reuse Manual. * * UPS originated from : U.S. Army Topographic Engineering Center * Geospatial Information Division * 7701 Telegraph Road * Alexandria, VA 22310-3864 * * * LICENSES * * None apply to this component. * * * RESTRICTIONS * * UPS has no restrictions. * * * ENVIRONMENT * * UPS was tested and certified in the following environments: * * 1. Solaris 2.5 with GCC version 2.8.1 * 2. Windows 95 with MS Visual C++ version 6 * * * MODIFICATIONS * * Date Description * ---- ----------- * 06-11-95 Original Code * 03-01-97 Original Code * * */ /**********************************************************************/ /* * DEFINES */ #define UPS_NO_ERROR 0x0000 #define UPS_LAT_ERROR 0x0001 #define UPS_LON_ERROR 0x0002 #define UPS_HEMISPHERE_ERROR 0x0004 #define UPS_EASTING_ERROR 0x0008 #define UPS_NORTHING_ERROR 0x0010 #define UPS_A_ERROR 0x0020 #define UPS_INV_F_ERROR 0x0040 /**********************************************************************/ /* * FUNCTION PROTOTYPES * for UPS.C */ /* ensure proper linkage to c++ programs */ #ifdef __cplusplus extern "C" { #endif long Set_UPS_Parameters( double a, double f); /* * The function SET_UPS_PARAMETERS receives the ellipsoid parameters and sets * the corresponding state variables. If any errors occur, the error code(s) * are returned by the function, otherwise UPS_NO_ERROR is returned. * * a : Semi-major axis of ellipsoid in meters (input) * f : Flattening of ellipsoid (input) */ void Get_UPS_Parameters( double *a, double *f); /* * The function Get_UPS_Parameters returns the current ellipsoid parameters. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) */ long Convert_Geodetic_To_UPS ( double Latitude, double Longitude, char *Hemisphere, double *Easting, double *Northing); /* * The function Convert_Geodetic_To_UPS converts geodetic (latitude and * longitude) coordinates to UPS (hemisphere, easting, and northing) * coordinates, according to the current ellipsoid parameters. If any * errors occur, the error code(s) are returned by the function, * otherwide UPS_NO_ERROR is returned. * * Latitude : Latitude in radians (input) * Longitude : Longitude in radians (input) * Hemisphere : Hemisphere either 'N' or 'S' (output) * Easting : Easting/X in meters (output) * Northing : Northing/Y in meters (output) */ long Convert_UPS_To_Geodetic(char Hemisphere, double Easting, double Northing, double *Latitude, double *Longitude); /* * The function Convert_UPS_To_Geodetic converts UPS (hemisphere, easting, * and northing) coordinates to geodetic (latitude and longitude) coordinates * according to the current ellipsoid parameters. If any errors occur, the * error code(s) are returned by the function, otherwise UPS_NO_ERROR is * returned. * * Hemisphere : Hemisphere either 'N' or 'S' (input) * Easting : Easting/X in meters (input) * Northing : Northing/Y in meters (input) * Latitude : Latitude in radians (output) * Longitude : Longitude in radians (output) */ #ifdef __cplusplus } #endif #endif /* UPS_H */ direwolf-1.5+dfsg/geotranz/usng.c000066400000000000000000001276101347750676600171160ustar00rootroot00000000000000/***************************************************************************/ /* RSC IDENTIFIER: USNG * * ABSTRACT * * This component converts between geodetic coordinates (latitude and * longitude) and United States National Grid (USNG) coordinates. * * ERROR HANDLING * * This component checks parameters for valid values. If an invalid value * is found, the error code is combined with the current error code using * the bitwise or. This combining allows multiple error codes to be * returned. The possible error codes are: * * USNG_NO_ERROR : No errors occurred in function * USNG_LAT_ERROR : Latitude outside of valid range * (-90 to 90 degrees) * USNG_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees) * USNG_STR_ERROR : An USNG string error: string too long, * too short, or badly formed * USNG_PRECISION_ERROR : The precision must be between 0 and 5 * inclusive. * USNG_A_ERROR : Semi-major axis less than or equal to zero * USNG_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * USNG_EASTING_ERROR : Easting outside of valid range * (100,000 to 900,000 meters for UTM) * (0 to 4,000,000 meters for UPS) * USNG_NORTHING_ERROR : Northing outside of valid range * (0 to 10,000,000 meters for UTM) * (0 to 4,000,000 meters for UPS) * USNG_ZONE_ERROR : Zone outside of valid range (1 to 60) * USNG_HEMISPHERE_ERROR : Invalid hemisphere ('N' or 'S') * * REUSE NOTES * * USNG is intended for reuse by any application that does conversions * between geodetic coordinates and USNG coordinates. * * REFERENCES * * Further information on USNG can be found in the Reuse Manual. * * USNG originated from : Federal Geographic Data Committee * 590 National Center * 12201 Sunrise Valley Drive * Reston, VA 22092 * * LICENSES * * None apply to this component. * * RESTRICTIONS * * * ENVIRONMENT * * USNG was tested and certified in the following environments: * * 1. Solaris 2.5 with GCC version 2.8.1 * 2. Windows XP with MS Visual C++ version 6 * * MODIFICATIONS * * Date Description * ---- ----------- * 06-05-06 Original Code (cloned from MGRS) */ /***************************************************************************/ /* * INCLUDES */ #include #include #include #include #include "ups.h" #include "utm.h" #include "usng.h" /* * ctype.h - Standard C character handling library * math.h - Standard C math library * stdio.h - Standard C input/output library * string.h - Standard C string handling library * ups.h - Universal Polar Stereographic (UPS) projection * utm.h - Universal Transverse Mercator (UTM) projection * usng.h - function prototype error checking */ /***************************************************************************/ /* * GLOBAL DECLARATIONS */ #define DEG_TO_RAD 0.017453292519943295 /* PI/180 */ #define RAD_TO_DEG 57.29577951308232087 /* 180/PI */ #define LETTER_A 0 /* ARRAY INDEX FOR LETTER A */ #define LETTER_B 1 /* ARRAY INDEX FOR LETTER B */ #define LETTER_C 2 /* ARRAY INDEX FOR LETTER C */ #define LETTER_D 3 /* ARRAY INDEX FOR LETTER D */ #define LETTER_E 4 /* ARRAY INDEX FOR LETTER E */ #define LETTER_F 5 /* ARRAY INDEX FOR LETTER F */ #define LETTER_G 6 /* ARRAY INDEX FOR LETTER G */ #define LETTER_H 7 /* ARRAY INDEX FOR LETTER H */ #define LETTER_I 8 /* ARRAY INDEX FOR LETTER I */ #define LETTER_J 9 /* ARRAY INDEX FOR LETTER J */ #define LETTER_K 10 /* ARRAY INDEX FOR LETTER K */ #define LETTER_L 11 /* ARRAY INDEX FOR LETTER L */ #define LETTER_M 12 /* ARRAY INDEX FOR LETTER M */ #define LETTER_N 13 /* ARRAY INDEX FOR LETTER N */ #define LETTER_O 14 /* ARRAY INDEX FOR LETTER O */ #define LETTER_P 15 /* ARRAY INDEX FOR LETTER P */ #define LETTER_Q 16 /* ARRAY INDEX FOR LETTER Q */ #define LETTER_R 17 /* ARRAY INDEX FOR LETTER R */ #define LETTER_S 18 /* ARRAY INDEX FOR LETTER S */ #define LETTER_T 19 /* ARRAY INDEX FOR LETTER T */ #define LETTER_U 20 /* ARRAY INDEX FOR LETTER U */ #define LETTER_V 21 /* ARRAY INDEX FOR LETTER V */ #define LETTER_W 22 /* ARRAY INDEX FOR LETTER W */ #define LETTER_X 23 /* ARRAY INDEX FOR LETTER X */ #define LETTER_Y 24 /* ARRAY INDEX FOR LETTER Y */ #define LETTER_Z 25 /* ARRAY INDEX FOR LETTER Z */ #define USNG_LETTERS 3 /* NUMBER OF LETTERS IN USNG */ #define ONEHT 100000.e0 /* ONE HUNDRED THOUSAND */ #define TWOMIL 2000000.e0 /* TWO MILLION */ #define TRUE 1 /* CONSTANT VALUE FOR TRUE VALUE */ #define FALSE 0 /* CONSTANT VALUE FOR FALSE VALUE */ #define PI 3.14159265358979323e0 /* PI */ #define PI_OVER_2 (PI / 2.0e0) #define MIN_EASTING 100000 #define MAX_EASTING 900000 #define MIN_NORTHING 0 #define MAX_NORTHING 10000000 #define MAX_PRECISION 5 /* Maximum precision of easting & northing */ #define MIN_UTM_LAT ( (-80 * PI) / 180.0 ) /* -80 degrees in radians */ #define MAX_UTM_LAT ( (84 * PI) / 180.0 ) /* 84 degrees in radians */ #define MIN_EAST_NORTH 0 #define MAX_EAST_NORTH 4000000 /* Ellipsoid parameters, default to WGS 84 */ double USNG_a = 6378137.0; /* Semi-major axis of ellipsoid in meters */ double USNG_f = 1 / 298.257223563; /* Flattening of ellipsoid */ double USNG_recpf = 298.257223563; char USNG_Ellipsoid_Code[3] = {'W','E',0}; typedef struct Latitude_Band_Value { long letter; /* letter representing latitude band */ double min_northing; /* minimum northing for latitude band */ double north; /* upper latitude for latitude band */ double south; /* lower latitude for latitude band */ double northing_offset; /* latitude band northing offset */ } Latitude_Band; static const Latitude_Band Latitude_Band_Table[20] = {{LETTER_C, 1100000.0, -72.0, -80.5, 0.0}, {LETTER_D, 2000000.0, -64.0, -72.0, 2000000.0}, {LETTER_E, 2800000.0, -56.0, -64.0, 2000000.0}, {LETTER_F, 3700000.0, -48.0, -56.0, 2000000.0}, {LETTER_G, 4600000.0, -40.0, -48.0, 4000000.0}, {LETTER_H, 5500000.0, -32.0, -40.0, 4000000.0}, {LETTER_J, 6400000.0, -24.0, -32.0, 6000000.0}, {LETTER_K, 7300000.0, -16.0, -24.0, 6000000.0}, {LETTER_L, 8200000.0, -8.0, -16.0, 8000000.0}, {LETTER_M, 9100000.0, 0.0, -8.0, 8000000.0}, {LETTER_N, 0.0, 8.0, 0.0, 0.0}, {LETTER_P, 800000.0, 16.0, 8.0, 0.0}, {LETTER_Q, 1700000.0, 24.0, 16.0, 0.0}, {LETTER_R, 2600000.0, 32.0, 24.0, 2000000.0}, {LETTER_S, 3500000.0, 40.0, 32.0, 2000000.0}, {LETTER_T, 4400000.0, 48.0, 40.0, 4000000.0}, {LETTER_U, 5300000.0, 56.0, 48.0, 4000000.0}, {LETTER_V, 6200000.0, 64.0, 56.0, 6000000.0}, {LETTER_W, 7000000.0, 72.0, 64.0, 6000000.0}, {LETTER_X, 7900000.0, 84.5, 72.0, 6000000.0}}; typedef struct UPS_Constant_Value { long letter; /* letter representing latitude band */ long ltr2_low_value; /* 2nd letter range - low number */ long ltr2_high_value; /* 2nd letter range - high number */ long ltr3_high_value; /* 3rd letter range - high number (UPS) */ double false_easting; /* False easting based on 2nd letter */ double false_northing; /* False northing based on 3rd letter */ } UPS_Constant; static const UPS_Constant UPS_Constant_Table[4] = {{LETTER_A, LETTER_J, LETTER_Z, LETTER_Z, 800000.0, 800000.0}, {LETTER_B, LETTER_A, LETTER_R, LETTER_Z, 2000000.0, 800000.0}, {LETTER_Y, LETTER_J, LETTER_Z, LETTER_P, 800000.0, 1300000.0}, {LETTER_Z, LETTER_A, LETTER_J, LETTER_P, 2000000.0, 1300000.0}}; /***************************************************************************/ /* * FUNCTIONS */ long USNG_Get_Latitude_Band_Min_Northing(long letter, double* min_northing, double* northing_offset) /* * The function USNG_Get_Latitude_Band_Min_Northing receives a latitude band letter * and uses the Latitude_Band_Table to determine the minimum northing and northing offset * for that latitude band letter. * * letter : Latitude band letter (input) * min_northing : Minimum northing for that letter (output) */ { /* USNG_Get_Latitude_Band_Min_Northing */ long error_code = USNG_NO_ERROR; if ((letter >= LETTER_C) && (letter <= LETTER_H)) { *min_northing = Latitude_Band_Table[letter-2].min_northing; *northing_offset = Latitude_Band_Table[letter-2].northing_offset; } else if ((letter >= LETTER_J) && (letter <= LETTER_N)) { *min_northing = Latitude_Band_Table[letter-3].min_northing; *northing_offset = Latitude_Band_Table[letter-3].northing_offset; } else if ((letter >= LETTER_P) && (letter <= LETTER_X)) { *min_northing = Latitude_Band_Table[letter-4].min_northing; *northing_offset = Latitude_Band_Table[letter-4].northing_offset; } else error_code |= USNG_STRING_ERROR; return error_code; } /* USNG_Get_Latitude_Band_Min_Northing */ long USNG_Get_Latitude_Range(long letter, double* north, double* south) /* * The function USNG_Get_Latitude_Range receives a latitude band letter * and uses the Latitude_Band_Table to determine the latitude band * boundaries for that latitude band letter. * * letter : Latitude band letter (input) * north : Northern latitude boundary for that letter (output) * north : Southern latitude boundary for that letter (output) */ { /* USNG_Get_Latitude_Range */ long error_code = USNG_NO_ERROR; if ((letter >= LETTER_C) && (letter <= LETTER_H)) { *north = Latitude_Band_Table[letter-2].north * DEG_TO_RAD; *south = Latitude_Band_Table[letter-2].south * DEG_TO_RAD; } else if ((letter >= LETTER_J) && (letter <= LETTER_N)) { *north = Latitude_Band_Table[letter-3].north * DEG_TO_RAD; *south = Latitude_Band_Table[letter-3].south * DEG_TO_RAD; } else if ((letter >= LETTER_P) && (letter <= LETTER_X)) { *north = Latitude_Band_Table[letter-4].north * DEG_TO_RAD; *south = Latitude_Band_Table[letter-4].south * DEG_TO_RAD; } else error_code |= USNG_STRING_ERROR; return error_code; } /* USNG_Get_Latitude_Range */ long USNG_Get_Latitude_Letter(double latitude, int* letter) /* * The function USNG_Get_Latitude_Letter receives a latitude value * and uses the Latitude_Band_Table to determine the latitude band * letter for that latitude. * * latitude : Latitude (input) * letter : Latitude band letter (output) */ { /* USNG_Get_Latitude_Letter */ double temp = 0.0; long error_code = USNG_NO_ERROR; double lat_deg = latitude * RAD_TO_DEG; if (lat_deg >= 72 && lat_deg < 84.5) *letter = LETTER_X; else if (lat_deg > -80.5 && lat_deg < 72) { temp = ((latitude + (80.0 * DEG_TO_RAD)) / (8.0 * DEG_TO_RAD)) + 1.0e-12; *letter = Latitude_Band_Table[(int)temp].letter; } else error_code |= USNG_LAT_ERROR; return error_code; } /* USNG_Get_Latitude_Letter */ long USNG_Check_Zone(char* USNG, long* zone_exists) /* * The function USNG_Check_Zone receives a USNG coordinate string. * If a zone is given, TRUE is returned. Otherwise, FALSE * is returned. * * USNG : USNG coordinate string (input) * zone_exists : TRUE if a zone is given, * FALSE if a zone is not given (output) */ { /* USNG_Check_Zone */ int i = 0; int j = 0; int num_digits = 0; long error_code = USNG_NO_ERROR; /* skip any leading blanks */ while (USNG[i] == ' ') i++; j = i; while (isdigit(USNG[i])) i++; num_digits = i - j; if (num_digits <= 2) if (num_digits > 0) *zone_exists = TRUE; else *zone_exists = FALSE; else error_code |= USNG_STRING_ERROR; return error_code; } /* USNG_Check_Zone */ long Make_USNG_String (char* USNG, long Zone, int Letters[USNG_LETTERS], double Easting, double Northing, long Precision) /* * The function Make_USNG_String constructs a USNG string * from its component parts. * * USNG : USNG coordinate string (output) * Zone : UTM Zone (input) * Letters : USNG coordinate string letters (input) * Easting : Easting value (input) * Northing : Northing value (input) * Precision : Precision level of USNG string (input) */ { /* Make_USNG_String */ long i; long j; double divisor; long east; long north; char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; long error_code = USNG_NO_ERROR; i = 0; if (Zone) i = sprintf (USNG+i,"%2.2ld",Zone); else strncpy(USNG, " ", 2); // 2 spaces for (j=0;j<3;j++) USNG[i++] = alphabet[Letters[j]]; divisor = pow (10.0, (5 - Precision)); Easting = fmod (Easting, 100000.0); if (Easting >= 99999.5) Easting = 99999.0; east = (long)(Easting/divisor); i += sprintf (USNG+i, "%*.*ld", (int)Precision, (int)Precision, east); Northing = fmod (Northing, 100000.0); if (Northing >= 99999.5) Northing = 99999.0; north = (long)(Northing/divisor); i += sprintf (USNG+i, "%*.*ld", (int)Precision, (int)Precision, north); return (error_code); } /* Make_USNG_String */ long Break_USNG_String (char* USNG, long* Zone, long Letters[USNG_LETTERS], double* Easting, double* Northing, long* Precision) /* * The function Break_USNG_String breaks down a USNG * coordinate string into its component parts. * * USNG : USNG coordinate string (input) * Zone : UTM Zone (output) * Letters : USNG coordinate string letters (output) * Easting : Easting value (output) * Northing : Northing value (output) * Precision : Precision level of USNG string (output) */ { /* Break_USNG_String */ long num_digits; long num_letters; long i = 0; long j = 0; long error_code = USNG_NO_ERROR; while (USNG[i] == ' ') i++; /* skip any leading blanks */ j = i; while (isdigit(USNG[i])) i++; num_digits = i - j; if (num_digits <= 2) if (num_digits > 0) { char zone_string[3]; /* get zone */ strncpy (zone_string, USNG+j, 2); zone_string[2] = 0; sscanf (zone_string, "%ld", Zone); if ((*Zone < 1) || (*Zone > 60)) error_code |= USNG_STRING_ERROR; } else *Zone = 0; else error_code |= USNG_STRING_ERROR; j = i; while (isalpha(USNG[i])) i++; num_letters = i - j; if (num_letters == 3) { /* get letters */ Letters[0] = (toupper(USNG[j]) - (long)'A'); if ((Letters[0] == LETTER_I) || (Letters[0] == LETTER_O)) error_code |= USNG_STRING_ERROR; Letters[1] = (toupper(USNG[j+1]) - (long)'A'); if ((Letters[1] == LETTER_I) || (Letters[1] == LETTER_O)) error_code |= USNG_STRING_ERROR; Letters[2] = (toupper(USNG[j+2]) - (long)'A'); if ((Letters[2] == LETTER_I) || (Letters[2] == LETTER_O)) error_code |= USNG_STRING_ERROR; } else error_code |= USNG_STRING_ERROR; j = i; while (isdigit(USNG[i])) i++; num_digits = i - j; if ((num_digits <= 10) && (num_digits%2 == 0)) { long n; char east_string[6]; char north_string[6]; long east; long north; double multiplier; /* get easting & northing */ n = num_digits/2; *Precision = n; if (n > 0) { strncpy (east_string, USNG+j, n); east_string[n] = 0; sscanf (east_string, "%ld", &east); strncpy (north_string, USNG+j+n, n); north_string[n] = 0; sscanf (north_string, "%ld", &north); multiplier = pow (10.0, 5 - n); *Easting = east * multiplier; *Northing = north * multiplier; } else { *Easting = 0.0; *Northing = 0.0; } } else error_code |= USNG_STRING_ERROR; return (error_code); } /* Break_USNG_String */ void USNG_Get_Grid_Values (long zone, long* ltr2_low_value, long* ltr2_high_value, double *pattern_offset) /* * The function USNG_Get_Grid_Values sets the letter range used for * the 2nd letter in the USNG coordinate string, based on the set * number of the utm zone. It also sets the pattern offset using a * value of A for the second letter of the grid square, based on * the grid pattern and set number of the utm zone. * * zone : Zone number (input) * ltr2_low_value : 2nd letter low number (output) * ltr2_high_value : 2nd letter high number (output) * pattern_offset : Pattern offset (output) */ { /* BEGIN USNG_Get_Grid_Values */ long set_number; /* Set number (1-6) based on UTM zone number */ set_number = zone % 6; if (!set_number) set_number = 6; if ((set_number == 1) || (set_number == 4)) { *ltr2_low_value = LETTER_A; *ltr2_high_value = LETTER_H; } else if ((set_number == 2) || (set_number == 5)) { *ltr2_low_value = LETTER_J; *ltr2_high_value = LETTER_R; } else if ((set_number == 3) || (set_number == 6)) { *ltr2_low_value = LETTER_S; *ltr2_high_value = LETTER_Z; } /* False northing at A for second letter of grid square */ if ((set_number % 2) == 0) *pattern_offset = 500000.0; else *pattern_offset = 0.0; } /* END OF USNG_Get_Grid_Values */ long UTM_To_USNG (long Zone, double Latitude, double Easting, double Northing, long Precision, char *USNG) /* * The function UTM_To_USNG calculates a USNG coordinate string * based on the zone, latitude, easting and northing. * * Zone : Zone number (input) * Latitude : Latitude in radians (input) * Easting : Easting (input) * Northing : Northing (input) * Precision : Precision (input) * USNG : USNG coordinate string (output) */ { /* BEGIN UTM_To_USNG */ double pattern_offset; /* Pattern offset for 3rd letter */ double grid_northing; /* Northing used to derive 3rd letter of USNG */ long ltr2_low_value; /* 2nd letter range - low number */ long ltr2_high_value; /* 2nd letter range - high number */ int letters[USNG_LETTERS]; /* Number location of 3 letters in alphabet */ double divisor; long error_code = USNG_NO_ERROR; /* Round easting and northing values */ divisor = pow (10.0, (5 - Precision)); Easting = (long)(Easting/divisor) * divisor; Northing = (long)(Northing/divisor) * divisor; if( Latitude <= 0.0 && Northing == 1.0e7) { Latitude = 0.0; Northing = 0.0; } ltr2_low_value = LETTER_A; // Make compiler shut up about possibly uninitialized value. // It should be set by the following but compiler doesn't know. USNG_Get_Grid_Values(Zone, <r2_low_value, <r2_high_value, &pattern_offset); error_code = USNG_Get_Latitude_Letter(Latitude, &letters[0]); if (!error_code) { grid_northing = Northing; while (grid_northing >= TWOMIL) { grid_northing = grid_northing - TWOMIL; } grid_northing = grid_northing + pattern_offset; if(grid_northing >= TWOMIL) grid_northing = grid_northing - TWOMIL; letters[2] = (long)(grid_northing / ONEHT); if (letters[2] > LETTER_H) letters[2] = letters[2] + 1; if (letters[2] > LETTER_N) letters[2] = letters[2] + 1; letters[1] = ltr2_low_value + ((long)(Easting / ONEHT) -1); if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_N)) letters[1] = letters[1] + 1; Make_USNG_String (USNG, Zone, letters, Easting, Northing, Precision); } return error_code; } /* END UTM_To_USNG */ long Set_USNG_Parameters (double a, double f, char *Ellipsoid_Code) /* * The function SET_USNG_PARAMETERS receives the ellipsoid parameters and sets * the corresponding state variables. If any errors occur, the error code(s) * are returned by the function, otherwise USNG_NO_ERROR is returned. * * a : Semi-major axis of ellipsoid in meters (input) * f : Flattening of ellipsoid (input) * Ellipsoid_Code : 2-letter code for ellipsoid (input) */ { /* Set_USNG_Parameters */ double inv_f = 1 / f; long Error_Code = USNG_NO_ERROR; if (a <= 0.0) { /* Semi-major axis must be greater than zero */ Error_Code |= USNG_A_ERROR; } if ((inv_f < 250) || (inv_f > 350)) { /* Inverse flattening must be between 250 and 350 */ Error_Code |= USNG_INV_F_ERROR; } if (!Error_Code) { /* no errors */ USNG_a = a; USNG_f = f; USNG_recpf = inv_f; strcpy (USNG_Ellipsoid_Code, Ellipsoid_Code); } return (Error_Code); } /* Set_USNG_Parameters */ void Get_USNG_Parameters (double *a, double *f, char* Ellipsoid_Code) /* * The function Get_USNG_Parameters returns the current ellipsoid * parameters. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) * Ellipsoid_Code : 2-letter code for ellipsoid (output) */ { /* Get_USNG_Parameters */ *a = USNG_a; *f = USNG_f; strcpy (Ellipsoid_Code, USNG_Ellipsoid_Code); return; } /* Get_USNG_Parameters */ long Convert_Geodetic_To_USNG (double Latitude, double Longitude, long Precision, char* USNG) /* * The function Convert_Geodetic_To_USNG converts Geodetic (latitude and * longitude) coordinates to a USNG coordinate string, according to the * current ellipsoid parameters. If any errors occur, the error code(s) * are returned by the function, otherwise USNG_NO_ERROR is returned. * * Latitude : Latitude in radians (input) * Longitude : Longitude in radians (input) * Precision : Precision level of USNG string (input) * USNG : USNG coordinate string (output) * */ { /* Convert_Geodetic_To_USNG */ long zone; char hemisphere; double easting; double northing; long temp_error_code = USNG_NO_ERROR; long error_code = USNG_NO_ERROR; if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2)) { /* Latitude out of range */ error_code |= USNG_LAT_ERROR; } if ((Longitude < -PI) || (Longitude > (2*PI))) { /* Longitude out of range */ error_code |= USNG_LON_ERROR; } if ((Precision < 0) || (Precision > MAX_PRECISION)) error_code |= USNG_PRECISION_ERROR; if (!error_code) { if ((Latitude < MIN_UTM_LAT) || (Latitude > MAX_UTM_LAT)) { temp_error_code = Set_UPS_Parameters (USNG_a, USNG_f); if(!temp_error_code) { temp_error_code |= Convert_Geodetic_To_UPS (Latitude, Longitude, &hemisphere, &easting, &northing); if(!temp_error_code) error_code |= Convert_UPS_To_USNG (hemisphere, easting, northing, Precision, USNG); else { if(temp_error_code & UPS_LAT_ERROR) error_code |= USNG_LAT_ERROR; if(temp_error_code & UPS_LON_ERROR) error_code |= USNG_LON_ERROR; } } else { if(temp_error_code & UPS_A_ERROR) error_code |= USNG_A_ERROR; if(temp_error_code & UPS_INV_F_ERROR) error_code |= USNG_INV_F_ERROR; } } else { temp_error_code = Set_UTM_Parameters (USNG_a, USNG_f, 0); if(!temp_error_code) { temp_error_code |= Convert_Geodetic_To_UTM (Latitude, Longitude, &zone, &hemisphere, &easting, &northing); if(!temp_error_code) error_code |= UTM_To_USNG (zone, Latitude, easting, northing, Precision, USNG); else { if(temp_error_code & UTM_LAT_ERROR) error_code |= USNG_LAT_ERROR; if(temp_error_code & UTM_LON_ERROR) error_code |= USNG_LON_ERROR; if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR) error_code |= USNG_ZONE_ERROR; if(temp_error_code & UTM_EASTING_ERROR) error_code |= USNG_EASTING_ERROR; if(temp_error_code & UTM_NORTHING_ERROR) error_code |= USNG_NORTHING_ERROR; } } else { if(temp_error_code & UTM_A_ERROR) error_code |= USNG_A_ERROR; if(temp_error_code & UTM_INV_F_ERROR) error_code |= USNG_INV_F_ERROR; if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR) error_code |= USNG_ZONE_ERROR; } } } return (error_code); } /* Convert_Geodetic_To_USNG */ long Convert_USNG_To_Geodetic (char* USNG, double *Latitude, double *Longitude) /* * The function Convert_USNG_To_Geodetic converts a USNG coordinate string * to Geodetic (latitude and longitude) coordinates * according to the current ellipsoid parameters. If any errors occur, * the error code(s) are returned by the function, otherwise UTM_NO_ERROR * is returned. * * USNG : USNG coordinate string (input) * Latitude : Latitude in radians (output) * Longitude : Longitude in radians (output) * */ { /* Convert_USNG_To_Geodetic */ long zone; char hemisphere = '?'; double easting; double northing; long zone_exists; long temp_error_code = USNG_NO_ERROR; long error_code = USNG_NO_ERROR; error_code = USNG_Check_Zone(USNG, &zone_exists); if (!error_code) { if (zone_exists) { error_code |= Convert_USNG_To_UTM (USNG, &zone, &hemisphere, &easting, &northing); if(!error_code || (error_code & USNG_LAT_WARNING)) { temp_error_code = Set_UTM_Parameters (USNG_a, USNG_f, 0); if(!temp_error_code) { temp_error_code |= Convert_UTM_To_Geodetic (zone, hemisphere, easting, northing, Latitude, Longitude); if(temp_error_code) { if((temp_error_code & UTM_ZONE_ERROR) || (temp_error_code & UTM_HEMISPHERE_ERROR)) error_code |= USNG_STRING_ERROR; if(temp_error_code & UTM_EASTING_ERROR) error_code |= USNG_EASTING_ERROR; if(temp_error_code & UTM_NORTHING_ERROR) error_code |= USNG_NORTHING_ERROR; } } else { if(temp_error_code & UTM_A_ERROR) error_code |= USNG_A_ERROR; if(temp_error_code & UTM_INV_F_ERROR) error_code |= USNG_INV_F_ERROR; if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR) error_code |= USNG_ZONE_ERROR; } } } else { error_code |= Convert_USNG_To_UPS (USNG, &hemisphere, &easting, &northing); if(!error_code) { temp_error_code = Set_UPS_Parameters (USNG_a, USNG_f); if(!temp_error_code) { temp_error_code |= Convert_UPS_To_Geodetic (hemisphere, easting, northing, Latitude, Longitude); if(temp_error_code) { if(temp_error_code & UPS_HEMISPHERE_ERROR) error_code |= USNG_STRING_ERROR; if(temp_error_code & UPS_EASTING_ERROR) error_code |= USNG_EASTING_ERROR; if(temp_error_code & UPS_LAT_ERROR) error_code |= USNG_NORTHING_ERROR; } } else { if(temp_error_code & UPS_A_ERROR) error_code |= USNG_A_ERROR; if(temp_error_code & UPS_INV_F_ERROR) error_code |= USNG_INV_F_ERROR; } } } } return (error_code); } /* END OF Convert_USNG_To_Geodetic */ long Convert_UTM_To_USNG (long Zone, char Hemisphere, double Easting, double Northing, long Precision, char* USNG) /* * The function Convert_UTM_To_USNG converts UTM (zone, easting, and * northing) coordinates to a USNG coordinate string, according to the * current ellipsoid parameters. If any errors occur, the error code(s) * are returned by the function, otherwise USNG_NO_ERROR is returned. * * Zone : UTM zone (input) * Hemisphere : North or South hemisphere (input) * Easting : Easting (X) in meters (input) * Northing : Northing (Y) in meters (input) * Precision : Precision level of USNG string (input) * USNG : USNG coordinate string (output) */ { /* Convert_UTM_To_USNG */ double latitude; /* Latitude of UTM point */ double longitude; /* Longitude of UTM point */ long utm_error_code = USNG_NO_ERROR; long error_code = USNG_NO_ERROR; if ((Zone < 1) || (Zone > 60)) error_code |= USNG_ZONE_ERROR; if ((Hemisphere != 'S') && (Hemisphere != 'N')) error_code |= USNG_HEMISPHERE_ERROR; if ((Easting < MIN_EASTING) || (Easting > MAX_EASTING)) error_code |= USNG_EASTING_ERROR; if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING)) error_code |= USNG_NORTHING_ERROR; if ((Precision < 0) || (Precision > MAX_PRECISION)) error_code |= USNG_PRECISION_ERROR; if (!error_code) { Set_UTM_Parameters (USNG_a, USNG_f, 0); utm_error_code = Convert_UTM_To_Geodetic (Zone, Hemisphere, Easting, Northing, &latitude, &longitude); if(utm_error_code) { if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR)) error_code |= USNG_STRING_ERROR; if(utm_error_code & UTM_EASTING_ERROR) error_code |= USNG_EASTING_ERROR; if(utm_error_code & UTM_NORTHING_ERROR) error_code |= USNG_NORTHING_ERROR; } error_code |= UTM_To_USNG (Zone, latitude, Easting, Northing, Precision, USNG); } return (error_code); } /* Convert_UTM_To_USNG */ long Convert_USNG_To_UTM (char *USNG, long *Zone, char *Hemisphere, double *Easting, double *Northing) /* * The function Convert_USNG_To_UTM converts a USNG coordinate string * to UTM projection (zone, hemisphere, easting and northing) coordinates * according to the current ellipsoid parameters. If any errors occur, * the error code(s) are returned by the function, otherwise UTM_NO_ERROR * is returned. * * USNG : USNG coordinate string (input) * Zone : UTM zone (output) * Hemisphere : North or South hemisphere (output) * Easting : Easting (X) in meters (output) * Northing : Northing (Y) in meters (output) */ { /* Convert_USNG_To_UTM */ double min_northing; double northing_offset; long ltr2_low_value; long ltr2_high_value; double pattern_offset; double upper_lat_limit; /* North latitude limits based on 1st letter */ double lower_lat_limit; /* South latitude limits based on 1st letter */ double grid_easting; /* Easting for 100,000 meter grid square */ double grid_northing; /* Northing for 100,000 meter grid square */ long letters[USNG_LETTERS]; long in_precision; double latitude = 0.0; double longitude = 0.0; double divisor = 1.0; long utm_error_code = USNG_NO_ERROR; long error_code = USNG_NO_ERROR; error_code = Break_USNG_String (USNG, Zone, letters, Easting, Northing, &in_precision); if (!*Zone) error_code |= USNG_STRING_ERROR; else { if (!error_code) { if ((letters[0] == LETTER_X) && ((*Zone == 32) || (*Zone == 34) || (*Zone == 36))) error_code |= USNG_STRING_ERROR; else { if (letters[0] < LETTER_N) *Hemisphere = 'S'; else *Hemisphere = 'N'; ltr2_low_value = LETTER_A; // Make compiler shut up about possibly uninitialized values. ltr2_high_value = LETTER_Z; // They should be set by the following but compiler doesn't know. USNG_Get_Grid_Values(*Zone, <r2_low_value, <r2_high_value, &pattern_offset); /* Check that the second letter of the USNG string is within * the range of valid second letter values * Also check that the third letter is valid */ if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) || (letters[2] > LETTER_V)) error_code |= USNG_STRING_ERROR; if (!error_code) { double row_letter_northing = (double)(letters[2]) * ONEHT; grid_easting = (double)((letters[1]) - ltr2_low_value + 1) * ONEHT; if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_O)) grid_easting = grid_easting - ONEHT; if (letters[2] > LETTER_O) row_letter_northing = row_letter_northing - ONEHT; if (letters[2] > LETTER_I) row_letter_northing = row_letter_northing - ONEHT; if (row_letter_northing >= TWOMIL) row_letter_northing = row_letter_northing - TWOMIL; error_code = USNG_Get_Latitude_Band_Min_Northing(letters[0], &min_northing, &northing_offset); if (!error_code) { grid_northing = row_letter_northing - pattern_offset; if(grid_northing < 0) grid_northing += TWOMIL; grid_northing += northing_offset; if(grid_northing < min_northing) grid_northing += TWOMIL; *Easting = grid_easting + *Easting; *Northing = grid_northing + *Northing; /* check that point is within Zone Letter bounds */ utm_error_code = Set_UTM_Parameters(USNG_a, USNG_f, 0); if (!utm_error_code) { utm_error_code = Convert_UTM_To_Geodetic(*Zone,*Hemisphere,*Easting,*Northing,&latitude,&longitude); if (!utm_error_code) { divisor = pow (10.0, in_precision); error_code = USNG_Get_Latitude_Range(letters[0], &upper_lat_limit, &lower_lat_limit); if (!error_code) { if (!(((lower_lat_limit - DEG_TO_RAD/divisor) <= latitude) && (latitude <= (upper_lat_limit + DEG_TO_RAD/divisor)))) error_code |= USNG_LAT_ERROR; } } else { if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR)) error_code |= USNG_STRING_ERROR; if(utm_error_code & UTM_EASTING_ERROR) error_code |= USNG_EASTING_ERROR; if(utm_error_code & UTM_NORTHING_ERROR) error_code |= USNG_NORTHING_ERROR; } } else { if(utm_error_code & UTM_A_ERROR) error_code |= USNG_A_ERROR; if(utm_error_code & UTM_INV_F_ERROR) error_code |= USNG_INV_F_ERROR; if(utm_error_code & UTM_ZONE_OVERRIDE_ERROR) error_code |= USNG_ZONE_ERROR; } } } } } } return (error_code); } /* Convert_USNG_To_UTM */ long Convert_UPS_To_USNG (char Hemisphere, double Easting, double Northing, long Precision, char* USNG) /* * The function Convert_UPS_To_USNG converts UPS (hemisphere, easting, * and northing) coordinates to a USNG coordinate string according to * the current ellipsoid parameters. If any errors occur, the error * code(s) are returned by the function, otherwise UPS_NO_ERROR is * returned. * * Hemisphere : Hemisphere either 'N' or 'S' (input) * Easting : Easting/X in meters (input) * Northing : Northing/Y in meters (input) * Precision : Precision level of USNG string (input) * USNG : USNG coordinate string (output) */ { /* Convert_UPS_To_USNG */ double false_easting; /* False easting for 2nd letter */ double false_northing; /* False northing for 3rd letter */ double grid_easting; /* Easting used to derive 2nd letter of USNG */ double grid_northing; /* Northing used to derive 3rd letter of USNG */ long ltr2_low_value; /* 2nd letter range - low number */ int letters[USNG_LETTERS]; /* Number location of 3 letters in alphabet */ double divisor; int index = 0; long error_code = USNG_NO_ERROR; if ((Hemisphere != 'N') && (Hemisphere != 'S')) error_code |= USNG_HEMISPHERE_ERROR; if ((Easting < MIN_EAST_NORTH) || (Easting > MAX_EAST_NORTH)) error_code |= USNG_EASTING_ERROR; if ((Northing < MIN_EAST_NORTH) || (Northing > MAX_EAST_NORTH)) error_code |= USNG_NORTHING_ERROR; if ((Precision < 0) || (Precision > MAX_PRECISION)) error_code |= USNG_PRECISION_ERROR; if (!error_code) { divisor = pow (10.0, (5 - Precision)); Easting = (long)(Easting/divisor + 1.0e-9) * divisor; Northing = (long)(Northing/divisor) * divisor; if (Hemisphere == 'N') { if (Easting >= TWOMIL) letters[0] = LETTER_Z; else letters[0] = LETTER_Y; index = letters[0] - 22; ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value; false_easting = UPS_Constant_Table[index].false_easting; false_northing = UPS_Constant_Table[index].false_northing; } else { if (Easting >= TWOMIL) letters[0] = LETTER_B; else letters[0] = LETTER_A; ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value; false_easting = UPS_Constant_Table[letters[0]].false_easting; false_northing = UPS_Constant_Table[letters[0]].false_northing; } grid_northing = Northing; grid_northing = grid_northing - false_northing; letters[2] = (long)(grid_northing / ONEHT); if (letters[2] > LETTER_H) letters[2] = letters[2] + 1; if (letters[2] > LETTER_N) letters[2] = letters[2] + 1; grid_easting = Easting; grid_easting = grid_easting - false_easting; letters[1] = ltr2_low_value + ((long)(grid_easting / ONEHT)); if (Easting < TWOMIL) { if (letters[1] > LETTER_L) letters[1] = letters[1] + 3; if (letters[1] > LETTER_U) letters[1] = letters[1] + 2; } else { if (letters[1] > LETTER_C) letters[1] = letters[1] + 2; if (letters[1] > LETTER_H) letters[1] = letters[1] + 1; if (letters[1] > LETTER_L) letters[1] = letters[1] + 3; } Make_USNG_String (USNG, 0, letters, Easting, Northing, Precision); } return (error_code); } /* Convert_UPS_To_USNG */ long Convert_USNG_To_UPS ( char *USNG, char *Hemisphere, double *Easting, double *Northing) /* * The function Convert_USNG_To_UPS converts a USNG coordinate string * to UPS (hemisphere, easting, and northing) coordinates, according * to the current ellipsoid parameters. If any errors occur, the error * code(s) are returned by the function, otherwide UPS_NO_ERROR is returned. * * USNG : USNG coordinate string (input) * Hemisphere : Hemisphere either 'N' or 'S' (output) * Easting : Easting/X in meters (output) * Northing : Northing/Y in meters (output) */ { /* Convert_USNG_To_UPS */ long ltr2_high_value; /* 2nd letter range - high number */ long ltr3_high_value; /* 3rd letter range - high number (UPS) */ long ltr2_low_value; /* 2nd letter range - low number */ double false_easting; /* False easting for 2nd letter */ double false_northing; /* False northing for 3rd letter */ double grid_easting; /* easting for 100,000 meter grid square */ double grid_northing; /* northing for 100,000 meter grid square */ long zone = 0; long letters[USNG_LETTERS]; long in_precision = 0; int index = 0; long error_code = USNG_NO_ERROR; error_code = Break_USNG_String (USNG, &zone, letters, Easting, Northing, &in_precision); if (zone) error_code |= USNG_STRING_ERROR; else { if (!error_code) { if (letters[0] >= LETTER_Y) { *Hemisphere = 'N'; index = letters[0] - 22; ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value; ltr2_high_value = UPS_Constant_Table[index].ltr2_high_value; ltr3_high_value = UPS_Constant_Table[index].ltr3_high_value; false_easting = UPS_Constant_Table[index].false_easting; false_northing = UPS_Constant_Table[index].false_northing; } else { *Hemisphere = 'S'; ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value; ltr2_high_value = UPS_Constant_Table[letters[0]].ltr2_high_value; ltr3_high_value = UPS_Constant_Table[letters[0]].ltr3_high_value; false_easting = UPS_Constant_Table[letters[0]].false_easting; false_northing = UPS_Constant_Table[letters[0]].false_northing; } /* Check that the second letter of the USNG string is within * the range of valid second letter values * Also check that the third letter is valid */ if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) || ((letters[1] == LETTER_D) || (letters[1] == LETTER_E) || (letters[1] == LETTER_M) || (letters[1] == LETTER_N) || (letters[1] == LETTER_V) || (letters[1] == LETTER_W)) || (letters[2] > ltr3_high_value)) error_code = USNG_STRING_ERROR; if (!error_code) { grid_northing = (double)letters[2] * ONEHT + false_northing; if (letters[2] > LETTER_I) grid_northing = grid_northing - ONEHT; if (letters[2] > LETTER_O) grid_northing = grid_northing - ONEHT; grid_easting = (double)((letters[1]) - ltr2_low_value) * ONEHT + false_easting; if (ltr2_low_value != LETTER_A) { if (letters[1] > LETTER_L) grid_easting = grid_easting - 300000.0; if (letters[1] > LETTER_U) grid_easting = grid_easting - 200000.0; } else { if (letters[1] > LETTER_C) grid_easting = grid_easting - 200000.0; if (letters[1] > LETTER_I) grid_easting = grid_easting - ONEHT; if (letters[1] > LETTER_L) grid_easting = grid_easting - 300000.0; } *Easting = grid_easting + *Easting; *Northing = grid_northing + *Northing; } } } return (error_code); } /* Convert_USNG_To_UPS */ direwolf-1.5+dfsg/geotranz/usng.h000066400000000000000000000224261347750676600171220ustar00rootroot00000000000000#ifndef USNG_H #define USNG_H /***************************************************************************/ /* RSC IDENTIFIER: USNG * * ABSTRACT * * This component converts between geodetic coordinates (latitude and * longitude) and United States National Grid (USNG) coordinates. * * ERROR HANDLING * * This component checks parameters for valid values. If an invalid value * is found, the error code is combined with the current error code using * the bitwise or. This combining allows multiple error codes to be * returned. The possible error codes are: * * USNG_NO_ERROR : No errors occurred in function * USNG_LAT_ERROR : Latitude outside of valid range * (-90 to 90 degrees) * USNG_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees) * USNG_STR_ERROR : An USNG string error: string too long, * too short, or badly formed * USNG_PRECISION_ERROR : The precision must be between 0 and 5 * inclusive. * USNG_A_ERROR : Semi-major axis less than or equal to zero * USNG_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * USNG_EASTING_ERROR : Easting outside of valid range * (100,000 to 900,000 meters for UTM) * (0 to 4,000,000 meters for UPS) * USNG_NORTHING_ERROR : Northing outside of valid range * (0 to 10,000,000 meters for UTM) * (0 to 4,000,000 meters for UPS) * USNG_ZONE_ERROR : Zone outside of valid range (1 to 60) * USNG_HEMISPHERE_ERROR : Invalid hemisphere ('N' or 'S') * * REUSE NOTES * * USNG is intended for reuse by any application that does conversions * between geodetic coordinates and USNG coordinates. * * REFERENCES * * Further information on USNG can be found in the Reuse Manual. * * USNG originated from : Federal Geographic Data Committee * 590 National Center * 12201 Sunrise Valley Drive * Reston, VA 22092 * * LICENSES * * None apply to this component. * * RESTRICTIONS * * * ENVIRONMENT * * USNG was tested and certified in the following environments: * * 1. Solaris 2.5 with GCC version 2.8.1 * 2. Windows 95 with MS Visual C++ version 6 * * MODIFICATIONS * * Date Description * ---- ----------- * 06-05-06 Original Code (cloned from MGRS) * */ /***************************************************************************/ /* * DEFINES */ #define USNG_NO_ERROR 0x0000 #define USNG_LAT_ERROR 0x0001 #define USNG_LON_ERROR 0x0002 #define USNG_STRING_ERROR 0x0004 #define USNG_PRECISION_ERROR 0x0008 #define USNG_A_ERROR 0x0010 #define USNG_INV_F_ERROR 0x0020 #define USNG_EASTING_ERROR 0x0040 #define USNG_NORTHING_ERROR 0x0080 #define USNG_ZONE_ERROR 0x0100 #define USNG_HEMISPHERE_ERROR 0x0200 #define USNG_LAT_WARNING 0x0400 /***************************************************************************/ /* * FUNCTION PROTOTYPES */ /* ensure proper linkage to c++ programs */ #ifdef __cplusplus extern "C" { #endif long Set_USNG_Parameters(double a, double f, char *Ellipsoid_Code); /* * The function Set_USNG_Parameters receives the ellipsoid parameters and sets * the corresponding state variables. If any errors occur, the error code(s) * are returned by the function, otherwise USNG_NO_ERROR is returned. * * a : Semi-major axis of ellipsoid in meters (input) * f : Flattening of ellipsoid (input) * Ellipsoid_Code : 2-letter code for ellipsoid (input) */ void Get_USNG_Parameters(double *a, double *f, char *Ellipsoid_Code); /* * The function Get_USNG_Parameters returns the current ellipsoid * parameters. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) * Ellipsoid_Code : 2-letter code for ellipsoid (output) */ long Convert_Geodetic_To_USNG (double Latitude, double Longitude, long Precision, char *USNG); /* * The function Convert_Geodetic_To_USNG converts geodetic (latitude and * longitude) coordinates to a USNG coordinate string, according to the * current ellipsoid parameters. If any errors occur, the error code(s) * are returned by the function, otherwise USNG_NO_ERROR is returned. * * Latitude : Latitude in radians (input) * Longitude : Longitude in radians (input) * Precision : Precision level of USNG string (input) * USNG : USNG coordinate string (output) * */ long Convert_USNG_To_Geodetic (char *USNG, double *Latitude, double *Longitude); /* * This function converts a USNG coordinate string to Geodetic (latitude * and longitude in radians) coordinates. If any errors occur, the error * code(s) are returned by the function, otherwise USNG_NO_ERROR is returned. * * USNG : USNG coordinate string (input) * Latitude : Latitude in radians (output) * Longitude : Longitude in radians (output) * */ long Convert_UTM_To_USNG (long Zone, char Hemisphere, double Easting, double Northing, long Precision, char *USNG); /* * The function Convert_UTM_To_USNG converts UTM (zone, easting, and * northing) coordinates to a USNG coordinate string, according to the * current ellipsoid parameters. If any errors occur, the error code(s) * are returned by the function, otherwise USNG_NO_ERROR is returned. * * Zone : UTM zone (input) * Hemisphere : North or South hemisphere (input) * Easting : Easting (X) in meters (input) * Northing : Northing (Y) in meters (input) * Precision : Precision level of USNG string (input) * USNG : USNG coordinate string (output) */ long Convert_USNG_To_UTM (char *USNG, long *Zone, char *Hemisphere, double *Easting, double *Northing); /* * The function Convert_USNG_To_UTM converts a USNG coordinate string * to UTM projection (zone, hemisphere, easting and northing) coordinates * according to the current ellipsoid parameters. If any errors occur, * the error code(s) are returned by the function, otherwise UTM_NO_ERROR * is returned. * * USNG : USNG coordinate string (input) * Zone : UTM zone (output) * Hemisphere : North or South hemisphere (output) * Easting : Easting (X) in meters (output) * Northing : Northing (Y) in meters (output) */ long Convert_UPS_To_USNG ( char Hemisphere, double Easting, double Northing, long Precision, char *USNG); /* * The function Convert_UPS_To_USNG converts UPS (hemisphere, easting, * and northing) coordinates to a USNG coordinate string according to * the current ellipsoid parameters. If any errors occur, the error * code(s) are returned by the function, otherwise UPS_NO_ERROR is * returned. * * Hemisphere : Hemisphere either 'N' or 'S' (input) * Easting : Easting/X in meters (input) * Northing : Northing/Y in meters (input) * Precision : Precision level of USNG string (input) * USNG : USNG coordinate string (output) */ long Convert_USNG_To_UPS ( char *USNG, char *Hemisphere, double *Easting, double *Northing); /* * The function Convert_USNG_To_UPS converts a USNG coordinate string * to UPS (hemisphere, easting, and northing) coordinates, according * to the current ellipsoid parameters. If any errors occur, the error * code(s) are returned by the function, otherwide UPS_NO_ERROR is returned. * * USNG : USNG coordinate string (input) * Hemisphere : Hemisphere either 'N' or 'S' (output) * Easting : Easting/X in meters (output) * Northing : Northing/Y in meters (output) */ #ifdef __cplusplus } #endif #endif /* USNG_H */ direwolf-1.5+dfsg/geotranz/utm.c000066400000000000000000000305471347750676600167510ustar00rootroot00000000000000/***************************************************************************/ /* RSC IDENTIFIER: UTM * * ABSTRACT * * This component provides conversions between geodetic coordinates * (latitude and longitudes) and Universal Transverse Mercator (UTM) * projection (zone, hemisphere, easting, and northing) coordinates. * * ERROR HANDLING * * This component checks parameters for valid values. If an invalid value * is found, the error code is combined with the current error code using * the bitwise or. This combining allows multiple error codes to be * returned. The possible error codes are: * * UTM_NO_ERROR : No errors occurred in function * UTM_LAT_ERROR : Latitude outside of valid range * (-80.5 to 84.5 degrees) * UTM_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees) * UTM_EASTING_ERROR : Easting outside of valid range * (100,000 to 900,000 meters) * UTM_NORTHING_ERROR : Northing outside of valid range * (0 to 10,000,000 meters) * UTM_ZONE_ERROR : Zone outside of valid range (1 to 60) * UTM_HEMISPHERE_ERROR : Invalid hemisphere ('N' or 'S') * UTM_ZONE_OVERRIDE_ERROR: Zone outside of valid range * (1 to 60) and within 1 of 'natural' zone * UTM_A_ERROR : Semi-major axis less than or equal to zero * UTM_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * * REUSE NOTES * * UTM is intended for reuse by any application that performs a Universal * Transverse Mercator (UTM) projection or its inverse. * * REFERENCES * * Further information on UTM can be found in the Reuse Manual. * * UTM originated from : U.S. Army Topographic Engineering Center * Geospatial Information Division * 7701 Telegraph Road * Alexandria, VA 22310-3864 * * LICENSES * * None apply to this component. * * RESTRICTIONS * * UTM has no restrictions. * * ENVIRONMENT * * UTM was tested and certified in the following environments: * * 1. Solaris 2.5 with GCC, version 2.8.1 * 2. MSDOS with MS Visual C++, version 6 * * MODIFICATIONS * * Date Description * ---- ----------- * 10-02-97 Original Code * */ /***************************************************************************/ /* * INCLUDES */ #include "tranmerc.h" #include "utm.h" /* * tranmerc.h - Is used to convert transverse mercator coordinates * utm.h - Defines the function prototypes for the utm module. */ /***************************************************************************/ /* * DEFINES */ #define PI 3.14159265358979323e0 /* PI */ #define MIN_LAT ( (-80.5 * PI) / 180.0 ) /* -80.5 degrees in radians */ #define MAX_LAT ( (84.5 * PI) / 180.0 ) /* 84.5 degrees in radians */ #define MIN_EASTING 100000 #define MAX_EASTING 900000 #define MIN_NORTHING 0 #define MAX_NORTHING 10000000 /***************************************************************************/ /* * GLOBAL DECLARATIONS */ static double UTM_a = 6378137.0; /* Semi-major axis of ellipsoid in meters */ static double UTM_f = 1 / 298.257223563; /* Flattening of ellipsoid */ static long UTM_Override = 0; /* Zone override flag */ /***************************************************************************/ /* * FUNCTIONS * */ long Set_UTM_Parameters(double a, double f, long override) { /* * The function Set_UTM_Parameters receives the ellipsoid parameters and * UTM zone override parameter as inputs, and sets the corresponding state * variables. If any errors occur, the error code(s) are returned by the * function, otherwise UTM_NO_ERROR is returned. * * a : Semi-major axis of ellipsoid, in meters (input) * f : Flattening of ellipsoid (input) * override : UTM override zone, zero indicates no override (input) */ double inv_f = 1 / f; long Error_Code = UTM_NO_ERROR; if (a <= 0.0) { /* Semi-major axis must be greater than zero */ Error_Code |= UTM_A_ERROR; } if ((inv_f < 250) || (inv_f > 350)) { /* Inverse flattening must be between 250 and 350 */ Error_Code |= UTM_INV_F_ERROR; } if ((override < 0) || (override > 60)) { Error_Code |= UTM_ZONE_OVERRIDE_ERROR; } if (!Error_Code) { /* no errors */ UTM_a = a; UTM_f = f; UTM_Override = override; } return (Error_Code); } /* END OF Set_UTM_Parameters */ void Get_UTM_Parameters(double *a, double *f, long *override) { /* * The function Get_UTM_Parameters returns the current ellipsoid * parameters and UTM zone override parameter. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) * override : UTM override zone, zero indicates no override (output) */ *a = UTM_a; *f = UTM_f; *override = UTM_Override; } /* END OF Get_UTM_Parameters */ long Convert_Geodetic_To_UTM (double Latitude, double Longitude, long *Zone, char *Hemisphere, double *Easting, double *Northing) { /* * The function Convert_Geodetic_To_UTM converts geodetic (latitude and * longitude) coordinates to UTM projection (zone, hemisphere, easting and * northing) coordinates according to the current ellipsoid and UTM zone * override parameters. If any errors occur, the error code(s) are returned * by the function, otherwise UTM_NO_ERROR is returned. * * Latitude : Latitude in radians (input) * Longitude : Longitude in radians (input) * Zone : UTM zone (output) * Hemisphere : North or South hemisphere (output) * Easting : Easting (X) in meters (output) * Northing : Northing (Y) in meters (output) */ long Lat_Degrees; long Long_Degrees; long temp_zone; long Error_Code = UTM_NO_ERROR; double Origin_Latitude = 0; double Central_Meridian = 0; double False_Easting = 500000; double False_Northing = 0; double Scale = 0.9996; if ((Latitude < MIN_LAT) || (Latitude > MAX_LAT)) { /* Latitude out of range */ Error_Code |= UTM_LAT_ERROR; } if ((Longitude < -PI) || (Longitude > (2*PI))) { /* Longitude out of range */ Error_Code |= UTM_LON_ERROR; } if (!Error_Code) { /* no errors */ if((Latitude > -1.0e-9) && (Latitude < 0)) Latitude = 0.0; if (Longitude < 0) Longitude += (2*PI) + 1.0e-10; Lat_Degrees = (long)(Latitude * 180.0 / PI); Long_Degrees = (long)(Longitude * 180.0 / PI); if (Longitude < PI) temp_zone = (long)(31 + ((Longitude * 180.0 / PI) / 6.0)); else temp_zone = (long)(((Longitude * 180.0 / PI) / 6.0) - 29); if (temp_zone > 60) temp_zone = 1; /* UTM special cases */ if ((Lat_Degrees > 55) && (Lat_Degrees < 64) && (Long_Degrees > -1) && (Long_Degrees < 3)) temp_zone = 31; if ((Lat_Degrees > 55) && (Lat_Degrees < 64) && (Long_Degrees > 2) && (Long_Degrees < 12)) temp_zone = 32; if ((Lat_Degrees > 71) && (Long_Degrees > -1) && (Long_Degrees < 9)) temp_zone = 31; if ((Lat_Degrees > 71) && (Long_Degrees > 8) && (Long_Degrees < 21)) temp_zone = 33; if ((Lat_Degrees > 71) && (Long_Degrees > 20) && (Long_Degrees < 33)) temp_zone = 35; if ((Lat_Degrees > 71) && (Long_Degrees > 32) && (Long_Degrees < 42)) temp_zone = 37; if (UTM_Override) { if ((temp_zone == 1) && (UTM_Override == 60)) temp_zone = UTM_Override; else if ((temp_zone == 60) && (UTM_Override == 1)) temp_zone = UTM_Override; else if ((Lat_Degrees > 71) && (Long_Degrees > -1) && (Long_Degrees < 42)) { if (((temp_zone-2) <= UTM_Override) && (UTM_Override <= (temp_zone+2))) temp_zone = UTM_Override; else Error_Code = UTM_ZONE_OVERRIDE_ERROR; } else if (((temp_zone-1) <= UTM_Override) && (UTM_Override <= (temp_zone+1))) temp_zone = UTM_Override; else Error_Code = UTM_ZONE_OVERRIDE_ERROR; } if (!Error_Code) { if (temp_zone >= 31) Central_Meridian = (6 * temp_zone - 183) * PI / 180.0; else Central_Meridian = (6 * temp_zone + 177) * PI / 180.0; *Zone = temp_zone; if (Latitude < 0) { False_Northing = 10000000; *Hemisphere = 'S'; } else *Hemisphere = 'N'; Set_Transverse_Mercator_Parameters(UTM_a, UTM_f, Origin_Latitude, Central_Meridian, False_Easting, False_Northing, Scale); Convert_Geodetic_To_Transverse_Mercator(Latitude, Longitude, Easting, Northing); if ((*Easting < MIN_EASTING) || (*Easting > MAX_EASTING)) Error_Code = UTM_EASTING_ERROR; if ((*Northing < MIN_NORTHING) || (*Northing > MAX_NORTHING)) Error_Code |= UTM_NORTHING_ERROR; } } /* END OF if (!Error_Code) */ return (Error_Code); } /* END OF Convert_Geodetic_To_UTM */ long Convert_UTM_To_Geodetic(long Zone, char Hemisphere, double Easting, double Northing, double *Latitude, double *Longitude) { /* * The function Convert_UTM_To_Geodetic converts UTM projection (zone, * hemisphere, easting and northing) coordinates to geodetic(latitude * and longitude) coordinates, according to the current ellipsoid * parameters. If any errors occur, the error code(s) are returned * by the function, otherwise UTM_NO_ERROR is returned. * * Zone : UTM zone (input) * Hemisphere : North or South hemisphere (input) * Easting : Easting (X) in meters (input) * Northing : Northing (Y) in meters (input) * Latitude : Latitude in radians (output) * Longitude : Longitude in radians (output) */ long Error_Code = UTM_NO_ERROR; long tm_error_code = UTM_NO_ERROR; double Origin_Latitude = 0; double Central_Meridian = 0; double False_Easting = 500000; double False_Northing = 0; double Scale = 0.9996; if ((Zone < 1) || (Zone > 60)) Error_Code |= UTM_ZONE_ERROR; if ((Hemisphere != 'S') && (Hemisphere != 'N')) Error_Code |= UTM_HEMISPHERE_ERROR; if ((Easting < MIN_EASTING) || (Easting > MAX_EASTING)) Error_Code |= UTM_EASTING_ERROR; if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING)) Error_Code |= UTM_NORTHING_ERROR; if (!Error_Code) { /* no errors */ if (Zone >= 31) Central_Meridian = ((6 * Zone - 183) * PI / 180.0 /*+ 0.00000005*/); else Central_Meridian = ((6 * Zone + 177) * PI / 180.0 /*+ 0.00000005*/); if (Hemisphere == 'S') False_Northing = 10000000; Set_Transverse_Mercator_Parameters(UTM_a, UTM_f, Origin_Latitude, Central_Meridian, False_Easting, False_Northing, Scale); tm_error_code = Convert_Transverse_Mercator_To_Geodetic(Easting, Northing, Latitude, Longitude); if(tm_error_code) { if(tm_error_code & TRANMERC_EASTING_ERROR) Error_Code |= UTM_EASTING_ERROR; if(tm_error_code & TRANMERC_NORTHING_ERROR) Error_Code |= UTM_NORTHING_ERROR; } if ((*Latitude < MIN_LAT) || (*Latitude > MAX_LAT)) { /* Latitude out of range */ Error_Code |= UTM_NORTHING_ERROR; } } return (Error_Code); } /* END OF Convert_UTM_To_Geodetic */ direwolf-1.5+dfsg/geotranz/utm.h000066400000000000000000000152431347750676600167520ustar00rootroot00000000000000#ifndef UTM_H #define UTM_H /***************************************************************************/ /* RSC IDENTIFIER: UTM * * ABSTRACT * * This component provides conversions between geodetic coordinates * (latitude and longitudes) and Universal Transverse Mercator (UTM) * projection (zone, hemisphere, easting, and northing) coordinates. * * ERROR HANDLING * * This component checks parameters for valid values. If an invalid value * is found, the error code is combined with the current error code using * the bitwise or. This combining allows multiple error codes to be * returned. The possible error codes are: * * UTM_NO_ERROR : No errors occurred in function * UTM_LAT_ERROR : Latitude outside of valid range * (-80.5 to 84.5 degrees) * UTM_LON_ERROR : Longitude outside of valid range * (-180 to 360 degrees) * UTM_EASTING_ERROR : Easting outside of valid range * (100,000 to 900,000 meters) * UTM_NORTHING_ERROR : Northing outside of valid range * (0 to 10,000,000 meters) * UTM_ZONE_ERROR : Zone outside of valid range (1 to 60) * UTM_HEMISPHERE_ERROR : Invalid hemisphere ('N' or 'S') * UTM_ZONE_OVERRIDE_ERROR: Zone outside of valid range * (1 to 60) and within 1 of 'natural' zone * UTM_A_ERROR : Semi-major axis less than or equal to zero * UTM_INV_F_ERROR : Inverse flattening outside of valid range * (250 to 350) * * REUSE NOTES * * UTM is intended for reuse by any application that performs a Universal * Transverse Mercator (UTM) projection or its inverse. * * REFERENCES * * Further information on UTM can be found in the Reuse Manual. * * UTM originated from : U.S. Army Topographic Engineering Center * Geospatial Information Division * 7701 Telegraph Road * Alexandria, VA 22310-3864 * * LICENSES * * None apply to this component. * * RESTRICTIONS * * UTM has no restrictions. * * ENVIRONMENT * * UTM was tested and certified in the following environments: * * 1. Solaris 2.5 with GCC, version 2.8.1 * 2. MSDOS with MS Visual C++, version 6 * * MODIFICATIONS * * Date Description * ---- ----------- * 10-02-97 Original Code * */ /***************************************************************************/ /* * DEFINES */ #define UTM_NO_ERROR 0x0000 #define UTM_LAT_ERROR 0x0001 #define UTM_LON_ERROR 0x0002 #define UTM_EASTING_ERROR 0x0004 #define UTM_NORTHING_ERROR 0x0008 #define UTM_ZONE_ERROR 0x0010 #define UTM_HEMISPHERE_ERROR 0x0020 #define UTM_ZONE_OVERRIDE_ERROR 0x0040 #define UTM_A_ERROR 0x0080 #define UTM_INV_F_ERROR 0x0100 /***************************************************************************/ /* * FUNCTION PROTOTYPES * for UTM.C */ /* ensure proper linkage to c++ programs */ #ifdef __cplusplus extern "C" { #endif long Set_UTM_Parameters(double a, double f, long override); /* * The function Set_UTM_Parameters receives the ellipsoid parameters and * UTM zone override parameter as inputs, and sets the corresponding state * variables. If any errors occur, the error code(s) are returned by the * function, otherwise UTM_NO_ERROR is returned. * * a : Semi-major axis of ellipsoid, in meters (input) * f : Flattening of ellipsoid (input) * override : UTM override zone, zero indicates no override (input) */ void Get_UTM_Parameters(double *a, double *f, long *override); /* * The function Get_UTM_Parameters returns the current ellipsoid * parameters and UTM zone override parameter. * * a : Semi-major axis of ellipsoid, in meters (output) * f : Flattening of ellipsoid (output) * override : UTM override zone, zero indicates no override (output) */ long Convert_Geodetic_To_UTM (double Latitude, double Longitude, long *Zone, char *Hemisphere, double *Easting, double *Northing); /* * The function Convert_Geodetic_To_UTM converts geodetic (latitude and * longitude) coordinates to UTM projection (zone, hemisphere, easting and * northing) coordinates according to the current ellipsoid and UTM zone * override parameters. If any errors occur, the error code(s) are returned * by the function, otherwise UTM_NO_ERROR is returned. * * Latitude : Latitude in radians (input) * Longitude : Longitude in radians (input) * Zone : UTM zone (output) * Hemisphere : North or South hemisphere (output) * Easting : Easting (X) in meters (output) * Northing : Northing (Y) in meters (output) */ long Convert_UTM_To_Geodetic(long Zone, char Hemisphere, double Easting, double Northing, double *Latitude, double *Longitude); /* * The function Convert_UTM_To_Geodetic converts UTM projection (zone, * hemisphere, easting and northing) coordinates to geodetic(latitude * and longitude) coordinates, according to the current ellipsoid * parameters. If any errors occur, the error code(s) are returned * by the function, otherwise UTM_NO_ERROR is returned. * * Zone : UTM zone (input) * Hemisphere : North or South hemisphere (input) * Easting : Easting (X) in meters (input) * Northing : Northing (Y) in meters (input) * Latitude : Latitude in radians (output) * Longitude : Longitude in radians (output) */ #ifdef __cplusplus } #endif #endif /* UTM_H */ direwolf-1.5+dfsg/grm_sym.h000066400000000000000000000526151347750676600157750ustar00rootroot00000000000000 /* * grm_sym.h * * Symbol codes for use in $PGRMWPL sentence. * * Copied from * Garmin Device Interface Specification * May 19, 2006 * Drawing Number: 001-00063-00 Rev. C */ typedef unsigned short symbol_type_t; enum symbol_type_e { /*--------------------------------------------------------------- Marine symbols ---------------------------------------------------------------*/ sym_anchor = 0, /* white anchor symbol */ sym_bell = 1, /* white bell symbol */ sym_diamond_grn = 2, /* green diamond symbol */ sym_diamond_red = 3, /* red diamond symbol */ sym_dive1 = 4, /* diver down flag 1 */ sym_dive2 = 5, /* diver down flag 2 */ sym_dollar = 6, /* white dollar symbol */ sym_fish = 7, /* white fish symbol */ sym_fuel = 8, /* white fuel symbol */ sym_horn = 9, /* white horn symbol */ sym_house = 10, /* white house symbol */ sym_knife = 11, /* white knife & fork symbol */ sym_light = 12, /* white light symbol */ sym_mug = 13, /* white mug symbol */ sym_skull = 14, /* white skull and crossbones symbol*/ sym_square_grn = 15, /* green square symbol */ sym_square_red = 16, /* red square symbol */ sym_wbuoy = 17, /* white buoy waypoint symbol */ sym_wpt_dot = 18, /* waypoint dot */ sym_wreck = 19, /* white wreck symbol */ sym_null = 20, /* null symbol (transparent) */ sym_mob = 21, /* man overboard symbol */ sym_buoy_ambr = 22, /* amber map buoy symbol */ sym_buoy_blck = 23, /* black map buoy symbol */ sym_buoy_blue = 24, /* blue map buoy symbol */ sym_buoy_grn = 25, /* green map buoy symbol */ sym_buoy_grn_red = 26, /* green/red map buoy symbol */ sym_buoy_grn_wht = 27, /* green/white map buoy symbol */ sym_buoy_orng = 28, /* orange map buoy symbol */ sym_buoy_red = 29, /* red map buoy symbol */ sym_buoy_red_grn = 30, /* red/green map buoy symbol */ sym_buoy_red_wht = 31, /* red/white map buoy symbol */ sym_buoy_violet = 32, /* violet map buoy symbol */ sym_buoy_wht = 33, /* white map buoy symbol */ sym_buoy_wht_grn = 34, /* white/green map buoy symbol */ sym_buoy_wht_red = 35, /* white/red map buoy symbol */ sym_dot = 36, /* white dot symbol */ sym_rbcn = 37, /* radio beacon symbol */ sym_boat_ramp = 150, /* boat ramp symbol */ sym_camp = 151, /* campground symbol */ sym_restrooms = 152, /* restrooms symbol */ sym_showers = 153, /* shower symbol */ sym_drinking_wtr = 154, /* drinking water symbol */ sym_phone = 155, /* telephone symbol */ sym_1st_aid = 156, /* first aid symbol */ sym_info = 157, /* information symbol */ sym_parking = 158, /* parking symbol */ sym_park = 159, /* park symbol */ sym_picnic = 160, /* picnic symbol */ sym_scenic = 161, /* scenic area symbol */ sym_skiing = 162, /* skiing symbol */ sym_swimming = 163, /* swimming symbol */ sym_dam = 164, /* dam symbol */ sym_controlled = 165, /* controlled area symbol */ sym_danger = 166, /* danger symbol */ sym_restricted = 167, /* restricted area symbol */ sym_null_2 = 168, /* null symbol */ sym_ball = 169, /* ball symbol */ sym_car = 170, /* car symbol */ sym_deer = 171, /* deer symbol */ sym_shpng_cart = 172, /* shopping cart symbol */ sym_lodging = 173, /* lodging symbol */ sym_mine = 174, /* mine symbol */ sym_trail_head = 175, /* trail head symbol */ sym_truck_stop = 176, /* truck stop symbol */ sym_user_exit = 177, /* user exit symbol */ sym_flag = 178, /* flag symbol */ sym_circle_x = 179, /* circle with x in the center */ sym_open_24hr = 180, /* open 24 hours symbol */ sym_fhs_facility = 181, /* U Fishing Hot Spots(tm) Facility */ sym_bot_cond = 182, /* Bottom Conditions */ sym_tide_pred_stn = 183, /* Tide/Current Prediction Station */ sym_anchor_prohib = 184, /* U anchor prohibited symbol */ sym_beacon = 185, /* U beacon symbol */ sym_coast_guard = 186, /* U coast guard symbol */ sym_reef = 187, /* U reef symbol */ sym_weedbed = 188, /* U weedbed symbol */ sym_dropoff = 189, /* U dropoff symbol */ sym_dock = 190, /* U dock symbol */ sym_marina = 191, /* U marina symbol */ sym_bait_tackle = 192, /* U bait and tackle symbol */ sym_stump = 193, /* U stump symbol */ /*--------------------------------------------------------------- User customizable symbols The values from sym_begin_custom to sym_end_custom inclusive are reserved for the identification of user customizable symbols. ---------------------------------------------------------------*/ sym_begin_custom = 7680, /* first user customizable symbol */ sym_end_custom = 8191, /* last user customizable symbol */ /*--------------------------------------------------------------- Land symbols ---------------------------------------------------------------*/ sym_is_hwy = 8192, /* interstate hwy symbol */ sym_us_hwy = 8193, /* us hwy symbol */ sym_st_hwy = 8194, /* state hwy symbol */ sym_mi_mrkr = 8195, /* mile marker symbol */ sym_trcbck = 8196, /* TracBack (feet) symbol */ sym_golf = 8197, /* golf symbol */ sym_sml_cty = 8198, /* small city symbol */ sym_med_cty = 8199, /* medium city symbol */ sym_lrg_cty = 8200, /* large city symbol */ sym_freeway = 8201, /* intl freeway hwy symbol */ sym_ntl_hwy = 8202, /* intl national hwy symbol */ sym_cap_cty = 8203, /* capitol city symbol (star) */ sym_amuse_pk = 8204, /* amusement park symbol */ sym_bowling = 8205, /* bowling symbol */ sym_car_rental = 8206, /* car rental symbol */ sym_car_repair = 8207, /* car repair symbol */ sym_fastfood = 8208, /* fast food symbol */ sym_fitness = 8209, /* fitness symbol */ sym_movie = 8210, /* movie symbol */ sym_museum = 8211, /* museum symbol */ sym_pharmacy = 8212, /* pharmacy symbol */ sym_pizza = 8213, /* pizza symbol */ sym_post_ofc = 8214, /* post office symbol */ sym_rv_park = 8215, /* RV park symbol */ sym_school = 8216, /* school symbol */ sym_stadium = 8217, /* stadium symbol */ sym_store = 8218, /* dept. store symbol */ sym_zoo = 8219, /* zoo symbol */ sym_gas_plus = 8220, /* convenience store symbol */ sym_faces = 8221, /* live theater symbol */ sym_ramp_int = 8222, /* ramp intersection symbol */ sym_st_int = 8223, /* street intersection symbol */ sym_weigh_sttn = 8226, /* inspection/weigh station symbol */ sym_toll_booth = 8227, /* toll booth symbol */ sym_elev_pt = 8228, /* elevation point symbol */ sym_ex_no_srvc = 8229, /* exit without services symbol */ sym_geo_place_mm = 8230, /* Geographic place name, man-made */ sym_geo_place_wtr = 8231, /* Geographic place name, water */ sym_geo_place_lnd = 8232, /* Geographic place name, land */ sym_bridge = 8233, /* bridge symbol */ sym_building = 8234, /* building symbol */ sym_cemetery = 8235, /* cemetery symbol */ sym_church = 8236, /* church symbol */ sym_civil = 8237, /* civil location symbol */ sym_crossing = 8238, /* crossing symbol */ sym_hist_town = 8239, /* historical town symbol */ sym_levee = 8240, /* levee symbol */ sym_military = 8241, /* military location symbol */ sym_oil_field = 8242, /* oil field symbol */ sym_tunnel = 8243, /* tunnel symbol */ sym_beach = 8244, /* beach symbol */ sym_forest = 8245, /* forest symbol */ sym_summit = 8246, /* summit symbol */ sym_lrg_ramp_int = 8247, /* large ramp intersection symbol */ sym_lrg_ex_no_srvc = 8248, /* large exit without services smbl */ sym_badge = 8249, /* police/official badge symbol */ sym_cards = 8250, /* gambling/casino symbol */ sym_snowski = 8251, /* snow skiing symbol */ sym_iceskate = 8252, /* ice skating symbol */ sym_wrecker = 8253, /* tow truck (wrecker) symbol */ sym_border = 8254, /* border crossing (port of entry) */ sym_geocache = 8255, /* geocache location */ sym_geocache_fnd = 8256, /* found geocache */ sym_cntct_smiley = 8257, /* Rino contact symbol, "smiley" */ sym_cntct_ball_cap = 8258, /* Rino contact symbol, "ball cap" */ sym_cntct_big_ears = 8259, /* Rino contact symbol, "big ear" */ sym_cntct_spike = 8260, /* Rino contact symbol, "spike" */ sym_cntct_goatee = 8261, /* Rino contact symbol, "goatee" */ sym_cntct_afro = 8262, /* Rino contact symbol, "afro" */ sym_cntct_dreads = 8263, /* Rino contact symbol, "dreads" */ sym_cntct_female1 = 8264, /* Rino contact symbol, "female 1" */ sym_cntct_female2 = 8265, /* Rino contact symbol, "female 2" */ sym_cntct_female3 = 8266, /* Rino contact symbol, "female 3" */ sym_cntct_ranger = 8267, /* Rino contact symbol, "ranger" */ sym_cntct_kung_fu = 8268, /* Rino contact symbol, "kung fu" */ sym_cntct_sumo = 8269, /* Rino contact symbol, "sumo" */ sym_cntct_pirate = 8270, /* Rino contact symbol, "pirate" */ sym_cntct_biker = 8271, /* Rino contact symbol, "biker" */ sym_cntct_alien = 8272, /* Rino contact symbol, "alien" */ sym_cntct_bug = 8273, /* Rino contact symbol, "bug" */ sym_cntct_cat = 8274, /* Rino contact symbol, "cat" */ sym_cntct_dog = 8275, /* Rino contact symbol, "dog" */ sym_cntct_pig = 8276, /* Rino contact symbol, "pig" */ sym_hydrant = 8282, /* water hydrant symbol */ sym_flag_blue = 8284, /* blue flag symbol */ sym_flag_green = 8285, /* green flag symbol */ sym_flag_red = 8286, /* red flag symbol */ sym_pin_blue = 8287, /* blue pin symbol */ sym_pin_green = 8288, /* green pin symbol */ sym_pin_red = 8289, /* red pin symbol */ sym_block_blue = 8290, /* blue block symbol */ sym_block_green = 8291, /* green block symbol */ sym_block_red = 8292, /* red block symbol */ sym_bike_trail = 8293, /* bike trail symbol */ sym_circle_red = 8294, /* red circle symbol */ sym_circle_green = 8295, /* green circle symbol */ sym_circle_blue = 8296, /* blue circle symbol */ sym_diamond_blue = 8299, /* blue diamond symbol */ sym_oval_red = 8300, /* red oval symbol */ sym_oval_green = 8301, /* green oval symbol */ sym_oval_blue = 8302, /* blue oval symbol */ sym_rect_red = 8303, /* red rectangle symbol */ sym_rect_green = 8304, /* green rectangle symbol */ sym_rect_blue = 8305, /* blue rectangle symbol */ sym_square_blue = 8308, /* blue square symbol */ sym_letter_a_red = 8309, /* red letter 'A' symbol */ sym_letter_b_red = 8310, /* red letter 'B' symbol */ sym_letter_c_red = 8311, /* red letter 'C' symbol */ sym_letter_d_red = 8312, /* red letter 'D' symbol */ sym_letter_a_green = 8313, /* green letter 'A' symbol */ sym_letter_c_green = 8314, /* green letter 'C' symbol */ sym_letter_b_green = 8315, /* green letter 'B' symbol */ sym_letter_d_green = 8316, /* green letter 'D' symbol */ sym_letter_a_blue = 8317, /* blue letter 'A' symbol */ sym_letter_b_blue = 8318, /* blue letter 'B' symbol */ sym_letter_c_blue = 8319, /* blue letter 'C' symbol */ sym_letter_d_blue = 8320, /* blue letter 'D' symbol */ sym_number_0_red = 8321, /* red number '0' symbol */ sym_number_1_red = 8322, /* red number '1' symbol */ sym_number_2_red = 8323, /* red number '2' symbol */ sym_number_3_red = 8324, /* red number '3' symbol */ sym_number_4_red = 8325, /* red number '4' symbol */ sym_number_5_red = 8326, /* red number '5' symbol */ sym_number_6_red = 8327, /* red number '6' symbol */ sym_number_7_red = 8328, /* red number '7' symbol */ sym_number_8_red = 8329, /* red number '8' symbol */ sym_number_9_red = 8330, /* red number '9' symbol */ sym_number_0_green = 8331, /* green number '0' symbol */ sym_number_1_green = 8332, /* green number '1' symbol */ sym_number_2_green = 8333, /* green number '2' symbol */ sym_number_3_green = 8334, /* green number '3' symbol */ sym_number_4_green = 8335, /* green number '4' symbol */ sym_number_5_green = 8336, /* green number '5' symbol */ sym_number_6_green = 8337, /* green number '6' symbol */ sym_number_7_green = 8338, /* green number '7' symbol */ sym_number_8_green = 8339, /* green number '8' symbol */ sym_number_9_green = 8340, /* green number '9' symbol */ sym_number_0_blue = 8341, /* blue number '0' symbol */ sym_number_1_blue = 8342, /* blue number '1' symbol */ sym_number_2_blue = 8343, /* blue number '2' symbol */ sym_number_3_blue = 8344, /* blue number '3' symbol */ sym_number_4_blue = 8345, /* blue number '4' symbol */ sym_number_5_blue = 8346, /* blue number '5' symbol */ sym_number_6_blue = 8347, /* blue number '6' symbol */ sym_number_7_blue = 8348, /* blue number '7' symbol */ sym_number_8_blue = 8349, /* blue number '8' symbol */ sym_number_9_blue = 8350, /* blue number '9' symbol */ sym_triangle_blue = 8351, /* blue triangle symbol */ sym_triangle_green = 8352, /* green triangle symbol */ sym_triangle_red = 8353, /* red triangle symbol */ sym_food_asian = 8359, /* asian food symbol */ sym_food_deli = 8360, /* deli symbol */ sym_food_italian = 8361, /* italian food symbol */ sym_food_seafood = 8362, /* seafood symbol */ sym_food_steak = 8363, /* steak symbol */ /*--------------------------------------------------------------- Aviation symbols ---------------------------------------------------------------*/ sym_airport = 16384, /* airport symbol */ sym_int = 16385, /* intersection symbol */ sym_ndb = 16386, /* non-directional beacon symbol */ sym_vor = 16387, /* VHF omni-range symbol */ sym_heliport = 16388, /* heliport symbol */ sym_private = 16389, /* private field symbol */ sym_soft_fld = 16390, /* soft field symbol */ sym_tall_tower = 16391, /* tall tower symbol */ sym_short_tower = 16392, /* short tower symbol */ sym_glider = 16393, /* glider symbol */ sym_ultralight = 16394, /* ultralight symbol */ sym_parachute = 16395, /* parachute symbol */ sym_vortac = 16396, /* VOR/TACAN symbol */ sym_vordme = 16397, /* VOR-DME symbol */ sym_faf = 16398, /* first approach fix */ sym_lom = 16399, /* localizer outer marker */ sym_map = 16400, /* missed approach point */ sym_tacan = 16401, /* TACAN symbol */ sym_seaplane = 16402, /* Seaplane Base */ }; /* * Mapping from APRS symbols to Garmin. */ // TODO: NEEDS MORE WORK!!! #define SYMTAB_SIZE 95 #define sym_default sym_diamond_grn static const symbol_type_t grm_primary_symtab[SYMTAB_SIZE] = { sym_default, // 00 --no-symbol-- sym_cntct_ranger, // ! 01 Police, Sheriff sym_default, // " 02 reserved (was rain) sym_rbcn, // # 03 DIGI (white center) sym_phone, // $ 04 PHONE sym_rbcn, // % 05 DX CLUSTER sym_rbcn, // & 06 HF GATEway sym_glider, // ' 07 Small AIRCRAFT sym_rbcn, // ( 08 Mobile Satellite Station sym_default, // ) 09 Wheelchair (handicapped) sym_car, // * 10 SnowMobile sym_1st_aid, // + 11 Red Cross sym_cntct_ball_cap, // , 12 Boy Scouts sym_house, // - 13 House QTH (VHF) sym_default, // . 14 X sym_default, // / 15 Red Dot sym_default, // 0 16 # circle (obsolete) sym_default, // 1 17 TBD sym_default, // 2 18 TBD sym_default, // 3 19 TBD sym_default, // 4 20 TBD sym_default, // 5 21 TBD sym_default, // 6 22 TBD sym_default, // 7 23 TBD sym_default, // 8 24 TBD sym_default, // 9 25 TBD sym_default, // : 26 FIRE sym_camp, // ; 27 Campground (Portable ops) sym_cntct_biker, // < 28 Motorcycle sym_default, // = 29 RAILROAD ENGINE sym_car, // > 30 CAR sym_default, // ? 31 SERVER for Files sym_default, // @ 32 HC FUTURE predict (dot) sym_1st_aid, // A 33 Aid Station sym_rbcn, // B 34 BBS or PBBS sym_boat_ramp, // C 35 Canoe sym_default, // D 36 sym_default, // E 37 EYEBALL (Eye catcher!) sym_default, // F 38 Farm Vehicle (tractor) sym_default, // G 39 Grid Square (6 digit) sym_lodging, // H 40 HOTEL (blue bed symbol) sym_rbcn, // I 41 TcpIp on air network stn sym_default, // J 42 sym_school, // K 43 School sym_default, // L 44 PC user sym_default, // M 45 MacAPRS sym_default, // N 46 NTS Station sym_parachute, // O 47 BALLOON sym_cntct_ranger, // P 48 Police sym_default, // Q 49 TBD sym_rv_park, // R 50 REC. VEHICLE sym_glider, // S 51 SHUTTLE sym_default, // T 52 SSTV sym_car, // U 53 BUS sym_cntct_biker, // V 54 ATV sym_default, // W 55 National WX Service Site sym_default, // X 56 HELO sym_default, // Y 57 YACHT (sail) sym_default, // Z 58 WinAPRS sym_cntct_smiley, // [ 59 Human/Person (HT) sym_triangle_green, // \ 60 TRIANGLE(DF station) sym_default, // ] 61 MAIL/PostOffice(was PBBS) sym_glider, // ^ 62 LARGE AIRCRAFT sym_default, // _ 63 WEATHER Station (blue) sym_rbcn, // ` 64 Dish Antenna sym_1st_aid, // a 65 AMBULANCE sym_cntct_biker, // b 66 BIKE sym_default, // c 67 Incident Command Post sym_hydrant, // d 68 Fire dept sym_deer, // e 69 HORSE (equestrian) sym_hydrant, // f 70 FIRE TRUCK sym_glider, // g 71 Glider sym_1st_aid, // h 72 HOSPITAL sym_default, // i 73 IOTA (islands on the air) sym_car, // j 74 JEEP sym_car, // k 75 TRUCK sym_default, // l 76 Laptop sym_rbcn, // m 77 Mic-E Repeater sym_default, // n 78 Node (black bulls-eye) sym_default, // o 79 EOC sym_cntct_dog, // p 80 ROVER (puppy, or dog) sym_default, // q 81 GRID SQ shown above 128 m sym_rbcn, // r 82 Repeater sym_default, // s 83 SHIP (pwr boat) sym_truck_stop, // t 84 TRUCK STOP sym_truck_stop, // u 85 TRUCK (18 wheeler) sym_car, // v 86 VAN sym_drinking_wtr, // w 87 WATER station sym_default, // x 88 xAPRS (Unix) sym_tall_tower, // y 89 YAGI @ QTH sym_default, // z 90 TBD sym_default, // { 91 sym_default, // | 92 TNC Stream Switch sym_default, // } 93 sym_default }; // ~ 94 TNC Stream Switch static const symbol_type_t grm_alternate_symtab[SYMTAB_SIZE] = { sym_default, // 00 --no-symbol-- sym_default, // ! 01 EMERGENCY (!) sym_default, // " 02 reserved sym_default, // # 03 OVERLAY DIGI (green star) sym_default, // $ 04 Bank or ATM (green box) sym_default, // % 05 Power Plant with overlay sym_rbcn, // & 06 I=Igte IGate R=RX T=1hopTX 2=2hopTX sym_default, // ' 07 Crash (& now Incident sites) sym_default, // ( 08 CLOUDY (other clouds w ovrly) sym_hydrant, // ) 09 Firenet MEO, MODIS Earth Obs. sym_default, // * 10 SNOW (& future ovrly codes) sym_default, // + 11 Church sym_cntct_female1, // , 12 Girl Scouts sym_house, // - 13 House (H=HF) (O = Op Present) sym_default, // . 14 Ambiguous (Big Question mark) sym_default, // / 15 Waypoint Destination sym_default, // 0 16 CIRCLE (E/I/W=IRLP/Echolink/WIRES) sym_default, // 1 17 sym_default, // 2 18 sym_default, // 3 19 sym_default, // 4 20 sym_default, // 5 21 sym_default, // 6 22 sym_default, // 7 23 sym_default, // 8 24 802.11 or other network node sym_default, // 9 25 Gas Station (blue pump) sym_default, // : 26 Hail (& future ovrly codes) sym_park, // ; 27 Park/Picnic area sym_default, // < 28 ADVISORY (one WX flag) sym_rbcn, // = 29 APRStt Touchtone (DTMF users) sym_car, // > 30 OVERLAYED CAR sym_default, // ? 31 INFO Kiosk (Blue box with ?) sym_default, // @ 32 HURICANE/Trop-Storm sym_default, // A 33 overlayBOX DTMF & RFID & XO sym_default, // B 34 Blwng Snow (& future codes) sym_coast_guard, // C 35 Coast Guard sym_default, // D 36 Drizzle (proposed APRStt) sym_default, // E 37 Smoke (& other vis codes) sym_default, // F 38 Freezng rain (&future codes) sym_default, // G 39 Snow Shwr (& future ovrlys) sym_default, // H 40 Haze (& Overlay Hazards) sym_default, // I 41 Rain Shower sym_default, // J 42 Lightening (& future ovrlys) sym_rbcn, // K 43 Kenwood HT (W) sym_light, // L 44 Lighthouse sym_default, // M 45 MARS (A=Army,N=Navy,F=AF) sym_default, // N 46 Navigation Buoy sym_default, // O 47 Rocket sym_default, // P 48 Parking sym_default, // Q 49 QUAKE sym_default, // R 50 Restaurant sym_rbcn, // S 51 Satellite/Pacsat sym_default, // T 52 Thunderstorm sym_default, // U 53 SUNNY sym_default, // V 54 VORTAC Nav Aid sym_default, // W 55 # NWS site (NWS options) sym_pharmacy, // X 56 Pharmacy Rx (Apothicary) sym_rbcn, // Y 57 Radios and devices sym_default, // Z 58 sym_default, // [ 59 W.Cloud (& humans w Ovrly) sym_default, // \ 60 New overlayable GPS symbol sym_default, // ] 61 sym_glider, // ^ 62 # Aircraft (shows heading) sym_default, // _ 63 # WX site (green digi) sym_default, // ` 64 Rain (all types w ovrly) sym_default, // a 65 ARRL, ARES, WinLINK sym_default, // b 66 Blwng Dst/Snd (& others) sym_default, // c 67 CD triangle RACES/SATERN/etc sym_default, // d 68 DX spot by callsign sym_default, // e 69 Sleet (& future ovrly codes) sym_default, // f 70 Funnel Cloud sym_default, // g 71 Gale Flags sym_default, // h 72 Store. or HAMFST Hh=HAM store sym_default, // i 73 BOX or points of Interest sym_default, // j 74 WorkZone (Steam Shovel) sym_car, // k 75 Special Vehicle SUV,ATV,4x4 sym_default, // l 76 Areas (box,circles,etc) sym_default, // m 77 Value Sign (3 digit display) sym_default, // n 78 OVERLAY TRIANGLE sym_default, // o 79 small circle sym_default, // p 80 Prtly Cldy (& future ovrlys) sym_default, // q 81 sym_restrooms, // r 82 Restrooms sym_default, // s 83 OVERLAY SHIP/boat (top view) sym_default, // t 84 Tornado sym_car, // u 85 OVERLAYED TRUCK sym_car, // v 86 OVERLAYED Van sym_default, // w 87 Flooding sym_wreck, // x 88 Wreck or Obstruction ->X<- sym_default, // y 89 Skywarn sym_default, // z 90 OVERLAYED Shelter sym_default, // { 91 Fog (& future ovrly codes) sym_default, // | 92 TNC Stream Switch sym_default, // } 93 sym_default }; // ~ 94 TNC Stream Switch direwolf-1.5+dfsg/hdlc_rec.c000066400000000000000000000422261347750676600160530ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /******************************************************************************** * * File: hdlc_rec.c * * Purpose: Extract HDLC frames from a stream of bits. * *******************************************************************************/ #include "direwolf.h" #include #include #include #include "demod.h" #include "hdlc_rec.h" #include "hdlc_rec2.h" #include "fcs_calc.h" #include "textcolor.h" #include "ax25_pad.h" #include "rrbb.h" #include "multi_modem.h" #include "demod_9600.h" /* for descramble() */ #include "ptt.h" //#define TEST 1 /* Define for unit testing. */ //#define DEBUG3 1 /* monitor the data detect signal. */ /* * Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS. */ #define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2) #define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2) /* * This is the current state of the HDLC decoder. * * It is possible to run multiple decoders concurrently by * having a separate set of state variables for each. * * Should have a reset function instead of initializations here. */ struct hdlc_state_s { int prev_raw; /* Keep track of previous bit so */ /* we can look for transitions. */ /* Should be only 0 or 1. */ int lfsr; /* Descrambler shift register for 9600 baud. */ int prev_descram; /* Previous descrambled for 9600 baud. */ unsigned char pat_det; /* 8 bit pattern detector shift register. */ /* See below for more details. */ unsigned int flag4_det; /* Last 32 raw bits to look for 4 */ /* flag patterns in a row. */ unsigned char oacc; /* Accumulator for building up an octet. */ int olen; /* Number of bits in oacc. */ /* When this reaches 8, oacc is copied */ /* to the frame buffer and olen is zeroed. */ /* The value of -1 is a special case meaning */ /* bits should not be accumulated. */ unsigned char frame_buf[MAX_FRAME_LEN]; /* One frame is kept here. */ int frame_len; /* Number of octets in frame_buf. */ /* Should be in range of 0 .. MAX_FRAME_LEN. */ int data_detect; /* True when HDLC data is detected. */ /* This will not be triggered by voice or other */ /* noise or even tones. */ rrbb_t rrbb; /* Handle for bit array for raw received bits. */ }; static struct hdlc_state_s hdlc_state[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; static int num_subchan[MAX_CHANS]; //TODO1.2 use ptr rather than copy. static int composite_dcd[MAX_CHANS][MAX_SUBCHANS+1]; /*********************************************************************************** * * Name: hdlc_rec_init * * Purpose: Call once at the beginning to initialize. * * Inputs: None. * ***********************************************************************************/ static int was_init = 0; void hdlc_rec_init (struct audio_s *pa) { int ch, sub, slice; struct hdlc_state_s *H; //text_color_set(DW_COLOR_DEBUG); //dw_printf ("hdlc_rec_init (%p) \n", pa); assert (pa != NULL); memset (composite_dcd, 0, sizeof(composite_dcd)); for (ch = 0; ch < MAX_CHANS; ch++) { if (pa->achan[ch].valid) { num_subchan[ch] = pa->achan[ch].num_subchan; assert (num_subchan[ch] >= 1 && num_subchan[ch] <= MAX_SUBCHANS); for (sub = 0; sub < num_subchan[ch]; sub++) { for (slice = 0; slice < MAX_SLICERS; slice++) { H = &hdlc_state[ch][sub][slice]; H->olen = -1; // TODO: FIX13 wasteful if not needed. // Should loop on number of slicers, not max. H->rrbb = rrbb_new(ch, sub, slice, pa->achan[ch].modem_type == MODEM_SCRAMBLE, H->lfsr, H->prev_descram); } } } } hdlc_rec2_init (pa); was_init = 1; } /*********************************************************************************** * * Name: hdlc_rec_bit * * Purpose: Extract HDLC frames from a stream of bits. * * Inputs: chan - Channel number. * * subchan - This allows multiple demodulators per channel. * * slice - Allows multiple slicers per demodulator (subchannel). * * raw - One bit from the demodulator. * should be 0 or 1. * * is_scrambled - Is the data scrambled? * * descram_state - Current descrambler state. (not used - remove) * * * Description: This is called once for each received bit. * For each valid frame, process_rec_frame() * is called for further processing. * ***********************************************************************************/ // TODO: int not_used_remove void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove) { int dbit; /* Data bit after undoing NRZI. */ /* Should be only 0 or 1. */ struct hdlc_state_s *H; assert (was_init == 1); assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); /* * Different state information for each channel / subchannel / slice. */ H = &hdlc_state[chan][subchan][slice]; /* * Using NRZI encoding, * A '0' bit is represented by an inversion since previous bit. * A '1' bit is represented by no change. */ if (is_scrambled) { int descram; descram = descramble(raw, &(H->lfsr)); dbit = (descram == H->prev_descram); H->prev_descram = descram; H->prev_raw = raw; } else { dbit = (raw == H->prev_raw); H->prev_raw = raw; } /* * Octets are sent LSB first. * Shift the most recent 8 bits thru the pattern detector. */ H->pat_det >>= 1; if (dbit) { H->pat_det |= 0x80; } H->flag4_det >>= 1; if (dbit) { H->flag4_det |= 0x80000000; } /* * "Data Carrier detect" function based on data patterns rather than * audio signal strength. * * Idle time, at beginning of transmission should be filled * with the special "flag" characters. * * Idle time of all zero bits (alternating tones at maximum rate) * has also been observed rarely. It is easy to understand the reasoning. * The tones alternate at the maximum rate, making it symmetrical and providing * the most opportunity for the PLL to lock on to the edges. * It also violates the published protocol spec. * * Recognize zero(s) followed by a single flag even though it violates the spec. * * It has been reported that the TinyTrak4 does this. * https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/1207 */ /* * Originally, this looked for 4 flags in a row or 3 zeros and a flag. * Is that too fussy? * Here are the numbers of start of DCD for our favorite Track 2 test. * * 7e7e7e7e 504 7e000000 32 * 7e7e7e-- 513 7e0000-- 33 * 7e7e---- 555 7e00---- 42 * 7e------ 2088 * * I don't think we want to look for a single flag because that would * make DCD too sensitive to noise and it would interfere with waiting for a * clear channel to transmit. Even a two byte match causes a lot of flickering * when listening to live signals. Let's try 3 and see how that works out. */ //if (H->flag4_det == 0x7e7e7e7e) { if ((H->flag4_det & 0xffffff00) == 0x7e7e7e00) { //if ((H->flag4_det & 0xffff0000) == 0x7e7e0000) { if ( ! H->data_detect) { H->data_detect = 1; dcd_change (chan, subchan, slice, 1); } } //else if (H->flag4_det == 0x7e000000) { else if ((H->flag4_det & 0xffffff00) == 0x7e000000) { //else if ((H->flag4_det & 0xffff0000) == 0x7e000000) { if ( ! H->data_detect) { H->data_detect = 1; dcd_change (chan, subchan, slice, 1); } } /* * Loss of signal should result in lack of transitions. * (all '1' bits) for at least a little while. * * When this was written, I was only concerned about 1200 baud. * For 9600, added later, there is a (de)scrambling function. * So if there is no change in the signal, we would get pseudo random bits here. * Maybe we need to put in another check earlier so DCD is not held on too long * after loss of signal for 9600. * No, that would not be a good idea. part of a valid frame, when scrambled, * could have seven or more "1" bits in a row. * Needs more study. */ if (H->pat_det == 0xff) { if ( H->data_detect ) { H->data_detect = 0; dcd_change (chan, subchan, slice, 0); } } /* * End of data carrier detect. * * The rest is concerned with framing. */ rrbb_append_bit (H->rrbb, raw); if (H->pat_det == 0x7e) { rrbb_chop8 (H->rrbb); /* * The special pattern 01111110 indicates beginning and ending of a frame. * If we have an adequate number of whole octets, it is a candidate for * further processing. * * It might look odd that olen is being tested for 7 instead of 0. * This is because oacc would already have 7 bits from the special * "flag" pattern before it is detected here. */ #if OLD_WAY #if TEST text_color_set(DW_COLOR_DEBUG); dw_printf ("\nfound flag, olen = %d, frame_len = %d\n", olen, frame_len); #endif if (H->olen == 7 && H->frame_len >= MIN_FRAME_LEN) { unsigned short actual_fcs, expected_fcs; #if TEST int j; dw_printf ("TRADITIONAL: frame len = %d\n", H->frame_len); for (j=0; jframe_len; j++) { dw_printf (" %02x", H->frame_buf[j]); } dw_printf ("\n"); #endif /* Check FCS, low byte first, and process... */ /* Alternatively, it is possible to include the two FCS bytes */ /* in the CRC calculation and look for a magic constant. */ /* That would be easier in the case where the CRC is being */ /* accumulated along the way as the octets are received. */ /* I think making a second pass over it and comparing is */ /* easier to understand. */ actual_fcs = H->frame_buf[H->frame_len-2] | (H->frame_buf[H->frame_len-1] << 8); expected_fcs = fcs_calc (H->frame_buf, H->frame_len - 2); if (actual_fcs == expected_fcs) { alevel_t alevel = demod_get_audio_level (chan, subchan); multi_modem_process_rec_frame (chan, subchan, slice, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE); /* len-2 to remove FCS. */ } else { #if TEST dw_printf ("*** actual fcs = %04x, expected fcs = %04x ***\n", actual_fcs, expected_fcs); #endif } } #else /* * New way - Decode the raw bits in later step. */ #if TEST text_color_set(DW_COLOR_DEBUG); dw_printf ("\nfound flag, channel %d.%d, %d bits in frame\n", chan, subchan, rrbb_get_len(H->rrbb) - 1); #endif if (rrbb_get_len(H->rrbb) >= MIN_FRAME_LEN * 8) { alevel_t alevel = demod_get_audio_level (chan, subchan); rrbb_set_audio_level (H->rrbb, alevel); hdlc_rec2_block (H->rrbb); /* Now owned by someone else who will free it. */ H->rrbb = rrbb_new (chan, subchan, slice, is_scrambled, H->lfsr, H->prev_descram); /* Allocate a new one. */ } else { rrbb_clear (H->rrbb, is_scrambled, H->lfsr, H->prev_descram); } H->olen = 0; /* Allow accumulation of octets. */ H->frame_len = 0; rrbb_append_bit (H->rrbb, H->prev_raw); /* Last bit of flag. Needed to get first data bit. */ /* Now that we are saving other initial state information, */ /* it would be sensible to do the same for this instead */ /* of lumping it in with the frame data bits. */ #endif } //#define EXPERIMENT12B 1 #if EXPERIMENT12B else if (H->pat_det == 0xff) { /* * Valid data will never have seven 1 bits in a row. * * 11111110 * * This indicates loss of signal. * But we will let it slip thru because it might diminish * our single bit fixup effort. Instead give up on frame * only when we see eight 1 bits in a row. * * 11111111 * * What is the impact? No difference. * * Before: atest -P E -F 1 ../02_Track_2.wav = 1003 * After: atest -P E -F 1 ../02_Track_2.wav = 1003 */ #else else if (H->pat_det == 0xfe) { /* * Valid data will never have 7 one bits in a row. * * 11111110 * * This indicates loss of signal. */ #endif H->olen = -1; /* Stop accumulating octets. */ H->frame_len = 0; /* Discard anything in progress. */ rrbb_clear (H->rrbb, is_scrambled, H->lfsr, H->prev_descram); } else if ( (H->pat_det & 0xfc) == 0x7c ) { /* * If we have five '1' bits in a row, followed by a '0' bit, * * 0111110xx * * the current '0' bit should be discarded because it was added for * "bit stuffing." */ ; } else { /* * In all other cases, accumulate bits into octets, and complete octets * into the frame buffer. */ if (H->olen >= 0) { H->oacc >>= 1; if (dbit) { H->oacc |= 0x80; } H->olen++; if (H->olen == 8) { H->olen = 0; if (H->frame_len < MAX_FRAME_LEN) { H->frame_buf[H->frame_len] = H->oacc; H->frame_len++; } } } } } /*------------------------------------------------------------------- * * Name: hdlc_rec_gathering * * Purpose: Report whether bits are currently being gathered into a frame. * This is used to influence the PLL inertia. * The idea is that the PLL should be a little more agreeable to * synchronize with the incoming data stream when not in a frame * and resist changing a little more when capturing a frame. * * Inputs: chan * subchan * slice * * Returns: True if we are currently gathering bits. * In this case we want the PLL to have more inertia. * * Discussion: This simply returns the data carrier detect state. * A couple other variations were tried but turned out to * be slightly worse. * *--------------------------------------------------------------------*/ int hdlc_rec_gathering (int chan, int subchan, int slice) { assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); // Counts from Track 1 & Track 2 // data_detect 992 988 // olen>=0 992 985 // OR-ed 992 985 return ( hdlc_state[chan][subchan][slice].data_detect ); } /* end hdlc_rec_gathering */ /*------------------------------------------------------------------- * * Name: dcd_change * * Purpose: Combine DCD states of all subchannels/ into an overall * state for the channel. * * Inputs: chan * * subchan 0 to MAX_SUBCHANS-1 for HDLC. * SPECIAL CASE --> MAX_SUBCHANS for DTMF decoder. * * slice slicer number, 0 .. MAX_SLICERS - 1. * * state 1 for active, 0 for not. * * Returns: None. Use hdlc_rec_data_detect_any to retrieve result. * * Description: DCD for the channel is active if ANY of the subchannels/slices * are active. Update the DCD indicator. * * version 1.3: Add DTMF detection into the final result. * This is now called from dtmf.c too. * *--------------------------------------------------------------------*/ void dcd_change (int chan, int subchan, int slice, int state) { int old, new; assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan <= MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); assert (state == 0 || state == 1); #if DEBUG3 text_color_set(DW_COLOR_DEBUG); dw_printf ("DCD %d.%d.%d = %d \n", chan, subchan, slice, state); #endif old = hdlc_rec_data_detect_any(chan); if (state) { composite_dcd[chan][subchan] |= (1 << slice); } else { composite_dcd[chan][subchan] &= ~ (1 << slice); } new = hdlc_rec_data_detect_any(chan); if (new != old) { ptt_set (OCTYPE_DCD, chan, new); } } /*------------------------------------------------------------------- * * Name: hdlc_rec_data_detect_any * * Purpose: Determine if the radio channel is curently busy * with packet data. * This version doesn't care about voice or other sounds. * This is used by the transmit logic to transmit only * when the channel is clear. * * Inputs: chan - Audio channel. * * Returns: True if channel is busy (data detected) or * false if OK to transmit. * * * Description: We have two different versions here. * * hdlc_rec_data_detect_any sees if ANY of the decoders * for this channel are receving a signal. This is * used to determine whether the channel is clear and * we can transmit. This would apply to the 300 baud * HF SSB case where we have multiple decoders running * at the same time. The channel is busy if ANY of them * thinks the channel is busy. * * Version 1.3: New option for input signal to inhibit transmit. * *--------------------------------------------------------------------*/ int hdlc_rec_data_detect_any (int chan) { int sc; assert (chan >= 0 && chan < MAX_CHANS); for (sc = 0; sc < num_subchan[chan]; sc++) { if (composite_dcd[chan][sc] != 0) return (1); } if (get_input(ICTYPE_TXINH, chan) == 1) return (1); return (0); } /* end hdlc_rec_data_detect_any */ /* end hdlc_rec.c */ direwolf-1.5+dfsg/hdlc_rec.h000066400000000000000000000013111347750676600160460ustar00rootroot00000000000000 #include "audio.h" void hdlc_rec_init (struct audio_s *pa); void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, int descram_state); /* Provided elsewhere to process a complete frame. */ //void process_rec_frame (int chan, unsigned char *fbuf, int flen, int level); /* Is HLDC decoder is currently gathering bits into a frame? */ /* Similar to, but not exactly the same as, data carrier detect. */ /* We use this to influence the PLL inertia. */ int hdlc_rec_gathering (int chan, int subchan, int slice); /* Transmit needs to know when someone else is transmitting. */ void dcd_change (int chan, int subchan, int slice, int state); int hdlc_rec_data_detect_any (int chan); direwolf-1.5+dfsg/hdlc_rec2.c000066400000000000000000000704161347750676600161370ustar00rootroot00000000000000// This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /******************************************************************************** * * File: hdlc_rec2.c * * Purpose: Extract HDLC frame from a block of bits after someone * else has done the work of pulling it out from between * the special "flag" sequences. * * * New in version 1.1: * * Several enhancements provided by Fabrice FAURE: * * - Additional types of attempts to fix a bad CRC. * - Optimized code to reduce execution time. * - Improved detection of duplicate packets from different fixup attempts. * - Set limit on number of packets in fix up later queue. * * One of the new recovery attempt cases recovers three additional * packets that were lost before. The one thing I disagree with is * use of the word "swap" because that sounds like two things * are being exchanged for each other. I would prefer "flip" * or "invert" to describe changing a bit to the opposite state. * I took "swap" out of the user-visible messages but left the * rest of the source code as provided. * * Test results: We intentionally use the worst demodulator so there * is more opportunity to try to fix the frames. * * atest -P A -F n 02_Track_2.wav * * n description frames sec * -- ----------- ------ --- * 0 no attempt 963 40 error-free frames * 1 invert 1 979 41 16 more * 2 invert 2 982 42 3 more * 3 invert 3 982 42 no change * 4 remove 1 982 43 no change * 5 remove 2 982 43 no change * 6 remove 3 982 43 no change * 7 insert 1 982 45 no change * 8 insert 2 982 47 no change * 9 invert two sep 993 178 11 more, some visually obvious errors. * 10 invert many? 993 190 no change * 11 remove many 995 190 2 more, need to investigate in detail. * 12 remove two sep 995 201 no change * * Observations: The "insert" and "remove" techniques had no benefit. I would not expect them to. * We have a phase locked loop that attempts to track any slight variations in the * timing so we sample near the middle of the bit interval. Bits can get corrupted * by noise but not disappear or just appear. That would be a gap in the timing. * These should probably be removed in a future version. * * * Version 1.2: Now works for 9600 baud. * This was more complicated due to the data scrambling. * It was necessary to retain more initial state information after * the start flag octet. * * Version 1.3: Took out all of the "insert" and "remove" cases because they * offer no benenfit. * * Took out the delayed processing and just do it realtime. * Changed SWAP to INVERT because it is more descriptive. * *******************************************************************************/ #include "direwolf.h" #include #include #include #include //Optimize processing by accessing directly to decoded bits #define RRBB_C 1 #include "hdlc_rec2.h" #include "fcs_calc.h" #include "textcolor.h" #include "ax25_pad.h" #include "rrbb.h" #include "rdq.h" #include "multi_modem.h" #include "dtime_now.h" #include "demod_9600.h" /* for descramble() */ #include "audio.h" /* for struct audio_s */ //#include "ax25_pad.h" /* for AX25_MAX_ADDR_LEN */ //#define DEBUG 1 //#define DEBUGx 1 //#define DEBUG_LATER 1 /* Audio configuration. */ static struct audio_s *save_audio_config_p; /* * Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS. */ #define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2) #define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2) /* * This is the current state of the HDLC decoder. * * It is possible to run multiple decoders concurrently by * having a separate set of state variables for each. * * Should have a reset function instead of initializations here. */ struct hdlc_state_s { int prev_raw; /* Keep track of previous bit so */ /* we can look for transitions. */ /* Should be only 0 or 1. */ int is_scrambled; /* Set for 9600 baud. */ int lfsr; /* Descrambler shift register for 9600 baud. */ int prev_descram; /* Previous unscrambled for 9600 baud. */ unsigned char pat_det; /* 8 bit pattern detector shift register. */ /* See below for more details. */ unsigned char oacc; /* Accumulator for building up an octet. */ int olen; /* Number of bits in oacc. */ /* When this reaches 8, oacc is copied */ /* to the frame buffer and olen is zeroed. */ unsigned char frame_buf[MAX_FRAME_LEN]; /* One frame is kept here. */ int frame_len; /* Number of octets in frame_buf. */ /* Should be in range of 0 .. MAX_FRAME_LEN. */ }; static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall); static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel); static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped, enum sanity_e sanity_test); /*********************************************************************************** * * Name: hdlc_rec2_init * * Purpose: Initialization. * * Inputs: p_audio_config - Pointer to configuration settings. * This is what we care about for each channel. * * enum retry_e fix_bits; * Level of effort to recover from * a bad FCS on the frame. * 0 = no effort * 1 = try inverting a single bit * 2... = more techniques... * * enum sanity_e sanity_test; * Sanity test to apply when finding a good * CRC after changing one or more bits. * Must look like APRS, AX.25, or anything. * * int passall; * Allow thru even with bad CRC after exhausting * all fixup attempts. * * Description: Save pointer to configuration for later use. * ***********************************************************************************/ void hdlc_rec2_init (struct audio_s *p_audio_config) { save_audio_config_p = p_audio_config; } /*********************************************************************************** * * Name: hdlc_rec2_block * * Purpose: Extract HDLC frame from a stream of bits. * * Inputs: block - Handle for bit array. * * Description: The other (original) hdlc decoder took one bit at a time * right out of the demodulator. * * This is different in that it processes a block of bits * previously extracted from between two "flag" patterns. * * This allows us to try decoding the same received data more * than once. * * Version 1.2: Now works properly for G3RUH type scrambling. * ***********************************************************************************/ void hdlc_rec2_block (rrbb_t block) { int chan = rrbb_get_chan(block); int subchan = rrbb_get_subchan(block); int slice = rrbb_get_slice(block); alevel_t alevel = rrbb_get_audio_level(block); retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits; int passall = save_audio_config_p->achan[chan].passall; int ok; #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("\n--- try to decode ---\n"); #endif /* Create an empty retry configuration */ retry_conf_t retry_cfg; memset (&retry_cfg, 0, sizeof(retry_cfg)); /* * For our first attempt we don't try to alter any bits. * Still let it thru if passall AND no retries are desired. */ retry_cfg.type = RETRY_TYPE_NONE; retry_cfg.mode = RETRY_MODE_CONTIGUOUS; retry_cfg.retry = RETRY_NONE; retry_cfg.u_bits.contig.nr_bits = 0; retry_cfg.u_bits.contig.bit_idx = 0; ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, passall & (fix_bits == RETRY_NONE)); if (ok) { #if DEBUG text_color_set(DW_COLOR_INFO); dw_printf ("Got it the first time.\n"); #endif rrbb_delete (block); return; } /* * Not successful with frame in orginal form. * See if we can "fix" it. */ if (try_to_fix_quick_now (block, chan, subchan, slice, alevel)) { rrbb_delete (block); return; } if (passall) { /* Exhausted all desired fix up attempts. */ /* Let thru even with bad CRC. Of course, it still */ /* needs to be a minimum number of whole octets. */ ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, 1); rrbb_delete (block); } else { rrbb_delete (block); } } /* end hdlc_rec2_block */ /*********************************************************************************** * * Name: try_to_fix_quick_now * * Purpose: Attempt some quick fixups that don't take very long. * * Inputs: block - Stream of bits that might be a frame. * chan - Radio channel from which it was received. * subchan - Which demodulator when more than one per channel. * alevel - Audio level for later reporting. * * Global In: configuration fix_bits - Maximum level of fix up to attempt. * * RETRY_NONE (0) - Don't try any. * RETRY_INVERT_SINGLE (1) - Try inverting single bits. * etc. * * configuration passall - Let it thru with bad CRC after exhausting * all fixup attempts. * * * Returns: 1 for success. "try_decode" has passed the result along to the * processing step. * 0 for failure. Caller might continue with more aggressive attempts. * * Original: Some of the attempted fix up techniques are quick. * We will attempt them immediately after receiving the frame. * Others, that take time order N**2, will be done in a later section. * * Version 1.2: Now works properly for G3RUH type scrambling. * * Version 1.3: Removed the extra cases that didn't help. * The separated bit case is now handled immediately instead of * being thrown in a queue for later processing. * ***********************************************************************************/ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel) { int ok; int len, i; retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits; //int passall = save_audio_config_p->achan[chan].passall; len = rrbb_get_len(block); /* Prepare the retry configuration */ retry_conf_t retry_cfg; memset (&retry_cfg, 0, sizeof(retry_cfg)); /* Will modify only contiguous bits*/ retry_cfg.mode = RETRY_MODE_CONTIGUOUS; /* * Try inverting one bit. */ if (fix_bits < RETRY_INVERT_SINGLE) { /* Stop before single bit fix up. */ return 0; /* failure. */ } /* Try to swap one bit */ retry_cfg.type = RETRY_TYPE_SWAP; retry_cfg.retry = RETRY_INVERT_SINGLE; retry_cfg.u_bits.contig.nr_bits = 1; for (i=0; iachan[chan].fix_bits; int passall = save_audio_config_p->achan[chan].passall; #if DEBUG_LATER double tstart, tend; #endif retry_conf_t retry_cfg; memset (&retry_cfg, 0, sizeof(retry_cfg)); //len = rrbb_get_len(block); /* * All fix up attempts have failed. * Should we pass it along anyhow with a bad CRC? * Note that we still need a minimum number of whole octets. */ if (passall) { retry_cfg.type = RETRY_TYPE_NONE; retry_cfg.mode = RETRY_MODE_CONTIGUOUS; retry_cfg.retry = RETRY_NONE; retry_cfg.u_bits.contig.nr_bits = 0; retry_cfg.u_bits.contig.bit_idx = 0; ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, passall); return (ok); } return (0); } /* end hdlc_rec2_try_to_fix_later */ /* * Check if the specified index of bit has been modified with the current type of configuration * Provide a specific implementation for contiguous mode to optimize number of tests done in the loop */ inline static char is_contig_bit_modified(int bit_idx, retry_conf_t retry_conf) { int cont_bit_idx = retry_conf.u_bits.contig.bit_idx; int cont_nr_bits = retry_conf.u_bits.contig.nr_bits; if (bit_idx >= cont_bit_idx && (bit_idx < cont_bit_idx + cont_nr_bits )) return 1; else return 0; } /* * Check if the specified index of bit has been modified with the current type of configuration in separated bit index mode * Provide a specific implementation for separated mode to optimize number of tests done in the loop */ inline static char is_sep_bit_modified(int bit_idx, retry_conf_t retry_conf) { if (bit_idx == retry_conf.u_bits.sep.bit_idx_a || bit_idx == retry_conf.u_bits.sep.bit_idx_b || bit_idx == retry_conf.u_bits.sep.bit_idx_c) return 1; else return 0; } /*********************************************************************************** * * Name: try_decode * * Purpose: * * Inputs: block - Bit string that was collected between "flag" patterns. * * chan, subchan - where it came from. * * alevel - audio level for later reporting. * * retry_conf - Controls changes that will be attempted to get a good CRC. * * retry: * Level of effort to recover from a bad FCS on the frame. * RETRY_NONE = 0 * RETRY_INVERT_SINGLE = 1 * RETRY_INVERT_DOUBLE = 2 * RETRY_INVERT_TRIPLE = 3 * RETRY_INVERT_TWO_SEP = 4 * * mode: RETRY_MODE_CONTIGUOUS - change adjacent bits. * contig.bit_idx - first bit position * contig.nr_bits - number of bits * * RETRY_MODE_SEPARATED - change bits not next to each other. * sep.bit_idx_a - bit positions * sep.bit_idx_b - bit positions * sep.bit_idx_c - bit positions * * type: RETRY_TYPE_NONE - Make no changes. * RETRY_TYPE_SWAP - Try inverting. * * passall - All it thru even with bad CRC. * Valid only when no changes make. i.e. * retry == RETRY_NONE, type == RETRY_TYPE_NONE * * Returns: 1 = successfully extracted something. * 0 = failure. * ***********************************************************************************/ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall) { struct hdlc_state_s H; int blen; /* Block length in bits. */ int i; int raw; /* From demodulator. Should be 0 or 1. */ #if DEBUGx int crc_failed = 1; #endif int retry_conf_mode = retry_conf.mode; int retry_conf_type = retry_conf.type; int retry_conf_retry = retry_conf.retry; H.is_scrambled = rrbb_get_is_scrambled (block); H.prev_descram = rrbb_get_prev_descram (block); H.lfsr = rrbb_get_descram_state (block); H.prev_raw = rrbb_get_bit (block, 0); /* Actually last bit of the */ /* opening flag so we can derive the */ /* first data bit. */ /* Does this make sense? */ /* This is the last bit of the "flag" pattern. */ /* If it was corrupted we wouldn't have detected */ /* the start of frame. */ if ((retry_conf.mode == RETRY_MODE_CONTIGUOUS && is_contig_bit_modified(0, retry_conf)) || (retry_conf.mode == RETRY_MODE_SEPARATED && is_sep_bit_modified(0, retry_conf))) { H.prev_raw = ! H.prev_raw; } H.pat_det = 0; H.oacc = 0; H.olen = 0; H.frame_len = 0; blen = rrbb_get_len(block); #if DEBUGx text_color_set(DW_COLOR_DEBUG); if (retry_conf.type == RETRY_TYPE_NONE) dw_printf ("try_decode: blen=%d\n", blen); #endif for (i=1; i>= 1; /* * Using NRZI encoding, * A '0' bit is represented by an inversion since previous bit. * A '1' bit is represented by no change. * Note: this code can be factorized with the raw != H.prev_raw code at the cost of processing time */ int dbit ; if (H.is_scrambled) { int descram; descram = descramble(raw, &(H.lfsr)); dbit = (descram == H.prev_descram); H.prev_descram = descram; H.prev_raw = raw; } else { dbit = (raw == H.prev_raw); H.prev_raw = raw; } if (dbit) { H.pat_det |= 0x80; /* Valid data will never have 7 one bits in a row: exit. */ if (H.pat_det == 0xfe) { #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("try_decode: found abort, i=%d\n", i); #endif return 0; } H.oacc >>= 1; H.oacc |= 0x80; } else { /* The special pattern 01111110 indicates beginning and ending of a frame: exit. */ if (H.pat_det == 0x7e) { #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("try_decode: found flag, i=%d\n", i); #endif return 0; /* * If we have five '1' bits in a row, followed by a '0' bit, * * 011111xx * * the current '0' bit should be discarded because it was added for * "bit stuffing." */ } else if ( (H.pat_det >> 2) == 0x1f ) { continue; } H.oacc >>= 1; } /* * Now accumulate bits into octets, and complete octets * into the frame buffer. */ H.olen++; if (H.olen & 8) { H.olen = 0; if (H.frame_len < MAX_FRAME_LEN) { H.frame_buf[H.frame_len] = H.oacc; H.frame_len++; } } } /* end of loop on all bits in block */ /* * Do we have a minimum number of complete bytes? */ #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("try_decode: olen=%d, frame_len=%d\n", H.olen, H.frame_len); #endif if (H.olen == 0 && H.frame_len >= MIN_FRAME_LEN) { unsigned short actual_fcs, expected_fcs; #if DEBUGx if (retry_conf.type == RETRY_TYPE_NONE) { int j; text_color_set(DW_COLOR_DEBUG); dw_printf ("NEW WAY: frame len = %d\n", H.frame_len); for (j=0; jachan[chan].sanity_test)) { // TODO: Shouldn't be necessary to pass chan, subchan, alevel into // try_decode because we can obtain them from block. // Let's make sure that assumption is good... assert (rrbb_get_chan(block) == chan); assert (rrbb_get_subchan(block) == subchan); multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, retry_conf.retry); /* len-2 to remove FCS. */ return 1; /* success */ } else if (passall) { if (retry_conf_retry == RETRY_NONE && retry_conf_type == RETRY_TYPE_NONE) { //text_color_set(DW_COLOR_ERROR); //dw_printf ("ATTEMPTING PASSALL PROCESSING\n"); multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, RETRY_MAX); /* len-2 to remove FCS. */ return 1; /* success */ } else { text_color_set(DW_COLOR_ERROR); dw_printf ("try_decode: internal error passall = %d, retry_conf_retry = %d, retry_conf_type = %d\n", passall, retry_conf_retry, retry_conf_type); } } else { goto failure; } } else { #if DEBUGx crc_failed = 0; #endif goto failure; } failure: #if DEBUGx if (retry_conf.type == RETRY_TYPE_NONE ) { int j; text_color_set(DW_COLOR_ERROR); if (crc_failed) dw_printf ("CRC failed\n"); if (H.olen != 0) dw_printf ("Bad olen: %d \n", H.olen); else if (H.frame_len < MIN_FRAME_LEN) { dw_printf ("Frame too small\n"); goto end; } dw_printf ("FAILURE with frame: frame len = %d\n", H.frame_len); dw_printf ("\n"); for (j=0; j>1); } dw_printf ("\nORIG\n"); for (j=0; j 10) { #if DEBUGx text_color_set(DW_COLOR_ERROR); dw_printf ("sanity_check: FAILED. Too few or many addresses.\n"); #endif return 0; } /* * Addresses can contain only upper case letters, digits, and space. */ for (j=0; j> 1; addr[1] = buf[j+1] >> 1; addr[2] = buf[j+2] >> 1; addr[3] = buf[j+3] >> 1; addr[4] = buf[j+4] >> 1; addr[5] = buf[j+5] >> 1; addr[6] = '\0'; if ( (! isupper(addr[0]) && ! isdigit(addr[0])) || (! isupper(addr[1]) && ! isdigit(addr[1]) && addr[1] != ' ') || (! isupper(addr[2]) && ! isdigit(addr[2]) && addr[2] != ' ') || (! isupper(addr[3]) && ! isdigit(addr[3]) && addr[3] != ' ') || (! isupper(addr[4]) && ! isdigit(addr[4]) && addr[4] != ' ') || (! isupper(addr[5]) && ! isdigit(addr[5]) && addr[5] != ' ')) { #if DEBUGx text_color_set(DW_COLOR_ERROR); dw_printf ("sanity_check: FAILED. Invalid characters in addresses \"%s\"\n", addr); #endif return 0; } } /* * That's good enough for the AX.25 sanity check. * Continue below for additional APRS checking. */ if (sanity_test == SANITY_AX25) { return (1); } /* * The next two bytes should be 0x03 and 0xf0 for APRS. */ if (buf[alen] != 0x03 || buf[alen+1] != 0xf0) { return (0); } /* * Finally, look for bogus characters in the information part. * In theory, the bytes could have any values. * In practice, we find only printable ASCII characters and: * * 0x0a line feed * 0x0d carriage return * 0x1c MIC-E * 0x1d MIC-E * 0x1e MIC-E * 0x1f MIC-E * 0x7f MIC-E * 0x80 "{UIV32N}<0x0d><0x9f><0x80>" * 0x9f "{UIV32N}<0x0d><0x9f><0x80>" * 0xb0 degree symbol, ISO LATIN1 * (Note: UTF-8 uses two byte sequence 0xc2 0xb0.) * 0xbe invalid MIC-E encoding. * 0xf8 degree symbol, Microsoft code page 437 * * So, if we have something other than these (in English speaking countries!), * chances are that we have bogus data from twiddling the wrong bits. * * Notice that we shouldn't get here for good packets. This extra level * of checking happens only if we twiddled a couple of bits, possibly * creating bad data. We want to be very fussy. */ for (j=alen+2; j= 0x1c && ch <= 0x7f) || ch == 0x0a || ch == 0x0d || ch == 0x80 || ch == 0x9f || ch == 0xc2 || ch == 0xb0 || ch == 0xf8) ) { #if DEBUGx text_color_set(DW_COLOR_ERROR); dw_printf ("sanity_check: FAILED. Probably bogus info char 0x%02x\n", ch); #endif return 0; } } return 1; } /* end hdlc_rec2.c */ direwolf-1.5+dfsg/hdlc_rec2.h000066400000000000000000000026761347750676600161470ustar00rootroot00000000000000 #ifndef HDLC_REC2_H #define HDLC_REC2_H 1 #include "ax25_pad.h" /* for packet_t, alevel_t */ #include "rrbb.h" #include "audio.h" /* for struct audio_s */ typedef enum retry_mode_e { RETRY_MODE_CONTIGUOUS=0, RETRY_MODE_SEPARATED=1, } retry_mode_t; typedef enum retry_type_e { RETRY_TYPE_NONE=0, RETRY_TYPE_SWAP=1 } retry_type_t; typedef struct retry_conf_s { retry_t retry; retry_mode_t mode; retry_type_t type; union { struct { int bit_idx_a; /* */ int bit_idx_b; /* */ int bit_idx_c; /* */ } sep; /* RETRY_MODE_SEPARATED */ struct { int bit_idx; int nr_bits; } contig; /* RETRY_MODE_CONTIGUOUS */ } u_bits; int insert_value; } retry_conf_t; #if defined(DIREWOLF_C) || defined(ATEST_C) || defined(UDPTEST_C) static const char * retry_text[] = { "NONE", "SINGLE", "DOUBLE", "TRIPLE", "TWO_SEP", "PASSALL" }; #endif void hdlc_rec2_init (struct audio_s *audio_config_p); void hdlc_rec2_block (rrbb_t block); int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel); /* Provided by the top level application to process a complete frame. */ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t level, retry_t retries, char *spectrum); #endif direwolf-1.5+dfsg/hdlc_send.c000066400000000000000000000124651347750676600162350ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 2014 John Langner, WB2OSZ // // 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, see . // #include "direwolf.h" #include #include "hdlc_send.h" #include "audio.h" #include "gen_tone.h" #include "textcolor.h" #include "fcs_calc.h" static void send_control (int, int); static void send_data (int, int); static void send_bit (int, int); static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags" /*------------------------------------------------------------- * * Name: hdlc_send * * Purpose: Convert HDLC frames to a stream of bits. * * Inputs: chan - Audio channel number, 0 = first. * * fbuf - Frame buffer address. * * flen - Frame length, not including the FCS. * * bad_fcs - Append an invalid FCS for testing purposes. * * Outputs: Bits are shipped out by calling tone_gen_put_bit(). * * Returns: Number of bits sent including "flags" and the * stuffing bits. * The required time can be calculated by dividing this * number by the transmit rate of bits/sec. * * Description: Convert to stream of bits including: * start flag * bit stuffed data * calculated FCS * end flag * NRZI encoding * * * Assumptions: It is assumed that the tone_gen module has been * properly initialized so that bits sent with * tone_gen_put_bit() are processed correctly. * *--------------------------------------------------------------*/ int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs) { int j, fcs; number_of_bits_sent[chan] = 0; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d, bad_fcs = %d)\n", chan, fbuf, flen, bad_fcs); fflush (stdout); #endif send_control (chan, 0x7e); /* Start frame */ for (j=0; j> 8) & 0xff); } else { send_data (chan, fcs & 0xff); send_data (chan, (fcs >> 8) & 0xff); } send_control (chan, 0x7e); /* End frame */ return (number_of_bits_sent[chan]); } /*------------------------------------------------------------- * * Name: hdlc_send_flags * * Purpose: Send HDLC flags before and after the frame. * * Inputs: chan - Audio channel number, 0 = first. * * nflags - Number of flag patterns to send. * * finish - True for end of transmission. * This causes the last audio buffer to be flushed. * * Outputs: Bits are shipped out by calling tone_gen_put_bit(). * * Returns: Number of bits sent. * There is no bit-stuffing so we would expect this to * be 8 * nflags. * The required time can be calculated by dividing this * number by the transmit rate of bits/sec. * * Assumptions: It is assumed that the tone_gen module has been * properly initialized so that bits sent with * tone_gen_put_bit() are processed correctly. * *--------------------------------------------------------------*/ int hdlc_send_flags (int chan, int nflags, int finish) { int j; number_of_bits_sent[chan] = 0; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("hdlc_send_flags ( chan = %d, nflags = %d, finish = %d )\n", chan, nflags, finish); fflush (stdout); #endif /* The AX.25 spec states that when the transmitter is on but not sending data */ /* it should send a continuous stream of "flags." */ for (j=0; j>= 1; } stuff[chan] = 0; } static void send_data (int chan, int x) { int i; for (i=0; i<8; i++) { send_bit (chan, x & 1); if (x & 1) { stuff[chan]++; if (stuff[chan] == 5) { send_bit (chan, 0); stuff[chan] = 0; } } else { stuff[chan] = 0; } x >>= 1; } } /* * NRZI encoding. * data 1 bit -> no change. * data 0 bit -> invert signal. */ static void send_bit (int chan, int b) { static int output[MAX_CHANS]; if (b == 0) { output[chan] = ! output[chan]; } tone_gen_put_bit (chan, output[chan]); number_of_bits_sent[chan]++; } /* end hdlc_send.c */direwolf-1.5+dfsg/hdlc_send.h000066400000000000000000000002611347750676600162310ustar00rootroot00000000000000 /* hdlc_send.h */ int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs); int hdlc_send_flags (int chan, int flags, int finish); /* end hdlc_send.h */ direwolf-1.5+dfsg/igate.c000066400000000000000000002220221347750676600153730ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: igate.c * * Purpose: IGate client. * * Description: Establish connection with a tier 2 IGate server * and relay packets between RF and Internet. * * References: APRS-IS (Automatic Packet Reporting System-Internet Service) * http://www.aprs-is.net/Default.aspx * * APRS iGate properties * http://wiki.ham.fi/APRS_iGate_properties * * Notes to iGate developers * https://github.com/hessu/aprsc/blob/master/doc/IGATE-HINTS.md#igates-dropping-duplicate-packets-unnecessarily * * SATgate mode. * http://www.tapr.org/pipermail/aprssig/2016-January/045283.html * *---------------------------------------------------------------*/ /*------------------------------------------------------------------ * * From http://windows.microsoft.com/en-us/windows7/ipv6-frequently-asked-questions * * How can I enable IPv6? * Follow these steps: * * Open Network Connections by clicking the Start button, and then clicking * Control Panel. In the search box, type adapter, and then, under Network * and Sharing Center, click View network connections. * * Right-click your network connection, and then click Properties. * If you're prompted for an administrator password or confirmation, type * the password or provide confirmation. * * Select the check box next to Internet Protocol Version 6 (TCP/IPv6). * *---------------------------------------------------------------*/ /* * Native Windows: Use the Winsock interface. * Linux: Use the BSD socket interface. * Cygwin: Can use either one. */ #include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h #if __WIN32__ /* The goal is to support Windows XP and later. */ #include #include // _WIN32_WINNT must be set to 0x0501 before including this #else #include #include #include #include #include #include #include #include #endif #include #include #include #include #include #include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "version.h" #include "digipeater.h" #include "tq.h" #include "igate.h" #include "latlong.h" #include "pfilter.h" #include "dtime_now.h" #include "mheard.h" #if __WIN32__ static unsigned __stdcall connnect_thread (void *arg); static unsigned __stdcall igate_recv_thread (void *arg); static unsigned __stdcall satgate_delay_thread (void *arg); #else static void * connnect_thread (void *arg); static void * igate_recv_thread (void *arg); static void * satgate_delay_thread (void *arg); #endif static dw_mutex_t dp_mutex; /* Critical section for delayed packet queue. */ static packet_t dp_queue_head; static void satgate_delay_packet (packet_t pp, int chan); static void send_packet_to_server (packet_t pp, int chan); static void send_msg_to_server (const char *msg, int msg_len); static void maybe_xmit_packet_from_igate (char *message, int chan); static void rx_to_ig_init (void); static void rx_to_ig_remember (packet_t pp); static int rx_to_ig_allow (packet_t pp); static void ig_to_tx_init (void); static int ig_to_tx_allow (packet_t pp, int chan); /* * File descriptor for socket to IGate server. * Set to -1 if not connected. * (Don't use SOCKET type because it is unsigned.) */ static volatile int igate_sock = -1; /* * After connecting to server, we want to make sure * that the login sequence is sent first. * This is set to true after the login is complete. */ static volatile int ok_to_send = 0; /* * Convert Internet address to text. * Can't use InetNtop because it is supported only on Windows Vista and later. */ static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize) { struct sockaddr_in *sa4; struct sockaddr_in6 *sa6; switch (Family) { case AF_INET: sa4 = (struct sockaddr_in *)pAddr; #if __WIN32__ snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1, sa4->sin_addr.S_un.S_un_b.s_b2, sa4->sin_addr.S_un.S_un_b.s_b3, sa4->sin_addr.S_un.S_un_b.s_b4); #else inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize); #endif break; case AF_INET6: sa6 = (struct sockaddr_in6 *)pAddr; #if __WIN32__ snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x", ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7])); #else inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize); #endif break; default: snprintf (pStringBuf, StringBufSize, "Invalid address family!"); } //assert (strlen(pStringBuf) < StringBufSize); return pStringBuf; } #if ITEST /* For unit testing. */ int main (int argc, char *argv[]) { struct audio_s audio_config; struct igate_config_s igate_config; struct digi_config_s digi_config; packet_t pp; memset (&audio_config, 0, sizeof(audio_config)); audio_config.adev[0].num_chans = 2; strlcpy (audio_config.achan[0].mycall, "WB2OSZ-1", sizeof(audio_config.achan[0].mycall)); strlcpy (audio_config.achan[1].mycall, "WB2OSZ-2", sizeof(audio_config.achan[0].mycall)); memset (&igate_config, 0, sizeof(igate_config)); strlcpy (igate_config.t2_server_name, "localhost", sizeof(igate_config.t2_server_name)); igate_config.t2_server_port = 14580; strlcpy (igate_config.t2_login, "WB2OSZ-JL", sizeof(igate_config.t2_login)); strlcpy (igate_config.t2_passcode, "-1", sizeof(igate_config.t2_passcode)); igate_config.t2_filter = strdup ("r/1/2/3"); igate_config.tx_chan = 0; strlcpy (igate_config.tx_via, ",WIDE2-1", sizeof(igate_config.tx_via)); igate_config.tx_limit_1 = 3; igate_config.tx_limit_5 = 5; memset (&digi_config, 0, sizeof(digi_config)); igate_init(&igate_config, &digi_config); while (igate_sock == -1) { SLEEP_SEC(1); } SLEEP_SEC (2); pp = ax25_from_text ("A>B,C,D:Ztest message 1", 0); igate_send_rec_packet (0, pp); ax25_delete (pp); SLEEP_SEC (2); pp = ax25_from_text ("A>B,C,D:Ztest message 2", 0); igate_send_rec_packet (0, pp); ax25_delete (pp); SLEEP_SEC (2); pp = ax25_from_text ("A>B,C,D:Ztest message 2", 0); /* Should suppress duplicate. */ igate_send_rec_packet (0, pp); ax25_delete (pp); SLEEP_SEC (2); pp = ax25_from_text ("A>B,TCPIP,D:ZShould drop this due to path", 0); igate_send_rec_packet (0, pp); ax25_delete (pp); SLEEP_SEC (2); pp = ax25_from_text ("A>B,C,D:?Should drop query", 0); igate_send_rec_packet (0, pp); ax25_delete (pp); SLEEP_SEC (5); pp = ax25_from_text ("A>B,C,D:}E>F,G*,H:Zthird party stuff", 0); igate_send_rec_packet (0, pp); ax25_delete (pp); #if 1 while (1) { SLEEP_SEC (20); text_color_set(DW_COLOR_INFO); dw_printf ("Send received packet\n"); send_msg_to_server ("W1ABC>APRS:?", strlen("W1ABC>APRS:?"); } #endif return 0; } #endif /* * Global stuff (to this file) * * These are set by init function and need to * be kept around in case connection is lost and * we need to reestablish the connection later. */ static struct audio_s *save_audio_config_p; static struct igate_config_s *save_igate_config_p; static struct digi_config_s *save_digi_config_p; static int s_debug; /* * Statistics for IGate function. * Note that the RF related counters are just a subset of what is happening on radio channels. * * TODO: should have debug option to print these occasionally. */ static int stats_failed_connect; /* Number of times we tried to connect to */ /* a server and failed. A small number is not */ /* a bad thing. Each name should have a bunch */ /* of addresses for load balancing and */ /* redundancy. */ static int stats_connects; /* Number of successful connects to a server. */ /* Normally you'd expect this to be 1. */ /* Could be larger if one disappears and we */ /* try again to find a different one. */ static time_t stats_connect_at; /* Most recent time connection was established. */ /* can be used to determine elapsed connect time. */ static int stats_rf_recv_packets; /* Number of candidate packets from the radio. */ /* This is not the total number of AX.25 frames received */ /* over the radio; only APRS packets get this far. */ static int stats_uplink_packets; /* Number of packets passed along to the IGate */ /* server after filtering. */ static int stats_uplink_bytes; /* Total number of bytes sent to IGate server */ /* including login, packets, and hearbeats. */ static int stats_downlink_bytes; /* Total number of bytes from IGate server including */ /* packets, heartbeats, other messages. */ static int stats_downlink_packets; /* Number of packets from IGate server for possible transmission. */ /* Fewer might be transmitted due to filtering or rate limiting. */ static int stats_rf_xmit_packets; /* Number of packets passed along to radio, for the IGate function, */ /* after filtering, rate limiting, or other restrictions. */ /* Number of packets transmitted for beacons, digipeating, */ /* or client applications are not included here. */ static int stats_msg_cnt; /* Number of "messages" transmitted. Subset of above. */ /* A "message" has the data type indicator of ":" and it is */ /* not the special case of telemetry metadata. */ /* * Make some of these available for IGate statistics beacon like * * WB2OSZ>APDW14,WIDE1-1:t2_server_name, p_igate_config->t2_server_port, p_igate_config->t2_login, p_igate_config->t2_passcode, p_igate_config->t2_filter); #endif /* * Save the arguments for later use. */ save_audio_config_p = p_audio_config; save_igate_config_p = p_igate_config; save_digi_config_p = p_digi_config; stats_failed_connect = 0; stats_connects = 0; stats_connect_at = 0; stats_rf_recv_packets = 0; stats_uplink_packets = 0; stats_uplink_bytes = 0; stats_downlink_bytes = 0; stats_downlink_packets = 0; stats_rf_xmit_packets = 0; stats_msg_cnt = 0; rx_to_ig_init (); ig_to_tx_init (); /* * Continue only if we have server name, login, and passcode. */ if (strlen(p_igate_config->t2_server_name) == 0 || strlen(p_igate_config->t2_login) == 0 || strlen(p_igate_config->t2_passcode) == 0) { return; } /* * This connects to the server and sets igate_sock. * It also sends periodic messages to say I'm still alive. */ #if __WIN32__ connnect_th = (HANDLE)_beginthreadex (NULL, 0, connnect_thread, (void *)NULL, 0, NULL); if (connnect_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error: Could not create IGate connection thread\n"); return; } #else e = pthread_create (&connect_listen_tid, NULL, connnect_thread, (void *)NULL); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Internal error: Could not create IGate connection thread"); return; } #endif /* * This reads messages from client when igate_sock is valid. */ #if __WIN32__ cmd_recv_th = (HANDLE)_beginthreadex (NULL, 0, igate_recv_thread, NULL, 0, NULL); if (cmd_recv_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error: Could not create IGate reading thread\n"); return; } #else e = pthread_create (&cmd_listen_tid, NULL, igate_recv_thread, NULL); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Internal error: Could not create IGate reading thread"); return; } #endif /* * This lets delayed packets continue after specified amount of time. */ if (p_igate_config->satgate_delay > 0) { #if __WIN32__ satgate_delay_th = (HANDLE)_beginthreadex (NULL, 0, satgate_delay_thread, NULL, 0, NULL); if (satgate_delay_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error: Could not create SATgate delay thread\n"); return; } #else e = pthread_create (&satgate_delay_tid, NULL, satgate_delay_thread, NULL); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Internal error: Could not create SATgate delay thread"); return; } #endif dw_mutex_init(&dp_mutex); } } /* end igate_init */ /*------------------------------------------------------------------- * * Name: connnect_thread * * Purpose: Establish connection with IGate server. * Send periodic heartbeat to keep keep connection active. * Reconnect if something goes wrong and we got disconnected. * * Inputs: arg - Not used. * * Outputs: igate_sock - File descriptor for communicating with client app. * Will be -1 if not connected. * * References: TCP client example. * http://msdn.microsoft.com/en-us/library/windows/desktop/ms737591(v=vs.85).aspx * * Linux IPv6 HOWTO * http://www.tldp.org/HOWTO/Linux+IPv6-HOWTO/ * *--------------------------------------------------------------------*/ /* * Addresses don't get mixed up very well. * IPv6 always shows up last so we'd probably never * end up using any of them. Use our own shuffle. */ static void shuffle (struct addrinfo *host[], int nhosts) { int j, k; assert (RAND_MAX >= nhosts); /* for % to work right */ if (nhosts < 2) return; srand (time(NULL)); for (j=0; j=0 && kt2_server_port); #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("DEBUG: igate connect_thread start, port = %d = '%s'\n", save_igate_config_p->t2_server_port, server_port_str); #endif #if __WIN32__ err = WSAStartup (MAKEWORD(2,2), &wsadata); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf("WSAStartup failed: %d\n", err); return (0); } if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { text_color_set(DW_COLOR_ERROR); dw_printf("Could not find a usable version of Winsock.dll\n"); WSACleanup(); //sleep (1); return (0); } #endif memset (&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */ // IPv6 is half baked on Windows XP. // We might need to leave out IPv6 support for Windows version. // hints.ai_family = AF_INET; /* IPv4 only. */ #if IPV6_ONLY /* IPv6 addresses always show up at end of list. */ /* Force use of them for testing. */ hints.ai_family = AF_INET6; /* IPv6 only */ #endif hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; /* * Repeat forever. */ while (1) { /* * Connect to IGate server if not currently connected. */ if (igate_sock == -1) { SLEEP_SEC (5); ai_head = NULL; err = getaddrinfo(save_igate_config_p->t2_server_name, server_port_str, &hints, &ai_head); if (err != 0) { text_color_set(DW_COLOR_ERROR); #if __WIN32__ dw_printf ("Can't get address for IGate server %s, err=%d\n", save_igate_config_p->t2_server_name, WSAGetLastError()); #else dw_printf ("Can't get address for IGate server %s, %s\n", save_igate_config_p->t2_server_name, gai_strerror(err)); #endif freeaddrinfo(ai_head); continue; } #if DEBUG_DNS text_color_set(DW_COLOR_DEBUG); dw_printf ("getaddrinfo returns:\n"); #endif num_hosts = 0; for (ai = ai_head; ai != NULL; ai = ai->ai_next) { #if DEBUG_DNS text_color_set(DW_COLOR_DEBUG); ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); dw_printf (" %s\n", ipaddr_str); #endif hosts[num_hosts] = ai; if (num_hosts < MAX_HOSTS) num_hosts++; } // We can get multiple addresses back for the host name. // These should be somewhat randomized for load balancing. // It turns out the IPv6 addresses are always at the // end for both Windows and Linux. We do our own shuffling // to mix them up better and give IPv6 a chance. shuffle (hosts, num_hosts); #if DEBUG_DNS text_color_set(DW_COLOR_DEBUG); dw_printf ("after shuffling:\n"); for (n=0; nai_family, hosts[n]->ai_addr, ipaddr_str, sizeof(ipaddr_str)); dw_printf (" %s\n", ipaddr_str); } #endif // Try each address until we find one that is successful. for (n=0; nai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); #if __WIN32__ if (is == INVALID_SOCKET) { text_color_set(DW_COLOR_ERROR); dw_printf ("IGate: Socket creation failed, err=%d", WSAGetLastError()); WSACleanup(); is = -1; stats_failed_connect++; continue; } #else if (err != 0) { text_color_set(DW_COLOR_INFO); dw_printf("Connect to IGate server %s (%s) failed.\n\n", save_igate_config_p->t2_server_name, ipaddr_str); (void) close (is); is = -1; stats_failed_connect++; continue; } #endif #ifndef DEBUG_DNS err = connect(is, ai->ai_addr, (int)ai->ai_addrlen); #if __WIN32__ if (err == SOCKET_ERROR) { text_color_set(DW_COLOR_INFO); dw_printf("Connect to IGate server %s (%s) failed.\n\n", save_igate_config_p->t2_server_name, ipaddr_str); closesocket (is); is = -1; stats_failed_connect++; continue; } // TODO: set TCP_NODELAY? #else if (err != 0) { text_color_set(DW_COLOR_INFO); dw_printf("Connect to IGate server %s (%s) failed.\n\n", save_igate_config_p->t2_server_name, ipaddr_str); (void) close (is); is = -1; stats_failed_connect++; continue; } /* IGate documentation says to use it. */ /* Does it really make a difference for this application? */ int flag = 1; err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), sizeof(flag)); if (err < 0) { text_color_set(DW_COLOR_INFO); dw_printf("setsockopt TCP_NODELAY failed.\n"); } #endif stats_connects++; stats_connect_at = time(NULL); /* Success. */ text_color_set(DW_COLOR_INFO); dw_printf("\nNow connected to IGate server %s (%s)\n", save_igate_config_p->t2_server_name, ipaddr_str ); if (strchr(ipaddr_str, ':') != NULL) { dw_printf("Check server status here http://[%s]:14501\n\n", ipaddr_str); } else { dw_printf("Check server status here http://%s:14501\n\n", ipaddr_str); } /* * Set igate_sock so everyone else can start using it. * But make the Rx -> Internet messages wait until after login. */ ok_to_send = 0; igate_sock = is; #endif break; } freeaddrinfo(ai_head); if (igate_sock != -1) { char stemp[256]; /* * Send login message. * Software name and version must not contain spaces. */ SLEEP_SEC(3); snprintf (stemp, sizeof(stemp), "user %s pass %s vers Dire-Wolf %d.%d", save_igate_config_p->t2_login, save_igate_config_p->t2_passcode, MAJOR_VERSION, MINOR_VERSION); if (save_igate_config_p->t2_filter != NULL) { strlcat (stemp, " filter ", sizeof(stemp)); strlcat (stemp, save_igate_config_p->t2_filter, sizeof(stemp)); } send_msg_to_server (stemp, strlen(stemp)); /* Delay until it is ok to start sending packets. */ SLEEP_SEC(7); ok_to_send = 1; } } /* * If connected to IGate server, send heartbeat periodically to keep connection active. */ if (igate_sock != -1) { SLEEP_SEC(10); } if (igate_sock != -1) { SLEEP_SEC(10); } if (igate_sock != -1) { SLEEP_SEC(10); } if (igate_sock != -1) { char heartbeat[10]; strlcpy (heartbeat, "#", sizeof(heartbeat)); /* This will close the socket if any error. */ send_msg_to_server (heartbeat, strlen(heartbeat)); } } exit(0); // Unreachable but stops compiler from complaining // about function not returning a value. } /* end connnect_thread */ /*------------------------------------------------------------------- * * Name: igate_send_rec_packet * * Purpose: Send a packet to the IGate server * * Inputs: chan - Radio channel it was received on. * * recv_pp - Pointer to packet object. * *** CALLER IS RESPONSIBLE FOR DELETING IT! ** * * * Description: Send message to IGate Server if connected. * * Assumptions: (1) Caller has already verified it is an APRS packet. * i.e. control = 3 for UI frame, protocol id = 0xf0 for no layer 3 * * (2) This is being called only for packets received with * a correct CRC. We don't want to propagate corrupted data. * *--------------------------------------------------------------------*/ #define IGATE_MAX_MSG 512 /* "All 'packets' sent to APRS-IS must be in the TNC2 format terminated */ /* by a carriage return, line feed sequence. No line may exceed 512 bytes */ /* including the CR/LF sequence." */ void igate_send_rec_packet (int chan, packet_t recv_pp) { packet_t pp; int n; unsigned char *pinfo; int info_len; if (igate_sock == -1) { return; /* Silently discard if not connected. */ } if ( ! ok_to_send) { return; /* Login not complete. */ } /* Gather statistics. */ stats_rf_recv_packets++; /* * Check for filtering from specified channel to the IGate server. * * Should we do this after unwrapping the payload from a third party packet? * In my experience, third party packets have only been seen coming from IGates. * In that case, the payload will have TCPIP in the path and it will be dropped. */ if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) { if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp, 1) != 1) { // Is this useful troubleshooting information or just distracting noise? // Originally this was always printed but there was a request to add a "quiet" option to suppress this. // version 1.4: Instead, make the default off and activate it only with the debug igate option. if (s_debug >= 1) { text_color_set(DW_COLOR_INFO); dw_printf ("Packet from channel %d to IGate was rejected by filter: %s\n", chan, save_digi_config_p->filter_str[chan][MAX_CHANS]); } return; } } /* * First make a copy of it because it might be modified in place. */ pp = ax25_dup (recv_pp); assert (pp != NULL); /* * Third party frames require special handling to unwrap payload. */ while (ax25_get_dti(pp) == '}') { packet_t inner_pp; for (n = 0; n < ax25_get_num_repeaters(pp); n++) { char via[AX25_MAX_ADDR_LEN]; /* includes ssid. Do we want to ignore it? */ ax25_get_addr_with_ssid (pp, n + AX25_REPEATER_1, via); if (strcmp(via, "TCPIP") == 0 || strcmp(via, "TCPXX") == 0 || strcmp(via, "RFONLY") == 0 || strcmp(via, "NOGATE") == 0) { if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Rx IGate: Do not relay with %s in path.\n", via); } ax25_delete (pp); return; } } if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Rx IGate: Unwrap third party message.\n"); } inner_pp = ax25_unwrap_third_party(pp); if (inner_pp == NULL) { ax25_delete (pp); return; } ax25_delete (pp); pp = inner_pp; } /* * Do not relay packets with TCPIP, TCPXX, RFONLY, or NOGATE in the via path. */ for (n = 0; n < ax25_get_num_repeaters(pp); n++) { char via[AX25_MAX_ADDR_LEN]; /* includes ssid. Do we want to ignore it? */ ax25_get_addr_with_ssid (pp, n + AX25_REPEATER_1, via); if (strcmp(via, "TCPIP") == 0 || strcmp(via, "TCPXX") == 0 || strcmp(via, "RFONLY") == 0 || strcmp(via, "NOGATE") == 0) { if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Rx IGate: Do not relay with %s in path.\n", via); } ax25_delete (pp); return; } } /* * Do not relay generic query. */ if (ax25_get_dti(pp) == '?') { if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Rx IGate: Do not relay generic query.\n"); } ax25_delete (pp); return; } /* * Cut the information part at the first CR or LF. * This is required because CR/LF is used as record separator when sending to server. * Do NOT trim trailing spaces. * Starting in 1.4 we preserve any nul characters in the information part. */ if (ax25_cut_at_crlf (pp) > 0) { if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Rx IGate: Truncated information part at CR.\n"); } } info_len = ax25_get_info (pp, &pinfo); /* * Someone around here occasionally sends a packet with no information part. */ if (info_len == 0) { if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Rx IGate: Information part length is zero.\n"); } ax25_delete (pp); return; } // TODO: Should we drop raw touch tone data object type generated here? /* * If the SATgate mode is enabled, see if it should be delayed. * The rule is if we hear it directly and it has at least one * digipeater so there is potential of being re-transmitted. * (Digis are all unused if we are hearing it directly from source.) */ if (save_igate_config_p->satgate_delay > 0 && ax25_get_heard(pp) == AX25_SOURCE && ax25_get_num_repeaters(pp) > 0) { satgate_delay_packet (pp, chan); } else { send_packet_to_server (pp, chan); } } /* end igate_send_rec_packet */ /*------------------------------------------------------------------- * * Name: send_packet_to_server * * Purpose: Convert to text and send to the IGate server. * * Inputs: pp - Packet object. * * chan - Radio channel where it was received. * * Description: Duplicate detection is handled here. * Suppress if same was sent recently. * *--------------------------------------------------------------------*/ static void send_packet_to_server (packet_t pp, int chan) { unsigned char *pinfo; int info_len; char msg[IGATE_MAX_MSG]; info_len = ax25_get_info (pp, &pinfo); /* * We will often see the same packet multiple times close together due to digipeating. * The consensus seems to be that we should just send the first and drop the later duplicates. * There is some dissent on this issue. http://www.tapr.org/pipermail/aprssig/2016-July/045907.html * There could be some value to sending them all to provide information about digipeater paths. * However, the servers should drop all duplicates so we wasting everyone's time but sending duplicates. * If you feel strongly about this issue, you could remove the following section. * Currently rx_to_ig_allow only checks for recent duplicates. */ if ( ! rx_to_ig_allow(pp)) { if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Rx IGate: Drop duplicate of same packet seen recently.\n"); } ax25_delete (pp); return; } /* * Finally, append ",qAR," and my call to the path. */ /* * It seems that the specification has changed recently. * http://www.tapr.org/pipermail/aprssig/2016-December/046456.html * * We can see the history at the Internet Archive Wayback Machine. * * http://www.aprs-is.net/Connecting.aspx * captured Oct 19, 2016: * ... Only the qAR construct may be generated by a client (IGate) on APRS-IS. * Captured Dec 1, 2016: * ... Only the qAR and qAO constructs may be generated by a client (IGate) on APRS-IS. * * http://www.aprs-is.net/q.aspx * Captured April 23, 2016: * (no mention of client generating qAO.) * Captured July 19, 2016: * qAO - (letter O) Packet is placed on APRS-IS by a receive-only IGate from RF. * The callSSID following the qAO is the callSSID of the IGate. Note that receive-only * IGates are discouraged on standard APRS frequencies. Please consider a bidirectional * IGate that only gates to RF messages for stations heard directly. */ ax25_format_addrs (pp, msg); msg[strlen(msg)-1] = '\0'; /* Remove trailing ":" */ if (save_igate_config_p->tx_chan >= 0) { strlcat (msg, ",qAR,", sizeof(msg)); } else { strlcat (msg, ",qAO,", sizeof(msg)); // new for version 1.4. } strlcat (msg, save_audio_config_p->achan[chan].mycall, sizeof(msg)); strlcat (msg, ":", sizeof(msg)); // It was reported that APRS packets, containing a nul byte in the information part, // are being truncated. https://github.com/wb2osz/direwolf/issues/84 // // One might argue that the packets are invalid and the proper behavior would be // to simply discard them, the same way we do if the CRC is bad. One might argue // that we should simply pass along whatever we receive even if we don't like it. // We really shouldn't modify it and make the situation even worse. // // Chapter 5 of the APRS spec ( http://www.aprs.org/doc/APRS101.PDF ) says: // // "The comment may contain any printable ASCII characters (except | and ~, // which are reserved for TNC channel switching)." // // "Printable" would exclude character values less than space (00100000), e.g. // tab, carriage return, line feed, nul. Sometimes we see carriage return // (00001010) at the end of APRS packets. This would be in violation of the // specification. // // The MIC-E position format can have non printable characters (0x1c ... 0x1f, 0x7f) // in the information part. An unfortunate decision, but it is not in the comment part. // // The base 91 telemetry format (http://he.fi/doc/aprs-base91-comment-telemetry.txt ), // which is not part of the APRS spec, uses the | character in the comment to delimit encoded // telemetry data. This would be in violation of the original spec. No one cares. // // The APRS Spec Addendum 1.2 Proposals ( http://www.aprs.org/aprs12/datum.txt) // adds use of UTF-8 (https://en.wikipedia.org/wiki/UTF-8 )for the free form text in // messages and comments. It can't be used in the fixed width fields. // // Non-ASCII characters are represented by multi-byte sequences. All bytes in these // multi-byte sequences have the most significant bit set to 1. Using UTF-8 would not // add any nul (00000000) bytes to the stream. // // Based on all of that, we would not expect to see a nul character in the information part. // // There are two known cases where we can have a nul character value. // // * The Kenwood TM-D710A sometimes sends packets like this: // // VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast= // K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV = // // Notice that the data type indicator of "4" is not valid. If we remove // 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format. // This same thing has been observed from others and is intermittent. // // * AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes. // This is wrong, it should be using UTF-8. // // Rather than using strlcat here, we need to use memcpy and maintain our // own lengths, being careful to avoid buffer overflow. int msg_len = strlen(msg); // What we have so far before info part. if (info_len > IGATE_MAX_MSG - msg_len - 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("Rx IGate: Too long. Truncating.\n"); info_len = IGATE_MAX_MSG - msg_len - 2; } if (info_len > 0) { memcpy (msg + msg_len, pinfo, info_len); msg_len += info_len; } send_msg_to_server (msg, msg_len); stats_uplink_packets++; /* * Remember what was sent to avoid duplicates in near future. */ rx_to_ig_remember (pp); ax25_delete (pp); } /* end send_packet_to_server */ /*------------------------------------------------------------------- * * Name: send_msg_to_server * * Purpose: Send something to the IGate server. * This one function should be used for login, hearbeats, * and packets. * * Inputs: imsg - Message. We will add CR/LF here. * * imsg_len - Length of imsg in bytes. * It could contain nul characters so we can't * use the normal C string functions. * * Description: Send message to IGate Server if connected. * Disconnect from server, and notify user, if any error. * Should use a word other than message because that has * a specific meaning for APRS. * *--------------------------------------------------------------------*/ static void send_msg_to_server (const char *imsg, int imsg_len) { int err; char stemp[IGATE_MAX_MSG+1]; int stemp_len; if (igate_sock == -1) { return; /* Silently discard if not connected. */ } stemp_len = imsg_len; if (stemp_len + 2 > IGATE_MAX_MSG) { text_color_set(DW_COLOR_ERROR); dw_printf ("Rx IGate: Too long. Truncating.\n"); stemp_len = IGATE_MAX_MSG - 2; } memcpy (stemp, imsg, stemp_len); if (s_debug >= 1) { text_color_set(DW_COLOR_XMIT); dw_printf ("[rx>ig] "); ax25_safe_print (stemp, stemp_len, 0); dw_printf ("\n"); } stemp[stemp_len++] = '\r'; stemp[stemp_len++] = '\n'; stemp[stemp_len] = '\0'; stats_uplink_bytes += stemp_len; #if __WIN32__ err = SOCK_SEND (igate_sock, stemp, stemp_len); if (err == SOCKET_ERROR) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError %d sending to IGate server. Closing connection.\n\n", WSAGetLastError()); //dw_printf ("DEBUG: igate_sock=%d, line=%d\n", igate_sock, __LINE__); closesocket (igate_sock); igate_sock = -1; WSACleanup(); } #else err = SOCK_SEND (igate_sock, stemp, stemp_len); if (err <= 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError sending to IGate server. Closing connection.\n\n"); close (igate_sock); igate_sock = -1; } #endif } /* end send_msg_to_server */ /*------------------------------------------------------------------- * * Name: get1ch * * Purpose: Read one byte from socket. * * Inputs: igate_sock - file handle for socket. * * Returns: One byte from stream. * Waits and tries again later if any error. * * *--------------------------------------------------------------------*/ static int get1ch (void) { unsigned char ch; int n; while (1) { while (igate_sock == -1) { SLEEP_SEC(5); /* Not connected. Try again later. */ } /* Just get one byte at a time. */ // TODO: might read complete packets and unpack from own buffer // rather than using a system call for each byte. n = SOCK_RECV (igate_sock, (char*)(&ch), 1); if (n == 1) { #if DEBUG9 dw_printf (log_fp, "%02x %c %c", ch, isprint(ch) ? ch : '.' , (isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.'); if (ch == '\r') fprintf (log_fp, " CR"); if (ch == '\n') fprintf (log_fp, " LF"); fprintf (log_fp, "\n"); #endif return(ch); } text_color_set(DW_COLOR_ERROR); dw_printf ("\nError reading from IGate server. Closing connection.\n\n"); #if __WIN32__ closesocket (igate_sock); #else close (igate_sock); #endif igate_sock = -1; } } /* end get1ch */ /*------------------------------------------------------------------- * * Name: igate_recv_thread * * Purpose: Wait for messages from IGate Server. * * Inputs: arg - Not used. * * Outputs: igate_sock - File descriptor for communicating with client app. * * Description: Process messages from the IGate server. * *--------------------------------------------------------------------*/ #if __WIN32__ static unsigned __stdcall igate_recv_thread (void *arg) #else static void * igate_recv_thread (void *arg) #endif { unsigned char ch; unsigned char message[1000]; // Spec says max 512. int len; #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("igate_recv_thread ( socket = %d )\n", igate_sock); #endif while (1) { len = 0; do { ch = get1ch(); stats_downlink_bytes++; // I never expected to see a nul character but it can happen. // If found, change it to <0x00> and ax25_from_text will change it back to a single byte. // Along the way we can use the normal C string handling. if (ch == 0 && len < (int)(sizeof(message)) - 5) { message[len++] = '<'; message[len++] = '0'; message[len++] = 'x'; message[len++] = '0'; message[len++] = '0'; message[len++] = '>'; } else if (len < (int)(sizeof(message))) { message[len++] = ch; } } while (ch != '\n'); message[sizeof(message)-1] = '\0'; /* * We have a complete message terminated by LF. * * Remove CR LF from end. * This is a record separator for the protocol, not part of the data. * Should probably have an error if we don't have this. */ if (len >=2 && message[len-1] == '\n') { message[len-1] = '\0'; len--; } if (len >=1 && message[len-1] == '\r') { message[len-1] = '\0'; len--; } /* * I've seen a case where the original RF packet had a trailing CR but * after someone else sent it to the server and it came back to me, that * CR was now a trailing space. * * At first I was tempted to trim a trailing space as well. * By fixing this one case it might corrupt the data in other cases. * We compensate for this by ignoring trailing spaces when performing * the duplicate detection and removal. * * We need to transmit exactly as we get it. */ /* * I've also seen a multiple trailing spaces like this. * Notice how safe_print shows a trailing space in hexadecimal to make it obvious. * * W1CLA-1>APVR30,TCPIP*,qAC,T2TOKYO3:;IRLP-4942*141503z4218.46NI07108.24W0446325-146IDLE <0x20> */ if (len == 0) { /* * Discard if zero length. */ } else if (message[0] == '#') { /* * Heartbeat or other control message. * * Print only if within seconds of logging in. * That way we can see login confirmation but not * be bothered by the heart beat messages. */ if ( ! ok_to_send) { text_color_set(DW_COLOR_REC); dw_printf ("[ig] "); ax25_safe_print ((char *)message, len, 0); dw_printf ("\n"); } } else { /* * Convert to third party packet and transmit. * * Future: might have ability to configure multiple transmit * channels, each with own client side filtering and via path. * Loop here over all configured channels. */ text_color_set(DW_COLOR_REC); dw_printf ("\n[ig>tx] "); // formerly just [ig] ax25_safe_print ((char *)message, len, 0); dw_printf ("\n"); if ((int)strlen((char*)message) != len) { // Invalid. Either drop it or pass it along as-is. Don't change. text_color_set(DW_COLOR_ERROR); dw_printf("'nul' character found in packet from IS. This should never happen.\n"); dw_printf("The source station is probably transmitting with defective software.\n"); //if (strcmp((char*)pinfo, "4P") == 0) { // dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\n"); //} } /* * Record that we heard from the source address. */ mheard_save_is ((char *)message); stats_downlink_packets++; /* * Possibly transmit if so configured. */ int to_chan = save_igate_config_p->tx_chan; if (to_chan >= 0) { maybe_xmit_packet_from_igate ((char*)message, to_chan); } } } /* while (1) */ return (0); } /* end igate_recv_thread */ /*------------------------------------------------------------------- * * Name: satgate_delay_packet * * Purpose: Put packet into holding area for a while rather than * sending it immediately to the IS server. * * Inputs: pp - Packet object. * * chan - Radio channel where received. * * Outputs: Appended to queue. * * Description: If we hear a packet directly and the same one digipeated, * we only send the first to the APRS IS due to duplicate removal. * It may be desirable to favor the digipeated packet over the * original. For this situation, we have an option which delays * a packet if we hear it directly and the via path is not empty. * We know we heard it directly if none of the digipeater * addresses have been used. * This way the digipeated packet will go first. * The original is sent about 10 seconds later. * Duplicate removal will drop the original if there is no * corresponding digipeated version. * *--------------------------------------------------------------------*/ static void satgate_delay_packet (packet_t pp, int chan) { packet_t pnext, plast; //if (s_debug >= 1) { text_color_set(DW_COLOR_INFO); dw_printf ("Rx IGate: SATgate mode, delay packet heard directly.\n"); //} ax25_set_release_time (pp, dtime_now() + save_igate_config_p->satgate_delay); //TODO: save channel too. dw_mutex_lock (&dp_mutex); if (dp_queue_head == NULL) { dp_queue_head = pp; } else { plast = dp_queue_head; while ((pnext = ax25_get_nextp(plast)) != NULL) { plast = pnext; } ax25_set_nextp (plast, pp); } dw_mutex_unlock (&dp_mutex); } /* end satgate_delay_packet */ /*------------------------------------------------------------------- * * Name: satgate_delay_thread * * Purpose: Release packet when specified release time has arrived. * * Inputs: dp_queue_head - Queue of packets. * * Outputs: Sent to APRS IS. * * Description: For simplicity we'll just poll each second. * Release the packet when its time has arrived. * *--------------------------------------------------------------------*/ #if __WIN32__ static unsigned __stdcall satgate_delay_thread (void *arg) #else static void * satgate_delay_thread (void *arg) #endif { double release_time; int chan = 0; // TODO: get receive channel somehow. // only matters if multi channel with different names. while (1) { SLEEP_SEC (1); /* Don't need critical region just to peek */ if (dp_queue_head != NULL) { double now = dtime_now(); release_time = ax25_get_release_time (dp_queue_head); #if 0 text_color_set(DW_COLOR_DEBUG); dw_printf ("SATgate: %.1f sec remaining\n", release_time - now); #endif if (now > release_time) { packet_t pp; dw_mutex_lock (&dp_mutex); pp = dp_queue_head; dp_queue_head = ax25_get_nextp(pp); dw_mutex_unlock (&dp_mutex); ax25_set_nextp (pp, NULL); send_packet_to_server (pp, chan); } } /* if something in queue */ } /* while (1) */ return (0); } /* end satgate_delay_thread */ /*------------------------------------------------------------------- * * Name: maybe_xmit_packet_from_igate * * Purpose: Convert text string, from IGate server, to third party * packet and send to transmit queue if appropriate. * * Inputs: message - As sent by the server. * Any trailing CRLF should have been removed. * Typical examples: * * KA1BTK-5>APDR13,TCPIP*,qAC,T2IRELAND:=4237.62N/07040.68W$/A=-00054 http://aprsdroid.org/ * N1HKO-10>APJI40,TCPIP*,qAC,N1HKO-JS:APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmAT3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile * * Notice how the final address in the header might not * be a valid AX.25 address. We see a 9 character address * (with no ssid) and an ssid of two letters. * We don't care because we end up discarding them before * repackaging to go over the radio. * * The "q construct" ( http://www.aprs-is.net/q.aspx ) provides * a clue about the journey taken. "qAX" means that the station sending * the packet to the server did not login properly as a ham radio * operator so we don't want to put this on to RF. * * to_chan - Radio channel for transmitting. * *--------------------------------------------------------------------*/ static void maybe_xmit_packet_from_igate (char *message, int to_chan) { packet_t pp3; char payload[AX25_MAX_PACKET_LEN]; /* what is max len? */ char src[AX25_MAX_ADDR_LEN]; /* Source address. */ char *pinfo = NULL; int info_len; int n; assert (to_chan >= 0 && to_chan < MAX_CHANS); /* * Try to parse it into a packet object. * This will contain "q constructs" and we might see an address * with two alphnumeric characters in the SSID so we must use * the non-strict parsing. * * Bug: Up to 8 digipeaters are allowed in radio format. * There is a potential of finding a larger number here. */ pp3 = ax25_from_text(message, 0); if (pp3 == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Tx IGate: Could not parse message from server.\n"); dw_printf ("%s\n", message); return; } ax25_get_addr_with_ssid (pp3, AX25_SOURCE, src); /* * Drop if path contains: * NOGATE or RFONLY - means IGate should not pass them. * TCPXX or qAX - means it came from somewhere that did not identify itself correctly. */ for (n = 0; n < ax25_get_num_repeaters(pp3); n++) { char via[AX25_MAX_ADDR_LEN]; /* includes ssid. Do we want to ignore it? */ ax25_get_addr_with_ssid (pp3, n + AX25_REPEATER_1, via); if (strcmp(via, "qAX") == 0 || strcmp(via, "TCPXX") == 0 || strcmp(via, "RFONLY") == 0 || strcmp(via, "NOGATE") == 0) { if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Tx IGate: Do not transmit with %s in path.\n", via); } ax25_delete (pp3); return; } } /* * Apply our own packet filtering if configured. * Do we want to do this before or after removing the VIA path? * I suppose by doing it first, we have the possibility of * filtering by stations along the way or the q construct. */ assert (to_chan >= 0 && to_chan < MAX_CHANS); /* * We have a rather strange special case here. * If we recently transmitted a 'message' from some station, * send the position of the message sender when it comes along later. * * If we have a position report, look up the sender and see if we should * bypass the normal filtering. */ // TODO: Not quite this simple. Should have a function to check for position. // $ raw gps could be a position. @ could be weather data depending on symbol. info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo)); int msp_special_case = 0; if (info_len >= 1 && strchr("!=/@'`", *pinfo) != NULL) { int n = mheard_get_msp(src); if (n > 0) { msp_special_case = 1; if (s_debug >= 1) { text_color_set(DW_COLOR_INFO); dw_printf ("Special case, allow position from message sender %s, %d remaining.\n", src, n - 1); } mheard_set_msp (src, n - 1); } } if ( ! msp_special_case) { if (save_digi_config_p->filter_str[MAX_CHANS][to_chan] != NULL) { if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3, 1) != 1) { // Previously there was a debug message here about the packet being dropped by filtering. // This is now handled better by the "-df" command line option for filtering details. // TODO: clean up - remove these lines. //if (s_debug >= 1) { // text_color_set(DW_COLOR_INFO); // dw_printf ("Packet from IGate to channel %d was rejected by filter: %s\n", to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan]); //} ax25_delete (pp3); return; } } } /* * Remove the VIA path. * * For example, we might get something like this from the server. * K1USN-1>APWW10,TCPIP*,qAC,N5JXS-F1:T#479,100,048,002,500,000,10000000<0x0d><0x0a> * * We want to reduce it to this before wrapping it as third party traffic. * K1USN-1>APWW10:T#479,100,048,002,500,000,10000000<0x0d><0x0a> */ /* * These are typical examples where we see TCPIP*,qAC, * * N3LLO-4>APRX28,TCPIP*,qAC,T2NUENGLD:T#474,21.4,0.3,114.0,4.0,0.0,00000000 * N1WJO>APWW10,TCPIP*,qAC,T2MAINE:)147.120!4412.27N/07033.27WrW1OCA repeater136.5 Tone Norway Me * AB1OC-10>APWW10,TCPIP*,qAC,T2IAD2:=4242.70N/07135.41W#(Time 0:00:00)!INSERVICE!!W60! * * But sometimes we get a different form: * * N1YG-1>T1SY9P,WIDE1-1,WIDE2-2,qAR,W2DAN-15:'c&<0x7f>l <0x1c>-/> * W1HS-8>TSSP9T,WIDE1-1,WIDE2-1,qAR,N3LLO-2:`d^Vl"W>/'"85}|*&%_'[|!wLK!|3 * N1RCW-1>APU25N,MA2-2,qAR,KA1VCQ-1:=4140.41N/07030.21W-Home Station/Fill-in Digi {UIV32N} * N1IEJ>T4PY3U,W1EMA-1,WIDE1*,WIDE2-2,qAR,KD1KE:`a5"l!<0x7f>-/]"4f}Retired & Busy= * * Oh! They have qAR rather than qAC. What does that mean? * From http://www.aprs-is.net/q.aspx * * qAC - Packet was received from the client directly via a verified connection (FROMCALL=login). * The callSSID following the qAC is the server's callsign-SSID. * * qAR - Packet was received directly (via a verified connection) from an IGate using the ,I construct. * The callSSID following the qAR it the callSSID of the IGate. * * What is the ",I" construct? * Do we care here? * Is is something new and improved that we should be using in the other direction? */ while (ax25_get_num_repeaters(pp3) > 0) { ax25_remove_addr (pp3, AX25_REPEATER_1); } /* * Replace the VIA path with TCPIP and my call. * Mark my call as having been used. */ ax25_set_addr (pp3, AX25_REPEATER_1, "TCPIP"); ax25_set_h (pp3, AX25_REPEATER_1); ax25_set_addr (pp3, AX25_REPEATER_2, save_audio_config_p->achan[to_chan].mycall); ax25_set_h (pp3, AX25_REPEATER_2); /* * Convert to text representation. */ memset (payload, 0, sizeof(payload)); ax25_format_addrs (pp3, payload); info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo)); (void)(info_len); strlcat (payload, pinfo, sizeof(payload)); #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("Tx IGate: payload=%s\n", payload); #endif /* * Encapsulate for sending over radio if no reason to drop it. */ /* * We don't want to suppress duplicate "messages" within a short time period. * Suppose we transmitted a "message" for some station and it did not respond with an ack. * 25 seconds later the sender retries. Wouldn't we want to pass along that retry? * * "Messages" get preferential treatment because they are high value and very rare. * -> Bypass the duplicate suppression. * -> Raise the rate limiting value. */ if (ig_to_tx_allow (pp3, to_chan)) { char radio [500]; packet_t pradio; snprintf (radio, sizeof(radio), "%s>%s%d%d%s:}%s", save_audio_config_p->achan[to_chan].mycall, APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, save_igate_config_p->tx_via, payload); pradio = ax25_from_text (radio, 1); /* Oops. Didn't have a check for NULL here. */ /* Could this be the cause of rare and elusive crashes in 1.2? */ if (pradio != NULL) { #if ITEST text_color_set(DW_COLOR_XMIT); dw_printf ("Xmit: %s\n", radio); ax25_delete (pradio); #else /* This consumes packet so don't reference it again! */ tq_append (to_chan, TQ_PRIO_1_LO, pradio); #endif stats_rf_xmit_packets++; // Any type of packet. // TEMP TEST: metadata temporarily allowed during testing. if (*pinfo == ':' && ! is_telem_metadata(pinfo)) { // temp test // if (*pinfo == ':') { // We transmitted a "message." Telemetry metadata is excluded. // Remember to pass along address of the sender later. stats_msg_cnt++; // Update statistics. mheard_set_msp (src, save_igate_config_p->igmsp); } ig_to_tx_remember (pp3, save_igate_config_p->tx_chan, 0); // correct. version before encapsulating it. } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Received invalid packet from IGate.\n"); dw_printf ("%s\n", payload); dw_printf ("Will not attempt to transmit third party packet.\n"); dw_printf ("%s\n", radio); } } ax25_delete (pp3); } /* end maybe_xmit_packet_from_igate */ /*------------------------------------------------------------------- * * Name: rx_to_ig_remember * * Purpose: Keep a record of packets sent to the IGate server * so we don't send duplicates within some set amount of time. * * Inputs: pp - Pointer to a packet object. * *------------------------------------------------------------------- * * Name: rx_to_ig_allow * * Purpose: Check whether this is a duplicate of another * recently received from RF and sent to the Server * * Input: pp - Pointer to packet object. * * Returns: True if it is OK to send. * *------------------------------------------------------------------- * * Description: These two functions perform the final stage of filtering * before sending a received (from radio) packet to the IGate server. * * rx_to_ig_remember must be called for every packet sent to the server. * * rx_to_ig_allow decides whether this should be allowed thru * based on recent activity. We will drop the packet if it is a * duplicate of another sent recently. * * Rather than storing the entire packet, we just keep a CRC to * reduce memory and processing requirements. We do the same in * the digipeater function to suppress duplicates. * * There is a 1 / 65536 chance of getting a false positive match * which is good enough for this application. * * * Original thinking: * * Occasionally someone will get on one of the discussion groups and say: * I don't think my IGate is working. I look at packets, from local stations, * on aprs.fi or findu.com, and they are always through some other IGate station, * never mine. * Then someone has to explain, this is not a valid strategy for analyzing * everything going thru the network. The APRS-IS servers drop duplicate * packets (ignoring the via path) within a 30 second period. If some * other IGate gets the same thing there a millisecond faster than you, * the one you send is discarded. * In this scenario, it would make sense to perform additional duplicate * suppression before forwarding RF packets to the Server. * I don't recall if I saw some specific recommendation to do this or if * it just seemed like the obvious thing to do to avoid sending useless * stuff that would just be discarded anyhow. It seems others came to the * same conclusion. http://www.tapr.org/pipermail/aprssig/2016-July/045907.html * * Version 1.5: Rethink strategy. * * Issue 85, https://github.com/wb2osz/direwolf/issues/85 , * got me thinking about this some more. Sending more information will * allow the APRS-IS servers to perform future additional network analysis. * To make a long story short, the RF>IS direction duplicate checking * is now disabled. The code is still there in case I change my mind * and want to add a configuration option to allow it. The dedupe * time is set to 0 which means don't do the checking. * *--------------------------------------------------------------------*/ #define RX2IG_HISTORY_MAX 30 /* Remember the last 30 sent to IGate server. */ static int rx2ig_insert_next; static time_t rx2ig_time_stamp[RX2IG_HISTORY_MAX]; static unsigned short rx2ig_checksum[RX2IG_HISTORY_MAX]; static void rx_to_ig_init (void) { int n; for (n=0; nrx2ig_dedupe_time == 0) { return; } rx2ig_time_stamp[rx2ig_insert_next] = time(NULL); rx2ig_checksum[rx2ig_insert_next] = ax25_dedupe_crc(pp); if (s_debug >= 3) { char src[AX25_MAX_ADDR_LEN]; char dest[AX25_MAX_ADDR_LEN]; unsigned char *pinfo; int info_len; ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); info_len = ax25_get_info (pp, &pinfo); (void)info_len; text_color_set(DW_COLOR_DEBUG); dw_printf ("rx_to_ig_remember [%d] = %d %d \"%s>%s:%s\"\n", rx2ig_insert_next, (int)(rx2ig_time_stamp[rx2ig_insert_next]), rx2ig_checksum[rx2ig_insert_next], src, dest, pinfo); } rx2ig_insert_next++; if (rx2ig_insert_next >= RX2IG_HISTORY_MAX) { rx2ig_insert_next = 0; } } static int rx_to_ig_allow (packet_t pp) { unsigned short crc = ax25_dedupe_crc(pp); time_t now = time(NULL); int j; if (s_debug >= 2) { char src[AX25_MAX_ADDR_LEN]; char dest[AX25_MAX_ADDR_LEN]; unsigned char *pinfo; int info_len; ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); info_len = ax25_get_info (pp, &pinfo); (void)info_len; text_color_set(DW_COLOR_DEBUG); dw_printf ("rx_to_ig_allow? %d \"%s>%s:%s\"\n", crc, src, dest, pinfo); } // Do we have duplicate checking at all in the RF>IS direction? if (save_igate_config_p->rx2ig_dedupe_time == 0) { if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("rx_to_ig_allow? YES, no dedupe checking\n"); } return 1; } // Yes, check for duplicates within certain time. for (j=0; j= now - save_igate_config_p->rx2ig_dedupe_time) { if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); // could be multiple entries and this might not be the most recent. dw_printf ("rx_to_ig_allow? NO. Seen %d seconds ago.\n", (int)(now - rx2ig_time_stamp[j])); } return 0; } } if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("rx_to_ig_allow? YES\n"); } return 1; } /* end rx_to_ig_allow */ /*------------------------------------------------------------------- * * Name: ig_to_tx_remember * * Purpose: Keep a record of packets sent from IGate server to radio transmitter * so we don't send duplicates within some set amount of time. * * Inputs: pp - Pointer to a packet object. * * chan - Channel number where it is being transmitted. * Duplicate detection needs to be separate for each radio channel. * * bydigi - True if transmitted by digipeater function. False for IGate. * Why do we care about digpeating here? See discussion below. * *------------------------------------------------------------------------------ * * Name: ig_to_tx_allow * * Purpose: Check whether this is a duplicate of another sent recently * or if we exceed the transmit rate limits. * * Input: pp - Pointer to packet object. * * chan - Radio channel number where we want to transmit. * * Returns: True if it is OK to send. * *------------------------------------------------------------------------------ * * Description: These two functions perform the final stage of filtering * before sending a packet from the IGate server to the radio. * * ig_to_tx_remember must be called for every packet, from the IGate * server, sent to the radio transmitter. * * ig_to_tx_allow decides whether this should be allowed thru * based on recent activity. We will drop the packet if it is a * duplicate of another sent recently. * * This is the essentially the same as the pair of functions * above, for RF to IS, with one additional restriction. * * The typical residential Internet connection is around 10,000 * to 50,000 times faster than the radio links we are using. It would * be easy to completely saturate the radio channel if we are * not careful. * * Besides looking for duplicates, this will also tabulate the * number of packets sent during the past minute and past 5 * minutes and stop sending if a limit is reached. * * More Discussion: * * Consider the following example. * I hear a packet from W1TG-1 three times over the radio then get the * (almost) same thing twice from APRS-IS. * * * Digipeater N3LEE-10 audio level = 23(10/6) [NONE] __||||||| * [0.5] W1TG-1>APU25N,N3LEE-10*,WIDE2-1: * Station Capabilities, Ambulance, UIview 32 bit apps * IGATE,MSG_CNT=30,LOC_CNT=61 * * [0H] W1TG-1>APU25N,N3LEE-10,WB2OSZ-14*: * * Digipeater WIDE2 (probably N3LEE-4) audio level = 22(10/6) [NONE] __||||||| * [0.5] W1TG-1>APU25N,N3LEE-10,N3LEE-4,WIDE2*: * Station Capabilities, Ambulance, UIview 32 bit apps * IGATE,MSG_CNT=30,LOC_CNT=61 * * Digipeater WIDE2 (probably AB1OC-10) audio level = 31(14/11) [SINGLE] ____:____ * [0.4] W1TG-1>APU25N,N3LEE-10,AB1OC-10,WIDE2*: * Station Capabilities, Ambulance, UIview 32 bit apps * IGATE,MSG_CNT=30,LOC_CNT=61 * * [ig] W1TG-1>APU25N,WIDE2-2,qAR,W1GLO-11:APDW13,WIDE1-1:}W1TG-1>APU25N,TCPIP,WB2OSZ-14*:APU25N,K1FFK,WIDE2*,qAR,WB2ZII-15: * [0L] WB2OSZ-14>APDW13,WIDE1-1:}W1TG-1>APU25N,TCPIP,WB2OSZ-14*: * * * The first one gets retransmitted by digipeating. * * Why are we getting the same thing twice from APRS-IS? Shouldn't remove duplicates? * Look closely. The original packet, on RF, had a CR character at the end. * At first I thought duplicate removal was broken but it turns out they * are not exactly the same. * * >>> The receive IGate spec says a packet should be cut at a CR. <<< * * In one case it is removed as expected In another case, it is replaced by a trailing * space character. Maybe someone thought non printable characters should be * replaced by spaces??? (I have since been told someone thought it would be a good * idea to replace unprintable characters with spaces. How's that working out for MIC-E position???) * * At first I was tempted to remove any trailing spaces to make up for the other * IGate adding it. Two wrongs don't make a right. Trailing spaces are not that * rare and removing them would corrupt the data. My new strategy is for * the duplicate detection compare to ignore trailing space, CR, and LF. * * We already transmitted the same thing by the digipeater function so this should * also go into memory for avoiding duplicates out of the transmit IGate. * * Future: * Should the digipeater function avoid transmitting something if it * was recently transmitted by the IGate funtion? * This code is pretty much the same as dedupe.c. Maybe it could all * be combined into one. Need to ponder this some more. * *--------------------------------------------------------------------*/ /* Here is another complete example, with the "-diii" debugging option to show details. We receive the signal directly from the source: (zzz.log 1011) N1ZKO-7 audio level = 33(16/10) [NONE] ___|||||| [0.5] N1ZKO-7>T2TS7X,WIDE1-1,WIDE2-1:`c6wl!i[/>"4]}[scanning]=<0x0d> MIC-E, Human, Kenwood TH-D72, In Service N 42 43.7800, W 071 26.9100, 0 MPH, course 177, alt 230 ft [scanning] We did not send it to the IS server recently. Rx IGate: Truncated information part at CR. rx_to_ig_allow? 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]=" rx_to_ig_allow? YES Send it now and remember that fact. [rx>ig] N1ZKO-7>T2TS7X,WIDE1-1,WIDE2-1,qAR,WB2OSZ-14:`c6wl!i[/>"4]}[scanning]= rx_to_ig_remember [21] = 1447683040 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]=" Digipeat it. Notice how it has a trailing CR. TODO: Why is the CRC different? Content looks the same. ig_to_tx_remember [38] = ch0 d1 1447683040 27598 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]= " [0H] N1ZKO-7>T2TS7X,WB2OSZ-14*,WIDE2-1:`c6wl!i[/>"4]}[scanning]=<0x0d> Now we hear it again, thru a digipeater. Not sure who. Was it UNCAN or was it someone else who doesn't use tracing? See my rant in the User Guide about this. Digipeater WIDE2 (probably UNCAN) audio level = 30(15/10) [NONE] __|||::__ [0.4] N1ZKO-7>T2TS7X,KB1POR-2,UNCAN,WIDE2*:`c6wl!i[/>"4]}[scanning]=<0x0d> MIC-E, Human, Kenwood TH-D72, In Service N 42 43.7800, W 071 26.9100, 0 MPH, course 177, alt 230 ft [scanning] Was sent to server recently so don't do it again. Rx IGate: Truncated information part at CR. rx_to_ig_allow? 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]=" rx_to_ig_allow? NO. Seen 1 seconds ago. Rx IGate: Drop duplicate of same packet seen recently. We hear it a third time, by a different digipeater. Digipeater WIDE1 (probably N3LEE-10) audio level = 23(12/6) [NONE] __||||||| [0.5] N1ZKO-7>T2TS7X,N3LEE-10,WIDE1*,WIDE2-1:`c6wl!i[/>"4]}[scanning]=<0x0d> MIC-E, Human, Kenwood TH-D72, In Service N 42 43.7800, W 071 26.9100, 0 MPH, course 177, alt 230 ft [scanning] It's a duplicate, so don't send to server. Rx IGate: Truncated information part at CR. rx_to_ig_allow? 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]=" rx_to_ig_allow? NO. Seen 2 seconds ago. Rx IGate: Drop duplicate of same packet seen recently. Digipeater: Drop redundant packet to channel 0. The server sends it to us. NOTICE: The CR at the end has been replaced by a space. [ig>tx] N1ZKO-7>T2TS7X,K1FFK,WA2MJM-15*,qAR,WB2ZII-15:`c6wl!i[/>"4]}[scanning]=<0x20> Should we transmit it? No, we sent it recently by the digipeating function (note "bydigi=1"). DEBUG: ax25_dedupe_crc ignoring trailing space. ig_to_tx_allow? ch0 27598 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]= " ig_to_tx_allow? NO. Sent 4 seconds ago. bydigi=1 Tx IGate: Drop duplicate packet transmitted recently. [0L] WB2OSZ-14>APDW13,WIDE1-1:}W1AST>TRPR4T,TCPIP,WB2OSZ-14*:`d=Ml!3>/"4N} [rx>ig] # */ #define IG2TX_DEDUPE_TIME 60 /* Do not send duplicate within 60 seconds. */ #define IG2TX_HISTORY_MAX 50 /* Remember the last 50 sent from server to radio. */ /* Ideally this should be a critical region because */ /* it is being written by two threads but I'm not that concerned. */ static int ig2tx_insert_next; static time_t ig2tx_time_stamp[IG2TX_HISTORY_MAX]; static unsigned short ig2tx_checksum[IG2TX_HISTORY_MAX]; static unsigned char ig2tx_chan[IG2TX_HISTORY_MAX]; static unsigned short ig2tx_bydigi[IG2TX_HISTORY_MAX]; static void ig_to_tx_init (void) { int n; for (n=0; n= 3) { char src[AX25_MAX_ADDR_LEN]; char dest[AX25_MAX_ADDR_LEN]; unsigned char *pinfo; int info_len; ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); info_len = ax25_get_info (pp, &pinfo); (void)info_len; text_color_set(DW_COLOR_DEBUG); dw_printf ("ig_to_tx_remember [%d] = ch%d d%d %d %d \"%s>%s:%s\"\n", ig2tx_insert_next, chan, bydigi, (int)(now), crc, src, dest, pinfo); } ig2tx_time_stamp[ig2tx_insert_next] = now; ig2tx_checksum[ig2tx_insert_next] = crc; ig2tx_chan[ig2tx_insert_next] = chan; ig2tx_bydigi[ig2tx_insert_next] = bydigi; ig2tx_insert_next++; if (ig2tx_insert_next >= IG2TX_HISTORY_MAX) { ig2tx_insert_next = 0; } } static int ig_to_tx_allow (packet_t pp, int chan) { unsigned short crc = ax25_dedupe_crc(pp); time_t now = time(NULL); int j; int count_1, count_5; int increase_limit; unsigned char *pinfo; int info_len; info_len = ax25_get_info (pp, &pinfo); (void)info_len; if (s_debug >= 2) { char src[AX25_MAX_ADDR_LEN]; char dest[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); text_color_set(DW_COLOR_DEBUG); dw_printf ("ig_to_tx_allow? ch%d %d \"%s>%s:%s\"\n", chan, crc, src, dest, pinfo); } /* Consider transmissions on this channel only by either digi or IGate. */ for (j=0; j= now - IG2TX_DEDUPE_TIME) { /* We have a duplicate within some time period. */ if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) { /* I think I want to avoid the duplicate suppression for "messages." */ /* Suppose we transmit a message from station X and it doesn't get an ack back. */ /* Station X then sends exactly the same thing 20 seconds later. */ /* We don't want to suppress the retry. */ if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("ig_to_tx_allow? Yes for duplicate message sent %d seconds ago. bydigi=%d\n", (int)(now - ig2tx_time_stamp[j]), ig2tx_bydigi[j]); } } else { /* Normal (non-message) case. */ if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); // could be multiple entries and this might not be the most recent. dw_printf ("ig_to_tx_allow? NO. Duplicate sent %d seconds ago. bydigi=%d\n", (int)(now - ig2tx_time_stamp[j]), ig2tx_bydigi[j]); } text_color_set(DW_COLOR_INFO); dw_printf ("Tx IGate: Drop duplicate packet transmitted recently.\n"); return 0; } } } /* IGate transmit counts must not include digipeater transmissions. */ count_1 = 0; count_5 = 0; for (j=0; j= now - 60) count_1++; if (ig2tx_time_stamp[j] >= now - 300) count_5++; } } /* "Messages" (special APRS data type ":") are intentional and more */ /* important than all of the other mostly repetitive useless junk */ /* flowing thru here. */ /* It would be unfortunate to discard a message because we already */ /* hit our limit. I don't want to completely eliminate limiting for */ /* messages, in case something goes terribly wrong, but we can triple */ /* the normal limit for them. */ increase_limit = 1; if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) { increase_limit = 3; } if (count_1 >= save_igate_config_p->tx_limit_1 * increase_limit) { text_color_set(DW_COLOR_ERROR); dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 1 minute.\n", save_igate_config_p->tx_limit_1); return 0; } if (count_5 >= save_igate_config_p->tx_limit_5 * increase_limit) { text_color_set(DW_COLOR_ERROR); dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 5 minutes.\n", save_igate_config_p->tx_limit_5); return 0; } if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("ig_to_tx_allow? YES\n"); } return 1; } /* end ig_to_tx_allow */ /* end igate.c */ direwolf-1.5+dfsg/igate.h000066400000000000000000000070531347750676600154050ustar00rootroot00000000000000 /*---------------------------------------------------------------------------- * * Name: igate.h * * Purpose: Interface to the Internet Gateway functions. * *-----------------------------------------------------------------------------*/ #ifndef IGATE_H #define IGATE_H 1 #include "ax25_pad.h" #include "digipeater.h" #include "audio.h" #define DEFAULT_IGATE_PORT 14580 struct igate_config_s { /* * For logging into the IGate server. */ char t2_server_name[40]; /* Tier 2 IGate server name. */ int t2_server_port; /* Typically 14580. */ char t2_login[AX25_MAX_ADDR_LEN];/* e.g. WA9XYZ-15 */ /* Note that the ssid could be any two alphanumeric */ /* characters not just 1 thru 15. */ /* Could be same or different than the radio call(s). */ /* Not sure what the consequences would be. */ char t2_passcode[8]; /* Max. 5 digits. Could be "-1". */ char *t2_filter; /* Optional filter for IS -> RF direction. */ /* This is the "server side" filter. */ /* A better name would be subscription or something */ /* like that because we can only ask for more. */ /* * For transmitting. */ int tx_chan; /* Radio channel for transmitting. */ /* 0=first, etc. -1 for none. */ /* Presently IGate can transmit on only a single channel. */ /* A future version might generalize this. */ /* Each transmit channel would have its own client side filtering. */ char tx_via[80]; /* VIA path for transmitting third party packets. */ /* Usual text representation. */ /* Must start with "," if not empty so it can */ /* simply be inserted after the destination address. */ int max_digi_hops; /* Maximum number of digipeater hops possible for via path. */ /* Derived from the SSID when last character of address is a digit. */ /* e.g. "WIDE1-1,WIDE5-2" would be 3. */ /* This is useful to know so we can determine how many */ /* stations we might be able to reach. */ int tx_limit_1; /* Max. packets to transmit in 1 minute. */ int tx_limit_5; /* Max. packets to transmit in 5 minutes. */ int igmsp; /* Number of message sender position reports to allow. */ /* Common practice is to default to 1. */ /* We allow additional flexibility of 0 to disable feature */ /* or a small number to allow more. */ /* * Receiver to IS data options. */ int rx2ig_dedupe_time; /* seconds. 0 to disable. */ /* * Special SATgate mode to delay packets heard directly. */ int satgate_delay; /* seconds. 0 to disable. */ }; #define IGATE_TX_LIMIT_1_DEFAULT 6 #define IGATE_TX_LIMIT_1_MAX 20 #define IGATE_TX_LIMIT_5_DEFAULT 20 #define IGATE_TX_LIMIT_5_MAX 80 #define IGATE_RX2IG_DEDUPE_TIME 0 /* Issue 85. 0 means disable dupe checking in RF>IS direction. */ /* See comments in rx_to_ig_remember & rx_to_ig_allow. */ /* Currently there is no configuration setting to change this. */ #define DEFAULT_SATGATE_DELAY 10 #define MIN_SATGATE_DELAY 5 #define MAX_SATGATE_DELAY 30 /* Call this once at startup */ void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config, int debug_level); /* Call this with each packet received from the radio. */ void igate_send_rec_packet (int chan, packet_t recv_pp); /* This when digipeater transmits. Set bydigi to 1 . */ void ig_to_tx_remember (packet_t pp, int chan, int bydigi); /* Get statistics for IGATE status beacon. */ int igate_get_msg_cnt (void); int igate_get_pkt_cnt (void); int igate_get_upl_cnt (void); int igate_get_dnl_cnt (void); #endif direwolf-1.5+dfsg/kiss.c000066400000000000000000000356111347750676600152610ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 2014, 2016, 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: kiss.c * * Purpose: Act as a virtual KISS TNC for use by other packet radio applications. * This file implements it with a pseudo terminal for Linux only. * * Description: It implements the KISS TNC protocol as described in: * http://www.ka9q.net/papers/kiss.html * * Briefly, a frame is composed of * * * FEND (0xC0) * * Contents - with special escape sequences so a 0xc0 * byte in the data is not taken as end of frame. * as part of the data. * * FEND * * The first byte of the frame contains: * * * port number in upper nybble. * * command in lower nybble. * * * Commands from application recognized: * * _0 Data Frame AX.25 frame in raw format. * * _1 TXDELAY See explanation in xmit.c. * * _2 Persistence " " * * _3 SlotTime " " * * _4 TXtail " " * Spec says it is obsolete but Xastir * sends it and we respect it. * * _5 FullDuplex Ignored. * * _6 SetHardware TNC specific. * * FF Return Exit KISS mode. Ignored. * * * Messages sent to client application: * * _0 Data Frame Received AX.25 frame in raw format. * * * Platform differences: * * For the Linux case, * We supply a pseudo terminal for use by other applications. * * Version 1.5: Split serial port version off into its own file. * *---------------------------------------------------------------*/ #if __WIN32__ // Stub for Windows. #include "direwolf.h" #include "kiss.h" void kisspt_init (struct misc_config_s *mc) { return; } void kisspt_set_debug (int n) { return; } void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client) { return; } #else // Rest of file is for Linux only. #include "direwolf.h" #include #include #include #include #include #include #include #include #include #include #ifdef __OpenBSD__ #include #else #include #endif #include "tq.h" #include "ax25_pad.h" #include "textcolor.h" #include "kiss.h" #include "kiss_frame.h" #include "xmit.h" /* * Accumulated KISS frame and state of decoder. */ static kiss_frame_t kf; /* * These are for a Linux pseudo terminal. */ static int pt_master_fd = -1; /* File descriptor for my end. */ static char pt_slave_name[32]; /* Pseudo terminal slave name */ /* like /dev/pts/999 */ /* * Symlink to pseudo terminal name which changes. */ #define TMP_KISSTNC_SYMLINK "/tmp/kisstnc" static void * kisspt_listen_thread (void *arg); static int kisspt_debug = 0; /* Print information flowing from and to client. */ void kisspt_set_debug (int n) { kisspt_debug = n; } /* In server.c. Should probably move to some misc. function file. */ void hex_dump (unsigned char *p, int len); /*------------------------------------------------------------------- * * Name: kisspt_init * * Purpose: Set up a pseudo terminal acting as a virtual KISS TNC. * * * Inputs: * * Outputs: * * Description: (1) Create a pseudo terminal for the client to use. * (2) Start a new thread to listen for commands from client app * so the main application doesn't block while we wait. * * *--------------------------------------------------------------------*/ static int kisspt_open_pt (void); void kisspt_init (struct misc_config_s *mc) { pthread_t kiss_pterm_listen_tid; int e; memset (&kf, 0, sizeof(kf)); /* * This reads messages from client. */ pt_master_fd = -1; if (mc->enable_kiss_pt) { pt_master_fd = kisspt_open_pt (); if (pt_master_fd != -1) { e = pthread_create (&kiss_pterm_listen_tid, (pthread_attr_t*)NULL, kisspt_listen_thread, NULL); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create kiss listening thread for Linux pseudo terminal"); } } } else { //text_color_set(DW_COLOR_INFO); //dw_printf ("Use -p command line option to enable KISS pseudo terminal.\n"); } #if DEBUG text_color_set (DW_COLOR_DEBUG); dw_printf ("end of kisspt_init: pt_master_fd = %d\n", pt_master_fd); #endif } /* * Returns fd for master side of pseudo terminal or -1 for error. */ static int kisspt_open_pt (void) { int fd; char *pts; struct termios ts; int e; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("kisspt_open_pt ( )\n"); #endif fd = posix_openpt(O_RDWR|O_NOCTTY); if (fd == -1 || grantpt (fd) == -1 || unlockpt (fd) == -1 || (pts = ptsname (fd)) == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Could not create pseudo terminal for KISS TNC.\n"); return (-1); } strlcpy (pt_slave_name, pts, sizeof(pt_slave_name)); e = tcgetattr (fd, &ts); if (e != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't get pseudo terminal attributes, err=%d\n", e); perror ("pt tcgetattr"); } cfmakeraw (&ts); ts.c_cc[VMIN] = 1; /* wait for at least one character */ ts.c_cc[VTIME] = 0; /* no fancy timing. */ e = tcsetattr (fd, TCSANOW, &ts); if (e != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't set pseudo terminal attributes, err=%d\n", e); perror ("pt tcsetattr"); } /* * We had a problem here since the beginning. * If no one was reading from the other end of the pseudo * terminal, the buffer space would eventually fill up, * the write here would block, and the receive decode * thread would get stuck. * * March 2016 - A "select" was put before the read to * solve a different problem. With that in place, we can * now use non-blocking I/O and detect the buffer full * condition here. */ // text_color_set(DW_COLOR_DEBUG); // dw_printf("Debug: Try using non-blocking mode for pseudo terminal.\n"); int flags = fcntl(fd, F_GETFL, 0); e = fcntl (fd, F_SETFL, flags | O_NONBLOCK); if (e != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno); perror ("pt fcntl"); } text_color_set(DW_COLOR_INFO); dw_printf("Virtual KISS TNC is available on %s\n", pt_slave_name); #if 1 // Sample code shows this. Why would we open it here? // On Ubuntu, the slave side disappears after a few // seconds if no one opens it. Same on Raspbian which // is also based on Debian. // Need to revisit this. int pt_slave_fd; pt_slave_fd = open(pt_slave_name, O_RDWR|O_NOCTTY); if (pt_slave_fd < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't open %s\n", pt_slave_name); perror (""); return -1; } #endif /* * The device name is not the same every time. * This is inconvenient for the application because it might * be necessary to change the device name in the configuration. * Create a symlink, /tmp/kisstnc, so the application configuration * does not need to change when the pseudo terminal name changes. */ unlink (TMP_KISSTNC_SYMLINK); // TODO: Is this removed when application exits? if (symlink (pt_slave_name, TMP_KISSTNC_SYMLINK) == 0) { dw_printf ("Created symlink %s -> %s\n", TMP_KISSTNC_SYMLINK, pt_slave_name); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create symlink %s\n", TMP_KISSTNC_SYMLINK); perror (""); } return (fd); } /*------------------------------------------------------------------- * * Name: kisspt_send_rec_packet * * Purpose: Send a received packet or text string to the client app. * * Inputs: chan - Channel number where packet was received. * 0 = first, 1 = second if any. * * kiss_cmd - Usually KISS_CMD_DATA_FRAME but we can also have * KISS_CMD_SET_HARDWARE when responding to a query. * * pp - Identifier for packet object. * * fbuf - Address of raw received frame buffer * or a text string. * * flen - Length of raw received frame not including the FCS * or -1 for a text string. * * client - Not used for pseudo terminal. * Here so that 3 related functions all have * the same parameter list. * * Description: Send message to client. * We really don't care if anyone is listening or not. * I don't even know if we can find out. * *--------------------------------------------------------------------*/ void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client) { unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2]; int kiss_len; int err; if (pt_master_fd == -1) { return; } if (flen < 0) { flen = strlen((char*)fbuf); if (kisspt_debug) { kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen); } strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff)); kiss_len = strlen((char *)kiss_buff); } else { unsigned char stemp[AX25_MAX_PACKET_LEN + 1]; if (flen > (int)(sizeof(stemp)) - 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nPseudo Terminal KISS buffer too small. Truncated.\n\n"); flen = (int)(sizeof(stemp)) - 1; } stemp[0] = (chan << 4) | kiss_cmd; memcpy (stemp+1, fbuf, flen); if (kisspt_debug >= 2) { /* AX.25 frame with the CRC removed. */ text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); dw_printf ("Packet content before adding KISS framing and any escapes:\n"); hex_dump (fbuf, flen); } kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff); /* This has KISS framing and escapes for sending to client app. */ if (kisspt_debug) { kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len); } } err = write (pt_master_fd, kiss_buff, (size_t)kiss_len); if (err == -1 && errno == EWOULDBLOCK) { text_color_set (DW_COLOR_INFO); dw_printf ("KISS SEND - Discarding message because no one is listening.\n"); dw_printf ("This happens when you use the -p option and don't read from the pseudo terminal.\n"); } else if (err != kiss_len) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError sending KISS message to client application on pseudo terminal. fd=%d, len=%d, write returned %d, errno = %d\n\n", pt_master_fd, kiss_len, err, errno); perror ("pt write"); } } /* kisspt_send_rec_packet */ /*------------------------------------------------------------------- * * Name: kisspt_get * * Purpose: Read one byte from the KISS client app. * * Global In: pt_master_fd * * Returns: one byte (value 0 - 255) or terminate thread on error. * * Description: There is room for improvment here. Reading one byte * at a time is inefficient. We could read a large block * into a local buffer and return a byte from that most of the time. * Is it worth the effort? I don't know. With GHz processors and * the low data rate here it might not make a noticable difference. * *--------------------------------------------------------------------*/ static int kisspt_get (void) { unsigned char ch; int n = 0; fd_set fd_in, fd_ex; int rc; while ( n == 0 ) { /* * Since the beginning we've always had a couple annoying problems with * the pseudo terminal KISS interface. * When using "kissattach" we would sometimes get the error message: * * kissattach: Error setting line discipline: TIOCSETD: Device or resource busy * Are you sure you have enabled MKISS support in the kernel * or, if you made it a module, that the module is loaded? * * martinhpedersen came up with the interesting idea of putting in a "select" * before the "read" and explained it like this: * * "Reading from master fd of the pty before the client has connected leads * to trouble with kissattach. Use select to check if the slave has sent * any data before trying to read from it." * * "This fix resolves the issue by not reading from the pty's master fd, until * kissattach has opened and configured the slave. This is implemented using * select() to wait for data before reading from the master fd." * * The submitted code looked like this: * * FD_ZERO(&fd_in); * rc = select(pt_master_fd + 1, &fd_in, NULL, &fd_in, NULL); * * That doesn't look right to me for a couple reasons. * First, I would expect to use FD_SET for the fd. * Second, using the same bit mask for two arguments doesn't seem * like a good idea because select modifies them. * When I tried running it, we don't get the failure message * anymore but the select never returns so we can't read data from * the KISS client app. * * I think this is what we want. * * Tested on Raspian (ARM) and Ubuntu (x86_64). * We don't get the error from kissattach anymore. */ FD_ZERO(&fd_in); FD_SET(pt_master_fd, &fd_in); FD_ZERO(&fd_ex); FD_SET(pt_master_fd, &fd_ex); rc = select(pt_master_fd + 1, &fd_in, NULL, &fd_ex, NULL); #if 0 text_color_set(DW_COLOR_DEBUG); dw_printf ("select returns %d, errno=%d, fd=%d, fd_in=%08x, fd_ex=%08x\n", rc, errno, pt_master_fd, *((int*)(&fd_in)), *((int*)(&fd_in))); #endif if (rc == 0) { continue; // When could we get a 0? } if (rc == -1 || (n = read(pt_master_fd, &ch, (size_t)1)) != 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError receiving KISS message from client application. Closing %s.\n\n", pt_slave_name); perror (""); close (pt_master_fd); pt_master_fd = -1; unlink (TMP_KISSTNC_SYMLINK); pthread_exit (NULL); } } #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("kisspt_get(%d) returns 0x%02x\n", fd, ch); #endif return (ch); } /*------------------------------------------------------------------- * * Name: kisspt_listen_thread * * Purpose: Read messages from serial port KISS client application. * * Global In: * * Description: Reads bytes from the KISS client app and * sends them to kiss_rec_byte for processing. * *--------------------------------------------------------------------*/ static void * kisspt_listen_thread (void *arg) { unsigned char ch; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("kisspt_listen_thread ( %d )\n", fd); #endif while (1) { ch = kisspt_get(); kiss_rec_byte (&kf, ch, kisspt_debug, -1, kisspt_send_rec_packet); } return (void *) 0; /* Unreachable but avoids compiler warning. */ } #endif // Linux version /* end kiss.c */ direwolf-1.5+dfsg/kiss.h000066400000000000000000000005401347750676600152570ustar00rootroot00000000000000 /* * Name: kiss.h * * This is for the pseudo terminal KISS interface. */ #include "ax25_pad.h" /* for packet_t */ #include "config.h" void kisspt_init (struct misc_config_s *misc_config); void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client); void kisspt_set_debug (int n); /* end kiss.h */ direwolf-1.5+dfsg/kiss_frame.c000066400000000000000000000660641347750676600164410ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2014, 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: kiss_frame.c * * Purpose: Common code used by Serial port and network versions of KISS protocol. * * Description: The KISS TNC protocol is described in http://www.ka9q.net/papers/kiss.html * * ( An extended form, to handle multiple TNCs on a single serial port. * Not applicable for our situation. http://he.fi/pub/oh7lzb/bpq/multi-kiss.pdf ) * * Briefly, a frame is composed of * * * FEND (0xC0) * * Contents - with special escape sequences so a 0xc0 * byte in the data is not taken as end of frame. * as part of the data. * * FEND * * The first byte of the frame contains: * * * port number (radio channel) in upper nybble. * * command in lower nybble. * * * Commands from application tp TNC: * * _0 Data Frame AX.25 frame in raw format. * * _1 TXDELAY See explanation in xmit.c. * * _2 Persistence " " * * _3 SlotTime " " * * _4 TXtail " " * Spec says it is obsolete but Xastir * sends it and we respect it. * * _5 FullDuplex Full Duplex. Transmit immediately without * waiting for channel to be clear. * * _6 SetHardware TNC specific. * * _C XKISS extension - not supported. * _E XKISS extention - not supported. * * FF Return Exit KISS mode. Ignored. * * * Messages sent to client application: * * _0 Data Frame Received AX.25 frame in raw format. * * _6 SetHardware TNC specific. * Usually a response to a query. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "kiss_frame.h" #include "tq.h" #include "xmit.h" #include "version.h" /* In server.c. Should probably move to some misc. function file. */ void hex_dump (unsigned char *p, int len); #ifdef KISSUTIL void hex_dump (unsigned char *p, int len) { int n, i, offset; offset = 0; while (len > 0) { n = len < 16 ? len : 16; printf (" %03x: ", offset); for (i=0; i * <0x0d> * XFLOW OFF<0x0d> * FULLDUP OFF<0x0d> * KISS ON<0x0d> * RESTART<0x0d> * <0x03><0x03><0x03> * TC 1<0x0d> * TN 2,0<0x0d><0x0d><0x0d> * XFLOW OFF<0x0d> * FULLDUP OFF<0x0d> * KISS ON<0x0d> * RESTART<0x0d> * * This keeps repeating over and over and over and over again if * it doesn't get any sort of response. * * Let's try to keep it happy by sending back a command prompt. */ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int)) { //dw_printf ("kiss_frame ( %c %02x ) \n", ch, ch); switch (kf->state) { case KS_SEARCHING: /* Searching for starting FEND. */ default: if (ch == FEND) { /* Start of frame. But first print any collected noise for debugging. */ if (kf->noise_len > 0) { if (debug) { kiss_debug_print (FROM_CLIENT, "Rejected Noise", kf->noise, kf->noise_len); } kf->noise_len = 0; } kf->kiss_len = 0; kf->kiss_msg[kf->kiss_len++] = ch; kf->state = KS_COLLECTING; return; } /* Noise to be rejected. */ if (kf->noise_len < MAX_NOISE_LEN) { kf->noise[kf->noise_len++] = ch; } if (ch == '\r') { if (debug) { kiss_debug_print (FROM_CLIENT, "Rejected Noise", kf->noise, kf->noise_len); kf->noise[kf->noise_len] = '\0'; } #ifndef KISSUTIL /* Try to appease client app by sending something back. */ if (strcasecmp("restart\r", (char*)(kf->noise)) == 0 || strcasecmp("reset\r", (char*)(kf->noise)) == 0) { // first 2 parameters don't matter when length is -1 indicating text. (*sendfun) (0, 0, (unsigned char *)"\xc0\xc0", -1, client); } else { (*sendfun) (0, 0, (unsigned char *)"\r\ncmd:", -1, client); } #endif kf->noise_len = 0; } return; break; case KS_COLLECTING: /* Frame collection in progress. */ if (ch == FEND) { unsigned char unwrapped[AX25_MAX_PACKET_LEN]; int ulen; /* End of frame. */ if (kf->kiss_len == 0) { /* Empty frame. Starting a new one. */ kf->kiss_msg[kf->kiss_len++] = ch; return; } if (kf->kiss_len == 1 && kf->kiss_msg[0] == FEND) { /* Empty frame. Just go on collecting. */ return; } kf->kiss_msg[kf->kiss_len++] = ch; if (debug) { /* As received over the wire from client app. */ kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len); } ulen = kiss_unwrap (kf->kiss_msg, kf->kiss_len, unwrapped); if (debug >= 2) { /* Append CRC to this and it goes out over the radio. */ text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); dw_printf ("Packet content after removing KISS framing and any escapes:\n"); /* Don't include the "type" indicator. */ /* It contains the radio channel and type should always be 0 here. */ hex_dump (unwrapped+1, ulen-1); } kiss_process_msg (unwrapped, ulen, debug, client, sendfun); kf->state = KS_SEARCHING; return; } if (kf->kiss_len < MAX_KISS_LEN) { kf->kiss_msg[kf->kiss_len++] = ch; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS message exceeded maximum length.\n"); } return; break; } return; /* unreachable but suppress compiler warning. */ } /* end kiss_rec_byte */ /*------------------------------------------------------------------- * * Name: kiss_process_msg * * Purpose: Process a message from the KISS client. * * Inputs: kiss_msg - Kiss frame with FEND and escapes removed. * The first byte contains channel and command. * * kiss_len - Number of bytes including the command. * * debug - Debug option is selected. * * client - Client app number for TCP KISS. * Ignored for pseudo termal and serial port. * * sendfun - Function to send something to the client application. * "Set Hardware" can send a response. * *-----------------------------------------------------------------*/ #ifndef KISSUTIL // All these ifdefs in here are a sign that this should be refactored. // Should split this into multiple files. // Some functions are only for the TNC end. // Other functions are suitble for both TNC and client app. // This is used only by the TNC sided. void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int)) { int port; int cmd; packet_t pp; alevel_t alevel; port = (kiss_msg[0] >> 4) & 0xf; cmd = kiss_msg[0] & 0xf; switch (cmd) { case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */ /* Special hack - Discard apparently bad data from Linux AX25. */ /* Note July 2017: There is a variant of of KISS, called SMACK, that assumes */ /* a TNC can never have more than 8 ports. http://symek.de/g/smack.html */ /* It uses the MSB to indicate that a checksum is added. I wonder if this */ /* is why we sometimes hear about a request to transmit on channel 8. */ /* Should we have a message that asks the user if SMACK is being used, */ /* and if so, turn it off in the application configuration? */ /* Our current default is a maximum of 6 channels but it is easily */ /* increased by changing one number and recompiling. */ if (kiss_len > 16 && (port == 2 || port == 8) && kiss_msg[1] == 'Q' << 1 && kiss_msg[2] == 'S' << 1 && kiss_msg[3] == 'T' << 1 && kiss_msg[4] == ' ' << 1 && kiss_msg[15] == 3 && kiss_msg[16] == 0xcd) { if (debug) { text_color_set(DW_COLOR_ERROR); dw_printf ("Special case - Drop packets which appear to be in error.\n"); } return; } /* Verify that the port (channel) number is valid. */ if (port < 0 || port >= MAX_CHANS || ! save_audio_config_p->achan[port].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid transmit channel %d from KISS client app.\n", port); text_color_set(DW_COLOR_DEBUG); kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len); return; } memset (&alevel, 0xff, sizeof(alevel)); pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Invalid KISS data frame from client app.\n"); } else { /* How can we determine if it is an original or repeated message? */ /* If there is at least one digipeater in the frame, AND */ /* that digipeater has been used, it should go out quickly thru */ /* the high priority queue. */ /* Otherwise, it is an original for the low priority queue. */ if (ax25_get_num_repeaters(pp) >= 1 && ax25_get_h(pp,AX25_REPEATER_1)) { tq_append (port, TQ_PRIO_0_HI, pp); } else { tq_append (port, TQ_PRIO_1_LO, pp); } } break; case KISS_CMD_TXDELAY: /* 1 = TXDELAY */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for TXDELAY command.\n"); return; } text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set TXDELAY = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port); if (kiss_msg[1] < 4 || kiss_msg[1] > 100) { text_color_set(DW_COLOR_ERROR); dw_printf ("Are you sure you want such an extreme value for TXDELAY?\n"); dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); } xmit_set_txdelay (port, kiss_msg[1]); break; case KISS_CMD_PERSISTENCE: /* 2 = Persistence */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for PERSISTENCE command.\n"); return; } text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set Persistence = %d, port %d\n", kiss_msg[1], port); if (kiss_msg[1] < 5 || kiss_msg[1] > 250) { text_color_set(DW_COLOR_ERROR); dw_printf ("Are you sure you want such an extreme value for PERSIST?\n"); dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); } xmit_set_persist (port, kiss_msg[1]); break; case KISS_CMD_SLOTTIME: /* 3 = SlotTime */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for SLOTTIME command.\n"); return; } text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set SlotTime = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port); if (kiss_msg[1] < 2 || kiss_msg[1] > 50) { text_color_set(DW_COLOR_ERROR); dw_printf ("Are you sure you want such an extreme value for SLOTTIME?\n"); dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); } xmit_set_slottime (port, kiss_msg[1]); break; case KISS_CMD_TXTAIL: /* 4 = TXtail */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for TXTAIL command.\n"); return; } text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set TXtail = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port); if (kiss_msg[1] < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("Setting TXTAIL so low is asking for trouble. You probably don't want to do this.\n"); dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); } xmit_set_txtail (port, kiss_msg[1]); break; case KISS_CMD_FULLDUPLEX: /* 5 = FullDuplex */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for FULLDUPLEX command.\n"); return; } text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set FullDuplex = %d, port %d\n", kiss_msg[1], port); xmit_set_fulldup (port, kiss_msg[1]); break; case KISS_CMD_SET_HARDWARE: /* 6 = TNC specific */ if (kiss_len < 2) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS ERROR: Missing value for SET HARDWARE command.\n"); return; } kiss_msg[kiss_len] = '\0'; text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set hardware \"%s\", port %d\n", (char*)(kiss_msg+1), port); kiss_set_hardware (port, (char*)(kiss_msg+1), debug, client, sendfun); break; case KISS_CMD_END_KISS: /* 15 = End KISS mode, port should be 15. */ /* Ignore it. */ text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol end KISS mode - Ignored.\n"); break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Invalid command %d\n", cmd); kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len); text_color_set(DW_COLOR_INFO); dw_printf ("Troubleshooting tip:\n"); dw_printf ("Use \"-d kn\" option on direwolf command line to observe\n"); dw_printf ("all communication with the client application.\n"); if (cmd == XKISS_CMD_DATA || cmd == XKISS_CMD_POLL) { dw_printf ("\n"); dw_printf ("It looks like you are trying to use the \"XKISS\" protocol which is not supported.\n"); dw_printf ("Change your application settings to use standard \"KISS\" rather than some other variant.\n"); dw_printf ("If you are using Winlink Express, configure like this:\n"); dw_printf (" Packet TNC Type: KISS\n"); dw_printf (" Packet TNC Model: NORMAL -- Using ACKMODE will cause this error.\n"); dw_printf ("\n"); } break; } } /* end kiss_process_msg */ #endif // ifndef KISSUTIL /*------------------------------------------------------------------- * * Name: kiss_set_hardware * * Purpose: Process the "set hardware" command. * * Inputs: chan - channel, 0 - 15. * * command - All but the first byte. e.g. "TXBUF:99" * Case sensitive. * Will be modified so be sure caller doesn't care. * * debug - debug level. * * client - Client app number for TCP KISS. * Needed so we can send any response to the right client app. * Ignored for pseudo terminal and serial port. * * sendfun - Function to send something to the client application. * * This is the tricky part. We can have any combination of * serial port, pseudo terminal, and multiple TCP clients. * We need to send the response to same place where query came * from. The function is different for each class of device * and we need a client number for the TCP case because we * can have multiple TCP KISS clients at the same time. * * * Description: This is new in version 1.5. "Set hardware" was previously ignored. * * There are times when the client app might want to send configuration * commands, such as modem speed, to the KISS TNC or inquire about its * current state. * * The immediate motivation for adding this is that one application wants * to know how many frames are currently in the transmit queue. This can * be used for throttling of large transmissions and performing some action * after the last frame has been sent. * * The original KISS protocol spec offers no guidance on what "Set Hardware" might look * like. I'm aware of only two, drastically different, implementations: * * fldigi - http://www.w1hkj.com/FldigiHelp-3.22/kiss_command_page.html * * Everything is in human readable in both directions: * * COMMAND: [ parameter [ , parameter ... ] ] * * Lack of a parameter, in the client to TNC direction, is a query * which should generate a response in the same format. * * Used by applications, http://www.w1hkj.com/FldigiHelp/kiss_host_prgs_page.html * - BPQ32 * - UIChar * - YAAC * * mobilinkd - https://raw.githubusercontent.com/mobilinkd/tnc1/tnc2/bertos/net/kiss.c * * Single byte with the command / response code, followed by * zero or more value bytes. * * Used by applications: * - APRSdroid * * It would be beneficial to adopt one of them rather than doing something * completely different. It might even be possible to recognize both. * This might allow leveraging of other existing applications. * * Let's start with the easy to understand human readable format. * * Commands: (Client to TNC, with parameter(s) to set something.) * * none yet * * Queries: (Client to TNC, no parameters, generate a response.) * * Query Response Comment * ----- -------- ------- * * TNC: TNC:DIREWOLF 9.9 9.9 represents current version. * * TXBUF: TXBUF:999 Number of bytes (not frames) in transmit queue. * *--------------------------------------------------------------------*/ #ifndef KISSUTIL static void kiss_set_hardware (int chan, char *command, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int)) { char *param; char response[100]; param = strchr (command, ':'); if (param != NULL) { *param = '\0'; param++; if (strcmp(command, "TNC") == 0) { /* TNC - Identify software version. */ if (strlen(param) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Set Hardware TNC: Did not expect a parameter.\n"); } snprintf (response, sizeof(response), "DIREWOLF %d.%d", MAJOR_VERSION, MINOR_VERSION); (*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), client); } else if (strcmp(command, "TXBUF") == 0) { /* TXBUF - Number of bytes in transmit queue. */ if (strlen(param) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Set Hardware TXBUF: Did not expect a parameter.\n"); } int n = tq_count (chan, -1, "", "", 1); snprintf (response, sizeof(response), "TXBUF:%d", n); (*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), client); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Set Hardware unrecognized command: %s.\n", command); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS Set Hardware \"%s\" expected the form COMMAND:[parameter[,parameter...]]\n", command); } return; } /* end kiss_set_hardware */ #endif // ifndef KISSUTIL /*------------------------------------------------------------------- * * Name: kiss_debug_print * * Purpose: Print message to/from client for debugging. * * Inputs: fromto - Direction of message. * special - Comment if not a KISS frame. * pmsg - Address of the message block. * msg_len - Length of the message. * *--------------------------------------------------------------------*/ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len) { #ifndef KISSUTIL const char *direction [2] = { "from", "to" }; const char *prefix [2] = { "<<<", ">>>" }; const char *function[16] = { "Data frame", "TXDELAY", "P", "SlotTime", "TXtail", "FullDuplex", "SetHardware", "Invalid 7", "Invalid 8", "Invalid 9", "Invalid 10", "Invalid 11", "Invalid 12", "Invalid 13", "Invalid 14", "Return" }; #endif text_color_set(DW_COLOR_DEBUG); #ifdef KISSUTIL dw_printf ("From KISS TNC:\n"); #else dw_printf ("\n"); if (special == NULL) { unsigned char *p; /* to skip over FEND if present. */ p = pmsg; if (*p == FEND) p++; dw_printf ("%s %s %s KISS client application, port %d, total length = %d\n", prefix[(int)fromto], function[p[0] & 0xf], direction[(int)fromto], (p[0] >> 4) & 0xf, msg_len); } else { dw_printf ("%s %s %s KISS client application, total length = %d\n", prefix[(int)fromto], special, direction[(int)fromto], msg_len); } #endif hex_dump (pmsg, msg_len); } /* end kiss_debug_print */ #endif #endif /* DECAMAIN */ /* Quick unit test for encapsulate & unwrap */ // $ gcc -DKISSTEST kiss_frame.c ; ./a // Quick KISS test passed OK. #if KISSTEST int main () { unsigned char din[512]; unsigned char kissed[520]; unsigned char dout[520]; int klen; int dlen; int k; for (k = 0; k < 512; k++) { if (k < 256) { din[k] = k; } else { din[k] = 511 - k; } } klen = kiss_encapsulate (din, 512, kissed); assert (klen == 512 + 6); dlen = kiss_unwrap (kissed, klen, dout); assert (dlen == 512); assert (memcmp(din, dout, 512) == 0); dlen = kiss_unwrap (kissed+1, klen-1, dout); assert (dlen == 512); assert (memcmp(din, dout, 512) == 0); dw_printf ("Quick KISS test passed OK.\n"); exit (EXIT_SUCCESS); } #endif /* KISSTEST */ #endif /* WALK96 */ /* end kiss_frame.c */ direwolf-1.5+dfsg/kiss_frame.h000066400000000000000000000037011347750676600164330ustar00rootroot00000000000000 /* kiss_frame.h */ #include "audio.h" /* for struct audio_s */ /* * The first byte of a KISS frame has: * channel in upper nybble. * command in lower nybble. */ #define KISS_CMD_DATA_FRAME 0 #define KISS_CMD_TXDELAY 1 #define KISS_CMD_PERSISTENCE 2 #define KISS_CMD_SLOTTIME 3 #define KISS_CMD_TXTAIL 4 #define KISS_CMD_FULLDUPLEX 5 #define KISS_CMD_SET_HARDWARE 6 #define XKISS_CMD_DATA 12 // Not supported. http://he.fi/pub/oh7lzb/bpq/multi-kiss.pdf #define XKISS_CMD_POLL 14 // Not supported. #define KISS_CMD_END_KISS 15 /* * Special characters used by SLIP protocol. */ #define FEND 0xC0 #define FESC 0xDB #define TFEND 0xDC #define TFESC 0xDD enum kiss_state_e { KS_SEARCHING = 0, /* Looking for FEND to start KISS frame. */ /* Must be 0 so we can simply zero whole structure to initialize. */ KS_COLLECTING}; /* In process of collecting KISS frame. */ #define MAX_KISS_LEN 2048 /* Spec calls for at least 1024. */ /* Might want to make it longer to accomodate */ /* maximum packet length. */ #define MAX_NOISE_LEN 100 typedef struct kiss_frame_s { enum kiss_state_e state; unsigned char kiss_msg[MAX_KISS_LEN]; /* Leading FEND is optional. */ /* Contains escapes and ending FEND. */ int kiss_len; unsigned char noise[MAX_NOISE_LEN]; int noise_len; } kiss_frame_t; #ifndef KISSUTIL void kiss_frame_init (struct audio_s *pa); #endif int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out); int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out); void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int)); typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t; void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int)); void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len); /* end kiss_frame.h */ direwolf-1.5+dfsg/kissnet.c000066400000000000000000000541171347750676600157720ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011-2014, 2015, 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: kissnet.c * * Purpose: Provide service to other applications via KISS protocol via TCP socket. * * Input: * * Outputs: * * Description: This provides a TCP socket for communication with a client application. * * It implements the KISS TNS protocol as described in: * http://www.ka9q.net/papers/kiss.html * * Briefly, a frame is composed of * * * FEND (0xC0) * * Contents - with special escape sequences so a 0xc0 * byte in the data is not taken as end of frame. * as part of the data. * * FEND * * The first byte of the frame contains: * * * port number in upper nybble. * * command in lower nybble. * * * Commands from application recognized: * * _0 Data Frame AX.25 frame in raw format. * * _1 TXDELAY See explanation in xmit.c. * * _2 Persistence " " * * _3 SlotTime " " * * _4 TXtail " " * Spec says it is obsolete but Xastir * sends it and we respect it. * * _5 FullDuplex Ignored. * * _6 SetHardware TNC specific. * * FF Return Exit KISS mode. Ignored. * * * Messages sent to client application: * * _0 Data Frame Received AX.25 frame in raw format. * * * * * References: Getting Started with Winsock * http://msdn.microsoft.com/en-us/library/windows/desktop/bb530742(v=vs.85).aspx * * Future: Originally we had: * KISS over serial port. * AGW over socket. * This is the two of them munged together and we end up with duplicate code. * It would have been better to separate out the transport and application layers. * Maybe someday. * *---------------------------------------------------------------*/ /* * Native Windows: Use the Winsock interface. * Linux: Use the BSD socket interface. */ #include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h #if __WIN32__ #include #include // _WIN32_WINNT must be set to 0x0501 before including this #else #include #include #include #include #include #ifdef __OpenBSD__ #include #else #include #endif #endif #include #include #include #include #include "tq.h" #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "kissnet.h" #include "kiss_frame.h" #include "xmit.h" void hex_dump (unsigned char *p, int len); // This should be in a .h file. /* * Early on we allowed one AGW connection and one KISS TCP connection at a time. * In version 1.1, we allowed multiple concurrent client apps to attach with the AGW network protocol. * In Version 1.5, we do essentially the same here to allow multiple concurrent KISS TCP clients. * The default is a limit of 3 client applications at the same time. * You can increase the limit by changing the line below. * A larger number consumes more resources so don't go crazy by making it larger than needed. */ #define MAX_NET_CLIENTS 3 static int client_sock[MAX_NET_CLIENTS]; /* File descriptor for socket for */ /* communication with client application. */ /* Set to -1 if not connected. */ /* (Don't use SOCKET type because it is unsigned.) */ static kiss_frame_t kf[MAX_NET_CLIENTS]; /* Accumulated KISS frame and state of decoder. */ // TODO: define in one place, use everywhere. #if __WIN32__ #define THREAD_F unsigned __stdcall #else #define THREAD_F void * #endif static THREAD_F connect_listen_thread (void *arg); static THREAD_F kissnet_listen_thread (void *arg); static int kiss_debug = 0; /* Print information flowing from and to client. */ void kiss_net_set_debug (int n) { kiss_debug = n; } /*------------------------------------------------------------------- * * Name: kissnet_init * * Purpose: Set up a server to listen for connection requests from * an application such as Xastir or APRSIS32. * * Inputs: mc->kiss_port - TCP port for server. * Main program has default of 8000 but allows * an alternative to be specified on the command line * * 0 means disable. New in version 1.2. * * Outputs: * * Description: This starts two threads: * * to listen for a connection from client app. * * to listen for commands from client app. * so the main application doesn't block while we wait for these. * *--------------------------------------------------------------------*/ void kissnet_init (struct misc_config_s *mc) { int client; #if __WIN32__ HANDLE connect_listen_th; HANDLE cmd_listen_th[MAX_NET_CLIENTS]; #else pthread_t connect_listen_tid; pthread_t cmd_listen_tid[MAX_NET_CLIENTS]; int e; #endif int kiss_port = mc->kiss_port; /* default 8001 but easily changed. */ #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("kissnet_init ( %d )\n", kiss_port); #endif for (client=0; clientai_family, ai->ai_socktype, ai->ai_protocol); if (listen_sock == INVALID_SOCKET) { text_color_set(DW_COLOR_ERROR); dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError()); return (0); } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("Binding to port %s ... \n", kiss_port_str); #endif err = bind( listen_sock, ai->ai_addr, (int)ai->ai_addrlen); if (err == SOCKET_ERROR) { text_color_set(DW_COLOR_ERROR); dw_printf("Bind failed with error: %d\n", WSAGetLastError()); // TODO: provide corresponding text. dw_printf("Some other application is probably already using port %s.\n", kiss_port_str); dw_printf("Try using a different port number with KISSPORT in the configuration file.\n"); freeaddrinfo(ai); closesocket(listen_sock); WSACleanup(); return (0); } freeaddrinfo(ai); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("opened KISS socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, kiss_port_str ); #endif while (1) { int client; int c; client = -1; for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) { if (client_sock[c] <= 0) { client = c; } } /* * Listen for connection if we have not reached maximum. */ if (client >= 0) { if(listen(listen_sock, MAX_NET_CLIENTS) == SOCKET_ERROR) { text_color_set(DW_COLOR_ERROR); dw_printf("Listen failed with error: %d\n", WSAGetLastError()); return (0); } text_color_set(DW_COLOR_INFO); dw_printf("Ready to accept KISS TCP client application %d on port %s ...\n", client, kiss_port_str); client_sock[client] = accept(listen_sock, NULL, NULL); if (client_sock[client] == -1) { text_color_set(DW_COLOR_ERROR); dw_printf("Accept failed with error: %d\n", WSAGetLastError()); closesocket(listen_sock); WSACleanup(); return (0); } text_color_set(DW_COLOR_INFO); dw_printf("\nAttached to KISS TCP client application %d ...\n\n", client); // Reset the state and buffer. memset (&(kf[client]), 0, sizeof(kf[client])); } else { SLEEP_SEC(1); /* wait then check again if more clients allowed. */ } } #else /* End of Windows case, now Linux. */ struct sockaddr_in sockaddr; /* Internet socket address stuct */ socklen_t sockaddr_size = sizeof(struct sockaddr_in); int kiss_port = (int)(long)arg; int listen_sock; int bcopt = 1; listen_sock= socket(AF_INET,SOCK_STREAM,0); if (listen_sock == -1) { text_color_set(DW_COLOR_ERROR); perror ("connect_listen_thread: Socket creation failed"); return (NULL); } /* Version 1.3 - as suggested by G8BPQ. */ /* Without this, if you kill the application then try to run it */ /* again quickly the port number is unavailable for a while. */ /* Don't try doing the same thing On Windows; It has a different meaning. */ /* http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t */ setsockopt (listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&bcopt, 4); sockaddr.sin_addr.s_addr = INADDR_ANY; sockaddr.sin_port = htons(kiss_port); sockaddr.sin_family = AF_INET; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("Binding to port %d ... \n", kiss_port); #endif if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr)) == -1) { text_color_set(DW_COLOR_ERROR); dw_printf("Bind failed with error: %d\n", errno); dw_printf("%s\n", strerror(errno)); dw_printf("Some other application is probably already using port %d.\n", kiss_port); dw_printf("Try using a different port number with KISSPORT in the configuration file.\n"); return (NULL); } getsockname( listen_sock, (struct sockaddr *)(&sockaddr), &sockaddr_size); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("opened KISS TCP socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) ); #endif while (1) { int client; int c; client = -1; for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) { if (client_sock[c] <= 0) { client = c; } } if (client >= 0) { if(listen(listen_sock,MAX_NET_CLIENTS) == -1) { text_color_set(DW_COLOR_ERROR); perror ("connect_listen_thread: Listen failed"); return (NULL); } text_color_set(DW_COLOR_INFO); dw_printf("Ready to accept KISS TCP client application %d on port %d ...\n", client, kiss_port); client_sock[client] = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size); text_color_set(DW_COLOR_INFO); dw_printf("\nAttached to KISS TCP client application %d...\n\n", client); // Reset the state and buffer. memset (&(kf[client]), 0, sizeof(kf[client])); } else { SLEEP_SEC(1); /* wait then check again if more clients allowed. */ } } #endif } /*------------------------------------------------------------------- * * Name: kissnet_send_rec_packet * * Purpose: Send a received packet to the client app. * * Inputs: chan - Channel number where packet was received. * 0 = first, 1 = second if any. * // TODO: add kiss_cmd * * fbuf - Address of raw received frame buffer * or a text string. * * kiss_cmd - Usually KISS_CMD_DATA_FRAME but we can also have * KISS_CMD_SET_HARDWARE when responding to a query. * * flen - Number of bytes for AX.25 frame. * When called from kiss_rec_byte, flen will be -1 * indicating a text string rather than frame content. * This is used to fake out an application that thinks * it is using a traditional TNC and tries to put it * into KISS mode. * * tcpclient - It is possible to have more than client attached * at the same time with TCP KISS. * When a frame is received from the radio we want it * to go to all of the clients. In this case specify -1. * When responding to a command from the client, we want * to send only to that one client app. In this case * use the value 0 .. MAX_NET_CLIENTS-1. * * Description: Send message to client(s) if connected. * Disconnect from client, and notify user, if any error. * *--------------------------------------------------------------------*/ void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int tcpclient) { unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN]; int kiss_len; int err; int first, last, client; // Something received over the radio would be sent to all attached clients. // However, there are times we want to send a response only to a particular client. // In the case of a serial port or pseudo terminal, there is only one potential client. // so the response would be sent to only one place. A new parameter has been added for this. if (tcpclient >= 0 && tcpclient < MAX_NET_CLIENTS) { first = tcpclient; last = tcpclient; } else if (tcpclient == -1) { first = 0; last = MAX_NET_CLIENTS - 1; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("KISS TCP: Internal error, kissnet_send_rec_packet, tcpclient = %d.\n", tcpclient); return; } for (client = first; client <= last; client++) { if (client_sock[client] != -1) { if (flen < 0) { // A client app might think it is attached to a traditional TNC. // It might try sending commands over and over again trying to get the TNC into KISS mode. // We recognize this attempt and send it something to keep it happy. text_color_set(DW_COLOR_ERROR); dw_printf ("KISS TCP: Something unexpected from client application.\n"); dw_printf ("Is client app treating this like an old TNC with command mode?\n"); dw_printf ("This can be caused by the application sending commands to put a\n"); dw_printf ("traditional TNC into KISS mode. It is usually a harmless warning.\n"); dw_printf ("For best results, configure for a KISS-only TNC to avoid this.\n"); dw_printf ("In the case of APRSISCE/32, use \"Simply(KISS)\" rather than \"KISS.\"\n"); flen = strlen((char*)fbuf); if (kiss_debug) { kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen); } strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff)); kiss_len = strlen((char *)kiss_buff); } else { unsigned char stemp[AX25_MAX_PACKET_LEN + 1]; assert (flen < (int)(sizeof(stemp))); stemp[0] = (chan << 4) | kiss_cmd; memcpy (stemp+1, fbuf, flen); if (kiss_debug >= 2) { /* AX.25 frame with the CRC removed. */ text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); dw_printf ("Packet content before adding KISS framing and any escapes:\n"); hex_dump (fbuf, flen); } kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff); /* This has the escapes and the surrounding FENDs. */ if (kiss_debug) { kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len); } } #if __WIN32__ err = SOCK_SEND(client_sock[client], (char*)kiss_buff, kiss_len); if (err == SOCKET_ERROR) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError %d sending message to KISS client %d application. Closing connection.\n\n", WSAGetLastError(), client); closesocket (client_sock[client]); client_sock[client] = -1; WSACleanup(); } #else err = SOCK_SEND (client_sock[client], kiss_buff, kiss_len); if (err <= 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError sending message to KISS client %d application. Closing connection.\n\n", client); close (client_sock[client]); client_sock[client] = -1; } #endif } } } /* end kissnet_send_rec_packet */ /*------------------------------------------------------------------- * * Name: kissnet_listen_thread * * Purpose: Wait for KISS messages from an application. * * Inputs: arg - client number, 0 .. MAX_NET_CLIENTS-1 * * Outputs: client_sock[n] - File descriptor for communicating with client app. * * Description: Process messages from the client application. * Note that the client can go away and come back again and * re-establish communication without restarting this application. * *--------------------------------------------------------------------*/ /* Return one byte (value 0 - 255) */ static int kiss_get (int client) { unsigned char ch; int n; while (1) { while (client_sock[client] <= 0) { SLEEP_SEC(1); /* Not connected. Try again later. */ } /* Just get one byte at a time. */ n = SOCK_RECV (client_sock[client], (char *)(&ch), 1); if (n == 1) { #if DEBUG9 dw_printf (log_fp, "%02x %c %c", ch, isprint(ch) ? ch : '.' , (isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.'); if (ch == FEND) fprintf (log_fp, " FEND"); if (ch == FESC) fprintf (log_fp, " FESC"); if (ch == TFEND) fprintf (log_fp, " TFEND"); if (ch == TFESC) fprintf (log_fp, " TFESC"); if (ch == '\r') fprintf (log_fp, " CR"); if (ch == '\n') fprintf (log_fp, " LF"); fprintf (log_fp, "\n"); if (ch == FEND) fflush (log_fp); #endif return(ch); } text_color_set(DW_COLOR_ERROR); dw_printf ("\nKISS client application %d has gone away.\n\n", client); #if __WIN32__ closesocket (client_sock[client]); #else close (client_sock[client]); #endif client_sock[client] = -1; } } static THREAD_F kissnet_listen_thread (void *arg) { unsigned char ch; int client = (int)(long)arg; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("kissnet_listen_thread ( client = %d, socket fd = %d )\n", client, client_sock[client]); #endif assert (client >= 0 && client < MAX_NET_CLIENTS); // So why is kissnet_send_rec_packet mentioned here for incoming from the client app? // The logic exists for the serial port case where the client might think it is // attached to a traditional TNC. It might try sending commands over and over again // trying to get the TNC into KISS mode. To keep it happy, we recognize this attempt // and send it something to keep it happy. // In the case of a serial port or pseudo terminal, there is only one potential client // so the response would be sent to only one place. // Starting in version 1.5, this now can have multiple attached clients. We wouldn't // want to send the response to all of them. Actually, we should be providing only // "Simply KISS" as some call it. while (1) { ch = kiss_get(client); kiss_rec_byte (&(kf[client]), ch, kiss_debug, client, kissnet_send_rec_packet); } #if __WIN32__ return(0); #else return (THREAD_F) 0; /* Unreachable but avoids compiler warning. */ #endif } /* end kissnet_listen_thread */ /* end kissnet.c */ direwolf-1.5+dfsg/kissnet.h000066400000000000000000000004641347750676600157730ustar00rootroot00000000000000 /* * Name: kissnet.h */ #include "ax25_pad.h" /* for packet_t */ #include "config.h" void kissnet_init (struct misc_config_s *misc_config); void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client); void kiss_net_set_debug (int n); /* end kissnet.h */ direwolf-1.5+dfsg/kissserial.c000066400000000000000000000323561347750676600164640ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 2014, 2016, 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: kissserial.c * * Purpose: Act as a virtual KISS TNC for use by other packet radio applications. * This file provides the service by good old fashioned serial port. * Other files implement a pseudo terminal or TCP KISS interface. * * Description: This implements the KISS TNC protocol as described in: * http://www.ka9q.net/papers/kiss.html * * Briefly, a frame is composed of * * * FEND (0xC0) * * Contents - with special escape sequences so a 0xc0 * byte in the data is not taken as end of frame. * as part of the data. * * FEND * * The first byte of the frame contains: * * * port number in upper nybble. * * command in lower nybble. * * Commands from application recognized: * * _0 Data Frame AX.25 frame in raw format. * * _1 TXDELAY See explanation in xmit.c. * * _2 Persistence " " * * _3 SlotTime " " * * _4 TXtail " " * Spec says it is obsolete but Xastir * sends it and we respect it. * * _5 FullDuplex Ignored. * * _6 SetHardware TNC specific. * * FF Return Exit KISS mode. Ignored. * * * Messages sent to client application: * * _0 Data Frame Received AX.25 frame in raw format. * * * Platform differences: * * This file implements KISS over a serial port. * It should behave pretty much the same for both Windows and Linux. * * When running a client application on Windows, two applications * can be connected together using a a "Null-modem emulator" * such as com0com from http://sourceforge.net/projects/com0com/ * * (When running a client application, on the same host, with Linux, * a pseudo terminal can be used for old applications. More modern * applications will generally have AGW and/or KISS over TCP.) * * * version 1.5: Split out from kiss.c, simplified, consistent for Windows and Linux. * Add polling option for use with Bluetooth. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "serial_port.h" #include "kissserial.h" #include "kiss_frame.h" #include "xmit.h" /* * Save Configuration for later use. */ static struct misc_config_s *g_misc_config_p; /* * Accumulated KISS frame and state of decoder. */ static kiss_frame_t kf; /* * The serial port device handle. * MYFD... are defined in kissserial.h */ static MYFDTYPE serialport_fd = MYFDERROR; // TODO: define in one place, use everywhere. #if __WIN32__ #define THREAD_F unsigned __stdcall #else #define THREAD_F void * #endif static THREAD_F kissserial_listen_thread (void *arg); static int kissserial_debug = 0; /* Print information flowing from and to client. */ void kissserial_set_debug (int n) { kissserial_debug = n; } /* In server.c. Should probably move to some misc. function file. */ void hex_dump (unsigned char *p, int len); /*------------------------------------------------------------------- * * Name: kissserial_init * * Purpose: Set up a serial port acting as a virtual KISS TNC. * * Inputs: mc-> * kiss_serial_port - Name of device for real or virtual serial port. * kiss_serial_speed - Speed, bps, or 0 meaning leave it alone. * kiss_serial_poll - When non-zero, poll each n seconds to see if * device has appeared. * * Outputs: * * Description: (1) Open file descriptor for the device. * (2) Start a new thread to listen for commands from client app * so the main application doesn't block while we wait. * *--------------------------------------------------------------------*/ void kissserial_init (struct misc_config_s *mc) { #if __WIN32__ HANDLE kissserial_listen_th; #else pthread_t kissserial_listen_tid; int e; #endif g_misc_config_p = mc; memset (&kf, 0, sizeof(kf)); if (strlen(g_misc_config_p->kiss_serial_port) > 0) { if (g_misc_config_p->kiss_serial_poll == 0) { // Normal case, try to open the serial port at start up time. serialport_fd = serial_port_open (g_misc_config_p->kiss_serial_port, g_misc_config_p->kiss_serial_speed); if (serialport_fd != MYFDERROR) { text_color_set(DW_COLOR_INFO); dw_printf ("Opened %s for serial port KISS.\n", g_misc_config_p->kiss_serial_port); } else { // An error message was already displayed. } } else { // Polling case. Defer until read and device not opened. text_color_set(DW_COLOR_INFO); dw_printf ("Will be checking periodically for %s\n", g_misc_config_p->kiss_serial_port); } if (g_misc_config_p->kiss_serial_poll != 0 || serialport_fd != MYFDERROR) { #if __WIN32__ kissserial_listen_th = (HANDLE)_beginthreadex (NULL, 0, kissserial_listen_thread, NULL, 0, NULL); if (kissserial_listen_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create kiss serial thread\n"); return; } #else e = pthread_create (&kissserial_listen_tid, NULL, kissserial_listen_thread, NULL); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create kiss serial thread."); } #endif } } #if DEBUG text_color_set (DW_COLOR_DEBUG); dw_printf ("end of kiss_init: serialport_fd = %d, polling = %d\n", serialport_fd, g_misc_config_p->kiss_serial_poll); #endif } /*------------------------------------------------------------------- * * Name: kissserial_send_rec_packet * * Purpose: Send a received packet or text string to the client app. * * Inputs: chan - Channel number where packet was received. * 0 = first, 1 = second if any. * * kiss_cmd - Usually KISS_CMD_DATA_FRAME but we can also have * KISS_CMD_SET_HARDWARE when responding to a query. * * pp - Identifier for packet object. * * fbuf - Address of raw received frame buffer * or a text string. * * flen - Length of raw received frame not including the FCS * or -1 for a text string. * * client - Not used for serial port version. * Here so that 3 related functions all have * the same parameter list. * * Description: Send message to client. * We really don't care if anyone is listening or not. * I don't even know if we can find out. * *--------------------------------------------------------------------*/ void kissserial_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client) { unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2]; int kiss_len; int err; /* * Quietly discard if we don't have open connection. */ if (serialport_fd == MYFDERROR) { return; } if (flen < 0) { flen = strlen((char*)fbuf); if (kissserial_debug) { kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen); } strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff)); kiss_len = strlen((char *)kiss_buff); } else { unsigned char stemp[AX25_MAX_PACKET_LEN + 1]; if (flen > (int)(sizeof(stemp)) - 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nSerial Port KISS buffer too small. Truncated.\n\n"); flen = (int)(sizeof(stemp)) - 1; } stemp[0] = (chan << 4) | kiss_cmd; memcpy (stemp+1, fbuf, flen); if (kissserial_debug >= 2) { /* AX.25 frame with the CRC removed. */ text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); dw_printf ("Packet content before adding KISS framing and any escapes:\n"); hex_dump (fbuf, flen); } kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff); /* This has KISS framing and escapes for sending to client app. */ if (kissserial_debug) { kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len); } } /* * This write can block on Windows if using the virtual null modem * and nothing is connected to the other end. * The solution is found in the com0com ReadMe file: * * Q. My application hangs during its startup when it sends anything to one paired * COM port. The only way to unhang it is to start HyperTerminal, which is connected * to the other paired COM port. I didn't have this problem with physical serial * ports. * A. Your application can hang because receive buffer overrun is disabled by * default. You can fix the problem by enabling receive buffer overrun for the * receiving port. Also, to prevent some flow control issues you need to enable * baud rate emulation for the sending port. So, if your application use port CNCA0 * and other paired port is CNCB0, then: * * 1. Launch the Setup Command Prompt shortcut. * 2. Enter the change commands, for example: * * command> change CNCB0 EmuOverrun=yes * command> change CNCA0 EmuBR=yes */ err = serial_port_write (serialport_fd, (char*)kiss_buff, kiss_len); if (err != kiss_len) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError sending KISS message to client application thru serial port.\n\n"); serial_port_close (serialport_fd); serialport_fd = MYFDERROR; } } /* kissserial_send_rec_packet */ /*------------------------------------------------------------------- * * Name: kissserial_get * * Purpose: Read one byte from the KISS client app. * * Global In: serialport_fd * * Returns: one byte (value 0 - 255) or terminate thread on error. * * Description: There is room for improvment here. Reading one byte * at a time is inefficient. We could read a large block * into a local buffer and return a byte from that most of the time. * Is it worth the effort? I don't know. With GHz processors and * the low data rate here it might not make a noticable difference. * *--------------------------------------------------------------------*/ static int kissserial_get (void) { int ch; // normally 0-255 but -1 for error. if (g_misc_config_p->kiss_serial_poll == 0) { /* * Normal case, was opened at start up time. */ ch = serial_port_get1 (serialport_fd); if (ch < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nSerial Port KISS read error. Closing connection.\n\n"); serial_port_close (serialport_fd); serialport_fd = MYFDERROR; #if __WIN32__ ExitThread (0); #else pthread_exit (NULL); #endif } #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("kissserial_get(%d) returns 0x%02x\n", fd, ch); #endif return (ch); } /* * Polling case. Wait until device is present and open. */ while (1) { if (serialport_fd != MYFDERROR) { // Open, try to read. ch = serial_port_get1 (serialport_fd); if (ch >= 0) { return (ch); } text_color_set(DW_COLOR_ERROR); dw_printf ("\nSerial Port KISS read error. Closing connection.\n\n"); serial_port_close (serialport_fd); serialport_fd = MYFDERROR; } else { // Not open. Wait for it to appear and try opening. struct stat buf; SLEEP_SEC (g_misc_config_p->kiss_serial_poll); if (stat(g_misc_config_p->kiss_serial_port, &buf) == 0) { // It's there now. Try to open. serialport_fd = serial_port_open (g_misc_config_p->kiss_serial_port, g_misc_config_p->kiss_serial_speed); if (serialport_fd != MYFDERROR) { text_color_set(DW_COLOR_INFO); dw_printf ("\nOpened %s for serial port KISS.\n\n", g_misc_config_p->kiss_serial_port); memset (&kf, 0, sizeof(kf)); // Start with clean state. } else { // An error message was already displayed. } } } } } /* end kissserial_get */ /*------------------------------------------------------------------- * * Name: kissserial_listen_thread * * Purpose: Read messages from serial port KISS client application. * * Global In: serialport_fd * * Description: Reads bytes from the serial port KISS client app and * sends them to kiss_rec_byte for processing. * kiss_rec_byte is a common function used by all 3 KISS * interfaces: serial port, pseudo terminal, and TCP. * *--------------------------------------------------------------------*/ static THREAD_F kissserial_listen_thread (void *arg) { unsigned char ch; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("kissserial_listen_thread ( %d )\n", fd); #endif while (1) { ch = kissserial_get(); kiss_rec_byte (&kf, ch, kissserial_debug, -1, kissserial_send_rec_packet); } #if __WIN32__ return(0); #else return (THREAD_F) 0; /* Unreachable but avoids compiler warning. */ #endif } /* end kissserial.c */ direwolf-1.5+dfsg/kissserial.h000066400000000000000000000005021347750676600164550ustar00rootroot00000000000000 /* * Name: kissserial.h */ #include "ax25_pad.h" /* for packet_t */ #include "config.h" void kissserial_init (struct misc_config_s *misc_config); void kissserial_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client); void kissserial_set_debug (int n); /* end kissserial.h */ direwolf-1.5+dfsg/kissutil.c000066400000000000000000000630641347750676600161620ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: kissutil.c * * Purpose: Utility for talking to a KISS TNC. * * Description: Convert between KISS format and usual text representation. * This might also serve as the starting point for an application * that uses a KISS TNC. * The TNC can be attached by TCP or a serial port. * * Usage: kissutil [ options ] * * Default is to connect to localhost:8001. * See the "usage" functions at the bottom for details. * *---------------------------------------------------------------*/ #include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h #if __WIN32__ #include #include // _WIN32_WINNT must be set to 0x0501 before including this #else #include #include #include #include #endif #include #include #include #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "serial_port.h" #include "kiss_frame.h" #include "sock.h" #include "dtime_now.h" #include "audio.h" // for DEFAULT_TXDELAY, etc. #include "dtime_now.h" // TODO: define in one place, use everywhere. #if __WIN32__ #define THREAD_F unsigned __stdcall #else #define THREAD_F void * #endif #if __WIN32__ #define DIR_CHAR "\\" #else #define DIR_CHAR "/" #endif static THREAD_F tnc_listen_net (void *arg); static THREAD_F tnc_listen_serial (void *arg); static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen); static void hex_dump (unsigned char *p, int len); static void usage(void); static void usage2(void); /* Obtained from the command line. */ static char hostname[50] = "localhost"; /* -h option. */ /* DNS host name or IPv4 address. */ /* Some of the code is there for IPv6 but */ /* it needs more work. */ /* Defaults to "localhost" if not specified. */ static char port[30] = "8001"; /* -p option. */ /* If it begins with a digit, it is considered */ /* a TCP port number at the hostname. */ /* Otherwise, we treat it as a serial port name. */ static int using_tcp = 1; /* Are we using TCP or serial port for TNC? */ /* Use corresponding one of the next two. */ /* This is derived from the first character of port. */ static int server_sock = -1; /* File descriptor for socket interface. */ /* Set to -1 if not used. */ /* (Don't use SOCKET type because it is unsigned.) */ static MYFDTYPE serial_fd = (MYFDTYPE)(-1); /* Serial port handle. */ static int serial_speed = 9600; /* -s option. */ /* Serial port speed, bps. */ static int verbose = 0; /* -v option. */ /* Display the KISS protocol in hexadecimal for troubleshooting. */ static char transmit_from[120] = ""; /* -f option */ /* When specified, files are read from this directory */ /* rather than using stdin. Each file is one or more */ /* lines in the standard monitoring format. */ static char receive_output[120] = ""; /* -o option */ /* When specified, each received frame is stored as a file */ /* with a unique name here. */ /* Directory must already exist; we won't create it. */ static char timestamp_format[60] = ""; /* -T option */ /* Precede received frames with timestamp. */ /* Command line option uses "strftime" format string. */ #if __WIN32__ #define THREAD_F unsigned __stdcall #else #define THREAD_F void * #endif #if __WIN32__ static HANDLE tnc_th; #else static pthread_t tnc_tid; #endif static void process_input (char *stuff); /* Trim any CR, LF from the end of line. */ static void trim (char *stuff) { char *p; p = stuff + strlen(stuff) - 1; while (strlen(stuff) > 0 && (*p == '\r' || *p == '\n')) { *p = '\0'; p--; } } /* end trim */ /*------------------------------------------------------------------ * * Name: main * * Purpose: Attach to KISS TNC and exchange information. * * Usage: See "usage" functions at end. * *---------------------------------------------------------------*/ int main (int argc, char *argv[]) { text_color_init (0); // Turn off text color. // It could interfere with trying to pipe stdout to some other application. #if __WIN32__ #else int e; setlinebuf (stdout); // TODO: What is the Windows equivalent? #endif /* * Extract command line args. */ while (1) { int option_index = 0; int c; static struct option long_options[] = { //{"future1", 1, 0, 0}, //{"future2", 0, 0, 0}, //{"future3", 1, 0, 'c'}, {0, 0, 0, 0} }; /* ':' following option character means arg is required. */ c = getopt_long(argc, argv, "h:p:s:vf:o:T:", long_options, &option_index); if (c == -1) break; switch (c) { case 'h': /* -h for hostname. */ strlcpy (hostname, optarg, sizeof(hostname)); break; case 'p': /* -p for port, either TCP or serial device. */ strlcpy (port, optarg, sizeof(port)); break; case 's': /* -s for serial port speed. */ serial_speed = atoi(optarg); break; case 'v': /* -v for verbose. */ verbose++; break; case 'f': /* -f for transmit files directory. */ strlcpy (transmit_from, optarg, sizeof(transmit_from)); break; case 'o': /* -o for receive output directory. */ strlcpy (receive_output, optarg, sizeof(receive_output)); break; case 'T': /* -T for receive timestamp. */ strlcpy (timestamp_format, optarg, sizeof(timestamp_format)); break; case '?': /* Unknown option message was already printed. */ usage (); break; default: /* Should not be here. */ text_color_set(DW_COLOR_DEBUG); dw_printf("?? getopt returned character code 0%o ??\n", c); usage (); } } /* end while(1) for options */ if (optind < argc) { text_color_set(DW_COLOR_ERROR); dw_printf ("Warning: Unused command line arguments are ignored.\n"); } /* * If receive queue directory was specified, make sure that it exists. */ if (strlen(receive_output) > 0) { struct stat s; if (stat(receive_output, &s) == 0) { if ( ! S_ISDIR(s.st_mode)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Receive queue location, %s, is not a directory.\n", receive_output); exit (EXIT_FAILURE); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Receive queue location, %s, does not exist.\n", receive_output); exit (EXIT_FAILURE); } } /* If port begins with digit, consider it to be TCP. */ /* Otherwise, treat as serial port name. */ using_tcp = isdigit(port[0]); #if __WIN32__ if (using_tcp) { tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_net, (void *)99, 0, NULL); } else { tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_serial, (void *)99, 0, NULL); } if (tnc_th == NULL) { printf ("Internal error: Could not create TNC listen thread.\n"); exit (EXIT_FAILURE); } #else if (using_tcp) { e = pthread_create (&tnc_tid, NULL, tnc_listen_net, (void *)(long)99); } else { e = pthread_create (&tnc_tid, NULL, tnc_listen_serial, (void *)(long)99); } if (e != 0) { perror("Internal error: Could not create TNC listen thread."); exit (EXIT_FAILURE); } #endif /* * Process keyboard or other input source. */ char stuff[1000]; if (strlen(transmit_from) > 0) { /* * Process and delete all files in specified directory. * When done, sleep for a second and try again. * This doesn't take them in any particular order. * A future enhancement might sort by name or timestamp. */ while (1) { DIR *dp; struct dirent *ep; //text_color_set(DW_COLOR_DEBUG); //dw_printf("Get directory listing...\n"); dp = opendir (transmit_from); if (dp != NULL) { while ((ep = readdir(dp)) != NULL) { char path [300]; FILE *fp; if (ep->d_name[0] == '.') continue; text_color_set(DW_COLOR_DEBUG); dw_printf ("Processing %s for transmit...\n", ep->d_name); strlcpy (path, transmit_from, sizeof(path)); strlcat (path, DIR_CHAR, sizeof(path)); strlcat (path, ep->d_name, sizeof(path)); fp = fopen (path, "r"); if (fp != NULL) { while (fgets(stuff, sizeof(stuff), fp) != NULL) { trim (stuff); text_color_set(DW_COLOR_DEBUG); dw_printf ("%s\n", stuff); // TODO: Don't delete file if errors encountered? process_input (stuff); } fclose (fp); unlink (path); } else { text_color_set(DW_COLOR_ERROR); dw_printf("Can't open for read: %s\n", path); } } closedir (dp); } else { text_color_set(DW_COLOR_ERROR); dw_printf("Can't access transmit queue directory %s. Quitting.\n", transmit_from); exit (EXIT_FAILURE); } SLEEP_SEC (1); } } else { /* * Using stdin. */ while (fgets(stuff, sizeof(stuff), stdin) != NULL) { process_input (stuff); } } return (EXIT_SUCCESS); } /* end main */ /*------------------------------------------------------------------- * * Name: process_input * * Purpose: Process frames/commands from user, either interactively or from files. * * Inputs: stuff - A frame is in usual format like SOURCE>DEST,DIGI:whatever. * Commands begin with lower case letter. * Note that it can be modified by this function. * * Later Enhancement: Return success/fail status. The transmit queue processing might want * to preserve files that were not processed as expected. * *--------------------------------------------------------------------*/ static int parse_number (char *str, int de_fault) { int n; while (isspace(*str)) { str++; } if (strlen(str) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Missing number for KISS command. Using default %d.\n", de_fault); return (de_fault); } n = atoi(str); if (n < 0 || n > 255) { // must fit in a byte. text_color_set(DW_COLOR_ERROR); dw_printf ("Number for KISS command is out of range 0-255. Using default %d.\n", de_fault); return (de_fault); } return (n); } static void process_input (char *stuff) { char *p; int chan = 0; /* * Remove any end of line character(s). */ trim (stuff); /* * Optional prefix, like "[9]" or "[99]" to specify channel. */ p = stuff; while (isspace(*p)) p++; if (*p == '[') { p++; if (p[1] == ']') { chan = atoi(p); p += 2; } else if (p[2] == ']') { chan = atoi(p); p += 3; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR! One or two digit channel number and ] was expected after [ at beginning of line.\n"); usage2(); return; } if (chan < 0 || chan > 15) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR! KISS channel number must be in range of 0 thru 15.\n"); usage2(); return; } while (isspace(*p)) p++; } /* * If it starts with upper case letter or digit, assume it is an AX.25 frame in monitor format. * Lower case is a command (e.g. Persistence or set Hardware). * Anything else, print explanation of what is expected. */ if (isupper(*p) || isdigit(*p)) { // Parse the "TNC2 monitor format" and convert to AX.25 frame. unsigned char frame_data[AX25_MAX_PACKET_LEN]; packet_t pp = ax25_from_text (p, 1); if (pp != NULL) { int frame_len = ax25_pack (pp, frame_data); send_to_kiss_tnc (chan, KISS_CMD_DATA_FRAME, (char*)frame_data, frame_len); ax25_delete (pp); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR! Could not convert to AX.25 frame: %s\n", p); } } else if (islower(*p)) { char value; switch (*p) { case 'd': // txDelay, 10ms units value = parse_number(p+1, DEFAULT_TXDELAY); send_to_kiss_tnc (chan, KISS_CMD_TXDELAY, &value, 1); break; case 'p': // Persistence value = parse_number(p+1, DEFAULT_PERSIST); send_to_kiss_tnc (chan, KISS_CMD_PERSISTENCE, &value, 1); break; case 's': // Slot time, 10ms units value = parse_number(p+1, DEFAULT_SLOTTIME); send_to_kiss_tnc (chan, KISS_CMD_SLOTTIME, &value, 1); break; case 't': // txTail, 10ms units value = parse_number(p+1, DEFAULT_TXTAIL); send_to_kiss_tnc (chan, KISS_CMD_TXTAIL, &value, 1); break; case 'f': // Full duplex value = parse_number(p+1, 0); send_to_kiss_tnc (chan, KISS_CMD_FULLDUPLEX, &value, 1); break; case 'h': // set Hardware p++; while (*p != '\0' && isspace(*p)) { p++; } send_to_kiss_tnc (chan, KISS_CMD_SET_HARDWARE, p, strlen(p)); break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid command. Must be one of d p s t f h.\n"); usage2 (); break; } } else { usage2 (); } } /* end process_input */ /*------------------------------------------------------------------- * * Name: send_to_kiss_tnc * * Purpose: Encapsulate the data/command, into a KISS frame, and send to the TNC. * * Inputs: chan - channel number. * * cmd - KISS_CMD_DATA_FRAME, KISS_CMD_SET_HARDWARE, etc. * * data - Information for KISS frame. * * dlen - Number of bytes in data. * * Description: Encapsulate as KISS frame and send to TNC. * *--------------------------------------------------------------------*/ static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen) { unsigned char temp[1000]; unsigned char kissed[2000]; int klen; if (chan < 0 || chan > 15) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Invalid channel %d - must be in range 0 to 15.\n", chan); chan = 0; } if (cmd < 0 || cmd > 15) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Invalid command %d - must be in range 0 to 15.\n", cmd); cmd = 0; } if (dlen < 0 || dlen > (int)(sizeof(temp)-1)) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Invalid data length %d - must be in range 0 to %d.\n", dlen, (int)(sizeof(temp)-1)); dlen = sizeof(temp)-1; } temp[0] = (chan << 4) | cmd; memcpy (temp+1, data, dlen); klen = kiss_encapsulate(temp, dlen+1, kissed); if (verbose) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Sending to KISS TNC:\n"); hex_dump (kissed, klen); } if (using_tcp) { int rc = SOCK_SEND(server_sock, (char*)kissed, klen); if (rc != klen) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR writing KISS frame to socket.\n"); } } else { int rc = serial_port_write (serial_fd, (char*)kissed, klen); if (rc != klen) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR writing KISS frame to serial port.\n"); } } } /* end send_to_kiss_tnc */ /*------------------------------------------------------------------- * * Name: tnc_listen_net * * Purpose: Connect to KISS TNC via TCP port. * Print everything it sends to us. * * Inputs: arg - Currently not used. * * Global In: host * port * * Global Out: server_sock - Needed to send to the TNC. * *--------------------------------------------------------------------*/ static THREAD_F tnc_listen_net (void *arg) { int err; char ipaddr_str[SOCK_IPADDR_LEN]; // Text form of IP address. char data[4096]; int allow_ipv6 = 0; // Maybe someday. int debug = 0; int client = 0; // Not used in this situation. kiss_frame_t kstate; memset (&kstate, 0, sizeof(kstate)); err = sock_init (); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Network interface failure. Can't go on.\n"); exit (EXIT_FAILURE); } /* * Connect to network KISS TNC. */ // For the IGate we would loop around and try to reconnect if the TNC // goes away. We should probably do the same here. server_sock = sock_connect (hostname, port, "TCP KISS TNC", allow_ipv6, debug, ipaddr_str); if (server_sock == -1) { text_color_set(DW_COLOR_ERROR); // Should have been a message already. What else is there to say? exit (EXIT_FAILURE); } /* * Print what we get from TNC. */ int len; while ((len = SOCK_RECV (server_sock, (char*)(data), sizeof(data))) > 0) { int j; for (j = 0; j < len; j++) { // Feed in one byte at a time. // kiss_process_msg is called when a complete frame has been accumulated. // When verbose is specified, we get debug output like this: // // <<< Data frame from KISS client application, port 0, total length = 46 // 000: c0 00 82 a0 88 ae 62 6a e0 ae 84 64 9e a6 b4 ff ......bj...d.... // ... // It says "from KISS client application" because it was written // on the assumption it was being used in only one direction. // Not worried enough about it to do anything at this time. kiss_rec_byte (&kstate, data[j], verbose, client, NULL); } } text_color_set(DW_COLOR_ERROR); dw_printf ("Read error from TCP KISS TNC. Terminating.\n"); exit (EXIT_FAILURE); } /* end tnc_listen_net */ /*------------------------------------------------------------------- * * Name: tnc_listen_serial * * Purpose: Connect to KISS TNC via serial port. * Print everything it sends to us. * * Inputs: arg - Currently not used. * * Global In: port * serial_speed * * Global Out: serial_fd - Need for sending to the TNC. * *--------------------------------------------------------------------*/ static THREAD_F tnc_listen_serial (void *arg) { int client = 0; kiss_frame_t kstate; memset (&kstate, 0, sizeof(kstate)); serial_fd = serial_port_open (port, serial_speed); if (serial_fd == MYFDERROR) { text_color_set(DW_COLOR_ERROR); dw_printf("Unable to connect to KISS TNC serial port %s.\n", port); #if __WIN32__ #else // More detail such as "permission denied" or "no such device" dw_printf("%s\n", strerror(errno)); #endif exit (EXIT_FAILURE); } /* * Read and print. */ while (1) { int ch; ch = serial_port_get1(serial_fd); if (ch < 0) { dw_printf("Read error from serial port KISS TNC.\n"); exit (EXIT_FAILURE); } // Feed in one byte at a time. // kiss_process_msg is called when a complete frame has been accumulated. kiss_rec_byte (&kstate, ch, verbose, client, NULL); } } /* end tnc_listen_serial */ /*------------------------------------------------------------------- * * Name: kiss_process_msg * * Purpose: Process a frame from the KISS TNC. * This is called when a complete frame has been accumulated. * In this case, we simply print it. * * Inputs: kiss_msg - Kiss frame with FEND and escapes removed. * The first byte contains channel and command. * * kiss_len - Number of bytes including the command. * * debug - Debug option is selected. * * client - Not used in this case. * * sendfun - Not used in this case. * *-----------------------------------------------------------------*/ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int)) { int chan; int cmd; packet_t pp; alevel_t alevel; chan = (kiss_msg[0] >> 4) & 0xf; cmd = kiss_msg[0] & 0xf; switch (cmd) { case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */ memset (&alevel, 0, sizeof(alevel)); pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); printf ("ERROR - Invalid KISS data frame from TNC.\n"); } else { char prefix[100]; // Channel and optional timestamp. // Like [0] or [2 12:34:56] char addrs[AX25_MAX_ADDRS*AX25_MAX_ADDR_LEN]; // Like source>dest,digi,...,digi: unsigned char *pinfo; int info_len; if (strlen(timestamp_format) > 0) { char ts[100]; timestamp_user_format (ts, sizeof(ts), timestamp_format); snprintf (prefix, sizeof(prefix), "[%d %s]", chan, ts); } else { snprintf (prefix, sizeof(prefix), "[%d]", chan); } ax25_format_addrs (pp, addrs); info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_REC); dw_printf ("%s %s", prefix, addrs); // [channel] Addresses followed by : // Safe print will replace any unprintable characters with // hexadecimal representation. ax25_safe_print ((char *)pinfo, info_len, 0); dw_printf ("\n"); #if __WIN32__ fflush (stdout); #endif /* * Add to receive queue directory if specified. * File name will be based on current local time. * If you want UTC, just set an environment variable like this: * * TZ=UTC kissutil ... */ if (strlen(receive_output) > 0) { char fname [30]; char path [300]; FILE *fp; timestamp_filename (fname, (int)sizeof(fname)); strlcpy (path, receive_output, sizeof(path)); strlcat (path, DIR_CHAR, sizeof(path)); strlcat (path, fname, sizeof(path)); text_color_set(DW_COLOR_DEBUG); dw_printf ("Save received frame to %s\n", path); fp = fopen (path, "w"); if (fp != NULL) { fprintf (fp, "%s %s%s\n", prefix, addrs, pinfo); fclose (fp); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Unable to open for write: %s\n", path); } } ax25_delete (pp); } break; case KISS_CMD_SET_HARDWARE: /* 6 = TNC specific */ kiss_msg[kiss_len] = '\0'; text_color_set(DW_COLOR_REC); // Display as "h ..." for in/out symmetry. // Use safe print here? dw_printf ("[%d] h %s\n", chan, (char*)(kiss_msg+1)); break; /* * The rest should only go TO the TNC and not come FROM it. */ case KISS_CMD_TXDELAY: /* 1 = TXDELAY */ case KISS_CMD_PERSISTENCE: /* 2 = Persistence */ case KISS_CMD_SLOTTIME: /* 3 = SlotTime */ case KISS_CMD_TXTAIL: /* 4 = TXtail */ case KISS_CMD_FULLDUPLEX: /* 5 = FullDuplex */ case KISS_CMD_END_KISS: /* 15 = End KISS mode, port should be 15. */ default: text_color_set(DW_COLOR_ERROR); printf ("Unexpected KISS command %d, channel %d\n", cmd, chan); break; } } /* end kiss_process_msg */ // TODO: We have multiple copies of this. Move to some misc file. void hex_dump (unsigned char *p, int len) { int n, i, offset; offset = 0; while (len > 0) { n = len < 16 ? len : 16; printf (" %03x: ", offset); for (i=0; i. // /*------------------------------------------------------------------ * * Module: latlong.c * * Purpose: Various functions for dealing with latitude and longitude. * * Description: Originally, these were scattered around in many places. * Over time they might all be gathered into one place * for consistency, reuse, and easier maintenance. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #include "latlong.h" #include "textcolor.h" /*------------------------------------------------------------------ * * Name: latitude_to_str * * Purpose: Convert numeric latitude to string for transmission. * * Inputs: dlat - Floating point degrees. * ambiguity - If 1, 2, 3, or 4, blank out that many trailing digits. * * Outputs: slat - String in format ddmm.mm[NS] * Should always be exactly 8 characters + NUL. * * Returns: None * * Idea for future: * Non zero ambiguity removes least significant digits without rounding. * Maybe we could use -1 and -2 to add extra digits using !DAO! as * documented in http://www.aprs.org/datum.txt * * For example, -1 adds one more human readable digit. * lat minutes 12.345 would produce "12.34" and !W5 ! * * -2 would encode almost 2 digits in base 91. * lat minutes 10.0027 would produce "10.00" and !w: ! * *----------------------------------------------------------------*/ void latitude_to_str (double dlat, int ambiguity, char *slat) { char hemi; /* Hemisphere: N or S */ int ideg; /* whole number of degrees. */ double dmin; /* Minutes after removing degrees. */ char smin[8]; /* Minutes in format mm.mm */ if (dlat < -90.) { text_color_set(DW_COLOR_ERROR); dw_printf ("Latitude is less than -90. Changing to -90.n"); dlat = -90.; } if (dlat > 90.) { text_color_set(DW_COLOR_ERROR); dw_printf ("Latitude is greater than 90. Changing to 90.n"); dlat = 90.; } if (dlat < 0) { dlat = (- dlat); hemi = 'S'; } else { hemi = 'N'; } ideg = (int)dlat; dmin = (dlat - ideg) * 60.; snprintf (smin, sizeof(smin), "%05.2f", dmin); /* Due to roundoff, 59.9999 could come out as "60.00" */ if (smin[0] == '6') { smin[0] = '0'; ideg++; } sprintf (slat, "%02d%s%c", ideg, smin, hemi); if (ambiguity >= 1) { slat[6] = ' '; if (ambiguity >= 2) { slat[5] = ' '; if (ambiguity >= 3) { slat[3] = ' '; if (ambiguity >= 4) { slat[2] = ' '; } } } } } /* end latitude_to_str */ /*------------------------------------------------------------------ * * Name: longitude_to_str * * Purpose: Convert numeric longitude to string for transmission. * * Inputs: dlong - Floating point degrees. * ambiguity - If 1, 2, 3, or 4, blank out that many trailing digits. * * Outputs: slat - String in format dddmm.mm[NS] * Should always be exactly 9 characters + NUL. * * Returns: None * *----------------------------------------------------------------*/ void longitude_to_str (double dlong, int ambiguity, char *slong) { char hemi; /* Hemisphere: N or S */ int ideg; /* whole number of degrees. */ double dmin; /* Minutes after removing degrees. */ char smin[8]; /* Minutes in format mm.mm */ if (dlong < -180.) { text_color_set(DW_COLOR_ERROR); dw_printf ("Longitude is less than -180. Changing to -180.n"); dlong = -180.; } if (dlong > 180.) { text_color_set(DW_COLOR_ERROR); dw_printf ("Longitude is greater than 180. Changing to 180.n"); dlong = 180.; } if (dlong < 0) { dlong = (- dlong); hemi = 'W'; } else { hemi = 'E'; } ideg = (int)dlong; dmin = (dlong - ideg) * 60.; snprintf (smin, sizeof(smin), "%05.2f", dmin); /* Due to roundoff, 59.9999 could come out as "60.00" */ if (smin[0] == '6') { smin[0] = '0'; ideg++; } sprintf (slong, "%03d%s%c", ideg, smin, hemi); /* * The spec says position ambiguity in latitude also * applies to longitude automatically. * Blanking longitude digits is not necessary but I do it * because it makes things clearer. */ if (ambiguity >= 1) { slong[7] = ' '; if (ambiguity >= 2) { slong[6] = ' '; if (ambiguity >= 3) { slong[4] = ' '; if (ambiguity >= 4) { slong[3] = ' '; } } } } } /* end longitude_to_str */ /*------------------------------------------------------------------ * * Name: latitude_to_comp_str * * Purpose: Convert numeric latitude to compressed string for transmission. * * Inputs: dlat - Floating point degrees. * * Outputs: slat - String in format yyyy. * Exactly 4 bytes, no nul terminator. * *----------------------------------------------------------------*/ void latitude_to_comp_str (double dlat, char *clat) { int y, y0, y1, y2, y3; if (dlat < -90.) { text_color_set(DW_COLOR_ERROR); dw_printf ("Latitude is less than -90. Changing to -90.n"); dlat = -90.; } if (dlat > 90.) { text_color_set(DW_COLOR_ERROR); dw_printf ("Latitude is greater than 90. Changing to 90.n"); dlat = 90.; } y = (int)round(380926. * (90. - dlat)); y0 = y / (91*91*91); y -= y0 * (91*91*91); y1 = y / (91*91); y -= y1 * (91*91); y2 = y / (91); y -= y2 * (91); y3 = y; clat[0] = y0 + 33; clat[1] = y1 + 33; clat[2] = y2 + 33; clat[3] = y3 + 33; } /*------------------------------------------------------------------ * * Name: longitude_to_comp_str * * Purpose: Convert numeric longitude to compressed string for transmission. * * Inputs: dlong - Floating point degrees. * * Outputs: slat - String in format xxxx. * Exactly 4 bytes, no nul terminator. * *----------------------------------------------------------------*/ void longitude_to_comp_str (double dlong, char *clon) { int x, x0, x1, x2, x3; if (dlong < -180.) { text_color_set(DW_COLOR_ERROR); dw_printf ("Longitude is less than -180. Changing to -180.n"); dlong = -180.; } if (dlong > 180.) { text_color_set(DW_COLOR_ERROR); dw_printf ("Longitude is greater than 180. Changing to 180.n"); dlong = 180.; } x = (int)round(190463. * (180. + dlong)); x0 = x / (91*91*91); x -= x0 * (91*91*91); x1 = x / (91*91); x -= x1 * (91*91); x2 = x / (91); x -= x2 * (91); x3 = x; clon[0] = x0 + 33; clon[1] = x1 + 33; clon[2] = x2 + 33; clon[3] = x3 + 33; } /*------------------------------------------------------------------ * * Name: latitude_to_nmea * * Purpose: Convert numeric latitude to strings for NMEA sentence. * * Inputs: dlat - Floating point degrees. * * Outputs: slat - String in format ddmm.mmmm * hemi - Hemisphere or empty string. * * Returns: None * *----------------------------------------------------------------*/ void latitude_to_nmea (double dlat, char *slat, char *hemi) { int ideg; /* whole number of degrees. */ double dmin; /* Minutes after removing degrees. */ char smin[10]; /* Minutes in format mm.mmmm */ if (dlat == G_UNKNOWN) { strcpy (slat, ""); strcpy (hemi, ""); return; } if (dlat < -90.) { text_color_set(DW_COLOR_ERROR); dw_printf ("Latitude is less than -90. Changing to -90.n"); dlat = -90.; } if (dlat > 90.) { text_color_set(DW_COLOR_ERROR); dw_printf ("Latitude is greater than 90. Changing to 90.n"); dlat = 90.; } if (dlat < 0) { dlat = (- dlat); strcpy (hemi, "S"); } else { strcpy (hemi, "N"); } ideg = (int)dlat; dmin = (dlat - ideg) * 60.; snprintf (smin, sizeof(smin), "%07.4f", dmin); /* Due to roundoff, 59.99999 could come out as "60.0000" */ if (smin[0] == '6') { smin[0] = '0'; ideg++; } sprintf (slat, "%02d%s", ideg, smin); } /* end latitude_to_str */ /*------------------------------------------------------------------ * * Name: longitude_to_nmea * * Purpose: Convert numeric longitude to strings for NMEA sentence. * * Inputs: dlong - Floating point degrees. * * Outputs: slong - String in format dddmm.mmmm * hemi - Hemisphere or empty string. * * Returns: None * *----------------------------------------------------------------*/ void longitude_to_nmea (double dlong, char *slong, char *hemi) { int ideg; /* whole number of degrees. */ double dmin; /* Minutes after removing degrees. */ char smin[10]; /* Minutes in format mm.mmmm */ if (dlong == G_UNKNOWN) { strcpy (slong, ""); strcpy (hemi, ""); return; } if (dlong < -180.) { text_color_set(DW_COLOR_ERROR); dw_printf ("longitude is less than -180. Changing to -180.n"); dlong = -180.; } if (dlong > 180.) { text_color_set(DW_COLOR_ERROR); dw_printf ("longitude is greater than 180. Changing to 180.n"); dlong = 180.; } if (dlong < 0) { dlong = (- dlong); strcpy (hemi, "W"); } else { strcpy (hemi, "E"); } ideg = (int)dlong; dmin = (dlong - ideg) * 60.; snprintf (smin, sizeof(smin), "%07.4f", dmin); /* Due to roundoff, 59.99999 could come out as "60.0000" */ if (smin[0] == '6') { smin[0] = '0'; ideg++; } sprintf (slong, "%03d%s", ideg, smin); } /* end longitude_to_nmea */ /*------------------------------------------------------------------ * * Function: latitude_from_nmea * * Purpose: Convert NMEA latitude encoding to degrees. * * Inputs: pstr - Pointer to numeric string. * phemi - Pointer to following field. Should be N or S. * * Returns: Double precision value in degrees. Negative for South. * * Description: Latitude field has * 2 digits for degrees * 2 digits for minutes * period * Variable number of fractional digits for minutes. * I've seen 2, 3, and 4 fractional digits. * * * Bugs: Very little validation of data. * * Errors: Return constant G_UNKNOWN for any type of error. * *------------------------------------------------------------------*/ double latitude_from_nmea (char *pstr, char *phemi) { double lat; if ( ! isdigit((unsigned char)(pstr[0]))) return (G_UNKNOWN); if (pstr[4] != '.') return (G_UNKNOWN); lat = (pstr[0] - '0') * 10 + (pstr[1] - '0') + atof(pstr+2) / 60.0; if (lat < 0 || lat > 90) { text_color_set(DW_COLOR_ERROR); dw_printf("Error: Latitude not in range of 0 to 90.\n"); } // Saw this one time: // $GPRMC,000000,V,0000.0000,0,00000.0000,0,000,000,000000,,*01 // If location is unknown, I think the hemisphere should be // an empty string. TODO: Check on this. // 'V' means void, so sentence should be discarded rather than // trying to extract any data from it. if (*phemi != 'N' && *phemi != 'S' && *phemi != '\0') { text_color_set(DW_COLOR_ERROR); dw_printf("Error: Latitude hemisphere should be N or S.\n"); } if (*phemi == 'S') lat = ( - lat); return (lat); } /*------------------------------------------------------------------ * * Function: longitude_from_nmea * * Purpose: Convert NMEA longitude encoding to degrees. * * Inputs: pstr - Pointer to numeric string. * phemi - Pointer to following field. Should be E or W. * * Returns: Double precision value in degrees. Negative for West. * * Description: Longitude field has * 3 digits for degrees * 2 digits for minutes * period * Variable number of fractional digits for minutes * * * Bugs: Very little validation of data. * * Errors: Return constant G_UNKNOWN for any type of error. * *------------------------------------------------------------------*/ double longitude_from_nmea (char *pstr, char *phemi) { double lon; if ( ! isdigit((unsigned char)(pstr[0]))) return (G_UNKNOWN); if (pstr[5] != '.') return (G_UNKNOWN); lon = (pstr[0] - '0') * 100 + (pstr[1] - '0') * 10 + (pstr[2] - '0') + atof(pstr+3) / 60.0; if (lon < 0 || lon > 180) { text_color_set(DW_COLOR_ERROR); dw_printf("Error: Longitude not in range of 0 to 180.\n"); } if (*phemi != 'E' && *phemi != 'W' && *phemi != '\0') { text_color_set(DW_COLOR_ERROR); dw_printf("Error: Longitude hemisphere should be E or W.\n"); } if (*phemi == 'W') lon = ( - lon); return (lon); } /*------------------------------------------------------------------ * * Function: ll_distance_km * * Purpose: Calculate distance between two locations. * * Inputs: lat1, lon1 - One location, in degrees. * lat2, lon2 - other location * * Returns: Distance in km. * * Description: The Ubiquitous Haversine formula. * *------------------------------------------------------------------*/ #define R 6371 double ll_distance_km (double lat1, double lon1, double lat2, double lon2) { double a; lat1 *= M_PI / 180; lon1 *= M_PI / 180; lat2 *= M_PI / 180; lon2 *= M_PI / 180; a = pow(sin((lat2-lat1)/2),2) + cos(lat1) * cos(lat2) * pow(sin((lon2-lon1)/2),2); return (R * 2 *atan2(sqrt(a), sqrt(1-a))); } /*------------------------------------------------------------------ * * Function: ll_bearing_deg * * Purpose: Calculate bearing between two locations. * * Inputs: lat1, lon1 - starting location, in degrees. * lat2, lon2 - destination location * * Returns: Initial Bearing, in degrees. * The calculation produces Range +- 180 degrees. * But I think that 0 - 360 would be more customary? * *------------------------------------------------------------------*/ double ll_bearing_deg (double lat1, double lon1, double lat2, double lon2) { double b; lat1 *= M_PI / 180; lon1 *= M_PI / 180; lat2 *= M_PI / 180; lon2 *= M_PI / 180; b = atan2 (sin(lon2-lon1) * cos(lat2), cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(lon2-lon1)); b *= 180 / M_PI; if (b < 0) b += 360; return (b); } /*------------------------------------------------------------------ * * Function: ll_dest_lat * ll_dest_lon * * Purpose: Calculate the destination location given a starting point, * distance, and bearing, * * Inputs: lat1, lon1 - starting location, in degrees. * dist - distance in km. * bearing - direction in degrees. Shouldn't matter * if it is in +- 180 or 0 to 360 range. * * Returns: New latitude or longitude. * *------------------------------------------------------------------*/ double ll_dest_lat (double lat1, double lon1, double dist, double bearing) { double lat2; lat1 *= M_PI / 180; // Everything to radians. lon1 *= M_PI / 180; bearing *= M_PI / 180; lat2 = asin(sin(lat1) * cos(dist/R) + cos(lat1) * sin(dist/R) * cos(bearing)); lat2 *= 180 / M_PI; // Back to degrees. return (lat2); } double ll_dest_lon (double lat1, double lon1, double dist, double bearing) { double lon2; double lat2; lat1 *= M_PI / 180; // Everything to radians. lon1 *= M_PI / 180; bearing *= M_PI / 180; lat2 = asin(sin(lat1) * cos(dist/R) + cos(lat1) * sin(dist/R) * cos(bearing)); lon2 = lon1 + atan2(sin(bearing) * sin(dist/R) * cos(lat1), cos(dist/R) - sin(lat1) * sin(lat2)); lon2 *= 180 / M_PI; // Back to degrees. return (lon2); } /*------------------------------------------------------------------ * * Function: ll_from_grid_square * * Purpose: Convert Maidenhead locator to latitude and longitude. * * Inputs: maidenhead - 2, 4, 6, 8, 10, or 12 character grid square locator. * * Outputs: dlat, dlon - Latitude and longitude. * Original values unchanged if error. * * Returns: 1 for success, 0 if error. * * Reference: A good converter for spot checking. Only handles 4 or 6 characters :-( * http://home.arcor.de/waldemar.kebsch/The_Makrothen_Contest/fmaidenhead.html * * Rambling: What sort of resolution does this provide? * For 8 character form, each latitude unit is 0.25 minute. * (Longitude can be up to twice that around the equator.) * 6371 km * 2 * pi * 0.25 / 60 / 360 = 0.463 km. Is that right? * * Using this calculator, http://www.earthpoint.us/Convert.aspx * It gives lower left corner of square rather than the middle. :-( * * FN42MA00 --> 19T 334361mE 4651711mN * FN42MA11 --> 19T 335062mE 4652157mN * ------ ------- * 701 446 meters difference. * * With another two pairs, we are down around 2 meters for latitude. * *------------------------------------------------------------------*/ #define MH_MIN_PAIR 1 #define MH_MAX_PAIR 6 #define MH_UNITS ( 18 * 10 * 24 * 10 * 24 * 10 * 2 ) static const struct { char *position; char min_ch; char max_ch; int value; } mh_pair[MH_MAX_PAIR] = { { "first", 'A', 'R', 10 * 24 * 10 * 24 * 10 * 2 }, { "second", '0', '9', 24 * 10 * 24 * 10 * 2 }, { "third", 'A', 'X', 10 * 24 * 10 * 2 }, { "fourth", '0', '9', 24 * 10 * 2 }, { "fifth", 'A', 'X', 10 * 2 }, { "sixth", '0', '9', 2 } }; // Even so we can get center of square. #if 1 int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon) { char mh[16]; /* Local copy, changed to upper case. */ int ilat = 0, ilon = 0; /* In units in table above. */ char *p; int n; int np = strlen(maidenhead) / 2; /* Number of pairs of characters. */ if (strlen(maidenhead) %2 != 0 || np < MH_MIN_PAIR || np > MH_MAX_PAIR) { text_color_set(DW_COLOR_ERROR); dw_printf("Maidenhead locator \"%s\" must from 1 to %d pairs of characters.\n", maidenhead, MH_MAX_PAIR); return (0); } strlcpy (mh, maidenhead, sizeof(mh)); for (p = mh; *p != '\0'; p++) { if (islower(*p)) *p = toupper(*p); } for (n = 0; n < np; n++) { if (mh[2*n] < mh_pair[n].min_ch || mh[2*n] > mh_pair[n].max_ch || mh[2*n+1] < mh_pair[n].min_ch || mh[2*n+1] > mh_pair[n].max_ch) { text_color_set(DW_COLOR_ERROR); dw_printf("The %s pair of characters in Maidenhead locator \"%s\" must be in range of %c thru %c.\n", mh_pair[n].position, maidenhead, mh_pair[n].min_ch, mh_pair[n].max_ch); return (0); } ilon += ( mh[2*n] - mh_pair[n].min_ch ) * mh_pair[n].value; ilat += ( mh[2*n+1] - mh_pair[n].min_ch ) * mh_pair[n].value; if (n == np-1) { // If last pair, take center of square. ilon += mh_pair[n].value / 2; ilat += mh_pair[n].value / 2; } } *dlat = (double)ilat / MH_UNITS * 180. - 90.; *dlon = (double)ilon / MH_UNITS * 360. - 180.; //text_color_set(DW_COLOR_DEBUG); //dw_printf("DEBUG: Maidenhead conversion \"%s\" -> %.6f %.6f\n", maidenhead, *dlat, *dlon); return (1); } #else int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon) { double lat, lon; char mh[16]; if (strlen(maidenhead) != 2 && strlen(maidenhead) != 4 && strlen(maidenhead) != 6 && strlen(maidenhead) != 8) { text_color_set(DW_COLOR_ERROR); dw_printf("Maidenhead locator \"%s\" must 2, 4, 6, or 8 characters.\n", maidenhead); return (0); } strcpy (mh, maidenhead); if (islower(mh[0])) mh[0] = toupper(mh[0]); if (islower(mh[1])) mh[1] = toupper(mh[1]); if (mh[0] < 'A' || mh[0] > 'R' || mh[1] < 'A' || mh[1] > 'R') { text_color_set(DW_COLOR_ERROR); dw_printf("The first pair of characters in Maidenhead locator \"%s\" must be in range of A thru R.\n", maidenhead); return (0); } /* Lon: 360 deg / 18 squares = 20 deg / square */ /* Lat: 180 deg / 18 squares = 10 deg / square */ lon = (mh[0] - 'A') * 20 - 180; lat = (mh[1] - 'A') * 10 - 90; if (strlen(mh) >= 4) { if ( ! isdigit(mh[2]) || ! isdigit(mh[3]) ) { text_color_set(DW_COLOR_ERROR); dw_printf("The second pair of characters in Maidenhead locator \"%s\" must be digits.\n", maidenhead); return (0); } /* Lon: 20 deg / 10 squares = 2 deg / square */ /* Lat: 10 deg / 10 squares = 1 deg / square */ lon += (mh[2] - '0') * 2; lat += (mh[3] - '0'); if (strlen(mh) >=6) { if (islower(mh[4])) mh[4] = toupper(mh[4]); if (islower(mh[5])) mh[5] = toupper(mh[5]); if (mh[4] < 'A' || mh[4] > 'X' || mh[5] < 'A' || mh[5] > 'X') { text_color_set(DW_COLOR_ERROR); dw_printf("The third pair of characters in Maidenhead locator \"%s\" must be in range of A thru X.\n", maidenhead); return (0); } /* Lon: 2 deg / 24 squares = 5 minutes / square */ /* Lat: 1 deg / 24 squares = 2.5 minutes / square */ lon += (mh[4] - 'A') * 5.0 / 60.0; lat += (mh[5] - 'A') * 2.5 / 60.0; if (strlen(mh) >= 8) { if ( ! isdigit(mh[6]) || ! isdigit(mh[7]) ) { text_color_set(DW_COLOR_ERROR); dw_printf("The fourth pair of characters in Maidenhead locator \"%s\" must be digits.\n", maidenhead); return (0); } /* Lon: 5 min / 10 squares = 0.5 minutes / square */ /* Lat: 2.5 min / 10 squares = 0.25 minutes / square */ lon += (mh[6] - '0') * 0.50 / 60.0; lat += (mh[7] - '0') * 0.25 / 60.0; lon += 0.250 / 60.0; /* Move from corner to center of square */ lat += 0.125 / 60.0; } else { lon += 2.5 / 60.0; /* Move from corner to center of square */ lat += 1.25 / 60.0; } } else { lon += 1.0; /* Move from corner to center of square */ lat += 0.5; } } else { lon += 10; /* Move from corner to center of square */ lat += 5; } //text_color_set(DW_COLOR_DEBUG); //dw_printf("DEBUG: Maidenhead conversion \"%s\" -> %.6f %.6f\n", maidenhead, lat, lon); *dlat = lat; *dlon = lon; return (1); } #endif /* end ll_from_grid_square */ #if LLTEST /* gcc -o lltest -DLLTEST latlong.c textcolor.o misc.a && lltest */ int main (int argc, char *argv[]) { char result[20]; int errors = 0; int ok; double dlat, dlon; double d, b; /* Latitude to APRS format. */ latitude_to_str (45.25, 0, result); if (strcmp(result, "4515.00N") != 0) { errors++; dw_printf ("Error 1.1: Did not expect \"%s\"\n", result); } latitude_to_str (-45.25, 0, result); if (strcmp(result, "4515.00S") != 0) { errors++; dw_printf ("Error 1.2: Did not expect \"%s\"\n", result); } latitude_to_str (45.999830, 0, result); if (strcmp(result, "4559.99N") != 0) { errors++; dw_printf ("Error 1.3: Did not expect \"%s\"\n", result); } latitude_to_str (45.99999, 0, result); if (strcmp(result, "4600.00N") != 0) { errors++; dw_printf ("Error 1.4: Did not expect \"%s\"\n", result); } latitude_to_str (45.999830, 1, result); if (strcmp(result, "4559.9 N") != 0) { errors++; dw_printf ("Error 1.5: Did not expect \"%s\"\n", result); } latitude_to_str (45.999830, 2, result); if (strcmp(result, "4559. N") != 0) { errors++; dw_printf ("Error 1.6: Did not expect \"%s\"\n", result); } latitude_to_str (45.999830, 3, result); if (strcmp(result, "455 . N") != 0) { errors++; dw_printf ("Error 1.7: Did not expect \"%s\"\n", result); } latitude_to_str (45.999830, 4, result); if (strcmp(result, "45 . N") != 0) { errors++; dw_printf ("Error 1.8: Did not expect \"%s\"\n", result); } /* Longitude to APRS format. */ longitude_to_str (45.25, 0, result); if (strcmp(result, "04515.00E") != 0) { errors++; dw_printf ("Error 2.1: Did not expect \"%s\"\n", result); } longitude_to_str (-45.25, 0, result); if (strcmp(result, "04515.00W") != 0) { errors++; dw_printf ("Error 2.2: Did not expect \"%s\"\n", result); } longitude_to_str (45.999830, 0, result); if (strcmp(result, "04559.99E") != 0) { errors++; dw_printf ("Error 2.3: Did not expect \"%s\"\n", result); } longitude_to_str (45.99999, 0, result); if (strcmp(result, "04600.00E") != 0) { errors++; dw_printf ("Error 2.4: Did not expect \"%s\"\n", result); } longitude_to_str (45.999830, 1, result); if (strcmp(result, "04559.9 E") != 0) { errors++; dw_printf ("Error 2.5: Did not expect \"%s\"\n", result); } longitude_to_str (45.999830, 2, result); if (strcmp(result, "04559. E") != 0) { errors++; dw_printf ("Error 2.6: Did not expect \"%s\"\n", result); } longitude_to_str (45.999830, 3, result); if (strcmp(result, "0455 . E") != 0) { errors++; dw_printf ("Error 2.7: Did not expect \"%s\"\n", result); } longitude_to_str (45.999830, 4, result); if (strcmp(result, "045 . E") != 0) { errors++; dw_printf ("Error 2.8: Did not expect \"%s\"\n", result); } /* Compressed format. */ /* Protocol spec example has <*e7 but I got <*e8 due to rounding rather than truncation to integer. */ memset(result, 0, sizeof(result)); latitude_to_comp_str (-90.0, result); if (strcmp(result, "{{!!") != 0) { errors++; dw_printf ("Error 3.1: Did not expect \"%s\"\n", result); } latitude_to_comp_str (49.5, result); if (strcmp(result, "5L!!") != 0) { errors++; dw_printf ("Error 3.2: Did not expect \"%s\"\n", result); } latitude_to_comp_str (90.0, result); if (strcmp(result, "!!!!") != 0) { errors++; dw_printf ("Error 3.3: Did not expect \"%s\"\n", result); } longitude_to_comp_str (-180.0, result); if (strcmp(result, "!!!!") != 0) { errors++; dw_printf ("Error 3.4: Did not expect \"%s\"\n", result); } longitude_to_comp_str (-72.75, result); if (strcmp(result, "<*e8") != 0) { errors++; dw_printf ("Error 3.5: Did not expect \"%s\"\n", result); } longitude_to_comp_str (180.0, result); if (strcmp(result, "{{!!") != 0) { errors++; dw_printf ("Error 3.6: Did not expect \"%s\"\n", result); } // to be continued for others... NMEA... /* Distance & bearing - Take a couple examples from other places and see if we get similar results. */ // http://www.movable-type.co.uk/scripts/latlong.html d = ll_distance_km (35., 45., 35., 135.); b = ll_bearing_deg (35., 45., 35., 135.); if (d < 7862 || d > 7882) { errors++; dw_printf ("Error 5.1: Did not expect distance %.1f\n", d); } if (b < 59.7 || b > 60.3) { errors++; dw_printf ("Error 5.2: Did not expect bearing %.1f\n", b); } // Sydney to Kinsale. https://woodshole.er.usgs.gov/staffpages/cpolloni/manitou/ccal.htm d = ll_distance_km (-33.8688, 151.2093, 51.7059, -8.5222); b = ll_bearing_deg (-33.8688, 151.2093, 51.7059, -8.5222); if (d < 17435 || d > 17455) { errors++; dw_printf ("Error 5.3: Did not expect distance %.1f\n", d); } if (b < 327-1 || b > 327+1) { errors++; dw_printf ("Error 5.4: Did not expect bearing %.1f\n", b); } /* * More distance and bearing. * Here we will start at some location1 (lat1,lon1) and go some distance (d1) at some bearing (b1). * This results in a new location2 (lat2, lon2). * We then calculate the distance and bearing from location1 to location2 and compare with the intention. */ int lat1, lon1, d1 = 10, b1; double lat2, lon2, d2, b2; for (lat1 = -60; lat1 <= 60; lat1 += 30) { for (lon1 = -180; lon1 <= 180; lon1 +=30) { for (b1 = 0; b1 < 360; b1 += 15) { lat2 = ll_dest_lat ((double)lat1, (double)lon1, (double)d1, (double)b1); lon2 = ll_dest_lon ((double)lat1, (double)lon1, (double)d1, (double)b1); d2 = ll_distance_km ((double)lat1, (double)lon1, lat2, lon2); b2 = ll_bearing_deg ((double)lat1, (double)lon1, lat2, lon2); if (b2 > 359.9 && b2 < 360.1) b2 = 0; // must be within 0.1% of distance and 0.1 degree. if (d2 < 0.999 * d1 || d2 > 1.001 * d1) { errors++; dw_printf ("Error 5.8: lat1=%d, lon2=%d, d1=%d, b1=%d, d2=%.2f\n", lat1, lon1, d1, b1, d2); } if (b2 < b1 - 0.1 || b2 > b1 + 0.1) { errors++; dw_printf ("Error 5.9: lat1=%d, lon2=%d, d1=%d, b1=%d, b2=%.2f\n", lat1, lon1, d1, b1, b2); } } } } /* Maidenhead locator to lat/long. */ ok = ll_from_grid_square ("BL11", &dlat, &dlon); if (!ok || dlat < 20.4999999 || dlat > 21.5000001 || dlon < -157.0000001 || dlon > -156.9999999) { errors++; dw_printf ("Error 7.1: Did not expect %.6f %.6f\n", dlat, dlon); } ok = ll_from_grid_square ("BL11BH", &dlat, &dlon); if (!ok || dlat < 21.31249 || dlat > 21.31251 || dlon < -157.87501 || dlon > -157.87499) { errors++; dw_printf ("Error 7.2: Did not expect %.6f %.6f\n", dlat, dlon); } #if 0 // TODO: add more test cases after comparing results with other cconverters. // Many other converters are limited to smaller number of characters, // or return corner rather than center of square, or return 3 decimal places for degrees. ok = ll_from_grid_square ("BL11BH16", &dlat, &dlon); if (!ok || dlat < 21.? || dlat > 21.? || dlon < -157.? || dlon > -157.?) { errors++; dw_printf ("Error 7.3: Did not expect %.6f %.6f\n", dlat, dlon); } ok = ll_from_grid_square ("BL11BH16oo", &dlat, &dlon); if (!ok || dlat < 21.? || dlat > 21.? || dlon < -157.? || dlon > -157.?) { errors++; dw_printf ("Error 7.4: Did not expect %.6f %.6f\n", dlat, dlon); } ok = ll_from_grid_square ("BL11BH16oo66", &dlat, &dlon); if (!ok || dlat < 21.? || dlat > 21.? || dlon < -157.? || dlon > -157.?) { errors++; dw_printf ("Error 7.5: Did not expect %.6f %.6f\n", dlat, dlon); } #endif if (errors > 0) { text_color_set (DW_COLOR_ERROR); dw_printf ("\nLocation Coordinate Conversion Test - FAILED!\n"); exit (EXIT_FAILURE); } text_color_set (DW_COLOR_REC); dw_printf ("\nLocation Coordinate Conversion Test - SUCCESS!\n"); exit (EXIT_SUCCESS); } #endif /* end latlong.c */direwolf-1.5+dfsg/latlong.h000066400000000000000000000013431347750676600157500ustar00rootroot00000000000000 /* latlong.h */ /* Use this value for unknown latitude/longitude or other values. */ #define G_UNKNOWN (-999999) void latitude_to_str (double dlat, int ambiguity, char *slat); void longitude_to_str (double dlong, int ambiguity, char *slong); void latitude_to_comp_str (double dlat, char *clat); void longitude_to_comp_str (double dlon, char *clon); void latitude_to_nmea (double dlat, char *slat, char *hemi); void longitude_to_nmea (double dlong, char *slong, char *hemi); double latitude_from_nmea (char *pstr, char *phemi); double longitude_from_nmea (char *pstr, char *phemi); double ll_distance_km (double lat1, double lon1, double lat2, double lon2); int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon);direwolf-1.5+dfsg/ll2utm.c000066400000000000000000000050201347750676600155160ustar00rootroot00000000000000/* Latitude / Longitude to UTM conversion */ #include "direwolf.h" #include #include #include #include "utm.h" #include "mgrs.h" #include "usng.h" #include "error_string.h" #define D2R(d) ((d) * M_PI / 180.) #define R2D(r) ((r) * 180. / M_PI) static void usage(); int main (int argc, char *argv[]) { double easting; double northing; double lat, lon; char mgrs[32]; char usng[32]; char hemisphere; long lzone; long err; char message[300]; if (argc != 3) usage(); lat = atof(argv[1]); lon = atof(argv[2]); // UTM err = Convert_Geodetic_To_UTM (D2R(lat), D2R(lon), &lzone, &hemisphere, &easting, &northing); if (err == 0) { printf ("UTM zone = %ld, hemisphere = %c, easting = %.0f, northing = %.0f\n", lzone, hemisphere, easting, northing); } else { utm_error_string (err, message); fprintf (stderr, "Conversion to UTM failed:\n%s\n\n", message); // Others could still succeed, keep going. } // Practice run with MGRS to see if it will succeed err = Convert_Geodetic_To_MGRS (D2R(lat), D2R(lon), 5L, mgrs); if (err == 0) { // OK, hope changing precision doesn't make a difference. long precision; printf ("MGRS ="); for (precision = 1; precision <= 5; precision++) { Convert_Geodetic_To_MGRS (D2R(lat), D2R(lon), precision, mgrs); printf (" %s", mgrs); } printf ("\n"); } else { mgrs_error_string (err, message); fprintf (stderr, "Conversion to MGRS failed:\n%s\n", message); } // Same for USNG. err = Convert_Geodetic_To_USNG (D2R(lat), D2R(lon), 5L, usng); if (err == 0) { long precision; printf ("USNG ="); for (precision = 1; precision <= 5; precision++) { Convert_Geodetic_To_USNG (D2R(lat), D2R(lon), precision, usng); printf (" %s", usng); } printf ("\n"); } else { usng_error_string (err, message); fprintf (stderr, "Conversion to USNG failed:\n%s\n", message); } exit (0); } static void usage (void) { fprintf (stderr, "Latitude / Longitude to UTM conversion\n"); fprintf (stderr, "\n"); fprintf (stderr, "Usage:\n"); fprintf (stderr, "\tll2utm latitude longitude\n"); fprintf (stderr, "\n"); fprintf (stderr, "where,\n"); fprintf (stderr, "\tLatitude and longitude are in decimal degrees.\n"); fprintf (stderr, "\t Use negative for south or west.\n"); fprintf (stderr, "\n"); fprintf (stderr, "Example:\n"); fprintf (stderr, "\tll2utm 42.662139 -71.365553\n"); exit (1); }direwolf-1.5+dfsg/log.c000066400000000000000000000351541347750676600150730ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2014, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * File: log.c * * Purpose: Save received packets to a log file. * * Description: Rather than saving the raw, sometimes rather cryptic and * unreadable, format, write separated properties into * CSV format for easy reading and later processing. * * There are two alternatives here. * * -L logfile Specify full file path. * * -l logdir Daily names will be created here. * * Use one or the other but not both. * *------------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "decode_aprs.h" #include "log.h" /* * CSV format needs quotes if value contains comma or quote. */ static void quote_for_csv (char *out, size_t outsize, const char *in) { const char *p; char *q = out; int need_quote = 0; for (p = in; *p != '\0'; p++) { if (*p == ',' || *p == '"') { need_quote = 1; break; } } // BUG: need to avoid buffer overflow on "out". *strcpy* if (need_quote) { *q++ = '"'; for (p = in; *p != '\0'; p++) { if (*p == '"') { *q++ = *p; } *q++ = *p; } *q++ = '"'; *q = '\0'; } else { strlcpy (out, in, outsize); } } /*------------------------------------------------------------------ * * Function: log_init * * Purpose: Initialization at start of application. * * Inputs: daily_names - True if daily names should be generated. * In this case path is a directory. * When false, path would be the file name. * * path - Log file name or just directory. * Use "." for current directory. * Empty string disables feature. * * Global Out: g_daily_names - True if daily names should be generated. * * g_log_path - Save directory or full name here for later use. * * g_log_fp - File pointer for writing. * Note that file is kept open. * We don't open/close for every new item. * * g_open_fname - Name of currently open file. * Applicable only when g_daily_names is true. * *------------------------------------------------------------------*/ static int g_daily_names; static char g_log_path[80]; static FILE *g_log_fp; static char g_open_fname[20]; void log_init (int daily_names, char *path) { struct stat st; g_daily_names = daily_names; strlcpy (g_log_path, "", sizeof(g_log_path)); g_log_fp = NULL; strlcpy (g_open_fname, "", sizeof(g_open_fname)); if (strlen(path) == 0) { return; } if (g_daily_names) { // Original strategy. Automatic daily file names. if (stat(path,&st) == 0) { // Exists, but is it a directory? if (S_ISDIR(st.st_mode)) { // Specified directory exists. strlcpy (g_log_path, path, sizeof(g_log_path)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Log file location \"%s\" is not a directory.\n", path); dw_printf ("Using current working directory \".\" instead.\n"); strlcpy (g_log_path, ".", sizeof(g_log_path)); } } else { // Doesn't exist. Try to create it. // parent directory must exist. // We don't create multiple levels like "mkdir -p" #if __WIN32__ if (_mkdir (path) == 0) { #else if (mkdir (path, 0777) == 0) { #endif // Success. text_color_set(DW_COLOR_INFO); dw_printf ("Log file location \"%s\" has been created.\n", path); strlcpy (g_log_path, path, sizeof(g_log_path)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create log file location \"%s\".\n", path); dw_printf ("%s\n", strerror(errno)); dw_printf ("Using current working directory \".\" instead.\n"); strlcpy (g_log_path, ".", sizeof(g_log_path)); } } } else { // Added in version 1.5. Single file. // Typically logrotate would be used to keep size under control. text_color_set(DW_COLOR_INFO); dw_printf ("Log file is \"%s\"\n", path); strlcpy (g_log_path, path, sizeof(g_log_path)); } } /* end log_init */ /*------------------------------------------------------------------ * * Function: log_write * * Purpose: Save information to log file. * * Inputs: chan - Radio channel where heard. * * A - Explode information from APRS packet. * * pp - Received packet object. * * alevel - audio level. * * retries - Amount of effort to get a good CRC. * *------------------------------------------------------------------*/ void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries) { time_t now; struct tm tm; if (strlen(g_log_path) == 0) return; now = time(NULL); // Get current time. (void)gmtime_r (&now, &tm); if (g_daily_names) { // Original strategy. Automatic daily file names. char fname[20]; // Generate the file name from current date, UTC. // Why UTC rather than local time? I don't recall the reasoning. // It's been there a few years and no on complained so leave it alone for now. // Microsoft doesn't recognize %F as equivalent to %Y-%m-%d strftime (fname, sizeof(fname), "%Y-%m-%d.log", &tm); // Close current file if name has changed if (g_log_fp != NULL && strcmp(fname, g_open_fname) != 0) { log_term (); } // Open for append if not already open. if (g_log_fp == NULL) { char full_path[120]; struct stat st; int already_there; strlcpy (full_path, g_log_path, sizeof(full_path)); #if __WIN32__ strlcat (full_path, "\\", sizeof(full_path)); #else strlcat (full_path, "/", sizeof(full_path)); #endif strlcat (full_path, fname, sizeof(full_path)); // See if file already exists and not empty. // This is used later to write a header if it did not exist already. already_there = (stat(full_path,&st) == 0) && (st.st_size > 0); text_color_set(DW_COLOR_INFO); dw_printf("Opening log file \"%s\".\n", fname); g_log_fp = fopen (full_path, "a"); if (g_log_fp != NULL) { strlcpy (g_open_fname, fname, sizeof(g_open_fname)); } else { text_color_set(DW_COLOR_ERROR); dw_printf("Can't open log file \"%s\" for write.\n", full_path); dw_printf ("%s\n", strerror(errno)); strlcpy (g_open_fname, "", sizeof(g_open_fname)); return; } // Write a header suitable for importing into a spreadsheet // only if this will be the first line. if ( ! already_there) { fprintf (g_log_fp, "chan,utime,isotime,source,heard,level,error,dti,name,symbol,latitude,longitude,speed,course,altitude,frequency,offset,tone,system,status,telemetry,comment\n"); } } } else { // Added in version 1.5. Single file. // Open for append if not already open. if (g_log_fp == NULL) { struct stat st; int already_there; // See if file already exists and not empty. // This is used later to write a header if it did not exist already. already_there = (stat(g_log_path,&st) == 0) && (st.st_size > 0); text_color_set(DW_COLOR_INFO); dw_printf("Opening log file \"%s\"\n", g_log_path); g_log_fp = fopen (g_log_path, "a"); if (g_log_fp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf("Can't open log file \"%s\" for write.\n", g_log_path); dw_printf ("%s\n", strerror(errno)); strlcpy (g_log_path, "", sizeof(g_log_path)); return; } // Write a header suitable for importing into a spreadsheet // only if this will be the first line. if ( ! already_there) { fprintf (g_log_fp, "chan,utime,isotime,source,heard,level,error,dti,name,symbol,latitude,longitude,speed,course,altitude,frequency,offset,tone,system,status,telemetry,comment\n"); } } } // Add line to file if it is now open. if (g_log_fp != NULL) { char itime[24]; char heard[AX25_MAX_ADDR_LEN+1]; int h; char stemp[256]; char slat[16], slon[16], sspd[12], scse[12], salt[12]; char sfreq[20], soffs[10], stone[10]; char sdti[10]; char sname[24]; char ssymbol[8]; char smfr[60]; char sstatus[40]; char stelemetry[200]; char scomment[256]; char alevel_text[32]; // Microsoft doesn't recognize %T as equivalent to %H:%M:%S strftime (itime, sizeof(itime), "%Y-%m-%dT%H:%M:%SZ", &tm); /* Who are we hearing? Original station or digipeater? */ /* Similar code in direwolf.c. Combine into one function? */ strlcpy(heard, "", sizeof(heard)); if (pp != NULL) { if (ax25_get_num_addr(pp) == 0) { /* Not AX.25. No station to display below. */ h = -1; strlcpy (heard, "", sizeof(heard)); } else { h = ax25_get_heard(pp); ax25_get_addr_with_ssid(pp, h, heard); } if (h >= AX25_REPEATER_2 && strncmp(heard, "WIDE", 4) == 0 && isdigit(heard[4]) && heard[5] == '\0') { ax25_get_addr_with_ssid(pp, h-1, heard); strlcat (heard, "?", sizeof(heard)); } } ax25_alevel_to_text (alevel, alevel_text); // Might need to quote anything that could contain comma or quote. strlcpy(sdti, "", sizeof(sdti)); if (pp != NULL) { stemp[0] = ax25_get_dti(pp); stemp[1] = '\0'; quote_for_csv (sdti, sizeof(sdti), stemp); } quote_for_csv (sname, sizeof(sname), (strlen(A->g_name) > 0) ? A->g_name : A->g_src); stemp[0] = A->g_symbol_table; stemp[1] = A->g_symbol_code; stemp[2] = '\0'; quote_for_csv (ssymbol, sizeof(ssymbol), stemp); quote_for_csv (smfr, sizeof(smfr), A->g_mfr); quote_for_csv (sstatus, sizeof(sstatus), A->g_mic_e_status); quote_for_csv (stelemetry, sizeof(stelemetry), A->g_telemetry); quote_for_csv (scomment, sizeof(scomment), A->g_comment); strlcpy (slat, "", sizeof(slat)); if (A->g_lat != G_UNKNOWN) snprintf (slat, sizeof(slat), "%.6f", A->g_lat); strlcpy (slon, "", sizeof(slon)); if (A->g_lon != G_UNKNOWN) snprintf (slon, sizeof(slon), "%.6f", A->g_lon); strlcpy (sspd, "", sizeof(sspd)); if (A->g_speed_mph != G_UNKNOWN) snprintf (sspd, sizeof(sspd), "%.1f", DW_MPH_TO_KNOTS(A->g_speed_mph)); strlcpy (scse, "", sizeof(scse)); if (A->g_course != G_UNKNOWN) snprintf (scse, sizeof(scse), "%.1f", A->g_course); strlcpy (salt, "", sizeof(salt)); if (A->g_altitude_ft != G_UNKNOWN) snprintf (salt, sizeof(salt), "%.1f", DW_FEET_TO_METERS(A->g_altitude_ft)); strlcpy (sfreq, "", sizeof(sfreq)); if (A->g_freq != G_UNKNOWN) snprintf (sfreq, sizeof(sfreq), "%.3f", A->g_freq); strlcpy (soffs, "", sizeof(soffs)); if (A->g_offset != G_UNKNOWN) snprintf (soffs, sizeof(soffs), "%+d", A->g_offset); strlcpy (stone, "", sizeof(stone)); if (A->g_tone != G_UNKNOWN) snprintf (stone, sizeof(stone), "%.1f", A->g_tone); if (A->g_dcs != G_UNKNOWN) snprintf (stone, sizeof(stone), "D%03o", A->g_dcs); fprintf (g_log_fp, "%d,%d,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", chan, (int)now, itime, A->g_src, heard, alevel_text, (int)retries, sdti, sname, ssymbol, slat, slon, sspd, scse, salt, sfreq, soffs, stone, smfr, sstatus, stelemetry, scomment); fflush (g_log_fp); } } /* end log_write */ /*------------------------------------------------------------------ * * Function: log_rr_bits * * Purpose: Quick hack to look at the C and RR bits just to see what is there. * This seems like a good place because it is a small subset of the function above. * * Inputs: A - Explode information from APRS packet. * * pp - Received packet object. * *------------------------------------------------------------------*/ void log_rr_bits (decode_aprs_t *A, packet_t pp) { if (1) { char heard[AX25_MAX_ADDR_LEN+1]; char smfr[60]; char *p; int src_c, dst_c; int src_rr, dst_rr; // Sanitize system type (manufacturer) changing any comma to period. strlcpy (smfr, A->g_mfr, sizeof(smfr)); for (p=smfr; *p!='\0'; p++) { if (*p == ',') *p = '.'; } /* Who are we hearing? Original station or digipeater? */ /* Similar code in direwolf.c. Combine into one function? */ strlcpy(heard, "", sizeof(heard)); if (pp != NULL) { int h; if (ax25_get_num_addr(pp) == 0) { /* Not AX.25. No station to display below. */ h = -1; strlcpy (heard, "", sizeof(heard)); } else { h = ax25_get_heard(pp); ax25_get_addr_with_ssid(pp, h, heard); } if (h >= AX25_REPEATER_2 && strncmp(heard, "WIDE", 4) == 0 && isdigit(heard[4]) && heard[5] == '\0') { ax25_get_addr_with_ssid(pp, h-1, heard); strlcat (heard, "?", sizeof(heard)); } src_c = ax25_get_h (pp, AX25_SOURCE); dst_c = ax25_get_h (pp, AX25_DESTINATION); src_rr = ax25_get_rr (pp, AX25_SOURCE); dst_rr = ax25_get_rr (pp, AX25_DESTINATION); // C RR for source // C RR for destination // system type // source // station heard text_color_set(DW_COLOR_INFO); dw_printf ("%d %d%d %d %d%d,%s,%s,%s\n", src_c, (src_rr >> 1) & 1, src_rr & 1, dst_c, (dst_rr >> 1) & 1, dst_rr & 1, smfr, A->g_src, heard); } } } /* end log_rr_bits */ /*------------------------------------------------------------------ * * Function: log_term * * Purpose: Close any open log file. * Called when exiting or when date changes. * *------------------------------------------------------------------*/ void log_term (void) { if (g_log_fp != NULL) { text_color_set(DW_COLOR_INFO); if (g_daily_names) { dw_printf("Closing log file \"%s\".\n", g_open_fname); } else { dw_printf("Closing log file \"%s\".\n", g_log_path); } fclose (g_log_fp); g_log_fp = NULL; strlcpy (g_open_fname, "", sizeof(g_open_fname)); } } /* end log_term */ /* end log.c */ direwolf-1.5+dfsg/log.h000066400000000000000000000005241347750676600150710ustar00rootroot00000000000000 /* log.h */ #include "hdlc_rec2.h" // for retry_t #include "decode_aprs.h" // for decode_aprs_t #include "ax25_pad.h" void log_init (int daily_names, char *path); void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries); void log_rr_bits (decode_aprs_t *A, packet_t pp); void log_term (void); direwolf-1.5+dfsg/log2gpx.c000066400000000000000000000315621347750676600156730ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2014 John Langner, WB2OSZ // // 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, see . // #include "direwolf.h" #include #include #include /* * Information we gather for each thing. */ typedef struct thing_s { double lat; double lon; float alt; /* Meters above average sea level. */ float course; float speed; /* Meters per second. */ char time[20+1+3]; char name[9+1+2]; char desc[32]; /* freq/offset/tone something like 146.955 MHz -600k PL 74.4 */ char comment[80]; /* Combined mic-e status and comment text */ } thing_t; static thing_t *things; /* Dynamically sized array. */ static int max_things; /* Current size. */ static int num_things; /* Number of elements currently in use. */ #define UNKNOWN_VALUE (-999) /* Special value to indicate unknown altitude, speed, course. */ #define KNOTS_TO_METERS_PER_SEC(x) ((x)*0.51444444444) static void read_csv(FILE *fp); static void unquote (char *in, char *out); static int compar(const void *a, const void *b); static void process_things (int first, int last); int main (int argc, char *argv[]) { int first, last; /* * Allocate array for data. * Expand it as needed if initial size is inadequate. */ num_things = 0; max_things = 1000; things = malloc (max_things * sizeof(thing_t)); /* * Read files listed or stdin if none. */ if (argc == 1) { read_csv (stdin); } else { int n; for (n=1; n\n"); printf ("\n"); /* * Group together all records for the same entity. */ last = first = 0; while (first < num_things) { while (last < num_things-1 && strcmp(things[first].name, things[last+1].name) == 0) { last++; } process_things (first, last); first = last + 1; } /* * GPX file tail. */ printf ("\n"); exit (0); } /* * Read from given file, already open, into things array. */ static void read_csv(FILE *fp) { char raw[500]; char csv[500]; int n; while (fgets(raw, sizeof(raw), fp) != NULL) { char *next; char *pchan; char *putime; char *pisotime; char *psource; char *pheard; char *plevel; char *perror; char *pdti; char *pname; char *psymbol; char *platitude; char *plongitude; char *pspeed; char *pcourse; char *paltitude; char *pfreq; char *poffset; char *ptone; char *psystem; char *pstatus; char *ptelemetry; char *pcomment; n = strlen(raw) - 1; while (n >= 0 && (raw[n] == '\r' || raw[n] == '\n')) { raw[n] = '\0'; n--; } unquote (raw, csv); //printf ("%s\n", csv); /* * Separate out the fields. */ next = csv; pchan = strsep(&next,"\t"); putime = strsep(&next,"\t"); pisotime = strsep(&next,"\t"); psource = strsep(&next,"\t"); pheard = strsep(&next,"\t"); plevel = strsep(&next,"\t"); perror = strsep(&next,"\t"); pdti = strsep(&next,"\t"); pname = strsep(&next,"\t"); psymbol = strsep(&next,"\t"); platitude = strsep(&next,"\t"); plongitude = strsep(&next,"\t"); pspeed = strsep(&next,"\t"); /* Knots, must convert. */ pcourse = strsep(&next,"\t"); paltitude = strsep(&next,"\t"); /* Meters, already correct units. */ pfreq = strsep(&next,"\t"); poffset = strsep(&next,"\t"); ptone = strsep(&next,"\t"); psystem = strsep(&next,"\t"); pstatus = strsep(&next,"\t"); ptelemetry = strsep(&next,"\t"); /* Currently unused. Add to description? */ pcomment = strsep(&next,"\t"); /* Suppress the 'set but not used' warnings. */ /* Alternatively, we might use __attribute__((unused)) */ (void)(ptelemetry); (void)(psystem); (void)(psymbol); (void)(pdti); (void)(perror); (void)(plevel); (void)(pheard); (void)(psource); (void)(putime); /* * Skip header line with names of fields. */ if (strcmp(pchan, "chan") == 0) { continue; } /* * Save only if we have valid data. * (Some packets don't contain a position.) */ if (pisotime != NULL && strlen(pisotime) > 0 && pname != NULL && strlen(pname) > 0 && platitude != NULL && strlen(platitude) > 0 && plongitude != NULL && strlen(plongitude) > 0) { float speed = UNKNOWN_VALUE; float course = UNKNOWN_VALUE; float alt = UNKNOWN_VALUE; double freq = UNKNOWN_VALUE; int offset = UNKNOWN_VALUE; char stemp[16], desc[32], comment[256]; if (pspeed != NULL && strlen(pspeed) > 0) { speed = KNOTS_TO_METERS_PER_SEC(atof(pspeed)); } if (pcourse != NULL && strlen(pcourse) > 0) { course = atof(pcourse); } if (paltitude != NULL && strlen(paltitude) > 0) { alt = atof(platitude); } /* combine freq/offset/tone into one description string. */ if (pfreq != NULL && strlen(pfreq) > 0) { freq = atof(pfreq); snprintf (desc, sizeof(desc), "%.3f MHz", freq); } else { strlcpy (desc, "", sizeof(desc)); } if (poffset != NULL && strlen(poffset) > 0) { offset = atoi(poffset); if (offset != 0 && offset % 1000 == 0) { snprintf (stemp, sizeof(stemp), "%+dM", offset / 1000); } else { snprintf (stemp, sizeof(stemp), "%+dk", offset); } if (strlen(desc) > 0) strlcat (desc, " ", sizeof(desc)); strlcat (desc, stemp, sizeof(desc)); } if (ptone != NULL && strlen(ptone) > 0) { if (*ptone == 'D') { snprintf (stemp, sizeof(stemp), "DCS %s", ptone+1); } else { snprintf (stemp, sizeof(stemp), "PL %s", ptone); } if (strlen(desc) > 0) strlcat (desc, " ", sizeof(desc)); strlcat (desc, stemp, sizeof(desc)); } strlcpy (comment, "", sizeof(comment)); if (pstatus != NULL && strlen(pstatus) > 0) { strlcpy (comment, pstatus, sizeof(comment)); } if (pcomment != NULL && strlen(pcomment) > 0) { if (strlen(comment) > 0) strlcat (comment, ", ", sizeof(comment)); strlcat (comment, pcomment, sizeof(comment)); } if (num_things == max_things) { /* It's full. Grow the array by 50%. */ max_things += max_things / 2; things = realloc (things, max_things*sizeof(thing_t)); } things[num_things].lat = atof(platitude); things[num_things].lon = atof(plongitude); things[num_things].speed = speed; things[num_things].course = course; things[num_things].alt = alt; strncpy (things[num_things].time, pisotime, sizeof(things[num_things].time)); strncpy (things[num_things].name, pname, sizeof(things[num_things].name)); strncpy (things[num_things].desc, desc, sizeof(things[num_things].desc)); strncpy (things[num_things].comment, comment, sizeof(things[num_things].comment)); num_things++; } } } /* * Compare function for use with qsort. * Order by name then date/time. */ static int compar(const void *a, const void *b) { thing_t *ta = (thing_t *)a; thing_t *tb = (thing_t *)b; int n; n = strcmp(ta->name, tb->name); if (n != 0) return (n); return (strcmp(ta->time, tb->time)); } /* * Take quoting out of CSV data. * Replace field separator commas with tabs while retaining * commas that were part of the original data before quoting. */ static void unquote (char *in, char *out) { char *p; char *q = out; /* Mind your p's and q's */ int quoted = 0; for (p=in; *p!='\0'; p++) { if (*p == '"') { if (p == in || ( !quoted && *(p-1) == ',')) { /* " found at beginning of field */ quoted = 1; } else if (*(p+1) == '\0' || (quoted && *(p+1) == ',')) { /* " found at end of field */ quoted = 0; } else { /* " found somewhere in middle of field. */ /* We expect to be in quoted state and we should have a pair. */ if (quoted && *(p+1) == '"') { /* Keep one and drop the other. */ *q++ = *p; p++; } else { /* This shouldn't happen. */ fprintf (stderr, "CSV data quoting is messed up.\n"); *q++ = *p; } } } else if (*p == ',') { if (quoted) { /* Comma in original data. Keep it. */ *q++ = *p; } else { /* Comma is field separator. Replace with tab. */ *q++ = '\t'; } } else { /* copy ordinary character. */ *q++ = *p; } } *q = '\0'; } /* * Prepare text values for XML. * Replace significant characters with "predefined entities." */ static void xml_text (char *in, char *out) { char *p, *q; q = out; for (p = in; *p != '\0'; p++) { if (*p == '"') { *q++ = '&'; *q++ = 'q'; *q++ = 'u'; *q++ = 'o'; *q++ = 't'; *q++ = ';'; } else if (*p == '&') { *q++ = '&'; *q++ = 'a'; *q++ = 'm'; *q++ = 'p'; *q++ = ';'; } else if (*p == '\'') { *q++ = '&'; *q++ = 'a'; *q++ = 'p'; *q++ = 'o'; *q++ = 's'; *q++ = ';'; } else if (*p == '<') { *q++ = '&'; *q++ = 'l'; *q++ = 't'; *q++ = ';'; } else if (*p == '>') { *q++ = '&'; *q++ = 'g'; *q++ = 't'; *q++ = ';'; } else { *q++ = *p; } } *q = '\0'; } /* * Process all things with the same name. * They should be sorted by time. * For stationary entities, generate just one GPX waypoint. * For moving entities, generate a GPX track. */ static void process_things (int first, int last) { //printf ("process %d to %d\n", first, last); int i; int moved = 0; char safe_name[30]; char safe_comment[120]; for (i=first+1; i<=last; i++) { if (things[i].lat != things[first].lat) moved = 1; if (things[i].lon != things[first].lon) moved = 1; } if (moved) { /* * Generate track for moving thing. */ xml_text (things[first].name, safe_name); xml_text (things[first].comment, safe_comment); printf (" \n"); printf (" %s\n", safe_name); printf (" \n"); for (i=first; i<=last; i++) { printf (" \n", things[i].lat, things[i].lon); if (things[i].speed != UNKNOWN_VALUE) { printf (" %.1f\n", things[i].speed); } if (things[i].course != UNKNOWN_VALUE) { printf (" %.1f\n", things[i].course); } if (things[i].alt != UNKNOWN_VALUE) { printf (" %.1f\n", things[i].alt); } if (strlen(things[i].desc) > 0) { printf (" %s\n", things[i].desc); } if (strlen(safe_comment) > 0) { printf (" %s\n", safe_comment); } printf (" \n", things[i].time); printf (" \n"); } printf (" \n"); printf (" \n"); /* Also generate waypoint for last location. */ } // Future possibility? // Symbol Name -- not standardized. /* * Generate waypoint for stationary thing or last known position for moving thing. */ xml_text (things[last].name, safe_name); xml_text (things[last].comment, safe_comment); printf (" \n", things[last].lat, things[last].lon); if (things[last].alt != UNKNOWN_VALUE) { printf (" %.1f\n", things[last].alt); } if (strlen(things[i].desc) > 0) { printf (" %s\n", things[i].desc); } if (strlen(safe_comment) > 0) { printf (" %s\n", safe_comment); } printf (" %s\n", safe_name); printf (" \n"); }direwolf-1.5+dfsg/man1/000077500000000000000000000000001347750676600147725ustar00rootroot00000000000000direwolf-1.5+dfsg/man1/aclients.1000066400000000000000000000025531347750676600166630ustar00rootroot00000000000000.TH ACLIENTS 1 .SH NAME aclients \- Test program for side-by-side TNC performance comparison. .SH SYNOPSIS .B aclients .I tnc ... .RS .P Each \fItnc\fR reference is a port, =, and a description. .P .RE .SH DESCRIPTION \fBaclients\fR is used to compare how well different TNCs decode AS.25 frames. The port can be a serial port name, host_name:tcp_port, ip_addr:port, or simply tcp_port. .P .SH OPTIONS None. .SH EXAMPLES .B aclients /dev/ttyS0=KPC3+ /dev/ttyUSB0=D710A 8000=DireWolf 192.168.1.64:8002=other .P Serial port /dev/ttyS0 is connected to a KPC3+ with monitor mode turned on. .P Serial port /dev/ttyUSB0 is connected to a TM-D710A with monitor mode turned on. .P The Dire Wolf software TNC is available on network port 8000. .P Some other software TNC is available on network port 8002 on host 192.168.1.64. .P Packets from each are displayed in columns so it is easy to see how well each decodes the received signals. .P The "Receive Performance" section of the \fBUser Guide\fR contains some complete examples of how to set up tests and the results. .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/man1/atest.1000066400000000000000000000035341347750676600162010ustar00rootroot00000000000000.TH ATEST 1 .SH NAME atest \- Decode AX.25 frames from an audio file. .SH SYNOPSIS .B atest [ \fIoptions\fR ] .I wav-file-in .RS .P \fIwav-file-in\fR is a WAV format audio file. .P .RE .SH DESCRIPTION \fBatest\fR is a test application which decodes AX.25 frames from an audio recording. This provides an easy way to test Dire Wolf decoding performance much quicker than normal real-time. .SH OPTIONS .TP .BI "-B " "n" Bits / second for data. Proper modem selected for 300, 1200, 9600. 300 baud uses 1600/1800 Hz AFSK. 1200 (default) baud uses 1200/2200 Hz AFSK. 9600 baud uses K9NG/G2RUH standard. .TP .BI "-D " "n" Divide audio sample rate by n. .TP .BI "-F " "n" Amount of effort to try fixing frames with an invalid CRC. 0 (default) = consider only correct frames. 1 = Try to fix only a single bit. more = Try modifying more bits to get a good CRC. .TP .BI "-P " "m" Select the demodulator type such as A, B, C, D (default for 300 baud), E (default for 1200 baud), F, A+, B+, C+, D+, E+, F+. .SH EXAMPLES .P .PD 0 .B gen_packets -o test1.wav .P .B atest test1.wav .PD .P .PD 0 .B gen_packets -B 300 -o test3.wav .P .B atest -B 300 test3.wav .PD .P .PD 0 .B gen_packets -B 9600 -o test9.wav .P .B atest -B 9600 test9.wav .PD .P .RS This generates and decodes 3 test files with 1200, 300, and 9600 bits per second. .RE .P .PD 0 .B atest 02_Track_2.wav .P .B atest -P C+ 02_Track_2.wav .P .B atest -F 1 02_Track_2.wav .P .B atest -P C+ -F 1 02_Track_2.wav .PD .P .RS Try different combinations of options to find the best decoding performance. .RE .P .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/man1/decode_aprs.1000066400000000000000000000055201347750676600173260ustar00rootroot00000000000000.TH DECODE_APRS 1 .SH NAME decode_aprs \- Convert APRS raw data to human readable form. .SH SYNOPSIS .B decode_aprs [ \fItext-file\fR ] .RS .P \fItext-file\fR should contain AX.25 packets in the standard monitoring format or as a series two digit hexadecimal numbers. If the first number is 00 or c0, it will be treated as a KISS frame. If no file specified, data will be read from stdin. .P .RE .SH DESCRIPTION \fBdecode_aprs\fR is useful for understanding sometimes obscure APRS packets and finding errors. .SH OPTIONS None. .SH EXAMPLES You see something like this show up on your screen: .P .RS M0XER-3>APRS63,WIDE2-1:!/4\\;u/)K$O J]YD/A=041216|h`RY(1>q!(| .RE .P What does it mean? If you haven't spent a lot of time studying the APRS protocol specification, most of it probably looks like random noise. Pipe it into decode_aprs to find out. .P .RS .B echo 'M0XER-3>APRS63,WIDE2-1:!/4\\\\;u/)K$O J]YD/A=041216|h`RY(1>q!(|' | decode_aprs .RE .P http://www.findu.com/cgi-bin/errors.cgi has a never-ending collection of packets with errors. Sometimes it's not obvious what is wrong with them. Dire Wolf will usually tell you what is wrong. First, cut-n-paste the bad packets into a text file. Here a few examples: .P .RS .nf n2cma>APRS,TCPIP*,qAC,SEVENTH:@212127z43.2333n/77.1w_338/002g001t025P000h65b10208.wview_5_19_0 .P K0YTH-10>APNU3B,NULL,qAR,K0DMF-10:!4601.5NS09255.52W#PHG6360/W2,MNn 444.575 .P 00 82 a0 ae ae 62 60 e0 82 96 68 84 40 40 60 9c 68 b0 ae 86 40 e0 40 ae 92 88 8a 64 63 03 f0 3e 45 4d 36 34 6e 65 2f 23 20 45 63 68 6f 6c 69 6e 6b 20 31 34 35 2e 33 31 30 2f 31 30 30 68 7a 20 54 6f 6e 65 +.fi .RE .P If you simply fed this into decode_aprs, it would complain about the lower case in qA-something, added by the IGate, in the via path. We can take it out with something like this: .P .RS .B cat findu-errors.txt | sed -e 's/,qA.*:/:/' | decode_aprs .RE .P In the first case, we get, .P .RS Address has lower case letters. "n2cma" must be all upper case. .RE .P After changing the source address to upper case, there are other issues. Identifying them is left as an exercise for the reader. .P In the second example, .P .RS .PD 0 Invalid character in latitude. Found 'N' when expecting 0-9 for hundredths of minutes. .P Invalid character in longitude. Found '9' when expecting 0 or 1 for hundreds of degrees. .PD .RE .P In the third example, .P .RS .PD 0 Warning: Lower case letter in Maidenhead locator. Specification requires upper case. .P Digi2 Address, " WIDE2-1" contains character other than letter or digit in character position 1. .PD .RE .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/man1/direwolf.1000066400000000000000000000063541347750676600166770ustar00rootroot00000000000000.TH DIREWOLF 1 .SH NAME direwolf \- Soundcard TNC for packet radio. .SH SYNOPSIS .B direwolf [ \fIoptions\fR ] [ \- | \fBudp:\fR9999 ] .P The first audio channel can be streamed thru stdin or a UDP port. This is typically used with an SDR receiver. .SH DESCRIPTION \fBdirewolf\fR is a software "soundcard" modem/TNC and APRS encoder/decoder. It can be used stand-alone to receive APRS messages, as a digipeater, APRStt gateway, or Internet Gateway (IGate). It can also be used as a virtual TNC for other applications such as APRSIS32, UI-View32, Xastir, APRS-TW, YAAC, UISS, Linux AX25, SARTrack, RMS Express, and many others. .SH OPTIONS .TP .BI "-c " "file" Read configuration file from specified location rather than the default locations. .TP .BI "-l " "logdir" Generate daily log files in specified directory. Use "." for current directory. .TP .BI "-L " "logfile" Generate single log file with fixed name. .TP .BI "-r " "n" Audio sample rate per second for first channel. Default 44100. .TP .BI "-n " "n" Number of audio channels for first device. 1 or 2. Default 1. .TP .BI "-b " "n" Audio sample size for first channel. 8 or 16. Default 16. .TP .BI "-B " "n" Data rate in bits/sec for first channel. Standard values are 300, 1200, 9600. .PD 0 .RS .RS If < 600, tones are set to 1600 & 1800. .P If > 2400, K9NG/G3RUH scrambling is used. .P Otherwise, AFSK tones are set to 1200 & 2200. .RE .RE .PD .TP .BI "-D " "n" Divide audio sample by n for first channel. .TP .BI "-d " "x" Debug options. Specify one or more of the following in place of x. .PD 0 .RS .RS a = AGWPE network protocol client. .P k = KISS serial port client. .P n = Network KISS client. .P u = Display non-ASCII text in hexadecimal. .P p = Packet dump in hexadecimal. .P g = GPS interface. .P W = Waypoints for position or object reports. .P t = Tracker beacon. .P o = Output controls such as PTT and DCD. .P i = IGate .P h = Hamlib verbose level. Repeat for more. .RE .RE .PD .TP .BI "-q " "x" Quiet (suppress output). Specify one or more of the following in place of x. .PD 0 .RS .RS h = Heard line with the audio level. .P d = Decoding of APRS packets. .RE .RE .PD .TP .BI "-t " "n" Text colors. 1=normal, 0=disabled. .TP .B "-p " Enable pseudo terminal for KISS protocol. .TP .B "-x " Send Xmit level calibration tones. .TP .B "-U " Print UTF-8 test string and exit. .TP .B "-S " Print Symbol tables and exit. .TP .BI "-a " "n" Report audio device statistics each n seconds. .SH EXAMPLES gqrx (2.3 and later) has the ability to send streaming audio through a UDP socket to another application for further processing. direwolf can listen over a UDP port with options like this: .RS .P direwolf \-n 1 \-r 48000 \-b 16 udp:7355 .RE .P Other SDR applications might produce audio on stdout so it is convenient to pipe into the next application. In this example, the final "-" means read from stdin. .RS .P rtl_fm \-f 144.39M \-o 4 \- | direwolf \-n 1 \-r 24000 \-b 16 \- .RE .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/man1/gen_packets.1000066400000000000000000000051031347750676600173360ustar00rootroot00000000000000.TH GEN_PACKETS 1 .SH NAME gen_packets \- Generate audio file for AX.25 frames. .SH SYNOPSIS .B gen_packets \-o .I wav-file-out [ \fIoptions\fR ] [ \fItext-file\fR | \- ] .RS .P \fIwav-file-out\fR is the result. The \-o option is required. .P \fItext-file\fR may contain AX.25 packets in the standard monitoring format. Use "-" to read from stdin. If not specified, a default builtin message will be used. .RE .SH DESCRIPTION \fBgen_packets\fR is a test application which converts text to AX.25 audio for testing packet decoders. It is very flexible allowing a wide range of audio sample rates, data speeds, and AFSK tones. It will even generate the scrambled signals commonly used for 9600 baud operation. .SH OPTIONS .TP .BI "-a " "n" Signal amplitude in range of 0-200%. Default 50. Note that 100% is corresponds to signal peaks of +/- 16383 so we have plenty of headroom to avoid saturation. .TP .BI "-b " "n" Bits / second for data. Default is 1200. .TP .BI "-B " "n" Bits / second for data. Proper modem selected for 300, 1200, 9600. .TP .BI "-g" Scrambled baseband rather than AFSK. .TP .BI "-m " "n" Mark frequency. Default is 1200. .TP .BI "-s " "n" Space frequency. Default is 2200. .TP .BI "-r " "n" Audio sample Rate. Default is 44100. .TP .BI "-n " "n" Generate specified number of frames with increasing noise. (For built-in message only.) .TP .BI "-o " "file" Send output to .wav file. .TP .B "-8" 8 bit audio rather than 16. .TP .B "-2" 2 channels of audio rather than 1. .SH EXAMPLES .P .B gen_packets \-o x.wav .P .RS With all defaults, a built-in test message is generated with standard Bell 202 tones used for packet radio on ordinary VHF FM transceivers. .RE .P .B gen_packets \-o x.wav \-g \-b 9600 .PD 0 .P .PD .B gen_packets \-o x.wav \-B 9600 .P .RS Both of these are equivalent. "-B 9600" automatically selects scrambled baseband rather than AFSK. .RE .P .B gen_packets \-o x.wav \-m 1600 \-s 1800 \-b 300 .PD 0 .P .PD .B gen_packets \-o x.wav \-B 300 .P .RS Both of these generate 200 Hz shift, 300 baud, suitable for HF SSB transceiver. .RE .P .B echo \-n 'WB2OSZ>WORLD:Hello, world!' | gen_packets \-a 25 \-o x.wav \- .PD 0 .P .PD .B atest x.wav .P .RS Read message from stdin and put quarter volume sound into the file x.wav. Decode the sound file. .RE .P .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/man1/kissutil.1000066400000000000000000000033421347750676600167250ustar00rootroot00000000000000.TH KISSUTIL 1 .SH NAME kissutil \- KISS TNC troubleshooting and Application Interface. .SH SYNOPSIS .B kissutil [ \fIoptions\fR ] .SH DESCRIPTION \fBkissutil\fR can be used interactively for troubleshooting a KISS TNC. It is usable with direwolf and other generic KISS TNCs connected to a serial port. It can also be used as an application interface where each side places files in a directory for the other to process. See User Guide for more details. .SH OPTIONS .TP .BI "-h " "host" Hostname or IP address for a TCP KISS TNC. Default is localhost. .TP .BI "-p " "port" A number may be specified for a TCP port other than the default 8001. If not a number, it is considered to be a serial port name such as /dev/ttyS0 or COM3. .TP .BI "-s " "speed" Speed for serial port. e.g. 9600. .TP .BI "-o " "rec-directory" For each received frame, a new file is created here. It is expected that some other application will process files in this directory then delete them. .TP .BI "-T " "format" Each received frame will be preceded by a timestamp in the specified format. See strftime documentation for a description of the format string. Example: %H:%M:%S for current time in hours, minutes, seconds. .TP .BI "-f " "xmit-directory" Files in this directory are transmited and deleted. Another application places a file here when it wants something to be transmitted. .TP .BI "-v " Verbose - Display the KISS frames going to and from the TNC. .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/man1/ll2utm.1000066400000000000000000000013311347750676600162710ustar00rootroot00000000000000.TH LL2UTM 1 .SH NAME ll2utm \- Convert Latitude and Longitude to UTM coordinates. .SH SYNOPSIS .B ll2utm .I latitude longitude .P Latitude and longitude are in decimal degrees. Use negative for south or west. .SH DESCRIPTION \fBll2utm\fR converts Latitude and Longitude to UTM coordinates. .SH OPTIONS .TP None. .SH EXAMPLES .P .B ll2utm 42.619 -71.34717 .P zone = 19T, easting = 307504, northing = 4721177 .P .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/man1/log2gpx.1000066400000000000000000000017621347750676600164440ustar00rootroot00000000000000.TH LOG2GPX 1 .SH NAME log2gpx \- Convert Dire Wolf log files to GPX format. .SH SYNOPSIS .B log2gpx [ \fIfile\fR ... ] .P The command line can contain one or more log file names. If no files are specified, stdin is used. .P The result is always written to stdout. Redirect stdout to save results to a file. .SH DESCRIPTION \fBlog2gpx\fR converts Dire Wolf log files to the GPX format used by many mapping applications. .P Stationary entities are converted to waypoints. Moving entities are converted to tracks. .SH OPTIONS .TP None. .SH EXAMPLES .P .B direwolf -l logdir .P .B log2gpx logdir/* > everybody.gpx .P .B egrep -e '^[^,]+,[^,]+,[^,]+,WB2OSZ,' logdir/* | log2gpx > justme.gpx .P .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/man1/text2tt.1000066400000000000000000000027471347750676600165040ustar00rootroot00000000000000.TH TEXT2TT 1 .SH NAME text2tt \- Convert text to Touch Tone representation .SH SYNOPSIS .B text2tt .I text ... .P .SH DESCRIPTION \fBtext2tt\fR converts text to the Touch Tone sequence used by APRStt. There are two types of encoding: .RS .HP .BR "Multi-Press" " - Used for comments." .RS .P Letters are represented by one or more presses of the same key depending on their order listed on the button. e.g. Press 5 key once for J, twice for K, thrice for L. .P To specify a digit use the number of letters listed on the button plus one. e.g. Press 5 key four times to get digit 5. When two characters in a row use the same key, use the "A" key as a separator. .RE .P .BR "Two-Key" " - Used for callsigns." .RS .P Digits are represented by a single key press. .P Letters (or space) are represented by the corresponding key followed by A, B, C, or D depending on the order of the letter in the order listed. .RE .RE .P This application will convert using both methods. .SH OPTIONS .TP None. .SH EXAMPLES .P .B text2tt abcdefg 0123 .P .PD 0 .P Push buttons for multi-press method: .P "2A22A2223A33A33340A00122223333" .P Push buttons for two-key method: .P "2A2B2C3A3B3C4A0A0123" .PD .P .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/man1/tt2text.1000066400000000000000000000030271347750676600164740ustar00rootroot00000000000000.TH TT2TEXT 1 .SH NAME tt2text \- Convert Touch Tone sequence to text .SH SYNOPSIS .B tt2text .I touch-tone-squence .P .SH DESCRIPTION \fBtt2text\fR converts a Touch Tone squence to text. There are two types of encoding: .RS .HP .BR "Multi-Press" " - Used for comments." .RS .P Letters are represented by one or more presses of the same key depending on their order listed on the button. e.g. Press 5 key once for J, twice for K, thrice for L. .P To specify a digit use the number of letters listed on the button plus one. e.g. Press 5 key four times to get digit 5. When two characters in a row use the same key, use the "A" key as a separator. .RE .P .BR "Two-Key" " - Used for callsigns." .RS .P Digits are represented by a single key press. .P Letters (or space) are represented by the corresponding key followed by A, B, C, or D depending on the order of the letter in the order listed. .RE .RE .P This application will try to decode the sequence using both methods. .SH OPTIONS .TP None. .SH EXAMPLES .P .B tt2text 2A22A2223A33A33340A00122223333 .P .PD 0 .P Could be either type of encoding. .P Decoded text from multi-press method: .P "ABCDEFG 0123" .P Decoded text from two-key method: .P "A2A222D3D3334 00122223333" .PD .P .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/man1/utm2ll.1000066400000000000000000000014541347750676600162770ustar00rootroot00000000000000.TH UTM2LL 1 .SH NAME utm2ll \- Convert UTM coordinates to Latitude and Longitude. .SH SYNOPSIS .B utm2ll .I zone easting northing .RS .P \fIzone\fR is UTM zone 1 thru 60 with optional latitudinal band. .P \fIeasting\fR is x coordinate in meters. .P \fInorthing\fR is y coordinate in meters. .RE .SH DESCRIPTION \fBll2utm\fR converts UTM coordinates to latitude and longitude. .SH OPTIONS .TP None. .SH EXAMPLES .P .B utm2ll 19T 306130 4726010 .P latitude = 42.662139, longitude = -71.365553 .P .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll direwolf-1.5+dfsg/mgn_icon.h000066400000000000000000000236571347750676600161150ustar00rootroot00000000000000 /* * MGN_icon.h * * Waypoint icon codes for use in the $PMGNWPL sentence. * * Derived from Data Transmission Protocol For Magellan Products - version 2.11, March 2003 * * http://www.gpsinformation.org/mag-proto-2-11.pdf * * * That's 13 years ago. There should be something newer available but I can't find it. * * The is based on the newer models at the time. Earlier models had shorter incompatible icon lists. */ #define MGN_crossed_square "a" #define MGN_box "b" #define MGN_house "c" #define MGN_aerial "d" #define MGN_airport "e" #define MGN_amusement_park "f" #define MGN_ATM "g" #define MGN_auto_repair "h" #define MGN_boating "I" #define MGN_camping "j" #define MGN_exit_ramp "k" #define MGN_first_aid "l" #define MGN_nav_aid "m" #define MGN_buoy "n" #define MGN_fuel "o" #define MGN_garden "p" #define MGN_golf "q" #define MGN_hotel "r" #define MGN_hunting_fishing "s" #define MGN_large_city "t" #define MGN_lighthouse "u" #define MGN_major_city "v" #define MGN_marina "w" #define MGN_medium_city "x" #define MGN_museum "y" #define MGN_obstruction "z" #define MGN_park "aa" #define MGN_resort "ab" #define MGN_restaurant "ac" #define MGN_rock "ad" #define MGN_scuba "ae" #define MGN_RV_service "af" #define MGN_shooting "ag" #define MGN_sight_seeing "ah" #define MGN_small_city "ai" #define MGN_sounding "aj" #define MGN_sports_arena "ak" #define MGN_tourist_info "al" #define MGN_truck_service "am" #define MGN_winery "an" #define MGN_wreck "ao" #define MGN_zoo "ap" /* * Mapping from APRS symbols to Magellan. * * This is a bit of a challenge because there * are no icons for moving objects. * We can use airport for flying things but * what about wheeled transportation devices? */ // TODO: NEEDS MORE WORK!!! #define MGN_default MGN_crossed_square #define SYMTAB_SIZE 95 static const char mgn_primary_symtab[SYMTAB_SIZE][3] = { MGN_default, // 00 --no-symbol-- MGN_default, // ! 01 Police, Sheriff MGN_default, // " 02 reserved (was rain) MGN_aerial, // # 03 DIGI (white center) MGN_default, // $ 04 PHONE MGN_aerial, // % 05 DX CLUSTER MGN_aerial, // & 06 HF GATEway MGN_airport, // ' 07 Small AIRCRAFT MGN_aerial, // ( 08 Mobile Satellite Station MGN_default, // ) 09 Wheelchair (handicapped) MGN_default, // * 10 SnowMobile MGN_default, // + 11 Red Cross MGN_default, // , 12 Boy Scouts MGN_house, // - 13 House QTH (VHF) MGN_default, // . 14 X MGN_default, // / 15 Red Dot MGN_default, // 0 16 # circle (obsolete) MGN_default, // 1 17 TBD MGN_default, // 2 18 TBD MGN_default, // 3 19 TBD MGN_default, // 4 20 TBD MGN_default, // 5 21 TBD MGN_default, // 6 22 TBD MGN_default, // 7 23 TBD MGN_default, // 8 24 TBD MGN_default, // 9 25 TBD MGN_default, // : 26 FIRE MGN_camping, // ; 27 Campground (Portable ops) MGN_default, // < 28 Motorcycle MGN_default, // = 29 RAILROAD ENGINE MGN_default, // > 30 CAR MGN_default, // ? 31 SERVER for Files MGN_default, // @ 32 HC FUTURE predict (dot) MGN_first_aid, // A 33 Aid Station MGN_aerial, // B 34 BBS or PBBS MGN_boating, // C 35 Canoe MGN_default, // D 36 MGN_default, // E 37 EYEBALL (Eye catcher!) MGN_default, // F 38 Farm Vehicle (tractor) MGN_default, // G 39 Grid Square (6 digit) MGN_hotel, // H 40 HOTEL (blue bed symbol) MGN_aerial, // I 41 TcpIp on air network stn MGN_default, // J 42 MGN_default, // K 43 School MGN_default, // L 44 PC user MGN_default, // M 45 MacAPRS MGN_aerial, // N 46 NTS Station MGN_airport, // O 47 BALLOON MGN_default, // P 48 Police MGN_default, // Q 49 TBD MGN_RV_service, // R 50 REC. VEHICLE MGN_airport, // S 51 SHUTTLE MGN_default, // T 52 SSTV MGN_default, // U 53 BUS MGN_default, // V 54 ATV MGN_default, // W 55 National WX Service Site MGN_default, // X 56 HELO MGN_boating, // Y 57 YACHT (sail) MGN_default, // Z 58 WinAPRS MGN_default, // [ 59 Human/Person (HT) MGN_default, // \ 60 TRIANGLE(DF station) MGN_default, // ] 61 MAIL/PostOffice(was PBBS) MGN_airport, // ^ 62 LARGE AIRCRAFT MGN_default, // _ 63 WEATHER Station (blue) MGN_aerial, // ` 64 Dish Antenna MGN_default, // a 65 AMBULANCE MGN_default, // b 66 BIKE MGN_default, // c 67 Incident Command Post MGN_default, // d 68 Fire dept MGN_zoo, // e 69 HORSE (equestrian) MGN_default, // f 70 FIRE TRUCK MGN_airport, // g 71 Glider MGN_default, // h 72 HOSPITAL MGN_default, // i 73 IOTA (islands on the air) MGN_default, // j 74 JEEP MGN_default, // k 75 TRUCK MGN_default, // l 76 Laptop MGN_aerial, // m 77 Mic-E Repeater MGN_default, // n 78 Node (black bulls-eye) MGN_default, // o 79 EOC MGN_zoo, // p 80 ROVER (puppy, or dog) MGN_default, // q 81 GRID SQ shown above 128 m MGN_aerial, // r 82 Repeater MGN_default, // s 83 SHIP (pwr boat) MGN_default, // t 84 TRUCK STOP MGN_default, // u 85 TRUCK (18 wheeler) MGN_default, // v 86 VAN MGN_default, // w 87 WATER station MGN_aerial, // x 88 xAPRS (Unix) MGN_aerial, // y 89 YAGI @ QTH MGN_default, // z 90 TBD MGN_default, // { 91 MGN_default, // | 92 TNC Stream Switch MGN_default, // } 93 MGN_default }; // ~ 94 TNC Stream Switch static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = { MGN_default, // 00 --no-symbol-- MGN_default, // ! 01 EMERGENCY (!) MGN_default, // " 02 reserved MGN_aerial, // # 03 OVERLAY DIGI (green star) MGN_ATM, // $ 04 Bank or ATM (green box) MGN_default, // % 05 Power Plant with overlay MGN_aerial, // & 06 I=Igte IGate R=RX T=1hopTX 2=2hopTX MGN_default, // ' 07 Crash (& now Incident sites) MGN_default, // ( 08 CLOUDY (other clouds w ovrly) MGN_aerial, // ) 09 Firenet MEO, MODIS Earth Obs. MGN_default, // * 10 SNOW (& future ovrly codes) MGN_default, // + 11 Church MGN_default, // , 12 Girl Scouts MGN_house, // - 13 House (H=HF) (O = Op Present) MGN_default, // . 14 Ambiguous (Big Question mark) MGN_default, // / 15 Waypoint Destination MGN_default, // 0 16 CIRCLE (E/I/W=IRLP/Echolink/WIRES) MGN_default, // 1 17 MGN_default, // 2 18 MGN_default, // 3 19 MGN_default, // 4 20 MGN_default, // 5 21 MGN_default, // 6 22 MGN_default, // 7 23 MGN_aerial, // 8 24 802.11 or other network node MGN_fuel, // 9 25 Gas Station (blue pump) MGN_default, // : 26 Hail (& future ovrly codes) MGN_park, // ; 27 Park/Picnic area MGN_default, // < 28 ADVISORY (one WX flag) MGN_default, // = 29 APRStt Touchtone (DTMF users) MGN_default, // > 30 OVERLAYED CAR MGN_tourist_info, // ? 31 INFO Kiosk (Blue box with ?) MGN_default, // @ 32 HURICANE/Trop-Storm MGN_box, // A 33 overlayBOX DTMF & RFID & XO MGN_default, // B 34 Blwng Snow (& future codes) MGN_boating, // C 35 Coast Guard MGN_default, // D 36 Drizzle (proposed APRStt) MGN_default, // E 37 Smoke (& other vis codes) MGN_default, // F 38 Freezng rain (&future codes) MGN_default, // G 39 Snow Shwr (& future ovrlys) MGN_default, // H 40 Haze (& Overlay Hazards) MGN_default, // I 41 Rain Shower MGN_default, // J 42 Lightening (& future ovrlys) MGN_default, // K 43 Kenwood HT (W) MGN_lighthouse, // L 44 Lighthouse MGN_default, // M 45 MARS (A=Army,N=Navy,F=AF) MGN_buoy, // N 46 Navigation Buoy MGN_airport, // O 47 Rocket MGN_default, // P 48 Parking MGN_default, // Q 49 QUAKE MGN_restaurant, // R 50 Restaurant MGN_aerial, // S 51 Satellite/Pacsat MGN_default, // T 52 Thunderstorm MGN_default, // U 53 SUNNY MGN_nav_aid, // V 54 VORTAC Nav Aid MGN_default, // W 55 # NWS site (NWS options) MGN_default, // X 56 Pharmacy Rx (Apothicary) MGN_aerial, // Y 57 Radios and devices MGN_default, // Z 58 MGN_default, // [ 59 W.Cloud (& humans w Ovrly) MGN_default, // \ 60 New overlayable GPS symbol MGN_default, // ] 61 MGN_airport, // ^ 62 # Aircraft (shows heading) MGN_default, // _ 63 # WX site (green digi) MGN_default, // ` 64 Rain (all types w ovrly) MGN_aerial, // a 65 ARRL, ARES, WinLINK MGN_default, // b 66 Blwng Dst/Snd (& others) MGN_default, // c 67 CD triangle RACES/SATERN/etc MGN_default, // d 68 DX spot by callsign MGN_default, // e 69 Sleet (& future ovrly codes) MGN_default, // f 70 Funnel Cloud MGN_default, // g 71 Gale Flags MGN_default, // h 72 Store. or HAMFST Hh=HAM store MGN_box, // i 73 BOX or points of Interest MGN_default, // j 74 WorkZone (Steam Shovel) MGN_default, // k 75 Special Vehicle SUV,ATV,4x4 MGN_default, // l 76 Areas (box,circles,etc) MGN_default, // m 77 Value Sign (3 digit display) MGN_default, // n 78 OVERLAY TRIANGLE MGN_default, // o 79 small circle MGN_default, // p 80 Prtly Cldy (& future ovrlys) MGN_default, // q 81 MGN_default, // r 82 Restrooms MGN_default, // s 83 OVERLAY SHIP/boat (top view) MGN_default, // t 84 Tornado MGN_default, // u 85 OVERLAYED TRUCK MGN_default, // v 86 OVERLAYED Van MGN_default, // w 87 Flooding MGN_wreck, // x 88 Wreck or Obstruction ->X<- MGN_default, // y 89 Skywarn MGN_default, // z 90 OVERLAYED Shelter MGN_default, // { 91 Fog (& future ovrly codes) MGN_default, // | 92 TNC Stream Switch MGN_default, // } 93 MGN_default }; // ~ 94 TNC Stream Switch direwolf-1.5+dfsg/mheard.c000066400000000000000000000542261347750676600155530ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * File: mheard.c * * Purpose: Maintain a list of all stations heard. * * Description: This was added for IGate statistics and checking if a user is local * but would also be useful for the AGW network protocol 'H' request. * * This application has no GUI and is not interactive so * I'm not sure what else we might do with the information. * * Why mheard instead of just heard? The KPC-3+ has an MHEARD command * to list stations heard. I guess that stuck in my mind. * It should be noted that here "heard" refers to the AX.25 source station. * Before printing the received packet, the "heard" line refers to who * we heard over the radio. This would be the digipeater with "*" after * its name. * * Future Ideas: Someone suggested using SQLite to store the information * so other applications could access it. * *------------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "textcolor.h" #include "decode_aprs.h" #include "ax25_pad.h" #include "hdlc_rec2.h" // for retry_t #include "mheard.h" #include "latlong.h" // This is getting updated from two different threads so we need a critical region // for adding new nodes. static dw_mutex_t mheard_mutex; // I think we can get away without a critical region for reading if we follow these // rules: // // (1) When adding a new node, make sure it is complete, including next ptr, // before adding it to the list. // (2) Update the start of list pointer last. // (2) Nothing gets deleted. // If we ever decide to start cleaning out very old data, all access would then // need to use the mutex. /* * Information for each station heard over the radio or from Internet Server. */ typedef struct mheard_s { struct mheard_s *pnext; // Pointer to next in list. char callsign[AX25_MAX_ADDR_LEN]; // Callsign from the AX.25 source field. int count; // Number of times heard. // We don't use this for anything. // Just something potentially interesting when looking at data dump. int chan; // Most recent channel where heard. int num_digi_hops; // Number of digipeater hops before we heard it. // over radio. Zero when heard directly. time_t last_heard_rf; // Timestamp when last heard over the radio. time_t last_heard_is; // Timestamp when last heard from Internet Server. double dlat, dlon; // Last position. G_UNKNOWN for unknown. int msp; // Allow message sender positon report. // When non zero, an IS>RF position report is allowed. // Then decremented. // What else would be useful? // The AGW protocol is by channel and returns // first heard in addition to last heard. } mheard_t; /* * The list could be quite long and we hit this a lot so use a hash table. */ #define MHEARD_HASH_SIZE 73 // Best if prime number. static mheard_t *mheard_hash[MHEARD_HASH_SIZE]; static inline int hash_index(char *callsign) { int n = 0; char *p = callsign; while (*p != '\0') { n += *p++; } return (n % MHEARD_HASH_SIZE); } static mheard_t *mheard_ptr(char *callsign) { int n = hash_index(callsign); mheard_t *p = mheard_hash[n]; while (p != NULL) { if (strcmp(callsign,p->callsign) == 0) return (p); p = p->pnext; } return (NULL); } static int mheard_debug = 0; /*------------------------------------------------------------------ * * Function: mheard_init * * Purpose: Initialization at start of application. * * Inputs: debug - Debug level. * * Description: Clear pointer table. * Save debug level for later use. * *------------------------------------------------------------------*/ void mheard_init (int debug) { int i; mheard_debug = debug; for (i = 0; i < MHEARD_HASH_SIZE; i++) { mheard_hash[i] = NULL; } /* * Mutex to coordinate adding new nodes. */ dw_mutex_init(&mheard_mutex); } /* end mheard_init */ /*------------------------------------------------------------------ * * Function: mheard_dump * * Purpose: Print list of stations heard for debugging. * *------------------------------------------------------------------*/ /* convert some time in past to hours:minutes text format. */ static void age(char *result, time_t now, time_t t) { int s, h, m; if (t == 0) { strcpy (result, "- "); return; } s = (int)(now - t); m = s / 60; h = m / 60; m -= h * 60; sprintf (result, "%4d:%02d", h, m); } /* Convert latitude, longitude to text or - if not defined. */ static void latlon (char * result, double dlat, double dlon) { if (dlat != G_UNKNOWN && dlon != G_UNKNOWN) { sprintf (result, "%6.2f %7.2f", dlat, dlon); } else { strcpy (result, " - - "); } } /* Compare last heard time for use with qsort. */ #define MAXX(x,y) (((x)>(y))?(x):(y)) static int compar(const void *a, const void *b) { mheard_t *ma = *((mheard_t **)a); mheard_t *mb = *((mheard_t **)b); time_t ta = MAXX(ma->last_heard_rf, ma->last_heard_is); time_t tb = MAXX(mb->last_heard_rf, mb->last_heard_is); return (tb - ta); } #define MAXDUMP 1000 static void mheard_dump (void) { int i; mheard_t *mptr; time_t now = time(NULL); char stuff[80]; char rf[16]; // hours:minutes char is[16]; char position[40]; mheard_t *station[MAXDUMP]; int num_stations = 0; /* Get linear array of node pointers so they can be sorted easily. */ num_stations = 0; for (i = 0; i < MHEARD_HASH_SIZE; i++) { for (mptr = mheard_hash[i]; mptr != NULL; mptr = mptr->pnext) { if (num_stations < MAXDUMP) { station[num_stations] = mptr; num_stations++; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("mheard_dump - max number of stations exceeded.\n"); } } } /* Sort most recently heard to the top then print. */ qsort (station, num_stations, sizeof(mheard_t *), compar); text_color_set(DW_COLOR_DEBUG); dw_printf ("callsign cnt chan hops RF IS lat long msp\n"); for (i = 0; i < num_stations; i++) { mptr = station[i]; age (rf, now, mptr->last_heard_rf); age (is, now, mptr->last_heard_is); latlon (position, mptr->dlat, mptr->dlon); snprintf (stuff, sizeof(stuff), "%-9s %3d %d %d %7s %7s %s %d\n", mptr->callsign, mptr->count, mptr->chan, mptr->num_digi_hops, rf, is, position, mptr->msp); dw_printf ("%s", stuff); } } /* end mheard_dump */ /*------------------------------------------------------------------ * * Function: mheard_save_rf * * Purpose: Save information about station heard over the radio. * * Inputs: chan - Radio channel where heard. * * A - Exploded information from APRS packet. * * pp - Received packet object. * * alevel - audio level. * * retries - Amount of effort to get a good CRC. * * Description: Calling sequence was copied from "log_write." * It has a lot more than what we currently keep but the * hooks are there so it will be easy to capture additional * information when the need arises. * *------------------------------------------------------------------*/ void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries) { time_t now = time(NULL); char source[AX25_MAX_ADDR_LEN]; int hops; mheard_t *mptr; ax25_get_addr_with_ssid (pp, AX25_SOURCE, source); /* * How many digipeaters has it gone thru before we hear it? * We can count the number of digi addresses that are marked as "has been used." * This is not always accurate because there is inconsistency in digipeater behavior. * The base AX.25 spec seems clear in this regard. The used digipeaters should * should accurately reflict the path taken by the packet. Sometimes we see excess * stuff in there. Even when you understand what is going on, it is still an ambiguous * situation. Look for my rant in the User Guide. */ hops = ax25_get_heard(pp) - AX25_SOURCE; mptr = mheard_ptr(source); if (mptr == NULL) { int i; /* * Not heard before. Add it. */ if (mheard_debug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("mheard_save_rf: %s %d - added new\n", source, hops); } mptr = calloc(sizeof(mheard_t),1); strlcpy (mptr->callsign, source, sizeof(mptr->callsign)); mptr->count = 1; mptr->chan = chan; mptr->num_digi_hops = hops; mptr->last_heard_rf = now; mptr->dlat = G_UNKNOWN; mptr->dlon = G_UNKNOWN; i = hash_index(source); dw_mutex_lock (&mheard_mutex); mptr->pnext = mheard_hash[i]; // before inserting into list. mheard_hash[i] = mptr; dw_mutex_unlock (&mheard_mutex); } else { /* * Update existing entry. * The only tricky part here is that we might hear the same transmission * several times. First direct, then thru various digipeater paths. * We are interested in the shortest path if heard very recently. */ if (hops > mptr->num_digi_hops && (int)(now - mptr->last_heard_rf) < 15) { if (mheard_debug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("mheard_save_rf: %s %d - skip because hops was %d %d seconds ago.\n", source, hops, mptr->num_digi_hops, (int)(now - mptr->last_heard_rf) ); } } else { if (mheard_debug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("mheard_save_rf: %s %d - update time, was %d hops %d seconds ago.\n", source, hops, mptr->num_digi_hops, (int)(now - mptr->last_heard_rf)); } mptr->count++; mptr->chan = chan; mptr->num_digi_hops = hops; mptr->last_heard_rf = now; } } if (A->g_lat != G_UNKNOWN && A->g_lon != G_UNKNOWN) { mptr->dlat = A->g_lat; mptr->dlon = A->g_lon; } if (mheard_debug >= 2) { int limit = 10; // normally 30 or 60. more frequent when debugging. text_color_set(DW_COLOR_DEBUG); dw_printf ("mheard debug, %d min, DIR_CNT=%d,LOC_CNT=%d,RF_CNT=%d\n", limit, mheard_count(0,limit), mheard_count(2,limit), mheard_count(8,limit)); } if (mheard_debug) { mheard_dump (); } } /* end mheard_save_rf */ /*------------------------------------------------------------------ * * Function: mheard_save_is * * Purpose: Save information about station heard via Internet Server. * * Inputs: ptext - Packet in monitoring text form as sent by the Internet server. * * Any trailing CRLF should have been removed. * Typical examples: * * KA1BTK-5>APDR13,TCPIP*,qAC,T2IRELAND:=4237.62N/07040.68W$/A=-00054 http://aprsdroid.org/ * N1HKO-10>APJI40,TCPIP*,qAC,N1HKO-JS:APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmAT3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile * * Notice how the final address in the header might not * be a valid AX.25 address. We see a 9 character address * (with no ssid) and an ssid of two letters. * * The "q construct" ( http://www.aprs-is.net/q.aspx ) provides * a clue about the journey taken but I don't think we care here. * * All we should care about here is the the source address. * * Description: * *------------------------------------------------------------------*/ void mheard_save_is (char *ptext) { packet_t pp; time_t now = time(NULL); char source[AX25_MAX_ADDR_LEN]; mheard_t *mptr; /* * Try to parse it into a packet object. * This will contain "q constructs" and we might see an address * with two alphnumeric characters in the SSID so we must use * the non-strict parsing. * * Bug: Up to 8 digipeaters are allowed in radio format. * There is a potential of finding a larger number here. */ pp = ax25_from_text(ptext, 0); if (pp == NULL) { if (mheard_debug) { text_color_set(DW_COLOR_ERROR); dw_printf ("mheard_save_is: Could not parse message from server.\n"); dw_printf ("%s\n", ptext); } return; } ax25_get_addr_with_ssid (pp, AX25_SOURCE, source); mptr = mheard_ptr(source); if (mptr == NULL) { int i; /* * Not heard before. Add it. */ if (mheard_debug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("mheard_save_is: %s - added new\n", source); } mptr = calloc(sizeof(mheard_t),1); strlcpy (mptr->callsign, source, sizeof(mptr->callsign)); mptr->count = 1; mptr->last_heard_is = now; mptr->dlat = G_UNKNOWN; mptr->dlon = G_UNKNOWN; i = hash_index(source); dw_mutex_lock (&mheard_mutex); mptr->pnext = mheard_hash[i]; // before inserting into list. mheard_hash[i] = mptr; dw_mutex_unlock (&mheard_mutex); } else { /* Already there. UPdate last heard from IS time. */ if (mheard_debug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("mheard_save_is: %s - update time, was %d seconds ago.\n", source, (int)(now - mptr->last_heard_rf)); } mptr->count++; mptr->last_heard_is = now; } // Is is desirable to save any location in this case? // I don't think it would help. // The whole purpose of keeping the location is for message sending filter. // We wouldn't want to try sending a message to the station if we didn't hear it over the radio. // On the other hand, I don't think it would hurt. // The filter always includes a time since last heard over the radi. if (mheard_debug >= 2) { int limit = 10; // normally 30 or 60 text_color_set(DW_COLOR_DEBUG); dw_printf ("mheard debug, %d min, DIR_CNT=%d,LOC_CNT=%d,RF_CNT=%d\n", limit, mheard_count(0,limit), mheard_count(2,limit), mheard_count(8,limit)); } if (mheard_debug) { mheard_dump (); } ax25_delete (pp); } /* end mheard_save_is */ /*------------------------------------------------------------------ * * Function: mheard_count * * Purpose: Count local stations for IGate statistics report like this: * * pnext) { if (p->last_heard_rf >= since && p->num_digi_hops <= max_hops) { count++; } } } if (mheard_debug == 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("mheard_count(<= %d digi hops, last %d minutes) returns %d\n", max_hops, time_limit, count); } return (count); } /* end mheard_count */ /*------------------------------------------------------------------ * * Function: mheard_was_recently_nearby * * Purpose: Determine whether given station was heard recently on the radio. * * Inputs: role - "addressee" or "source" if debug out is desired. * Otherwise empty string. * * callsign - Callsign for station. * * time_limit - Include only stations heard within this many minutes. * Typically 30 or 60. * * max_hops - Include only stations heard with this number of * digipeater hops or less. For reporting, we might use: * * dlat, dlon, km - Include only stations within distance of location. * Not used if G_UNKNOWN is supplied. * * Returns: 1 for true, 0 for false. * *------------------------------------------------------------------*/ int mheard_was_recently_nearby (char *role, char *callsign, int time_limit, int max_hops, double dlat, double dlon, double km) { mheard_t *mptr; time_t now; int heard_ago; if (role != NULL && strlen(role) > 0) { text_color_set(DW_COLOR_INFO); if (dlat != G_UNKNOWN && dlon != G_UNKNOWN && km != G_UNKNOWN) { dw_printf ("Was message %s %s heard in the past %d minutes, with %d or fewer digipeater hops, and within %.1f km of %.2f %.2f?\n", role, callsign, time_limit, max_hops, km, dlat, dlon); } else { dw_printf ("Was message %s %s heard in the past %d minutes, with %d or fewer digipeater hops?\n", role, callsign, time_limit, max_hops); } } mptr = mheard_ptr(callsign); if (mptr == NULL || mptr->last_heard_rf == 0) { if (role != NULL && strlen(role) > 0) { text_color_set(DW_COLOR_INFO); dw_printf ("No, we have not heard %s over the radio.\n", callsign); } return (0); } now = time(NULL); heard_ago = (int)(now - mptr->last_heard_rf) / 60; if (heard_ago > time_limit) { if (role != NULL && strlen(role) > 0) { text_color_set(DW_COLOR_INFO); dw_printf ("No, %s was last heard over the radio %d minutes ago with %d digipeater hops.\n", callsign, heard_ago, mptr->num_digi_hops); } return (0); } if (mptr->num_digi_hops > max_hops) { if (role != NULL && strlen(role) > 0) { text_color_set(DW_COLOR_INFO); dw_printf ("No, %s was last heard over the radio with %d digipeater hops %d minutes ago.\n", callsign, mptr->num_digi_hops, heard_ago); } return (0); } // Apply physical distance check? if (dlat != G_UNKNOWN && dlon != G_UNKNOWN && km != G_UNKNOWN && mptr->dlat != G_UNKNOWN && mptr->dlon != G_UNKNOWN) { double dist = ll_distance_km (mptr->dlat, mptr->dlon, dlat, dlon); if (dist > km) { if (role != NULL && strlen(role) > 0) { text_color_set(DW_COLOR_INFO); dw_printf ("No, %s was %.1f km away although it was %d digipeater hops %d minutes ago.\n", callsign, dist, mptr->num_digi_hops, heard_ago); } return (0); } else { if (role != NULL && strlen(role) > 0) { text_color_set(DW_COLOR_INFO); dw_printf ("Yes, %s last heard over radio %d minutes ago, %d digipeater hops. Last location %.1f km away.\n", callsign, heard_ago, mptr->num_digi_hops, dist); } return (1); } } // Passed all the tests. if (role != NULL && strlen(role) > 0) { text_color_set(DW_COLOR_INFO); dw_printf ("Yes, %s last heard over radio %d minutes ago, %d digipeater hops.\n", callsign, heard_ago, mptr->num_digi_hops); } return (1); } /* end mheard_was_recently_nearby */ /*------------------------------------------------------------------ * * Function: mheard_set_msp * * Purpose: Set the "message sender position" count for specified station. * * Inputs: callsign - Callsign for station which sent the "message." * * num - Number of position reports to allow. Typically 1. * *------------------------------------------------------------------*/ void mheard_set_msp (char *callsign, int num) { mheard_t *mptr; mptr = mheard_ptr(callsign); if (mptr != NULL) { mptr->msp = num; if (mheard_debug) { text_color_set(DW_COLOR_INFO); dw_printf ("MSP for %s set to %d\n", callsign, num); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error: Can't find %s to set MSP.\n", callsign); } } /* end mheard_set_msp */ /*------------------------------------------------------------------ * * Function: mheard_get_msp * * Purpose: Get the "message sender position" count for specified station. * * Inputs: callsign - Callsign for station which sent the "message." * * Returns: The cound for the specified station. * 0 if not found. * *------------------------------------------------------------------*/ int mheard_get_msp (char *callsign) { mheard_t *mptr; mptr = mheard_ptr(callsign); if (mptr != NULL) { if (mheard_debug) { text_color_set(DW_COLOR_INFO); dw_printf ("MSP for %s is %d\n", callsign, mptr->msp); } return (mptr->msp); // Should we have a time limit? } return (0); } /* end mheard_get_msp */ /* end mheard.c */ direwolf-1.5+dfsg/mheard.h000066400000000000000000000007561347750676600155570ustar00rootroot00000000000000 /* mheard.h */ #include "decode_aprs.h" // for decode_aprs_t void mheard_init (int debug); void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries); void mheard_save_is (char *ptext); int mheard_count (int max_hops, int time_limit); int mheard_was_recently_nearby (char *role, char *callsign, int time_limit, int max_hops, double dlat, double dlon, double km); void mheard_set_msp (char *callsign, int num); int mheard_get_msp (char *callsign);direwolf-1.5+dfsg/misc/000077500000000000000000000000001347750676600150715ustar00rootroot00000000000000direwolf-1.5+dfsg/misc/README-dire-wolf.txt000066400000000000000000000015521347750676600204600ustar00rootroot00000000000000 Files in this directory fill in the gaps missing for some operating systems. -------------------------------------- These are part of the standard C library for Linux and similar operating systems. For the Windows version we need to include our own copy. They were copied from Cygwin source. /usr/src/cygwin-1.7.10-1/newlib/libc/string/... strsep.c strtok_r.c -------------------------------------- This was also missing on Windows but available everywhere else. strcasestr.c -------------------------------------- The are used for the Linux and Windows versions. They should be part of the standard C library for OpenBSD, FreeBSD, Mac OS X. These are from OpenBSD. http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/net/tnftp/files/libnetbsd/strlcpy.c http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/net/tnftp/files/libnetbsd/strlcat.c strlcpy.c strlcat.c direwolf-1.5+dfsg/misc/strcasestr.c000066400000000000000000000045361347750676600174420ustar00rootroot00000000000000/*- * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Chris Torek. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ //#include #include #include /* * Find the first occurrence of find in s, ignore case. */ char * strcasestr(const char *s, const char *find) { char c, sc; size_t len; if ((c = *find++) != 0) { c = tolower((unsigned char)c); len = strlen(find); do { do { if ((sc = *s++) == 0) return (NULL); } while ((char)tolower((unsigned char)sc) != c); } while (strncasecmp(s, find, len) != 0); s--; } return ((char *)s); } direwolf-1.5+dfsg/misc/strlcat.c000066400000000000000000000075201347750676600167150ustar00rootroot00000000000000 /*------------------------------------------------------------------ * * Module: strlcat.c * * Purpose: Safe string functions to guard against buffer overflow. * * Description: The size of character strings, especially when coming from the * outside, can sometimes exceed a fixed size storage area. * * There was one case where a MIC-E format packet had an enormous * comment that exceeded an internal buffer of 256 characters, * resulting in a crash. * * We are not always meticulous about checking sizes to avoid overflow. * Use of these functions, instead of strcpy and strcat, should * help avoid issues. * * Orgin: From OpenBSD as the copyright notice indicates. * The GNU folks didn't think it was appropriate for inclusion * in glibc. https://lwn.net/Articles/507319/ * * Modifications: Added extra debug output when strings are truncated. * Not sure if I will leave this in the release version * or just let it happen silently. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include "textcolor.h" /* $NetBSD: strlcat.c,v 1.5 2014/10/31 18:59:32 spz Exp $ */ /* from NetBSD: strlcat.c,v 1.16 2003/10/27 00:12:42 lukem Exp */ /* from OpenBSD: strlcat.c,v 1.10 2003/04/12 21:56:39 millert Exp */ /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Appends src to string dst of size siz (unlike strncat, siz is the * full size of dst, not space left). At most siz-1 characters * will be copied. Always NUL terminates (unless siz <= strlen(dst)). * Returns strlen(src) + MIN(siz, strlen(initial dst)). * If retval >= siz, truncation occurred. */ #if DEBUG_STRL size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line) #else size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz) #endif { char *d = dst; const char *s = src; size_t n = siz; size_t dlen; size_t retval; #if DEBUG_STRL if (dst == NULL) { text_color_set (DW_COLOR_ERROR); dw_printf ("ERROR: strlcat dst is NULL. (%s %s %d)\n", file, func, line); return (0); } if (src == NULL) { text_color_set (DW_COLOR_ERROR); dw_printf ("ERROR: strlcat src is NULL. (%s %s %d)\n", file, func, line); return (0); } if (siz == 1 || siz == 4) { text_color_set (DW_COLOR_ERROR); dw_printf ("Suspicious strlcat siz. Is it using sizeof a pointer variable? (%s %s %d)\n", file, func, line); } #endif /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) { retval = dlen + strlen(s); goto the_end; } while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; retval = dlen + (s - src); /* count does not include NUL */ the_end: #if DEBUG_STRL if (retval >= siz) { text_color_set (DW_COLOR_ERROR); dw_printf ("WARNING: strlcat result length %d exceeds maximum length %d. (%s %s %d)\n", (int)retval, (int)(siz-1), file, func, line); } #endif return (retval); }direwolf-1.5+dfsg/misc/strlcpy.c000066400000000000000000000073021347750676600167370ustar00rootroot00000000000000 /*------------------------------------------------------------------ * * Module: strlcpy.c * * Purpose: Safe string functions to guard against buffer overflow. * * Description: The size of character strings, especially when coming from the * outside, can sometimes exceed a fixed size storage area. * * There was one case where a MIC-E format packet had an enormous * comment that exceeded an internal buffer of 256 characters, * resulting in a crash. * * We are not always meticulous about checking sizes to avoid overflow. * Use of these functions, instead of strcpy and strcat, should * help avoid issues. * * Orgin: From OpenBSD as the copyright notice indicates. * The GNU folks didn't think it was appropriate for inclusion * in glibc. https://lwn.net/Articles/507319/ * * Modifications: Added extra debug output when strings are truncated. * Not sure if I will leave this in the release version * or just let it happen silently. * *---------------------------------------------------------------*/ /* $NetBSD: strlcpy.c,v 1.5 2014/10/31 18:59:32 spz Exp $ */ /* from NetBSD: strlcpy.c,v 1.14 2003/10/27 00:12:42 lukem Exp */ /* from OpenBSD: strlcpy.c,v 1.7 2003/04/12 21:56:39 millert Exp */ /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "direwolf.h" #include #include #include "textcolor.h" /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ #if DEBUG_STRL size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line) #else size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz) #endif { char *d = dst; const char *s = src; size_t n = siz; size_t retval; #if DEBUG_STRL if (dst == NULL) { text_color_set (DW_COLOR_ERROR); dw_printf ("ERROR: strlcpy dst is NULL. (%s %s %d)\n", file, func, line); return (0); } if (src == NULL) { text_color_set (DW_COLOR_ERROR); dw_printf ("ERROR: strlcpy src is NULL. (%s %s %d)\n", file, func, line); return (0); } if (siz == 1 || siz == 4) { text_color_set (DW_COLOR_ERROR); dw_printf ("Suspicious strlcpy siz. Is it using sizeof a pointer variable? (%s %s %d)\n", file, func, line); } #endif /* Copy as many bytes as will fit */ if (n != 0 && --n != 0) { do { if ((*d++ = *s++) == 0) break; } while (--n != 0); } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } retval = s - src - 1; /* count does not include NUL */ #if DEBUG_STRL if (retval >= siz) { text_color_set (DW_COLOR_ERROR); dw_printf ("WARNING: strlcpy result length %d exceeds maximum length %d. (%s %s %d)\n", (int)retval, (int)(siz-1), file, func, line); } #endif return (retval); } direwolf-1.5+dfsg/misc/strsep.c000066400000000000000000000007711347750676600165620ustar00rootroot00000000000000/* BSD strsep function */ /* Copyright 2002, Red Hat Inc. */ /* undef STRICT_ANSI so that strsep prototype will be defined */ #undef __STRICT_ANSI__ #include //#include <_ansi.h> //#include #define _DEFUN(name,arglist,args) name(args) #define _AND , extern char *__strtok_r (char *, const char *, char **, int); char * _DEFUN (strsep, (source_ptr, delim), register char **source_ptr _AND register const char *delim) { return __strtok_r (*source_ptr, delim, source_ptr, 0); } direwolf-1.5+dfsg/misc/strtok_r.c000066400000000000000000000054741347750676600171160ustar00rootroot00000000000000/* * Copyright (c) 1988 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #define _DEFUN(name,arglist,args) name(args) #define _AND , char * _DEFUN (__strtok_r, (s, delim, lasts, skip_leading_delim), register char *s _AND register const char *delim _AND char **lasts _AND int skip_leading_delim) { register char *spanp; register int c, sc; char *tok; if (s == NULL && (s = *lasts) == NULL) return (NULL); /* * Skip (span) leading delimiters (s += strspn(s, delim), sort of). */ cont: c = *s++; for (spanp = (char *)delim; (sc = *spanp++) != 0;) { if (c == sc) { if (skip_leading_delim) { goto cont; } else { *lasts = s; s[-1] = 0; return (s - 1); } } } if (c == 0) { /* no non-delimiter characters */ *lasts = NULL; return (NULL); } tok = s - 1; /* * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). * Note that delim must have one NUL; we stop if we see that, too. */ for (;;) { c = *s++; spanp = (char *)delim; do { if ((sc = *spanp++) == c) { if (c == 0) s = NULL; else s[-1] = 0; *lasts = s; return (tok); } } while (sc != 0); } /* NOTREACHED */ } char * _DEFUN (strtok_r, (s, delim, lasts), register char *s _AND register const char *delim _AND char **lasts) { return __strtok_r (s, delim, lasts, 1); } direwolf-1.5+dfsg/morse.c000066400000000000000000000266431347750676600154420ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: morse.c * * Purpose: Generate audio for morse code. * * Description: * * Reference: * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include #include "textcolor.h" #include "audio.h" #include "ptt.h" #include "gen_tone.h" /* for gen_tone_put_sample */ #include "morse.h" /* * Might get ambitious and make this adjustable some day. * Good enough for now. */ #define MORSE_TONE 800 #define TIME_UNITS_TO_MS(tu,wpm) (((tu)*1200.0)/(wpm)) static const struct morse_s { char ch; char enc[8]; /* $ has 7 elements */ } morse[] = { { 'A', ".-" }, { 'B', "-..." }, { 'C', "-.-." }, { 'D', "-.." }, { 'E', "." }, { 'F', "..-." }, { 'G', "--." }, { 'H', "...." }, { 'I', "." }, { 'J', ".---" }, { 'K', "-.-" }, { 'L', ".-.." }, { 'M', "--" }, { 'N', "-." }, { 'O', "---" }, { 'P', ".--." }, { 'Q', "--.-" }, { 'R', ".-." }, { 'S', "..." }, { 'T', "-" }, { 'U', "..-" }, { 'V', "...-" }, { 'W', ".--" }, { 'X', "-..-" }, { 'Y', "-.--" }, { 'Z', "--.." }, { '1', ".----" }, { '2', "..---" }, { '3', "...--" }, { '4', "....-" }, { '5', "....." }, { '6', "-...." }, { '7', "--..." }, { '8', "---.." }, { '9', "----." }, { '0', "-----" }, { '-', "-...-" }, { '.', ".-.-.-" }, { ',', "--..--" }, { '?', "..--.." }, { '/', "-..-." }, { '=', "-...-" }, /* from ARRL */ { '-', "-....-" }, { ')', "-.--.-" }, /* does not distinguish open/close */ { ':', "---..." }, { ';', "-.-.-." }, { '"', ".-..-." }, { '\'', ".----." }, { '$', "...-..-" }, { '!', "-.-.--" }, /* more from wikipedia */ { '(', "-.--." }, { '&', ".-..." }, { '+', ".-.-." }, { '_', "..--.-" }, { '@', ".--.-." }, }; #define NUM_MORSE ((int)(sizeof(morse) / sizeof(struct morse_s))) static void morse_tone (int chan, int tu, int wpm); static void morse_quiet (int chan, int tu, int wpm); static void morse_quiet_ms (int chan, int ms); static int morse_lookup (int ch); static int morse_units_ch (int ch); static int morse_units_str (char *str); /* * Properties of the digitized sound stream. */ static struct audio_s *save_audio_config_p; /* Constants after initialization. */ #define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 ) static short sine_table[256]; /*------------------------------------------------------------------ * * Name: morse_init * * Purpose: Initialize for tone generation. * * Inputs: audio_config_p - Pointer to audio configuration structure. * * The fields we care about are: * * samples_per_sec * * amp - Signal amplitude on scale of 0 .. 100. * * 100 will produce maximum amplitude of +-32k samples. * * Returns: 0 for success. * -1 for failure. * * Description: Precompute a sine wave table. * *----------------------------------------------------------------*/ int morse_init (struct audio_s *audio_config_p, int amp) { int j; /* * Save away modem parameters for later use. */ save_audio_config_p = audio_config_p; for (j=0; j<256; j++) { double a; int s; a = ((double)(j) / 256.0) * (2 * M_PI); s = (int) (sin(a) * 32767.0 * amp / 100.0); /* 16 bit sound sample is in range of -32768 .. +32767. */ assert (s >= -32768 && s <= 32767); sine_table[j] = s; } return (0); } /* end morse_init */ /*------------------------------------------------------------------- * * Name: morse_send * * Purpose: Given a string, generate appropriate lengths of * tone and silence. * * Inputs: chan - Radio channel number. * str - Character string to send. * wpm - Speed in words per minute. * txdelay - Delay (ms) from PTT to first character. * txtail - Delay (ms) from last character to PTT off. * * * Returns: Total number of milliseconds to activate PTT. * This includes delays before the first character * and after the last to avoid chopping off part of it. * * Description: xmit_thread calls this instead of the usual hdlc_send * when we have a special packet that means send morse * code. * *--------------------------------------------------------------------*/ int morse_send (int chan, char *str, int wpm, int txdelay, int txtail) { int time_units; char *p; time_units = 0; morse_quiet_ms (chan, txdelay); for (p = str; *p != '\0'; p++) { int i; i = morse_lookup (*p); if (i >= 0) { const char *e; for (e = morse[i].enc; *e != '\0'; e++) { if (*e == '.') { morse_tone (chan,1,wpm); time_units++; } else { morse_tone (chan,3,wpm); time_units += 3; } if (e[1] != '\0') { morse_quiet (chan,1,wpm); time_units++; } } } else { morse_quiet (chan,1,wpm); time_units++; } if (p[1] != '\0') { morse_quiet (chan,3,wpm); time_units += 3; } } morse_quiet_ms (chan, txtail); if (time_units != morse_units_str(str)) { dw_printf ("morse: Internal error. Inconsistent length, %d vs. %d calculated.\n", time_units, morse_units_str(str)); } audio_flush(ACHAN2ADEV(chan)); return (txdelay + (int) (TIME_UNITS_TO_MS(time_units, wpm) + 0.5) + txtail); } /* end morse_send */ /*------------------------------------------------------------------- * * Name: morse_tone * * Purpose: Generate tone for specified number of time units. * * Inputs: chan - Radio channel. * tu - Number of time units. Should be 1 or 3. * wpm - Speed in WPM. * *--------------------------------------------------------------------*/ static void morse_tone (int chan, int tu, int wpm) { #if MTEST1 int n; for (n=0; nachan[chan].valid); tone_phase = 0; f1_change_per_sample = (int) (((double)MORSE_TONE * TICKS_PER_CYCLE / (double)save_audio_config_p->adev[a].samples_per_sec ) + 0.5); nsamples = (int) ((TIME_UNITS_TO_MS(tu,wpm) * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5); for (j=0; j> 24) & 0xff]; gen_tone_put_sample (chan, a, sam); }; #endif } /* end morse_tone */ /*------------------------------------------------------------------- * * Name: morse_quiet * * Purpose: Generate silence for specified number of time units. * * Inputs: chan - Radio channel. * tu - Number of time units. * wpm - Speed in WPM. * *--------------------------------------------------------------------*/ static void morse_quiet (int chan, int tu, int wpm) { #if MTEST1 int n; for (n=0; nachan[chan].valid); nsamples = (int) ((TIME_UNITS_TO_MS(tu,wpm) * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5); for (j=0; jachan[chan].valid); nsamples = (int) ((ms * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5); for (j=0; j. // /*------------------------------------------------------------------ * * Name: multi_modem.c * * Purpose: Use multiple modems in parallel to increase chances * of decoding less than ideal signals. * * Description: The initial motivation was for HF SSB where mistuning * causes a shift in the audio frequencies. Here, we can * have multiple modems tuned to staggered pairs of tones * in hopes that one will be close enough. * * The overall structure opens the door to other approaches * as well. For VHF FM, the tones should always have the * right frequencies but we might want to tinker with other * modem parameters instead of using a single compromise. * * Originally: The the interface application is in 3 places: * * (a) Main program (direwolf.c or atest.c) calls * demod_init to set up modem properties and * hdlc_rec_init for the HDLC decoders. * * (b) demod_process_sample is called for each audio sample * from the input audio stream. * * (c) When a valid AX.25 frame is found, process_rec_frame, * provided by the application, in direwolf.c or atest.c, * is called. Normally this comes from hdlc_rec.c but * there are a couple other special cases to consider. * It can be called from hdlc_rec2.c if it took a long * time to "fix" corrupted bits. aprs_tt.c constructs * a fake packet when a touch tone message is received. * * New in version 0.9: * * Put an extra layer in between which potentially uses * multiple modems & HDLC decoders per channel. The tricky * part is picking the best one when there is more than one * success and discarding the rest. * * New in version 1.1: * * Several enhancements provided by Fabrice FAURE: * * Additional types of attempts to fix a bad CRC. * Optimized code to reduce execution time. * Improved detection of duplicate packets from * different fixup attempts. * Set limit on number of packets in fix up later queue. * *------------------------------------------------------------------*/ //#define DEBUG 1 #define DIGIPEATER_C #include "direwolf.h" #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "multi_modem.h" #include "demod.h" #include "hdlc_rec.h" #include "hdlc_rec2.h" #include "dlq.h" // Properties of the radio channels. static struct audio_s *save_audio_config_p; // Candidates for further processing. static struct { packet_t packet_p; alevel_t alevel; retry_t retries; int age; unsigned int crc; int score; } candidate[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; //#define PROCESS_AFTER_BITS 2 // version 1.4. Was a little short for skew of PSK with different modem types, optional pre-filter #define PROCESS_AFTER_BITS 3 static int process_age[MAX_CHANS]; static void pick_best_candidate (int chan); /*------------------------------------------------------------------------------ * * Name: multi_modem_init * * Purpose: Called at application start up to initialize appropriate * modems and HDLC decoders. * * Input: Modem properties structure as filled in from the configuration file. * * Outputs: * * Description: Called once at application startup time. * *------------------------------------------------------------------------------*/ void multi_modem_init (struct audio_s *pa) { int chan; /* * Save audio configuration for later use. */ save_audio_config_p = pa; memset (candidate, 0, sizeof(candidate)); demod_init (save_audio_config_p); hdlc_rec_init (save_audio_config_p); for (chan=0; chanachan[chan].valid) { if (save_audio_config_p->achan[chan].baud <= 0) { text_color_set(DW_COLOR_ERROR); dw_printf("Internal error, chan=%d, %s, %d\n", chan, __FILE__, __LINE__); save_audio_config_p->achan[chan].baud = DEFAULT_BAUD; } int real_baud = save_audio_config_p->achan[chan].baud; if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK) real_baud = save_audio_config_p->achan[chan].baud / 2; if (save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) real_baud = save_audio_config_p->achan[chan].baud / 3; process_age[chan] = PROCESS_AFTER_BITS * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / real_baud ; //crc_queue_of_last_to_app[chan] = NULL; } } } #if 0 //Add a crc to the end of the queue and returns the numbers of CRC stored in the queue int crc_queue_append (unsigned int crc, unsigned int chan) { crc_t plast; // crc_t plast1; crc_t pnext; crc_t new_crc; unsigned int nb_crc = 1; if (chan>=MAX_CHANS) { return -1; } new_crc = (crc_t) malloc (10*sizeof(struct crc_s)); if (!new_crc) return -1; new_crc->crc = crc; new_crc->nextp = NULL; if (crc_queue_of_last_to_app[chan] == NULL) { crc_queue_of_last_to_app[chan] = new_crc; nb_crc = 1; } else { nb_crc = 2; plast = crc_queue_of_last_to_app[chan]; pnext = plast->nextp; while (pnext != NULL) { nb_crc++; plast = pnext; pnext = pnext->nextp; } plast->nextp = new_crc; } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("Out crc_queue_append nb_crc = %d\n", nb_crc); #endif return nb_crc; } //Remove the crc from the top of the queue unsigned int crc_queue_remove (unsigned int chan) { unsigned int res; // crc_t plast; // crc_t pnext; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("In crc_queue_remove\n"); #endif crc_t removed_crc; if (chan>=MAX_CHANS) { return 0; } removed_crc = crc_queue_of_last_to_app[chan]; if (removed_crc == NULL) { return 0; } else { crc_queue_of_last_to_app[chan] = removed_crc->nextp; res = removed_crc->crc; free(removed_crc); } return res; } unsigned char is_crc_in_queue(unsigned int chan, unsigned int crc) { crc_t plast; crc_t pnext; if (crc_queue_of_last_to_app[chan] == NULL) { return 0; } else { plast = crc_queue_of_last_to_app[chan]; do { pnext = plast->nextp; if (plast->crc == crc) { return 1; } plast = pnext; } while (pnext != NULL); } return 0; } #endif /* if 0 */ /*------------------------------------------------------------------------------ * * Name: multi_modem_process_sample * * Purpose: Feed the sample into the proper modem(s) for the channel. * * Inputs: chan - Radio channel number * * audio_sample * * Description: In earlier versions we always had a one-to-one mapping with * demodulators and HDLC decoders. * This was added so we could have multiple modems running in * parallel with different mark/space tones to compensate for * mistuning of HF SSB signals. * It was also possible to run multiple filters, for the same * tones, in parallel (e.g. ABC). * * Version 1.2: Let's try something new for an experiment. * We will have a single mark/space demodulator but multiple * slicers, using different levels, each with its own HDLC decoder. * We now have a separate variable, num_demod, which could be 1 * while num_subchan is larger. * * Version 1.3: Go back to num_subchan with single meaning of number of demodulators. * We now have separate independent variable, num_slicers, for the * mark/space imbalance compensation. * num_demod, while probably more descriptive, should not exist anymore. * *------------------------------------------------------------------------------*/ static float dc_average[MAX_CHANS]; int multi_modem_get_dc_average (int chan) { // Scale to +- 200 so it will like the deviation measurement. return ( (int) ((float)(dc_average[chan]) * (200.0f / 32767.0f) ) ); } __attribute__((hot)) void multi_modem_process_sample (int chan, int audio_sample) { int d; int subchan; static int i = 0; /* for interleaving among multiple demodulators. */ // Accumulate an average DC bias level. // Shouldn't happen with a soundcard but could with mistuned SDR. dc_average[chan] = dc_average[chan] * 0.999f + (float)audio_sample * 0.001f; // Issue 128. Someone ran into this. //assert (save_audio_config_p->achan[chan].num_subchan > 0 && save_audio_config_p->achan[chan].num_subchan <= MAX_SUBCHANS); //assert (save_audio_config_p->achan[chan].num_slicers > 0 && save_audio_config_p->achan[chan].num_slicers <= MAX_SLICERS); if (save_audio_config_p->achan[chan].num_subchan <= 0 || save_audio_config_p->achan[chan].num_subchan > MAX_SUBCHANS || save_audio_config_p->achan[chan].num_slicers <= 0 || save_audio_config_p->achan[chan].num_slicers > MAX_SLICERS) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR! Something is seriously wrong in %s %s.\n", __FILE__, __func__); dw_printf ("chan = %d, num_subchan = %d [max %d], num_slicers = %d [max %d]\n", chan, save_audio_config_p->achan[chan].num_subchan, MAX_SUBCHANS, save_audio_config_p->achan[chan].num_slicers, MAX_SLICERS); dw_printf ("Please report this message and include a copy of your configuration file.\n"); exit (EXIT_FAILURE); } /* Formerly one loop. */ /* 1.2: We can feed one demodulator but end up with multiple outputs. */ if (save_audio_config_p->achan[chan].interleave > 1) { // TODO: temp debug, remove this. assert (save_audio_config_p->achan[chan].interleave == save_audio_config_p->achan[chan].num_subchan); demod_process_sample(chan, i, audio_sample); i++; if (i >= save_audio_config_p->achan[chan].interleave) i = 0; } else { /* Send same thing to all. */ for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) { demod_process_sample(chan, d, audio_sample); } } for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) { int slice; for (slice = 0; slice < save_audio_config_p->achan[chan].num_slicers; slice++) { if (candidate[chan][subchan][slice].packet_p != NULL) { candidate[chan][subchan][slice].age++; if (candidate[chan][subchan][slice].age > process_age[chan]) { pick_best_candidate (chan); } } } } } /*------------------------------------------------------------------- * * Name: multi_modem_process_rec_frame * * Purpose: This is called when we receive a frame with a valid * FCS and acceptable size. * * Inputs: chan - Audio channel number, 0 or 1. * subchan - Which modem found it. * slice - Which slice found it. * fbuf - Pointer to first byte in HDLC frame. * flen - Number of bytes excluding the FCS. * alevel - Audio level, range of 0 - 100. * (Special case, use negative to skip * display of audio level line. * Use -2 to indicate DTMF message.) * retries - Level of bit correction used. * * * Description: Add to list of candidates. Best one will be picked later. * *--------------------------------------------------------------------*/ /* It gets a little more complicated when we try fixing frames with imperfect CRCs. Changing of adjacent bits is quick and done immediately. These all come in at nearly the same time. The processing of two separated bits can take a long time and is handled in the background by another thread. These could come in seconds later. We need a way to remove duplicates. I think these are the two cases we need to consider. (1) Same result as earlier no error or adjacent bit errors. ____||||_ 0.0: ptr=00000000 0.1: ptr=00000000 0.2: ptr=00000000 0.3: ptr=00000000 0.4: ptr=009E5540, retry=0, age=295, crc=9458, score=5024 0.5: ptr=0082F008, retry=0, age=294, crc=9458, score=5026 *** 0.6: ptr=009CE560, retry=0, age=293, crc=9458, score=5026 0.7: ptr=009CEE08, retry=0, age=293, crc=9458, score=5024 0.8: ptr=00000000 ___._____ 0.0: ptr=00000000 0.1: ptr=00000000 0.2: ptr=00000000 0.3: ptr=009E5540, retry=4, age=295, crc=9458, score=1000 *** 0.4: ptr=00000000 0.5: ptr=00000000 0.6: ptr=00000000 0.7: ptr=00000000 0.8: ptr=00000000 (2) Only results from adjusting two non-adjacent bits. ||||||||_ 0.0: ptr=022EBA08, retry=0, age=289, crc=5acd, score=5042 0.1: ptr=022EA8B8, retry=0, age=290, crc=5acd, score=5048 0.2: ptr=022EB160, retry=0, age=290, crc=5acd, score=5052 0.3: ptr=05BD0048, retry=0, age=291, crc=5acd, score=5054 *** 0.4: ptr=04FE0048, retry=0, age=292, crc=5acd, score=5054 0.5: ptr=05E10048, retry=0, age=294, crc=5acd, score=5052 0.6: ptr=053D0048, retry=0, age=294, crc=5acd, score=5048 0.7: ptr=02375558, retry=0, age=295, crc=5acd, score=5042 0.8: ptr=00000000 _______._ 0.0: ptr=00000000 0.1: ptr=00000000 0.2: ptr=00000000 0.3: ptr=00000000 0.4: ptr=00000000 0.5: ptr=00000000 0.6: ptr=00000000 0.7: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000 *** 0.8: ptr=00000000 ________. 0.0: ptr=00000000 0.1: ptr=00000000 0.2: ptr=00000000 0.3: ptr=00000000 0.4: ptr=00000000 0.5: ptr=00000000 0.6: ptr=00000000 0.7: ptr=00000000 0.8: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000 *** These can both be covered by keepin the last CRC and dropping duplicates. In theory we could get another frame in between with a slow computer so the complete solution would be to remember more than one. */ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries) { packet_t pp; assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SUBCHANS); pp = ax25_from_frame (fbuf, flen, alevel); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Unexpected internal problem, %s %d\n", __FILE__, __LINE__); return; /* oops! why would it fail? */ } /* * If only one demodulator/slicer, push it thru and forget about all this foolishness. */ if (save_audio_config_p->achan[chan].num_subchan == 1 && save_audio_config_p->achan[chan].num_slicers == 1) { int drop_it = 0; if (save_audio_config_p->recv_error_rate != 0) { float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0 //text_color_set(DW_COLOR_INFO); //dw_printf ("TEMP DEBUG. recv error rate = %d\n", save_audio_config_p->recv_error_rate); if (save_audio_config_p->recv_error_rate / 100.0 > r) { drop_it = 1; text_color_set(DW_COLOR_INFO); dw_printf ("Intentionally dropping incoming frame. Recv Error rate = %d per cent.\n", save_audio_config_p->recv_error_rate); } } if (drop_it ) { ax25_delete (pp); } else { dlq_rec_frame (chan, subchan, slice, pp, alevel, retries, ""); } return; } /* * Otherwise, save them up for a few bit times so we can pick the best. */ if (candidate[chan][subchan][slice].packet_p != NULL) { /* Oops! Didn't expect it to be there. */ ax25_delete (candidate[chan][subchan][slice].packet_p); candidate[chan][subchan][slice].packet_p = NULL; } candidate[chan][subchan][slice].packet_p = pp; candidate[chan][subchan][slice].alevel = alevel; candidate[chan][subchan][slice].retries = retries; candidate[chan][subchan][slice].age = 0; candidate[chan][subchan][slice].crc = ax25_m_m_crc(pp); } /*------------------------------------------------------------------- * * Name: pick_best_candidate * * Purpose: This is called when we have one or more candidates * available for a certain amount of time. * * Description: Pick the best one and send it up to the application. * Discard the others. * * Rules: We prefer one received perfectly but will settle for * one where some bits had to be flipped to get a good CRC. * *--------------------------------------------------------------------*/ /* This is a suitable order for interleaved "G" demodulators. */ /* Opposite order would be suitable for multi-frequency although */ /* multiple slicers are of questionable value for HF SSB. */ #define subchan_from_n(x) ((x) % save_audio_config_p->achan[chan].num_subchan) #define slice_from_n(x) ((x) / save_audio_config_p->achan[chan].num_subchan) static void pick_best_candidate (int chan) { int best_n, best_score; char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; int n, j, k; int num_bars = save_audio_config_p->achan[chan].num_slicers * save_audio_config_p->achan[chan].num_subchan; memset (spectrum, 0, sizeof(spectrum)); for (n = 0; n < num_bars; n++) { j = subchan_from_n(n); k = slice_from_n(n); /* Build the spectrum display. */ if (candidate[chan][j][k].packet_p == NULL) { spectrum[n] = '_'; } else if (candidate[chan][j][k].retries == RETRY_NONE) { spectrum[n] = '|'; } else if (candidate[chan][j][k].retries == RETRY_INVERT_SINGLE) { spectrum[n] = ':'; } else { spectrum[n] = '.'; } /* Begining score depends on effort to get a valid frame CRC. */ if (candidate[chan][j][k].packet_p == NULL) { candidate[chan][j][k].score = 0; } else { /* Originally, this produced 0 for the PASSALL case. */ /* This didn't work so well when looking for the best score. */ /* Around 1.3 dev H, we add an extra 1 in here so the minimum */ /* score should now be 1 for anything received. */ candidate[chan][j][k].score = RETRY_MAX * 1000 - ((int)candidate[chan][j][k].retries * 1000) + 1; } } /* Bump it up slightly if others nearby have the same CRC. */ for (n = 0; n < num_bars; n++) { int m; j = subchan_from_n(n); k = slice_from_n(n); if (candidate[chan][j][k].packet_p != NULL) { for (m = 0; m < num_bars; m++) { int mj = subchan_from_n(m); int mk = slice_from_n(m); if (m != n && candidate[chan][mj][mk].packet_p != NULL) { if (candidate[chan][j][k].crc == candidate[chan][mj][mk].crc) { candidate[chan][j][k].score += (num_bars+1) - abs(m-n); } } } } } best_n = 0; best_score = 0; for (n = 0; n < num_bars; n++) { j = subchan_from_n(n); k = slice_from_n(n); if (candidate[chan][j][k].packet_p != NULL) { if (candidate[chan][j][k].score > best_score) { best_score = candidate[chan][j][k].score; best_n = n; } } } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("\n%s\n", spectrum); for (n = 0; n < num_bars; n++) { j = subchan_from_n(n); k = slice_from_n(n); if (candidate[chan][j][k].packet_p == NULL) { dw_printf ("%d.%d.%d: ptr=%p\n", chan, j, k, candidate[chan][j][k].packet_p); } else { dw_printf ("%d.%d.%d: ptr=%p, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, j, k, candidate[chan][j][k].packet_p, (int)(candidate[chan][j][k].retries), candidate[chan][j][k].age, candidate[chan][j][k].crc, candidate[chan][j][k].score, (n == best_n) ? "***" : ""); } } #endif if (best_score == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Unexpected internal problem, %s %d. How can best score be zero?\n", __FILE__, __LINE__); } /* * send the best one along. */ /* Delete those not chosen. */ for (n = 0; n < num_bars; n++) { j = subchan_from_n(n); k = slice_from_n(n); if (n != best_n && candidate[chan][j][k].packet_p != NULL) { ax25_delete (candidate[chan][j][k].packet_p); candidate[chan][j][k].packet_p = NULL; } } /* Pass along one. */ j = subchan_from_n(best_n); k = slice_from_n(best_n); int drop_it = 0; if (save_audio_config_p->recv_error_rate != 0) { float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0 //text_color_set(DW_COLOR_INFO); //dw_printf ("TEMP DEBUG. recv error rate = %d\n", save_audio_config_p->recv_error_rate); if (save_audio_config_p->recv_error_rate / 100.0 > r) { drop_it = 1; text_color_set(DW_COLOR_INFO); dw_printf ("Intentionally dropping incoming frame. Recv Error rate = %d per cent.\n", save_audio_config_p->recv_error_rate); } } if ( drop_it ) { ax25_delete (candidate[chan][j][k].packet_p); candidate[chan][j][k].packet_p = NULL; } else { dlq_rec_frame (chan, j, k, candidate[chan][j][k].packet_p, candidate[chan][j][k].alevel, (int)(candidate[chan][j][k].retries), spectrum); /* Someone else owns it now and will delete it later. */ candidate[chan][j][k].packet_p = NULL; } /* Clear in preparation for next time. */ memset (candidate[chan], 0, sizeof(candidate[chan])); } /* end pick_best_candidate */ /* end multi_modem.c */ direwolf-1.5+dfsg/multi_modem.h000066400000000000000000000007331347750676600166250ustar00rootroot00000000000000/* multi_modem.h */ #ifndef MULTI_MODEM_H #define MULTI_MODEM 1 /* Needed for typedef retry_t. */ #include "hdlc_rec2.h" /* Needed for struct audio_s */ #include "audio.h" void multi_modem_init (struct audio_s *pmodem); void multi_modem_process_sample (int c, int audio_sample); int multi_modem_get_dc_average (int chan); void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries); #endif direwolf-1.5+dfsg/pfilter.c000066400000000000000000001540741347750676600157620ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2015, 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: pfilter.c * * Purpose: Packet filtering based on characteristics. * * Description: Sometimes it is desirable to digipeat or drop packets based on rules. * For example, you might want to pass only weather information thru * a cross band digipeater or you might want to drop all packets from * an abusive user that is overloading the channel. * * The filter specifications are loosely modeled after the IGate Server-side Filter * Commands: http://www.aprs-is.net/javaprsfilter.aspx * * We add AND, OR, NOT, and ( ) to allow very flexible control. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "decode_aprs.h" #include "latlong.h" #include "pfilter.h" #include "mheard.h" /* * Global stuff (to this file) * * These are set by init function. */ static struct igate_config_s *save_igate_config_p; static int s_debug = 0; /*------------------------------------------------------------------- * * Name: pfilter_init * * Purpose: One time initialization when main application starts up. * * Inputs: p_igate_config - IGate configuration. * * debug_level - 0 no debug output. * 1 single summary line with final result. Indent by 1. * 2 details from each filter specification. Indent by 3. * 3 Logical operators. Indent by 2. * *--------------------------------------------------------------------*/ void pfilter_init (struct igate_config_s *p_igate_config, int debug_level) { s_debug = debug_level; save_igate_config_p = p_igate_config; } typedef enum token_type_e { TOKEN_AND, TOKEN_OR, TOKEN_NOT, TOKEN_LPAREN, TOKEN_RPAREN, TOKEN_FILTER_SPEC, TOKEN_EOL } token_type_t; #define MAX_FILTER_LEN 1024 #define MAX_TOKEN_LEN 1024 typedef struct pfstate_s { int from_chan; /* From and to channels. MAX_CHANS is used for IGate. */ int to_chan; /* Used only for debug and error messages. */ /* * Original filter string from config file. * All control characters should be replaced by spaces. */ char filter_str[MAX_FILTER_LEN]; int nexti; /* Next available character index. */ /* * Packet object. */ packet_t pp; /* * Are we processing APRS or connected mode? * This determines whch types of filters are available. */ int is_aprs; /* * Packet split into separate parts if APRS. * Most interesting fields are: * * g_symbol_table - / \ or overlay * g_symbol_code * g_lat, g_lon - Location * g_name - for object or item * g_comment */ decode_aprs_t decoded; /* * These are set by next_token. */ token_type_t token_type; char token_str[MAX_TOKEN_LEN]; /* Printable string representation for use in error messages. */ int tokeni; /* Index in original string for enhanced error messages. */ } pfstate_t; static int parse_expr (pfstate_t *pf); static int parse_or_expr (pfstate_t *pf); static int parse_and_expr (pfstate_t *pf); static int parse_primary (pfstate_t *pf); static int parse_filter_spec (pfstate_t *pf); static void next_token (pfstate_t *pf); static void print_error (pfstate_t *pf, char *msg); static int filt_bodgu (pfstate_t *pf, char *pattern); static int filt_t (pfstate_t *pf); static int filt_r (pfstate_t *pf, char *sdist); static int filt_s (pfstate_t *pf); static int filt_i (pfstate_t *pf); static char *bool2text (int val) { if (val == 1) return "TRUE"; if (val == 0) return "FALSE"; if (val == -1) return "ERROR"; return "OOPS!"; } /*------------------------------------------------------------------- * * Name: pfilter.c * * Purpose: Decide whether a packet should be allowed thru. * * Inputs: from_chan - Channel packet is coming from. * to_chan - Channel packet is going to. * Both are 0 .. MAX_CHANS-1 or MAX_CHANS for IGate. * For debug/error messages only. * * filter - String of filter specs and logical operators to combine them. * * pp - Packet object handle. * * is_aprs - True for APRS, false for connected mode digipeater. * Connected mode allows a subset of the filter types, only * looking at the addresses, not information part contents. * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: This might be running in multiple threads at the same time so * no static data allowed and take other thread-safe precautions. * *--------------------------------------------------------------------*/ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs) { pfstate_t pfstate; char *p; int result; assert (from_chan >= 0 && from_chan <= MAX_CHANS); assert (to_chan >= 0 && to_chan <= MAX_CHANS); memset (&pfstate, 0, sizeof(pfstate)); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR in pfilter: NULL packet pointer. Please report this!\n"); return (-1); } if (filter == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR in pfilter: NULL filter string pointer. Please report this!\n"); return (-1); } pfstate.from_chan = from_chan; pfstate.to_chan = to_chan; /* Copy filter string, changing any control characers to spaces. */ strlcpy (pfstate.filter_str, filter, sizeof(pfstate.filter_str)); pfstate.nexti = 0; for (p = pfstate.filter_str; *p != '\0'; p++) { if (iscntrl(*p)) { *p = ' '; } } pfstate.pp = pp; pfstate.is_aprs = is_aprs; if (is_aprs) { decode_aprs (&pfstate.decoded, pp, 1); } next_token(&pfstate); if (pfstate.token_type == TOKEN_EOL) { /* Empty filter means reject all. */ result = 0; } else { result = parse_expr (&pfstate); if (pfstate.token_type != TOKEN_AND && pfstate.token_type != TOKEN_OR && pfstate.token_type != TOKEN_EOL) { print_error (&pfstate, "Expected logical operator or end of line here."); result = -1; } } if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); if (from_chan == MAX_CHANS) { dw_printf (" Packet filter from IGate to radio channel %d returns %s\n", to_chan, bool2text(result)); } else if (to_chan == MAX_CHANS) { dw_printf (" Packet filter from radio channel %d to IGate returns %s\n", from_chan, bool2text(result)); } else if (is_aprs) { dw_printf (" Packet filter for APRS digipeater from radio channel %d to %d returns %s\n", from_chan, to_chan, bool2text(result)); } else { dw_printf (" Packet filter for traditional digipeater from radio channel %d to %d returns %s\n", from_chan, to_chan, bool2text(result)); } } return (result); } /* end pfilter */ /*------------------------------------------------------------------- * * Name: next_token * * Purpose: Extract the next token from input string. * * Inputs: pf - Pointer to current state information. * * Outputs: See definition of the structure. * * Description: Look for these special operators: & | ! ( ) end-of-line * Anything else is considered a filter specification. * Note that a filter-spec must be followed by space or * end of line. This is so the magic characters can appear in one. * * Future: Maybe allow words like 'OR' as alternatives to symbols like '|'. * * Unresolved Issue: * * Adding the special operators adds a new complication. * How do we handle the case where we want those characters in * a filter specification? For example how do we know if the * last character of /#& means HF gateway or AND the next part * of the expression. * * Approach 1: Require white space after all filter specifications. * Currently implemented. * Simple. Easy to explain. * More readable than having everything squashed together. * * Approach 2: Use escape character to get literal value. e.g. s/#\& * Linux people would be comfortable with this but * others might have a problem with it. * * Approach 3: use quotation marks if it contains special characters or space. * "s/#&" Simple. Allows embedded space but I'm not sure * that's useful. Doesn't hurt to always put the quotes there * if you can't remember which characters are special. * *--------------------------------------------------------------------*/ static void next_token (pfstate_t *pf) { while (pf->filter_str[pf->nexti] == ' ') { pf->nexti++; } pf->tokeni = pf->nexti; if (pf->filter_str[pf->nexti] == '\0') { pf->token_type = TOKEN_EOL; strlcpy (pf->token_str, "end-of-line", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '&') { pf->nexti++; pf->token_type = TOKEN_AND; strlcpy (pf->token_str, "\"&\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '|') { pf->nexti++; pf->token_type = TOKEN_OR; strlcpy (pf->token_str, "\"|\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '!') { pf->nexti++; pf->token_type = TOKEN_NOT; strlcpy (pf->token_str, "\"!\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '(') { pf->nexti++; pf->token_type = TOKEN_LPAREN; strlcpy (pf->token_str, "\"(\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == ')') { pf->nexti++; pf->token_type = TOKEN_RPAREN; strlcpy (pf->token_str, "\")\"", sizeof(pf->token_str)); } else { char *p = pf->token_str; pf->token_type = TOKEN_FILTER_SPEC; do { *p++ = pf->filter_str[pf->nexti++]; } while (pf->filter_str[pf->nexti] != ' ' && pf->filter_str[pf->nexti] != '\0'); *p = '\0'; } } /* end next_token */ /*------------------------------------------------------------------- * * Name: parse_expr * parse_or_expr * parse_and_expr * parse_primary * * Purpose: Recursive descent parser to evaluate filter specifications * contained within expressions with & | ! ( ). * * Inputs: pf - Pointer to current state information. * * Returns: 1 = yes * 0 = no * -1 = error detected * *--------------------------------------------------------------------*/ static int parse_expr (pfstate_t *pf) { int result; result = parse_or_expr (pf); return (result); } /* or_expr:: and_expr [ | and_expr ] ... */ static int parse_or_expr (pfstate_t *pf) { int result; result = parse_and_expr (pf); if (result < 0) return (-1); while (pf->token_type == TOKEN_OR) { int e; next_token (pf); e = parse_and_expr (pf); if (s_debug >= 3) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s | %s\n", bool2text(result), bool2text(e)); } if (e < 0) return (-1); result |= e; } return (result); } /* and_expr:: primary [ & primary ] ... */ static int parse_and_expr (pfstate_t *pf) { int result; result = parse_primary (pf); if (result < 0) return (-1); while (pf->token_type == TOKEN_AND) { int e; next_token (pf); e = parse_primary (pf); if (s_debug >= 3) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s & %s\n", bool2text(result), bool2text(e)); } if (e < 0) return (-1); result &= e; } return (result); } /* primary:: ( expr ) */ /* ! primary */ /* filter_spec */ static int parse_primary (pfstate_t *pf) { int result; if (pf->token_type == TOKEN_LPAREN) { next_token (pf); result = parse_expr (pf); if (pf->token_type == TOKEN_RPAREN) { next_token (pf); } else { print_error (pf, "Expected \")\" here.\n"); result = -1; } } else if (pf->token_type == TOKEN_NOT) { int e; next_token (pf); e = parse_primary (pf); if (s_debug >= 3) { text_color_set(DW_COLOR_DEBUG); dw_printf (" ! %s\n", bool2text(e)); } if (e < 0) result = -1; else result = ! e; } else if (pf->token_type == TOKEN_FILTER_SPEC) { result = parse_filter_spec (pf); } else { print_error (pf, "Expected filter specification, (, or ! here."); result = -1; } return (result); } /*------------------------------------------------------------------- * * Name: parse_filter_spec * * Purpose: Parse and evaluate filter specification. * * Inputs: pf - Pointer to current state information. * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: All filter specifications are allowed for APRS. * Only those dealing with addresses are allowed for connected digipeater. * * b - budlist (source) * d - digipeaters used * v - digipeaters not used * u - unproto (destination) * *--------------------------------------------------------------------*/ static int parse_filter_spec (pfstate_t *pf) { int result = -1; if ( ( ! pf->is_aprs) && strchr ("01bdvu", pf->token_str[0]) == NULL) { print_error (pf, "Only b, d, v, and u specifications are allowed for connected mode digipeater filtering."); result = -1; next_token (pf); return (result); } /* undocumented: can use 0 or 1 for testing. */ if (strcmp(pf->token_str, "0") == 0) { result = 0; } else if (strcmp(pf->token_str, "1") == 0) { result = 1; } /* simple string matching */ /* b - budlist */ else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) { /* Budlist - source address */ char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, addr); result = filt_bodgu (pf, addr); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), addr); } } /* o - object or item name */ else if (pf->token_str[0] == 'o' && ispunct(pf->token_str[1])) { result = filt_bodgu (pf, pf->decoded.g_name); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), pf->decoded.g_name); } } /* d - was digipeated by */ else if (pf->token_str[0] == 'd' && ispunct(pf->token_str[1])) { int n; // loop on all digipeaters result = 0; for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) { // Consider only those with the H (has-been-used) bit set. if (ax25_get_h (pf->pp, n)) { char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, n, addr); result = filt_bodgu (pf, addr); } } if (s_debug >= 2) { char path[100]; ax25_format_via_path (pf->pp, path, sizeof(path)); if (strlen(path) == 0) { strcpy (path, "no digipeater path"); } text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), path); } } /* v - via not used */ else if (pf->token_str[0] == 'v' && ispunct(pf->token_str[1])) { int n; // loop on all digipeaters (mnemonic Via) result = 0; for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) { // This is different than the previous "d" filter. // Consider only those where the the H (has-been-used) bit is NOT set. if ( ! ax25_get_h (pf->pp, n)) { char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, n, addr); result = filt_bodgu (pf, addr); } } if (s_debug >= 2) { char path[100]; ax25_format_via_path (pf->pp, path, sizeof(path)); if (strlen(path) == 0) { strcpy (path, "no digipeater path"); } text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), path); } } /* g - Addressee of message. */ else if (pf->token_str[0] == 'g' && ispunct(pf->token_str[1])) { if (ax25_get_dti(pf->pp) == ':') { result = filt_bodgu (pf, pf->decoded.g_addressee); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), pf->decoded.g_addressee); } } else { result = 0; if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), "not a message"); } } } /* u - unproto (destination) */ else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) { /* Probably want to exclude mic-e types */ /* because destination is used for part of location. */ if (ax25_get_dti(pf->pp) != '\'' && ax25_get_dti(pf->pp) != '`') { char addr[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr); result = filt_bodgu (pf, addr); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), addr); } } else { result = 0; if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), "MIC-E packet type"); } } } /* t - type: position, weather, etc. */ else if (pf->token_str[0] == 't' && ispunct(pf->token_str[1])) { result = filt_t (pf); if (s_debug >= 2) { char *infop = NULL; (void) ax25_get_info (pf->pp, (unsigned char **)(&infop)); text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %c data type indicator\n", pf->token_str, bool2text(result), *infop); } } /* r - range */ else if (pf->token_str[0] == 'r' && ispunct(pf->token_str[1])) { /* range */ char sdist[30]; strcpy (sdist, "unknown distance"); result = filt_r (pf, sdist); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), sdist); } } /* s - symbol */ else if (pf->token_str[0] == 's' && ispunct(pf->token_str[1])) { /* symbol */ result = filt_s (pf); if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); if (pf->decoded.g_symbol_table == '/') { dw_printf (" %s returns %s for symbol %c in primary table\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code); } else if (pf->decoded.g_symbol_table == '\\') { dw_printf (" %s returns %s for symbol %c in alternate table\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code); } else { dw_printf (" %s returns %s for symbol %c with overlay %c\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code, pf->decoded.g_symbol_table); } } } /* i - IGate messaging default */ else if (pf->token_str[0] == 'i' && ispunct(pf->token_str[1])) { /* IGatge messaging */ result = filt_i (pf); if (s_debug >= 2) { char *infop = NULL; (void) ax25_get_info (pf->pp, (unsigned char **)(&infop)); text_color_set(DW_COLOR_DEBUG); if (*infop == ':' && ! is_telem_metadata(infop)) { dw_printf (" %s returns %s for message to %s\n", pf->token_str, bool2text(result), pf->decoded.g_addressee); } else { dw_printf (" %s returns %s for not an APRS 'message'\n", pf->token_str, bool2text(result)); } } } /* unrecognized filter type */ else { char stemp[80]; snprintf (stemp, sizeof(stemp), "Unrecognized filter type '%c'", pf->token_str[0]); print_error (pf, stemp); result = -1; } next_token (pf); return (result); } /*------------------------------------------------------------------------------ * * Name: filt_bodgu * * Purpose: Filter with text pattern matching * * Inputs: pf - Pointer to current state information. * token_str should have one of these filter specs: * * Budlist b/call1/call2... * Object o/obj1/obj2... * Digipeater d/digi1/digi2... * Group Msg g/call1/call2... * Unproto u/unproto1/unproto2... * Via-not-yet v/digi1/digi2...noteapd * * arg - Value to match from source addr, destination, * used digipeater, object name, etc. * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: Same function is used for all of these because they are so similar. * Look for exact match to any of the specifed strings. * All of them allow wildcarding with single * at the end. * *------------------------------------------------------------------------------*/ static int filt_bodgu (pfstate_t *pf, char *arg) { char str[MAX_TOKEN_LEN]; char *cp; char sep[2]; char *v; int result = 0; strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; while (result == 0 && (v = strsep (&cp, sep)) != NULL) { int mlen; char *w; if ((w = strchr(v,'*')) != NULL) { /* Wildcarding. Should have single * on end. */ mlen = w - v; if (mlen != (int)(strlen(v) - 1)) { print_error (pf, "Any wildcard * must be at the end of pattern.\n"); return (-1); } if (strncmp(v,arg,mlen) == 0) result = 1; } else { /* Try for exact match. */ if (strcmp(v,arg) == 0) result = 1; } } return (result); } /*------------------------------------------------------------------------------ * * Name: filt_t * * Purpose: Filter by packet type. * * Inputs: pf - Pointer to current state information. * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: The filter is based the type filtering described here: * http://www.aprs-is.net/javAPRSFilter.aspx * * Most of these simply check the first byte of the information part. * Trying to detect NWS information is a little trickier. * http://www.aprs-is.net/WX/ * http://wxsvr.aprs.net.au/protocol-new.html * *------------------------------------------------------------------------------*/ /* Telemetry metadata is a special case of message. */ /* We want to categorize it as telemetry rather than message. */ int is_telem_metadata (char *infop) { if (*infop != ':') return (0); if (strlen(infop) < 16) return (0); if (strncmp(infop+10, ":PARM.", 6) == 0) return (1); if (strncmp(infop+10, ":UNIT.", 6) == 0) return (1); if (strncmp(infop+10, ":EQNS.", 6) == 0) return (1); if (strncmp(infop+10, ":BITS.", 6) == 0) return (1); return (0); } static int filt_t (pfstate_t *pf) { char src[AX25_MAX_ADDR_LEN]; char *infop = NULL; char *f; memset (src, 0, sizeof(src)); ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, src); (void) ax25_get_info (pf->pp, (unsigned char **)(&infop)); assert (infop != NULL); for (f = pf->token_str + 2; *f != '\0'; f++) { switch (*f) { case 'p': /* Position */ if (*infop == '!') return (1); if (*infop == '/') return (1); if (*infop == '=') return (1); if (*infop == '@') return (1); if (*infop == '\'') return (1); // MIC-E if (*infop == '`') return (1); // MIC-E // What if we have "_" symbol code for weather? // Still consider as position. // The same packet can match more than one type here. break; case 'o': /* Object */ if (*infop == ';') return (1); break; case 'i': /* Item */ if (*infop == ')') return (1); break; case 'm': /* Message */ if (*infop == ':' && ! is_telem_metadata(infop)) return (1); break; case 'q': /* Query */ if (*infop == '?') return (1); break; case 'c': /* station Capabilities - my extension */ /* Most often used for IGate statistics. */ if (*infop == '<') return (1); break; case 's': /* Status */ if (*infop == '>') return (1); break; case 't': /* Telemetry */ if (*infop == 'T') return (1); if (is_telem_metadata(infop)) return (1); break; case 'u': /* User-defined */ if (*infop == '{') return (1); break; case 'h': /* third party Header - my extension */ if (*infop == '}') return (1); break; case 'w': /* Weather */ if (*infop == '*') return (1); // Peet Bros if (*infop == '_') return (1); // Weather report, no position. if (strncmp(infop, "!!", 2) == 0) return(1); // Ultimeter 2000. /* '$' is normally raw GPS. Check for special case. */ if (strncmp(infop, "$ULTW", 5) == 0) return (1); /* Positions !=/@ with symbol code _ are weather. */ if (strchr("!=/@", *infop) != NULL && pf->decoded.g_symbol_code == '_') return (1); /* Object with _ symbol is also weather. APRS protocol spec page 66. */ if (*infop == ';' && pf->decoded.g_symbol_code == '_') return (1); // TODO: need more test cases at end for new weather cases. break; case 'n': /* NWS format */ /* * This is the interesting case. * The source must be exactly 6 upper case letters, no SSID. */ if (strlen(src) != 6) break; if (! isupper(src[0])) break; if (! isupper(src[1])) break; if (! isupper(src[2])) break; if (! isupper(src[3])) break; if (! isupper(src[4])) break; if (! isupper(src[5])) break; /* * We can have a "message" with addressee starting with NWS, SKY, or BOM (Australian version.) */ if (strncmp(infop, ":NWS", 4) == 0) return (1); if (strncmp(infop, ":SKY", 4) == 0) return (1); if (strncmp(infop, ":BOM", 4) == 0) return (1); /* * Or we can have an object. * It's not exactly clear how to distiguish this from other objects. * It looks like the first 3 characters of the source should be the same * as the first 3 characters of the addressee. */ if (infop[0] == ';' && infop[1] == src[0] && infop[2] == src[1] && infop[3] == src[2]) return (1); break; default: print_error (pf, "Invalid letter in t/ filter.\n"); return (-1); break; } } return (0); /* Didn't match anything. Reject */ } /* end filt_t */ /*------------------------------------------------------------------------------ * * Name: filt_r * * Purpose: Is it in range (kilometers) of given location. * * Inputs: pf - Pointer to current state information. * token_str should contain something of format: * * r/lat/lon/dist * * We also need to know the location (if any) from the packet. * * decoded.g_lat & decoded.g_lon * * Outputs: sdist - Distance as a string for troubleshooting. * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: * *------------------------------------------------------------------------------*/ static int filt_r (pfstate_t *pf, char *sdist) { char str[MAX_TOKEN_LEN]; char *cp; char sep[2]; char *v; double dlat, dlon, ddist, km; strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; if (pf->decoded.g_lat == G_UNKNOWN || pf->decoded.g_lon == G_UNKNOWN) { return (0); } v = strsep (&cp, sep); if (v == NULL) { print_error (pf, "Missing latitude for Range filter."); return (-1); } dlat = atof(v); v = strsep (&cp, sep); if (v == NULL) { print_error (pf, "Missing longitude for Range filter."); return (-1); } dlon = atof(v); v = strsep (&cp, sep); if (v == NULL) { print_error (pf, "Missing distance for Range filter."); return (-1); } ddist = atof(v); km = ll_distance_km (dlat, dlon, pf->decoded.g_lat, pf->decoded.g_lon); sprintf (sdist, "%.2f km", km); if (km <= ddist) { return (1); } return (0); } /*------------------------------------------------------------------------------ * * Name: filt_s * * Purpose: Filter by symbol. * * Inputs: pf - Pointer to current state information. * token_str should contain something of format: * * s/pri/alt/over * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: * * s/pri * s/pri/alt * s/pri/alt/ * s/pri/alt/over * * "pri" is zero or more symbols from the primary symbol set. * Symbol codes are any printable ASCII character other than | or ~. * (Zero symbols here would be sensible only if later alt part is specified.) * "alt" is one or more symbols from the alternate symbol set. * "over" is overlay characters for the alternate symbol set. * Only upper case letters, digits, and \ are allowed here. * If the last part is not specified, any overlay or lack of overlay, is ignored. * If the last part is specified, only the listed overlays will match. * An explicit lack of overlay is represented by the \ character. * * Examples: * s/O Balloon. * s/-> House or car from primary symbol table. * * s//# Alternate table digipeater, with or without overlay. * s//#/\ Alternate table digipeater, only if no overlay. * s//#/SL1 Alternate table digipeater, with overlay S, L, or 1. * s//#/SL\ Alternate table digipeater, with S, L, or no overlay. * * s/s/s Any variation of watercraft. Either symbol table. With or without overlay. * s/s/s/ Ship or ship sideview, only if no overlay. * s//s/J Jet Ski. * * What if you want to use the / symbol when / is being used as a delimiter here? Recall that you * can use some other special character after the initial lower case letter and this becomes the * delimiter for the rest of the specification. * * Examples: * * s:/ Red Dot. * s::/ Waypoint Destination, with or without overlay. * s:/:/ Either Red Dot or Waypoint Destination. * s:/:/: Either Red Dot or Waypoint Destination, no overlay. * * Bad example: * * Someone tried using this to include ballons: s/'/O/-/#/_ * probably following the buddy filter pattern of / between each alternative. * There should be an error message because it has more than 3 delimiter characters. * * *------------------------------------------------------------------------------*/ static int filt_s (pfstate_t *pf) { char str[MAX_TOKEN_LEN]; char *cp; char sep[2]; // Delimiter character. Typically / but it could be different. char *pri = NULL, *alt = NULL, *over = NULL, *extra = NULL; char *x; strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; // First, separate the parts and do a strict syntax check. pri = strsep (&cp, sep); if (pri != NULL) { // Zero length is acceptable if alternate symbol(s) specified. Will check that later. for (x = pri; *x != '\0'; x++) { if ( ! isprint(*x) || *x == '|' || *x == '~') { print_error (pf, "Symbol filter, primary must be printable ASCII character(s) other than | or ~."); return (-1); } } alt = strsep (&cp, sep); if (alt != NULL) { // Zero length after second / would be pointless. if (strlen(alt) == 0) { print_error (pf, "Nothing specified for alternate symbol table."); return (-1); } for (x = alt; *x != '\0'; x++) { if ( ! isprint(*x) || *x == '|' || *x == '~') { print_error (pf, "Symbol filter, alternate must be printable ASCII character(s) other than | or ~."); return (-1); } } over = strsep (&cp, sep); if (over != NULL) { // Zero length is acceptable and is not the same as missing. for (x = over; *x != '\0'; x++) { if ( (! isupper(*x)) && (! isdigit(*x)) && *x != '\\') { print_error (pf, "Symbol filter, overlay must be upper case letter, digit, or \\."); return (-1); } } extra = strsep (&cp, sep); if (extra != NULL) { print_error (pf, "More than 3 delimiter characters in Symbol filter."); return (-1); } } } else { // No alt part is OK if at least one primary symbol was specified. if (strlen(pri) == 0) { print_error (pf, "No symbols specified for Symbol filter."); return (-1); } } } else { print_error (pf, "Missing arguments for Symbol filter."); return (-1); } // This applies only for Position, Object, Item. // decode_aprs() should set symbol code to space to mean undefined. if (pf->decoded.g_symbol_code == ' ') { return (0); } // Look for Primary symbols. if (pf->decoded.g_symbol_table == '/') { if (pri != NULL && strlen(pri) > 0) { return (strchr(pri, pf->decoded.g_symbol_code) != NULL); } } if (alt == NULL) { return (0); } //printf ("alt=\"%s\" sym='%c'\n", alt, pf->decoded.g_symbol_code); // Look for Alternate symbols. if (strchr(alt, pf->decoded.g_symbol_code) != NULL) { // We have a match but that might not be enough. // We must see if there was an overlay part specified. if (over != NULL) { if (strlen(over) > 0) { // Non-zero length overlay part was specified. // Need to match one of them. return (strchr(over, pf->decoded.g_symbol_table) != NULL); } else { // Zero length overlay part was specified. // We must have no overlay, i.e. table is \. return (pf->decoded.g_symbol_table == '\\'); } } else { // No check of overlay part. Just make sure it is not primary table. return (pf->decoded.g_symbol_table != '/'); } } return (0); } /* end filt_s */ /*------------------------------------------------------------------------------ * * Name: filt_i * * Purpose: IGate messaging default behavior. * * Inputs: pf - Pointer to current state information. * token_str should contain something of format: * * i/time/hops/lat/lon/km * * Returns: 1 = yes * 0 = no * -1 = error detected * * Description: Selection is based on time since last heard on RF, and distance * in terms of digipeater hops and/or phyiscal location. * * i/time * i/time/hops * i/time/hops/lat/lon/km * * * "time" is maximum number of minutes since message addressee was last heard. * This is required. * * "hops" is maximum number of digpeater hops. (i.e. 0 for heard directly). * If hops is not specified, the maximum transmit digipeater hop count, * from the IGTXVIA configuration will be used. * The rest is distanced, in kilometers, from given point. * * Examples: * i/60/0 Heard in past 60 minutes directly. * i/45 Past 45 minutes, default max digi hops. * i/30/3 Default time, max 3 digi hops. * i/30/8/42.6/-71.3/50. * * * It only makes sense to use this for the IS>RF direction. * The basic idea is that we want to transmit a "message" only if the * addressee has been heard recently and is not too far away. * * After passing along a "message" we will also allow the next * position report from the sender of the "message." * That is done somewhere else. We are not concerned with it here. * *------------------------------------------------------------------------------*/ static int filt_i (pfstate_t *pf) { char str[MAX_TOKEN_LEN]; char *cp; char sep[2]; char *v; int heardtime = 30; #if PFTEST int maxhops = 2; #else int maxhops = save_igate_config_p->max_digi_hops; // from IGTXVIA config. #endif double dlat = G_UNKNOWN; double dlon = G_UNKNOWN; double km = G_UNKNOWN; char src[AX25_MAX_ADDR_LEN]; char *infop = NULL; int info_len; //char *f; //char addressee[AX25_MAX_ADDR_LEN]; strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; // Get parameters or defaults. v = strsep (&cp, sep); if (v != NULL && strlen(v) > 0) { heardtime = atoi(v); } else { print_error (pf, "Missing time limit for IGate message filter."); return (-1); } v = strsep (&cp, sep); if (v != NULL) { if (strlen(v) > 0) { maxhops = atoi(v); } else { print_error (pf, "Missing max digipeater hops for IGate message filter."); return (-1); } v = strsep (&cp, sep); if (v != NULL && strlen(v) > 0) { dlat = atof(v); v = strsep (&cp, sep); if (v != NULL && strlen(v) > 0) { dlon = atof(v); } else { print_error (pf, "Missing longitude for IGate message filter."); return (-1); } v = strsep (&cp, sep); if (v != NULL && strlen(v) > 0) { km = atof(v); } else { print_error (pf, "Missing distance, in km, for IGate message filter."); return (-1); } } v = strsep (&cp, sep); if (v != NULL) { print_error (pf, "Something unexpected after distance for IGate message filter."); return (-1); } } #if PFTEST text_color_set(DW_COLOR_DEBUG); dw_printf ("debug: IGate message filter, %d minutes, %d hops, %.2f %.2f %.2f km\n", heardtime, maxhops, dlat, dlon, km); #endif /* * Get source address and info part. * Addressee has already been extracted into pf->decoded.g_addressee. */ memset (src, 0, sizeof(src)); ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, src); info_len = ax25_get_info (pf->pp, (unsigned char **)(&infop)); if (infop == NULL) return (0); if (info_len < 1) return (0); // Determine packet type. We are interested only in "message." // Telemetry metadata is not considered a message. if (*infop != ':') return (0); if (is_telem_metadata(infop)) return (0); #if defined(PFTEST) || defined(DIGITEST) // TODO: test functionality too, not just syntax. (void)dlat; // Suppress set and not used warning. (void)dlon; (void)km; (void)maxhops; (void)heardtime; return (1); #else /* * Condition 1: * "the receiving station has been heard within range within a predefined time * period (range defined as digi hops, distance, or both)." */ int was_heard = mheard_was_recently_nearby ("addressee", pf->decoded.g_addressee, heardtime, maxhops, dlat, dlon, km); if ( ! was_heard) return (0); /* * Condition 2: * "the sending station has not been heard via RF within a predefined time period * (packets gated from the Internet by other stations are excluded from this test)." * * This is the part I'm not so sure about. * I guess the intention is that if the sender can be heard over RF, then the addressee * might hear the sender without the help of Igate stations. * Suppose the sender was 1 digipeater hop to the west and the addressee was 1 digipeater hop to the east. * I can communicate with each of them with 1 digipeater hop but for them to reach each other, they * might need 3 hops and using that many is generally frowned upon and rare. * * Maybe we could compromise here and say the sender must have been heard directly. * It sent the message currently being processed so we must have heard it very recently, i.e. in * the past minute, rather than the usual 30 or 60 minutes for the addressee. */ was_heard = mheard_was_recently_nearby ("source", src, 1, 0, G_UNKNOWN, G_UNKNOWN, G_UNKNOWN); if (was_heard) return (0); return (1); #endif } /* end filt_i */ /*------------------------------------------------------------------- * * Name: print_error * * Purpose: Print error message with context so someone can figure out what caused it. * * Inputs: pf - Pointer to current state information. * * str - Specific error message. * *--------------------------------------------------------------------*/ static void print_error (pfstate_t *pf, char *msg) { char intro[50]; if (pf->from_chan == MAX_CHANS) { if (pf->to_chan == MAX_CHANS) { snprintf (intro, sizeof(intro), "filter[IG,IG]: "); } else { snprintf (intro, sizeof(intro), "filter[IG,%d]: ", pf->to_chan); } } else { if (pf->to_chan == MAX_CHANS) { snprintf (intro, sizeof(intro), "filter[%d,IG]: ", pf->from_chan); } else { snprintf (intro, sizeof(intro), "filter[%d,%d]: ", pf->from_chan, pf->to_chan); } } text_color_set (DW_COLOR_ERROR); dw_printf ("%s%s\n", intro, pf->filter_str); dw_printf ("%*s\n", (int)(strlen(intro) + pf->tokeni + 1), "^"); dw_printf ("%s\n", msg); } #if PFTEST /*------------------------------------------------------------------- * * Name: main & pftest * * Purpose: Unit test for packet filtering. * * Usage: gcc -Wall -o pftest -DPFTEST pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o latlong.o symbols.o telemetry.o tt_text.c misc.a regex.a && ./pftest * * *--------------------------------------------------------------------*/ static int error_count = 0; static void pftest (int test_num, char *filter, char *packet, int expected); int main () { dw_printf ("Quick test for packet filtering.\n"); dw_printf ("Some error messages are normal. Look at the final success/fail message.\n"); pftest (1, "", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (2, "0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (3, "1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (10, "0 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (11, "0 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (12, "1 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (13, "1 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (14, "0 | 0 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (20, "0 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (21, "0 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (22, "1 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (23, "1 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (24, "1 & 1 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (24, "1 & 0 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (24, "1 & 1 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (30, "0 | ! 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (31, "! 1 | ! 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (32, "! ! 1 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (33, "1 | ! ! 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (40, "1 &(!0 |0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (41, "0 |(!0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (42, "1 |(!!0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (42, "(!(1 ) & (1 ))", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (50, "b/W2UB/WB2OSZ-5/N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (51, "b/W2UB/WB2OSZ-14/N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (52, "b#W2UB#WB2OSZ-5#N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (53, "b#W2UB#WB2OSZ-14#N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (60, "o/HOME", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home *111111z4237.14N/07120.83W-Chelmsford MA", 0); pftest (61, "o/home", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home *111111z4237.14N/07120.83W-Chelmsford MA", 1); pftest (62, "o/HOME", "HOME>APDW12,WIDE1-1,WIDE2-1:;AWAY *111111z4237.14N/07120.83W-Chelmsford MA", 0); pftest (63, "o/WB2OSZ-5", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (64, "o/HOME", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:)home!4237.14N/07120.83W-Chelmsford MA", 0); pftest (65, "o/home", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:)home!4237.14N/07120.83W-Chelmsford MA", 1); pftest (70, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (71, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1*,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (72, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (73, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3*,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (74, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4*:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (75, "d/DIGI9/DIGI2", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (80, "g/W2UB", "WB2OSZ-5>APDW12::W2UB :text", 1); pftest (81, "g/W2UB/W2UB-*", "WB2OSZ-5>APDW12::W2UB-9 :text", 1); pftest (82, "g/W2UB/*", "WB2OSZ-5>APDW12::XXX :text", 1); pftest (83, "g/W2UB/W*UB", "WB2OSZ-5>APDW12::W2UB-9 :text", -1); pftest (84, "g/W2UB*", "WB2OSZ-5>APDW12::W2UB-9 :text", 1); pftest (85, "g/W2UB*", "WB2OSZ-5>APDW12::W2UBZZ :text", 1); pftest (86, "g/W2UB", "WB2OSZ-5>APDW12::W2UB-9 :text", 0); pftest (87, "g/*", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (88, "g/W*", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (90, "u/APWW10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 1); pftest (91, "u/TRSY3T", "W1WRA-7>TRSY3T,WIDE1-1,WIDE2-1:`c-:l!hK\\>\"4b}=<0x0d>", 0); pftest (92, "u/APDW11/APDW12", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (93, "u/APDW", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); // rather sparse coverage of the cases pftest (100, "t/mqt", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (101, "t/mqtp", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (102, "t/mqtp", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home *111111z4237.14N/07120.83W-Chelmsford MA", 0); pftest (103, "t/mqop", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home *111111z4237.14N/07120.83W-Chelmsford MA", 1); pftest (104, "t/p", "W1WRA-7>TRSY3T,WIDE1-1,WIDE2-1:`c-:l!hK\\>\"4b}=<0x0d>", 1); pftest (104, "t/s", "KB1CHU-13>APWW10,W1CLA-1*,WIDE2-1:>FN42pb/_DX: W1MHL 36.0mi 306<0xb0> 13:24 4223.32N 07115.23W", 1); pftest (110, "t/p", "N8VIM>APN391,AB1OC-10,W1MRA*,WIDE2:$ULTW0000000001110B6E27F4FFF3897B0001035E004E04DD00030000<0x0d><0x0a>", 0); pftest (111, "t/w", "N8VIM>APN391,AB1OC-10,W1MRA*,WIDE2:$ULTW0000000001110B6E27F4FFF3897B0001035E004E04DD00030000<0x0d><0x0a>", 1); pftest (112, "t/t", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 0); pftest (113, "t/w", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 1); /* Telemetry metadata is a special case of message. */ pftest (114, "t/t", "KJ4SNT>APMI04::KJ4SNT :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 1); pftest (115, "t/m", "KJ4SNT>APMI04::KJ4SNT :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 0); pftest (116, "t/t", "KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000", 1); pftest (120, "t/p", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 0); pftest (122, "t/p", "CWAPID>APRS::SKYCWA :DDHHMMz,ADVISETYPE,zcs{seq#", 0); pftest (123, "t/p", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0); pftest (124, "t/n", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 1); pftest (125, "t/n", "CWAPID>APRS::SKYCWA :DDHHMMz,ADVISETYPE,zcs{seq#", 1); pftest (126, "t/n", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 1); pftest (127, "t/", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0); pftest (128, "t/c", "S0RCE>DEST:DEST:DEST:}thirdpartyheaderwhatever", 1); pftest (131, "t/c", "S0RCE>DEST:}thirdpartyheaderwhatever", 0); pftest (140, "r/42.6/-71.3/10", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (141, "r/42.6/-71.3/10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0); pftest (145, "( t/t & b/WB2OSZ ) | ( t/o & ! r/42.6/-71.3/1 )", "WB2OSZ>APDW12:;home *111111z4237.14N/07120.83W-Chelmsford MA", 1); pftest (150, "s/->", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (151, "s/->", "WB2OSZ-5>APDW12:!4237.14N/07120.83W-PHG7140Chelmsford MA", 1); pftest (152, "s/->", "WB2OSZ-5>APDW12:!4237.14N/07120.83W>PHG7140Chelmsford MA", 1); pftest (153, "s/->", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W>PHG7140Chelmsford MA", 0); pftest (154, "s//#", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (155, "s//#", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1); pftest (156, "s//#", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0); pftest (157, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (158, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1); pftest (159, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0); pftest (160, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (161, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 0); pftest (162, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0); pftest (163, "s//#/LS\\", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1); pftest (170, "s:/", "WB2OSZ-5>APDW12:!4237.14N/07120.83W/PHG7140Chelmsford MA", 1); pftest (171, "s:/", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W/PHG7140Chelmsford MA", 0); pftest (172, "s::/", "WB2OSZ-5>APDW12:!4237.14N/07120.83W/PHG7140Chelmsford MA", 0); pftest (173, "s::/", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W/PHG7140Chelmsford MA", 1); pftest (174, "s:/:/", "WB2OSZ-5>APDW12:!4237.14N/07120.83W/PHG7140Chelmsford MA", 1); pftest (175, "s:/:/", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W/PHG7140Chelmsford MA", 1); pftest (176, "s:/:/", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 1); pftest (177, "s:/:/:X", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 1); // FIXME: Different on Windows and 64 bit Linux. //pftest (178, "s:/:/:", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 1); pftest (179, "s:/:/:\\", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 0); pftest (180, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (181, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1*,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (182, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1); pftest (183, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3*,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (184, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4*:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); pftest (185, "v/DIGI9/DIGI2", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0); /* Test error reporting. */ pftest (200, "x/", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (201, "t/w & ( t/w | t/w ", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (202, "t/w ) ", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (203, "!", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (203, "t/w t/w", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (204, "r/42.6/-71.3", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", -1); pftest (220, "i/30/8/42.6/-71.3/50", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (222, "i/30/8/42.6/-71.3/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); pftest (223, "i/30/8/42.6/-71.3", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); pftest (224, "i/30/8/42.6/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); pftest (225, "i/30/8/42.6", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); pftest (226, "i/30/8/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (227, "i/30/8", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); // FIXME: behaves differently on Windows and Linux. Why? // On Windows we have our own version of strsep because it's not in the MS library. // It must behave differently than the Linux version when nothing follows the last separator. //pftest (228, "i/30/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (229, "i/30", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); pftest (230, "i/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1); pftest (240, "s/", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (241, "s/'/O/-/#/_", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (242, "s/O/O/c", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (243, "s/O/O/1/2", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (244, "s/O/|/1", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (245, "s//", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (246, "s///", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); // TODO: to be continued... if (error_count > 0) { text_color_set (DW_COLOR_ERROR); dw_printf ("\nPacket Filtering Test - FAILED!\n"); exit (EXIT_FAILURE); } text_color_set (DW_COLOR_REC); dw_printf ("\nPacket Filtering Test - SUCCESS!\n"); exit (EXIT_SUCCESS); } static void pftest (int test_num, char *filter, char *monitor, int expected) { int result; packet_t pp; text_color_set (DW_COLOR_DEBUG); dw_printf ("test number %d\n", test_num); pp = ax25_from_text (monitor, 1); assert (pp != NULL); result = pfilter (0, 0, filter, pp, 1); if (result != expected) { text_color_set (DW_COLOR_ERROR); dw_printf ("Unexpected result for test number %d\n", test_num); error_count++; } ax25_delete (pp); } #endif /* if TEST */ /* end pfilter.c */ direwolf-1.5+dfsg/pfilter.h000066400000000000000000000004041347750676600157520ustar00rootroot00000000000000 /* pfilter.h */ #include "igate.h" // for igate_config_s void pfilter_init (struct igate_config_s *p_igate_config, int debug_level); int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs); int is_telem_metadata (char *infop);direwolf-1.5+dfsg/ptt.c000066400000000000000000001275261347750676600151260ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017 John Langner, WB2OSZ // // 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, see . /*------------------------------------------------------------------ * * Module: ptt.c * * Purpose: Activate the output control lines for push to talk (PTT) and other purposes. * * Description: Traditionally this is done with the RTS signal of the serial port. * * If we have two radio channels and only one serial port, DTR * can be used for the second channel. * * If __WIN32__ is defined, we use the Windows interface. * Otherwise we use the Linux interface. * * Version 0.9: Add ability to use GPIO pins on Linux. * * Version 1.1: Add parallel printer port for x86 Linux only. * * This is hardcoded to use the primary motherboard parallel * printer port at I/O address 0x378. This might work with * a PCI card configured to use the same address if the * motherboard does not have a built in parallel port. * It won't work with a USB-to-parallel-printer-port adapter. * * Version 1.2: More than two radio channels. * Generalize for additional signals besides PTT. * * Version 1.3: HAMLIB support. * * Version 1.4: The spare "future" indicator is now used when connected to another station. * * Take advantage of the new 'gpio' group and new /sys/class/gpio protections in Raspbian Jessie. * * Handle more complicated gpio node names for CubieBoard, etc. * * Version 1.5: Ability to use GPIO pins of CM108/CM119 for PTT signal. * * * References: http://www.robbayer.com/files/serial-win.pdf * * https://www.kernel.org/doc/Documentation/gpio.txt * *---------------------------------------------------------------*/ /* A growing number of people have been asking about support for the DMK URI or the similar RB-USB RIM. These use a C-Media CM108/CM119 with an interesting addition, a GPIO pin is used to drive PTT. Here is some related information. DMK URI: http://www.dmkeng.com/URI_Order_Page.htm http://dmkeng.com/images/URI%20Schematic.pdf RB-USB RIM: http://www.repeater-builder.com/products/usb-rim-lite.html http://www.repeater-builder.com/voip/pdf/cm119-datasheet.pdf Homebrew versions of the same idea: http://images.ohnosec.org/usbfob.pdf http://www.qsl.net/kb9mwr/projects/voip/usbfob-119.pdf http://rtpdir.weebly.com/uploads/1/6/8/7/1687703/usbfob.pdf http://www.repeater-builder.com/projects/fob/USB-Fob-Construction.pdf Applications that have support for this: http://docs.allstarlink.org/drupal/ http://soundmodem.sourcearchive.com/documentation/0.16-1/ptt_8c_source.html https://github.com/N0NB/hamlib/blob/master/src/cm108.c#L190 http://permalink.gmane.org/gmane.linux.hams.hamlib.devel/3420 Information about the "hidraw" device: http://unix.stackexchange.com/questions/85379/dev-hidraw-read-permissions http://www.signal11.us/oss/udev/ http://www.signal11.us/oss/hidapi/ https://github.com/signal11/hidapi/blob/master/libusb/hid.c http://stackoverflow.com/questions/899008/howto-write-to-the-gpio-pin-of-the-cm108-chip-in-linux https://www.kernel.org/doc/Documentation/hid/hidraw.txt https://github.com/torvalds/linux/blob/master/samples/hidraw/hid-example.c Similar chips: SSS1621, SSS1623 https://irongarment.wordpress.com/2011/03/29/cm108-compatible-chips-with-gpio/ Here is an attempt to add direct CM108 support. Seems to be hardcoded for only a single USB audio adapter. https://github.com/donothingloop/direwolf_cm108 In version 1.3, we add HAMLIB support which should be able to do this in a roundabout way. (Linux only at this point.) This is documented in the User Guide, section called, "Hamlib PTT Example 2: Use GPIO of USB audio adapter. (e.g. DMK URI)" It's rather involved and the explantion doesn't cover the case of multiple USB-Audio adapters. It would be nice to have a little script which lists all of the USB-Audio adapters and the corresponding /dev/hidraw device. ( We now have it. The included "cm108" application. ) In version 1.5 we have a flexible, easy to use implementation for Linux. Windows would be a lot of extra work because USB devices are nothing like Linux. We'd be starting from scratch to figure out how to do it. */ #include "direwolf.h" // should be first. This includes windows.h. #include #include #include #include #include #include #if __WIN32__ #else #include #include #include #include #include #include #include #include #include #ifdef USE_HAMLIB #include #endif #ifdef USE_CM108 #include "cm108.h" #endif /* So we can have more common code for fd. */ typedef int HANDLE; #define INVALID_HANDLE_VALUE (-1) #endif #include "textcolor.h" #include "audio.h" #include "ptt.h" #include "dlq.h" #if __WIN32__ #define RTS_ON(fd) EscapeCommFunction(fd,SETRTS); #define RTS_OFF(fd) EscapeCommFunction(fd,CLRRTS); #define DTR_ON(fd) EscapeCommFunction(fd,SETDTR); #define DTR_OFF(fd) EscapeCommFunction(fd,CLRDTR); #else #define RTS_ON(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff |= TIOCM_RTS; ioctl (fd, TIOCMSET, &stuff); } #define RTS_OFF(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff &= ~TIOCM_RTS; ioctl (fd, TIOCMSET, &stuff); } #define DTR_ON(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff |= TIOCM_DTR; ioctl (fd, TIOCMSET, &stuff); } #define DTR_OFF(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff &= ~TIOCM_DTR; ioctl (fd, TIOCMSET, &stuff); } #define LPT_IO_ADDR 0x378 #endif static struct audio_s *save_audio_config_p; /* Save config information for later use. */ static int ptt_debug_level = 0; void ptt_set_debug(int debug) { ptt_debug_level = debug; } /*------------------------------------------------------------------- * * Name: get_access_to_gpio * * Purpose: Try to get access to the GPIO device. * * Inputs: path - Path to device node. * /sys/class/gpio/export * /sys/class/gpio/unexport * /sys/class/gpio/gpio??/direction * /sys/class/gpio/gpio??/value * * Description: First see if we have access thru the usual uid/gid/mode method. * If that fails, we try a hack where we use "sudo chmod ..." to open up access. * That requires that sudo be configured to work without a password. * That's the case for 'pi' user in Raspbian but not not be for other boards / operating systems. * * Debug: Use the "-doo" command line option. * *------------------------------------------------------------------*/ #ifndef __WIN32__ #define MAX_GROUPS 50 static void get_access_to_gpio (const char *path) { static int my_uid = -1; static int my_gid = -1; static gid_t my_groups[MAX_GROUPS]; static int num_groups = 0; static int first_time = 1; struct stat finfo; int i; char cmd[80]; int err; /* * Does path even exist? */ if (stat(path, &finfo) < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't get properties of %s.\n", path); dw_printf ("This system is not configured with the GPIO user interface.\n"); dw_printf ("Use a different method for PTT control.\n"); exit (1); } if (first_time) { // No need to fetch same information each time. Cache it. my_uid = geteuid(); my_gid = getegid(); num_groups = getgroups (MAX_GROUPS, my_groups); if (num_groups < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("getgroups() failed to get supplementary groups, errno=%d\n", errno); num_groups = 0; } first_time = 0; } if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("%s: uid=%d, gid=%d, mode=o%o\n", path, finfo.st_uid, finfo.st_gid, finfo.st_mode); dw_printf ("my uid=%d, gid=%d, supplementary groups=", my_uid, my_gid); for (i = 0; i < num_groups; i++) { dw_printf (" %d", my_groups[i]); } dw_printf ("\n"); } /* * Do we have permission to access it? * * On Debian 7 (Wheezy) we see this: * * $ ls -l /sys/class/gpio/export * --w------- 1 root root 4096 Feb 27 12:31 /sys/class/gpio/export * * * Only root can write to it. * Our work-around is change the protection so that everyone can write. * This requires that the current user can use sudo without a password. * This has been the case for the predefined "pi" user but can be a problem * when people add new user names. * Other operating systems could have different default configurations. * * A better solution is available in Debian 8 (Jessie). The group is now "gpio" * so anyone in that group can now write to it. * * $ ls -l /sys/class/gpio/export * -rwxrwx--- 1 root gpio 4096 Mar 4 21:12 /sys/class/gpio/export * * * First see if we can access it by the usual file protection rules. * If not, we will try the "sudo chmod go+rw ..." hack. * */ /* * Do I have access? * We could just try to open for write but this gives us more debugging information. */ if ((my_uid == finfo.st_uid) && (finfo.st_mode & S_IWUSR)) { // user write 00200 if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("My uid matches and we have user write permission.\n"); } return; } if ((my_gid == finfo.st_gid) && (finfo.st_mode & S_IWGRP)) { // group write 00020 if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("My primary gid matches and we have group write permission.\n"); } return; } for (i = 0; i < num_groups; i++) { if ((my_groups[i] == finfo.st_gid) && (finfo.st_mode & S_IWGRP)) { // group write 00020 if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("My supplemental group %d matches and we have group write permission.\n", my_groups[i]); } return; } } if (finfo.st_mode & S_IWOTH) { // other write 00002 if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("We have other write permission.\n"); } return; } /* * We don't have permission. * Try a hack which requires that the user be set up to use sudo without a password. */ if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_ERROR); // debug message but different color so it stands out. dw_printf ("Trying 'sudo chmod go+rw %s' hack.\n", path); } snprintf (cmd, sizeof(cmd), "sudo chmod go+rw %s", path); err = system (cmd); (void)err; // suppress warning about not using result. /* * I don't trust status coming back from system() so we will check the mode again. */ if (stat(path, &finfo) < 0) { /* Unexpected because we could do it before. */ text_color_set(DW_COLOR_ERROR); dw_printf ("This system is not configured with the GPIO user interface.\n"); dw_printf ("Use a different method for PTT control.\n"); exit (1); } /* Did we succeed in changing the protection? */ if ( (finfo.st_mode & 0266) != 0266) { text_color_set(DW_COLOR_ERROR); dw_printf ("You don't have the necessary permission to access GPIO.\n"); dw_printf ("There are three different solutions: \n"); dw_printf (" 1. Run as root. (not recommended)\n"); dw_printf (" 2. If operating system has 'gpio' group, add your user id to it.\n"); dw_printf (" 3. Configure your user id for sudo without a password.\n"); dw_printf ("\n"); dw_printf ("Read the documentation and try -doo command line option for debugging details.\n"); exit (1); } } #endif /*------------------------------------------------------------------- * * Name: export_gpio * * Purpose: Tell the GPIO subsystem to export a GPIO line for * us to use, and set the initial state of the GPIO. * * Inputs: ch - Radio Channel. * ot - Output type. * invert: - Is the GPIO active low? * direction: - 0 for input, 1 for output * * Outputs: out_gpio_name - in the audio configuration structure. * in_gpio_name * *------------------------------------------------------------------*/ #ifndef __WIN32__ void export_gpio(int ch, int ot, int invert, int direction) { HANDLE fd; const char gpio_export_path[] = "/sys/class/gpio/export"; char gpio_direction_path[80]; char gpio_value_path[80]; char stemp[16]; int gpio_num; char *gpio_name; // Raspberry Pi was easy. GPIO 24 has the name gpio24. // Others, such as the Cubieboard, take a little more effort. // The name might be gpio24_ph11 meaning connector H, pin 11. // When we "export" GPIO number, we will store the corresponding // device name for future use when we want to access it. if (direction) { gpio_num = save_audio_config_p->achan[ch].octrl[ot].out_gpio_num; gpio_name = save_audio_config_p->achan[ch].octrl[ot].out_gpio_name; } else { gpio_num = save_audio_config_p->achan[ch].ictrl[ot].in_gpio_num; gpio_name = save_audio_config_p->achan[ch].ictrl[ot].in_gpio_name; } get_access_to_gpio (gpio_export_path); fd = open(gpio_export_path, O_WRONLY); if (fd < 0) { // Not expected. Above should have obtained permission or exited. text_color_set(DW_COLOR_ERROR); dw_printf ("Permissions do not allow access to GPIO.\n"); exit (1); } snprintf (stemp, sizeof(stemp), "%d", gpio_num); if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) { int e = errno; /* Ignore EBUSY error which seems to mean */ /* the device node already exists. */ if (e != EBUSY) { text_color_set(DW_COLOR_ERROR); dw_printf ("Error writing \"%s\" to %s, errno=%d\n", stemp, gpio_export_path, e); dw_printf ("%s\n", strerror(e)); exit (1); } } close (fd); /* * Added in release 1.4. * * On the RPi, the device path for GPIO number XX is simply /sys/class/gpio/gpioXX. * * There was a report that it is different for the CubieBoard. For instance * GPIO 61 has gpio61_pi13 in the path. This indicates connector "i" pin 13. * https://github.com/cubieplayer/Cubian/wiki/GPIO-Introduction * * For another similar single board computer, we find the same thing: * https://www.olimex.com/wiki/A20-OLinuXino-LIME#GPIO_under_Linux * * How should we deal with this? Some possibilities: * * (1) The user might explicitly mention the name in direwolf.conf. * (2) We might be able to find the names in some system device config file. * (3) Get a directory listing of /sys/class/gpio then search for a * matching name. Suppose we wanted GPIO 61. First look for an exact * match to "gpio61". If that is not found, look for something * matching the pattern "gpio61_*". * * We are finally implementing the third choice. */ struct dirent **file_list; int num_files; int i; int ok = 0; if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Contents of /sys/class/gpio:\n"); } num_files = scandir ("/sys/class/gpio", &file_list, NULL, alphasort); if (num_files < 0) { // Something went wrong. Fill in the simple expected name and keep going. text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR! Could not get directory listing for /sys/class/gpio\n"); snprintf (gpio_name, MAX_GPIO_NAME_LEN, "gpio%d", gpio_num); num_files = 0; ok = 1; } else { if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); for (i = 0; i < num_files; i++) { dw_printf("\t%s\n", file_list[i]->d_name); } } // Look for exact name gpioNN char lookfor[16]; snprintf (lookfor, sizeof(lookfor), "gpio%d", gpio_num); for (i = 0; i < num_files && ! ok; i++) { if (strcmp(lookfor, file_list[i]->d_name) == 0) { strlcpy (gpio_name, file_list[i]->d_name, MAX_GPIO_NAME_LEN); ok = 1; } } // If not found, Look for gpioNN_* snprintf (lookfor, sizeof(lookfor), "gpio%d_", gpio_num); for (i = 0; i < num_files && ! ok; i++) { if (strncmp(lookfor, file_list[i]->d_name, strlen(lookfor)) == 0) { strlcpy (gpio_name, file_list[i]->d_name, MAX_GPIO_NAME_LEN); ok = 1; } } // Free the storage allocated by scandir(). for (i = 0; i < num_files; i++) { free (file_list[i]); } free (file_list); } /* * We should now have the corresponding node name. */ if (ok) { if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("Path for gpio number %d is /sys/class/gpio/%s\n", gpio_num, gpio_name); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR! Could not find Path for gpio number %d.n", gpio_num); exit (1); } /* * Set output direction and initial state */ snprintf (gpio_direction_path, sizeof(gpio_direction_path), "/sys/class/gpio/%s/direction", gpio_name); get_access_to_gpio (gpio_direction_path); fd = open(gpio_direction_path, O_WRONLY); if (fd < 0) { int e = errno; text_color_set(DW_COLOR_ERROR); dw_printf ("Error opening %s\n", stemp); dw_printf ("%s\n", strerror(e)); exit (1); } char gpio_val[8]; if (direction) { if (invert) { strlcpy (gpio_val, "high", sizeof(gpio_val)); } else { strlcpy (gpio_val, "low", sizeof(gpio_val)); } } else { strlcpy (gpio_val, "in", sizeof(gpio_val)); } if (write (fd, gpio_val, strlen(gpio_val)) != strlen(gpio_val)) { int e = errno; text_color_set(DW_COLOR_ERROR); dw_printf ("Error writing initial state to %s\n", stemp); dw_printf ("%s\n", strerror(e)); exit (1); } close (fd); /* * Make sure that we have access to 'value'. * Do it once here, rather than each time we want to use it. */ snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", gpio_name); get_access_to_gpio (gpio_value_path); } #endif /* not __WIN32__ */ /*------------------------------------------------------------------- * * Name: ptt_init * * Purpose: Open serial port(s) used for PTT signals and set to proper state. * * Inputs: audio_config_p - Structure with communication parameters. * * for each channel we have: * * ptt_method Method for PTT signal. * PTT_METHOD_NONE - not configured. Could be using VOX. * PTT_METHOD_SERIAL - serial (com) port. * PTT_METHOD_GPIO - general purpose I/O. * PTT_METHOD_LPT - Parallel printer port. * PTT_METHOD_HAMLIB - HAMLib rig control. * PTT_METHOD_CM108 - GPIO pins of CM108 etc. USB Audio. * * ptt_device Name of serial port device. * e.g. COM1 or /dev/ttyS0. * HAMLIB can also use hostaddr:port. * Like /dev/hidraw1 for CM108. * * ptt_line RTS or DTR when using serial port. * * out_gpio_num GPIO number. Only used for Linux. * Valid only when ptt_method is PTT_METHOD_GPIO. * * ptt_lpt_bit Bit number for parallel printer port. * Bit 0 = pin 2, ..., bit 7 = pin 9. * Valid only when ptt_method is PTT_METHOD_LPT. * * ptt_invert Invert the signal. * Normally higher voltage means transmit or LED on. * * ptt_model Only for HAMLIB. * 2 to communicate with rigctld. * >= 3 for specific radio model. * -1 guess at what is out there. (AUTO option in config file.) * * Outputs: Remember required information for future use. * * Description: * *--------------------------------------------------------------------*/ static HANDLE ptt_fd[MAX_CHANS][NUM_OCTYPES]; /* Serial port handle or fd. */ /* Could be the same for two channels */ /* if using both RTS and DTR. */ #if USE_HAMLIB static RIG *rig[MAX_CHANS][NUM_OCTYPES]; #endif static char otnames[NUM_OCTYPES][8]; void ptt_init (struct audio_s *audio_config_p) { int ch; HANDLE fd = INVALID_HANDLE_VALUE; #if __WIN32__ #else int using_gpio; #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("ptt_init ( ... )\n"); #endif save_audio_config_p = audio_config_p; strlcpy (otnames[OCTYPE_PTT], "PTT", sizeof(otnames[OCTYPE_PTT])); strlcpy (otnames[OCTYPE_DCD], "DCD", sizeof(otnames[OCTYPE_DCD])); strlcpy (otnames[OCTYPE_CON], "CON", sizeof(otnames[OCTYPE_CON])); for (ch = 0; ch < MAX_CHANS; ch++) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { ptt_fd[ch][ot] = INVALID_HANDLE_VALUE; #if USE_HAMLIB rig[ch][ot] = NULL; #endif if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); dw_printf ("ch=%d, %s method=%d, device=%s, line=%d, gpio=%d, lpt_bit=%d, invert=%d\n", ch, otnames[ot], audio_config_p->achan[ch].octrl[ot].ptt_method, audio_config_p->achan[ch].octrl[ot].ptt_device, audio_config_p->achan[ch].octrl[ot].ptt_line, audio_config_p->achan[ch].octrl[ot].out_gpio_num, audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit, audio_config_p->achan[ch].octrl[ot].ptt_invert); } } } /* * Set up serial ports. */ for (ch = 0; ch < MAX_CHANS; ch++) { if (audio_config_p->achan[ch].valid) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_SERIAL) { #if __WIN32__ #else /* Translate Windows device name into Linux name. */ /* COM1 -> /dev/ttyS0, etc. */ if (strncasecmp(audio_config_p->achan[ch].octrl[ot].ptt_device, "COM", 3) == 0) { int n = atoi (audio_config_p->achan[ch].octrl[ot].ptt_device + 3); text_color_set(DW_COLOR_INFO); dw_printf ("Converted %s device '%s'", audio_config_p->achan[ch].octrl[ot].ptt_device, otnames[ot]); if (n < 1) n = 1; snprintf (audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(audio_config_p->achan[ch].octrl[ot].ptt_device), "/dev/ttyS%d", n-1); dw_printf (" to Linux equivalent '%s'\n", audio_config_p->achan[ch].octrl[ot].ptt_device); } #endif /* Can't open the same device more than once so we */ /* need more logic to look for the case of multiple radio */ /* channels using different pins of the same COM port. */ /* Did some earlier channel use the same device name? */ int same_device_used = 0; int j, k; for (j = ch; j >= 0; j--) { if (audio_config_p->achan[j].valid) { for (k = ((j==ch) ? (ot - 1) : (NUM_OCTYPES-1)); k >= 0; k--) { if (strcmp(audio_config_p->achan[ch].octrl[ot].ptt_device,audio_config_p->achan[j].octrl[k].ptt_device) == 0) { fd = ptt_fd[j][k]; same_device_used = 1; } } } } if ( ! same_device_used) { #if __WIN32__ char bettername[50]; // Bug fix in release 1.1 - Need to munge name for COM10 and up. // http://support.microsoft.com/kb/115831 strlcpy (bettername, audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(bettername)); if (strncasecmp(bettername, "COM", 3) == 0) { int n; n = atoi(bettername+3); if (n >= 10) { strlcpy (bettername, "\\\\.\\", sizeof(bettername)); strlcat (bettername, audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(bettername)); } } fd = CreateFile(bettername, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); #else /* O_NONBLOCK added in version 0.9. */ /* Was hanging with some USB-serial adapters. */ /* https://bugs.launchpad.net/ubuntu/+source/linux/+bug/661321/comments/12 */ fd = open (audio_config_p->achan[ch].octrl[ot].ptt_device, O_RDONLY | O_NONBLOCK); #endif } if (fd != INVALID_HANDLE_VALUE) { ptt_fd[ch][ot] = fd; } else { #if __WIN32__ #else int e = errno; #endif text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR can't open device %s for channel %d PTT control.\n", audio_config_p->achan[ch].octrl[ot].ptt_device, ch); #if __WIN32__ #else dw_printf ("%s\n", strerror(e)); #endif /* Don't try using it later if device open failed. */ audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE; } /* * Set initial state off. * ptt_set will invert output signal if appropriate. */ ptt_set (ot, ch, 0); } /* if serial method. */ } /* for each output type. */ } /* if channel valid. */ } /* For each channel. */ /* * Set up GPIO - for Linux only. */ #if __WIN32__ #else /* * Does any of them use GPIO? */ using_gpio = 0; for (ch=0; chachan[ch].valid) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) { using_gpio = 1; } } for (ot = 0; ot < NUM_ICTYPES; ot++) { if (audio_config_p->achan[ch].ictrl[ot].method == PTT_METHOD_GPIO) { using_gpio = 1; } } } } if (using_gpio) { get_access_to_gpio ("/sys/class/gpio/export"); } /* * We should now be able to create the device nodes for * the pins we want to use. */ for (ch = 0; ch < MAX_CHANS; ch++) { if (save_audio_config_p->achan[ch].valid) { int ot; // output control type, PTT, DCD, CON, ... int it; // input control type for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) { export_gpio(ch, ot, audio_config_p->achan[ch].octrl[ot].ptt_invert, 1); } } for (it = 0; it < NUM_ICTYPES; it++) { if (audio_config_p->achan[ch].ictrl[it].method == PTT_METHOD_GPIO) { export_gpio(ch, it, audio_config_p->achan[ch].ictrl[it].invert, 0); } } } } #endif /* * Set up parallel printer port. * * Restrictions: * Only the primary printer port. * For x86 Linux only. */ #if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) ) for (ch = 0; ch < MAX_CHANS; ch++) { if (save_audio_config_p->achan[ch].valid) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_LPT) { /* Can't open the same device more than once so we */ /* need more logic to look for the case of mutiple radio */ /* channels using different pins of the LPT port. */ /* Did some earlier channel use the same ptt device name? */ int same_device_used = 0; int j, k; for (j = ch; j >= 0; j--) { if (audio_config_p->achan[j].valid) { for (k = ((j==ch) ? (ot - 1) : (NUM_OCTYPES-1)); k >= 0; k--) { if (strcmp(audio_config_p->achan[ch].octrl[ot].ptt_device,audio_config_p->achan[j].octrl[k].ptt_device) == 0) { fd = ptt_fd[j][k]; same_device_used = 1; } } } } if ( ! same_device_used) { fd = open ("/dev/port", O_RDWR | O_NDELAY); } if (fd != INVALID_HANDLE_VALUE) { ptt_fd[ch][ot] = fd; } else { int e = errno; text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Can't open /dev/port for parallel printer port PTT control.\n"); dw_printf ("%s\n", strerror(e)); dw_printf ("You probably don't have adequate permissions to access I/O ports.\n"); dw_printf ("Either run direwolf as root or change these permissions:\n"); dw_printf (" sudo chmod go+rw /dev/port\n"); dw_printf (" sudo setcap cap_sys_rawio=ep `which direwolf`\n"); /* Don't try using it later if device open failed. */ audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE; } /* * Set initial state off. * ptt_set will invert output signal if appropriate. */ ptt_set (ot, ch, 0); } /* if parallel printer port method. */ } /* for each output type */ } /* if valid channel. */ } /* For each channel. */ #endif /* x86 Linux */ #ifdef USE_HAMLIB for (ch = 0; ch < MAX_CHANS; ch++) { if (save_audio_config_p->achan[ch].valid) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_HAMLIB) { if (ot == OCTYPE_PTT) { /* For "AUTO" model, try to guess what is out there. */ if (audio_config_p->achan[ch].octrl[ot].ptt_model == -1) { hamlib_port_t hport; memset (&hport, 0, sizeof(hport)); strlcpy (hport.pathname, audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(hport.pathname)); rig_load_all_backends(); audio_config_p->achan[ch].octrl[ot].ptt_model = rig_probe(&hport); if (audio_config_p->achan[ch].octrl[ot].ptt_model == RIG_MODEL_NONE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't guess rig model number for AUTO option. Run \"rigctl --list\" for a list of model numbers.\n"); continue; } text_color_set(DW_COLOR_INFO); dw_printf ("Hamlib AUTO option detected rig model %d. Run \"rigctl --list\" for a list of model numbers.\n", audio_config_p->achan[ch].octrl[ot].ptt_model); } rig[ch][ot] = rig_init(audio_config_p->achan[ch].octrl[ot].ptt_model); if (rig[ch][ot] == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Unknown rig model %d for hamlib. Run \"rigctl --list\" for a list of model numbers.\n", audio_config_p->achan[ch].octrl[ot].ptt_model); continue; } strlcpy (rig[ch][ot]->state.rigport.pathname, audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(rig[ch][ot]->state.rigport.pathname)); int err = rig_open(rig[ch][ot]); if (err != RIG_OK) { text_color_set(DW_COLOR_ERROR); dw_printf ("Hamlib Rig open error %d: %s\n", err, rigerror(err)); rig_cleanup (rig[ch][ot]); rig[ch][ot] = NULL; continue; } /* Successful. Later code should check for rig[ch][ot] not NULL. */ } else { text_color_set(DW_COLOR_ERROR); dw_printf ("HAMLIB can only be used for PTT. Not DCD or other output.\n"); } } } } } #endif /* * Confirm what is going on with CM108 GPIO output. * Could use some error checking for overlap. */ #if USE_CM108 for (ch = 0; ch < MAX_CHANS; ch++) { if (audio_config_p->achan[ch].valid) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_CM108) { text_color_set(DW_COLOR_INFO); dw_printf ("Using %s GPIO %d for channel %d %s control.\n", audio_config_p->achan[ch].octrl[ot].ptt_device, audio_config_p->achan[ch].octrl[ot].out_gpio_num, ch, otnames[ot]); } } } } #endif /* Why doesn't it transmit? Probably forgot to specify PTT option. */ for (ch=0; chachan[ch].valid) { if(audio_config_p->achan[ch].octrl[OCTYPE_PTT].ptt_method == PTT_METHOD_NONE) { text_color_set(DW_COLOR_INFO); dw_printf ("Note: PTT not configured for channel %d. (Ignore this if using VOX.)\n", ch); } } } } /* end ptt_init */ /*------------------------------------------------------------------- * * Name: ptt_set * * Purpose: Turn output control line on or off. * Originally this was just for PTT, hence the name. * Now that it is more general purpose, it should * probably be renamed something like octrl_set. * * Inputs: ot - Output control type: * OCTYPE_PTT, OCTYPE_DCD, OCTYPE_FUTURE * * chan - channel, 0 .. (number of channels)-1 * * ptt_signal - 1 for transmit, 0 for receive. * * * Assumption: ptt_init was called first. * * Description: Set the RTS or DTR line or GPIO pin. * More positive output corresponds to 1 unless invert is set. * *--------------------------------------------------------------------*/ void ptt_set (int ot, int chan, int ptt_signal) { int ptt = ptt_signal; int ptt2 = ptt_signal; assert (ot >= 0 && ot < NUM_OCTYPES); assert (chan >= 0 && chan < MAX_CHANS); if (ptt_debug_level >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("%s %d = %d\n", otnames[ot], chan, ptt_signal); } assert (chan >= 0 && chan < MAX_CHANS); if ( ! save_audio_config_p->achan[chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error, ptt_set ( %s, %d, %d ), did not expect invalid channel.\n", otnames[ot], chan, ptt); return; } /* * The data link state machine has an interest in activity on the radio channel. * This is a very convenient place to get that information. */ #ifndef TEST dlq_channel_busy (chan, ot, ptt_signal); #endif /* * Inverted output? */ if (save_audio_config_p->achan[chan].octrl[ot].ptt_invert) { ptt = ! ptt; } if (save_audio_config_p->achan[chan].octrl[ot].ptt_invert2) { ptt2 = ! ptt2; } /* * Using serial port? */ if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_SERIAL && ptt_fd[chan][ot] != INVALID_HANDLE_VALUE) { if (save_audio_config_p->achan[chan].octrl[ot].ptt_line == PTT_LINE_RTS) { if (ptt) { RTS_ON(ptt_fd[chan][ot]); } else { RTS_OFF(ptt_fd[chan][ot]); } } else if (save_audio_config_p->achan[chan].octrl[ot].ptt_line == PTT_LINE_DTR) { if (ptt) { DTR_ON(ptt_fd[chan][ot]); } else { DTR_OFF(ptt_fd[chan][ot]); } } /* * Second serial port control line? Typically driven with opposite phase but could be in phase. */ if (save_audio_config_p->achan[chan].octrl[ot].ptt_line2 == PTT_LINE_RTS) { if (ptt2) { RTS_ON(ptt_fd[chan][ot]); } else { RTS_OFF(ptt_fd[chan][ot]); } } else if (save_audio_config_p->achan[chan].octrl[ot].ptt_line2 == PTT_LINE_DTR) { if (ptt2) { DTR_ON(ptt_fd[chan][ot]); } else { DTR_OFF(ptt_fd[chan][ot]); } } /* else neither one */ } /* * Using GPIO? */ #if __WIN32__ #else if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIO) { int fd; char gpio_value_path[80]; char stemp[16]; snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", save_audio_config_p->achan[chan].octrl[ot].out_gpio_name); fd = open(gpio_value_path, O_WRONLY); if (fd < 0) { int e = errno; text_color_set(DW_COLOR_ERROR); dw_printf ("Error opening %s to set %s signal.\n", stemp, otnames[ot]); dw_printf ("%s\n", strerror(e)); return; } snprintf (stemp, sizeof(stemp), "%d", ptt); if (write (fd, stemp, 1) != 1) { int e = errno; text_color_set(DW_COLOR_ERROR); dw_printf ("Error setting GPIO %d for %s\n", save_audio_config_p->achan[chan].octrl[ot].out_gpio_num, otnames[ot]); dw_printf ("%s\n", strerror(e)); } close (fd); } #endif /* * Using parallel printer port? */ #if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) ) if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_LPT && ptt_fd[chan][ot] != INVALID_HANDLE_VALUE) { char lpt_data; //ssize_t n; lseek (ptt_fd[chan][ot], (off_t)LPT_IO_ADDR, SEEK_SET); if (read (ptt_fd[chan][ot], &lpt_data, (size_t)1) != 1) { int e = errno; text_color_set(DW_COLOR_ERROR); dw_printf ("Error reading current state of LPT for channel %d %s\n", chan, otnames[ot]); dw_printf ("%s\n", strerror(e)); } if (ptt) { lpt_data |= ( 1 << save_audio_config_p->achan[chan].octrl[ot].ptt_lpt_bit ); } else { lpt_data &= ~ ( 1 << save_audio_config_p->achan[chan].octrl[ot].ptt_lpt_bit ); } lseek (ptt_fd[chan][ot], (off_t)LPT_IO_ADDR, SEEK_SET); if (write (ptt_fd[chan][ot], &lpt_data, (size_t)1) != 1) { int e = errno; text_color_set(DW_COLOR_ERROR); dw_printf ("Error writing to LPT for channel %d %s\n", chan, otnames[ot]); dw_printf ("%s\n", strerror(e)); } } #endif /* x86 Linux */ #ifdef USE_HAMLIB /* * Using hamlib? */ if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_HAMLIB) { if (rig[chan][ot] != NULL) { int retcode = rig_set_ptt(rig[chan][ot], RIG_VFO_CURR, ptt ? RIG_PTT_ON : RIG_PTT_OFF); if (retcode != RIG_OK) { text_color_set(DW_COLOR_ERROR); dw_printf ("Error sending rig_set_ptt command for channel %d %s\n", chan, otnames[ot]); dw_printf ("%s\n", rigerror(retcode)); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't use rig_set_ptt for channel %d %s because rig_open failed.\n", chan, otnames[ot]); } } #endif /* * Using CM108 USB Audio adapter GPIO? */ #ifdef USE_CM108 if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_CM108) { if (cm108_set_gpio_pin (save_audio_config_p->achan[chan].octrl[ot].ptt_device, save_audio_config_p->achan[chan].octrl[ot].out_gpio_num, ptt) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR: %s for channel %d has failed. See User Guide for troubleshooting tips.\n", otnames[ot], chan); } } #endif } /* end ptt_set */ /*------------------------------------------------------------------- * * Name: get_input * * Purpose: Read the value of an input line * * Inputs: it - Input type (ICTYPE_TCINH supported so far) * chan - Audio channel number * * Outputs: 0 = inactive, 1 = active, -1 = error * * ------------------------------------------------------------------*/ int get_input (int it, int chan) { assert (it >= 0 && it < NUM_ICTYPES); assert (chan >= 0 && chan < MAX_CHANS); if ( ! save_audio_config_p->achan[chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error, get_input ( %d, %d ), did not expect invalid channel.\n", it, chan); return -1; } #if __WIN32__ #else if (save_audio_config_p->achan[chan].ictrl[it].method == PTT_METHOD_GPIO) { int fd; char gpio_value_path[80]; snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", save_audio_config_p->achan[chan].ictrl[it].in_gpio_name); get_access_to_gpio (gpio_value_path); fd = open(gpio_value_path, O_RDONLY); if (fd < 0) { int e = errno; text_color_set(DW_COLOR_ERROR); dw_printf ("Error opening %s to check input.\n", gpio_value_path); dw_printf ("%s\n", strerror(e)); return -1; } char vtemp[2]; if (read (fd, vtemp, 1) != 1) { int e = errno; text_color_set(DW_COLOR_ERROR); dw_printf ("Error getting GPIO %d value\n", save_audio_config_p->achan[chan].ictrl[it].in_gpio_num); dw_printf ("%s\n", strerror(e)); } close (fd); vtemp[1] = '\0'; if (atoi(vtemp) != save_audio_config_p->achan[chan].ictrl[it].invert) { return 1; } else { return 0; } } #endif return -1; /* Method was none, or something went wrong */ } /*------------------------------------------------------------------- * * Name: ptt_term * * Purpose: Make sure PTT and others are turned off when we exit. * * Inputs: none * * Description: * *--------------------------------------------------------------------*/ void ptt_term (void) { int n; for (n = 0; n < MAX_CHANS; n++) { if (save_audio_config_p->achan[n].valid) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { ptt_set (ot, n, 0); } } } for (n = 0; n < MAX_CHANS; n++) { if (save_audio_config_p->achan[n].valid) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (ptt_fd[n][ot] != INVALID_HANDLE_VALUE) { #if __WIN32__ CloseHandle (ptt_fd[n][ot]); #else close(ptt_fd[n][ot]); #endif ptt_fd[n][ot] = INVALID_HANDLE_VALUE; } } } } #ifdef USE_HAMLIB for (n = 0; n < MAX_CHANS; n++) { if (save_audio_config_p->achan[n].valid) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (rig[n][ot] != NULL) { rig_close(rig[n][ot]); rig_cleanup(rig[n][ot]); rig[n][ot] = NULL; } } } } #endif } /* * Quick stand-alone test for above. * * gcc -DTEST -o ptest ptt.c textcolor.o misc.a ; ./ptest * * TODO: Retest this, add CM108 GPIO to test. */ #if TEST int main () { struct audio_s my_audio_config; int n; int chan; memset (&my_audio_config, 0, sizeof(my_audio_config)); my_audio_config.adev[0].num_channels = 2; my_audio_config.achan[0].valid = 1; my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL; // TODO: device should be command line argument. strlcpy (my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device, "COM3", sizeof(my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device)); //strlcpy (my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device, "/dev/ttyUSB0", sizeof(my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device)); my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_RTS; my_audio_config.achan[1].valid = 1; my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL; strlcpy (my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device, "COM3", sizeof(my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device)); //strlcpy (my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device, "/dev/ttyUSB0", sizeof(my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device)); my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_DTR; /* initialize - both off */ ptt_init (&my_audio_config); SLEEP_SEC(2); /* flash each a few times. */ dw_printf ("turn on RTS a few times...\n"); chan = 0; for (n=0; n<3; n++) { ptt_set (OCTYPE_PTT, chan, 1); SLEEP_SEC(1); ptt_set (OCTYPE_PTT, chan, 0); SLEEP_SEC(1); } dw_printf ("turn on DTR a few times...\n"); chan = 1; for (n=0; n<3; n++) { ptt_set (OCTYPE_PTT, chan, 1); SLEEP_SEC(1); ptt_set (OCTYPE_PTT, chan, 0); SLEEP_SEC(1); } ptt_term(); /* Same thing again but invert RTS. */ my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_invert = 1; ptt_init (&my_audio_config); SLEEP_SEC(2); dw_printf ("INVERTED - RTS a few times...\n"); chan = 0; for (n=0; n<3; n++) { ptt_set (OCTYPE_PTT, chan, 1); SLEEP_SEC(1); ptt_set (OCTYPE_PTT, chan, 0); SLEEP_SEC(1); } dw_printf ("turn on DTR a few times...\n"); chan = 1; for (n=0; n<3; n++) { ptt_set (OCTYPE_PTT, chan, 1); SLEEP_SEC(1); ptt_set (OCTYPE_PTT, chan, 0); SLEEP_SEC(1); } ptt_term (); /* Test GPIO */ #if __arm__ memset (&my_audio_config, 0, sizeof(my_audio_config)); my_audio_config.adev[0].num_channels = 1; my_audio_config.valid[0] = 1; my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_GPIO; my_audio_config.adev[0].octrl[OCTYPE_PTT].out_gpio_num = 25; dw_printf ("Try GPIO %d a few times...\n", my_audio_config.out_gpio_num[0]); ptt_init (&my_audio_config); SLEEP_SEC(2); chan = 0; for (n=0; n<3; n++) { ptt_set (OCTYPE_PTT, chan, 1); SLEEP_SEC(1); ptt_set (OCTYPE_PTT, chan, 0); SLEEP_SEC(1); } ptt_term (); #endif /* Parallel printer port. */ #if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) ) // TODO #if 0 memset (&my_audio_config, 0, sizeof(my_audio_config)); my_audio_config.num_channels = 2; my_audio_config.valid[0] = 1; my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_LPT; my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_lpt_bit = 0; my_audio_config.valid[1] = 1; my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_LPT; my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_lpt_bit = 1; dw_printf ("Try LPT bits 0 & 1 a few times...\n"); ptt_init (&my_audio_config); for (n=0; n<8; n++) { ptt_set (OCTYPE_PTT, 0, n & 1); ptt_set (OCTYPE_PTT, 1, (n>>1) & 1); SLEEP_SEC(1); } ptt_term (); #endif #endif return(0); } #endif /* TEST */ /* end ptt.c */ direwolf-1.5+dfsg/ptt.h000066400000000000000000000005021347750676600151130ustar00rootroot00000000000000 #ifndef PTT_H #define PTT_H 1 #include "audio.h" /* for struct audio_s and definitions for octype values */ void ptt_set_debug(int debug); void ptt_init (struct audio_s *p_modem); void ptt_set (int octype, int chan, int ptt); void ptt_term (void); int get_input (int it, int chan); #endif /* end ptt.h */ direwolf-1.5+dfsg/rdq.c000066400000000000000000000176331347750676600151020ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: rdq.c * * Purpose: Retry later decode queue for frames with bad FCS. * * Description: * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "rdq.h" #include "dedupe.h" static rrbb_t queue_head = NULL; /* Head of linked list for queue. */ static int rdq_len = 0; #define RDQ_UNDERRUN_THRESHOLD 30 /* A warning will be emitted if there are still this number of packets to decode in the queue and we try to add another one */ static dw_mutex_t rdq_mutex; /* Critical section for updating queues. */ #if __WIN32__ static HANDLE wake_up_event; /* Notify try decode again thread when queue not empty. */ #else static pthread_cond_t wake_up_cond; /* Notify try decode again thread when queue not empty. */ static dw_mutex_t wake_up_mutex; /* Required by cond_wait. */ #endif /*------------------------------------------------------------------- * * Name: rdq_init * * Purpose: Initialize the receive decode again queue. * * Inputs: None. Only single queue for all channels. * * Outputs: * * Description: Initialize the queue to be empty and set up other * mechanisms for sharing it between different threads. * *--------------------------------------------------------------------*/ void rdq_init (void) { //int c, p; #if __WIN32__ #else int err; #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_init ( )\n"); dw_printf ("rdq_init: pthread_mutex_init...\n"); #endif dw_mutex_init (&rdq_mutex); #if __WIN32__ #else dw_mutex_init (&wake_up_mutex); #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_init: pthread_cond_init...\n"); #endif #if __WIN32__ wake_up_event = CreateEvent (NULL, 0, 0, NULL); if (wake_up_event == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("rdq_init: pthread_cond_init: can't create decode wake up event"); exit (1); } #else err = pthread_cond_init (&wake_up_cond, NULL); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_init: pthread_cond_init returns %d\n", err); #endif if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("rdq_init: pthread_cond_init err=%d", err); perror (""); exit (1); } #endif } /* end rdq_init */ /*------------------------------------------------------------------- * * Name: rdq_append * * Purpose: Add a packet to the end of the queue. * * Inputs: pp - Address of raw received bit buffer. * Caller should NOT make any references to * it after this point because it could * be deleted at any time. * * Outputs: * * Description: Add buffer to end of linked list. * Signal the decode thread if the queue was formerly empty. * *--------------------------------------------------------------------*/ void rdq_append (rrbb_t rrbb) { //int was_empty; rrbb_t plast; rrbb_t pnext; #ifndef __WIN32__ int err; #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_append (rrbb=%p)\n", rrbb); dw_printf ("rdq_append: enter critical section\n"); #endif dw_mutex_lock (&rdq_mutex); //was_empty = 1; //if (queue_head != NULL) { //was_empty = 0; //} if (queue_head == NULL) { queue_head = rrbb; } else { plast = queue_head; while ((pnext = rrbb_get_nextp(plast)) != NULL) { plast = pnext; } rrbb_set_nextp (plast, rrbb); } rdq_len++; if (rdq_len > RDQ_UNDERRUN_THRESHOLD) { text_color_set(DW_COLOR_ERROR); dw_printf ("Too many packets to decode (%d) in the queue, decrease the FIX_BITS value\n", rdq_len); } dw_mutex_unlock (&rdq_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_append: left critical section\n"); dw_printf ("rdq_append (): about to wake up retry decode thread.\n"); #endif #if __WIN32__ SetEvent (wake_up_event); #else dw_mutex_lock (&wake_up_mutex); err = pthread_cond_signal (&wake_up_cond); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("rdq_append: pthread_cond_signal err=%d", err); perror (""); exit (1); } dw_mutex_unlock (&wake_up_mutex); #endif } /*------------------------------------------------------------------- * * Name: rdq_wait_while_empty * * Purpose: Sleep while the queue is empty rather than * polling periodically. * * Inputs: None. * *--------------------------------------------------------------------*/ void rdq_wait_while_empty (void) { int is_empty; #ifndef __WIN32__ int err; #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_wait_while_empty () : enter critical section\n"); #endif dw_mutex_lock (&rdq_mutex); #if DEBUG //text_color_set(DW_COLOR_DEBUG); //dw_printf ("rdq_wait_while_empty (): after pthread_mutex_lock\n"); #endif is_empty = 1; if (queue_head != NULL) is_empty = 0; dw_mutex_unlock (&rdq_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_wait_while_empty () : left critical section\n"); #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_wait_while_empty (): is_empty = %d\n", is_empty); #endif if (is_empty) { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_wait_while_empty (): SLEEP - about to call cond wait\n"); #endif #if __WIN32__ WaitForSingleObject (wake_up_event, INFINITE); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_wait_while_empty (): returned from wait\n"); #endif #else dw_mutex_lock (&wake_up_mutex); err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_wait_while_empty (): WOKE UP - returned from cond wait, err = %d\n", err); #endif if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("rdq_wait_while_empty: pthread_cond_wait err=%d", err); perror (""); exit (1); } dw_mutex_unlock (&wake_up_mutex); #endif } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_wait_while_empty () returns (%d buffers remaining)\n", rdq_len); #endif } /*------------------------------------------------------------------- * * Name: rdq_remove * * Purpose: Remove raw bit buffer from the head of the queue. * * Inputs: none * * Returns: Pointer to rrbb object. * Caller should destroy it with rrbb_delete when finished with it. * *--------------------------------------------------------------------*/ rrbb_t rdq_remove (void) { rrbb_t result_p; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_remove() enter critical section\n"); #endif dw_mutex_lock (&rdq_mutex); rdq_len--; #if DEBUG dw_printf ("-rdq_len: %d\n", rdq_len); #endif if (queue_head == NULL) { result_p = NULL; } else { result_p = queue_head; queue_head = rrbb_get_nextp(result_p); rrbb_set_nextp (result_p, NULL); } dw_mutex_unlock (&rdq_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_remove() leave critical section, returns %p\n", result_p); #endif return (result_p); } /* end rdq.c */ direwolf-1.5+dfsg/rdq.h000066400000000000000000000007571347750676600151060ustar00rootroot00000000000000 /*------------------------------------------------------------------ * * Module: rdq.h * * Purpose: Retry decode queue - Hold raw received frames with errors * for retrying the decoding later. * *---------------------------------------------------------------*/ #ifndef RDQ_H #define RDQ_H 1 #include "rrbb.h" //#include "audio.h" void rdq_init (void); void rdq_append (rrbb_t rrbb); void rdq_wait_while_empty (void); rrbb_t rdq_remove (void); #endif /* end rdq.h */ direwolf-1.5+dfsg/recv.c000066400000000000000000000231151347750676600152430ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2014, 2015, 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: recv.c * * Purpose: Process audio input for receiving. * * This is for all platforms. * * * Description: In earlier versions, we supported a single audio device * and the main program looped around processing the * audio samples. The structure looked like this: * * main in direwolf.c: * * audio_init() * various other *_init() * * loop forever: * s = demod_get_sample. * multi_modem_process_sample(s) * * * When a packet is succesfully decoded, somebody calls * app_process_rec_frame, also in direwolf.c * * * Starting in version 1.2, we support multiple audio * devices at the same time. We now have a separate * thread for each audio device. Decoded frames are * sent to a single queue for serial processing. * * The new flow looks like this: * * main in direwolf.c: * * audio_init() * various other *_init() * recv_init() * recv_process() -- does not return * * * recv_init() This starts up a separate thread * for each audio device. * Each thread reads audio samples and * passes them to multi_modem_process_sample. * * The difference is that app_process_rec_frame * is no longer called directly. Instead * the frame is appended to a queue with dlq_rec_frame. * * Received frames can now be processed one at * a time and we don't need to worry about later * processing being reentrant. * * recv_process() This simply waits for something to show up * in the dlq queue and calls app_process_rec_frame * for each. * *---------------------------------------------------------------*/ //#define DEBUG 1 #include "direwolf.h" #include #include #include #include #include //#include //#include //#include #include #ifdef __FreeBSD__ #include #endif #include "audio.h" #include "demod.h" #include "multi_modem.h" #include "textcolor.h" #include "dlq.h" #include "recv.h" #include "dtmf.h" #include "aprs_tt.h" #include "dtime_now.h" #include "ax25_link.h" #if __WIN32__ static unsigned __stdcall recv_adev_thread (void *arg); #else static void * recv_adev_thread (void *arg); #endif static struct audio_s *save_pa; /* Keep pointer to audio configuration */ /* for later use. */ /*------------------------------------------------------------------ * * Name: recv_init * * Purpose: Start up a thread for each audio device. * * * Inputs: pa - Address of structure of type audio_s. * * * Returns: None. * * Errors: Exit if error. * No point in going on if we can't get audio. * *----------------------------------------------------------------*/ void recv_init (struct audio_s *pa) { #if __WIN32__ HANDLE xmit_th[MAX_ADEVS]; #else pthread_t xmit_tid[MAX_ADEVS]; #endif int a; save_pa = pa; for (a=0; aadev[a].defined) { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("recv_init: start up thread, a=%d\n", a); #endif #if __WIN32__ xmit_th[a] = (HANDLE)_beginthreadex (NULL, 0, recv_adev_thread, (void*)(long)a, 0, NULL); if (xmit_th[a] == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("FATAL: Could not create audio receive thread for device %d.\n", a); exit(1); } #else int e; e = pthread_create (&xmit_tid[a], NULL, recv_adev_thread, (void *)(long)a); if (e != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("FATAL: Could not create audio receive thread for device %d.\n", a); exit(1); } #endif } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("recv_init: all done\n"); #endif } } /* end recv_init */ /* Try using "hot" attribute for all functions */ /* which are used for each audio sample. */ /* Compiler & linker might gather */ /* them together to improve memory cache performance. */ /* Or maybe it won't make any difference. */ __attribute__((hot)) #if __WIN32__ static unsigned __stdcall recv_adev_thread (void *arg) #else static void * recv_adev_thread (void *arg) #endif { int a = (int)(long)arg; // audio device number. int eof; /* This audio device can have one (mono) or two (stereo) channels. */ /* Find number of the first channel. */ int first_chan = ADEVFIRSTCHAN(a); int num_chan = save_pa->adev[a].num_channels; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("recv_adev_thread is now running for a=%d\n", a); #endif /* * Get sound samples and decode them. */ eof = 0; while ( ! eof) { int audio_sample; int c; char tt; for (c=0; c= 256 * 256) eof = 1; multi_modem_process_sample(first_chan + c, audio_sample); /* Originally, the DTMF decoder was always active. */ /* It took very little CPU time and the thinking was that an */ /* attached application might be interested in this even when */ /* the APRStt gateway was not being used. */ /* Unfortunately it resulted in too many false detections of */ /* touch tones when hearing other types of digital communications */ /* on HF. Starting in version 1.0, the DTMF decoder is active */ /* only when the APRStt gateway is configured. */ /* The test below allows us to listen to only a single channel for */ /* for touch tone sequences. The DTMF decoder and the accumulation */ /* of digits into a sequence maintain separate data for each channel. */ /* We should be able to accept touch tone sequences concurrently on */ /* all channels. The only issue is when a complete sequence is */ /* sent to aprs_tt_sequence which doesn't have separate data for each */ /* channel. This shouldn't be a problem unless we have multiple */ /* sequences arriving at the same instant. */ if (save_pa->achan[first_chan + c].dtmf_decode != DTMF_DECODE_OFF) { tt = dtmf_sample (first_chan + c, audio_sample/16384.); if (tt != ' ') { aprs_tt_button (first_chan + c, tt); } } } /* When a complete frame is accumulated, */ /* dlq_rec_frame, is called. */ /* recv_process, below, drains the queue. */ } // What should we do now? // Seimply terminate the application? // Try to re-init the audio device a couple times before giving up? text_color_set(DW_COLOR_ERROR); dw_printf ("Terminating after audio input failure.\n"); exit (1); } void recv_process (void) { struct dlq_item_s *pitem; while (1) { int timed_out; double timeout_value = ax25_link_get_next_timer_expiry(); timed_out = dlq_wait_while_empty (timeout_value); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("recv_process: woke up, timed_out=%d\n", timed_out); #endif if (timed_out) { #if DEBUG text_color_set(DW_COLOR_ERROR); dw_printf ("recv_process: time waiting on dlq. call dl_timer_expiry.\n"); #endif dl_timer_expiry (); } else { pitem = dlq_remove (); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("recv_process: dlq_remove() returned pitem=%p\n", pitem); #endif if (pitem != NULL) { switch (pitem->type) { case DLQ_REC_FRAME: /* * This is the traditional processing. * For all frames: * - Print in standard monitoring format. * - Send to KISS client applications. * - Send to AGw client applications in raw mode. * For APRS frames: * - Explain what it means. * - Send to Igate. * - Digipeater. */ app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->retries, pitem->spectrum); /* * Link processing. */ lm_data_indication(pitem); break; case DLQ_CONNECT_REQUEST: dl_connect_request (pitem); break; case DLQ_DISCONNECT_REQUEST: dl_disconnect_request (pitem); break; case DLQ_XMIT_DATA_REQUEST: dl_data_request (pitem); break; case DLQ_REGISTER_CALLSIGN: dl_register_callsign (pitem); break; case DLQ_UNREGISTER_CALLSIGN: dl_unregister_callsign (pitem); break; case DLQ_CHANNEL_BUSY: lm_channel_busy (pitem); break; case DLQ_SEIZE_CONFIRM: lm_seize_confirm (pitem); break; case DLQ_CLIENT_CLEANUP: dl_client_cleanup (pitem); break; } dlq_delete (pitem); } #if DEBUG else { text_color_set(DW_COLOR_DEBUG); dw_printf ("recv_process: spurious wakeup. (Temp debugging message - not a problem if only occasional.)\n"); } #endif } } } /* end recv_process */ /* end recv.c */ direwolf-1.5+dfsg/recv.h000066400000000000000000000001161347750676600152440ustar00rootroot00000000000000 /* recv.h */ void recv_init (struct audio_s *pa); void recv_process (void);direwolf-1.5+dfsg/redecode.h000066400000000000000000000002341347750676600160600ustar00rootroot00000000000000 #ifndef REDECODE_H #define REDECODE_H 1 #include "rrbb.h" extern void redecode_init (struct audio_s *p_audio_config); #endif /* end redecode.h */ direwolf-1.5+dfsg/regex/000077500000000000000000000000001347750676600152505ustar00rootroot00000000000000direwolf-1.5+dfsg/regex/COPYING000066400000000000000000000431311347750676600163050ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 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. 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 Library 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. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. , 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 Library General Public License instead of this License. direwolf-1.5+dfsg/regex/INSTALL000066400000000000000000000531741347750676600163130ustar00rootroot00000000000000Installing the GNU C Library **************************** Before you do anything else, you should read the file `FAQ' located at the top level of the source tree. This file answers common questions and describes problems you may experience with compilation and installation. It is updated more frequently than this manual. Features can be added to GNU Libc via "add-on" bundles. These are separate tar files, which you unpack into the top level of the source tree. Then you give `configure' the `--enable-add-ons' option to activate them, and they will be compiled into the library. You will need recent versions of several GNU tools: definitely GCC and GNU Make, and possibly others. *Note Tools for Compilation::, below. Configuring and compiling GNU Libc ================================== GNU libc cannot be compiled in the source directory. You must build it in a separate build directory. For example, if you have unpacked the glibc sources in `/src/gnu/glibc-2.4', create a directory `/src/gnu/glibc-build' to put the object files in. This allows removing the whole build directory in case an error occurs, which is the safest way to get a fresh start and should always be done. From your object directory, run the shell script `configure' located at the top level of the source tree. In the scenario above, you'd type $ ../glibc-2.4/configure ARGS... Please note that even though you're building in a separate build directory, the compilation needs to modify a few files in the source directory, especially some files in the manual subdirectory. `configure' takes many options, but the only one that is usually mandatory is `--prefix'. This option tells `configure' where you want glibc installed. This defaults to `/usr/local', but the normal setting to install as the standard system library is `--prefix=/usr' for GNU/Linux systems and `--prefix=' (an empty prefix) for GNU/Hurd systems. It may also be useful to set the CC and CFLAGS variables in the environment when running `configure'. CC selects the C compiler that will be used, and CFLAGS sets optimization options for the compiler. The following list describes all of the available options for `configure': `--prefix=DIRECTORY' Install machine-independent data files in subdirectories of `DIRECTORY'. The default is to install in `/usr/local'. `--exec-prefix=DIRECTORY' Install the library and other machine-dependent files in subdirectories of `DIRECTORY'. The default is to the `--prefix' directory if that option is specified, or `/usr/local' otherwise. `--with-headers=DIRECTORY' Look for kernel header files in DIRECTORY, not `/usr/include'. Glibc needs information from the kernel's private header files. Glibc will normally look in `/usr/include' for them, but if you specify this option, it will look in DIRECTORY instead. This option is primarily of use on a system where the headers in `/usr/include' come from an older version of glibc. Conflicts can occasionally happen in this case. Note that Linux libc5 qualifies as an older version of glibc. You can also use this option if you want to compile glibc with a newer set of kernel headers than the ones found in `/usr/include'. `--enable-add-ons[=LIST]' Specify add-on packages to include in the build. If this option is specified with no list, it enables all the add-on packages it finds in the main source directory; this is the default behavior. You may specify an explicit list of add-ons to use in LIST, separated by spaces or commas (if you use spaces, remember to quote them from the shell). Each add-on in LIST can be an absolute directory name or can be a directory name relative to the main source directory, or relative to the build directory (that is, the current working directory). For example, `--enable-add-ons=nptl,../glibc-libidn-2.4'. `--enable-kernel=VERSION' This option is currently only useful on GNU/Linux systems. The VERSION parameter should have the form X.Y.Z and describes the smallest version of the Linux kernel the generated library is expected to support. The higher the VERSION number is, the less compatibility code is added, and the faster the code gets. `--with-binutils=DIRECTORY' Use the binutils (assembler and linker) in `DIRECTORY', not the ones the C compiler would default to. You can use this option if the default binutils on your system cannot deal with all the constructs in the GNU C library. In that case, `configure' will detect the problem and suppress these constructs, so that the library will still be usable, but functionality may be lost--for example, you can't build a shared libc with old binutils. `--without-fp' Use this option if your computer lacks hardware floating-point support and your operating system does not emulate an FPU. these `--disable-shared' Don't build shared libraries even if it is possible. Not all systems support shared libraries; you need ELF support and (currently) the GNU linker. `--disable-profile' Don't build libraries with profiling information. You may want to use this option if you don't plan to do profiling. `--enable-omitfp' Use maximum optimization for the normal (static and shared) libraries, and compile separate static libraries with debugging information and no optimization. We recommend not doing this. The extra optimization doesn't gain you much, it may provoke compiler bugs, and you won't be able to trace bugs through the C library. `--disable-versioning' Don't compile the shared libraries with symbol version information. Doing this will make the resulting library incompatible with old binaries, so it's not recommended. `--enable-static-nss' Compile static versions of the NSS (Name Service Switch) libraries. This is not recommended because it defeats the purpose of NSS; a program linked statically with the NSS libraries cannot be dynamically reconfigured to use a different name database. `--without-tls' By default the C library is built with support for thread-local storage if the used tools support it. By using `--without-tls' this can be prevented though there generally is no reason since it creates compatibility problems. `--build=BUILD-SYSTEM' `--host=HOST-SYSTEM' These options are for cross-compiling. If you specify both options and BUILD-SYSTEM is different from HOST-SYSTEM, `configure' will prepare to cross-compile glibc from BUILD-SYSTEM to be used on HOST-SYSTEM. You'll probably need the `--with-headers' option too, and you may have to override CONFIGURE's selection of the compiler and/or binutils. If you only specify `--host', `configure' will prepare for a native compile but use what you specify instead of guessing what your system is. This is most useful to change the CPU submodel. For example, if `configure' guesses your machine as `i586-pc-linux-gnu' but you want to compile a library for 386es, give `--host=i386-pc-linux-gnu' or just `--host=i386-linux' and add the appropriate compiler flags (`-mcpu=i386' will do the trick) to CFLAGS. If you specify just `--build', `configure' will get confused. To build the library and related programs, type `make'. This will produce a lot of output, some of which may look like errors from `make' but isn't. Look for error messages from `make' containing `***'. Those indicate that something is seriously wrong. The compilation process can take a long time, depending on the configuration and the speed of your machine. Some complex modules may take a very long time to compile, as much as several minutes on slower machines. Do not panic if the compiler appears to hang. If you want to run a parallel make, simply pass the `-j' option with an appropriate numeric parameter to `make'. You need a recent GNU `make' version, though. To build and run test programs which exercise some of the library facilities, type `make check'. If it does not complete successfully, do not use the built library, and report a bug after verifying that the problem is not already known. *Note Reporting Bugs::, for instructions on reporting bugs. Note that some of the tests assume they are not being run by `root'. We recommend you compile and test glibc as an unprivileged user. Before reporting bugs make sure there is no problem with your system. The tests (and later installation) use some pre-existing files of the system such as `/etc/passwd', `/etc/nsswitch.conf' and others. These files must all contain correct and sensible content. To format the `GNU C Library Reference Manual' for printing, type `make dvi'. You need a working TeX installation to do this. The distribution already includes the on-line formatted version of the manual, as Info files. You can regenerate those with `make info', but it shouldn't be necessary. The library has a number of special-purpose configuration parameters which you can find in `Makeconfig'. These can be overwritten with the file `configparms'. To change them, create a `configparms' in your build directory and add values as appropriate for your system. The file is included and parsed by `make' and has to follow the conventions for makefiles. It is easy to configure the GNU C library for cross-compilation by setting a few variables in `configparms'. Set `CC' to the cross-compiler for the target you configured the library for; it is important to use this same `CC' value when running `configure', like this: `CC=TARGET-gcc configure TARGET'. Set `BUILD_CC' to the compiler to use for programs run on the build system as part of compiling the library. You may need to set `AR' and `RANLIB' to cross-compiling versions of `ar' and `ranlib' if the native tools are not configured to work with object files for the target you configured for. Installing the C Library ======================== To install the library and its header files, and the Info files of the manual, type `env LANGUAGE=C LC_ALL=C make install'. This will build things, if necessary, before installing them; however, you should still compile everything first. If you are installing glibc as your primary C library, we recommend that you shut the system down to single-user mode first, and reboot afterward. This minimizes the risk of breaking things when the library changes out from underneath. If you're upgrading from Linux libc5 or some other C library, you need to replace the `/usr/include' with a fresh directory before installing it. The new `/usr/include' should contain the Linux headers, but nothing else. You must first build the library (`make'), optionally check it (`make check'), switch the include directories and then install (`make install'). The steps must be done in this order. Not moving the directory before install will result in an unusable mixture of header files from both libraries, but configuring, building, and checking the library requires the ability to compile and run programs against the old library. If you are upgrading from a previous installation of glibc 2.0 or 2.1, `make install' will do the entire job. You do not need to remove the old includes - if you want to do so anyway you must then follow the order given above. You may also need to reconfigure GCC to work with the new library. The easiest way to do that is to figure out the compiler switches to make it work again (`-Wl,--dynamic-linker=/lib/ld-linux.so.2' should work on GNU/Linux systems) and use them to recompile gcc. You can also edit the specs file (`/usr/lib/gcc-lib/TARGET/VERSION/specs'), but that is a bit of a black art. You can install glibc somewhere other than where you configured it to go by setting the `install_root' variable on the command line for `make install'. The value of this variable is prepended to all the paths for installation. This is useful when setting up a chroot environment or preparing a binary distribution. The directory should be specified with an absolute file name. Glibc 2.2 includes a daemon called `nscd', which you may or may not want to run. `nscd' caches name service lookups; it can dramatically improve performance with NIS+, and may help with DNS as well. One auxiliary program, `/usr/libexec/pt_chown', is installed setuid `root'. This program is invoked by the `grantpt' function; it sets the permissions on a pseudoterminal so it can be used by the calling process. This means programs like `xterm' and `screen' do not have to be setuid to get a pty. (There may be other reasons why they need privileges.) If you are using a 2.1 or newer Linux kernel with the `devptsfs' or `devfs' filesystems providing pty slaves, you don't need this program; otherwise you do. The source for `pt_chown' is in `login/programs/pt_chown.c'. After installation you might want to configure the timezone and locale installation of your system. The GNU C library comes with a locale database which gets configured with `localedef'. For example, to set up a German locale with name `de_DE', simply issue the command `localedef -i de_DE -f ISO-8859-1 de_DE'. To configure all locales that are supported by glibc, you can issue from your build directory the command `make localedata/install-locales'. To configure the locally used timezone, set the `TZ' environment variable. The script `tzselect' helps you to select the right value. As an example, for Germany, `tzselect' would tell you to use `TZ='Europe/Berlin''. For a system wide installation (the given paths are for an installation with `--prefix=/usr'), link the timezone file which is in `/usr/share/zoneinfo' to the file `/etc/localtime'. For Germany, you might execute `ln -s /usr/share/zoneinfo/Europe/Berlin /etc/localtime'. Recommended Tools for Compilation ================================= We recommend installing the following GNU tools before attempting to build the GNU C library: * GNU `make' 3.79 or newer You need the latest version of GNU `make'. Modifying the GNU C Library to work with other `make' programs would be so difficult that we recommend you port GNU `make' instead. *Really.* We recommend GNU `make' version 3.79. All earlier versions have severe bugs or lack features. * GCC 3.4 or newer, GCC 4.1 recommended The GNU C library can only be compiled with the GNU C compiler family. For the 2.3 releases, GCC 3.2 or higher is required; GCC 3.4 is the compiler we advise to use for 2.3 versions. For the 2.4 release, GCC 3.4 or higher is required; as of this writing, GCC 4.1 is the compiler we advise to use for current versions. On certain machines including `powerpc64', compilers prior to GCC 4.0 have bugs that prevent them compiling the C library code in the 2.4 release. On other machines, GCC 4.1 is required to build the C library with support for the correct `long double' type format; these include `powerpc' (32 bit), `s390' and `s390x'. You can use whatever compiler you like to compile programs that use GNU libc, but be aware that both GCC 2.7 and 2.8 have bugs in their floating-point support that may be triggered by the math library. Check the FAQ for any special compiler issues on particular platforms. * GNU `binutils' 2.15 or later You must use GNU `binutils' (as and ld) to build the GNU C library. No other assembler or linker has the necessary functionality at the moment. * GNU `texinfo' 3.12f To correctly translate and install the Texinfo documentation you need this version of the `texinfo' package. Earlier versions do not understand all the tags used in the document, and the installation mechanism for the info files is not present or works differently. * GNU `awk' 3.0, or higher `Awk' is used in several places to generate files. `gawk' 3.0 is known to work. * Perl 5 Perl is not required, but it is used if present to test the installation. We may decide to use it elsewhere in the future. * GNU `sed' 3.02 or newer `Sed' is used in several places to generate files. Most scripts work with any version of `sed'. The known exception is the script `po2test.sed' in the `intl' subdirectory which is used to generate `msgs.h' for the test suite. This script works correctly only with GNU `sed' 3.02. If you like to run the test suite, you should definitely upgrade `sed'. If you change any of the `configure.in' files you will also need * GNU `autoconf' 2.53 or higher and if you change any of the message translation files you will need * GNU `gettext' 0.10.36 or later You may also need these packages if you upgrade your source tree using patches, although we try to avoid this. Specific advice for GNU/Linux systems ===================================== If you are installing GNU libc on a GNU/Linux system, you need to have the header files from a 2.2 or newer kernel around for reference. For some architectures, like ia64, sh and hppa, you need at least headers from kernel 2.3.99 (sh and hppa) or 2.4.0 (ia64). You do not need to use that kernel, just have its headers where glibc can access at them. The easiest way to do this is to unpack it in a directory such as `/usr/src/linux-2.2.1'. In that directory, run `make config' and accept all the defaults. Then run `make include/linux/version.h'. Finally, configure glibc with the option `--with-headers=/usr/src/linux-2.2.1/include'. Use the most recent kernel you can get your hands on. An alternate tactic is to unpack the 2.2 kernel and run `make config' as above; then, rename or delete `/usr/include', create a new `/usr/include', and make symbolic links of `/usr/include/linux' and `/usr/include/asm' into the kernel sources. You can then configure glibc with no special options. This tactic is recommended if you are upgrading from libc5, since you need to get rid of the old header files anyway. After installing GNU libc, you may need to remove or rename `/usr/include/linux' and `/usr/include/asm', and replace them with copies of `include/linux' and `include/asm-$ARCHITECTURE' taken from the Linux source package which supplied kernel headers for building the library. ARCHITECTURE will be the machine architecture for which the library was built, such as `i386' or `alpha'. You do not need to do this if you did not specify an alternate kernel header source using `--with-headers'. The intent here is that these directories should be copies of, *not* symlinks to, the kernel headers used to build the library. Note that `/usr/include/net' and `/usr/include/scsi' should *not* be symlinks into the kernel sources. GNU libc provides its own versions of these files. GNU/Linux expects some components of the libc installation to be in `/lib' and some in `/usr/lib'. This is handled automatically if you configure glibc with `--prefix=/usr'. If you set some other prefix or allow it to default to `/usr/local', then all the components are installed there. If you are upgrading from libc5, you need to recompile every shared library on your system against the new library for the sake of new code, but keep the old libraries around for old binaries to use. This is complicated and difficult. Consult the Glibc2 HOWTO at `http://www.imaxx.net/~thrytis/glibc' for details. You cannot use `nscd' with 2.0 kernels, due to bugs in the kernel-side thread support. `nscd' happens to hit these bugs particularly hard, but you might have problems with any threaded program. Reporting Bugs ============== There are probably bugs in the GNU C library. There are certainly errors and omissions in this manual. If you report them, they will get fixed. If you don't, no one will ever know about them and they will remain unfixed for all eternity, if not longer. It is a good idea to verify that the problem has not already been reported. Bugs are documented in two places: The file `BUGS' describes a number of well known bugs and the bug tracking system has a WWW interface at `http://sources.redhat.com/bugzilla/'. The WWW interface gives you access to open and closed reports. A closed report normally includes a patch or a hint on solving the problem. To report a bug, first you must find it. With any luck, this will be the hard part. Once you've found a bug, make sure it's really a bug. A good way to do this is to see if the GNU C library behaves the same way some other C library does. If so, probably you are wrong and the libraries are right (but not necessarily). If not, one of the libraries is probably wrong. It might not be the GNU library. Many historical Unix C libraries permit things that we don't, such as closing a file twice. If you think you have found some way in which the GNU C library does not conform to the ISO and POSIX standards (*note Standards and Portability::), that is definitely a bug. Report it! Once you're sure you've found a bug, try to narrow it down to the smallest test case that reproduces the problem. In the case of a C library, you really only need to narrow it down to one library function call, if possible. This should not be too difficult. The final step when you have a simple test case is to report the bug. Do this using the WWW interface to the bug database. If you are not sure how a function should behave, and this manual doesn't tell you, that's a bug in the manual. Report that too! If the function's behavior disagrees with the manual, then either the library or the manual has a bug, so report the disagreement. If you find any errors or omissions in this manual, please report them to the bug database. If you refer to specific sections of the manual, please include the section names for easier identification. direwolf-1.5+dfsg/regex/LICENSES000066400000000000000000000247501347750676600164100ustar00rootroot00000000000000This file contains the copying permission notices for various files in the GNU C Library distribution that have copyright owners other than the Free Software Foundation. These notices all require that a copy of the notice be included in the accompanying documentation and be distributed with binary distributions of the code, so be sure to include this file along with any binary distributions derived from the GNU C Library. All code incorporated from 4.4 BSD is distributed under the following license: Copyright (C) 1991 Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. [This condition was removed.] 4. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The DNS resolver code, taken from BIND 4.9.5, is copyrighted both by UC Berkeley and by Digital Equipment Corporation. The DEC portions are under the following license: Portions Copyright (C) 1993 by Digital Equipment Corporation. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies, and that the name of Digital Equipment Corporation not be used in advertising or publicity pertaining to distribution of the document or software without specific, written prior permission. THE SOFTWARE IS PROVIDED ``AS IS'' AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. The Sun RPC support (from rpcsrc-4.0) is covered by the following license: Copyright (C) 1984, Sun Microsystems, Inc. Sun RPC is a product of Sun Microsystems, Inc. and is provided for unrestricted use provided that this legend is included on all tape media and as a part of the software program in whole or part. Users may copy or modify Sun RPC without charge, but are not authorized to license or distribute it to anyone else except as part of a product or program developed by the user. SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. Sun RPC is provided with no support and without any obligation on the part of Sun Microsystems, Inc. to assist in its use, correction, modification or enhancement. SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC OR ANY PART THEREOF. In no event will Sun Microsystems, Inc. be liable for any lost revenue or profits or other special, indirect and consequential damages, even if Sun has been advised of the possibility of such damages. The following CMU license covers some of the support code for Mach, derived from Mach 3.0: Mach Operating System Copyright (C) 1991,1990,1989 Carnegie Mellon University All Rights Reserved. Permission to use, copy, modify and distribute this software and its documentation is hereby granted, provided that both the copyright notice and this permission notice appear in all copies of the software, derivative works or modified versions, and any portions thereof, and that both notices appear in supporting documentation. CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS ``AS IS'' CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. Carnegie Mellon requests users of this software to return to Software Distribution Coordinator School of Computer Science Carnegie Mellon University Pittsburgh PA 15213-3890 or Software.Distribution@CS.CMU.EDU any improvements or extensions that they make and grant Carnegie Mellon the rights to redistribute these changes. The file if_ppp.h is under the following CMU license: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The following license covers the files from Intel's "Highly Optimized Mathematical Functions for Itanium" collection: Intel License Agreement Copyright (c) 2000, Intel Corporation All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of Intel Corporation may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The files inet/getnameinfo.c and sysdeps/posix/getaddrinfo.c are copyright (C) by Craig Metz and are distributed under the following license: /* The Inner Net License, Version 2.00 The author(s) grant permission for redistribution and use in source and binary forms, with or without modification, of the software and documentation provided that the following conditions are met: 0. If you receive a version of the software that is specifically labelled as not being for redistribution (check the version message and/or README), you are not permitted to redistribute that version of the software in any way or form. 1. All terms of the all other applicable copyrights and licenses must be followed. 2. Redistributions of source code must retain the authors' copyright notice(s), this list of conditions, and the following disclaimer. 3. Redistributions in binary form must reproduce the authors' copyright notice(s), this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 4. [The copyright holder has authorized the removal of this clause.] 5. Neither the name(s) of the author(s) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. If these license terms cause you a real problem, contact the author. */ direwolf-1.5+dfsg/regex/NEWS000066400000000000000000000000001347750676600157350ustar00rootroot00000000000000direwolf-1.5+dfsg/regex/README000066400000000000000000000000001347750676600161160ustar00rootroot00000000000000direwolf-1.5+dfsg/regex/README-dire-wolf.txt000066400000000000000000000005771347750676600206450ustar00rootroot00000000000000This is NOT used for the Linux version. For Linux and Cygwin, we use the built-in regular expression library. For the Windows version, we need to include our own version. The source was obtained from: http://gnuwin32.sourceforge.net/packages/regex.htm That is very old with loads of compile warnings. Should we upgrade from here? https://www.gnu.org/software/libc/sources.htmldirewolf-1.5+dfsg/regex/re_comp.h000066400000000000000000000020001347750676600170350ustar00rootroot00000000000000/* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C 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. The GNU C 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 the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifndef _RE_COMP_H #define _RE_COMP_H 1 /* This is only a wrapper around the file. XPG4.2 mentions this name. */ #include #endif /* re_comp.h */ direwolf-1.5+dfsg/regex/regcomp.c000066400000000000000000003273331347750676600170630ustar00rootroot00000000000000/* Extended regular expression matching and search library. Copyright (C) 2002,2003,2004,2005,2006,2007 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C 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. The GNU C 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 the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern, size_t length, reg_syntax_t syntax); static void re_compile_fastmap_iter (regex_t *bufp, const re_dfastate_t *init_state, char *fastmap); static reg_errcode_t init_dfa (re_dfa_t *dfa, size_t pat_len); #ifdef RE_ENABLE_I18N static void free_charset (re_charset_t *cset); #endif /* RE_ENABLE_I18N */ static void free_workarea_compile (regex_t *preg); static reg_errcode_t create_initial_state (re_dfa_t *dfa); #ifdef RE_ENABLE_I18N static void optimize_utf8 (re_dfa_t *dfa); #endif static reg_errcode_t analyze (regex_t *preg); static reg_errcode_t preorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)), void *extra); static reg_errcode_t postorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)), void *extra); static reg_errcode_t optimize_subexps (void *extra, bin_tree_t *node); static reg_errcode_t lower_subexps (void *extra, bin_tree_t *node); static bin_tree_t *lower_subexp (reg_errcode_t *err, regex_t *preg, bin_tree_t *node); static reg_errcode_t calc_first (void *extra, bin_tree_t *node); static reg_errcode_t calc_next (void *extra, bin_tree_t *node); static reg_errcode_t link_nfa_nodes (void *extra, bin_tree_t *node); static int duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint); static int search_duplicated_node (const re_dfa_t *dfa, int org_node, unsigned int constraint); static reg_errcode_t calc_eclosure (re_dfa_t *dfa); static reg_errcode_t calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, int node, int root); static reg_errcode_t calc_inveclosure (re_dfa_t *dfa); static int fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax); static int peek_token (re_token_t *token, re_string_t *input, reg_syntax_t syntax) internal_function; static bin_tree_t *parse (re_string_t *regexp, regex_t *preg, reg_syntax_t syntax, reg_errcode_t *err); static bin_tree_t *parse_reg_exp (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, int nest, reg_errcode_t *err); static bin_tree_t *parse_branch (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, int nest, reg_errcode_t *err); static bin_tree_t *parse_expression (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, int nest, reg_errcode_t *err); static bin_tree_t *parse_sub_exp (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, int nest, reg_errcode_t *err); static bin_tree_t *parse_dup_op (bin_tree_t *dup_elem, re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err); static bin_tree_t *parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err); static reg_errcode_t parse_bracket_element (bracket_elem_t *elem, re_string_t *regexp, re_token_t *token, int token_len, re_dfa_t *dfa, reg_syntax_t syntax, int accept_hyphen); static reg_errcode_t parse_bracket_symbol (bracket_elem_t *elem, re_string_t *regexp, re_token_t *token); #ifdef RE_ENABLE_I18N static reg_errcode_t build_equiv_class (bitset_t sbcset, re_charset_t *mbcset, int *equiv_class_alloc, const unsigned char *name); static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, re_charset_t *mbcset, int *char_class_alloc, const unsigned char *class_name, reg_syntax_t syntax); #else /* not RE_ENABLE_I18N */ static reg_errcode_t build_equiv_class (bitset_t sbcset, const unsigned char *name); static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, const unsigned char *class_name, reg_syntax_t syntax); #endif /* not RE_ENABLE_I18N */ static bin_tree_t *build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans, const unsigned char *class_name, const unsigned char *extra, int non_match, reg_errcode_t *err); static bin_tree_t *create_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, re_token_type_t type); static bin_tree_t *create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, const re_token_t *token); static bin_tree_t *duplicate_tree (const bin_tree_t *src, re_dfa_t *dfa); static void free_token (re_token_t *node); static reg_errcode_t free_tree (void *extra, bin_tree_t *node); static reg_errcode_t mark_opt_subexp (void *extra, bin_tree_t *node); /* This table gives an error message for each of the error codes listed in regex.h. Obviously the order here has to be same as there. POSIX doesn't require that we do anything for REG_NOERROR, but why not be nice? */ const char __re_error_msgid[] attribute_hidden = { #define REG_NOERROR_IDX 0 gettext_noop ("Success") /* REG_NOERROR */ "\0" #define REG_NOMATCH_IDX (REG_NOERROR_IDX + sizeof "Success") gettext_noop ("No match") /* REG_NOMATCH */ "\0" #define REG_BADPAT_IDX (REG_NOMATCH_IDX + sizeof "No match") gettext_noop ("Invalid regular expression") /* REG_BADPAT */ "\0" #define REG_ECOLLATE_IDX (REG_BADPAT_IDX + sizeof "Invalid regular expression") gettext_noop ("Invalid collation character") /* REG_ECOLLATE */ "\0" #define REG_ECTYPE_IDX (REG_ECOLLATE_IDX + sizeof "Invalid collation character") gettext_noop ("Invalid character class name") /* REG_ECTYPE */ "\0" #define REG_EESCAPE_IDX (REG_ECTYPE_IDX + sizeof "Invalid character class name") gettext_noop ("Trailing backslash") /* REG_EESCAPE */ "\0" #define REG_ESUBREG_IDX (REG_EESCAPE_IDX + sizeof "Trailing backslash") gettext_noop ("Invalid back reference") /* REG_ESUBREG */ "\0" #define REG_EBRACK_IDX (REG_ESUBREG_IDX + sizeof "Invalid back reference") gettext_noop ("Unmatched [ or [^") /* REG_EBRACK */ "\0" #define REG_EPAREN_IDX (REG_EBRACK_IDX + sizeof "Unmatched [ or [^") gettext_noop ("Unmatched ( or \\(") /* REG_EPAREN */ "\0" #define REG_EBRACE_IDX (REG_EPAREN_IDX + sizeof "Unmatched ( or \\(") gettext_noop ("Unmatched \\{") /* REG_EBRACE */ "\0" #define REG_BADBR_IDX (REG_EBRACE_IDX + sizeof "Unmatched \\{") gettext_noop ("Invalid content of \\{\\}") /* REG_BADBR */ "\0" #define REG_ERANGE_IDX (REG_BADBR_IDX + sizeof "Invalid content of \\{\\}") gettext_noop ("Invalid range end") /* REG_ERANGE */ "\0" #define REG_ESPACE_IDX (REG_ERANGE_IDX + sizeof "Invalid range end") gettext_noop ("Memory exhausted") /* REG_ESPACE */ "\0" #define REG_BADRPT_IDX (REG_ESPACE_IDX + sizeof "Memory exhausted") gettext_noop ("Invalid preceding regular expression") /* REG_BADRPT */ "\0" #define REG_EEND_IDX (REG_BADRPT_IDX + sizeof "Invalid preceding regular expression") gettext_noop ("Premature end of regular expression") /* REG_EEND */ "\0" #define REG_ESIZE_IDX (REG_EEND_IDX + sizeof "Premature end of regular expression") gettext_noop ("Regular expression too big") /* REG_ESIZE */ "\0" #define REG_ERPAREN_IDX (REG_ESIZE_IDX + sizeof "Regular expression too big") gettext_noop ("Unmatched ) or \\)") /* REG_ERPAREN */ }; const size_t __re_error_msgid_idx[] attribute_hidden = { REG_NOERROR_IDX, REG_NOMATCH_IDX, REG_BADPAT_IDX, REG_ECOLLATE_IDX, REG_ECTYPE_IDX, REG_EESCAPE_IDX, REG_ESUBREG_IDX, REG_EBRACK_IDX, REG_EPAREN_IDX, REG_EBRACE_IDX, REG_BADBR_IDX, REG_ERANGE_IDX, REG_ESPACE_IDX, REG_BADRPT_IDX, REG_EEND_IDX, REG_ESIZE_IDX, REG_ERPAREN_IDX }; /* Entry points for GNU code. */ /* re_compile_pattern is the GNU regular expression compiler: it compiles PATTERN (of length LENGTH) and puts the result in BUFP. Returns 0 if the pattern was valid, otherwise an error string. Assumes the `allocated' (and perhaps `buffer') and `translate' fields are set in BUFP on entry. */ const char * re_compile_pattern (pattern, length, bufp) const char *pattern; size_t length; struct re_pattern_buffer *bufp; { reg_errcode_t ret; /* And GNU code determines whether or not to get register information by passing null for the REGS argument to re_match, etc., not by setting no_sub, unless RE_NO_SUB is set. */ bufp->no_sub = !!(re_syntax_options & RE_NO_SUB); /* Match anchors at newline. */ bufp->newline_anchor = 1; ret = re_compile_internal (bufp, pattern, length, re_syntax_options); if (!ret) return NULL; return gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]); } #ifdef _LIBC weak_alias (__re_compile_pattern, re_compile_pattern) #endif /* Set by `re_set_syntax' to the current regexp syntax to recognize. Can also be assigned to arbitrarily: each pattern buffer stores its own syntax, so it can be changed between regex compilations. */ /* This has no initializer because initialized variables in Emacs become read-only after dumping. */ reg_syntax_t re_syntax_options; /* Specify the precise syntax of regexps for compilation. This provides for compatibility for various utilities which historically have different, incompatible syntaxes. The argument SYNTAX is a bit mask comprised of the various bits defined in regex.h. We return the old syntax. */ reg_syntax_t re_set_syntax (syntax) reg_syntax_t syntax; { reg_syntax_t ret = re_syntax_options; re_syntax_options = syntax; return ret; } #ifdef _LIBC weak_alias (__re_set_syntax, re_set_syntax) #endif int re_compile_fastmap (bufp) struct re_pattern_buffer *bufp; { re_dfa_t *dfa = (re_dfa_t *) bufp->buffer; char *fastmap = bufp->fastmap; memset (fastmap, '\0', sizeof (char) * SBC_MAX); re_compile_fastmap_iter (bufp, dfa->init_state, fastmap); if (dfa->init_state != dfa->init_state_word) re_compile_fastmap_iter (bufp, dfa->init_state_word, fastmap); if (dfa->init_state != dfa->init_state_nl) re_compile_fastmap_iter (bufp, dfa->init_state_nl, fastmap); if (dfa->init_state != dfa->init_state_begbuf) re_compile_fastmap_iter (bufp, dfa->init_state_begbuf, fastmap); bufp->fastmap_accurate = 1; return 0; } #ifdef _LIBC weak_alias (__re_compile_fastmap, re_compile_fastmap) #endif static inline void __attribute ((always_inline)) re_set_fastmap (char *fastmap, int icase, int ch) { fastmap[ch] = 1; if (icase) fastmap[tolower (ch)] = 1; } /* Helper function for re_compile_fastmap. Compile fastmap for the initial_state INIT_STATE. */ static void re_compile_fastmap_iter (regex_t *bufp, const re_dfastate_t *init_state, char *fastmap) { re_dfa_t *dfa = (re_dfa_t *) bufp->buffer; int node_cnt; int icase = (dfa->mb_cur_max == 1 && (bufp->syntax & RE_ICASE)); for (node_cnt = 0; node_cnt < init_state->nodes.nelem; ++node_cnt) { int node = init_state->nodes.elems[node_cnt]; re_token_type_t type = dfa->nodes[node].type; if (type == CHARACTER) { re_set_fastmap (fastmap, icase, dfa->nodes[node].opr.c); #ifdef RE_ENABLE_I18N if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1) { unsigned char *buf = alloca (dfa->mb_cur_max), *p; wchar_t wc; mbstate_t state; p = buf; *p++ = dfa->nodes[node].opr.c; while (++node < dfa->nodes_len && dfa->nodes[node].type == CHARACTER && dfa->nodes[node].mb_partial) *p++ = dfa->nodes[node].opr.c; memset (&state, '\0', sizeof (state)); if (mbrtowc (&wc, (const char *) buf, p - buf, &state) == p - buf && (__wcrtomb ((char *) buf, towlower (wc), &state) != (size_t) -1)) re_set_fastmap (fastmap, 0, buf[0]); } #endif } else if (type == SIMPLE_BRACKET) { int i, ch; for (i = 0, ch = 0; i < BITSET_WORDS; ++i) { int j; bitset_word_t w = dfa->nodes[node].opr.sbcset[i]; for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch) if (w & ((bitset_word_t) 1 << j)) re_set_fastmap (fastmap, icase, ch); } } #ifdef RE_ENABLE_I18N else if (type == COMPLEX_BRACKET) { int i; re_charset_t *cset = dfa->nodes[node].opr.mbcset; if (cset->non_match || cset->ncoll_syms || cset->nequiv_classes || cset->nranges || cset->nchar_classes) { # ifdef _LIBC if (_NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES) != 0) { /* In this case we want to catch the bytes which are the first byte of any collation elements. e.g. In da_DK, we want to catch 'a' since "aa" is a valid collation element, and don't catch 'b' since 'b' is the only collation element which starts from 'b'. */ const int32_t *table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); for (i = 0; i < SBC_MAX; ++i) if (table[i] < 0) re_set_fastmap (fastmap, icase, i); } # else if (dfa->mb_cur_max > 1) for (i = 0; i < SBC_MAX; ++i) if (__btowc (i) == WEOF) re_set_fastmap (fastmap, icase, i); # endif /* not _LIBC */ } for (i = 0; i < cset->nmbchars; ++i) { char buf[256]; mbstate_t state; memset (&state, '\0', sizeof (state)); if (__wcrtomb (buf, cset->mbchars[i], &state) != (size_t) -1) re_set_fastmap (fastmap, icase, *(unsigned char *) buf); if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1) { if (__wcrtomb (buf, towlower (cset->mbchars[i]), &state) != (size_t) -1) re_set_fastmap (fastmap, 0, *(unsigned char *) buf); } } } #endif /* RE_ENABLE_I18N */ else if (type == OP_PERIOD #ifdef RE_ENABLE_I18N || type == OP_UTF8_PERIOD #endif /* RE_ENABLE_I18N */ || type == END_OF_RE) { memset (fastmap, '\1', sizeof (char) * SBC_MAX); if (type == END_OF_RE) bufp->can_be_null = 1; return; } } } /* Entry point for POSIX code. */ /* regcomp takes a regular expression as a string and compiles it. PREG is a regex_t *. We do not expect any fields to be initialized, since POSIX says we shouldn't. Thus, we set `buffer' to the compiled pattern; `used' to the length of the compiled pattern; `syntax' to RE_SYNTAX_POSIX_EXTENDED if the REG_EXTENDED bit in CFLAGS is set; otherwise, to RE_SYNTAX_POSIX_BASIC; `newline_anchor' to REG_NEWLINE being set in CFLAGS; `fastmap' to an allocated space for the fastmap; `fastmap_accurate' to zero; `re_nsub' to the number of subexpressions in PATTERN. PATTERN is the address of the pattern string. CFLAGS is a series of bits which affect compilation. If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we use POSIX basic syntax. If REG_NEWLINE is set, then . and [^...] don't match newline. Also, regexec will try a match beginning after every newline. If REG_ICASE is set, then we considers upper- and lowercase versions of letters to be equivalent when matching. If REG_NOSUB is set, then when PREG is passed to regexec, that routine will report only success or failure, and nothing about the registers. It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for the return codes and their meanings.) */ int regcomp (preg, pattern, cflags) regex_t *__restrict preg; const char *__restrict pattern; int cflags; { reg_errcode_t ret; reg_syntax_t syntax = ((cflags & REG_EXTENDED) ? RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC); preg->buffer = NULL; preg->allocated = 0; preg->used = 0; /* Try to allocate space for the fastmap. */ preg->fastmap = re_malloc (char, SBC_MAX); if (BE (preg->fastmap == NULL, 0)) return REG_ESPACE; syntax |= (cflags & REG_ICASE) ? RE_ICASE : 0; /* If REG_NEWLINE is set, newlines are treated differently. */ if (cflags & REG_NEWLINE) { /* REG_NEWLINE implies neither . nor [^...] match newline. */ syntax &= ~RE_DOT_NEWLINE; syntax |= RE_HAT_LISTS_NOT_NEWLINE; /* It also changes the matching behavior. */ preg->newline_anchor = 1; } else preg->newline_anchor = 0; preg->no_sub = !!(cflags & REG_NOSUB); preg->translate = NULL; ret = re_compile_internal (preg, pattern, strlen (pattern), syntax); /* POSIX doesn't distinguish between an unmatched open-group and an unmatched close-group: both are REG_EPAREN. */ if (ret == REG_ERPAREN) ret = REG_EPAREN; /* We have already checked preg->fastmap != NULL. */ if (BE (ret == REG_NOERROR, 1)) /* Compute the fastmap now, since regexec cannot modify the pattern buffer. This function never fails in this implementation. */ (void) re_compile_fastmap (preg); else { /* Some error occurred while compiling the expression. */ re_free (preg->fastmap); preg->fastmap = NULL; } return (int) ret; } #ifdef _LIBC weak_alias (__regcomp, regcomp) #endif /* Returns a message corresponding to an error code, ERRCODE, returned from either regcomp or regexec. We don't use PREG here. */ size_t regerror (errcode, preg, errbuf, errbuf_size) int errcode; const regex_t *__restrict preg; char *__restrict errbuf; size_t errbuf_size; { const char *msg; size_t msg_size; if (BE (errcode < 0 || errcode >= (int) (sizeof (__re_error_msgid_idx) / sizeof (__re_error_msgid_idx[0])), 0)) /* Only error codes returned by the rest of the code should be passed to this routine. If we are given anything else, or if other regex code generates an invalid error code, then the program has a bug. Dump core so we can fix it. */ abort (); msg = gettext (__re_error_msgid + __re_error_msgid_idx[errcode]); msg_size = strlen (msg) + 1; /* Includes the null. */ if (BE (errbuf_size != 0, 1)) { if (BE (msg_size > errbuf_size, 0)) { #if defined HAVE_MEMPCPY || defined _LIBC *((char *) __mempcpy (errbuf, msg, errbuf_size - 1)) = '\0'; #else memcpy (errbuf, msg, errbuf_size - 1); errbuf[errbuf_size - 1] = 0; #endif } else memcpy (errbuf, msg, msg_size); } return msg_size; } #ifdef _LIBC weak_alias (__regerror, regerror) #endif #ifdef RE_ENABLE_I18N /* This static array is used for the map to single-byte characters when UTF-8 is used. Otherwise we would allocate memory just to initialize it the same all the time. UTF-8 is the preferred encoding so this is a worthwhile optimization. */ static const bitset_t utf8_sb_map = { /* Set the first 128 bits. */ [0 ... 0x80 / BITSET_WORD_BITS - 1] = BITSET_WORD_MAX }; #endif static void free_dfa_content (re_dfa_t *dfa) { int i, j; if (dfa->nodes) for (i = 0; i < dfa->nodes_len; ++i) free_token (dfa->nodes + i); re_free (dfa->nexts); for (i = 0; i < dfa->nodes_len; ++i) { if (dfa->eclosures != NULL) re_node_set_free (dfa->eclosures + i); if (dfa->inveclosures != NULL) re_node_set_free (dfa->inveclosures + i); if (dfa->edests != NULL) re_node_set_free (dfa->edests + i); } re_free (dfa->edests); re_free (dfa->eclosures); re_free (dfa->inveclosures); re_free (dfa->nodes); if (dfa->state_table) for (i = 0; i <= dfa->state_hash_mask; ++i) { struct re_state_table_entry *entry = dfa->state_table + i; for (j = 0; j < entry->num; ++j) { re_dfastate_t *state = entry->array[j]; free_state (state); } re_free (entry->array); } re_free (dfa->state_table); #ifdef RE_ENABLE_I18N if (dfa->sb_char != utf8_sb_map) re_free (dfa->sb_char); #endif re_free (dfa->subexp_map); #ifdef DEBUG re_free (dfa->re_str); #endif re_free (dfa); } /* Free dynamically allocated space used by PREG. */ void regfree (preg) regex_t *preg; { re_dfa_t *dfa = (re_dfa_t *) preg->buffer; if (BE (dfa != NULL, 1)) free_dfa_content (dfa); preg->buffer = NULL; preg->allocated = 0; re_free (preg->fastmap); preg->fastmap = NULL; re_free (preg->translate); preg->translate = NULL; } #ifdef _LIBC weak_alias (__regfree, regfree) #endif /* Entry points compatible with 4.2 BSD regex library. We don't define them unless specifically requested. */ #if defined _REGEX_RE_COMP || defined _LIBC /* BSD has one and only one pattern buffer. */ static struct re_pattern_buffer re_comp_buf; char * # ifdef _LIBC /* Make these definitions weak in libc, so POSIX programs can redefine these names if they don't use our functions, and still use regcomp/regexec above without link errors. */ weak_function # endif re_comp (s) const char *s; { reg_errcode_t ret; char *fastmap; if (!s) { if (!re_comp_buf.buffer) return gettext ("No previous regular expression"); return 0; } if (re_comp_buf.buffer) { fastmap = re_comp_buf.fastmap; re_comp_buf.fastmap = NULL; __regfree (&re_comp_buf); memset (&re_comp_buf, '\0', sizeof (re_comp_buf)); re_comp_buf.fastmap = fastmap; } if (re_comp_buf.fastmap == NULL) { re_comp_buf.fastmap = (char *) malloc (SBC_MAX); if (re_comp_buf.fastmap == NULL) return (char *) gettext (__re_error_msgid + __re_error_msgid_idx[(int) REG_ESPACE]); } /* Since `re_exec' always passes NULL for the `regs' argument, we don't need to initialize the pattern buffer fields which affect it. */ /* Match anchors at newlines. */ re_comp_buf.newline_anchor = 1; ret = re_compile_internal (&re_comp_buf, s, strlen (s), re_syntax_options); if (!ret) return NULL; /* Yes, we're discarding `const' here if !HAVE_LIBINTL. */ return (char *) gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]); } #ifdef _LIBC libc_freeres_fn (free_mem) { __regfree (&re_comp_buf); } #endif #endif /* _REGEX_RE_COMP */ /* Internal entry point. Compile the regular expression PATTERN, whose length is LENGTH. SYNTAX indicate regular expression's syntax. */ static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern, size_t length, reg_syntax_t syntax) { reg_errcode_t err = REG_NOERROR; re_dfa_t *dfa; re_string_t regexp; /* Initialize the pattern buffer. */ preg->fastmap_accurate = 0; preg->syntax = syntax; preg->not_bol = preg->not_eol = 0; preg->used = 0; preg->re_nsub = 0; preg->can_be_null = 0; preg->regs_allocated = REGS_UNALLOCATED; /* Initialize the dfa. */ dfa = (re_dfa_t *) preg->buffer; if (BE (preg->allocated < sizeof (re_dfa_t), 0)) { /* If zero allocated, but buffer is non-null, try to realloc enough space. This loses if buffer's address is bogus, but that is the user's responsibility. If ->buffer is NULL this is a simple allocation. */ dfa = re_realloc (preg->buffer, re_dfa_t, 1); if (dfa == NULL) return REG_ESPACE; preg->allocated = sizeof (re_dfa_t); preg->buffer = (unsigned char *) dfa; } preg->used = sizeof (re_dfa_t); err = init_dfa (dfa, length); if (BE (err != REG_NOERROR, 0)) { free_dfa_content (dfa); preg->buffer = NULL; preg->allocated = 0; return err; } #ifdef DEBUG /* Note: length+1 will not overflow since it is checked in init_dfa. */ dfa->re_str = re_malloc (char, length + 1); strncpy (dfa->re_str, pattern, length + 1); #endif __libc_lock_init (dfa->lock); err = re_string_construct (®exp, pattern, length, preg->translate, syntax & RE_ICASE, dfa); if (BE (err != REG_NOERROR, 0)) { re_compile_internal_free_return: free_workarea_compile (preg); re_string_destruct (®exp); free_dfa_content (dfa); preg->buffer = NULL; preg->allocated = 0; return err; } /* Parse the regular expression, and build a structure tree. */ preg->re_nsub = 0; dfa->str_tree = parse (®exp, preg, syntax, &err); if (BE (dfa->str_tree == NULL, 0)) goto re_compile_internal_free_return; /* Analyze the tree and create the nfa. */ err = analyze (preg); if (BE (err != REG_NOERROR, 0)) goto re_compile_internal_free_return; #ifdef RE_ENABLE_I18N /* If possible, do searching in single byte encoding to speed things up. */ if (dfa->is_utf8 && !(syntax & RE_ICASE) && preg->translate == NULL) optimize_utf8 (dfa); #endif /* Then create the initial state of the dfa. */ err = create_initial_state (dfa); /* Release work areas. */ free_workarea_compile (preg); re_string_destruct (®exp); if (BE (err != REG_NOERROR, 0)) { free_dfa_content (dfa); preg->buffer = NULL; preg->allocated = 0; } return err; } /* Initialize DFA. We use the length of the regular expression PAT_LEN as the initial length of some arrays. */ static reg_errcode_t init_dfa (re_dfa_t *dfa, size_t pat_len) { unsigned int table_size; #ifndef _LIBC char *codeset_name; #endif memset (dfa, '\0', sizeof (re_dfa_t)); /* Force allocation of str_tree_storage the first time. */ dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE; /* Avoid overflows. */ if (pat_len == SIZE_MAX) return REG_ESPACE; dfa->nodes_alloc = pat_len + 1; dfa->nodes = re_malloc (re_token_t, dfa->nodes_alloc); /* table_size = 2 ^ ceil(log pat_len) */ for (table_size = 1; ; table_size <<= 1) if (table_size > pat_len) break; dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size); dfa->state_hash_mask = table_size - 1; dfa->mb_cur_max = MB_CUR_MAX; #ifdef _LIBC if (dfa->mb_cur_max == 6 && strcmp (_NL_CURRENT (LC_CTYPE, _NL_CTYPE_CODESET_NAME), "UTF-8") == 0) dfa->is_utf8 = 1; dfa->map_notascii = (_NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_MAP_TO_NONASCII) != 0); #else # ifdef HAVE_LANGINFO_CODESET codeset_name = nl_langinfo (CODESET); # else codeset_name = getenv ("LC_ALL"); if (codeset_name == NULL || codeset_name[0] == '\0') codeset_name = getenv ("LC_CTYPE"); if (codeset_name == NULL || codeset_name[0] == '\0') codeset_name = getenv ("LANG"); if (codeset_name == NULL) codeset_name = ""; else if (strchr (codeset_name, '.') != NULL) codeset_name = strchr (codeset_name, '.') + 1; # endif if (strcasecmp (codeset_name, "UTF-8") == 0 || strcasecmp (codeset_name, "UTF8") == 0) dfa->is_utf8 = 1; /* We check exhaustively in the loop below if this charset is a superset of ASCII. */ dfa->map_notascii = 0; #endif #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) { if (dfa->is_utf8) dfa->sb_char = (re_bitset_ptr_t) utf8_sb_map; else { int i, j, ch; dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); if (BE (dfa->sb_char == NULL, 0)) return REG_ESPACE; /* Set the bits corresponding to single byte chars. */ for (i = 0, ch = 0; i < BITSET_WORDS; ++i) for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch) { wint_t wch = __btowc (ch); if (wch != WEOF) dfa->sb_char[i] |= (bitset_word_t) 1 << j; # ifndef _LIBC if (isascii (ch) && wch != ch) dfa->map_notascii = 1; # endif } } } #endif if (BE (dfa->nodes == NULL || dfa->state_table == NULL, 0)) return REG_ESPACE; return REG_NOERROR; } /* Initialize WORD_CHAR table, which indicate which character is "word". In this case "word" means that it is the word construction character used by some operators like "\<", "\>", etc. */ static void internal_function init_word_char (re_dfa_t *dfa) { int i, j, ch; dfa->word_ops_used = 1; for (i = 0, ch = 0; i < BITSET_WORDS; ++i) for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch) if (isalnum (ch) || ch == '_') dfa->word_char[i] |= (bitset_word_t) 1 << j; } /* Free the work area which are only used while compiling. */ static void free_workarea_compile (regex_t *preg) { re_dfa_t *dfa = (re_dfa_t *) preg->buffer; bin_tree_storage_t *storage, *next; for (storage = dfa->str_tree_storage; storage; storage = next) { next = storage->next; re_free (storage); } dfa->str_tree_storage = NULL; dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE; dfa->str_tree = NULL; re_free (dfa->org_indices); dfa->org_indices = NULL; } /* Create initial states for all contexts. */ static reg_errcode_t create_initial_state (re_dfa_t *dfa) { int first, i; reg_errcode_t err; re_node_set init_nodes; /* Initial states have the epsilon closure of the node which is the first node of the regular expression. */ first = dfa->str_tree->first->node_idx; dfa->init_node = first; err = re_node_set_init_copy (&init_nodes, dfa->eclosures + first); if (BE (err != REG_NOERROR, 0)) return err; /* The back-references which are in initial states can epsilon transit, since in this case all of the subexpressions can be null. Then we add epsilon closures of the nodes which are the next nodes of the back-references. */ if (dfa->nbackref > 0) for (i = 0; i < init_nodes.nelem; ++i) { int node_idx = init_nodes.elems[i]; re_token_type_t type = dfa->nodes[node_idx].type; int clexp_idx; if (type != OP_BACK_REF) continue; for (clexp_idx = 0; clexp_idx < init_nodes.nelem; ++clexp_idx) { re_token_t *clexp_node; clexp_node = dfa->nodes + init_nodes.elems[clexp_idx]; if (clexp_node->type == OP_CLOSE_SUBEXP && clexp_node->opr.idx == dfa->nodes[node_idx].opr.idx) break; } if (clexp_idx == init_nodes.nelem) continue; if (type == OP_BACK_REF) { int dest_idx = dfa->edests[node_idx].elems[0]; if (!re_node_set_contains (&init_nodes, dest_idx)) { re_node_set_merge (&init_nodes, dfa->eclosures + dest_idx); i = 0; } } } /* It must be the first time to invoke acquire_state. */ dfa->init_state = re_acquire_state_context (&err, dfa, &init_nodes, 0); /* We don't check ERR here, since the initial state must not be NULL. */ if (BE (dfa->init_state == NULL, 0)) return err; if (dfa->init_state->has_constraint) { dfa->init_state_word = re_acquire_state_context (&err, dfa, &init_nodes, CONTEXT_WORD); dfa->init_state_nl = re_acquire_state_context (&err, dfa, &init_nodes, CONTEXT_NEWLINE); dfa->init_state_begbuf = re_acquire_state_context (&err, dfa, &init_nodes, CONTEXT_NEWLINE | CONTEXT_BEGBUF); if (BE (dfa->init_state_word == NULL || dfa->init_state_nl == NULL || dfa->init_state_begbuf == NULL, 0)) return err; } else dfa->init_state_word = dfa->init_state_nl = dfa->init_state_begbuf = dfa->init_state; re_node_set_free (&init_nodes); return REG_NOERROR; } #ifdef RE_ENABLE_I18N /* If it is possible to do searching in single byte encoding instead of UTF-8 to speed things up, set dfa->mb_cur_max to 1, clear is_utf8 and change DFA nodes where needed. */ static void optimize_utf8 (re_dfa_t *dfa) { int node, i, mb_chars = 0, has_period = 0; for (node = 0; node < dfa->nodes_len; ++node) switch (dfa->nodes[node].type) { case CHARACTER: if (dfa->nodes[node].opr.c >= 0x80) mb_chars = 1; break; case ANCHOR: switch (dfa->nodes[node].opr.idx) { case LINE_FIRST: case LINE_LAST: case BUF_FIRST: case BUF_LAST: break; default: /* Word anchors etc. cannot be handled. */ return; } break; case OP_PERIOD: has_period = 1; break; case OP_BACK_REF: case OP_ALT: case END_OF_RE: case OP_DUP_ASTERISK: case OP_OPEN_SUBEXP: case OP_CLOSE_SUBEXP: break; case COMPLEX_BRACKET: return; case SIMPLE_BRACKET: /* Just double check. The non-ASCII range starts at 0x80. */ assert (0x80 % BITSET_WORD_BITS == 0); for (i = 0x80 / BITSET_WORD_BITS; i < BITSET_WORDS; ++i) if (dfa->nodes[node].opr.sbcset[i]) return; break; default: abort (); } if (mb_chars || has_period) for (node = 0; node < dfa->nodes_len; ++node) { if (dfa->nodes[node].type == CHARACTER && dfa->nodes[node].opr.c >= 0x80) dfa->nodes[node].mb_partial = 0; else if (dfa->nodes[node].type == OP_PERIOD) dfa->nodes[node].type = OP_UTF8_PERIOD; } /* The search can be in single byte locale. */ dfa->mb_cur_max = 1; dfa->is_utf8 = 0; dfa->has_mb_node = dfa->nbackref > 0 || has_period; } #endif /* Analyze the structure tree, and calculate "first", "next", "edest", "eclosure", and "inveclosure". */ static reg_errcode_t analyze (regex_t *preg) { re_dfa_t *dfa = (re_dfa_t *) preg->buffer; reg_errcode_t ret; /* Allocate arrays. */ dfa->nexts = re_malloc (int, dfa->nodes_alloc); dfa->org_indices = re_malloc (int, dfa->nodes_alloc); dfa->edests = re_malloc (re_node_set, dfa->nodes_alloc); dfa->eclosures = re_malloc (re_node_set, dfa->nodes_alloc); if (BE (dfa->nexts == NULL || dfa->org_indices == NULL || dfa->edests == NULL || dfa->eclosures == NULL, 0)) return REG_ESPACE; dfa->subexp_map = re_malloc (int, preg->re_nsub); if (dfa->subexp_map != NULL) { int i; for (i = 0; i < preg->re_nsub; i++) dfa->subexp_map[i] = i; preorder (dfa->str_tree, optimize_subexps, dfa); for (i = 0; i < preg->re_nsub; i++) if (dfa->subexp_map[i] != i) break; if (i == preg->re_nsub) { free (dfa->subexp_map); dfa->subexp_map = NULL; } } ret = postorder (dfa->str_tree, lower_subexps, preg); if (BE (ret != REG_NOERROR, 0)) return ret; ret = postorder (dfa->str_tree, calc_first, dfa); if (BE (ret != REG_NOERROR, 0)) return ret; preorder (dfa->str_tree, calc_next, dfa); ret = preorder (dfa->str_tree, link_nfa_nodes, dfa); if (BE (ret != REG_NOERROR, 0)) return ret; ret = calc_eclosure (dfa); if (BE (ret != REG_NOERROR, 0)) return ret; /* We only need this during the prune_impossible_nodes pass in regexec.c; skip it if p_i_n will not run, as calc_inveclosure can be quadratic. */ if ((!preg->no_sub && preg->re_nsub > 0 && dfa->has_plural_match) || dfa->nbackref) { dfa->inveclosures = re_malloc (re_node_set, dfa->nodes_len); if (BE (dfa->inveclosures == NULL, 0)) return REG_ESPACE; ret = calc_inveclosure (dfa); } return ret; } /* Our parse trees are very unbalanced, so we cannot use a stack to implement parse tree visits. Instead, we use parent pointers and some hairy code in these two functions. */ static reg_errcode_t postorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)), void *extra) { bin_tree_t *node, *prev; for (node = root; ; ) { /* Descend down the tree, preferably to the left (or to the right if that's the only child). */ while (node->left || node->right) if (node->left) node = node->left; else node = node->right; do { reg_errcode_t err = fn (extra, node); if (BE (err != REG_NOERROR, 0)) return err; if (node->parent == NULL) return REG_NOERROR; prev = node; node = node->parent; } /* Go up while we have a node that is reached from the right. */ while (node->right == prev || node->right == NULL); node = node->right; } } static reg_errcode_t preorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)), void *extra) { bin_tree_t *node; for (node = root; ; ) { reg_errcode_t err = fn (extra, node); if (BE (err != REG_NOERROR, 0)) return err; /* Go to the left node, or up and to the right. */ if (node->left) node = node->left; else { bin_tree_t *prev = NULL; while (node->right == prev || node->right == NULL) { prev = node; node = node->parent; if (!node) return REG_NOERROR; } node = node->right; } } } /* Optimization pass: if a SUBEXP is entirely contained, strip it and tell re_search_internal to map the inner one's opr.idx to this one's. Adjust backreferences as well. Requires a preorder visit. */ static reg_errcode_t optimize_subexps (void *extra, bin_tree_t *node) { re_dfa_t *dfa = (re_dfa_t *) extra; if (node->token.type == OP_BACK_REF && dfa->subexp_map) { int idx = node->token.opr.idx; node->token.opr.idx = dfa->subexp_map[idx]; dfa->used_bkref_map |= 1 << node->token.opr.idx; } else if (node->token.type == SUBEXP && node->left && node->left->token.type == SUBEXP) { int other_idx = node->left->token.opr.idx; node->left = node->left->left; if (node->left) node->left->parent = node; dfa->subexp_map[other_idx] = dfa->subexp_map[node->token.opr.idx]; if (other_idx < BITSET_WORD_BITS) dfa->used_bkref_map &= ~((bitset_word_t) 1 << other_idx); } return REG_NOERROR; } /* Lowering pass: Turn each SUBEXP node into the appropriate concatenation of OP_OPEN_SUBEXP, the body of the SUBEXP (if any) and OP_CLOSE_SUBEXP. */ static reg_errcode_t lower_subexps (void *extra, bin_tree_t *node) { regex_t *preg = (regex_t *) extra; reg_errcode_t err = REG_NOERROR; if (node->left && node->left->token.type == SUBEXP) { node->left = lower_subexp (&err, preg, node->left); if (node->left) node->left->parent = node; } if (node->right && node->right->token.type == SUBEXP) { node->right = lower_subexp (&err, preg, node->right); if (node->right) node->right->parent = node; } return err; } static bin_tree_t * lower_subexp (reg_errcode_t *err, regex_t *preg, bin_tree_t *node) { re_dfa_t *dfa = (re_dfa_t *) preg->buffer; bin_tree_t *body = node->left; bin_tree_t *op, *cls, *tree1, *tree; if (preg->no_sub /* We do not optimize empty subexpressions, because otherwise we may have bad CONCAT nodes with NULL children. This is obviously not very common, so we do not lose much. An example that triggers this case is the sed "script" /\(\)/x. */ && node->left != NULL && (node->token.opr.idx >= BITSET_WORD_BITS || !(dfa->used_bkref_map & ((bitset_word_t) 1 << node->token.opr.idx)))) return node->left; /* Convert the SUBEXP node to the concatenation of an OP_OPEN_SUBEXP, the contents, and an OP_CLOSE_SUBEXP. */ op = create_tree (dfa, NULL, NULL, OP_OPEN_SUBEXP); cls = create_tree (dfa, NULL, NULL, OP_CLOSE_SUBEXP); tree1 = body ? create_tree (dfa, body, cls, CONCAT) : cls; tree = create_tree (dfa, op, tree1, CONCAT); if (BE (tree == NULL || tree1 == NULL || op == NULL || cls == NULL, 0)) { *err = REG_ESPACE; return NULL; } op->token.opr.idx = cls->token.opr.idx = node->token.opr.idx; op->token.opt_subexp = cls->token.opt_subexp = node->token.opt_subexp; return tree; } /* Pass 1 in building the NFA: compute FIRST and create unlinked automaton nodes. Requires a postorder visit. */ static reg_errcode_t calc_first (void *extra, bin_tree_t *node) { re_dfa_t *dfa = (re_dfa_t *) extra; if (node->token.type == CONCAT) { node->first = node->left->first; node->node_idx = node->left->node_idx; } else { node->first = node; node->node_idx = re_dfa_add_node (dfa, node->token); if (BE (node->node_idx == -1, 0)) return REG_ESPACE; } return REG_NOERROR; } /* Pass 2: compute NEXT on the tree. Preorder visit. */ static reg_errcode_t calc_next (void *extra, bin_tree_t *node) { switch (node->token.type) { case OP_DUP_ASTERISK: node->left->next = node; break; case CONCAT: node->left->next = node->right->first; node->right->next = node->next; break; default: if (node->left) node->left->next = node->next; if (node->right) node->right->next = node->next; break; } return REG_NOERROR; } /* Pass 3: link all DFA nodes to their NEXT node (any order will do). */ static reg_errcode_t link_nfa_nodes (void *extra, bin_tree_t *node) { re_dfa_t *dfa = (re_dfa_t *) extra; int idx = node->node_idx; reg_errcode_t err = REG_NOERROR; switch (node->token.type) { case CONCAT: break; case END_OF_RE: assert (node->next == NULL); break; case OP_DUP_ASTERISK: case OP_ALT: { int left, right; dfa->has_plural_match = 1; if (node->left != NULL) left = node->left->first->node_idx; else left = node->next->node_idx; if (node->right != NULL) right = node->right->first->node_idx; else right = node->next->node_idx; assert (left > -1); assert (right > -1); err = re_node_set_init_2 (dfa->edests + idx, left, right); } break; case ANCHOR: case OP_OPEN_SUBEXP: case OP_CLOSE_SUBEXP: err = re_node_set_init_1 (dfa->edests + idx, node->next->node_idx); break; case OP_BACK_REF: dfa->nexts[idx] = node->next->node_idx; if (node->token.type == OP_BACK_REF) re_node_set_init_1 (dfa->edests + idx, dfa->nexts[idx]); break; default: assert (!IS_EPSILON_NODE (node->token.type)); dfa->nexts[idx] = node->next->node_idx; break; } return err; } /* Duplicate the epsilon closure of the node ROOT_NODE. Note that duplicated nodes have constraint INIT_CONSTRAINT in addition to their own constraint. */ static reg_errcode_t internal_function duplicate_node_closure (re_dfa_t *dfa, int top_org_node, int top_clone_node, int root_node, unsigned int init_constraint) { int org_node, clone_node, ret; unsigned int constraint = init_constraint; for (org_node = top_org_node, clone_node = top_clone_node;;) { int org_dest, clone_dest; if (dfa->nodes[org_node].type == OP_BACK_REF) { /* If the back reference epsilon-transit, its destination must also have the constraint. Then duplicate the epsilon closure of the destination of the back reference, and store it in edests of the back reference. */ org_dest = dfa->nexts[org_node]; re_node_set_empty (dfa->edests + clone_node); clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; dfa->nexts[clone_node] = dfa->nexts[org_node]; ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (ret < 0, 0)) return REG_ESPACE; } else if (dfa->edests[org_node].nelem == 0) { /* In case of the node can't epsilon-transit, don't duplicate the destination and store the original destination as the destination of the node. */ dfa->nexts[clone_node] = dfa->nexts[org_node]; break; } else if (dfa->edests[org_node].nelem == 1) { /* In case of the node can epsilon-transit, and it has only one destination. */ org_dest = dfa->edests[org_node].elems[0]; re_node_set_empty (dfa->edests + clone_node); if (dfa->nodes[org_node].type == ANCHOR) { /* In case of the node has another constraint, append it. */ if (org_node == root_node && clone_node != org_node) { /* ...but if the node is root_node itself, it means the epsilon closure have a loop, then tie it to the destination of the root_node. */ ret = re_node_set_insert (dfa->edests + clone_node, org_dest); if (BE (ret < 0, 0)) return REG_ESPACE; break; } constraint |= dfa->nodes[org_node].opr.ctx_type; } clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (ret < 0, 0)) return REG_ESPACE; } else /* dfa->edests[org_node].nelem == 2 */ { /* In case of the node can epsilon-transit, and it has two destinations. In the bin_tree_t and DFA, that's '|' and '*'. */ org_dest = dfa->edests[org_node].elems[0]; re_node_set_empty (dfa->edests + clone_node); /* Search for a duplicated node which satisfies the constraint. */ clone_dest = search_duplicated_node (dfa, org_dest, constraint); if (clone_dest == -1) { /* There are no such a duplicated node, create a new one. */ reg_errcode_t err; clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (ret < 0, 0)) return REG_ESPACE; err = duplicate_node_closure (dfa, org_dest, clone_dest, root_node, constraint); if (BE (err != REG_NOERROR, 0)) return err; } else { /* There are a duplicated node which satisfy the constraint, use it to avoid infinite loop. */ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (ret < 0, 0)) return REG_ESPACE; } org_dest = dfa->edests[org_node].elems[1]; clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (ret < 0, 0)) return REG_ESPACE; } org_node = org_dest; clone_node = clone_dest; } return REG_NOERROR; } /* Search for a node which is duplicated from the node ORG_NODE, and satisfies the constraint CONSTRAINT. */ static int search_duplicated_node (const re_dfa_t *dfa, int org_node, unsigned int constraint) { int idx; for (idx = dfa->nodes_len - 1; dfa->nodes[idx].duplicated && idx > 0; --idx) { if (org_node == dfa->org_indices[idx] && constraint == dfa->nodes[idx].constraint) return idx; /* Found. */ } return -1; /* Not found. */ } /* Duplicate the node whose index is ORG_IDX and set the constraint CONSTRAINT. Return the index of the new node, or -1 if insufficient storage is available. */ static int duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint) { int dup_idx = re_dfa_add_node (dfa, dfa->nodes[org_idx]); if (BE (dup_idx != -1, 1)) { dfa->nodes[dup_idx].constraint = constraint; if (dfa->nodes[org_idx].type == ANCHOR) dfa->nodes[dup_idx].constraint |= dfa->nodes[org_idx].opr.ctx_type; dfa->nodes[dup_idx].duplicated = 1; /* Store the index of the original node. */ dfa->org_indices[dup_idx] = org_idx; } return dup_idx; } static reg_errcode_t calc_inveclosure (re_dfa_t *dfa) { int src, idx, ret; for (idx = 0; idx < dfa->nodes_len; ++idx) re_node_set_init_empty (dfa->inveclosures + idx); for (src = 0; src < dfa->nodes_len; ++src) { int *elems = dfa->eclosures[src].elems; for (idx = 0; idx < dfa->eclosures[src].nelem; ++idx) { ret = re_node_set_insert_last (dfa->inveclosures + elems[idx], src); if (BE (ret == -1, 0)) return REG_ESPACE; } } return REG_NOERROR; } /* Calculate "eclosure" for all the node in DFA. */ static reg_errcode_t calc_eclosure (re_dfa_t *dfa) { int node_idx, incomplete; #ifdef DEBUG assert (dfa->nodes_len > 0); #endif incomplete = 0; /* For each nodes, calculate epsilon closure. */ for (node_idx = 0; ; ++node_idx) { reg_errcode_t err; re_node_set eclosure_elem; if (node_idx == dfa->nodes_len) { if (!incomplete) break; incomplete = 0; node_idx = 0; } #ifdef DEBUG assert (dfa->eclosures[node_idx].nelem != -1); #endif /* If we have already calculated, skip it. */ if (dfa->eclosures[node_idx].nelem != 0) continue; /* Calculate epsilon closure of `node_idx'. */ err = calc_eclosure_iter (&eclosure_elem, dfa, node_idx, 1); if (BE (err != REG_NOERROR, 0)) return err; if (dfa->eclosures[node_idx].nelem == 0) { incomplete = 1; re_node_set_free (&eclosure_elem); } } return REG_NOERROR; } /* Calculate epsilon closure of NODE. */ static reg_errcode_t calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, int node, int root) { reg_errcode_t err; unsigned int constraint; int i, incomplete; re_node_set eclosure; incomplete = 0; err = re_node_set_alloc (&eclosure, dfa->edests[node].nelem + 1); if (BE (err != REG_NOERROR, 0)) return err; /* This indicates that we are calculating this node now. We reference this value to avoid infinite loop. */ dfa->eclosures[node].nelem = -1; constraint = ((dfa->nodes[node].type == ANCHOR) ? dfa->nodes[node].opr.ctx_type : 0); /* If the current node has constraints, duplicate all nodes. Since they must inherit the constraints. */ if (constraint && dfa->edests[node].nelem && !dfa->nodes[dfa->edests[node].elems[0]].duplicated) { err = duplicate_node_closure (dfa, node, node, node, constraint); if (BE (err != REG_NOERROR, 0)) return err; } /* Expand each epsilon destination nodes. */ if (IS_EPSILON_NODE(dfa->nodes[node].type)) for (i = 0; i < dfa->edests[node].nelem; ++i) { re_node_set eclosure_elem; int edest = dfa->edests[node].elems[i]; /* If calculating the epsilon closure of `edest' is in progress, return intermediate result. */ if (dfa->eclosures[edest].nelem == -1) { incomplete = 1; continue; } /* If we haven't calculated the epsilon closure of `edest' yet, calculate now. Otherwise use calculated epsilon closure. */ if (dfa->eclosures[edest].nelem == 0) { err = calc_eclosure_iter (&eclosure_elem, dfa, edest, 0); if (BE (err != REG_NOERROR, 0)) return err; } else eclosure_elem = dfa->eclosures[edest]; /* Merge the epsilon closure of `edest'. */ re_node_set_merge (&eclosure, &eclosure_elem); /* If the epsilon closure of `edest' is incomplete, the epsilon closure of this node is also incomplete. */ if (dfa->eclosures[edest].nelem == 0) { incomplete = 1; re_node_set_free (&eclosure_elem); } } /* Epsilon closures include itself. */ re_node_set_insert (&eclosure, node); if (incomplete && !root) dfa->eclosures[node].nelem = 0; else dfa->eclosures[node] = eclosure; *new_set = eclosure; return REG_NOERROR; } /* Functions for token which are used in the parser. */ /* Fetch a token from INPUT. We must not use this function inside bracket expressions. */ static void internal_function fetch_token (re_token_t *result, re_string_t *input, reg_syntax_t syntax) { re_string_skip_bytes (input, peek_token (result, input, syntax)); } /* Peek a token from INPUT, and return the length of the token. We must not use this function inside bracket expressions. */ static int internal_function peek_token (re_token_t *token, re_string_t *input, reg_syntax_t syntax) { unsigned char c; if (re_string_eoi (input)) { token->type = END_OF_RE; return 0; } c = re_string_peek_byte (input, 0); token->opr.c = c; token->word_char = 0; #ifdef RE_ENABLE_I18N token->mb_partial = 0; if (input->mb_cur_max > 1 && !re_string_first_byte (input, re_string_cur_idx (input))) { token->type = CHARACTER; token->mb_partial = 1; return 1; } #endif if (c == '\\') { unsigned char c2; if (re_string_cur_idx (input) + 1 >= re_string_length (input)) { token->type = BACK_SLASH; return 1; } c2 = re_string_peek_byte_case (input, 1); token->opr.c = c2; token->type = CHARACTER; #ifdef RE_ENABLE_I18N if (input->mb_cur_max > 1) { wint_t wc = re_string_wchar_at (input, re_string_cur_idx (input) + 1); token->word_char = IS_WIDE_WORD_CHAR (wc) != 0; } else #endif token->word_char = IS_WORD_CHAR (c2) != 0; switch (c2) { case '|': if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_NO_BK_VBAR)) token->type = OP_ALT; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (!(syntax & RE_NO_BK_REFS)) { token->type = OP_BACK_REF; token->opr.idx = c2 - '1'; } break; case '<': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = WORD_FIRST; } break; case '>': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = WORD_LAST; } break; case 'b': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = WORD_DELIM; } break; case 'B': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = NOT_WORD_DELIM; } break; case 'w': if (!(syntax & RE_NO_GNU_OPS)) token->type = OP_WORD; break; case 'W': if (!(syntax & RE_NO_GNU_OPS)) token->type = OP_NOTWORD; break; case 's': if (!(syntax & RE_NO_GNU_OPS)) token->type = OP_SPACE; break; case 'S': if (!(syntax & RE_NO_GNU_OPS)) token->type = OP_NOTSPACE; break; case '`': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = BUF_FIRST; } break; case '\'': if (!(syntax & RE_NO_GNU_OPS)) { token->type = ANCHOR; token->opr.ctx_type = BUF_LAST; } break; case '(': if (!(syntax & RE_NO_BK_PARENS)) token->type = OP_OPEN_SUBEXP; break; case ')': if (!(syntax & RE_NO_BK_PARENS)) token->type = OP_CLOSE_SUBEXP; break; case '+': if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM)) token->type = OP_DUP_PLUS; break; case '?': if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM)) token->type = OP_DUP_QUESTION; break; case '{': if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES))) token->type = OP_OPEN_DUP_NUM; break; case '}': if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES))) token->type = OP_CLOSE_DUP_NUM; break; default: break; } return 2; } token->type = CHARACTER; #ifdef RE_ENABLE_I18N if (input->mb_cur_max > 1) { wint_t wc = re_string_wchar_at (input, re_string_cur_idx (input)); token->word_char = IS_WIDE_WORD_CHAR (wc) != 0; } else #endif token->word_char = IS_WORD_CHAR (token->opr.c); switch (c) { case '\n': if (syntax & RE_NEWLINE_ALT) token->type = OP_ALT; break; case '|': if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_NO_BK_VBAR)) token->type = OP_ALT; break; case '*': token->type = OP_DUP_ASTERISK; break; case '+': if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM)) token->type = OP_DUP_PLUS; break; case '?': if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM)) token->type = OP_DUP_QUESTION; break; case '{': if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) token->type = OP_OPEN_DUP_NUM; break; case '}': if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) token->type = OP_CLOSE_DUP_NUM; break; case '(': if (syntax & RE_NO_BK_PARENS) token->type = OP_OPEN_SUBEXP; break; case ')': if (syntax & RE_NO_BK_PARENS) token->type = OP_CLOSE_SUBEXP; break; case '[': token->type = OP_OPEN_BRACKET; break; case '.': token->type = OP_PERIOD; break; case '^': if (!(syntax & (RE_CONTEXT_INDEP_ANCHORS | RE_CARET_ANCHORS_HERE)) && re_string_cur_idx (input) != 0) { char prev = re_string_peek_byte (input, -1); if (!(syntax & RE_NEWLINE_ALT) || prev != '\n') break; } token->type = ANCHOR; token->opr.ctx_type = LINE_FIRST; break; case '$': if (!(syntax & RE_CONTEXT_INDEP_ANCHORS) && re_string_cur_idx (input) + 1 != re_string_length (input)) { re_token_t next; re_string_skip_bytes (input, 1); peek_token (&next, input, syntax); re_string_skip_bytes (input, -1); if (next.type != OP_ALT && next.type != OP_CLOSE_SUBEXP) break; } token->type = ANCHOR; token->opr.ctx_type = LINE_LAST; break; default: break; } return 1; } /* Peek a token from INPUT, and return the length of the token. We must not use this function out of bracket expressions. */ static int internal_function peek_token_bracket (re_token_t *token, re_string_t *input, reg_syntax_t syntax) { unsigned char c; if (re_string_eoi (input)) { token->type = END_OF_RE; return 0; } c = re_string_peek_byte (input, 0); token->opr.c = c; #ifdef RE_ENABLE_I18N if (input->mb_cur_max > 1 && !re_string_first_byte (input, re_string_cur_idx (input))) { token->type = CHARACTER; return 1; } #endif /* RE_ENABLE_I18N */ if (c == '\\' && (syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && re_string_cur_idx (input) + 1 < re_string_length (input)) { /* In this case, '\' escape a character. */ unsigned char c2; re_string_skip_bytes (input, 1); c2 = re_string_peek_byte (input, 0); token->opr.c = c2; token->type = CHARACTER; return 1; } if (c == '[') /* '[' is a special char in a bracket exps. */ { unsigned char c2; int token_len; if (re_string_cur_idx (input) + 1 < re_string_length (input)) c2 = re_string_peek_byte (input, 1); else c2 = 0; token->opr.c = c2; token_len = 2; switch (c2) { case '.': token->type = OP_OPEN_COLL_ELEM; break; case '=': token->type = OP_OPEN_EQUIV_CLASS; break; case ':': if (syntax & RE_CHAR_CLASSES) { token->type = OP_OPEN_CHAR_CLASS; break; } /* else fall through. */ default: token->type = CHARACTER; token->opr.c = c; token_len = 1; break; } return token_len; } switch (c) { case '-': token->type = OP_CHARSET_RANGE; break; case ']': token->type = OP_CLOSE_BRACKET; break; case '^': token->type = OP_NON_MATCH_LIST; break; default: token->type = CHARACTER; } return 1; } /* Functions for parser. */ /* Entry point of the parser. Parse the regular expression REGEXP and return the structure tree. If an error is occured, ERR is set by error code, and return NULL. This function build the following tree, from regular expression : CAT / \ / \ EOR CAT means concatenation. EOR means end of regular expression. */ static bin_tree_t * parse (re_string_t *regexp, regex_t *preg, reg_syntax_t syntax, reg_errcode_t *err) { re_dfa_t *dfa = (re_dfa_t *) preg->buffer; bin_tree_t *tree, *eor, *root; re_token_t current_token; dfa->syntax = syntax; fetch_token (¤t_token, regexp, syntax | RE_CARET_ANCHORS_HERE); tree = parse_reg_exp (regexp, preg, ¤t_token, syntax, 0, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; eor = create_tree (dfa, NULL, NULL, END_OF_RE); if (tree != NULL) root = create_tree (dfa, tree, eor, CONCAT); else root = eor; if (BE (eor == NULL || root == NULL, 0)) { *err = REG_ESPACE; return NULL; } return root; } /* This function build the following tree, from regular expression |: ALT / \ / \ ALT means alternative, which represents the operator `|'. */ static bin_tree_t * parse_reg_exp (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, int nest, reg_errcode_t *err) { re_dfa_t *dfa = (re_dfa_t *) preg->buffer; bin_tree_t *tree, *branch = NULL; tree = parse_branch (regexp, preg, token, syntax, nest, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; while (token->type == OP_ALT) { fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE); if (token->type != OP_ALT && token->type != END_OF_RE && (nest == 0 || token->type != OP_CLOSE_SUBEXP)) { branch = parse_branch (regexp, preg, token, syntax, nest, err); if (BE (*err != REG_NOERROR && branch == NULL, 0)) return NULL; } else branch = NULL; tree = create_tree (dfa, tree, branch, OP_ALT); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } } return tree; } /* This function build the following tree, from regular expression : CAT / \ / \ CAT means concatenation. */ static bin_tree_t * parse_branch (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, int nest, reg_errcode_t *err) { bin_tree_t *tree, *exp; re_dfa_t *dfa = (re_dfa_t *) preg->buffer; tree = parse_expression (regexp, preg, token, syntax, nest, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; while (token->type != OP_ALT && token->type != END_OF_RE && (nest == 0 || token->type != OP_CLOSE_SUBEXP)) { exp = parse_expression (regexp, preg, token, syntax, nest, err); if (BE (*err != REG_NOERROR && exp == NULL, 0)) { return NULL; } if (tree != NULL && exp != NULL) { tree = create_tree (dfa, tree, exp, CONCAT); if (tree == NULL) { *err = REG_ESPACE; return NULL; } } else if (tree == NULL) tree = exp; /* Otherwise exp == NULL, we don't need to create new tree. */ } return tree; } /* This function build the following tree, from regular expression a*: * | a */ static bin_tree_t * parse_expression (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, int nest, reg_errcode_t *err) { re_dfa_t *dfa = (re_dfa_t *) preg->buffer; bin_tree_t *tree; switch (token->type) { case CHARACTER: tree = create_token_tree (dfa, NULL, NULL, token); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) { while (!re_string_eoi (regexp) && !re_string_first_byte (regexp, re_string_cur_idx (regexp))) { bin_tree_t *mbc_remain; fetch_token (token, regexp, syntax); mbc_remain = create_token_tree (dfa, NULL, NULL, token); tree = create_tree (dfa, tree, mbc_remain, CONCAT); if (BE (mbc_remain == NULL || tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } } } #endif break; case OP_OPEN_SUBEXP: tree = parse_sub_exp (regexp, preg, token, syntax, nest + 1, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; break; case OP_OPEN_BRACKET: tree = parse_bracket_exp (regexp, dfa, token, syntax, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; break; case OP_BACK_REF: if (!BE (dfa->completed_bkref_map & (1 << token->opr.idx), 1)) { *err = REG_ESUBREG; return NULL; } dfa->used_bkref_map |= 1 << token->opr.idx; tree = create_token_tree (dfa, NULL, NULL, token); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } ++dfa->nbackref; dfa->has_mb_node = 1; break; case OP_OPEN_DUP_NUM: if (syntax & RE_CONTEXT_INVALID_DUP) { *err = REG_BADRPT; return NULL; } /* FALLTHROUGH */ case OP_DUP_ASTERISK: case OP_DUP_PLUS: case OP_DUP_QUESTION: if (syntax & RE_CONTEXT_INVALID_OPS) { *err = REG_BADRPT; return NULL; } else if (syntax & RE_CONTEXT_INDEP_OPS) { fetch_token (token, regexp, syntax); return parse_expression (regexp, preg, token, syntax, nest, err); } /* else fall through */ case OP_CLOSE_SUBEXP: if ((token->type == OP_CLOSE_SUBEXP) && !(syntax & RE_UNMATCHED_RIGHT_PAREN_ORD)) { *err = REG_ERPAREN; return NULL; } /* else fall through */ case OP_CLOSE_DUP_NUM: /* We treat it as a normal character. */ /* Then we can these characters as normal characters. */ token->type = CHARACTER; /* mb_partial and word_char bits should be initialized already by peek_token. */ tree = create_token_tree (dfa, NULL, NULL, token); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } break; case ANCHOR: if ((token->opr.ctx_type & (WORD_DELIM | NOT_WORD_DELIM | WORD_FIRST | WORD_LAST)) && dfa->word_ops_used == 0) init_word_char (dfa); if (token->opr.ctx_type == WORD_DELIM || token->opr.ctx_type == NOT_WORD_DELIM) { bin_tree_t *tree_first, *tree_last; if (token->opr.ctx_type == WORD_DELIM) { token->opr.ctx_type = WORD_FIRST; tree_first = create_token_tree (dfa, NULL, NULL, token); token->opr.ctx_type = WORD_LAST; } else { token->opr.ctx_type = INSIDE_WORD; tree_first = create_token_tree (dfa, NULL, NULL, token); token->opr.ctx_type = INSIDE_NOTWORD; } tree_last = create_token_tree (dfa, NULL, NULL, token); tree = create_tree (dfa, tree_first, tree_last, OP_ALT); if (BE (tree_first == NULL || tree_last == NULL || tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } } else { tree = create_token_tree (dfa, NULL, NULL, token); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } } /* We must return here, since ANCHORs can't be followed by repetition operators. eg. RE"^*" is invalid or "", it must not be "". */ fetch_token (token, regexp, syntax); return tree; case OP_PERIOD: tree = create_token_tree (dfa, NULL, NULL, token); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } if (dfa->mb_cur_max > 1) dfa->has_mb_node = 1; break; case OP_WORD: case OP_NOTWORD: tree = build_charclass_op (dfa, regexp->trans, (const unsigned char *) "alnum", (const unsigned char *) "_", token->type == OP_NOTWORD, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; break; case OP_SPACE: case OP_NOTSPACE: tree = build_charclass_op (dfa, regexp->trans, (const unsigned char *) "space", (const unsigned char *) "", token->type == OP_NOTSPACE, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; break; case OP_ALT: case END_OF_RE: return NULL; case BACK_SLASH: *err = REG_EESCAPE; return NULL; default: /* Must not happen? */ #ifdef DEBUG assert (0); #endif return NULL; } fetch_token (token, regexp, syntax); while (token->type == OP_DUP_ASTERISK || token->type == OP_DUP_PLUS || token->type == OP_DUP_QUESTION || token->type == OP_OPEN_DUP_NUM) { tree = parse_dup_op (tree, regexp, dfa, token, syntax, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; /* In BRE consecutive duplications are not allowed. */ if ((syntax & RE_CONTEXT_INVALID_DUP) && (token->type == OP_DUP_ASTERISK || token->type == OP_OPEN_DUP_NUM)) { *err = REG_BADRPT; return NULL; } } return tree; } /* This function build the following tree, from regular expression (): SUBEXP | */ static bin_tree_t * parse_sub_exp (re_string_t *regexp, regex_t *preg, re_token_t *token, reg_syntax_t syntax, int nest, reg_errcode_t *err) { re_dfa_t *dfa = (re_dfa_t *) preg->buffer; bin_tree_t *tree; size_t cur_nsub; cur_nsub = preg->re_nsub++; fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE); /* The subexpression may be a null string. */ if (token->type == OP_CLOSE_SUBEXP) tree = NULL; else { tree = parse_reg_exp (regexp, preg, token, syntax, nest, err); if (BE (*err == REG_NOERROR && token->type != OP_CLOSE_SUBEXP, 0)) *err = REG_EPAREN; if (BE (*err != REG_NOERROR, 0)) return NULL; } if (cur_nsub <= '9' - '1') dfa->completed_bkref_map |= 1 << cur_nsub; tree = create_tree (dfa, tree, NULL, SUBEXP); if (BE (tree == NULL, 0)) { *err = REG_ESPACE; return NULL; } tree->token.opr.idx = cur_nsub; return tree; } /* This function parse repetition operators like "*", "+", "{1,3}" etc. */ static bin_tree_t * parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err) { bin_tree_t *tree = NULL, *old_tree = NULL; int i, start, end, start_idx = re_string_cur_idx (regexp); re_token_t start_token = *token; if (token->type == OP_OPEN_DUP_NUM) { end = 0; start = fetch_number (regexp, token, syntax); if (start == -1) { if (token->type == CHARACTER && token->opr.c == ',') start = 0; /* We treat "{,m}" as "{0,m}". */ else { *err = REG_BADBR; /* {} is invalid. */ return NULL; } } if (BE (start != -2, 1)) { /* We treat "{n}" as "{n,n}". */ end = ((token->type == OP_CLOSE_DUP_NUM) ? start : ((token->type == CHARACTER && token->opr.c == ',') ? fetch_number (regexp, token, syntax) : -2)); } if (BE (start == -2 || end == -2, 0)) { /* Invalid sequence. */ if (BE (!(syntax & RE_INVALID_INTERVAL_ORD), 0)) { if (token->type == END_OF_RE) *err = REG_EBRACE; else *err = REG_BADBR; return NULL; } /* If the syntax bit is set, rollback. */ re_string_set_index (regexp, start_idx); *token = start_token; token->type = CHARACTER; /* mb_partial and word_char bits should be already initialized by peek_token. */ return elem; } if (BE (end != -1 && start > end, 0)) { /* First number greater than second. */ *err = REG_BADBR; return NULL; } } else { start = (token->type == OP_DUP_PLUS) ? 1 : 0; end = (token->type == OP_DUP_QUESTION) ? 1 : -1; } fetch_token (token, regexp, syntax); if (BE (elem == NULL, 0)) return NULL; if (BE (start == 0 && end == 0, 0)) { postorder (elem, free_tree, NULL); return NULL; } /* Extract "{n,m}" to "...{0,}". */ if (BE (start > 0, 0)) { tree = elem; for (i = 2; i <= start; ++i) { elem = duplicate_tree (elem, dfa); tree = create_tree (dfa, tree, elem, CONCAT); if (BE (elem == NULL || tree == NULL, 0)) goto parse_dup_op_espace; } if (start == end) return tree; /* Duplicate ELEM before it is marked optional. */ elem = duplicate_tree (elem, dfa); old_tree = tree; } else old_tree = NULL; if (elem->token.type == SUBEXP) postorder (elem, mark_opt_subexp, (void *) (long) elem->token.opr.idx); tree = create_tree (dfa, elem, NULL, (end == -1 ? OP_DUP_ASTERISK : OP_ALT)); if (BE (tree == NULL, 0)) goto parse_dup_op_espace; /* This loop is actually executed only when end != -1, to rewrite {0,n} as ((...?)?)?... We have already created the start+1-th copy. */ for (i = start + 2; i <= end; ++i) { elem = duplicate_tree (elem, dfa); tree = create_tree (dfa, tree, elem, CONCAT); if (BE (elem == NULL || tree == NULL, 0)) goto parse_dup_op_espace; tree = create_tree (dfa, tree, NULL, OP_ALT); if (BE (tree == NULL, 0)) goto parse_dup_op_espace; } if (old_tree) tree = create_tree (dfa, old_tree, tree, CONCAT); return tree; parse_dup_op_espace: *err = REG_ESPACE; return NULL; } /* Size of the names for collating symbol/equivalence_class/character_class. I'm not sure, but maybe enough. */ #define BRACKET_NAME_BUF_SIZE 32 #ifndef _LIBC /* Local function for parse_bracket_exp only used in case of NOT _LIBC. Build the range expression which starts from START_ELEM, and ends at END_ELEM. The result are written to MBCSET and SBCSET. RANGE_ALLOC is the allocated size of mbcset->range_starts, and mbcset->range_ends, is a pointer argument sinse we may update it. */ static reg_errcode_t internal_function # ifdef RE_ENABLE_I18N build_range_exp (bitset_t sbcset, re_charset_t *mbcset, int *range_alloc, bracket_elem_t *start_elem, bracket_elem_t *end_elem) # else /* not RE_ENABLE_I18N */ build_range_exp (bitset_t sbcset, bracket_elem_t *start_elem, bracket_elem_t *end_elem) # endif /* not RE_ENABLE_I18N */ { unsigned int start_ch, end_ch; /* Equivalence Classes and Character Classes can't be a range start/end. */ if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS, 0)) return REG_ERANGE; /* We can handle no multi character collating elements without libc support. */ if (BE ((start_elem->type == COLL_SYM && strlen ((char *) start_elem->opr.name) > 1) || (end_elem->type == COLL_SYM && strlen ((char *) end_elem->opr.name) > 1), 0)) return REG_ECOLLATE; # ifdef RE_ENABLE_I18N { wchar_t wc; wint_t start_wc; wint_t end_wc; wchar_t cmp_buf[6] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'}; start_ch = ((start_elem->type == SB_CHAR) ? start_elem->opr.ch : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0] : 0)); end_ch = ((end_elem->type == SB_CHAR) ? end_elem->opr.ch : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0] : 0)); start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM) ? __btowc (start_ch) : start_elem->opr.wch); end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM) ? __btowc (end_ch) : end_elem->opr.wch); if (start_wc == WEOF || end_wc == WEOF) return REG_ECOLLATE; cmp_buf[0] = start_wc; cmp_buf[4] = end_wc; if (wcscoll (cmp_buf, cmp_buf + 4) > 0) return REG_ERANGE; /* Got valid collation sequence values, add them as a new entry. However, for !_LIBC we have no collation elements: if the character set is single byte, the single byte character set that we build below suffices. parse_bracket_exp passes no MBCSET if dfa->mb_cur_max == 1. */ if (mbcset) { /* Check the space of the arrays. */ if (BE (*range_alloc == mbcset->nranges, 0)) { /* There is not enough space, need realloc. */ wchar_t *new_array_start, *new_array_end; int new_nranges; /* +1 in case of mbcset->nranges is 0. */ new_nranges = 2 * mbcset->nranges + 1; /* Use realloc since mbcset->range_starts and mbcset->range_ends are NULL if *range_alloc == 0. */ new_array_start = re_realloc (mbcset->range_starts, wchar_t, new_nranges); new_array_end = re_realloc (mbcset->range_ends, wchar_t, new_nranges); if (BE (new_array_start == NULL || new_array_end == NULL, 0)) return REG_ESPACE; mbcset->range_starts = new_array_start; mbcset->range_ends = new_array_end; *range_alloc = new_nranges; } mbcset->range_starts[mbcset->nranges] = start_wc; mbcset->range_ends[mbcset->nranges++] = end_wc; } /* Build the table for single byte characters. */ for (wc = 0; wc < SBC_MAX; ++wc) { cmp_buf[2] = wc; if (wcscoll (cmp_buf, cmp_buf + 2) <= 0 && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0) bitset_set (sbcset, wc); } } # else /* not RE_ENABLE_I18N */ { unsigned int ch; start_ch = ((start_elem->type == SB_CHAR ) ? start_elem->opr.ch : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0] : 0)); end_ch = ((end_elem->type == SB_CHAR ) ? end_elem->opr.ch : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0] : 0)); if (start_ch > end_ch) return REG_ERANGE; /* Build the table for single byte characters. */ for (ch = 0; ch < SBC_MAX; ++ch) if (start_ch <= ch && ch <= end_ch) bitset_set (sbcset, ch); } # endif /* not RE_ENABLE_I18N */ return REG_NOERROR; } #endif /* not _LIBC */ #ifndef _LIBC /* Helper function for parse_bracket_exp only used in case of NOT _LIBC.. Build the collating element which is represented by NAME. The result are written to MBCSET and SBCSET. COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a pointer argument since we may update it. */ static reg_errcode_t internal_function # ifdef RE_ENABLE_I18N build_collating_symbol (bitset_t sbcset, re_charset_t *mbcset, int *coll_sym_alloc, const unsigned char *name) # else /* not RE_ENABLE_I18N */ build_collating_symbol (bitset_t sbcset, const unsigned char *name) # endif /* not RE_ENABLE_I18N */ { size_t name_len = strlen ((const char *) name); if (BE (name_len != 1, 0)) return REG_ECOLLATE; else { bitset_set (sbcset, name[0]); return REG_NOERROR; } } #endif /* not _LIBC */ /* This function parse bracket expression like "[abc]", "[a-c]", "[[.a-a.]]" etc. */ static bin_tree_t * parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err) { #ifdef _LIBC const unsigned char *collseqmb; const char *collseqwc; uint32_t nrules; int32_t table_size; const int32_t *symb_table; const unsigned char *extra; /* Local function for parse_bracket_exp used in _LIBC environement. Seek the collating symbol entry correspondings to NAME. Return the index of the symbol in the SYMB_TABLE. */ auto inline int32_t __attribute ((always_inline)) seek_collating_symbol_entry (name, name_len) const unsigned char *name; size_t name_len; { int32_t hash = elem_hash ((const char *) name, name_len); int32_t elem = hash % table_size; if (symb_table[2 * elem] != 0) { int32_t second = hash % (table_size - 2) + 1; do { /* First compare the hashing value. */ if (symb_table[2 * elem] == hash /* Compare the length of the name. */ && name_len == extra[symb_table[2 * elem + 1]] /* Compare the name. */ && memcmp (name, &extra[symb_table[2 * elem + 1] + 1], name_len) == 0) { /* Yep, this is the entry. */ break; } /* Next entry. */ elem += second; } while (symb_table[2 * elem] != 0); } return elem; } /* Local function for parse_bracket_exp used in _LIBC environment. Look up the collation sequence value of BR_ELEM. Return the value if succeeded, UINT_MAX otherwise. */ auto inline unsigned int __attribute ((always_inline)) lookup_collation_sequence_value (br_elem) bracket_elem_t *br_elem; { if (br_elem->type == SB_CHAR) { /* if (MB_CUR_MAX == 1) */ if (nrules == 0) return collseqmb[br_elem->opr.ch]; else { wint_t wc = __btowc (br_elem->opr.ch); return __collseq_table_lookup (collseqwc, wc); } } else if (br_elem->type == MB_CHAR) { if (nrules != 0) return __collseq_table_lookup (collseqwc, br_elem->opr.wch); } else if (br_elem->type == COLL_SYM) { size_t sym_name_len = strlen ((char *) br_elem->opr.name); if (nrules != 0) { int32_t elem, idx; elem = seek_collating_symbol_entry (br_elem->opr.name, sym_name_len); if (symb_table[2 * elem] != 0) { /* We found the entry. */ idx = symb_table[2 * elem + 1]; /* Skip the name of collating element name. */ idx += 1 + extra[idx]; /* Skip the byte sequence of the collating element. */ idx += 1 + extra[idx]; /* Adjust for the alignment. */ idx = (idx + 3) & ~3; /* Skip the multibyte collation sequence value. */ idx += sizeof (unsigned int); /* Skip the wide char sequence of the collating element. */ idx += sizeof (unsigned int) * (1 + *(unsigned int *) (extra + idx)); /* Return the collation sequence value. */ return *(unsigned int *) (extra + idx); } else if (symb_table[2 * elem] == 0 && sym_name_len == 1) { /* No valid character. Match it as a single byte character. */ return collseqmb[br_elem->opr.name[0]]; } } else if (sym_name_len == 1) return collseqmb[br_elem->opr.name[0]]; } return UINT_MAX; } /* Local function for parse_bracket_exp used in _LIBC environement. Build the range expression which starts from START_ELEM, and ends at END_ELEM. The result are written to MBCSET and SBCSET. RANGE_ALLOC is the allocated size of mbcset->range_starts, and mbcset->range_ends, is a pointer argument sinse we may update it. */ auto inline reg_errcode_t __attribute ((always_inline)) build_range_exp (sbcset, mbcset, range_alloc, start_elem, end_elem) re_charset_t *mbcset; int *range_alloc; bitset_t sbcset; bracket_elem_t *start_elem, *end_elem; { unsigned int ch; uint32_t start_collseq; uint32_t end_collseq; /* Equivalence Classes and Character Classes can't be a range start/end. */ if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS, 0)) return REG_ERANGE; start_collseq = lookup_collation_sequence_value (start_elem); end_collseq = lookup_collation_sequence_value (end_elem); /* Check start/end collation sequence values. */ if (BE (start_collseq == UINT_MAX || end_collseq == UINT_MAX, 0)) return REG_ECOLLATE; if (BE ((syntax & RE_NO_EMPTY_RANGES) && start_collseq > end_collseq, 0)) return REG_ERANGE; /* Got valid collation sequence values, add them as a new entry. However, if we have no collation elements, and the character set is single byte, the single byte character set that we build below suffices. */ if (nrules > 0 || dfa->mb_cur_max > 1) { /* Check the space of the arrays. */ if (BE (*range_alloc == mbcset->nranges, 0)) { /* There is not enough space, need realloc. */ uint32_t *new_array_start; uint32_t *new_array_end; int new_nranges; /* +1 in case of mbcset->nranges is 0. */ new_nranges = 2 * mbcset->nranges + 1; new_array_start = re_realloc (mbcset->range_starts, uint32_t, new_nranges); new_array_end = re_realloc (mbcset->range_ends, uint32_t, new_nranges); if (BE (new_array_start == NULL || new_array_end == NULL, 0)) return REG_ESPACE; mbcset->range_starts = new_array_start; mbcset->range_ends = new_array_end; *range_alloc = new_nranges; } mbcset->range_starts[mbcset->nranges] = start_collseq; mbcset->range_ends[mbcset->nranges++] = end_collseq; } /* Build the table for single byte characters. */ for (ch = 0; ch < SBC_MAX; ch++) { uint32_t ch_collseq; /* if (MB_CUR_MAX == 1) */ if (nrules == 0) ch_collseq = collseqmb[ch]; else ch_collseq = __collseq_table_lookup (collseqwc, __btowc (ch)); if (start_collseq <= ch_collseq && ch_collseq <= end_collseq) bitset_set (sbcset, ch); } return REG_NOERROR; } /* Local function for parse_bracket_exp used in _LIBC environement. Build the collating element which is represented by NAME. The result are written to MBCSET and SBCSET. COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a pointer argument sinse we may update it. */ auto inline reg_errcode_t __attribute ((always_inline)) build_collating_symbol (sbcset, mbcset, coll_sym_alloc, name) re_charset_t *mbcset; int *coll_sym_alloc; bitset_t sbcset; const unsigned char *name; { int32_t elem, idx; size_t name_len = strlen ((const char *) name); if (nrules != 0) { elem = seek_collating_symbol_entry (name, name_len); if (symb_table[2 * elem] != 0) { /* We found the entry. */ idx = symb_table[2 * elem + 1]; /* Skip the name of collating element name. */ idx += 1 + extra[idx]; } else if (symb_table[2 * elem] == 0 && name_len == 1) { /* No valid character, treat it as a normal character. */ bitset_set (sbcset, name[0]); return REG_NOERROR; } else return REG_ECOLLATE; /* Got valid collation sequence, add it as a new entry. */ /* Check the space of the arrays. */ if (BE (*coll_sym_alloc == mbcset->ncoll_syms, 0)) { /* Not enough, realloc it. */ /* +1 in case of mbcset->ncoll_syms is 0. */ int new_coll_sym_alloc = 2 * mbcset->ncoll_syms + 1; /* Use realloc since mbcset->coll_syms is NULL if *alloc == 0. */ int32_t *new_coll_syms = re_realloc (mbcset->coll_syms, int32_t, new_coll_sym_alloc); if (BE (new_coll_syms == NULL, 0)) return REG_ESPACE; mbcset->coll_syms = new_coll_syms; *coll_sym_alloc = new_coll_sym_alloc; } mbcset->coll_syms[mbcset->ncoll_syms++] = idx; return REG_NOERROR; } else { if (BE (name_len != 1, 0)) return REG_ECOLLATE; else { bitset_set (sbcset, name[0]); return REG_NOERROR; } } } #endif re_token_t br_token; re_bitset_ptr_t sbcset; #ifdef RE_ENABLE_I18N re_charset_t *mbcset; int coll_sym_alloc = 0, range_alloc = 0, mbchar_alloc = 0; int equiv_class_alloc = 0, char_class_alloc = 0; #endif /* not RE_ENABLE_I18N */ int non_match = 0; bin_tree_t *work_tree; int token_len; int first_round = 1; #ifdef _LIBC collseqmb = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB); nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules) { /* if (MB_CUR_MAX > 1) */ collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC); table_size = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_SYMB_HASH_SIZEMB); symb_table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_TABLEMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); } #endif sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); #ifdef RE_ENABLE_I18N mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); #endif /* RE_ENABLE_I18N */ #ifdef RE_ENABLE_I18N if (BE (sbcset == NULL || mbcset == NULL, 0)) #else if (BE (sbcset == NULL, 0)) #endif /* RE_ENABLE_I18N */ { *err = REG_ESPACE; return NULL; } token_len = peek_token_bracket (token, regexp, syntax); if (BE (token->type == END_OF_RE, 0)) { *err = REG_BADPAT; goto parse_bracket_exp_free_return; } if (token->type == OP_NON_MATCH_LIST) { #ifdef RE_ENABLE_I18N mbcset->non_match = 1; #endif /* not RE_ENABLE_I18N */ non_match = 1; if (syntax & RE_HAT_LISTS_NOT_NEWLINE) bitset_set (sbcset, '\n'); re_string_skip_bytes (regexp, token_len); /* Skip a token. */ token_len = peek_token_bracket (token, regexp, syntax); if (BE (token->type == END_OF_RE, 0)) { *err = REG_BADPAT; goto parse_bracket_exp_free_return; } } /* We treat the first ']' as a normal character. */ if (token->type == OP_CLOSE_BRACKET) token->type = CHARACTER; while (1) { // Got warnings about being used uninitialized. // bracket_elem_t start_elem, end_elem; bracket_elem_t start_elem = {.type=0, .opr.name=NULL}, end_elem = {.type=0, .opr.name=NULL}; unsigned char start_name_buf[BRACKET_NAME_BUF_SIZE]; unsigned char end_name_buf[BRACKET_NAME_BUF_SIZE]; reg_errcode_t ret; int token_len2 = 0, is_range_exp = 0; re_token_t token2; start_elem.opr.name = start_name_buf; ret = parse_bracket_element (&start_elem, regexp, token, token_len, dfa, syntax, first_round); if (BE (ret != REG_NOERROR, 0)) { *err = ret; goto parse_bracket_exp_free_return; } first_round = 0; /* Get information about the next token. We need it in any case. */ token_len = peek_token_bracket (token, regexp, syntax); /* Do not check for ranges if we know they are not allowed. */ if (start_elem.type != CHAR_CLASS && start_elem.type != EQUIV_CLASS) { if (BE (token->type == END_OF_RE, 0)) { *err = REG_EBRACK; goto parse_bracket_exp_free_return; } if (token->type == OP_CHARSET_RANGE) { re_string_skip_bytes (regexp, token_len); /* Skip '-'. */ token_len2 = peek_token_bracket (&token2, regexp, syntax); if (BE (token2.type == END_OF_RE, 0)) { *err = REG_EBRACK; goto parse_bracket_exp_free_return; } if (token2.type == OP_CLOSE_BRACKET) { /* We treat the last '-' as a normal character. */ re_string_skip_bytes (regexp, -token_len); token->type = CHARACTER; } else is_range_exp = 1; } } if (is_range_exp == 1) { end_elem.opr.name = end_name_buf; ret = parse_bracket_element (&end_elem, regexp, &token2, token_len2, dfa, syntax, 1); if (BE (ret != REG_NOERROR, 0)) { *err = ret; goto parse_bracket_exp_free_return; } token_len = peek_token_bracket (token, regexp, syntax); #ifdef _LIBC *err = build_range_exp (sbcset, mbcset, &range_alloc, &start_elem, &end_elem); #else # ifdef RE_ENABLE_I18N *err = build_range_exp (sbcset, dfa->mb_cur_max > 1 ? mbcset : NULL, &range_alloc, &start_elem, &end_elem); # else *err = build_range_exp (sbcset, &start_elem, &end_elem); # endif #endif /* RE_ENABLE_I18N */ if (BE (*err != REG_NOERROR, 0)) goto parse_bracket_exp_free_return; } else { switch (start_elem.type) { case SB_CHAR: bitset_set (sbcset, start_elem.opr.ch); break; #ifdef RE_ENABLE_I18N case MB_CHAR: /* Check whether the array has enough space. */ if (BE (mbchar_alloc == mbcset->nmbchars, 0)) { wchar_t *new_mbchars; /* Not enough, realloc it. */ /* +1 in case of mbcset->nmbchars is 0. */ mbchar_alloc = 2 * mbcset->nmbchars + 1; /* Use realloc since array is NULL if *alloc == 0. */ new_mbchars = re_realloc (mbcset->mbchars, wchar_t, mbchar_alloc); if (BE (new_mbchars == NULL, 0)) goto parse_bracket_exp_espace; mbcset->mbchars = new_mbchars; } mbcset->mbchars[mbcset->nmbchars++] = start_elem.opr.wch; break; #endif /* RE_ENABLE_I18N */ case EQUIV_CLASS: *err = build_equiv_class (sbcset, #ifdef RE_ENABLE_I18N mbcset, &equiv_class_alloc, #endif /* RE_ENABLE_I18N */ start_elem.opr.name); if (BE (*err != REG_NOERROR, 0)) goto parse_bracket_exp_free_return; break; case COLL_SYM: *err = build_collating_symbol (sbcset, #ifdef RE_ENABLE_I18N mbcset, &coll_sym_alloc, #endif /* RE_ENABLE_I18N */ start_elem.opr.name); if (BE (*err != REG_NOERROR, 0)) goto parse_bracket_exp_free_return; break; case CHAR_CLASS: *err = build_charclass (regexp->trans, sbcset, #ifdef RE_ENABLE_I18N mbcset, &char_class_alloc, #endif /* RE_ENABLE_I18N */ start_elem.opr.name, syntax); if (BE (*err != REG_NOERROR, 0)) goto parse_bracket_exp_free_return; break; default: assert (0); break; } } if (BE (token->type == END_OF_RE, 0)) { *err = REG_EBRACK; goto parse_bracket_exp_free_return; } if (token->type == OP_CLOSE_BRACKET) break; } re_string_skip_bytes (regexp, token_len); /* Skip a token. */ /* If it is non-matching list. */ if (non_match) bitset_not (sbcset); #ifdef RE_ENABLE_I18N /* Ensure only single byte characters are set. */ if (dfa->mb_cur_max > 1) bitset_mask (sbcset, dfa->sb_char); if (mbcset->nmbchars || mbcset->ncoll_syms || mbcset->nequiv_classes || mbcset->nranges || (dfa->mb_cur_max > 1 && (mbcset->nchar_classes || mbcset->non_match))) { bin_tree_t *mbc_tree; int sbc_idx; /* Build a tree for complex bracket. */ dfa->has_mb_node = 1; br_token.type = COMPLEX_BRACKET; br_token.opr.mbcset = mbcset; mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token); if (BE (mbc_tree == NULL, 0)) goto parse_bracket_exp_espace; for (sbc_idx = 0; sbc_idx < BITSET_WORDS; ++sbc_idx) if (sbcset[sbc_idx]) break; /* If there are no bits set in sbcset, there is no point of having both SIMPLE_BRACKET and COMPLEX_BRACKET. */ if (sbc_idx < BITSET_WORDS) { /* Build a tree for simple bracket. */ br_token.type = SIMPLE_BRACKET; br_token.opr.sbcset = sbcset; work_tree = create_token_tree (dfa, NULL, NULL, &br_token); if (BE (work_tree == NULL, 0)) goto parse_bracket_exp_espace; /* Then join them by ALT node. */ work_tree = create_tree (dfa, work_tree, mbc_tree, OP_ALT); if (BE (work_tree == NULL, 0)) goto parse_bracket_exp_espace; } else { re_free (sbcset); work_tree = mbc_tree; } } else #endif /* not RE_ENABLE_I18N */ { #ifdef RE_ENABLE_I18N free_charset (mbcset); #endif /* Build a tree for simple bracket. */ br_token.type = SIMPLE_BRACKET; br_token.opr.sbcset = sbcset; work_tree = create_token_tree (dfa, NULL, NULL, &br_token); if (BE (work_tree == NULL, 0)) goto parse_bracket_exp_espace; } return work_tree; parse_bracket_exp_espace: *err = REG_ESPACE; parse_bracket_exp_free_return: re_free (sbcset); #ifdef RE_ENABLE_I18N free_charset (mbcset); #endif /* RE_ENABLE_I18N */ return NULL; } /* Parse an element in the bracket expression. */ static reg_errcode_t parse_bracket_element (bracket_elem_t *elem, re_string_t *regexp, re_token_t *token, int token_len, re_dfa_t *dfa, reg_syntax_t syntax, int accept_hyphen) { #ifdef RE_ENABLE_I18N int cur_char_size; cur_char_size = re_string_char_size_at (regexp, re_string_cur_idx (regexp)); if (cur_char_size > 1) { elem->type = MB_CHAR; elem->opr.wch = re_string_wchar_at (regexp, re_string_cur_idx (regexp)); re_string_skip_bytes (regexp, cur_char_size); return REG_NOERROR; } #endif /* RE_ENABLE_I18N */ re_string_skip_bytes (regexp, token_len); /* Skip a token. */ if (token->type == OP_OPEN_COLL_ELEM || token->type == OP_OPEN_CHAR_CLASS || token->type == OP_OPEN_EQUIV_CLASS) return parse_bracket_symbol (elem, regexp, token); if (BE (token->type == OP_CHARSET_RANGE, 0) && !accept_hyphen) { /* A '-' must only appear as anything but a range indicator before the closing bracket. Everything else is an error. */ re_token_t token2; (void) peek_token_bracket (&token2, regexp, syntax); if (token2.type != OP_CLOSE_BRACKET) /* The actual error value is not standardized since this whole case is undefined. But ERANGE makes good sense. */ return REG_ERANGE; } elem->type = SB_CHAR; elem->opr.ch = token->opr.c; return REG_NOERROR; } /* Parse a bracket symbol in the bracket expression. Bracket symbols are such as [::], [..], and [==]. */ static reg_errcode_t parse_bracket_symbol (bracket_elem_t *elem, re_string_t *regexp, re_token_t *token) { unsigned char ch, delim = token->opr.c; int i = 0; if (re_string_eoi(regexp)) return REG_EBRACK; for (;; ++i) { if (i >= BRACKET_NAME_BUF_SIZE) return REG_EBRACK; if (token->type == OP_OPEN_CHAR_CLASS) ch = re_string_fetch_byte_case (regexp); else ch = re_string_fetch_byte (regexp); if (re_string_eoi(regexp)) return REG_EBRACK; if (ch == delim && re_string_peek_byte (regexp, 0) == ']') break; elem->opr.name[i] = ch; } re_string_skip_bytes (regexp, 1); elem->opr.name[i] = '\0'; switch (token->type) { case OP_OPEN_COLL_ELEM: elem->type = COLL_SYM; break; case OP_OPEN_EQUIV_CLASS: elem->type = EQUIV_CLASS; break; case OP_OPEN_CHAR_CLASS: elem->type = CHAR_CLASS; break; default: break; } return REG_NOERROR; } /* Helper function for parse_bracket_exp. Build the equivalence class which is represented by NAME. The result are written to MBCSET and SBCSET. EQUIV_CLASS_ALLOC is the allocated size of mbcset->equiv_classes, is a pointer argument sinse we may update it. */ static reg_errcode_t #ifdef RE_ENABLE_I18N build_equiv_class (bitset_t sbcset, re_charset_t *mbcset, int *equiv_class_alloc, const unsigned char *name) #else /* not RE_ENABLE_I18N */ build_equiv_class (bitset_t sbcset, const unsigned char *name) #endif /* not RE_ENABLE_I18N */ { #ifdef _LIBC uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules != 0) { const int32_t *table, *indirect; const unsigned char *weights, *extra, *cp; unsigned char char_buf[2]; int32_t idx1, idx2; unsigned int ch; size_t len; /* This #include defines a local function! */ # include /* Calculate the index for equivalence class. */ cp = name; table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); weights = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB); idx1 = findidx (&cp); if (BE (idx1 == 0 || cp < name + strlen ((const char *) name), 0)) /* This isn't a valid character. */ return REG_ECOLLATE; /* Build single byte matcing table for this equivalence class. */ char_buf[1] = (unsigned char) '\0'; len = weights[idx1 & 0xffffff]; for (ch = 0; ch < SBC_MAX; ++ch) { char_buf[0] = ch; cp = char_buf; idx2 = findidx (&cp); /* idx2 = table[ch]; */ if (idx2 == 0) /* This isn't a valid character. */ continue; /* Compare only if the length matches and the collation rule index is the same. */ if (len == weights[idx2 & 0xffffff] && (idx1 >> 24) == (idx2 >> 24)) { int cnt = 0; while (cnt <= len && weights[(idx1 & 0xffffff) + 1 + cnt] == weights[(idx2 & 0xffffff) + 1 + cnt]) ++cnt; if (cnt > len) bitset_set (sbcset, ch); } } /* Check whether the array has enough space. */ if (BE (*equiv_class_alloc == mbcset->nequiv_classes, 0)) { /* Not enough, realloc it. */ /* +1 in case of mbcset->nequiv_classes is 0. */ int new_equiv_class_alloc = 2 * mbcset->nequiv_classes + 1; /* Use realloc since the array is NULL if *alloc == 0. */ int32_t *new_equiv_classes = re_realloc (mbcset->equiv_classes, int32_t, new_equiv_class_alloc); if (BE (new_equiv_classes == NULL, 0)) return REG_ESPACE; mbcset->equiv_classes = new_equiv_classes; *equiv_class_alloc = new_equiv_class_alloc; } mbcset->equiv_classes[mbcset->nequiv_classes++] = idx1; } else #endif /* _LIBC */ { if (BE (strlen ((const char *) name) != 1, 0)) return REG_ECOLLATE; bitset_set (sbcset, *name); } return REG_NOERROR; } /* Helper function for parse_bracket_exp. Build the character class which is represented by NAME. The result are written to MBCSET and SBCSET. CHAR_CLASS_ALLOC is the allocated size of mbcset->char_classes, is a pointer argument sinse we may update it. */ static reg_errcode_t #ifdef RE_ENABLE_I18N build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, re_charset_t *mbcset, int *char_class_alloc, const unsigned char *class_name, reg_syntax_t syntax) #else /* not RE_ENABLE_I18N */ build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, const unsigned char *class_name, reg_syntax_t syntax) #endif /* not RE_ENABLE_I18N */ { int i; const char *name = (const char *) class_name; /* In case of REG_ICASE "upper" and "lower" match the both of upper and lower cases. */ if ((syntax & RE_ICASE) && (strcmp (name, "upper") == 0 || strcmp (name, "lower") == 0)) name = "alpha"; #ifdef RE_ENABLE_I18N /* Check the space of the arrays. */ if (BE (*char_class_alloc == mbcset->nchar_classes, 0)) { /* Not enough, realloc it. */ /* +1 in case of mbcset->nchar_classes is 0. */ int new_char_class_alloc = 2 * mbcset->nchar_classes + 1; /* Use realloc since array is NULL if *alloc == 0. */ wctype_t *new_char_classes = re_realloc (mbcset->char_classes, wctype_t, new_char_class_alloc); if (BE (new_char_classes == NULL, 0)) return REG_ESPACE; mbcset->char_classes = new_char_classes; *char_class_alloc = new_char_class_alloc; } mbcset->char_classes[mbcset->nchar_classes++] = __wctype (name); #endif /* RE_ENABLE_I18N */ #define BUILD_CHARCLASS_LOOP(ctype_func) \ do { \ if (BE (trans != NULL, 0)) \ { \ for (i = 0; i < SBC_MAX; ++i) \ if (ctype_func (i)) \ bitset_set (sbcset, trans[i]); \ } \ else \ { \ for (i = 0; i < SBC_MAX; ++i) \ if (ctype_func (i)) \ bitset_set (sbcset, i); \ } \ } while (0) if (strcmp (name, "alnum") == 0) BUILD_CHARCLASS_LOOP (isalnum); else if (strcmp (name, "cntrl") == 0) BUILD_CHARCLASS_LOOP (iscntrl); else if (strcmp (name, "lower") == 0) BUILD_CHARCLASS_LOOP (islower); else if (strcmp (name, "space") == 0) BUILD_CHARCLASS_LOOP (isspace); else if (strcmp (name, "alpha") == 0) BUILD_CHARCLASS_LOOP (isalpha); else if (strcmp (name, "digit") == 0) BUILD_CHARCLASS_LOOP (isdigit); else if (strcmp (name, "print") == 0) BUILD_CHARCLASS_LOOP (isprint); else if (strcmp (name, "upper") == 0) BUILD_CHARCLASS_LOOP (isupper); else if (strcmp (name, "blank") == 0) BUILD_CHARCLASS_LOOP (isblank); else if (strcmp (name, "graph") == 0) BUILD_CHARCLASS_LOOP (isgraph); else if (strcmp (name, "punct") == 0) BUILD_CHARCLASS_LOOP (ispunct); else if (strcmp (name, "xdigit") == 0) BUILD_CHARCLASS_LOOP (isxdigit); else return REG_ECTYPE; return REG_NOERROR; } static bin_tree_t * build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans, const unsigned char *class_name, const unsigned char *extra, int non_match, reg_errcode_t *err) { re_bitset_ptr_t sbcset; #ifdef RE_ENABLE_I18N re_charset_t *mbcset; int alloc = 0; #endif /* not RE_ENABLE_I18N */ reg_errcode_t ret; re_token_t br_token; bin_tree_t *tree; sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); #ifdef RE_ENABLE_I18N mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); #endif /* RE_ENABLE_I18N */ #ifdef RE_ENABLE_I18N if (BE (sbcset == NULL || mbcset == NULL, 0)) #else /* not RE_ENABLE_I18N */ if (BE (sbcset == NULL, 0)) #endif /* not RE_ENABLE_I18N */ { *err = REG_ESPACE; return NULL; } if (non_match) { #ifdef RE_ENABLE_I18N mbcset->non_match = 1; #endif /* not RE_ENABLE_I18N */ } /* We don't care the syntax in this case. */ ret = build_charclass (trans, sbcset, #ifdef RE_ENABLE_I18N mbcset, &alloc, #endif /* RE_ENABLE_I18N */ class_name, 0); if (BE (ret != REG_NOERROR, 0)) { re_free (sbcset); #ifdef RE_ENABLE_I18N free_charset (mbcset); #endif /* RE_ENABLE_I18N */ *err = ret; return NULL; } /* \w match '_' also. */ for (; *extra; extra++) bitset_set (sbcset, *extra); /* If it is non-matching list. */ if (non_match) bitset_not (sbcset); #ifdef RE_ENABLE_I18N /* Ensure only single byte characters are set. */ if (dfa->mb_cur_max > 1) bitset_mask (sbcset, dfa->sb_char); #endif /* Build a tree for simple bracket. */ br_token.type = SIMPLE_BRACKET; br_token.opr.sbcset = sbcset; tree = create_token_tree (dfa, NULL, NULL, &br_token); if (BE (tree == NULL, 0)) goto build_word_op_espace; #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) { bin_tree_t *mbc_tree; /* Build a tree for complex bracket. */ br_token.type = COMPLEX_BRACKET; br_token.opr.mbcset = mbcset; dfa->has_mb_node = 1; mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token); if (BE (mbc_tree == NULL, 0)) goto build_word_op_espace; /* Then join them by ALT node. */ tree = create_tree (dfa, tree, mbc_tree, OP_ALT); if (BE (mbc_tree != NULL, 1)) return tree; } else { free_charset (mbcset); return tree; } #else /* not RE_ENABLE_I18N */ return tree; #endif /* not RE_ENABLE_I18N */ build_word_op_espace: re_free (sbcset); #ifdef RE_ENABLE_I18N free_charset (mbcset); #endif /* RE_ENABLE_I18N */ *err = REG_ESPACE; return NULL; } /* This is intended for the expressions like "a{1,3}". Fetch a number from `input', and return the number. Return -1, if the number field is empty like "{,1}". Return -2, If an error is occured. */ static int fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax) { int num = -1; unsigned char c; while (1) { fetch_token (token, input, syntax); c = token->opr.c; if (BE (token->type == END_OF_RE, 0)) return -2; if (token->type == OP_CLOSE_DUP_NUM || c == ',') break; num = ((token->type != CHARACTER || c < '0' || '9' < c || num == -2) ? -2 : ((num == -1) ? c - '0' : num * 10 + c - '0')); num = (num > RE_DUP_MAX) ? -2 : num; } return num; } #ifdef RE_ENABLE_I18N static void free_charset (re_charset_t *cset) { re_free (cset->mbchars); # ifdef _LIBC re_free (cset->coll_syms); re_free (cset->equiv_classes); re_free (cset->range_starts); re_free (cset->range_ends); # endif re_free (cset->char_classes); re_free (cset); } #endif /* RE_ENABLE_I18N */ /* Functions for binary tree operation. */ /* Create a tree node. */ static bin_tree_t * create_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, re_token_type_t type) { re_token_t t; t.type = type; return create_token_tree (dfa, left, right, &t); } static bin_tree_t * create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, const re_token_t *token) { bin_tree_t *tree; if (BE (dfa->str_tree_storage_idx == BIN_TREE_STORAGE_SIZE, 0)) { bin_tree_storage_t *storage = re_malloc (bin_tree_storage_t, 1); if (storage == NULL) return NULL; storage->next = dfa->str_tree_storage; dfa->str_tree_storage = storage; dfa->str_tree_storage_idx = 0; } tree = &dfa->str_tree_storage->data[dfa->str_tree_storage_idx++]; tree->parent = NULL; tree->left = left; tree->right = right; tree->token = *token; tree->token.duplicated = 0; tree->token.opt_subexp = 0; tree->first = NULL; tree->next = NULL; tree->node_idx = -1; if (left != NULL) left->parent = tree; if (right != NULL) right->parent = tree; return tree; } /* Mark the tree SRC as an optional subexpression. To be called from preorder or postorder. */ static reg_errcode_t mark_opt_subexp (void *extra, bin_tree_t *node) { int idx = (int) (long) extra; if (node->token.type == SUBEXP && node->token.opr.idx == idx) node->token.opt_subexp = 1; return REG_NOERROR; } /* Free the allocated memory inside NODE. */ static void free_token (re_token_t *node) { #ifdef RE_ENABLE_I18N if (node->type == COMPLEX_BRACKET && node->duplicated == 0) free_charset (node->opr.mbcset); else #endif /* RE_ENABLE_I18N */ if (node->type == SIMPLE_BRACKET && node->duplicated == 0) re_free (node->opr.sbcset); } /* Worker function for tree walking. Free the allocated memory inside NODE and its children. */ static reg_errcode_t free_tree (void *extra, bin_tree_t *node) { free_token (&node->token); return REG_NOERROR; } /* Duplicate the node SRC, and return new node. This is a preorder visit similar to the one implemented by the generic visitor, but we need more infrastructure to maintain two parallel trees --- so, it's easier to duplicate. */ static bin_tree_t * duplicate_tree (const bin_tree_t *root, re_dfa_t *dfa) { const bin_tree_t *node; bin_tree_t *dup_root; bin_tree_t **p_new = &dup_root, *dup_node = root->parent; for (node = root; ; ) { /* Create a new tree and link it back to the current parent. */ *p_new = create_token_tree (dfa, NULL, NULL, &node->token); if (*p_new == NULL) return NULL; (*p_new)->parent = dup_node; (*p_new)->token.duplicated = 1; dup_node = *p_new; /* Go to the left node, or up and to the right. */ if (node->left) { node = node->left; p_new = &dup_node->left; } else { const bin_tree_t *prev = NULL; while (node->right == prev || node->right == NULL) { prev = node; node = node->parent; dup_node = dup_node->parent; if (!node) return dup_root; } node = node->right; p_new = &dup_node->right; } } } direwolf-1.5+dfsg/regex/regex.c000066400000000000000000000056131347750676600165330ustar00rootroot00000000000000/* Extended regular expression matching and search library. Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C 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. The GNU C 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 the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* Make sure noone compiles this code with a C++ compiler. */ #ifdef __cplusplus # error "This is C code, use a C compiler" #endif #ifdef _LIBC /* We have to keep the namespace clean. */ # define regfree(preg) __regfree (preg) # define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef) # define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags) # define regerror(errcode, preg, errbuf, errbuf_size) \ __regerror(errcode, preg, errbuf, errbuf_size) # define re_set_registers(bu, re, nu, st, en) \ __re_set_registers (bu, re, nu, st, en) # define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \ __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) # define re_match(bufp, string, size, pos, regs) \ __re_match (bufp, string, size, pos, regs) # define re_search(bufp, string, size, startpos, range, regs) \ __re_search (bufp, string, size, startpos, range, regs) # define re_compile_pattern(pattern, length, bufp) \ __re_compile_pattern (pattern, length, bufp) # define re_set_syntax(syntax) __re_set_syntax (syntax) # define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \ __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop) # define re_compile_fastmap(bufp) __re_compile_fastmap (bufp) # include "../locale/localeinfo.h" #endif /* On some systems, limits.h sets RE_DUP_MAX to a lower value than GNU regex allows. Include it before , which correctly #undefs RE_DUP_MAX and sets it to the right value. */ #include #include #include "regex_internal.h" #include "regex_internal.c" #include "regcomp.c" #include "regexec.c" /* Binary backward compatibility. */ #if _LIBC # include # if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3) link_warning (re_max_failures, "the 're_max_failures' variable is obsolete and will go away.") int re_max_failures = 2000; # endif #endif direwolf-1.5+dfsg/regex/regex.h000066400000000000000000000531301347750676600165350ustar00rootroot00000000000000/* Definitions for data structures and routines for the regular expression library. Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003,2005,2006 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C 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. The GNU C 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 the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifndef _REGEX_H #define _REGEX_H 1 #include #ifndef __GNUC__ # define __DLL_IMPORT__ __declspec(dllimport) # define __DLL_EXPORT__ __declspec(dllexport) #else # define __DLL_IMPORT__ __attribute__((dllimport)) extern # define __DLL_EXPORT__ __attribute__((dllexport)) extern #endif #if (defined __WIN32__) || (defined _WIN32) # ifdef BUILD_REGEX_DLL # define REGEX_DLL_IMPEXP __DLL_EXPORT__ # elif defined(REGEX_STATIC) # define REGEX_DLL_IMPEXP # elif defined (USE_REGEX_DLL) # define REGEX_DLL_IMPEXP __DLL_IMPORT__ # elif defined (USE_REGEX_STATIC) # define REGEX_DLL_IMPEXP # else /* assume USE_REGEX_DLL */ # define REGEX_DLL_IMPEXP __DLL_IMPORT__ # endif #else /* __WIN32__ */ # define REGEX_DLL_IMPEXP #endif /* Allow the use in C++ code. */ #ifdef __cplusplus extern "C" { #endif /* The following two types have to be signed and unsigned integer type wide enough to hold a value of a pointer. For most ANSI compilers ptrdiff_t and size_t should be likely OK. Still size of these two types is 2 for Microsoft C. Ugh... */ typedef long int s_reg_t; typedef unsigned long int active_reg_t; /* The following bits are used to determine the regexp syntax we recognize. The set/not-set meanings are chosen so that Emacs syntax remains the value 0. The bits are given in alphabetical order, and the definitions shifted by one from the previous bit; thus, when we add or remove a bit, only one other definition need change. */ typedef unsigned long int reg_syntax_t; /* If this bit is not set, then \ inside a bracket expression is literal. If set, then such a \ quotes the following character. */ #define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1) /* If this bit is not set, then + and ? are operators, and \+ and \? are literals. If set, then \+ and \? are operators and + and ? are literals. */ #define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) /* If this bit is set, then character classes are supported. They are: [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. If not set, then character classes are not supported. */ #define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) /* If this bit is set, then ^ and $ are always anchors (outside bracket expressions, of course). If this bit is not set, then it depends: ^ is an anchor if it is at the beginning of a regular expression or after an open-group or an alternation operator; $ is an anchor if it is at the end of a regular expression, or before a close-group or an alternation operator. This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because POSIX draft 11.2 says that * etc. in leading positions is undefined. We already implemented a previous draft which made those constructs invalid, though, so we haven't changed the code back. */ #define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) /* If this bit is set, then special characters are always special regardless of where they are in the pattern. If this bit is not set, then special characters are special only in some contexts; otherwise they are ordinary. Specifically, * + ? and intervals are only special when not after the beginning, open-group, or alternation operator. */ #define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) /* If this bit is set, then *, +, ?, and { cannot be first in an re or immediately after an alternation or begin-group operator. */ #define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) /* If this bit is set, then . matches newline. If not set, then it doesn't. */ #define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) /* If this bit is set, then . doesn't match NUL. If not set, then it does. */ #define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) /* If this bit is set, nonmatching lists [^...] do not match newline. If not set, they do. */ #define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) /* If this bit is set, either \{...\} or {...} defines an interval, depending on RE_NO_BK_BRACES. If not set, \{, \}, {, and } are literals. */ #define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) /* If this bit is set, +, ? and | aren't recognized as operators. If not set, they are. */ #define RE_LIMITED_OPS (RE_INTERVALS << 1) /* If this bit is set, newline is an alternation operator. If not set, newline is literal. */ #define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) /* If this bit is set, then `{...}' defines an interval, and \{ and \} are literals. If not set, then `\{...\}' defines an interval. */ #define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) /* If this bit is set, (...) defines a group, and \( and \) are literals. If not set, \(...\) defines a group, and ( and ) are literals. */ #define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) /* If this bit is set, then \ matches . If not set, then \ is a back-reference. */ #define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) /* If this bit is set, then | is an alternation operator, and \| is literal. If not set, then \| is an alternation operator, and | is literal. */ #define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) /* If this bit is set, then an ending range point collating higher than the starting range point, as in [z-a], is invalid. If not set, then when ending range point collates higher than the starting range point, the range is ignored. */ #define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) /* If this bit is set, then an unmatched ) is ordinary. If not set, then an unmatched ) is invalid. */ #define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) /* If this bit is set, succeed as soon as we match the whole pattern, without further backtracking. */ #define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1) /* If this bit is set, do not process the GNU regex operators. If not set, then the GNU regex operators are recognized. */ #define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1) /* If this bit is set, turn on internal regex debugging. If not set, and debugging was on, turn it off. This only works if regex.c is compiled -DDEBUG. We define this bit always, so that all that's needed to turn on debugging is to recompile regex.c; the calling code can always have this bit set, and it won't affect anything in the normal case. */ #define RE_DEBUG (RE_NO_GNU_OPS << 1) /* If this bit is set, a syntactically invalid interval is treated as a string of ordinary characters. For example, the ERE 'a{1' is treated as 'a\{1'. */ #define RE_INVALID_INTERVAL_ORD (RE_DEBUG << 1) /* If this bit is set, then ignore case when matching. If not set, then case is significant. */ #define RE_ICASE (RE_INVALID_INTERVAL_ORD << 1) /* This bit is used internally like RE_CONTEXT_INDEP_ANCHORS but only for ^, because it is difficult to scan the regex backwards to find whether ^ should be special. */ #define RE_CARET_ANCHORS_HERE (RE_ICASE << 1) /* If this bit is set, then \{ cannot be first in an bre or immediately after an alternation or begin-group operator. */ #define RE_CONTEXT_INVALID_DUP (RE_CARET_ANCHORS_HERE << 1) /* If this bit is set, then no_sub will be set to 1 during re_compile_pattern. */ #define RE_NO_SUB (RE_CONTEXT_INVALID_DUP << 1) /* This global variable defines the particular regexp syntax to use (for some interfaces). When a regexp is compiled, the syntax used is stored in the pattern buffer, so changing this does not affect already-compiled regexps. */ REGEX_DLL_IMPEXP reg_syntax_t re_syntax_options; /* Define combinations of the above bits for the standard possibilities. (The [[[ comments delimit what gets put into the Texinfo file, so don't delete them!) */ /* [[[begin syntaxes]]] */ #define RE_SYNTAX_EMACS 0 #define RE_SYNTAX_AWK \ (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \ | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) #define RE_SYNTAX_GNU_AWK \ ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DEBUG) \ & ~(RE_DOT_NOT_NULL | RE_INTERVALS | RE_CONTEXT_INDEP_OPS \ | RE_CONTEXT_INVALID_OPS )) #define RE_SYNTAX_POSIX_AWK \ (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ | RE_INTERVALS | RE_NO_GNU_OPS) #define RE_SYNTAX_GREP \ (RE_BK_PLUS_QM | RE_CHAR_CLASSES \ | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \ | RE_NEWLINE_ALT) #define RE_SYNTAX_EGREP \ (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \ | RE_NEWLINE_ALT | RE_NO_BK_PARENS \ | RE_NO_BK_VBAR) #define RE_SYNTAX_POSIX_EGREP \ (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES \ | RE_INVALID_INTERVAL_ORD) /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ #define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC #define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC /* Syntax bits common to both basic and extended POSIX regex syntax. */ #define _RE_SYNTAX_POSIX_COMMON \ (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ | RE_INTERVALS | RE_NO_EMPTY_RANGES) #define RE_SYNTAX_POSIX_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM | RE_CONTEXT_INVALID_DUP) /* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this isn't minimal, since other operators, such as \`, aren't disabled. */ #define RE_SYNTAX_POSIX_MINIMAL_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) #define RE_SYNTAX_POSIX_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD) /* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is removed and RE_NO_BK_REFS is added. */ #define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) /* [[[end syntaxes]]] */ /* Maximum number of duplicates an interval can allow. Some systems (erroneously) define this in other header files, but we want our value, so remove any previous define. */ #ifdef RE_DUP_MAX # undef RE_DUP_MAX #endif /* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */ #define RE_DUP_MAX (0x7fff) /* POSIX `cflags' bits (i.e., information for `regcomp'). */ /* If this bit is set, then use extended regular expression syntax. If not set, then use basic regular expression syntax. */ #define REG_EXTENDED 1 /* If this bit is set, then ignore case when matching. If not set, then case is significant. */ #define REG_ICASE (REG_EXTENDED << 1) /* If this bit is set, then anchors do not match at newline characters in the string. If not set, then anchors do match at newlines. */ #define REG_NEWLINE (REG_ICASE << 1) /* If this bit is set, then report only success or fail in regexec. If not set, then returns differ between not matching and errors. */ #define REG_NOSUB (REG_NEWLINE << 1) /* POSIX `eflags' bits (i.e., information for regexec). */ /* If this bit is set, then the beginning-of-line operator doesn't match the beginning of the string (presumably because it's not the beginning of a line). If not set, then the beginning-of-line operator does match the beginning of the string. */ #define REG_NOTBOL 1 /* Like REG_NOTBOL, except for the end-of-line. */ #define REG_NOTEOL (1 << 1) /* Use PMATCH[0] to delimit the start and end of the search in the buffer. */ #define REG_STARTEND (1 << 2) /* If any error codes are removed, changed, or added, update the `re_error_msg' table in regex.c. */ typedef enum { #ifdef _XOPEN_SOURCE REG_ENOSYS = -1, /* This will never happen for this implementation. */ #endif REG_NOERROR = 0, /* Success. */ REG_NOMATCH, /* Didn't find a match (for regexec). */ /* POSIX regcomp return error codes. (In the order listed in the standard.) */ REG_BADPAT, /* Invalid pattern. */ REG_ECOLLATE, /* Inalid collating element. */ REG_ECTYPE, /* Invalid character class name. */ REG_EESCAPE, /* Trailing backslash. */ REG_ESUBREG, /* Invalid back reference. */ REG_EBRACK, /* Unmatched left bracket. */ REG_EPAREN, /* Parenthesis imbalance. */ REG_EBRACE, /* Unmatched \{. */ REG_BADBR, /* Invalid contents of \{\}. */ REG_ERANGE, /* Invalid range end. */ REG_ESPACE, /* Ran out of memory. */ REG_BADRPT, /* No preceding re for repetition op. */ /* Error codes we've added. */ REG_EEND, /* Premature end. */ REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */ REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ } reg_errcode_t; /* This data structure represents a compiled pattern. Before calling the pattern compiler, the fields `buffer', `allocated', `fastmap', `translate', and `no_sub' can be set. After the pattern has been compiled, the `re_nsub' field is available. All other fields are private to the regex routines. */ #ifndef RE_TRANSLATE_TYPE # define RE_TRANSLATE_TYPE unsigned char * #endif struct re_pattern_buffer { /* Space that holds the compiled pattern. It is declared as `unsigned char *' because its elements are sometimes used as array indexes. */ unsigned char *buffer; /* Number of bytes to which `buffer' points. */ unsigned long int allocated; /* Number of bytes actually used in `buffer'. */ unsigned long int used; /* Syntax setting with which the pattern was compiled. */ reg_syntax_t syntax; /* Pointer to a fastmap, if any, otherwise zero. re_search uses the fastmap, if there is one, to skip over impossible starting points for matches. */ char *fastmap; /* Either a translate table to apply to all characters before comparing them, or zero for no translation. The translation is applied to a pattern when it is compiled and to a string when it is matched. */ RE_TRANSLATE_TYPE translate; /* Number of subexpressions found by the compiler. */ size_t re_nsub; /* Zero if this pattern cannot match the empty string, one else. Well, in truth it's used only in `re_search_2', to see whether or not we should use the fastmap, so we don't set this absolutely perfectly; see `re_compile_fastmap' (the `duplicate' case). */ unsigned can_be_null : 1; /* If REGS_UNALLOCATED, allocate space in the `regs' structure for `max (RE_NREGS, re_nsub + 1)' groups. If REGS_REALLOCATE, reallocate space if necessary. If REGS_FIXED, use what's there. */ #define REGS_UNALLOCATED 0 #define REGS_REALLOCATE 1 #define REGS_FIXED 2 unsigned regs_allocated : 2; /* Set to zero when `regex_compile' compiles a pattern; set to one by `re_compile_fastmap' if it updates the fastmap. */ unsigned fastmap_accurate : 1; /* If set, `re_match_2' does not return information about subexpressions. */ unsigned no_sub : 1; /* If set, a beginning-of-line anchor doesn't match at the beginning of the string. */ unsigned not_bol : 1; /* Similarly for an end-of-line anchor. */ unsigned not_eol : 1; /* If true, an anchor at a newline matches. */ unsigned newline_anchor : 1; }; typedef struct re_pattern_buffer regex_t; /* Type for byte offsets within the string. POSIX mandates this. */ typedef int regoff_t; /* This is the structure we store register match data in. See regex.texinfo for a full description of what registers match. */ struct re_registers { unsigned num_regs; regoff_t *start; regoff_t *end; }; /* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer, `re_match_2' returns information about at least this many registers the first time a `regs' structure is passed. */ #ifndef RE_NREGS # define RE_NREGS 30 #endif /* POSIX specification for registers. Aside from the different names than `re_registers', POSIX uses an array of structures, instead of a structure of arrays. */ typedef struct { regoff_t rm_so; /* Byte offset from string's start to substring's start. */ regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ } regmatch_t; /* Declarations for routines. */ /* Sets the current default syntax to SYNTAX, and return the old syntax. You can also simply assign to the `re_syntax_options' variable. */ REGEX_DLL_IMPEXP reg_syntax_t re_set_syntax (reg_syntax_t __syntax); /* Compile the regular expression PATTERN, with length LENGTH and syntax given by the global `re_syntax_options', into the buffer BUFFER. Return NULL if successful, and an error string if not. */ REGEX_DLL_IMPEXP const char *re_compile_pattern (const char *__pattern, size_t __length, struct re_pattern_buffer *__buffer); /* Compile a fastmap for the compiled pattern in BUFFER; used to accelerate searches. Return 0 if successful and -2 if was an internal error. */ REGEX_DLL_IMPEXP int re_compile_fastmap (struct re_pattern_buffer *__buffer); /* Search in the string STRING (with length LENGTH) for the pattern compiled into BUFFER. Start searching at position START, for RANGE characters. Return the starting position of the match, -1 for no match, or -2 for an internal error. Also return register information in REGS (if REGS and BUFFER->no_sub are nonzero). */ REGEX_DLL_IMPEXP int re_search (struct re_pattern_buffer *__buffer, const char *__string, int __length, int __start, int __range, struct re_registers *__regs); /* Like `re_search', but search in the concatenation of STRING1 and STRING2. Also, stop searching at index START + STOP. */ REGEX_DLL_IMPEXP int re_search_2 (struct re_pattern_buffer *__buffer, const char *__string1, int __length1, const char *__string2, int __length2, int __start, int __range, struct re_registers *__regs, int __stop); /* Like `re_search', but return how many characters in STRING the regexp in BUFFER matched, starting at position START. */ REGEX_DLL_IMPEXP int re_match (struct re_pattern_buffer *__buffer, const char *__string, int __length, int __start, struct re_registers *__regs); /* Relates to `re_match' as `re_search_2' relates to `re_search'. */ REGEX_DLL_IMPEXP int re_match_2 (struct re_pattern_buffer *__buffer, const char *__string1, int __length1, const char *__string2, int __length2, int __start, struct re_registers *__regs, int __stop); /* Set REGS to hold NUM_REGS registers, storing them in STARTS and ENDS. Subsequent matches using BUFFER and REGS will use this memory for recording register information. STARTS and ENDS must be allocated with malloc, and must each be at least `NUM_REGS * sizeof (regoff_t)' bytes long. If NUM_REGS == 0, then subsequent matches should allocate their own register data. Unless this function is called, the first search or match using PATTERN_BUFFER will allocate its own register data, without freeing the old data. */ REGEX_DLL_IMPEXP void re_set_registers (struct re_pattern_buffer *__buffer, struct re_registers *__regs, unsigned int __num_regs, regoff_t *__starts, regoff_t *__ends); #if defined _REGEX_RE_COMP || defined _LIBC # ifndef _CRAY /* 4.2 bsd compatibility. */ REGEX_DLL_IMPEXP char *re_comp (const char *); REGEX_DLL_IMPEXP int re_exec (const char *); # endif #endif /* GCC 2.95 and later have "__restrict"; C99 compilers have "restrict", and "configure" may have defined "restrict". */ #ifndef __restrict # if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__)) # if defined restrict || 199901L <= __STDC_VERSION__ # define __restrict restrict # else # define __restrict # endif # endif #endif /* gcc 3.1 and up support the [restrict] syntax. */ #ifndef __restrict_arr # if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) \ && !defined __GNUG__ # define __restrict_arr __restrict # else # define __restrict_arr # endif #endif /* POSIX compatibility. */ REGEX_DLL_IMPEXP int regcomp (regex_t *__restrict __preg, const char *__restrict __pattern, int __cflags); REGEX_DLL_IMPEXP int regexec (const regex_t *__restrict __preg, const char *__restrict __string, size_t __nmatch, regmatch_t __pmatch[__restrict_arr], int __eflags); REGEX_DLL_IMPEXP size_t regerror (int __errcode, const regex_t *__restrict __preg, char *__restrict __errbuf, size_t __errbuf_size); REGEX_DLL_IMPEXP void regfree (regex_t *__preg); #ifdef __cplusplus } #endif /* C++ */ #endif /* regex.h */ direwolf-1.5+dfsg/regex/regex_internal.c000066400000000000000000001342431347750676600204310ustar00rootroot00000000000000/* Extended regular expression matching and search library. Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C 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. The GNU C 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 the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ static void re_string_construct_common (const char *str, int len, re_string_t *pstr, RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa) internal_function; static re_dfastate_t *create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes, unsigned int hash) internal_function; static re_dfastate_t *create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes, unsigned int context, unsigned int hash) internal_function; /* Functions for string operation. */ /* This function allocate the buffers. It is necessary to call re_string_reconstruct before using the object. */ static reg_errcode_t internal_function re_string_allocate (re_string_t *pstr, const char *str, int len, int init_len, RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa) { reg_errcode_t ret; int init_buf_len; /* Ensure at least one character fits into the buffers. */ if (init_len < dfa->mb_cur_max) init_len = dfa->mb_cur_max; init_buf_len = (len + 1 < init_len) ? len + 1: init_len; re_string_construct_common (str, len, pstr, trans, icase, dfa); ret = re_string_realloc_buffers (pstr, init_buf_len); if (BE (ret != REG_NOERROR, 0)) return ret; pstr->word_char = dfa->word_char; pstr->word_ops_used = dfa->word_ops_used; pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str; pstr->valid_len = (pstr->mbs_allocated || dfa->mb_cur_max > 1) ? 0 : len; pstr->valid_raw_len = pstr->valid_len; return REG_NOERROR; } /* This function allocate the buffers, and initialize them. */ static reg_errcode_t internal_function re_string_construct (re_string_t *pstr, const char *str, int len, RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa) { reg_errcode_t ret; memset (pstr, '\0', sizeof (re_string_t)); re_string_construct_common (str, len, pstr, trans, icase, dfa); if (len > 0) { ret = re_string_realloc_buffers (pstr, len + 1); if (BE (ret != REG_NOERROR, 0)) return ret; } pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str; if (icase) { #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) { while (1) { ret = build_wcs_upper_buffer (pstr); if (BE (ret != REG_NOERROR, 0)) return ret; if (pstr->valid_raw_len >= len) break; if (pstr->bufs_len > pstr->valid_len + dfa->mb_cur_max) break; ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2); if (BE (ret != REG_NOERROR, 0)) return ret; } } else #endif /* RE_ENABLE_I18N */ build_upper_buffer (pstr); } else { #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) build_wcs_buffer (pstr); else #endif /* RE_ENABLE_I18N */ { if (trans != NULL) re_string_translate_buffer (pstr); else { pstr->valid_len = pstr->bufs_len; pstr->valid_raw_len = pstr->bufs_len; } } } return REG_NOERROR; } /* Helper functions for re_string_allocate, and re_string_construct. */ static reg_errcode_t internal_function re_string_realloc_buffers (re_string_t *pstr, int new_buf_len) { #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) { wint_t *new_wcs = re_realloc (pstr->wcs, wint_t, new_buf_len); if (BE (new_wcs == NULL, 0)) return REG_ESPACE; pstr->wcs = new_wcs; if (pstr->offsets != NULL) { int *new_offsets = re_realloc (pstr->offsets, int, new_buf_len); if (BE (new_offsets == NULL, 0)) return REG_ESPACE; pstr->offsets = new_offsets; } } #endif /* RE_ENABLE_I18N */ if (pstr->mbs_allocated) { unsigned char *new_mbs = re_realloc (pstr->mbs, unsigned char, new_buf_len); if (BE (new_mbs == NULL, 0)) return REG_ESPACE; pstr->mbs = new_mbs; } pstr->bufs_len = new_buf_len; return REG_NOERROR; } static void internal_function re_string_construct_common (const char *str, int len, re_string_t *pstr, RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa) { pstr->raw_mbs = (const unsigned char *) str; pstr->len = len; pstr->raw_len = len; pstr->trans = trans; pstr->icase = icase ? 1 : 0; pstr->mbs_allocated = (trans != NULL || icase); pstr->mb_cur_max = dfa->mb_cur_max; pstr->is_utf8 = dfa->is_utf8; pstr->map_notascii = dfa->map_notascii; pstr->stop = pstr->len; pstr->raw_stop = pstr->stop; } #ifdef RE_ENABLE_I18N /* Build wide character buffer PSTR->WCS. If the byte sequence of the string are: (0), (1), (0), (1), Then wide character buffer will be: , WEOF , , WEOF , We use WEOF for padding, they indicate that the position isn't a first byte of a multibyte character. Note that this function assumes PSTR->VALID_LEN elements are already built and starts from PSTR->VALID_LEN. */ static void internal_function build_wcs_buffer (re_string_t *pstr) { #ifdef _LIBC unsigned char buf[MB_LEN_MAX]; assert (MB_LEN_MAX >= pstr->mb_cur_max); #else unsigned char buf[64]; #endif mbstate_t prev_st; int byte_idx, end_idx, remain_len; size_t mbclen; /* Build the buffers from pstr->valid_len to either pstr->len or pstr->bufs_len. */ end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; for (byte_idx = pstr->valid_len; byte_idx < end_idx;) { wchar_t wc; const char *p; remain_len = end_idx - byte_idx; prev_st = pstr->cur_state; /* Apply the translation if we need. */ if (BE (pstr->trans != NULL, 0)) { int i, ch; for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i) { ch = pstr->raw_mbs [pstr->raw_mbs_idx + byte_idx + i]; buf[i] = pstr->mbs[byte_idx + i] = pstr->trans[ch]; } p = (const char *) buf; } else p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx; mbclen = mbrtowc (&wc, p, remain_len, &pstr->cur_state); if (BE (mbclen == (size_t) -2, 0)) { /* The buffer doesn't have enough space, finish to build. */ pstr->cur_state = prev_st; break; } else if (BE (mbclen == (size_t) -1 || mbclen == 0, 0)) { /* We treat these cases as a singlebyte character. */ mbclen = 1; wc = (wchar_t) pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]; if (BE (pstr->trans != NULL, 0)) wc = pstr->trans[wc]; pstr->cur_state = prev_st; } /* Write wide character and padding. */ pstr->wcs[byte_idx++] = wc; /* Write paddings. */ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) pstr->wcs[byte_idx++] = WEOF; } pstr->valid_len = byte_idx; pstr->valid_raw_len = byte_idx; } /* Build wide character buffer PSTR->WCS like build_wcs_buffer, but for REG_ICASE. */ static reg_errcode_t internal_function build_wcs_upper_buffer (re_string_t *pstr) { mbstate_t prev_st; int src_idx, byte_idx, end_idx, remain_len; size_t mbclen; #ifdef _LIBC char buf[MB_LEN_MAX]; assert (MB_LEN_MAX >= pstr->mb_cur_max); #else char buf[64]; #endif byte_idx = pstr->valid_len; end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; /* The following optimization assumes that ASCII characters can be mapped to wide characters with a simple cast. */ if (! pstr->map_notascii && pstr->trans == NULL && !pstr->offsets_needed) { while (byte_idx < end_idx) { wchar_t wc; if (isascii (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]) && mbsinit (&pstr->cur_state)) { /* In case of a singlebyte character. */ pstr->mbs[byte_idx] = toupper (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]); /* The next step uses the assumption that wchar_t is encoded ASCII-safe: all ASCII values can be converted like this. */ pstr->wcs[byte_idx] = (wchar_t) pstr->mbs[byte_idx]; ++byte_idx; continue; } remain_len = end_idx - byte_idx; prev_st = pstr->cur_state; mbclen = mbrtowc (&wc, ((const char *) pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx), remain_len, &pstr->cur_state); if (BE (mbclen + 2 > 2, 1)) { wchar_t wcu = wc; if (iswlower (wc)) { size_t mbcdlen; wcu = towupper (wc); mbcdlen = wcrtomb (buf, wcu, &prev_st); if (BE (mbclen == mbcdlen, 1)) memcpy (pstr->mbs + byte_idx, buf, mbclen); else { src_idx = byte_idx; goto offsets_needed; } } else memcpy (pstr->mbs + byte_idx, pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx, mbclen); pstr->wcs[byte_idx++] = wcu; /* Write paddings. */ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) pstr->wcs[byte_idx++] = WEOF; } else if (mbclen == (size_t) -1 || mbclen == 0) { /* It is an invalid character or '\0'. Just use the byte. */ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]; pstr->mbs[byte_idx] = ch; /* And also cast it to wide char. */ pstr->wcs[byte_idx++] = (wchar_t) ch; if (BE (mbclen == (size_t) -1, 0)) pstr->cur_state = prev_st; } else { /* The buffer doesn't have enough space, finish to build. */ pstr->cur_state = prev_st; break; } } pstr->valid_len = byte_idx; pstr->valid_raw_len = byte_idx; return REG_NOERROR; } else for (src_idx = pstr->valid_raw_len; byte_idx < end_idx;) { wchar_t wc; const char *p; offsets_needed: remain_len = end_idx - byte_idx; prev_st = pstr->cur_state; if (BE (pstr->trans != NULL, 0)) { int i, ch; for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i) { ch = pstr->raw_mbs [pstr->raw_mbs_idx + src_idx + i]; buf[i] = pstr->trans[ch]; } p = (const char *) buf; } else p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + src_idx; mbclen = mbrtowc (&wc, p, remain_len, &pstr->cur_state); if (BE (mbclen + 2 > 2, 1)) { wchar_t wcu = wc; if (iswlower (wc)) { size_t mbcdlen; wcu = towupper (wc); mbcdlen = wcrtomb ((char *) buf, wcu, &prev_st); if (BE (mbclen == mbcdlen, 1)) memcpy (pstr->mbs + byte_idx, buf, mbclen); else if (mbcdlen != (size_t) -1) { size_t i; if (byte_idx + mbcdlen > pstr->bufs_len) { pstr->cur_state = prev_st; break; } if (pstr->offsets == NULL) { pstr->offsets = re_malloc (int, pstr->bufs_len); if (pstr->offsets == NULL) return REG_ESPACE; } if (!pstr->offsets_needed) { for (i = 0; i < (size_t) byte_idx; ++i) pstr->offsets[i] = i; pstr->offsets_needed = 1; } memcpy (pstr->mbs + byte_idx, buf, mbcdlen); pstr->wcs[byte_idx] = wcu; pstr->offsets[byte_idx] = src_idx; for (i = 1; i < mbcdlen; ++i) { pstr->offsets[byte_idx + i] = src_idx + (i < mbclen ? i : mbclen - 1); pstr->wcs[byte_idx + i] = WEOF; } pstr->len += mbcdlen - mbclen; if (pstr->raw_stop > src_idx) pstr->stop += mbcdlen - mbclen; end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; byte_idx += mbcdlen; src_idx += mbclen; continue; } else memcpy (pstr->mbs + byte_idx, p, mbclen); } else memcpy (pstr->mbs + byte_idx, p, mbclen); if (BE (pstr->offsets_needed != 0, 0)) { size_t i; for (i = 0; i < mbclen; ++i) pstr->offsets[byte_idx + i] = src_idx + i; } src_idx += mbclen; pstr->wcs[byte_idx++] = wcu; /* Write paddings. */ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) pstr->wcs[byte_idx++] = WEOF; } else if (mbclen == (size_t) -1 || mbclen == 0) { /* It is an invalid character or '\0'. Just use the byte. */ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + src_idx]; if (BE (pstr->trans != NULL, 0)) ch = pstr->trans [ch]; pstr->mbs[byte_idx] = ch; if (BE (pstr->offsets_needed != 0, 0)) pstr->offsets[byte_idx] = src_idx; ++src_idx; /* And also cast it to wide char. */ pstr->wcs[byte_idx++] = (wchar_t) ch; if (BE (mbclen == (size_t) -1, 0)) pstr->cur_state = prev_st; } else { /* The buffer doesn't have enough space, finish to build. */ pstr->cur_state = prev_st; break; } } pstr->valid_len = byte_idx; pstr->valid_raw_len = src_idx; return REG_NOERROR; } /* Skip characters until the index becomes greater than NEW_RAW_IDX. Return the index. */ static int internal_function re_string_skip_chars (re_string_t *pstr, int new_raw_idx, wint_t *last_wc) { mbstate_t prev_st; int rawbuf_idx; size_t mbclen; wchar_t wc = WEOF; /* Skip the characters which are not necessary to check. */ for (rawbuf_idx = pstr->raw_mbs_idx + pstr->valid_raw_len; rawbuf_idx < new_raw_idx;) { int remain_len; remain_len = pstr->len - rawbuf_idx; prev_st = pstr->cur_state; mbclen = mbrtowc (&wc, (const char *) pstr->raw_mbs + rawbuf_idx, remain_len, &pstr->cur_state); if (BE (mbclen == (size_t) -2 || mbclen == (size_t) -1 || mbclen == 0, 0)) { /* We treat these cases as a single byte character. */ if (mbclen == 0 || remain_len == 0) wc = L'\0'; else wc = *(unsigned char *) (pstr->raw_mbs + rawbuf_idx); mbclen = 1; pstr->cur_state = prev_st; } /* Then proceed the next character. */ rawbuf_idx += mbclen; } *last_wc = (wint_t) wc; return rawbuf_idx; } #endif /* RE_ENABLE_I18N */ /* Build the buffer PSTR->MBS, and apply the translation if we need. This function is used in case of REG_ICASE. */ static void internal_function build_upper_buffer (re_string_t *pstr) { int char_idx, end_idx; end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; for (char_idx = pstr->valid_len; char_idx < end_idx; ++char_idx) { int ch = pstr->raw_mbs[pstr->raw_mbs_idx + char_idx]; if (BE (pstr->trans != NULL, 0)) ch = pstr->trans[ch]; if (islower (ch)) pstr->mbs[char_idx] = toupper (ch); else pstr->mbs[char_idx] = ch; } pstr->valid_len = char_idx; pstr->valid_raw_len = char_idx; } /* Apply TRANS to the buffer in PSTR. */ static void internal_function re_string_translate_buffer (re_string_t *pstr) { int buf_idx, end_idx; end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; for (buf_idx = pstr->valid_len; buf_idx < end_idx; ++buf_idx) { int ch = pstr->raw_mbs[pstr->raw_mbs_idx + buf_idx]; pstr->mbs[buf_idx] = pstr->trans[ch]; } pstr->valid_len = buf_idx; pstr->valid_raw_len = buf_idx; } /* This function re-construct the buffers. Concretely, convert to wide character in case of pstr->mb_cur_max > 1, convert to upper case in case of REG_ICASE, apply translation. */ static reg_errcode_t internal_function re_string_reconstruct (re_string_t *pstr, int idx, int eflags) { int offset = idx - pstr->raw_mbs_idx; if (BE (offset < 0, 0)) { /* Reset buffer. */ #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) memset (&pstr->cur_state, '\0', sizeof (mbstate_t)); #endif /* RE_ENABLE_I18N */ pstr->len = pstr->raw_len; pstr->stop = pstr->raw_stop; pstr->valid_len = 0; pstr->raw_mbs_idx = 0; pstr->valid_raw_len = 0; pstr->offsets_needed = 0; pstr->tip_context = ((eflags & REG_NOTBOL) ? CONTEXT_BEGBUF : CONTEXT_NEWLINE | CONTEXT_BEGBUF); if (!pstr->mbs_allocated) pstr->mbs = (unsigned char *) pstr->raw_mbs; offset = idx; } if (BE (offset != 0, 1)) { /* Should the already checked characters be kept? */ if (BE (offset < pstr->valid_raw_len, 1)) { /* Yes, move them to the front of the buffer. */ #ifdef RE_ENABLE_I18N if (BE (pstr->offsets_needed, 0)) { int low = 0, high = pstr->valid_len, mid; do { mid = (high + low) / 2; if (pstr->offsets[mid] > offset) high = mid; else if (pstr->offsets[mid] < offset) low = mid + 1; else break; } while (low < high); if (pstr->offsets[mid] < offset) ++mid; pstr->tip_context = re_string_context_at (pstr, mid - 1, eflags); /* This can be quite complicated, so handle specially only the common and easy case where the character with different length representation of lower and upper case is present at or after offset. */ if (pstr->valid_len > offset && mid == offset && pstr->offsets[mid] == offset) { memmove (pstr->wcs, pstr->wcs + offset, (pstr->valid_len - offset) * sizeof (wint_t)); memmove (pstr->mbs, pstr->mbs + offset, pstr->valid_len - offset); pstr->valid_len -= offset; pstr->valid_raw_len -= offset; for (low = 0; low < pstr->valid_len; low++) pstr->offsets[low] = pstr->offsets[low + offset] - offset; } else { /* Otherwise, just find out how long the partial multibyte character at offset is and fill it with WEOF/255. */ pstr->len = pstr->raw_len - idx + offset; pstr->stop = pstr->raw_stop - idx + offset; pstr->offsets_needed = 0; while (mid > 0 && pstr->offsets[mid - 1] == offset) --mid; while (mid < pstr->valid_len) if (pstr->wcs[mid] != WEOF) break; else ++mid; if (mid == pstr->valid_len) pstr->valid_len = 0; else { pstr->valid_len = pstr->offsets[mid] - offset; if (pstr->valid_len) { for (low = 0; low < pstr->valid_len; ++low) pstr->wcs[low] = WEOF; memset (pstr->mbs, 255, pstr->valid_len); } } pstr->valid_raw_len = pstr->valid_len; } } else #endif { pstr->tip_context = re_string_context_at (pstr, offset - 1, eflags); #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) memmove (pstr->wcs, pstr->wcs + offset, (pstr->valid_len - offset) * sizeof (wint_t)); #endif /* RE_ENABLE_I18N */ if (BE (pstr->mbs_allocated, 0)) memmove (pstr->mbs, pstr->mbs + offset, pstr->valid_len - offset); pstr->valid_len -= offset; pstr->valid_raw_len -= offset; #if DEBUG assert (pstr->valid_len > 0); #endif } } else { /* No, skip all characters until IDX. */ #ifdef RE_ENABLE_I18N int prev_valid_len = pstr->valid_len; if (BE (pstr->offsets_needed, 0)) { pstr->len = pstr->raw_len - idx + offset; pstr->stop = pstr->raw_stop - idx + offset; pstr->offsets_needed = 0; } #endif pstr->valid_len = 0; #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) { int wcs_idx; wint_t wc = WEOF; if (pstr->is_utf8) { const unsigned char *raw, *p, *q, *end; /* Special case UTF-8. Multi-byte chars start with any byte other than 0x80 - 0xbf. */ raw = pstr->raw_mbs + pstr->raw_mbs_idx; end = raw + (offset - pstr->mb_cur_max); if (end < pstr->raw_mbs) end = pstr->raw_mbs; p = raw + offset - 1; #ifdef _LIBC /* We know the wchar_t encoding is UCS4, so for the simple case, ASCII characters, skip the conversion step. */ if (isascii (*p) && BE (pstr->trans == NULL, 1)) { memset (&pstr->cur_state, '\0', sizeof (mbstate_t)); /* pstr->valid_len = 0; */ wc = (wchar_t) *p; } else #endif for (; p >= end; --p) if ((*p & 0xc0) != 0x80) { mbstate_t cur_state; wchar_t wc2; int mlen = raw + pstr->len - p; unsigned char buf[6]; size_t mbclen; q = p; if (BE (pstr->trans != NULL, 0)) { int i = mlen < 6 ? mlen : 6; while (--i >= 0) buf[i] = pstr->trans[p[i]]; q = buf; } /* XXX Don't use mbrtowc, we know which conversion to use (UTF-8 -> UCS4). */ memset (&cur_state, 0, sizeof (cur_state)); mbclen = mbrtowc (&wc2, (const char *) p, mlen, &cur_state); if (raw + offset - p <= mbclen && mbclen < (size_t) -2) { memset (&pstr->cur_state, '\0', sizeof (mbstate_t)); pstr->valid_len = mbclen - (raw + offset - p); wc = wc2; } break; } } if (wc == WEOF) pstr->valid_len = re_string_skip_chars (pstr, idx, &wc) - idx; if (wc == WEOF) pstr->tip_context = re_string_context_at (pstr, prev_valid_len - 1, eflags); else pstr->tip_context = ((BE (pstr->word_ops_used != 0, 0) && IS_WIDE_WORD_CHAR (wc)) ? CONTEXT_WORD : ((IS_WIDE_NEWLINE (wc) && pstr->newline_anchor) ? CONTEXT_NEWLINE : 0)); if (BE (pstr->valid_len, 0)) { for (wcs_idx = 0; wcs_idx < pstr->valid_len; ++wcs_idx) pstr->wcs[wcs_idx] = WEOF; if (pstr->mbs_allocated) memset (pstr->mbs, 255, pstr->valid_len); } pstr->valid_raw_len = pstr->valid_len; } else #endif /* RE_ENABLE_I18N */ { int c = pstr->raw_mbs[pstr->raw_mbs_idx + offset - 1]; pstr->valid_raw_len = 0; if (pstr->trans) c = pstr->trans[c]; pstr->tip_context = (bitset_contain (pstr->word_char, c) ? CONTEXT_WORD : ((IS_NEWLINE (c) && pstr->newline_anchor) ? CONTEXT_NEWLINE : 0)); } } if (!BE (pstr->mbs_allocated, 0)) pstr->mbs += offset; } pstr->raw_mbs_idx = idx; pstr->len -= offset; pstr->stop -= offset; /* Then build the buffers. */ #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) { if (pstr->icase) { reg_errcode_t ret = build_wcs_upper_buffer (pstr); if (BE (ret != REG_NOERROR, 0)) return ret; } else build_wcs_buffer (pstr); } else #endif /* RE_ENABLE_I18N */ if (BE (pstr->mbs_allocated, 0)) { if (pstr->icase) build_upper_buffer (pstr); else if (pstr->trans != NULL) re_string_translate_buffer (pstr); } else pstr->valid_len = pstr->len; pstr->cur_idx = 0; return REG_NOERROR; } static unsigned char internal_function __attribute ((pure)) re_string_peek_byte_case (const re_string_t *pstr, int idx) { int ch, off; /* Handle the common (easiest) cases first. */ if (BE (!pstr->mbs_allocated, 1)) return re_string_peek_byte (pstr, idx); #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1 && ! re_string_is_single_byte_char (pstr, pstr->cur_idx + idx)) return re_string_peek_byte (pstr, idx); #endif off = pstr->cur_idx + idx; #ifdef RE_ENABLE_I18N if (pstr->offsets_needed) off = pstr->offsets[off]; #endif ch = pstr->raw_mbs[pstr->raw_mbs_idx + off]; #ifdef RE_ENABLE_I18N /* Ensure that e.g. for tr_TR.UTF-8 BACKSLASH DOTLESS SMALL LETTER I this function returns CAPITAL LETTER I instead of first byte of DOTLESS SMALL LETTER I. The latter would confuse the parser, since peek_byte_case doesn't advance cur_idx in any way. */ if (pstr->offsets_needed && !isascii (ch)) return re_string_peek_byte (pstr, idx); #endif return ch; } static unsigned char internal_function __attribute ((pure)) re_string_fetch_byte_case (re_string_t *pstr) { if (BE (!pstr->mbs_allocated, 1)) return re_string_fetch_byte (pstr); #ifdef RE_ENABLE_I18N if (pstr->offsets_needed) { int off, ch; /* For tr_TR.UTF-8 [[:islower:]] there is [[: CAPITAL LETTER I WITH DOT lower:]] in mbs. Skip in that case the whole multi-byte character and return the original letter. On the other side, with [[: DOTLESS SMALL LETTER I return [[:I, as doing anything else would complicate things too much. */ if (!re_string_first_byte (pstr, pstr->cur_idx)) return re_string_fetch_byte (pstr); off = pstr->offsets[pstr->cur_idx]; ch = pstr->raw_mbs[pstr->raw_mbs_idx + off]; if (! isascii (ch)) return re_string_fetch_byte (pstr); re_string_skip_bytes (pstr, re_string_char_size_at (pstr, pstr->cur_idx)); return ch; } #endif return pstr->raw_mbs[pstr->raw_mbs_idx + pstr->cur_idx++]; } static void internal_function re_string_destruct (re_string_t *pstr) { #ifdef RE_ENABLE_I18N re_free (pstr->wcs); re_free (pstr->offsets); #endif /* RE_ENABLE_I18N */ if (pstr->mbs_allocated) re_free (pstr->mbs); } /* Return the context at IDX in INPUT. */ static unsigned int internal_function re_string_context_at (const re_string_t *input, int idx, int eflags) { int c; if (BE (idx < 0, 0)) /* In this case, we use the value stored in input->tip_context, since we can't know the character in input->mbs[-1] here. */ return input->tip_context; if (BE (idx == input->len, 0)) return ((eflags & REG_NOTEOL) ? CONTEXT_ENDBUF : CONTEXT_NEWLINE | CONTEXT_ENDBUF); #ifdef RE_ENABLE_I18N if (input->mb_cur_max > 1) { wint_t wc; int wc_idx = idx; while(input->wcs[wc_idx] == WEOF) { #ifdef DEBUG /* It must not happen. */ assert (wc_idx >= 0); #endif --wc_idx; if (wc_idx < 0) return input->tip_context; } wc = input->wcs[wc_idx]; if (BE (input->word_ops_used != 0, 0) && IS_WIDE_WORD_CHAR (wc)) return CONTEXT_WORD; return (IS_WIDE_NEWLINE (wc) && input->newline_anchor ? CONTEXT_NEWLINE : 0); } else #endif { c = re_string_byte_at (input, idx); if (bitset_contain (input->word_char, c)) return CONTEXT_WORD; return IS_NEWLINE (c) && input->newline_anchor ? CONTEXT_NEWLINE : 0; } } /* Functions for set operation. */ static reg_errcode_t internal_function re_node_set_alloc (re_node_set *set, int size) { set->alloc = size; set->nelem = 0; set->elems = re_malloc (int, size); if (BE (set->elems == NULL, 0)) return REG_ESPACE; return REG_NOERROR; } static reg_errcode_t internal_function re_node_set_init_1 (re_node_set *set, int elem) { set->alloc = 1; set->nelem = 1; set->elems = re_malloc (int, 1); if (BE (set->elems == NULL, 0)) { set->alloc = set->nelem = 0; return REG_ESPACE; } set->elems[0] = elem; return REG_NOERROR; } static reg_errcode_t internal_function re_node_set_init_2 (re_node_set *set, int elem1, int elem2) { set->alloc = 2; set->elems = re_malloc (int, 2); if (BE (set->elems == NULL, 0)) return REG_ESPACE; if (elem1 == elem2) { set->nelem = 1; set->elems[0] = elem1; } else { set->nelem = 2; if (elem1 < elem2) { set->elems[0] = elem1; set->elems[1] = elem2; } else { set->elems[0] = elem2; set->elems[1] = elem1; } } return REG_NOERROR; } static reg_errcode_t internal_function re_node_set_init_copy (re_node_set *dest, const re_node_set *src) { dest->nelem = src->nelem; if (src->nelem > 0) { dest->alloc = dest->nelem; dest->elems = re_malloc (int, dest->alloc); if (BE (dest->elems == NULL, 0)) { dest->alloc = dest->nelem = 0; return REG_ESPACE; } memcpy (dest->elems, src->elems, src->nelem * sizeof (int)); } else re_node_set_init_empty (dest); return REG_NOERROR; } /* Calculate the intersection of the sets SRC1 and SRC2. And merge it to DEST. Return value indicate the error code or REG_NOERROR if succeeded. Note: We assume dest->elems is NULL, when dest->alloc is 0. */ static reg_errcode_t internal_function re_node_set_add_intersect (re_node_set *dest, const re_node_set *src1, const re_node_set *src2) { int i1, i2, is, id, delta, sbase; if (src1->nelem == 0 || src2->nelem == 0) return REG_NOERROR; /* We need dest->nelem + 2 * elems_in_intersection; this is a conservative estimate. */ if (src1->nelem + src2->nelem + dest->nelem > dest->alloc) { int new_alloc = src1->nelem + src2->nelem + dest->alloc; int *new_elems = re_realloc (dest->elems, int, new_alloc); if (BE (new_elems == NULL, 0)) return REG_ESPACE; dest->elems = new_elems; dest->alloc = new_alloc; } /* Find the items in the intersection of SRC1 and SRC2, and copy into the top of DEST those that are not already in DEST itself. */ sbase = dest->nelem + src1->nelem + src2->nelem; i1 = src1->nelem - 1; i2 = src2->nelem - 1; id = dest->nelem - 1; for (;;) { if (src1->elems[i1] == src2->elems[i2]) { /* Try to find the item in DEST. Maybe we could binary search? */ while (id >= 0 && dest->elems[id] > src1->elems[i1]) --id; if (id < 0 || dest->elems[id] != src1->elems[i1]) dest->elems[--sbase] = src1->elems[i1]; if (--i1 < 0 || --i2 < 0) break; } /* Lower the highest of the two items. */ else if (src1->elems[i1] < src2->elems[i2]) { if (--i2 < 0) break; } else { if (--i1 < 0) break; } } id = dest->nelem - 1; is = dest->nelem + src1->nelem + src2->nelem - 1; delta = is - sbase + 1; /* Now copy. When DELTA becomes zero, the remaining DEST elements are already in place; this is more or less the same loop that is in re_node_set_merge. */ dest->nelem += delta; if (delta > 0 && id >= 0) for (;;) { if (dest->elems[is] > dest->elems[id]) { /* Copy from the top. */ dest->elems[id + delta--] = dest->elems[is--]; if (delta == 0) break; } else { /* Slide from the bottom. */ dest->elems[id + delta] = dest->elems[id]; if (--id < 0) break; } } /* Copy remaining SRC elements. */ memcpy (dest->elems, dest->elems + sbase, delta * sizeof (int)); return REG_NOERROR; } /* Calculate the union set of the sets SRC1 and SRC2. And store it to DEST. Return value indicate the error code or REG_NOERROR if succeeded. */ static reg_errcode_t internal_function re_node_set_init_union (re_node_set *dest, const re_node_set *src1, const re_node_set *src2) { int i1, i2, id; if (src1 != NULL && src1->nelem > 0 && src2 != NULL && src2->nelem > 0) { dest->alloc = src1->nelem + src2->nelem; dest->elems = re_malloc (int, dest->alloc); if (BE (dest->elems == NULL, 0)) return REG_ESPACE; } else { if (src1 != NULL && src1->nelem > 0) return re_node_set_init_copy (dest, src1); else if (src2 != NULL && src2->nelem > 0) return re_node_set_init_copy (dest, src2); else re_node_set_init_empty (dest); return REG_NOERROR; } for (i1 = i2 = id = 0 ; i1 < src1->nelem && i2 < src2->nelem ;) { if (src1->elems[i1] > src2->elems[i2]) { dest->elems[id++] = src2->elems[i2++]; continue; } if (src1->elems[i1] == src2->elems[i2]) ++i2; dest->elems[id++] = src1->elems[i1++]; } if (i1 < src1->nelem) { memcpy (dest->elems + id, src1->elems + i1, (src1->nelem - i1) * sizeof (int)); id += src1->nelem - i1; } else if (i2 < src2->nelem) { memcpy (dest->elems + id, src2->elems + i2, (src2->nelem - i2) * sizeof (int)); id += src2->nelem - i2; } dest->nelem = id; return REG_NOERROR; } /* Calculate the union set of the sets DEST and SRC. And store it to DEST. Return value indicate the error code or REG_NOERROR if succeeded. */ static reg_errcode_t internal_function re_node_set_merge (re_node_set *dest, const re_node_set *src) { int is, id, sbase, delta; if (src == NULL || src->nelem == 0) return REG_NOERROR; if (dest->alloc < 2 * src->nelem + dest->nelem) { int new_alloc = 2 * (src->nelem + dest->alloc); int *new_buffer = re_realloc (dest->elems, int, new_alloc); if (BE (new_buffer == NULL, 0)) return REG_ESPACE; dest->elems = new_buffer; dest->alloc = new_alloc; } if (BE (dest->nelem == 0, 0)) { dest->nelem = src->nelem; memcpy (dest->elems, src->elems, src->nelem * sizeof (int)); return REG_NOERROR; } /* Copy into the top of DEST the items of SRC that are not found in DEST. Maybe we could binary search in DEST? */ for (sbase = dest->nelem + 2 * src->nelem, is = src->nelem - 1, id = dest->nelem - 1; is >= 0 && id >= 0; ) { if (dest->elems[id] == src->elems[is]) is--, id--; else if (dest->elems[id] < src->elems[is]) dest->elems[--sbase] = src->elems[is--]; else /* if (dest->elems[id] > src->elems[is]) */ --id; } if (is >= 0) { /* If DEST is exhausted, the remaining items of SRC must be unique. */ sbase -= is + 1; memcpy (dest->elems + sbase, src->elems, (is + 1) * sizeof (int)); } id = dest->nelem - 1; is = dest->nelem + 2 * src->nelem - 1; delta = is - sbase + 1; if (delta == 0) return REG_NOERROR; /* Now copy. When DELTA becomes zero, the remaining DEST elements are already in place. */ dest->nelem += delta; for (;;) { if (dest->elems[is] > dest->elems[id]) { /* Copy from the top. */ dest->elems[id + delta--] = dest->elems[is--]; if (delta == 0) break; } else { /* Slide from the bottom. */ dest->elems[id + delta] = dest->elems[id]; if (--id < 0) { /* Copy remaining SRC elements. */ memcpy (dest->elems, dest->elems + sbase, delta * sizeof (int)); break; } } } return REG_NOERROR; } /* Insert the new element ELEM to the re_node_set* SET. SET should not already have ELEM. return -1 if an error is occured, return 1 otherwise. */ static int internal_function re_node_set_insert (re_node_set *set, int elem) { int idx; /* In case the set is empty. */ if (set->alloc == 0) { if (BE (re_node_set_init_1 (set, elem) == REG_NOERROR, 1)) return 1; else return -1; } if (BE (set->nelem, 0) == 0) { /* We already guaranteed above that set->alloc != 0. */ set->elems[0] = elem; ++set->nelem; return 1; } /* Realloc if we need. */ if (set->alloc == set->nelem) { int *new_elems; set->alloc = set->alloc * 2; new_elems = re_realloc (set->elems, int, set->alloc); if (BE (new_elems == NULL, 0)) return -1; set->elems = new_elems; } /* Move the elements which follows the new element. Test the first element separately to skip a check in the inner loop. */ if (elem < set->elems[0]) { idx = 0; for (idx = set->nelem; idx > 0; idx--) set->elems[idx] = set->elems[idx - 1]; } else { for (idx = set->nelem; set->elems[idx - 1] > elem; idx--) set->elems[idx] = set->elems[idx - 1]; } /* Insert the new element. */ set->elems[idx] = elem; ++set->nelem; return 1; } /* Insert the new element ELEM to the re_node_set* SET. SET should not already have any element greater than or equal to ELEM. Return -1 if an error is occured, return 1 otherwise. */ static int internal_function re_node_set_insert_last (re_node_set *set, int elem) { /* Realloc if we need. */ if (set->alloc == set->nelem) { int *new_elems; set->alloc = (set->alloc + 1) * 2; new_elems = re_realloc (set->elems, int, set->alloc); if (BE (new_elems == NULL, 0)) return -1; set->elems = new_elems; } /* Insert the new element. */ set->elems[set->nelem++] = elem; return 1; } /* Compare two node sets SET1 and SET2. return 1 if SET1 and SET2 are equivalent, return 0 otherwise. */ static int internal_function __attribute ((pure)) re_node_set_compare (const re_node_set *set1, const re_node_set *set2) { int i; if (set1 == NULL || set2 == NULL || set1->nelem != set2->nelem) return 0; for (i = set1->nelem ; --i >= 0 ; ) if (set1->elems[i] != set2->elems[i]) return 0; return 1; } /* Return (idx + 1) if SET contains the element ELEM, return 0 otherwise. */ static int internal_function __attribute ((pure)) re_node_set_contains (const re_node_set *set, int elem) { unsigned int idx, right, mid; if (set->nelem <= 0) return 0; /* Binary search the element. */ idx = 0; right = set->nelem - 1; while (idx < right) { mid = (idx + right) / 2; if (set->elems[mid] < elem) idx = mid + 1; else right = mid; } return set->elems[idx] == elem ? idx + 1 : 0; } static void internal_function re_node_set_remove_at (re_node_set *set, int idx) { if (idx < 0 || idx >= set->nelem) return; --set->nelem; for (; idx < set->nelem; idx++) set->elems[idx] = set->elems[idx + 1]; } /* Add the token TOKEN to dfa->nodes, and return the index of the token. Or return -1, if an error will be occured. */ static int internal_function re_dfa_add_node (re_dfa_t *dfa, re_token_t token) { #ifdef RE_ENABLE_I18N int type = token.type; #endif if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0)) { size_t new_nodes_alloc = dfa->nodes_alloc * 2; int *new_nexts, *new_indices; re_node_set *new_edests, *new_eclosures; re_token_t *new_nodes; /* Avoid overflows. */ if (BE (new_nodes_alloc < dfa->nodes_alloc, 0)) return -1; new_nodes = re_realloc (dfa->nodes, re_token_t, new_nodes_alloc); if (BE (new_nodes == NULL, 0)) return -1; dfa->nodes = new_nodes; new_nexts = re_realloc (dfa->nexts, int, new_nodes_alloc); new_indices = re_realloc (dfa->org_indices, int, new_nodes_alloc); new_edests = re_realloc (dfa->edests, re_node_set, new_nodes_alloc); new_eclosures = re_realloc (dfa->eclosures, re_node_set, new_nodes_alloc); if (BE (new_nexts == NULL || new_indices == NULL || new_edests == NULL || new_eclosures == NULL, 0)) return -1; dfa->nexts = new_nexts; dfa->org_indices = new_indices; dfa->edests = new_edests; dfa->eclosures = new_eclosures; dfa->nodes_alloc = new_nodes_alloc; } dfa->nodes[dfa->nodes_len] = token; dfa->nodes[dfa->nodes_len].constraint = 0; #ifdef RE_ENABLE_I18N dfa->nodes[dfa->nodes_len].accept_mb = (type == OP_PERIOD && dfa->mb_cur_max > 1) || type == COMPLEX_BRACKET; #endif dfa->nexts[dfa->nodes_len] = -1; re_node_set_init_empty (dfa->edests + dfa->nodes_len); re_node_set_init_empty (dfa->eclosures + dfa->nodes_len); return dfa->nodes_len++; } static inline unsigned int internal_function calc_state_hash (const re_node_set *nodes, unsigned int context) { unsigned int hash = nodes->nelem + context; int i; for (i = 0 ; i < nodes->nelem ; i++) hash += nodes->elems[i]; return hash; } /* Search for the state whose node_set is equivalent to NODES. Return the pointer to the state, if we found it in the DFA. Otherwise create the new one and return it. In case of an error return NULL and set the error code in ERR. Note: - We assume NULL as the invalid state, then it is possible that return value is NULL and ERR is REG_NOERROR. - We never return non-NULL value in case of any errors, it is for optimization. */ static re_dfastate_t * internal_function re_acquire_state (reg_errcode_t *err, const re_dfa_t *dfa, const re_node_set *nodes) { unsigned int hash; re_dfastate_t *new_state; struct re_state_table_entry *spot; int i; if (BE (nodes->nelem == 0, 0)) { *err = REG_NOERROR; return NULL; } hash = calc_state_hash (nodes, 0); spot = dfa->state_table + (hash & dfa->state_hash_mask); for (i = 0 ; i < spot->num ; i++) { re_dfastate_t *state = spot->array[i]; if (hash != state->hash) continue; if (re_node_set_compare (&state->nodes, nodes)) return state; } /* There are no appropriate state in the dfa, create the new one. */ new_state = create_ci_newstate (dfa, nodes, hash); if (BE (new_state == NULL, 0)) *err = REG_ESPACE; return new_state; } /* Search for the state whose node_set is equivalent to NODES and whose context is equivalent to CONTEXT. Return the pointer to the state, if we found it in the DFA. Otherwise create the new one and return it. In case of an error return NULL and set the error code in ERR. Note: - We assume NULL as the invalid state, then it is possible that return value is NULL and ERR is REG_NOERROR. - We never return non-NULL value in case of any errors, it is for optimization. */ static re_dfastate_t * internal_function re_acquire_state_context (reg_errcode_t *err, const re_dfa_t *dfa, const re_node_set *nodes, unsigned int context) { unsigned int hash; re_dfastate_t *new_state; struct re_state_table_entry *spot; int i; if (nodes->nelem == 0) { *err = REG_NOERROR; return NULL; } hash = calc_state_hash (nodes, context); spot = dfa->state_table + (hash & dfa->state_hash_mask); for (i = 0 ; i < spot->num ; i++) { re_dfastate_t *state = spot->array[i]; if (state->hash == hash && state->context == context && re_node_set_compare (state->entrance_nodes, nodes)) return state; } /* There are no appropriate state in `dfa', create the new one. */ new_state = create_cd_newstate (dfa, nodes, context, hash); if (BE (new_state == NULL, 0)) *err = REG_ESPACE; return new_state; } /* Finish initialization of the new state NEWSTATE, and using its hash value HASH put in the appropriate bucket of DFA's state table. Return value indicates the error code if failed. */ static reg_errcode_t register_state (const re_dfa_t *dfa, re_dfastate_t *newstate, unsigned int hash) { struct re_state_table_entry *spot; reg_errcode_t err; int i; newstate->hash = hash; err = re_node_set_alloc (&newstate->non_eps_nodes, newstate->nodes.nelem); if (BE (err != REG_NOERROR, 0)) return REG_ESPACE; for (i = 0; i < newstate->nodes.nelem; i++) { int elem = newstate->nodes.elems[i]; if (!IS_EPSILON_NODE (dfa->nodes[elem].type)) re_node_set_insert_last (&newstate->non_eps_nodes, elem); } spot = dfa->state_table + (hash & dfa->state_hash_mask); if (BE (spot->alloc <= spot->num, 0)) { int new_alloc = 2 * spot->num + 2; re_dfastate_t **new_array = re_realloc (spot->array, re_dfastate_t *, new_alloc); if (BE (new_array == NULL, 0)) return REG_ESPACE; spot->array = new_array; spot->alloc = new_alloc; } spot->array[spot->num++] = newstate; return REG_NOERROR; } static void free_state (re_dfastate_t *state) { re_node_set_free (&state->non_eps_nodes); re_node_set_free (&state->inveclosure); if (state->entrance_nodes != &state->nodes) { re_node_set_free (state->entrance_nodes); re_free (state->entrance_nodes); } re_node_set_free (&state->nodes); re_free (state->word_trtable); re_free (state->trtable); re_free (state); } /* Create the new state which is independ of contexts. Return the new state if succeeded, otherwise return NULL. */ static re_dfastate_t * internal_function create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes, unsigned int hash) { int i; reg_errcode_t err; re_dfastate_t *newstate; newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); if (BE (newstate == NULL, 0)) return NULL; err = re_node_set_init_copy (&newstate->nodes, nodes); if (BE (err != REG_NOERROR, 0)) { re_free (newstate); return NULL; } newstate->entrance_nodes = &newstate->nodes; for (i = 0 ; i < nodes->nelem ; i++) { re_token_t *node = dfa->nodes + nodes->elems[i]; re_token_type_t type = node->type; if (type == CHARACTER && !node->constraint) continue; #ifdef RE_ENABLE_I18N newstate->accept_mb |= node->accept_mb; #endif /* RE_ENABLE_I18N */ /* If the state has the halt node, the state is a halt state. */ if (type == END_OF_RE) newstate->halt = 1; else if (type == OP_BACK_REF) newstate->has_backref = 1; else if (type == ANCHOR || node->constraint) newstate->has_constraint = 1; } err = register_state (dfa, newstate, hash); if (BE (err != REG_NOERROR, 0)) { free_state (newstate); newstate = NULL; } return newstate; } /* Create the new state which is depend on the context CONTEXT. Return the new state if succeeded, otherwise return NULL. */ static re_dfastate_t * internal_function create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes, unsigned int context, unsigned int hash) { int i, nctx_nodes = 0; reg_errcode_t err; re_dfastate_t *newstate; newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); if (BE (newstate == NULL, 0)) return NULL; err = re_node_set_init_copy (&newstate->nodes, nodes); if (BE (err != REG_NOERROR, 0)) { re_free (newstate); return NULL; } newstate->context = context; newstate->entrance_nodes = &newstate->nodes; for (i = 0 ; i < nodes->nelem ; i++) { unsigned int constraint = 0; re_token_t *node = dfa->nodes + nodes->elems[i]; re_token_type_t type = node->type; if (node->constraint) constraint = node->constraint; if (type == CHARACTER && !constraint) continue; #ifdef RE_ENABLE_I18N newstate->accept_mb |= node->accept_mb; #endif /* RE_ENABLE_I18N */ /* If the state has the halt node, the state is a halt state. */ if (type == END_OF_RE) newstate->halt = 1; else if (type == OP_BACK_REF) newstate->has_backref = 1; else if (type == ANCHOR) constraint = node->opr.ctx_type; if (constraint) { if (newstate->entrance_nodes == &newstate->nodes) { newstate->entrance_nodes = re_malloc (re_node_set, 1); if (BE (newstate->entrance_nodes == NULL, 0)) { free_state (newstate); return NULL; } re_node_set_init_copy (newstate->entrance_nodes, nodes); nctx_nodes = 0; newstate->has_constraint = 1; } if (NOT_SATISFY_PREV_CONSTRAINT (constraint,context)) { re_node_set_remove_at (&newstate->nodes, i - nctx_nodes); ++nctx_nodes; } } } err = register_state (dfa, newstate, hash); if (BE (err != REG_NOERROR, 0)) { free_state (newstate); newstate = NULL; } return newstate; } direwolf-1.5+dfsg/regex/regex_internal.h000066400000000000000000000522171347750676600204360ustar00rootroot00000000000000/* Extended regular expression matching and search library. Copyright (C) 2002, 2003, 2004, 2005, 2007 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C 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. The GNU C 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 the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifndef _REGEX_INTERNAL_H #define _REGEX_INTERNAL_H 1 #include #include #include #include #include #if defined HAVE_LANGINFO_H || defined HAVE_LANGINFO_CODESET || defined _LIBC # include #endif #if defined HAVE_LOCALE_H || defined _LIBC # include #endif #if defined HAVE_WCHAR_H || defined _LIBC # include #endif /* HAVE_WCHAR_H || _LIBC */ #if defined HAVE_WCTYPE_H || defined _LIBC # include #endif /* HAVE_WCTYPE_H || _LIBC */ #if defined HAVE_STDBOOL_H || defined _LIBC # include #endif /* HAVE_STDBOOL_H || _LIBC */ #if defined HAVE_STDINT_H || defined _LIBC # include #endif /* HAVE_STDINT_H || _LIBC */ #if defined _LIBC # include #else # define __libc_lock_define(CLASS,NAME) # define __libc_lock_init(NAME) do { } while (0) # define __libc_lock_lock(NAME) do { } while (0) # define __libc_lock_unlock(NAME) do { } while (0) #endif /* In case that the system doesn't have isblank(). */ #if !defined _LIBC && !defined HAVE_ISBLANK && !defined isblank # define isblank(ch) ((ch) == ' ' || (ch) == '\t') #endif #ifdef _LIBC # ifndef _RE_DEFINE_LOCALE_FUNCTIONS # define _RE_DEFINE_LOCALE_FUNCTIONS 1 # include # include # include # endif #endif /* This is for other GNU distributions with internationalized messages. */ #if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC # include # ifdef _LIBC # undef gettext # define gettext(msgid) \ INTUSE(__dcgettext) (_libc_intl_domainname, msgid, LC_MESSAGES) # endif #else # define gettext(msgid) (msgid) #endif #ifndef gettext_noop /* This define is so xgettext can find the internationalizable strings. */ # define gettext_noop(String) String #endif /* For loser systems without the definition. */ #ifndef SIZE_MAX # define SIZE_MAX ((size_t) -1) #endif #if (defined MB_CUR_MAX && HAVE_LOCALE_H && HAVE_WCTYPE_H && HAVE_WCHAR_H && HAVE_WCRTOMB && HAVE_MBRTOWC && HAVE_WCSCOLL) || _LIBC # define RE_ENABLE_I18N #endif #if __GNUC__ >= 3 # define BE(expr, val) __builtin_expect (expr, val) #else # define BE(expr, val) (expr) # define inline #endif /* Number of single byte character. */ #define SBC_MAX 256 #define COLL_ELEM_LEN_MAX 8 /* The character which represents newline. */ #define NEWLINE_CHAR '\n' #define WIDE_NEWLINE_CHAR L'\n' /* Rename to standard API for using out of glibc. */ #ifndef _LIBC # define __wctype wctype # define __iswctype iswctype # define __btowc btowc # define __mempcpy mempcpy # define __wcrtomb wcrtomb # define __regfree regfree # define attribute_hidden #endif /* not _LIBC */ #ifdef __GNUC__ # define __attribute(arg) __attribute__ (arg) #else # define __attribute(arg) #endif extern const char __re_error_msgid[] attribute_hidden; extern const size_t __re_error_msgid_idx[] attribute_hidden; /* An integer used to represent a set of bits. It must be unsigned, and must be at least as wide as unsigned int. */ typedef unsigned long int bitset_word_t; /* All bits set in a bitset_word_t. */ #define BITSET_WORD_MAX ULONG_MAX /* Number of bits in a bitset_word_t. */ #define BITSET_WORD_BITS (sizeof (bitset_word_t) * CHAR_BIT) /* Number of bitset_word_t in a bit_set. */ #define BITSET_WORDS (SBC_MAX / BITSET_WORD_BITS) typedef bitset_word_t bitset_t[BITSET_WORDS]; typedef bitset_word_t *re_bitset_ptr_t; typedef const bitset_word_t *re_const_bitset_ptr_t; #define bitset_set(set,i) \ (set[i / BITSET_WORD_BITS] |= (bitset_word_t) 1 << i % BITSET_WORD_BITS) #define bitset_clear(set,i) \ (set[i / BITSET_WORD_BITS] &= ~((bitset_word_t) 1 << i % BITSET_WORD_BITS)) #define bitset_contain(set,i) \ (set[i / BITSET_WORD_BITS] & ((bitset_word_t) 1 << i % BITSET_WORD_BITS)) #define bitset_empty(set) memset (set, '\0', sizeof (bitset_t)) #define bitset_set_all(set) memset (set, '\xff', sizeof (bitset_t)) #define bitset_copy(dest,src) memcpy (dest, src, sizeof (bitset_t)) #define PREV_WORD_CONSTRAINT 0x0001 #define PREV_NOTWORD_CONSTRAINT 0x0002 #define NEXT_WORD_CONSTRAINT 0x0004 #define NEXT_NOTWORD_CONSTRAINT 0x0008 #define PREV_NEWLINE_CONSTRAINT 0x0010 #define NEXT_NEWLINE_CONSTRAINT 0x0020 #define PREV_BEGBUF_CONSTRAINT 0x0040 #define NEXT_ENDBUF_CONSTRAINT 0x0080 #define WORD_DELIM_CONSTRAINT 0x0100 #define NOT_WORD_DELIM_CONSTRAINT 0x0200 typedef enum { INSIDE_WORD = PREV_WORD_CONSTRAINT | NEXT_WORD_CONSTRAINT, WORD_FIRST = PREV_NOTWORD_CONSTRAINT | NEXT_WORD_CONSTRAINT, WORD_LAST = PREV_WORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT, INSIDE_NOTWORD = PREV_NOTWORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT, LINE_FIRST = PREV_NEWLINE_CONSTRAINT, LINE_LAST = NEXT_NEWLINE_CONSTRAINT, BUF_FIRST = PREV_BEGBUF_CONSTRAINT, BUF_LAST = NEXT_ENDBUF_CONSTRAINT, WORD_DELIM = WORD_DELIM_CONSTRAINT, NOT_WORD_DELIM = NOT_WORD_DELIM_CONSTRAINT } re_context_type; typedef struct { int alloc; int nelem; int *elems; } re_node_set; typedef enum { NON_TYPE = 0, /* Node type, These are used by token, node, tree. */ CHARACTER = 1, END_OF_RE = 2, SIMPLE_BRACKET = 3, OP_BACK_REF = 4, OP_PERIOD = 5, #ifdef RE_ENABLE_I18N COMPLEX_BRACKET = 6, OP_UTF8_PERIOD = 7, #endif /* RE_ENABLE_I18N */ /* We define EPSILON_BIT as a macro so that OP_OPEN_SUBEXP is used when the debugger shows values of this enum type. */ #define EPSILON_BIT 8 OP_OPEN_SUBEXP = EPSILON_BIT | 0, OP_CLOSE_SUBEXP = EPSILON_BIT | 1, OP_ALT = EPSILON_BIT | 2, OP_DUP_ASTERISK = EPSILON_BIT | 3, ANCHOR = EPSILON_BIT | 4, /* Tree type, these are used only by tree. */ CONCAT = 16, SUBEXP = 17, /* Token type, these are used only by token. */ OP_DUP_PLUS = 18, OP_DUP_QUESTION, OP_OPEN_BRACKET, OP_CLOSE_BRACKET, OP_CHARSET_RANGE, OP_OPEN_DUP_NUM, OP_CLOSE_DUP_NUM, OP_NON_MATCH_LIST, OP_OPEN_COLL_ELEM, OP_CLOSE_COLL_ELEM, OP_OPEN_EQUIV_CLASS, OP_CLOSE_EQUIV_CLASS, OP_OPEN_CHAR_CLASS, OP_CLOSE_CHAR_CLASS, OP_WORD, OP_NOTWORD, OP_SPACE, OP_NOTSPACE, BACK_SLASH } re_token_type_t; #ifdef RE_ENABLE_I18N typedef struct { /* Multibyte characters. */ wchar_t *mbchars; /* Collating symbols. */ # ifdef _LIBC int32_t *coll_syms; # endif /* Equivalence classes. */ # ifdef _LIBC int32_t *equiv_classes; # endif /* Range expressions. */ # ifdef _LIBC uint32_t *range_starts; uint32_t *range_ends; # else /* not _LIBC */ wchar_t *range_starts; wchar_t *range_ends; # endif /* not _LIBC */ /* Character classes. */ wctype_t *char_classes; /* If this character set is the non-matching list. */ unsigned int non_match : 1; /* # of multibyte characters. */ int nmbchars; /* # of collating symbols. */ int ncoll_syms; /* # of equivalence classes. */ int nequiv_classes; /* # of range expressions. */ int nranges; /* # of character classes. */ int nchar_classes; } re_charset_t; #endif /* RE_ENABLE_I18N */ typedef struct { union { unsigned char c; /* for CHARACTER */ re_bitset_ptr_t sbcset; /* for SIMPLE_BRACKET */ #ifdef RE_ENABLE_I18N re_charset_t *mbcset; /* for COMPLEX_BRACKET */ #endif /* RE_ENABLE_I18N */ int idx; /* for BACK_REF */ re_context_type ctx_type; /* for ANCHOR */ } opr; #if __GNUC__ >= 2 re_token_type_t type : 8; #else re_token_type_t type; #endif unsigned int constraint : 10; /* context constraint */ unsigned int duplicated : 1; unsigned int opt_subexp : 1; #ifdef RE_ENABLE_I18N unsigned int accept_mb : 1; /* These 2 bits can be moved into the union if needed (e.g. if running out of bits; move opr.c to opr.c.c and move the flags to opr.c.flags). */ unsigned int mb_partial : 1; #endif unsigned int word_char : 1; } re_token_t; #define IS_EPSILON_NODE(type) ((type) & EPSILON_BIT) struct re_string_t { /* Indicate the raw buffer which is the original string passed as an argument of regexec(), re_search(), etc.. */ const unsigned char *raw_mbs; /* Store the multibyte string. In case of "case insensitive mode" like REG_ICASE, upper cases of the string are stored, otherwise MBS points the same address that RAW_MBS points. */ unsigned char *mbs; #ifdef RE_ENABLE_I18N /* Store the wide character string which is corresponding to MBS. */ wint_t *wcs; int *offsets; mbstate_t cur_state; #endif /* Index in RAW_MBS. Each character mbs[i] corresponds to raw_mbs[raw_mbs_idx + i]. */ int raw_mbs_idx; /* The length of the valid characters in the buffers. */ int valid_len; /* The corresponding number of bytes in raw_mbs array. */ int valid_raw_len; /* The length of the buffers MBS and WCS. */ int bufs_len; /* The index in MBS, which is updated by re_string_fetch_byte. */ int cur_idx; /* length of RAW_MBS array. */ int raw_len; /* This is RAW_LEN - RAW_MBS_IDX + VALID_LEN - VALID_RAW_LEN. */ int len; /* End of the buffer may be shorter than its length in the cases such as re_match_2, re_search_2. Then, we use STOP for end of the buffer instead of LEN. */ int raw_stop; /* This is RAW_STOP - RAW_MBS_IDX adjusted through OFFSETS. */ int stop; /* The context of mbs[0]. We store the context independently, since the context of mbs[0] may be different from raw_mbs[0], which is the beginning of the input string. */ unsigned int tip_context; /* The translation passed as a part of an argument of re_compile_pattern. */ RE_TRANSLATE_TYPE trans; /* Copy of re_dfa_t's word_char. */ re_const_bitset_ptr_t word_char; /* 1 if REG_ICASE. */ unsigned char icase; unsigned char is_utf8; unsigned char map_notascii; unsigned char mbs_allocated; unsigned char offsets_needed; unsigned char newline_anchor; unsigned char word_ops_used; int mb_cur_max; }; typedef struct re_string_t re_string_t; struct re_dfa_t; typedef struct re_dfa_t re_dfa_t; #ifndef _LIBC # ifdef __i386__ # define internal_function __attribute ((regparm (3), stdcall)) # else # define internal_function # endif #endif #ifndef NOT_IN_libc static reg_errcode_t re_string_realloc_buffers (re_string_t *pstr, int new_buf_len) internal_function; # ifdef RE_ENABLE_I18N static void build_wcs_buffer (re_string_t *pstr) internal_function; static int build_wcs_upper_buffer (re_string_t *pstr) internal_function; # endif /* RE_ENABLE_I18N */ static void build_upper_buffer (re_string_t *pstr) internal_function; static void re_string_translate_buffer (re_string_t *pstr) internal_function; static unsigned int re_string_context_at (const re_string_t *input, int idx, int eflags) internal_function __attribute ((pure)); #endif #define re_string_peek_byte(pstr, offset) \ ((pstr)->mbs[(pstr)->cur_idx + offset]) #define re_string_fetch_byte(pstr) \ ((pstr)->mbs[(pstr)->cur_idx++]) #define re_string_first_byte(pstr, idx) \ ((idx) == (pstr)->valid_len || (pstr)->wcs[idx] != WEOF) #define re_string_is_single_byte_char(pstr, idx) \ ((pstr)->wcs[idx] != WEOF && ((pstr)->valid_len == (idx) + 1 \ || (pstr)->wcs[(idx) + 1] != WEOF)) #define re_string_eoi(pstr) ((pstr)->stop <= (pstr)->cur_idx) #define re_string_cur_idx(pstr) ((pstr)->cur_idx) #define re_string_get_buffer(pstr) ((pstr)->mbs) #define re_string_length(pstr) ((pstr)->len) #define re_string_byte_at(pstr,idx) ((pstr)->mbs[idx]) #define re_string_skip_bytes(pstr,idx) ((pstr)->cur_idx += (idx)) #define re_string_set_index(pstr,idx) ((pstr)->cur_idx = (idx)) #ifdef HAVE_ALLOCA_H #include #endif #ifndef _LIBC # if HAVE_ALLOCA /* The OS usually guarantees only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely allocate anything larger than 4096 bytes. Also care for the possibility of a few compiler-allocated temporary stack slots. */ # define __libc_use_alloca(n) ((n) < 4032) # else /* alloca is implemented with malloc, so just use malloc. */ # define __libc_use_alloca(n) 0 # endif #endif #define re_malloc(t,n) ((t *) malloc ((n) * sizeof (t))) #define re_realloc(p,t,n) ((t *) realloc (p, (n) * sizeof (t))) #define re_free(p) free (p) struct bin_tree_t { struct bin_tree_t *parent; struct bin_tree_t *left; struct bin_tree_t *right; struct bin_tree_t *first; struct bin_tree_t *next; re_token_t token; /* `node_idx' is the index in dfa->nodes, if `type' == 0. Otherwise `type' indicate the type of this node. */ int node_idx; }; typedef struct bin_tree_t bin_tree_t; #define BIN_TREE_STORAGE_SIZE \ ((1024 - sizeof (void *)) / sizeof (bin_tree_t)) struct bin_tree_storage_t { struct bin_tree_storage_t *next; bin_tree_t data[BIN_TREE_STORAGE_SIZE]; }; typedef struct bin_tree_storage_t bin_tree_storage_t; #define CONTEXT_WORD 1 #define CONTEXT_NEWLINE (CONTEXT_WORD << 1) #define CONTEXT_BEGBUF (CONTEXT_NEWLINE << 1) #define CONTEXT_ENDBUF (CONTEXT_BEGBUF << 1) #define IS_WORD_CONTEXT(c) ((c) & CONTEXT_WORD) #define IS_NEWLINE_CONTEXT(c) ((c) & CONTEXT_NEWLINE) #define IS_BEGBUF_CONTEXT(c) ((c) & CONTEXT_BEGBUF) #define IS_ENDBUF_CONTEXT(c) ((c) & CONTEXT_ENDBUF) #define IS_ORDINARY_CONTEXT(c) ((c) == 0) #define IS_WORD_CHAR(ch) (isalnum (ch) || (ch) == '_') #define IS_NEWLINE(ch) ((ch) == NEWLINE_CHAR) #define IS_WIDE_WORD_CHAR(ch) (iswalnum (ch) || (ch) == L'_') #define IS_WIDE_NEWLINE(ch) ((ch) == WIDE_NEWLINE_CHAR) #define NOT_SATISFY_PREV_CONSTRAINT(constraint,context) \ ((((constraint) & PREV_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \ || ((constraint & PREV_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \ || ((constraint & PREV_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context))\ || ((constraint & PREV_BEGBUF_CONSTRAINT) && !IS_BEGBUF_CONTEXT (context))) #define NOT_SATISFY_NEXT_CONSTRAINT(constraint,context) \ ((((constraint) & NEXT_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \ || (((constraint) & NEXT_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \ || (((constraint) & NEXT_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context)) \ || (((constraint) & NEXT_ENDBUF_CONSTRAINT) && !IS_ENDBUF_CONTEXT (context))) struct re_dfastate_t { unsigned int hash; re_node_set nodes; re_node_set non_eps_nodes; re_node_set inveclosure; re_node_set *entrance_nodes; struct re_dfastate_t **trtable, **word_trtable; unsigned int context : 4; unsigned int halt : 1; /* If this state can accept `multi byte'. Note that we refer to multibyte characters, and multi character collating elements as `multi byte'. */ unsigned int accept_mb : 1; /* If this state has backreference node(s). */ unsigned int has_backref : 1; unsigned int has_constraint : 1; }; typedef struct re_dfastate_t re_dfastate_t; struct re_state_table_entry { int num; int alloc; re_dfastate_t **array; }; /* Array type used in re_sub_match_last_t and re_sub_match_top_t. */ typedef struct { int next_idx; int alloc; re_dfastate_t **array; } state_array_t; /* Store information about the node NODE whose type is OP_CLOSE_SUBEXP. */ typedef struct { int node; int str_idx; /* The position NODE match at. */ state_array_t path; } re_sub_match_last_t; /* Store information about the node NODE whose type is OP_OPEN_SUBEXP. And information about the node, whose type is OP_CLOSE_SUBEXP, corresponding to NODE is stored in LASTS. */ typedef struct { int str_idx; int node; state_array_t *path; int alasts; /* Allocation size of LASTS. */ int nlasts; /* The number of LASTS. */ re_sub_match_last_t **lasts; } re_sub_match_top_t; struct re_backref_cache_entry { int node; int str_idx; int subexp_from; int subexp_to; char more; char unused; unsigned short int eps_reachable_subexps_map; }; typedef struct { /* The string object corresponding to the input string. */ re_string_t input; #if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) const re_dfa_t *const dfa; #else const re_dfa_t *dfa; #endif /* EFLAGS of the argument of regexec. */ int eflags; /* Where the matching ends. */ int match_last; int last_node; /* The state log used by the matcher. */ re_dfastate_t **state_log; int state_log_top; /* Back reference cache. */ int nbkref_ents; int abkref_ents; struct re_backref_cache_entry *bkref_ents; int max_mb_elem_len; int nsub_tops; int asub_tops; re_sub_match_top_t **sub_tops; } re_match_context_t; typedef struct { re_dfastate_t **sifted_states; re_dfastate_t **limited_states; int last_node; int last_str_idx; re_node_set limits; } re_sift_context_t; struct re_fail_stack_ent_t { int idx; int node; regmatch_t *regs; re_node_set eps_via_nodes; }; struct re_fail_stack_t { int num; int alloc; struct re_fail_stack_ent_t *stack; }; struct re_dfa_t { re_token_t *nodes; size_t nodes_alloc; size_t nodes_len; int *nexts; int *org_indices; re_node_set *edests; re_node_set *eclosures; re_node_set *inveclosures; struct re_state_table_entry *state_table; re_dfastate_t *init_state; re_dfastate_t *init_state_word; re_dfastate_t *init_state_nl; re_dfastate_t *init_state_begbuf; bin_tree_t *str_tree; bin_tree_storage_t *str_tree_storage; re_bitset_ptr_t sb_char; int str_tree_storage_idx; /* number of subexpressions `re_nsub' is in regex_t. */ unsigned int state_hash_mask; int init_node; int nbackref; /* The number of backreference in this dfa. */ /* Bitmap expressing which backreference is used. */ bitset_word_t used_bkref_map; bitset_word_t completed_bkref_map; unsigned int has_plural_match : 1; /* If this dfa has "multibyte node", which is a backreference or a node which can accept multibyte character or multi character collating element. */ unsigned int has_mb_node : 1; unsigned int is_utf8 : 1; unsigned int map_notascii : 1; unsigned int word_ops_used : 1; int mb_cur_max; bitset_t word_char; reg_syntax_t syntax; int *subexp_map; #ifdef DEBUG char* re_str; #endif __libc_lock_define (, lock) }; #define re_node_set_init_empty(set) memset (set, '\0', sizeof (re_node_set)) #define re_node_set_remove(set,id) \ (re_node_set_remove_at (set, re_node_set_contains (set, id) - 1)) #define re_node_set_empty(p) ((p)->nelem = 0) #define re_node_set_free(set) re_free ((set)->elems) typedef enum { SB_CHAR, MB_CHAR, EQUIV_CLASS, COLL_SYM, CHAR_CLASS } bracket_elem_type; typedef struct { bracket_elem_type type; union { unsigned char ch; unsigned char *name; wchar_t wch; } opr; } bracket_elem_t; /* Inline functions for bitset operation. */ static inline void bitset_not (bitset_t set) { int bitset_i; for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i) set[bitset_i] = ~set[bitset_i]; } static inline void bitset_merge (bitset_t dest, const bitset_t src) { int bitset_i; for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i) dest[bitset_i] |= src[bitset_i]; } static inline void bitset_mask (bitset_t dest, const bitset_t src) { int bitset_i; for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i) dest[bitset_i] &= src[bitset_i]; } #ifdef RE_ENABLE_I18N /* Inline functions for re_string. */ static inline int internal_function __attribute ((pure)) re_string_char_size_at (const re_string_t *pstr, int idx) { int byte_idx; if (pstr->mb_cur_max == 1) return 1; for (byte_idx = 1; idx + byte_idx < pstr->valid_len; ++byte_idx) if (pstr->wcs[idx + byte_idx] != WEOF) break; return byte_idx; } static inline wint_t internal_function __attribute ((pure)) re_string_wchar_at (const re_string_t *pstr, int idx) { if (pstr->mb_cur_max == 1) return (wint_t) pstr->mbs[idx]; return (wint_t) pstr->wcs[idx]; } # ifndef NOT_IN_libc static int internal_function __attribute ((pure)) re_string_elem_size_at (const re_string_t *pstr, int idx) { # ifdef _LIBC const unsigned char *p, *extra; const int32_t *table, *indirect; int32_t tmp; # include uint_fast32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules != 0) { table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB); p = pstr->mbs + idx; tmp = findidx (&p); return p - pstr->mbs - idx; } else # endif /* _LIBC */ return 1; } # endif #endif /* RE_ENABLE_I18N */ #endif /* _REGEX_INTERNAL_H */ direwolf-1.5+dfsg/regex/regexec.c000066400000000000000000003736561347750676600170620ustar00rootroot00000000000000 /* this is very old and has massive numbers of compiler warnings. */ /* Maybe try upgrading to a newer version such as */ /* https://fossies.org/dox/glibc-2.24/regexec_8c_source.html */ /* Extended regular expression matching and search library. Copyright (C) 2002, 2003, 2004, 2005, 2007 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C 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. The GNU C 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 the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* Added 12/2016 to remove warning: */ /* incompatible implicit declaration of built-in function 'alloca' */ #if __WIN32__ #include #else #include #endif static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags, int n) internal_function; static void match_ctx_clean (re_match_context_t *mctx) internal_function; static void match_ctx_free (re_match_context_t *cache) internal_function; static reg_errcode_t match_ctx_add_entry (re_match_context_t *cache, int node, int str_idx, int from, int to) internal_function; static int search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx) internal_function; static reg_errcode_t match_ctx_add_subtop (re_match_context_t *mctx, int node, int str_idx) internal_function; static re_sub_match_last_t * match_ctx_add_sublast (re_sub_match_top_t *subtop, int node, int str_idx) internal_function; static void sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts, re_dfastate_t **limited_sts, int last_node, int last_str_idx) internal_function; static reg_errcode_t re_search_internal (const regex_t *preg, const char *string, int length, int start, int range, int stop, size_t nmatch, regmatch_t pmatch[], int eflags) internal_function; static int re_search_2_stub (struct re_pattern_buffer *bufp, const char *string1, int length1, const char *string2, int length2, int start, int range, struct re_registers *regs, int stop, int ret_len) internal_function; static int re_search_stub (struct re_pattern_buffer *bufp, const char *string, int length, int start, int range, int stop, struct re_registers *regs, int ret_len) internal_function; static unsigned re_copy_regs (struct re_registers *regs, regmatch_t *pmatch, int nregs, int regs_allocated) internal_function; static reg_errcode_t prune_impossible_nodes (re_match_context_t *mctx) internal_function; static int check_matching (re_match_context_t *mctx, int fl_longest_match, int *p_match_first) internal_function; static int check_halt_state_context (const re_match_context_t *mctx, const re_dfastate_t *state, int idx) internal_function; static void update_regs (const re_dfa_t *dfa, regmatch_t *pmatch, regmatch_t *prev_idx_match, int cur_node, int cur_idx, int nmatch) internal_function; static reg_errcode_t push_fail_stack (struct re_fail_stack_t *fs, int str_idx, int dest_node, int nregs, regmatch_t *regs, re_node_set *eps_via_nodes) internal_function; static reg_errcode_t set_regs (const regex_t *preg, const re_match_context_t *mctx, size_t nmatch, regmatch_t *pmatch, int fl_backtrack) internal_function; static reg_errcode_t free_fail_stack_return (struct re_fail_stack_t *fs) internal_function; #ifdef RE_ENABLE_I18N static int sift_states_iter_mb (const re_match_context_t *mctx, re_sift_context_t *sctx, int node_idx, int str_idx, int max_str_idx) internal_function; #endif /* RE_ENABLE_I18N */ static reg_errcode_t sift_states_backward (const re_match_context_t *mctx, re_sift_context_t *sctx) internal_function; static reg_errcode_t build_sifted_states (const re_match_context_t *mctx, re_sift_context_t *sctx, int str_idx, re_node_set *cur_dest) internal_function; static reg_errcode_t update_cur_sifted_state (const re_match_context_t *mctx, re_sift_context_t *sctx, int str_idx, re_node_set *dest_nodes) internal_function; static reg_errcode_t add_epsilon_src_nodes (const re_dfa_t *dfa, re_node_set *dest_nodes, const re_node_set *candidates) internal_function; static int check_dst_limits (const re_match_context_t *mctx, re_node_set *limits, int dst_node, int dst_idx, int src_node, int src_idx) internal_function; static int check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, int boundaries, int subexp_idx, int from_node, int bkref_idx) internal_function; static int check_dst_limits_calc_pos (const re_match_context_t *mctx, int limit, int subexp_idx, int node, int str_idx, int bkref_idx) internal_function; static reg_errcode_t check_subexp_limits (const re_dfa_t *dfa, re_node_set *dest_nodes, const re_node_set *candidates, re_node_set *limits, struct re_backref_cache_entry *bkref_ents, int str_idx) internal_function; static reg_errcode_t sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx, int str_idx, const re_node_set *candidates) internal_function; static reg_errcode_t merge_state_array (const re_dfa_t *dfa, re_dfastate_t **dst, re_dfastate_t **src, int num) internal_function; static re_dfastate_t *find_recover_state (reg_errcode_t *err, re_match_context_t *mctx) internal_function; static re_dfastate_t *transit_state (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *state) internal_function; static re_dfastate_t *merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *next_state) internal_function; static reg_errcode_t check_subexp_matching_top (re_match_context_t *mctx, re_node_set *cur_nodes, int str_idx) internal_function; #if 0 static re_dfastate_t *transit_state_sb (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *pstate) internal_function; #endif #ifdef RE_ENABLE_I18N static reg_errcode_t transit_state_mb (re_match_context_t *mctx, re_dfastate_t *pstate) internal_function; #endif /* RE_ENABLE_I18N */ static reg_errcode_t transit_state_bkref (re_match_context_t *mctx, const re_node_set *nodes) internal_function; static reg_errcode_t get_subexp (re_match_context_t *mctx, int bkref_node, int bkref_str_idx) internal_function; static reg_errcode_t get_subexp_sub (re_match_context_t *mctx, const re_sub_match_top_t *sub_top, re_sub_match_last_t *sub_last, int bkref_node, int bkref_str) internal_function; static int find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes, int subexp_idx, int type) internal_function; static reg_errcode_t check_arrival (re_match_context_t *mctx, state_array_t *path, int top_node, int top_str, int last_node, int last_str, int type) internal_function; static reg_errcode_t check_arrival_add_next_nodes (re_match_context_t *mctx, int str_idx, re_node_set *cur_nodes, re_node_set *next_nodes) internal_function; static reg_errcode_t check_arrival_expand_ecl (const re_dfa_t *dfa, re_node_set *cur_nodes, int ex_subexp, int type) internal_function; static reg_errcode_t check_arrival_expand_ecl_sub (const re_dfa_t *dfa, re_node_set *dst_nodes, int target, int ex_subexp, int type) internal_function; static reg_errcode_t expand_bkref_cache (re_match_context_t *mctx, re_node_set *cur_nodes, int cur_str, int subexp_num, int type) internal_function; static int build_trtable (const re_dfa_t *dfa, re_dfastate_t *state) internal_function; #ifdef RE_ENABLE_I18N static int check_node_accept_bytes (const re_dfa_t *dfa, int node_idx, const re_string_t *input, int idx) internal_function; # ifdef _LIBC static unsigned int find_collation_sequence_value (const unsigned char *mbs, size_t name_len) internal_function; # endif /* _LIBC */ #endif /* RE_ENABLE_I18N */ static int group_nodes_into_DFAstates (const re_dfa_t *dfa, const re_dfastate_t *state, re_node_set *states_node, bitset_t *states_ch) internal_function; static int check_node_accept (const re_match_context_t *mctx, const re_token_t *node, int idx) internal_function; static reg_errcode_t extend_buffers (re_match_context_t *mctx) internal_function; /* Entry point for POSIX code. */ /* regexec searches for a given pattern, specified by PREG, in the string STRING. If NMATCH is zero or REG_NOSUB was set in the cflags argument to `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at least NMATCH elements, and we set them to the offsets of the corresponding matched substrings. EFLAGS specifies `execution flags' which affect matching: if REG_NOTBOL is set, then ^ does not match at the beginning of the string; if REG_NOTEOL is set, then $ does not match at the end. We return 0 if we find a match and REG_NOMATCH if not. */ int regexec (preg, string, nmatch, pmatch, eflags) const regex_t *__restrict preg; const char *__restrict string; size_t nmatch; regmatch_t pmatch[]; int eflags; { reg_errcode_t err; int start, length; re_dfa_t *dfa = (re_dfa_t *) preg->buffer; (void)dfa; if (eflags & ~(REG_NOTBOL | REG_NOTEOL | REG_STARTEND)) return REG_BADPAT; if (eflags & REG_STARTEND) { start = pmatch[0].rm_so; length = pmatch[0].rm_eo; } else { start = 0; length = strlen (string); } __libc_lock_lock (dfa->lock); if (preg->no_sub) err = re_search_internal (preg, string, length, start, length - start, length, 0, NULL, eflags); else err = re_search_internal (preg, string, length, start, length - start, length, nmatch, pmatch, eflags); __libc_lock_unlock (dfa->lock); return err != REG_NOERROR; } #ifdef _LIBC # include versioned_symbol (libc, __regexec, regexec, GLIBC_2_3_4); # if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4) __typeof__ (__regexec) __compat_regexec; int attribute_compat_text_section __compat_regexec (const regex_t *__restrict preg, const char *__restrict string, size_t nmatch, regmatch_t pmatch[], int eflags) { return regexec (preg, string, nmatch, pmatch, eflags & (REG_NOTBOL | REG_NOTEOL)); } compat_symbol (libc, __compat_regexec, regexec, GLIBC_2_0); # endif #endif /* Entry points for GNU code. */ /* re_match, re_search, re_match_2, re_search_2 The former two functions operate on STRING with length LENGTH, while the later two operate on concatenation of STRING1 and STRING2 with lengths LENGTH1 and LENGTH2, respectively. re_match() matches the compiled pattern in BUFP against the string, starting at index START. re_search() first tries matching at index START, then it tries to match starting from index START + 1, and so on. The last start position tried is START + RANGE. (Thus RANGE = 0 forces re_search to operate the same way as re_match().) The parameter STOP of re_{match,search}_2 specifies that no match exceeding the first STOP characters of the concatenation of the strings should be concerned. If REGS is not NULL, and BUFP->no_sub is not set, the offsets of the match and all groups is stroed in REGS. (For the "_2" variants, the offsets are computed relative to the concatenation, not relative to the individual strings.) On success, re_match* functions return the length of the match, re_search* return the position of the start of the match. Return value -1 means no match was found and -2 indicates an internal error. */ int re_match (bufp, string, length, start, regs) struct re_pattern_buffer *bufp; const char *string; int length, start; struct re_registers *regs; { return re_search_stub (bufp, string, length, start, 0, length, regs, 1); } #ifdef _LIBC weak_alias (__re_match, re_match) #endif int re_search (bufp, string, length, start, range, regs) struct re_pattern_buffer *bufp; const char *string; int length, start, range; struct re_registers *regs; { return re_search_stub (bufp, string, length, start, range, length, regs, 0); } #ifdef _LIBC weak_alias (__re_search, re_search) #endif int re_match_2 (bufp, string1, length1, string2, length2, start, regs, stop) struct re_pattern_buffer *bufp; const char *string1, *string2; int length1, length2, start, stop; struct re_registers *regs; { return re_search_2_stub (bufp, string1, length1, string2, length2, start, 0, regs, stop, 1); } #ifdef _LIBC weak_alias (__re_match_2, re_match_2) #endif int re_search_2 (bufp, string1, length1, string2, length2, start, range, regs, stop) struct re_pattern_buffer *bufp; const char *string1, *string2; int length1, length2, start, range, stop; struct re_registers *regs; { return re_search_2_stub (bufp, string1, length1, string2, length2, start, range, regs, stop, 0); } #ifdef _LIBC weak_alias (__re_search_2, re_search_2) #endif static int re_search_2_stub (bufp, string1, length1, string2, length2, start, range, regs, stop, ret_len) struct re_pattern_buffer *bufp; const char *string1, *string2; int length1, length2, start, range, stop, ret_len; struct re_registers *regs; { const char *str; int rval; int len = length1 + length2; int free_str = 0; if (BE (length1 < 0 || length2 < 0 || stop < 0, 0)) return -2; /* Concatenate the strings. */ if (length2 > 0) if (length1 > 0) { char *s = re_malloc (char, len); if (BE (s == NULL, 0)) return -2; #ifdef _LIBC memcpy (__mempcpy (s, string1, length1), string2, length2); #else memcpy (s, string1, length1); memcpy (s + length1, string2, length2); #endif str = s; free_str = 1; } else str = string2; else str = string1; rval = re_search_stub (bufp, str, len, start, range, stop, regs, ret_len); if (free_str) re_free ((char *) str); return rval; } /* The parameters have the same meaning as those of re_search. Additional parameters: If RET_LEN is nonzero the length of the match is returned (re_match style); otherwise the position of the match is returned. */ static int re_search_stub (bufp, string, length, start, range, stop, regs, ret_len) struct re_pattern_buffer *bufp; const char *string; int length, start, range, stop, ret_len; struct re_registers *regs; { reg_errcode_t result; regmatch_t *pmatch; int nregs, rval; int eflags = 0; re_dfa_t *dfa = (re_dfa_t *) bufp->buffer; (void)dfa; /* Check for out-of-range. */ if (BE (start < 0 || start > length, 0)) return -1; if (BE (start + range > length, 0)) range = length - start; else if (BE (start + range < 0, 0)) range = -start; __libc_lock_lock (dfa->lock); eflags |= (bufp->not_bol) ? REG_NOTBOL : 0; eflags |= (bufp->not_eol) ? REG_NOTEOL : 0; /* Compile fastmap if we haven't yet. */ if (range > 0 && bufp->fastmap != NULL && !bufp->fastmap_accurate) re_compile_fastmap (bufp); if (BE (bufp->no_sub, 0)) regs = NULL; /* We need at least 1 register. */ if (regs == NULL) nregs = 1; else if (BE (bufp->regs_allocated == REGS_FIXED && regs->num_regs < bufp->re_nsub + 1, 0)) { nregs = regs->num_regs; if (BE (nregs < 1, 0)) { /* Nothing can be copied to regs. */ regs = NULL; nregs = 1; } } else nregs = bufp->re_nsub + 1; pmatch = re_malloc (regmatch_t, nregs); if (BE (pmatch == NULL, 0)) { rval = -2; goto out; } result = re_search_internal (bufp, string, length, start, range, stop, nregs, pmatch, eflags); rval = 0; /* I hope we needn't fill ther regs with -1's when no match was found. */ if (result != REG_NOERROR) rval = -1; else if (regs != NULL) { /* If caller wants register contents data back, copy them. */ bufp->regs_allocated = re_copy_regs (regs, pmatch, nregs, bufp->regs_allocated); if (BE (bufp->regs_allocated == REGS_UNALLOCATED, 0)) rval = -2; } if (BE (rval == 0, 1)) { if (ret_len) { assert (pmatch[0].rm_so == start); rval = pmatch[0].rm_eo - start; } else rval = pmatch[0].rm_so; } re_free (pmatch); out: __libc_lock_unlock (dfa->lock); return rval; } static unsigned re_copy_regs (regs, pmatch, nregs, regs_allocated) struct re_registers *regs; regmatch_t *pmatch; int nregs, regs_allocated; { int rval = REGS_REALLOCATE; int i; int need_regs = nregs + 1; /* We need one extra element beyond `num_regs' for the `-1' marker GNU code uses. */ /* Have the register data arrays been allocated? */ if (regs_allocated == REGS_UNALLOCATED) { /* No. So allocate them with malloc. */ regs->start = re_malloc (regoff_t, need_regs); regs->end = re_malloc (regoff_t, need_regs); if (BE (regs->start == NULL, 0) || BE (regs->end == NULL, 0)) return REGS_UNALLOCATED; regs->num_regs = need_regs; } else if (regs_allocated == REGS_REALLOCATE) { /* Yes. If we need more elements than were already allocated, reallocate them. If we need fewer, just leave it alone. */ if (BE (need_regs > regs->num_regs, 0)) { regoff_t *new_start = re_realloc (regs->start, regoff_t, need_regs); regoff_t *new_end = re_realloc (regs->end, regoff_t, need_regs); if (BE (new_start == NULL, 0) || BE (new_end == NULL, 0)) return REGS_UNALLOCATED; regs->start = new_start; regs->end = new_end; regs->num_regs = need_regs; } } else { assert (regs_allocated == REGS_FIXED); /* This function may not be called with REGS_FIXED and nregs too big. */ assert (regs->num_regs >= nregs); rval = REGS_FIXED; } /* Copy the regs. */ for (i = 0; i < nregs; ++i) { regs->start[i] = pmatch[i].rm_so; regs->end[i] = pmatch[i].rm_eo; } for ( ; i < regs->num_regs; ++i) regs->start[i] = regs->end[i] = -1; return rval; } /* Set REGS to hold NUM_REGS registers, storing them in STARTS and ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use this memory for recording register information. STARTS and ENDS must be allocated using the malloc library routine, and must each be at least NUM_REGS * sizeof (regoff_t) bytes long. If NUM_REGS == 0, then subsequent matches should allocate their own register data. Unless this function is called, the first search or match using PATTERN_BUFFER will allocate its own register data, without freeing the old data. */ void re_set_registers (bufp, regs, num_regs, starts, ends) struct re_pattern_buffer *bufp; struct re_registers *regs; unsigned num_regs; regoff_t *starts, *ends; { if (num_regs) { bufp->regs_allocated = REGS_REALLOCATE; regs->num_regs = num_regs; regs->start = starts; regs->end = ends; } else { bufp->regs_allocated = REGS_UNALLOCATED; regs->num_regs = 0; regs->start = regs->end = (regoff_t *) 0; } } #ifdef _LIBC weak_alias (__re_set_registers, re_set_registers) #endif /* Entry points compatible with 4.2 BSD regex library. We don't define them unless specifically requested. */ #if defined _REGEX_RE_COMP || defined _LIBC int # ifdef _LIBC weak_function # endif re_exec (s) const char *s; { return 0 == regexec (&re_comp_buf, s, 0, NULL, 0); } #endif /* _REGEX_RE_COMP */ /* Internal entry point. */ /* Searches for a compiled pattern PREG in the string STRING, whose length is LENGTH. NMATCH, PMATCH, and EFLAGS have the same mingings with regexec. START, and RANGE have the same meanings with re_search. Return REG_NOERROR if we find a match, and REG_NOMATCH if not, otherwise return the error code. Note: We assume front end functions already check ranges. (START + RANGE >= 0 && START + RANGE <= LENGTH) */ static reg_errcode_t re_search_internal (preg, string, length, start, range, stop, nmatch, pmatch, eflags) const regex_t *preg; const char *string; int length, start, range, stop, eflags; size_t nmatch; regmatch_t pmatch[]; { reg_errcode_t err; const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer; int left_lim, right_lim, incr; int fl_longest_match, match_first, match_kind, match_last = -1; int extra_nmatch; int sb, ch; #if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) re_match_context_t mctx = { .dfa = dfa }; #else re_match_context_t mctx; #endif char *fastmap = (preg->fastmap != NULL && preg->fastmap_accurate && range && !preg->can_be_null) ? preg->fastmap : NULL; RE_TRANSLATE_TYPE t = preg->translate; #if !(defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)) memset (&mctx, '\0', sizeof (re_match_context_t)); mctx.dfa = dfa; #endif extra_nmatch = (nmatch > preg->re_nsub) ? nmatch - (preg->re_nsub + 1) : 0; nmatch -= extra_nmatch; /* Check if the DFA haven't been compiled. */ if (BE (preg->used == 0 || dfa->init_state == NULL || dfa->init_state_word == NULL || dfa->init_state_nl == NULL || dfa->init_state_begbuf == NULL, 0)) return REG_NOMATCH; #ifdef DEBUG /* We assume front-end functions already check them. */ assert (start + range >= 0 && start + range <= length); #endif /* If initial states with non-begbuf contexts have no elements, the regex must be anchored. If preg->newline_anchor is set, we'll never use init_state_nl, so do not check it. */ if (dfa->init_state->nodes.nelem == 0 && dfa->init_state_word->nodes.nelem == 0 && (dfa->init_state_nl->nodes.nelem == 0 || !preg->newline_anchor)) { if (start != 0 && start + range != 0) return REG_NOMATCH; start = range = 0; } /* We must check the longest matching, if nmatch > 0. */ fl_longest_match = (nmatch != 0 || dfa->nbackref); err = re_string_allocate (&mctx.input, string, length, dfa->nodes_len + 1, preg->translate, preg->syntax & RE_ICASE, dfa); if (BE (err != REG_NOERROR, 0)) goto free_return; mctx.input.stop = stop; mctx.input.raw_stop = stop; mctx.input.newline_anchor = preg->newline_anchor; err = match_ctx_init (&mctx, eflags, dfa->nbackref * 2); if (BE (err != REG_NOERROR, 0)) goto free_return; /* We will log all the DFA states through which the dfa pass, if nmatch > 1, or this dfa has "multibyte node", which is a back-reference or a node which can accept multibyte character or multi character collating element. */ if (nmatch > 1 || dfa->has_mb_node) { mctx.state_log = re_malloc (re_dfastate_t *, mctx.input.bufs_len + 1); if (BE (mctx.state_log == NULL, 0)) { err = REG_ESPACE; goto free_return; } } else mctx.state_log = NULL; match_first = start; mctx.input.tip_context = (eflags & REG_NOTBOL) ? CONTEXT_BEGBUF : CONTEXT_NEWLINE | CONTEXT_BEGBUF; /* Check incrementally whether of not the input string match. */ incr = (range < 0) ? -1 : 1; left_lim = (range < 0) ? start + range : start; right_lim = (range < 0) ? start : start + range; sb = dfa->mb_cur_max == 1; match_kind = (fastmap ? ((sb || !(preg->syntax & RE_ICASE || t) ? 4 : 0) | (range >= 0 ? 2 : 0) | (t != NULL ? 1 : 0)) : 8); for (;; match_first += incr) { err = REG_NOMATCH; if (match_first < left_lim || right_lim < match_first) goto free_return; /* Advance as rapidly as possible through the string, until we find a plausible place to start matching. This may be done with varying efficiency, so there are various possibilities: only the most common of them are specialized, in order to save on code size. We use a switch statement for speed. */ switch (match_kind) { case 8: /* No fastmap. */ break; case 7: /* Fastmap with single-byte translation, match forward. */ while (BE (match_first < right_lim, 1) && !fastmap[t[(unsigned char) string[match_first]]]) ++match_first; goto forward_match_found_start_or_reached_end; case 6: /* Fastmap without translation, match forward. */ while (BE (match_first < right_lim, 1) && !fastmap[(unsigned char) string[match_first]]) ++match_first; forward_match_found_start_or_reached_end: if (BE (match_first == right_lim, 0)) { ch = match_first >= length ? 0 : (unsigned char) string[match_first]; if (!fastmap[t ? t[ch] : ch]) goto free_return; } break; case 4: case 5: /* Fastmap without multi-byte translation, match backwards. */ while (match_first >= left_lim) { ch = match_first >= length ? 0 : (unsigned char) string[match_first]; if (fastmap[t ? t[ch] : ch]) break; --match_first; } if (match_first < left_lim) goto free_return; break; default: /* In this case, we can't determine easily the current byte, since it might be a component byte of a multibyte character. Then we use the constructed buffer instead. */ for (;;) { /* If MATCH_FIRST is out of the valid range, reconstruct the buffers. */ unsigned int offset = match_first - mctx.input.raw_mbs_idx; if (BE (offset >= (unsigned int) mctx.input.valid_raw_len, 0)) { err = re_string_reconstruct (&mctx.input, match_first, eflags); if (BE (err != REG_NOERROR, 0)) goto free_return; offset = match_first - mctx.input.raw_mbs_idx; } /* If MATCH_FIRST is out of the buffer, leave it as '\0'. Note that MATCH_FIRST must not be smaller than 0. */ ch = (match_first >= length ? 0 : re_string_byte_at (&mctx.input, offset)); if (fastmap[ch]) break; match_first += incr; if (match_first < left_lim || match_first > right_lim) { err = REG_NOMATCH; goto free_return; } } break; } /* Reconstruct the buffers so that the matcher can assume that the matching starts from the beginning of the buffer. */ err = re_string_reconstruct (&mctx.input, match_first, eflags); if (BE (err != REG_NOERROR, 0)) goto free_return; #ifdef RE_ENABLE_I18N /* Don't consider this char as a possible match start if it part, yet isn't the head, of a multibyte character. */ if (!sb && !re_string_first_byte (&mctx.input, 0)) continue; #endif /* It seems to be appropriate one, then use the matcher. */ /* We assume that the matching starts from 0. */ mctx.state_log_top = mctx.nbkref_ents = mctx.max_mb_elem_len = 0; match_last = check_matching (&mctx, fl_longest_match, range >= 0 ? &match_first : NULL); if (match_last != -1) { if (BE (match_last == -2, 0)) { err = REG_ESPACE; goto free_return; } else { mctx.match_last = match_last; if ((!preg->no_sub && nmatch > 1) || dfa->nbackref) { re_dfastate_t *pstate = mctx.state_log[match_last]; mctx.last_node = check_halt_state_context (&mctx, pstate, match_last); } if ((!preg->no_sub && nmatch > 1 && dfa->has_plural_match) || dfa->nbackref) { err = prune_impossible_nodes (&mctx); if (err == REG_NOERROR) break; if (BE (err != REG_NOMATCH, 0)) goto free_return; match_last = -1; } else break; /* We found a match. */ } } match_ctx_clean (&mctx); } #ifdef DEBUG assert (match_last != -1); assert (err == REG_NOERROR); #endif /* Set pmatch[] if we need. */ if (nmatch > 0) { int reg_idx; /* Initialize registers. */ for (reg_idx = 1; reg_idx < nmatch; ++reg_idx) pmatch[reg_idx].rm_so = pmatch[reg_idx].rm_eo = -1; /* Set the points where matching start/end. */ pmatch[0].rm_so = 0; pmatch[0].rm_eo = mctx.match_last; if (!preg->no_sub && nmatch > 1) { err = set_regs (preg, &mctx, nmatch, pmatch, dfa->has_plural_match && dfa->nbackref > 0); if (BE (err != REG_NOERROR, 0)) goto free_return; } /* At last, add the offset to the each registers, since we slided the buffers so that we could assume that the matching starts from 0. */ for (reg_idx = 0; reg_idx < nmatch; ++reg_idx) if (pmatch[reg_idx].rm_so != -1) { #ifdef RE_ENABLE_I18N if (BE (mctx.input.offsets_needed != 0, 0)) { pmatch[reg_idx].rm_so = (pmatch[reg_idx].rm_so == mctx.input.valid_len ? mctx.input.valid_raw_len : mctx.input.offsets[pmatch[reg_idx].rm_so]); pmatch[reg_idx].rm_eo = (pmatch[reg_idx].rm_eo == mctx.input.valid_len ? mctx.input.valid_raw_len : mctx.input.offsets[pmatch[reg_idx].rm_eo]); } #else assert (mctx.input.offsets_needed == 0); #endif pmatch[reg_idx].rm_so += match_first; pmatch[reg_idx].rm_eo += match_first; } for (reg_idx = 0; reg_idx < extra_nmatch; ++reg_idx) { pmatch[nmatch + reg_idx].rm_so = -1; pmatch[nmatch + reg_idx].rm_eo = -1; } if (dfa->subexp_map) for (reg_idx = 0; reg_idx + 1 < nmatch; reg_idx++) if (dfa->subexp_map[reg_idx] != reg_idx) { pmatch[reg_idx + 1].rm_so = pmatch[dfa->subexp_map[reg_idx] + 1].rm_so; pmatch[reg_idx + 1].rm_eo = pmatch[dfa->subexp_map[reg_idx] + 1].rm_eo; } } free_return: re_free (mctx.state_log); if (dfa->nbackref) match_ctx_free (&mctx); re_string_destruct (&mctx.input); return err; } static reg_errcode_t prune_impossible_nodes (mctx) re_match_context_t *mctx; { const re_dfa_t *const dfa = mctx->dfa; int halt_node, match_last; reg_errcode_t ret; re_dfastate_t **sifted_states; re_dfastate_t **lim_states = NULL; re_sift_context_t sctx; #ifdef DEBUG assert (mctx->state_log != NULL); #endif match_last = mctx->match_last; halt_node = mctx->last_node; sifted_states = re_malloc (re_dfastate_t *, match_last + 1); if (BE (sifted_states == NULL, 0)) { ret = REG_ESPACE; goto free_return; } if (dfa->nbackref) { lim_states = re_malloc (re_dfastate_t *, match_last + 1); if (BE (lim_states == NULL, 0)) { ret = REG_ESPACE; goto free_return; } while (1) { memset (lim_states, '\0', sizeof (re_dfastate_t *) * (match_last + 1)); sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last); ret = sift_states_backward (mctx, &sctx); re_node_set_free (&sctx.limits); if (BE (ret != REG_NOERROR, 0)) goto free_return; if (sifted_states[0] != NULL || lim_states[0] != NULL) break; do { --match_last; if (match_last < 0) { ret = REG_NOMATCH; goto free_return; } } while (mctx->state_log[match_last] == NULL || !mctx->state_log[match_last]->halt); halt_node = check_halt_state_context (mctx, mctx->state_log[match_last], match_last); } ret = merge_state_array (dfa, sifted_states, lim_states, match_last + 1); re_free (lim_states); lim_states = NULL; if (BE (ret != REG_NOERROR, 0)) goto free_return; } else { sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last); ret = sift_states_backward (mctx, &sctx); re_node_set_free (&sctx.limits); if (BE (ret != REG_NOERROR, 0)) goto free_return; } re_free (mctx->state_log); mctx->state_log = sifted_states; sifted_states = NULL; mctx->last_node = halt_node; mctx->match_last = match_last; ret = REG_NOERROR; free_return: re_free (sifted_states); re_free (lim_states); return ret; } /* Acquire an initial state and return it. We must select appropriate initial state depending on the context, since initial states may have constraints like "\<", "^", etc.. */ static inline re_dfastate_t * __attribute ((always_inline)) internal_function acquire_init_state_context (reg_errcode_t *err, const re_match_context_t *mctx, int idx) { const re_dfa_t *const dfa = mctx->dfa; if (dfa->init_state->has_constraint) { unsigned int context; context = re_string_context_at (&mctx->input, idx - 1, mctx->eflags); if (IS_WORD_CONTEXT (context)) return dfa->init_state_word; else if (IS_ORDINARY_CONTEXT (context)) return dfa->init_state; else if (IS_BEGBUF_CONTEXT (context) && IS_NEWLINE_CONTEXT (context)) return dfa->init_state_begbuf; else if (IS_NEWLINE_CONTEXT (context)) return dfa->init_state_nl; else if (IS_BEGBUF_CONTEXT (context)) { /* It is relatively rare case, then calculate on demand. */ return re_acquire_state_context (err, dfa, dfa->init_state->entrance_nodes, context); } else /* Must not happen? */ return dfa->init_state; } else return dfa->init_state; } /* Check whether the regular expression match input string INPUT or not, and return the index where the matching end, return -1 if not match, or return -2 in case of an error. FL_LONGEST_MATCH means we want the POSIX longest matching. If P_MATCH_FIRST is not NULL, and the match fails, it is set to the next place where we may want to try matching. Note that the matcher assume that the maching starts from the current index of the buffer. */ static int internal_function check_matching (re_match_context_t *mctx, int fl_longest_match, int *p_match_first) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; int match = 0; int match_last = -1; int cur_str_idx = re_string_cur_idx (&mctx->input); re_dfastate_t *cur_state; int at_init_state = p_match_first != NULL; int next_start_idx = cur_str_idx; err = REG_NOERROR; cur_state = acquire_init_state_context (&err, mctx, cur_str_idx); /* An initial state must not be NULL (invalid). */ if (BE (cur_state == NULL, 0)) { assert (err == REG_ESPACE); return -2; } if (mctx->state_log != NULL) { mctx->state_log[cur_str_idx] = cur_state; /* Check OP_OPEN_SUBEXP in the initial state in case that we use them later. E.g. Processing back references. */ if (BE (dfa->nbackref, 0)) { at_init_state = 0; err = check_subexp_matching_top (mctx, &cur_state->nodes, 0); if (BE (err != REG_NOERROR, 0)) return err; if (cur_state->has_backref) { err = transit_state_bkref (mctx, &cur_state->nodes); if (BE (err != REG_NOERROR, 0)) return err; } } } /* If the RE accepts NULL string. */ if (BE (cur_state->halt, 0)) { if (!cur_state->has_constraint || check_halt_state_context (mctx, cur_state, cur_str_idx)) { if (!fl_longest_match) return cur_str_idx; else { match_last = cur_str_idx; match = 1; } } } while (!re_string_eoi (&mctx->input)) { re_dfastate_t *old_state = cur_state; int next_char_idx = re_string_cur_idx (&mctx->input) + 1; if (BE (next_char_idx >= mctx->input.bufs_len, 0) || (BE (next_char_idx >= mctx->input.valid_len, 0) && mctx->input.valid_len < mctx->input.len)) { err = extend_buffers (mctx); if (BE (err != REG_NOERROR, 0)) { assert (err == REG_ESPACE); return -2; } } cur_state = transit_state (&err, mctx, cur_state); if (mctx->state_log != NULL) cur_state = merge_state_with_log (&err, mctx, cur_state); if (cur_state == NULL) { /* Reached the invalid state or an error. Try to recover a valid state using the state log, if available and if we have not already found a valid (even if not the longest) match. */ if (BE (err != REG_NOERROR, 0)) return -2; if (mctx->state_log == NULL || (match && !fl_longest_match) || (cur_state = find_recover_state (&err, mctx)) == NULL) break; } if (BE (at_init_state, 0)) { if (old_state == cur_state) next_start_idx = next_char_idx; else at_init_state = 0; } if (cur_state->halt) { /* Reached a halt state. Check the halt state can satisfy the current context. */ if (!cur_state->has_constraint || check_halt_state_context (mctx, cur_state, re_string_cur_idx (&mctx->input))) { /* We found an appropriate halt state. */ match_last = re_string_cur_idx (&mctx->input); match = 1; /* We found a match, do not modify match_first below. */ p_match_first = NULL; if (!fl_longest_match) break; } } } if (p_match_first) *p_match_first += next_start_idx; return match_last; } /* Check NODE match the current context. */ static int internal_function check_halt_node_context (const re_dfa_t *dfa, int node, unsigned int context) { re_token_type_t type = dfa->nodes[node].type; unsigned int constraint = dfa->nodes[node].constraint; if (type != END_OF_RE) return 0; if (!constraint) return 1; if (NOT_SATISFY_NEXT_CONSTRAINT (constraint, context)) return 0; return 1; } /* Check the halt state STATE match the current context. Return 0 if not match, if the node, STATE has, is a halt node and match the context, return the node. */ static int internal_function check_halt_state_context (const re_match_context_t *mctx, const re_dfastate_t *state, int idx) { int i; unsigned int context; #ifdef DEBUG assert (state->halt); #endif context = re_string_context_at (&mctx->input, idx, mctx->eflags); for (i = 0; i < state->nodes.nelem; ++i) if (check_halt_node_context (mctx->dfa, state->nodes.elems[i], context)) return state->nodes.elems[i]; return 0; } /* Compute the next node to which "NFA" transit from NODE("NFA" is a NFA corresponding to the DFA). Return the destination node, and update EPS_VIA_NODES, return -1 in case of errors. */ static int internal_function proceed_next_node (const re_match_context_t *mctx, int nregs, regmatch_t *regs, int *pidx, int node, re_node_set *eps_via_nodes, struct re_fail_stack_t *fs) { const re_dfa_t *const dfa = mctx->dfa; int i, err; if (IS_EPSILON_NODE (dfa->nodes[node].type)) { re_node_set *cur_nodes = &mctx->state_log[*pidx]->nodes; re_node_set *edests = &dfa->edests[node]; int dest_node; err = re_node_set_insert (eps_via_nodes, node); if (BE (err < 0, 0)) return -2; /* Pick up a valid destination, or return -1 if none is found. */ for (dest_node = -1, i = 0; i < edests->nelem; ++i) { int candidate = edests->elems[i]; if (!re_node_set_contains (cur_nodes, candidate)) continue; if (dest_node == -1) dest_node = candidate; else { /* In order to avoid infinite loop like "(a*)*", return the second epsilon-transition if the first was already considered. */ if (re_node_set_contains (eps_via_nodes, dest_node)) return candidate; /* Otherwise, push the second epsilon-transition on the fail stack. */ else if (fs != NULL && push_fail_stack (fs, *pidx, candidate, nregs, regs, eps_via_nodes)) return -2; /* We know we are going to exit. */ break; } } return dest_node; } else { int naccepted = 0; re_token_type_t type = dfa->nodes[node].type; #ifdef RE_ENABLE_I18N if (dfa->nodes[node].accept_mb) naccepted = check_node_accept_bytes (dfa, node, &mctx->input, *pidx); else #endif /* RE_ENABLE_I18N */ if (type == OP_BACK_REF) { int subexp_idx = dfa->nodes[node].opr.idx + 1; naccepted = regs[subexp_idx].rm_eo - regs[subexp_idx].rm_so; if (fs != NULL) { if (regs[subexp_idx].rm_so == -1 || regs[subexp_idx].rm_eo == -1) return -1; else if (naccepted) { char *buf = (char *) re_string_get_buffer (&mctx->input); if (memcmp (buf + regs[subexp_idx].rm_so, buf + *pidx, naccepted) != 0) return -1; } } if (naccepted == 0) { int dest_node; err = re_node_set_insert (eps_via_nodes, node); if (BE (err < 0, 0)) return -2; dest_node = dfa->edests[node].elems[0]; if (re_node_set_contains (&mctx->state_log[*pidx]->nodes, dest_node)) return dest_node; } } if (naccepted != 0 || check_node_accept (mctx, dfa->nodes + node, *pidx)) { int dest_node = dfa->nexts[node]; *pidx = (naccepted == 0) ? *pidx + 1 : *pidx + naccepted; if (fs && (*pidx > mctx->match_last || mctx->state_log[*pidx] == NULL || !re_node_set_contains (&mctx->state_log[*pidx]->nodes, dest_node))) return -1; re_node_set_empty (eps_via_nodes); return dest_node; } } return -1; } static reg_errcode_t internal_function push_fail_stack (struct re_fail_stack_t *fs, int str_idx, int dest_node, int nregs, regmatch_t *regs, re_node_set *eps_via_nodes) { reg_errcode_t err; int num = fs->num++; if (fs->num == fs->alloc) { struct re_fail_stack_ent_t *new_array; new_array = realloc (fs->stack, (sizeof (struct re_fail_stack_ent_t) * fs->alloc * 2)); if (new_array == NULL) return REG_ESPACE; fs->alloc *= 2; fs->stack = new_array; } fs->stack[num].idx = str_idx; fs->stack[num].node = dest_node; fs->stack[num].regs = re_malloc (regmatch_t, nregs); if (fs->stack[num].regs == NULL) return REG_ESPACE; memcpy (fs->stack[num].regs, regs, sizeof (regmatch_t) * nregs); err = re_node_set_init_copy (&fs->stack[num].eps_via_nodes, eps_via_nodes); return err; } static int internal_function pop_fail_stack (struct re_fail_stack_t *fs, int *pidx, int nregs, regmatch_t *regs, re_node_set *eps_via_nodes) { int num = --fs->num; assert (num >= 0); *pidx = fs->stack[num].idx; memcpy (regs, fs->stack[num].regs, sizeof (regmatch_t) * nregs); re_node_set_free (eps_via_nodes); re_free (fs->stack[num].regs); *eps_via_nodes = fs->stack[num].eps_via_nodes; return fs->stack[num].node; } /* Set the positions where the subexpressions are starts/ends to registers PMATCH. Note: We assume that pmatch[0] is already set, and pmatch[i].rm_so == pmatch[i].rm_eo == -1 for 0 < i < nmatch. */ static reg_errcode_t internal_function set_regs (const regex_t *preg, const re_match_context_t *mctx, size_t nmatch, regmatch_t *pmatch, int fl_backtrack) { const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer; int idx, cur_node; re_node_set eps_via_nodes; struct re_fail_stack_t *fs; struct re_fail_stack_t fs_body = { 0, 2, NULL }; regmatch_t *prev_idx_match; int prev_idx_match_malloced = 0; #ifdef DEBUG assert (nmatch > 1); assert (mctx->state_log != NULL); #endif if (fl_backtrack) { fs = &fs_body; fs->stack = re_malloc (struct re_fail_stack_ent_t, fs->alloc); if (fs->stack == NULL) return REG_ESPACE; } else fs = NULL; cur_node = dfa->init_node; re_node_set_init_empty (&eps_via_nodes); if (__libc_use_alloca (nmatch * sizeof (regmatch_t))) prev_idx_match = (regmatch_t *) alloca (nmatch * sizeof (regmatch_t)); else { prev_idx_match = re_malloc (regmatch_t, nmatch); if (prev_idx_match == NULL) { free_fail_stack_return (fs); return REG_ESPACE; } prev_idx_match_malloced = 1; } memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch); for (idx = pmatch[0].rm_so; idx <= pmatch[0].rm_eo ;) { update_regs (dfa, pmatch, prev_idx_match, cur_node, idx, nmatch); if (idx == pmatch[0].rm_eo && cur_node == mctx->last_node) { int reg_idx; if (fs) { for (reg_idx = 0; reg_idx < nmatch; ++reg_idx) if (pmatch[reg_idx].rm_so > -1 && pmatch[reg_idx].rm_eo == -1) break; if (reg_idx == nmatch) { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return free_fail_stack_return (fs); } cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch, &eps_via_nodes); } else { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return REG_NOERROR; } } /* Proceed to next node. */ cur_node = proceed_next_node (mctx, nmatch, pmatch, &idx, cur_node, &eps_via_nodes, fs); if (BE (cur_node < 0, 0)) { if (BE (cur_node == -2, 0)) { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); free_fail_stack_return (fs); return REG_ESPACE; } if (fs) cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch, &eps_via_nodes); else { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return REG_NOMATCH; } } } re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return free_fail_stack_return (fs); } static reg_errcode_t internal_function free_fail_stack_return (struct re_fail_stack_t *fs) { if (fs) { int fs_idx; for (fs_idx = 0; fs_idx < fs->num; ++fs_idx) { re_node_set_free (&fs->stack[fs_idx].eps_via_nodes); re_free (fs->stack[fs_idx].regs); } re_free (fs->stack); } return REG_NOERROR; } static void internal_function update_regs (const re_dfa_t *dfa, regmatch_t *pmatch, regmatch_t *prev_idx_match, int cur_node, int cur_idx, int nmatch) { int type = dfa->nodes[cur_node].type; if (type == OP_OPEN_SUBEXP) { int reg_num = dfa->nodes[cur_node].opr.idx + 1; /* We are at the first node of this sub expression. */ if (reg_num < nmatch) { pmatch[reg_num].rm_so = cur_idx; pmatch[reg_num].rm_eo = -1; } } else if (type == OP_CLOSE_SUBEXP) { int reg_num = dfa->nodes[cur_node].opr.idx + 1; if (reg_num < nmatch) { /* We are at the last node of this sub expression. */ if (pmatch[reg_num].rm_so < cur_idx) { pmatch[reg_num].rm_eo = cur_idx; /* This is a non-empty match or we are not inside an optional subexpression. Accept this right away. */ memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch); } else { if (dfa->nodes[cur_node].opt_subexp && prev_idx_match[reg_num].rm_so != -1) /* We transited through an empty match for an optional subexpression, like (a?)*, and this is not the subexp's first match. Copy back the old content of the registers so that matches of an inner subexpression are undone as well, like in ((a?))*. */ memcpy (pmatch, prev_idx_match, sizeof (regmatch_t) * nmatch); else /* We completed a subexpression, but it may be part of an optional one, so do not update PREV_IDX_MATCH. */ pmatch[reg_num].rm_eo = cur_idx; } } } } /* This function checks the STATE_LOG from the SCTX->last_str_idx to 0 and sift the nodes in each states according to the following rules. Updated state_log will be wrote to STATE_LOG. Rules: We throw away the Node `a' in the STATE_LOG[STR_IDX] if... 1. When STR_IDX == MATCH_LAST(the last index in the state_log): If `a' isn't the LAST_NODE and `a' can't epsilon transit to the LAST_NODE, we throw away the node `a'. 2. When 0 <= STR_IDX < MATCH_LAST and `a' accepts string `s' and transit to `b': i. If 'b' isn't in the STATE_LOG[STR_IDX+strlen('s')], we throw away the node `a'. ii. If 'b' is in the STATE_LOG[STR_IDX+strlen('s')] but 'b' is thrown away, we throw away the node `a'. 3. When 0 <= STR_IDX < MATCH_LAST and 'a' epsilon transit to 'b': i. If 'b' isn't in the STATE_LOG[STR_IDX], we throw away the node `a'. ii. If 'b' is in the STATE_LOG[STR_IDX] but 'b' is thrown away, we throw away the node `a'. */ #define STATE_NODE_CONTAINS(state,node) \ ((state) != NULL && re_node_set_contains (&(state)->nodes, node)) static reg_errcode_t internal_function sift_states_backward (const re_match_context_t *mctx, re_sift_context_t *sctx) { reg_errcode_t err; int null_cnt = 0; int str_idx = sctx->last_str_idx; re_node_set cur_dest; #ifdef DEBUG assert (mctx->state_log != NULL && mctx->state_log[str_idx] != NULL); #endif /* Build sifted state_log[str_idx]. It has the nodes which can epsilon transit to the last_node and the last_node itself. */ err = re_node_set_init_1 (&cur_dest, sctx->last_node); if (BE (err != REG_NOERROR, 0)) return err; err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest); if (BE (err != REG_NOERROR, 0)) goto free_return; /* Then check each states in the state_log. */ while (str_idx > 0) { /* Update counters. */ null_cnt = (sctx->sifted_states[str_idx] == NULL) ? null_cnt + 1 : 0; if (null_cnt > mctx->max_mb_elem_len) { memset (sctx->sifted_states, '\0', sizeof (re_dfastate_t *) * str_idx); re_node_set_free (&cur_dest); return REG_NOERROR; } re_node_set_empty (&cur_dest); --str_idx; if (mctx->state_log[str_idx]) { err = build_sifted_states (mctx, sctx, str_idx, &cur_dest); if (BE (err != REG_NOERROR, 0)) goto free_return; } /* Add all the nodes which satisfy the following conditions: - It can epsilon transit to a node in CUR_DEST. - It is in CUR_SRC. And update state_log. */ err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest); if (BE (err != REG_NOERROR, 0)) goto free_return; } err = REG_NOERROR; free_return: re_node_set_free (&cur_dest); return err; } static reg_errcode_t internal_function build_sifted_states (const re_match_context_t *mctx, re_sift_context_t *sctx, int str_idx, re_node_set *cur_dest) { const re_dfa_t *const dfa = mctx->dfa; const re_node_set *cur_src = &mctx->state_log[str_idx]->non_eps_nodes; int i; /* Then build the next sifted state. We build the next sifted state on `cur_dest', and update `sifted_states[str_idx]' with `cur_dest'. Note: `cur_dest' is the sifted state from `state_log[str_idx + 1]'. `cur_src' points the node_set of the old `state_log[str_idx]' (with the epsilon nodes pre-filtered out). */ for (i = 0; i < cur_src->nelem; i++) { int prev_node = cur_src->elems[i]; int naccepted = 0; int ret; #ifdef DEBUG re_token_type_t type = dfa->nodes[prev_node].type; assert (!IS_EPSILON_NODE (type)); #endif #ifdef RE_ENABLE_I18N /* If the node may accept `multi byte'. */ if (dfa->nodes[prev_node].accept_mb) naccepted = sift_states_iter_mb (mctx, sctx, prev_node, str_idx, sctx->last_str_idx); #endif /* RE_ENABLE_I18N */ /* We don't check backreferences here. See update_cur_sifted_state(). */ if (!naccepted && check_node_accept (mctx, dfa->nodes + prev_node, str_idx) && STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + 1], dfa->nexts[prev_node])) naccepted = 1; if (naccepted == 0) continue; if (sctx->limits.nelem) { int to_idx = str_idx + naccepted; if (check_dst_limits (mctx, &sctx->limits, dfa->nexts[prev_node], to_idx, prev_node, str_idx)) continue; } ret = re_node_set_insert (cur_dest, prev_node); if (BE (ret == -1, 0)) return REG_ESPACE; } return REG_NOERROR; } /* Helper functions. */ static reg_errcode_t internal_function clean_state_log_if_needed (re_match_context_t *mctx, int next_state_log_idx) { int top = mctx->state_log_top; if (next_state_log_idx >= mctx->input.bufs_len || (next_state_log_idx >= mctx->input.valid_len && mctx->input.valid_len < mctx->input.len)) { reg_errcode_t err; err = extend_buffers (mctx); if (BE (err != REG_NOERROR, 0)) return err; } if (top < next_state_log_idx) { memset (mctx->state_log + top + 1, '\0', sizeof (re_dfastate_t *) * (next_state_log_idx - top)); mctx->state_log_top = next_state_log_idx; } return REG_NOERROR; } static reg_errcode_t internal_function merge_state_array (const re_dfa_t *dfa, re_dfastate_t **dst, re_dfastate_t **src, int num) { int st_idx; reg_errcode_t err; for (st_idx = 0; st_idx < num; ++st_idx) { if (dst[st_idx] == NULL) dst[st_idx] = src[st_idx]; else if (src[st_idx] != NULL) { re_node_set merged_set; err = re_node_set_init_union (&merged_set, &dst[st_idx]->nodes, &src[st_idx]->nodes); if (BE (err != REG_NOERROR, 0)) return err; dst[st_idx] = re_acquire_state (&err, dfa, &merged_set); re_node_set_free (&merged_set); if (BE (err != REG_NOERROR, 0)) return err; } } return REG_NOERROR; } static reg_errcode_t internal_function update_cur_sifted_state (const re_match_context_t *mctx, re_sift_context_t *sctx, int str_idx, re_node_set *dest_nodes) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err = REG_NOERROR; const re_node_set *candidates; candidates = ((mctx->state_log[str_idx] == NULL) ? NULL : &mctx->state_log[str_idx]->nodes); if (dest_nodes->nelem == 0) sctx->sifted_states[str_idx] = NULL; else { if (candidates) { /* At first, add the nodes which can epsilon transit to a node in DEST_NODE. */ err = add_epsilon_src_nodes (dfa, dest_nodes, candidates); if (BE (err != REG_NOERROR, 0)) return err; /* Then, check the limitations in the current sift_context. */ if (sctx->limits.nelem) { err = check_subexp_limits (dfa, dest_nodes, candidates, &sctx->limits, mctx->bkref_ents, str_idx); if (BE (err != REG_NOERROR, 0)) return err; } } sctx->sifted_states[str_idx] = re_acquire_state (&err, dfa, dest_nodes); if (BE (err != REG_NOERROR, 0)) return err; } if (candidates && mctx->state_log[str_idx]->has_backref) { err = sift_states_bkref (mctx, sctx, str_idx, candidates); if (BE (err != REG_NOERROR, 0)) return err; } return REG_NOERROR; } static reg_errcode_t internal_function add_epsilon_src_nodes (const re_dfa_t *dfa, re_node_set *dest_nodes, const re_node_set *candidates) { reg_errcode_t err = REG_NOERROR; int i; re_dfastate_t *state = re_acquire_state (&err, dfa, dest_nodes); if (BE (err != REG_NOERROR, 0)) return err; if (!state->inveclosure.alloc) { err = re_node_set_alloc (&state->inveclosure, dest_nodes->nelem); if (BE (err != REG_NOERROR, 0)) return REG_ESPACE; for (i = 0; i < dest_nodes->nelem; i++) re_node_set_merge (&state->inveclosure, dfa->inveclosures + dest_nodes->elems[i]); } return re_node_set_add_intersect (dest_nodes, candidates, &state->inveclosure); } static reg_errcode_t internal_function sub_epsilon_src_nodes (const re_dfa_t *dfa, int node, re_node_set *dest_nodes, const re_node_set *candidates) { int ecl_idx; reg_errcode_t err; re_node_set *inv_eclosure = dfa->inveclosures + node; re_node_set except_nodes; re_node_set_init_empty (&except_nodes); for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx) { int cur_node = inv_eclosure->elems[ecl_idx]; if (cur_node == node) continue; if (IS_EPSILON_NODE (dfa->nodes[cur_node].type)) { int edst1 = dfa->edests[cur_node].elems[0]; int edst2 = ((dfa->edests[cur_node].nelem > 1) ? dfa->edests[cur_node].elems[1] : -1); if ((!re_node_set_contains (inv_eclosure, edst1) && re_node_set_contains (dest_nodes, edst1)) || (edst2 > 0 && !re_node_set_contains (inv_eclosure, edst2) && re_node_set_contains (dest_nodes, edst2))) { err = re_node_set_add_intersect (&except_nodes, candidates, dfa->inveclosures + cur_node); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&except_nodes); return err; } } } } for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx) { int cur_node = inv_eclosure->elems[ecl_idx]; if (!re_node_set_contains (&except_nodes, cur_node)) { int idx = re_node_set_contains (dest_nodes, cur_node) - 1; re_node_set_remove_at (dest_nodes, idx); } } re_node_set_free (&except_nodes); return REG_NOERROR; } static int internal_function check_dst_limits (const re_match_context_t *mctx, re_node_set *limits, int dst_node, int dst_idx, int src_node, int src_idx) { const re_dfa_t *const dfa = mctx->dfa; int lim_idx, src_pos, dst_pos; int dst_bkref_idx = search_cur_bkref_entry (mctx, dst_idx); int src_bkref_idx = search_cur_bkref_entry (mctx, src_idx); for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx) { int subexp_idx; struct re_backref_cache_entry *ent; ent = mctx->bkref_ents + limits->elems[lim_idx]; subexp_idx = dfa->nodes[ent->node].opr.idx; dst_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx], subexp_idx, dst_node, dst_idx, dst_bkref_idx); src_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx], subexp_idx, src_node, src_idx, src_bkref_idx); /* In case of: ( ) ( ) ( ) */ if (src_pos == dst_pos) continue; /* This is unrelated limitation. */ else return 1; } return 0; } static int internal_function check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, int boundaries, int subexp_idx, int from_node, int bkref_idx) { const re_dfa_t *const dfa = mctx->dfa; const re_node_set *eclosures = dfa->eclosures + from_node; int node_idx; /* Else, we are on the boundary: examine the nodes on the epsilon closure. */ for (node_idx = 0; node_idx < eclosures->nelem; ++node_idx) { int node = eclosures->elems[node_idx]; switch (dfa->nodes[node].type) { case OP_BACK_REF: if (bkref_idx != -1) { struct re_backref_cache_entry *ent = mctx->bkref_ents + bkref_idx; do { int dst, cpos; if (ent->node != node) continue; if (subexp_idx < BITSET_WORD_BITS && !(ent->eps_reachable_subexps_map & ((bitset_word_t) 1 << subexp_idx))) continue; /* Recurse trying to reach the OP_OPEN_SUBEXP and OP_CLOSE_SUBEXP cases below. But, if the destination node is the same node as the source node, don't recurse because it would cause an infinite loop: a regex that exhibits this behavior is ()\1*\1* */ dst = dfa->edests[node].elems[0]; if (dst == from_node) { if (boundaries & 1) return -1; else /* if (boundaries & 2) */ return 0; } cpos = check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx, dst, bkref_idx); if (cpos == -1 /* && (boundaries & 1) */) return -1; if (cpos == 0 && (boundaries & 2)) return 0; if (subexp_idx < BITSET_WORD_BITS) ent->eps_reachable_subexps_map &= ~((bitset_word_t) 1 << subexp_idx); } while (ent++->more); } break; case OP_OPEN_SUBEXP: if ((boundaries & 1) && subexp_idx == dfa->nodes[node].opr.idx) return -1; break; case OP_CLOSE_SUBEXP: if ((boundaries & 2) && subexp_idx == dfa->nodes[node].opr.idx) return 0; break; default: break; } } return (boundaries & 2) ? 1 : 0; } static int internal_function check_dst_limits_calc_pos (const re_match_context_t *mctx, int limit, int subexp_idx, int from_node, int str_idx, int bkref_idx) { struct re_backref_cache_entry *lim = mctx->bkref_ents + limit; int boundaries; /* If we are outside the range of the subexpression, return -1 or 1. */ if (str_idx < lim->subexp_from) return -1; if (lim->subexp_to < str_idx) return 1; /* If we are within the subexpression, return 0. */ boundaries = (str_idx == lim->subexp_from); boundaries |= (str_idx == lim->subexp_to) << 1; if (boundaries == 0) return 0; /* Else, examine epsilon closure. */ return check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx, from_node, bkref_idx); } /* Check the limitations of sub expressions LIMITS, and remove the nodes which are against limitations from DEST_NODES. */ static reg_errcode_t internal_function check_subexp_limits (const re_dfa_t *dfa, re_node_set *dest_nodes, const re_node_set *candidates, re_node_set *limits, struct re_backref_cache_entry *bkref_ents, int str_idx) { reg_errcode_t err; int node_idx, lim_idx; for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx) { int subexp_idx; struct re_backref_cache_entry *ent; ent = bkref_ents + limits->elems[lim_idx]; if (str_idx <= ent->subexp_from || ent->str_idx < str_idx) continue; /* This is unrelated limitation. */ subexp_idx = dfa->nodes[ent->node].opr.idx; if (ent->subexp_to == str_idx) { int ops_node = -1; int cls_node = -1; for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) { int node = dest_nodes->elems[node_idx]; re_token_type_t type = dfa->nodes[node].type; if (type == OP_OPEN_SUBEXP && subexp_idx == dfa->nodes[node].opr.idx) ops_node = node; else if (type == OP_CLOSE_SUBEXP && subexp_idx == dfa->nodes[node].opr.idx) cls_node = node; } /* Check the limitation of the open subexpression. */ /* Note that (ent->subexp_to = str_idx != ent->subexp_from). */ if (ops_node >= 0) { err = sub_epsilon_src_nodes (dfa, ops_node, dest_nodes, candidates); if (BE (err != REG_NOERROR, 0)) return err; } /* Check the limitation of the close subexpression. */ if (cls_node >= 0) for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) { int node = dest_nodes->elems[node_idx]; if (!re_node_set_contains (dfa->inveclosures + node, cls_node) && !re_node_set_contains (dfa->eclosures + node, cls_node)) { /* It is against this limitation. Remove it form the current sifted state. */ err = sub_epsilon_src_nodes (dfa, node, dest_nodes, candidates); if (BE (err != REG_NOERROR, 0)) return err; --node_idx; } } } else /* (ent->subexp_to != str_idx) */ { for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) { int node = dest_nodes->elems[node_idx]; re_token_type_t type = dfa->nodes[node].type; if (type == OP_CLOSE_SUBEXP || type == OP_OPEN_SUBEXP) { if (subexp_idx != dfa->nodes[node].opr.idx) continue; /* It is against this limitation. Remove it form the current sifted state. */ err = sub_epsilon_src_nodes (dfa, node, dest_nodes, candidates); if (BE (err != REG_NOERROR, 0)) return err; } } } } return REG_NOERROR; } static reg_errcode_t internal_function sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx, int str_idx, const re_node_set *candidates) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; int node_idx, node; re_sift_context_t local_sctx; int first_idx = search_cur_bkref_entry (mctx, str_idx); if (first_idx == -1) return REG_NOERROR; local_sctx.sifted_states = NULL; /* Mark that it hasn't been initialized. */ for (node_idx = 0; node_idx < candidates->nelem; ++node_idx) { int enabled_idx; re_token_type_t type; struct re_backref_cache_entry *entry; node = candidates->elems[node_idx]; type = dfa->nodes[node].type; /* Avoid infinite loop for the REs like "()\1+". */ if (node == sctx->last_node && str_idx == sctx->last_str_idx) continue; if (type != OP_BACK_REF) continue; entry = mctx->bkref_ents + first_idx; enabled_idx = first_idx; do { int subexp_len; int to_idx; int dst_node; int ret; re_dfastate_t *cur_state; if (entry->node != node) continue; subexp_len = entry->subexp_to - entry->subexp_from; to_idx = str_idx + subexp_len; dst_node = (subexp_len ? dfa->nexts[node] : dfa->edests[node].elems[0]); if (to_idx > sctx->last_str_idx || sctx->sifted_states[to_idx] == NULL || !STATE_NODE_CONTAINS (sctx->sifted_states[to_idx], dst_node) || check_dst_limits (mctx, &sctx->limits, node, str_idx, dst_node, to_idx)) continue; if (local_sctx.sifted_states == NULL) { local_sctx = *sctx; err = re_node_set_init_copy (&local_sctx.limits, &sctx->limits); if (BE (err != REG_NOERROR, 0)) goto free_return; } local_sctx.last_node = node; local_sctx.last_str_idx = str_idx; ret = re_node_set_insert (&local_sctx.limits, enabled_idx); if (BE (ret < 0, 0)) { err = REG_ESPACE; goto free_return; } cur_state = local_sctx.sifted_states[str_idx]; err = sift_states_backward (mctx, &local_sctx); if (BE (err != REG_NOERROR, 0)) goto free_return; if (sctx->limited_states != NULL) { err = merge_state_array (dfa, sctx->limited_states, local_sctx.sifted_states, str_idx + 1); if (BE (err != REG_NOERROR, 0)) goto free_return; } local_sctx.sifted_states[str_idx] = cur_state; re_node_set_remove (&local_sctx.limits, enabled_idx); /* mctx->bkref_ents may have changed, reload the pointer. */ entry = mctx->bkref_ents + enabled_idx; } while (enabled_idx++, entry++->more); } err = REG_NOERROR; free_return: if (local_sctx.sifted_states != NULL) { re_node_set_free (&local_sctx.limits); } return err; } #ifdef RE_ENABLE_I18N static int internal_function sift_states_iter_mb (const re_match_context_t *mctx, re_sift_context_t *sctx, int node_idx, int str_idx, int max_str_idx) { const re_dfa_t *const dfa = mctx->dfa; int naccepted; /* Check the node can accept `multi byte'. */ naccepted = check_node_accept_bytes (dfa, node_idx, &mctx->input, str_idx); if (naccepted > 0 && str_idx + naccepted <= max_str_idx && !STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + naccepted], dfa->nexts[node_idx])) /* The node can't accept the `multi byte', or the destination was already thrown away, then the node could't accept the current input `multi byte'. */ naccepted = 0; /* Otherwise, it is sure that the node could accept `naccepted' bytes input. */ return naccepted; } #endif /* RE_ENABLE_I18N */ /* Functions for state transition. */ /* Return the next state to which the current state STATE will transit by accepting the current input byte, and update STATE_LOG if necessary. If STATE can accept a multibyte char/collating element/back reference update the destination of STATE_LOG. */ static re_dfastate_t * internal_function transit_state (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *state) { re_dfastate_t **trtable; unsigned char ch; #ifdef RE_ENABLE_I18N /* If the current state can accept multibyte. */ if (BE (state->accept_mb, 0)) { *err = transit_state_mb (mctx, state); if (BE (*err != REG_NOERROR, 0)) return NULL; } #endif /* RE_ENABLE_I18N */ /* Then decide the next state with the single byte. */ #if 0 if (0) /* don't use transition table */ return transit_state_sb (err, mctx, state); #endif /* Use transition table */ ch = re_string_fetch_byte (&mctx->input); for (;;) { trtable = state->trtable; if (BE (trtable != NULL, 1)) return trtable[ch]; trtable = state->word_trtable; if (BE (trtable != NULL, 1)) { unsigned int context; context = re_string_context_at (&mctx->input, re_string_cur_idx (&mctx->input) - 1, mctx->eflags); if (IS_WORD_CONTEXT (context)) return trtable[ch + SBC_MAX]; else return trtable[ch]; } if (!build_trtable (mctx->dfa, state)) { *err = REG_ESPACE; return NULL; } /* Retry, we now have a transition table. */ } } /* Update the state_log if we need */ re_dfastate_t * internal_function merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *next_state) { const re_dfa_t *const dfa = mctx->dfa; int cur_idx = re_string_cur_idx (&mctx->input); if (cur_idx > mctx->state_log_top) { mctx->state_log[cur_idx] = next_state; mctx->state_log_top = cur_idx; } else if (mctx->state_log[cur_idx] == 0) { mctx->state_log[cur_idx] = next_state; } else { re_dfastate_t *pstate; unsigned int context; re_node_set next_nodes, *log_nodes, *table_nodes = NULL; /* If (state_log[cur_idx] != 0), it implies that cur_idx is the destination of a multibyte char/collating element/ back reference. Then the next state is the union set of these destinations and the results of the transition table. */ pstate = mctx->state_log[cur_idx]; log_nodes = pstate->entrance_nodes; if (next_state != NULL) { table_nodes = next_state->entrance_nodes; *err = re_node_set_init_union (&next_nodes, table_nodes, log_nodes); if (BE (*err != REG_NOERROR, 0)) return NULL; } else next_nodes = *log_nodes; /* Note: We already add the nodes of the initial state, then we don't need to add them here. */ context = re_string_context_at (&mctx->input, re_string_cur_idx (&mctx->input) - 1, mctx->eflags); next_state = mctx->state_log[cur_idx] = re_acquire_state_context (err, dfa, &next_nodes, context); /* We don't need to check errors here, since the return value of this function is next_state and ERR is already set. */ if (table_nodes != NULL) re_node_set_free (&next_nodes); } if (BE (dfa->nbackref, 0) && next_state != NULL) { /* Check OP_OPEN_SUBEXP in the current state in case that we use them later. We must check them here, since the back references in the next state might use them. */ *err = check_subexp_matching_top (mctx, &next_state->nodes, cur_idx); if (BE (*err != REG_NOERROR, 0)) return NULL; /* If the next state has back references. */ if (next_state->has_backref) { *err = transit_state_bkref (mctx, &next_state->nodes); if (BE (*err != REG_NOERROR, 0)) return NULL; next_state = mctx->state_log[cur_idx]; } } return next_state; } /* Skip bytes in the input that correspond to part of a multi-byte match, then look in the log for a state from which to restart matching. */ re_dfastate_t * internal_function find_recover_state (reg_errcode_t *err, re_match_context_t *mctx) { re_dfastate_t *cur_state; do { int max = mctx->state_log_top; int cur_str_idx = re_string_cur_idx (&mctx->input); do { if (++cur_str_idx > max) return NULL; re_string_skip_bytes (&mctx->input, 1); } while (mctx->state_log[cur_str_idx] == NULL); cur_state = merge_state_with_log (err, mctx, NULL); } while (*err == REG_NOERROR && cur_state == NULL); return cur_state; } /* Helper functions for transit_state. */ /* From the node set CUR_NODES, pick up the nodes whose types are OP_OPEN_SUBEXP and which have corresponding back references in the regular expression. And register them to use them later for evaluating the correspoding back references. */ static reg_errcode_t internal_function check_subexp_matching_top (re_match_context_t *mctx, re_node_set *cur_nodes, int str_idx) { const re_dfa_t *const dfa = mctx->dfa; int node_idx; reg_errcode_t err; /* TODO: This isn't efficient. Because there might be more than one nodes whose types are OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all nodes. E.g. RE: (a){2} */ for (node_idx = 0; node_idx < cur_nodes->nelem; ++node_idx) { int node = cur_nodes->elems[node_idx]; if (dfa->nodes[node].type == OP_OPEN_SUBEXP && dfa->nodes[node].opr.idx < BITSET_WORD_BITS && (dfa->used_bkref_map & ((bitset_word_t) 1 << dfa->nodes[node].opr.idx))) { err = match_ctx_add_subtop (mctx, node, str_idx); if (BE (err != REG_NOERROR, 0)) return err; } } return REG_NOERROR; } #if 0 /* Return the next state to which the current state STATE will transit by accepting the current input byte. */ static re_dfastate_t * transit_state_sb (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *state) { const re_dfa_t *const dfa = mctx->dfa; re_node_set next_nodes; re_dfastate_t *next_state; int node_cnt, cur_str_idx = re_string_cur_idx (&mctx->input); unsigned int context; *err = re_node_set_alloc (&next_nodes, state->nodes.nelem + 1); if (BE (*err != REG_NOERROR, 0)) return NULL; for (node_cnt = 0; node_cnt < state->nodes.nelem; ++node_cnt) { int cur_node = state->nodes.elems[node_cnt]; if (check_node_accept (mctx, dfa->nodes + cur_node, cur_str_idx)) { *err = re_node_set_merge (&next_nodes, dfa->eclosures + dfa->nexts[cur_node]); if (BE (*err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return NULL; } } } context = re_string_context_at (&mctx->input, cur_str_idx, mctx->eflags); next_state = re_acquire_state_context (err, dfa, &next_nodes, context); /* We don't need to check errors here, since the return value of this function is next_state and ERR is already set. */ re_node_set_free (&next_nodes); re_string_skip_bytes (&mctx->input, 1); return next_state; } #endif #ifdef RE_ENABLE_I18N static reg_errcode_t internal_function transit_state_mb (re_match_context_t *mctx, re_dfastate_t *pstate) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; int i; for (i = 0; i < pstate->nodes.nelem; ++i) { re_node_set dest_nodes, *new_nodes; int cur_node_idx = pstate->nodes.elems[i]; int naccepted, dest_idx; unsigned int context; re_dfastate_t *dest_state; if (!dfa->nodes[cur_node_idx].accept_mb) continue; if (dfa->nodes[cur_node_idx].constraint) { context = re_string_context_at (&mctx->input, re_string_cur_idx (&mctx->input), mctx->eflags); if (NOT_SATISFY_NEXT_CONSTRAINT (dfa->nodes[cur_node_idx].constraint, context)) continue; } /* How many bytes the node can accept? */ naccepted = check_node_accept_bytes (dfa, cur_node_idx, &mctx->input, re_string_cur_idx (&mctx->input)); if (naccepted == 0) continue; /* The node can accepts `naccepted' bytes. */ dest_idx = re_string_cur_idx (&mctx->input) + naccepted; mctx->max_mb_elem_len = ((mctx->max_mb_elem_len < naccepted) ? naccepted : mctx->max_mb_elem_len); err = clean_state_log_if_needed (mctx, dest_idx); if (BE (err != REG_NOERROR, 0)) return err; #ifdef DEBUG assert (dfa->nexts[cur_node_idx] != -1); #endif new_nodes = dfa->eclosures + dfa->nexts[cur_node_idx]; dest_state = mctx->state_log[dest_idx]; if (dest_state == NULL) dest_nodes = *new_nodes; else { err = re_node_set_init_union (&dest_nodes, dest_state->entrance_nodes, new_nodes); if (BE (err != REG_NOERROR, 0)) return err; } context = re_string_context_at (&mctx->input, dest_idx - 1, mctx->eflags); mctx->state_log[dest_idx] = re_acquire_state_context (&err, dfa, &dest_nodes, context); if (dest_state != NULL) re_node_set_free (&dest_nodes); if (BE (mctx->state_log[dest_idx] == NULL && err != REG_NOERROR, 0)) return err; } return REG_NOERROR; } #endif /* RE_ENABLE_I18N */ static reg_errcode_t internal_function transit_state_bkref (re_match_context_t *mctx, const re_node_set *nodes) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; int i; int cur_str_idx = re_string_cur_idx (&mctx->input); for (i = 0; i < nodes->nelem; ++i) { int dest_str_idx, prev_nelem, bkc_idx; int node_idx = nodes->elems[i]; unsigned int context; const re_token_t *node = dfa->nodes + node_idx; re_node_set *new_dest_nodes; /* Check whether `node' is a backreference or not. */ if (node->type != OP_BACK_REF) continue; if (node->constraint) { context = re_string_context_at (&mctx->input, cur_str_idx, mctx->eflags); if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context)) continue; } /* `node' is a backreference. Check the substring which the substring matched. */ bkc_idx = mctx->nbkref_ents; err = get_subexp (mctx, node_idx, cur_str_idx); if (BE (err != REG_NOERROR, 0)) goto free_return; /* And add the epsilon closures (which is `new_dest_nodes') of the backreference to appropriate state_log. */ #ifdef DEBUG assert (dfa->nexts[node_idx] != -1); #endif for (; bkc_idx < mctx->nbkref_ents; ++bkc_idx) { int subexp_len; re_dfastate_t *dest_state; struct re_backref_cache_entry *bkref_ent; bkref_ent = mctx->bkref_ents + bkc_idx; if (bkref_ent->node != node_idx || bkref_ent->str_idx != cur_str_idx) continue; subexp_len = bkref_ent->subexp_to - bkref_ent->subexp_from; new_dest_nodes = (subexp_len == 0 ? dfa->eclosures + dfa->edests[node_idx].elems[0] : dfa->eclosures + dfa->nexts[node_idx]); dest_str_idx = (cur_str_idx + bkref_ent->subexp_to - bkref_ent->subexp_from); context = re_string_context_at (&mctx->input, dest_str_idx - 1, mctx->eflags); dest_state = mctx->state_log[dest_str_idx]; prev_nelem = ((mctx->state_log[cur_str_idx] == NULL) ? 0 : mctx->state_log[cur_str_idx]->nodes.nelem); /* Add `new_dest_node' to state_log. */ if (dest_state == NULL) { mctx->state_log[dest_str_idx] = re_acquire_state_context (&err, dfa, new_dest_nodes, context); if (BE (mctx->state_log[dest_str_idx] == NULL && err != REG_NOERROR, 0)) goto free_return; } else { re_node_set dest_nodes; err = re_node_set_init_union (&dest_nodes, dest_state->entrance_nodes, new_dest_nodes); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&dest_nodes); goto free_return; } mctx->state_log[dest_str_idx] = re_acquire_state_context (&err, dfa, &dest_nodes, context); re_node_set_free (&dest_nodes); if (BE (mctx->state_log[dest_str_idx] == NULL && err != REG_NOERROR, 0)) goto free_return; } /* We need to check recursively if the backreference can epsilon transit. */ if (subexp_len == 0 && mctx->state_log[cur_str_idx]->nodes.nelem > prev_nelem) { err = check_subexp_matching_top (mctx, new_dest_nodes, cur_str_idx); if (BE (err != REG_NOERROR, 0)) goto free_return; err = transit_state_bkref (mctx, new_dest_nodes); if (BE (err != REG_NOERROR, 0)) goto free_return; } } } err = REG_NOERROR; free_return: return err; } /* Enumerate all the candidates which the backreference BKREF_NODE can match at BKREF_STR_IDX, and register them by match_ctx_add_entry(). Note that we might collect inappropriate candidates here. However, the cost of checking them strictly here is too high, then we delay these checking for prune_impossible_nodes(). */ static reg_errcode_t internal_function get_subexp (re_match_context_t *mctx, int bkref_node, int bkref_str_idx) { const re_dfa_t *const dfa = mctx->dfa; int subexp_num, sub_top_idx; const char *buf = (const char *) re_string_get_buffer (&mctx->input); /* Return if we have already checked BKREF_NODE at BKREF_STR_IDX. */ int cache_idx = search_cur_bkref_entry (mctx, bkref_str_idx); if (cache_idx != -1) { const struct re_backref_cache_entry *entry = mctx->bkref_ents + cache_idx; do if (entry->node == bkref_node) return REG_NOERROR; /* We already checked it. */ while (entry++->more); } subexp_num = dfa->nodes[bkref_node].opr.idx; /* For each sub expression */ for (sub_top_idx = 0; sub_top_idx < mctx->nsub_tops; ++sub_top_idx) { reg_errcode_t err; re_sub_match_top_t *sub_top = mctx->sub_tops[sub_top_idx]; re_sub_match_last_t *sub_last; int sub_last_idx, sl_str, bkref_str_off; if (dfa->nodes[sub_top->node].opr.idx != subexp_num) continue; /* It isn't related. */ sl_str = sub_top->str_idx; bkref_str_off = bkref_str_idx; /* At first, check the last node of sub expressions we already evaluated. */ for (sub_last_idx = 0; sub_last_idx < sub_top->nlasts; ++sub_last_idx) { int sl_str_diff; sub_last = sub_top->lasts[sub_last_idx]; sl_str_diff = sub_last->str_idx - sl_str; /* The matched string by the sub expression match with the substring at the back reference? */ if (sl_str_diff > 0) { if (BE (bkref_str_off + sl_str_diff > mctx->input.valid_len, 0)) { /* Not enough chars for a successful match. */ if (bkref_str_off + sl_str_diff > mctx->input.len) break; err = clean_state_log_if_needed (mctx, bkref_str_off + sl_str_diff); if (BE (err != REG_NOERROR, 0)) return err; buf = (const char *) re_string_get_buffer (&mctx->input); } if (memcmp (buf + bkref_str_off, buf + sl_str, sl_str_diff) != 0) /* We don't need to search this sub expression any more. */ break; } bkref_str_off += sl_str_diff; sl_str += sl_str_diff; err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node, bkref_str_idx); /* Reload buf, since the preceding call might have reallocated the buffer. */ buf = (const char *) re_string_get_buffer (&mctx->input); if (err == REG_NOMATCH) continue; if (BE (err != REG_NOERROR, 0)) return err; } if (sub_last_idx < sub_top->nlasts) continue; if (sub_last_idx > 0) ++sl_str; /* Then, search for the other last nodes of the sub expression. */ for (; sl_str <= bkref_str_idx; ++sl_str) { int cls_node, sl_str_off; const re_node_set *nodes; sl_str_off = sl_str - sub_top->str_idx; /* The matched string by the sub expression match with the substring at the back reference? */ if (sl_str_off > 0) { if (BE (bkref_str_off >= mctx->input.valid_len, 0)) { /* If we are at the end of the input, we cannot match. */ if (bkref_str_off >= mctx->input.len) break; err = extend_buffers (mctx); if (BE (err != REG_NOERROR, 0)) return err; buf = (const char *) re_string_get_buffer (&mctx->input); } if (buf [bkref_str_off++] != buf[sl_str - 1]) break; /* We don't need to search this sub expression any more. */ } if (mctx->state_log[sl_str] == NULL) continue; /* Does this state have a ')' of the sub expression? */ nodes = &mctx->state_log[sl_str]->nodes; cls_node = find_subexp_node (dfa, nodes, subexp_num, OP_CLOSE_SUBEXP); if (cls_node == -1) continue; /* No. */ if (sub_top->path == NULL) { sub_top->path = calloc (sizeof (state_array_t), sl_str - sub_top->str_idx + 1); if (sub_top->path == NULL) return REG_ESPACE; } /* Can the OP_OPEN_SUBEXP node arrive the OP_CLOSE_SUBEXP node in the current context? */ err = check_arrival (mctx, sub_top->path, sub_top->node, sub_top->str_idx, cls_node, sl_str, OP_CLOSE_SUBEXP); if (err == REG_NOMATCH) continue; if (BE (err != REG_NOERROR, 0)) return err; sub_last = match_ctx_add_sublast (sub_top, cls_node, sl_str); if (BE (sub_last == NULL, 0)) return REG_ESPACE; err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node, bkref_str_idx); if (err == REG_NOMATCH) continue; } } return REG_NOERROR; } /* Helper functions for get_subexp(). */ /* Check SUB_LAST can arrive to the back reference BKREF_NODE at BKREF_STR. If it can arrive, register the sub expression expressed with SUB_TOP and SUB_LAST. */ static reg_errcode_t internal_function get_subexp_sub (re_match_context_t *mctx, const re_sub_match_top_t *sub_top, re_sub_match_last_t *sub_last, int bkref_node, int bkref_str) { reg_errcode_t err; int to_idx; /* Can the subexpression arrive the back reference? */ err = check_arrival (mctx, &sub_last->path, sub_last->node, sub_last->str_idx, bkref_node, bkref_str, OP_OPEN_SUBEXP); if (err != REG_NOERROR) return err; err = match_ctx_add_entry (mctx, bkref_node, bkref_str, sub_top->str_idx, sub_last->str_idx); if (BE (err != REG_NOERROR, 0)) return err; to_idx = bkref_str + sub_last->str_idx - sub_top->str_idx; return clean_state_log_if_needed (mctx, to_idx); } /* Find the first node which is '(' or ')' and whose index is SUBEXP_IDX. Search '(' if FL_OPEN, or search ')' otherwise. TODO: This function isn't efficient... Because there might be more than one nodes whose types are OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all nodes. E.g. RE: (a){2} */ static int internal_function find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes, int subexp_idx, int type) { int cls_idx; for (cls_idx = 0; cls_idx < nodes->nelem; ++cls_idx) { int cls_node = nodes->elems[cls_idx]; const re_token_t *node = dfa->nodes + cls_node; if (node->type == type && node->opr.idx == subexp_idx) return cls_node; } return -1; } /* Check whether the node TOP_NODE at TOP_STR can arrive to the node LAST_NODE at LAST_STR. We record the path onto PATH since it will be heavily reused. Return REG_NOERROR if it can arrive, or REG_NOMATCH otherwise. */ static reg_errcode_t internal_function check_arrival (re_match_context_t *mctx, state_array_t *path, int top_node, int top_str, int last_node, int last_str, int type) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err = REG_NOERROR; int subexp_num, backup_cur_idx, str_idx, null_cnt; re_dfastate_t *cur_state = NULL; re_node_set *cur_nodes, next_nodes; re_dfastate_t **backup_state_log; unsigned int context; subexp_num = dfa->nodes[top_node].opr.idx; /* Extend the buffer if we need. */ if (BE (path->alloc < last_str + mctx->max_mb_elem_len + 1, 0)) { re_dfastate_t **new_array; int old_alloc = path->alloc; path->alloc += last_str + mctx->max_mb_elem_len + 1; new_array = re_realloc (path->array, re_dfastate_t *, path->alloc); if (BE (new_array == NULL, 0)) { path->alloc = old_alloc; return REG_ESPACE; } path->array = new_array; memset (new_array + old_alloc, '\0', sizeof (re_dfastate_t *) * (path->alloc - old_alloc)); } // Original: // str_idx = path->next_idx ?: top_str; // Copied following from another version when cleaning up compiler warnings. str_idx = path->next_idx ? path->next_idx : top_str; /* Temporary modify MCTX. */ backup_state_log = mctx->state_log; backup_cur_idx = mctx->input.cur_idx; mctx->state_log = path->array; mctx->input.cur_idx = str_idx; /* Setup initial node set. */ context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags); if (str_idx == top_str) { err = re_node_set_init_1 (&next_nodes, top_node); if (BE (err != REG_NOERROR, 0)) return err; err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } } else { cur_state = mctx->state_log[str_idx]; if (cur_state && cur_state->has_backref) { err = re_node_set_init_copy (&next_nodes, &cur_state->nodes); if (BE (err != REG_NOERROR, 0)) return err; } else re_node_set_init_empty (&next_nodes); } if (str_idx == top_str || (cur_state && cur_state->has_backref)) { if (next_nodes.nelem) { err = expand_bkref_cache (mctx, &next_nodes, str_idx, subexp_num, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } } cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context); if (BE (cur_state == NULL && err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } mctx->state_log[str_idx] = cur_state; } for (null_cnt = 0; str_idx < last_str && null_cnt <= mctx->max_mb_elem_len;) { re_node_set_empty (&next_nodes); if (mctx->state_log[str_idx + 1]) { err = re_node_set_merge (&next_nodes, &mctx->state_log[str_idx + 1]->nodes); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } } if (cur_state) { err = check_arrival_add_next_nodes (mctx, str_idx, &cur_state->non_eps_nodes, &next_nodes); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } } ++str_idx; if (next_nodes.nelem) { err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } err = expand_bkref_cache (mctx, &next_nodes, str_idx, subexp_num, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } } context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags); cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context); if (BE (cur_state == NULL && err != REG_NOERROR, 0)) { re_node_set_free (&next_nodes); return err; } mctx->state_log[str_idx] = cur_state; null_cnt = cur_state == NULL ? null_cnt + 1 : 0; } re_node_set_free (&next_nodes); cur_nodes = (mctx->state_log[last_str] == NULL ? NULL : &mctx->state_log[last_str]->nodes); path->next_idx = str_idx; /* Fix MCTX. */ mctx->state_log = backup_state_log; mctx->input.cur_idx = backup_cur_idx; /* Then check the current node set has the node LAST_NODE. */ if (cur_nodes != NULL && re_node_set_contains (cur_nodes, last_node)) return REG_NOERROR; return REG_NOMATCH; } /* Helper functions for check_arrival. */ /* Calculate the destination nodes of CUR_NODES at STR_IDX, and append them to NEXT_NODES. TODO: This function is similar to the functions transit_state*(), however this function has many additional works. Can't we unify them? */ static reg_errcode_t internal_function check_arrival_add_next_nodes (re_match_context_t *mctx, int str_idx, re_node_set *cur_nodes, re_node_set *next_nodes) { const re_dfa_t *const dfa = mctx->dfa; int result; int cur_idx; #ifdef RE_ENABLE_I18N reg_errcode_t err = REG_NOERROR; #endif re_node_set union_set; re_node_set_init_empty (&union_set); for (cur_idx = 0; cur_idx < cur_nodes->nelem; ++cur_idx) { int naccepted = 0; int cur_node = cur_nodes->elems[cur_idx]; #ifdef DEBUG re_token_type_t type = dfa->nodes[cur_node].type; assert (!IS_EPSILON_NODE (type)); #endif #ifdef RE_ENABLE_I18N /* If the node may accept `multi byte'. */ if (dfa->nodes[cur_node].accept_mb) { naccepted = check_node_accept_bytes (dfa, cur_node, &mctx->input, str_idx); if (naccepted > 1) { re_dfastate_t *dest_state; int next_node = dfa->nexts[cur_node]; int next_idx = str_idx + naccepted; dest_state = mctx->state_log[next_idx]; re_node_set_empty (&union_set); if (dest_state) { err = re_node_set_merge (&union_set, &dest_state->nodes); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&union_set); return err; } } result = re_node_set_insert (&union_set, next_node); if (BE (result < 0, 0)) { re_node_set_free (&union_set); return REG_ESPACE; } mctx->state_log[next_idx] = re_acquire_state (&err, dfa, &union_set); if (BE (mctx->state_log[next_idx] == NULL && err != REG_NOERROR, 0)) { re_node_set_free (&union_set); return err; } } } #endif /* RE_ENABLE_I18N */ if (naccepted || check_node_accept (mctx, dfa->nodes + cur_node, str_idx)) { result = re_node_set_insert (next_nodes, dfa->nexts[cur_node]); if (BE (result < 0, 0)) { re_node_set_free (&union_set); return REG_ESPACE; } } } re_node_set_free (&union_set); return REG_NOERROR; } /* For all the nodes in CUR_NODES, add the epsilon closures of them to CUR_NODES, however exclude the nodes which are: - inside the sub expression whose number is EX_SUBEXP, if FL_OPEN. - out of the sub expression whose number is EX_SUBEXP, if !FL_OPEN. */ static reg_errcode_t internal_function check_arrival_expand_ecl (const re_dfa_t *dfa, re_node_set *cur_nodes, int ex_subexp, int type) { reg_errcode_t err; int idx, outside_node; re_node_set new_nodes; #ifdef DEBUG assert (cur_nodes->nelem); #endif err = re_node_set_alloc (&new_nodes, cur_nodes->nelem); if (BE (err != REG_NOERROR, 0)) return err; /* Create a new node set NEW_NODES with the nodes which are epsilon closures of the node in CUR_NODES. */ for (idx = 0; idx < cur_nodes->nelem; ++idx) { int cur_node = cur_nodes->elems[idx]; const re_node_set *eclosure = dfa->eclosures + cur_node; outside_node = find_subexp_node (dfa, eclosure, ex_subexp, type); if (outside_node == -1) { /* There are no problematic nodes, just merge them. */ err = re_node_set_merge (&new_nodes, eclosure); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&new_nodes); return err; } } else { /* There are problematic nodes, re-calculate incrementally. */ err = check_arrival_expand_ecl_sub (dfa, &new_nodes, cur_node, ex_subexp, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&new_nodes); return err; } } } re_node_set_free (cur_nodes); *cur_nodes = new_nodes; return REG_NOERROR; } /* Helper function for check_arrival_expand_ecl. Check incrementally the epsilon closure of TARGET, and if it isn't problematic append it to DST_NODES. */ static reg_errcode_t internal_function check_arrival_expand_ecl_sub (const re_dfa_t *dfa, re_node_set *dst_nodes, int target, int ex_subexp, int type) { int cur_node; for (cur_node = target; !re_node_set_contains (dst_nodes, cur_node);) { int err; if (dfa->nodes[cur_node].type == type && dfa->nodes[cur_node].opr.idx == ex_subexp) { if (type == OP_CLOSE_SUBEXP) { err = re_node_set_insert (dst_nodes, cur_node); if (BE (err == -1, 0)) return REG_ESPACE; } break; } err = re_node_set_insert (dst_nodes, cur_node); if (BE (err == -1, 0)) return REG_ESPACE; if (dfa->edests[cur_node].nelem == 0) break; if (dfa->edests[cur_node].nelem == 2) { err = check_arrival_expand_ecl_sub (dfa, dst_nodes, dfa->edests[cur_node].elems[1], ex_subexp, type); if (BE (err != REG_NOERROR, 0)) return err; } cur_node = dfa->edests[cur_node].elems[0]; } return REG_NOERROR; } /* For all the back references in the current state, calculate the destination of the back references by the appropriate entry in MCTX->BKREF_ENTS. */ static reg_errcode_t internal_function expand_bkref_cache (re_match_context_t *mctx, re_node_set *cur_nodes, int cur_str, int subexp_num, int type) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; int cache_idx_start = search_cur_bkref_entry (mctx, cur_str); struct re_backref_cache_entry *ent; if (cache_idx_start == -1) return REG_NOERROR; restart: ent = mctx->bkref_ents + cache_idx_start; do { int to_idx, next_node; /* Is this entry ENT is appropriate? */ if (!re_node_set_contains (cur_nodes, ent->node)) continue; /* No. */ to_idx = cur_str + ent->subexp_to - ent->subexp_from; /* Calculate the destination of the back reference, and append it to MCTX->STATE_LOG. */ if (to_idx == cur_str) { /* The backreference did epsilon transit, we must re-check all the node in the current state. */ re_node_set new_dests; reg_errcode_t err2, err3; next_node = dfa->edests[ent->node].elems[0]; if (re_node_set_contains (cur_nodes, next_node)) continue; err = re_node_set_init_1 (&new_dests, next_node); err2 = check_arrival_expand_ecl (dfa, &new_dests, subexp_num, type); err3 = re_node_set_merge (cur_nodes, &new_dests); re_node_set_free (&new_dests); if (BE (err != REG_NOERROR || err2 != REG_NOERROR || err3 != REG_NOERROR, 0)) { err = (err != REG_NOERROR ? err : (err2 != REG_NOERROR ? err2 : err3)); return err; } /* TODO: It is still inefficient... */ goto restart; } else { re_node_set union_set; next_node = dfa->nexts[ent->node]; if (mctx->state_log[to_idx]) { int ret; if (re_node_set_contains (&mctx->state_log[to_idx]->nodes, next_node)) continue; err = re_node_set_init_copy (&union_set, &mctx->state_log[to_idx]->nodes); ret = re_node_set_insert (&union_set, next_node); if (BE (err != REG_NOERROR || ret < 0, 0)) { re_node_set_free (&union_set); err = err != REG_NOERROR ? err : REG_ESPACE; return err; } } else { err = re_node_set_init_1 (&union_set, next_node); if (BE (err != REG_NOERROR, 0)) return err; } mctx->state_log[to_idx] = re_acquire_state (&err, dfa, &union_set); re_node_set_free (&union_set); if (BE (mctx->state_log[to_idx] == NULL && err != REG_NOERROR, 0)) return err; } } while (ent++->more); return REG_NOERROR; } /* Build transition table for the state. Return 1 if succeeded, otherwise return NULL. */ static int internal_function build_trtable (const re_dfa_t *dfa, re_dfastate_t *state) { reg_errcode_t err; int i, j, ch, need_word_trtable = 0; bitset_word_t elem, mask; bool dests_node_malloced = false; bool dest_states_malloced = false; int ndests; /* Number of the destination states from `state'. */ re_dfastate_t **trtable; re_dfastate_t **dest_states = NULL, **dest_states_word, **dest_states_nl; re_node_set follows, *dests_node; bitset_t *dests_ch; bitset_t acceptable; struct dests_alloc { re_node_set dests_node[SBC_MAX]; bitset_t dests_ch[SBC_MAX]; } *dests_alloc; /* We build DFA states which corresponds to the destination nodes from `state'. `dests_node[i]' represents the nodes which i-th destination state contains, and `dests_ch[i]' represents the characters which i-th destination state accepts. */ if (__libc_use_alloca (sizeof (struct dests_alloc))) dests_alloc = (struct dests_alloc *) alloca (sizeof (struct dests_alloc)); else { dests_alloc = re_malloc (struct dests_alloc, 1); if (BE (dests_alloc == NULL, 0)) return 0; dests_node_malloced = true; } dests_node = dests_alloc->dests_node; dests_ch = dests_alloc->dests_ch; /* Initialize transiton table. */ state->word_trtable = state->trtable = NULL; /* At first, group all nodes belonging to `state' into several destinations. */ ndests = group_nodes_into_DFAstates (dfa, state, dests_node, dests_ch); if (BE (ndests <= 0, 0)) { if (dests_node_malloced) free (dests_alloc); /* Return 0 in case of an error, 1 otherwise. */ if (ndests == 0) { state->trtable = (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX); return 1; } return 0; } err = re_node_set_alloc (&follows, ndests + 1); if (BE (err != REG_NOERROR, 0)) goto out_free; if (__libc_use_alloca ((sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX + ndests * 3 * sizeof (re_dfastate_t *))) dest_states = (re_dfastate_t **) alloca (ndests * 3 * sizeof (re_dfastate_t *)); else { dest_states = (re_dfastate_t **) malloc (ndests * 3 * sizeof (re_dfastate_t *)); if (BE (dest_states == NULL, 0)) { out_free: if (dest_states_malloced) free (dest_states); re_node_set_free (&follows); for (i = 0; i < ndests; ++i) re_node_set_free (dests_node + i); if (dests_node_malloced) free (dests_alloc); return 0; } dest_states_malloced = true; } dest_states_word = dest_states + ndests; dest_states_nl = dest_states_word + ndests; bitset_empty (acceptable); /* Then build the states for all destinations. */ for (i = 0; i < ndests; ++i) { int next_node; re_node_set_empty (&follows); /* Merge the follows of this destination states. */ for (j = 0; j < dests_node[i].nelem; ++j) { next_node = dfa->nexts[dests_node[i].elems[j]]; if (next_node != -1) { err = re_node_set_merge (&follows, dfa->eclosures + next_node); if (BE (err != REG_NOERROR, 0)) goto out_free; } } dest_states[i] = re_acquire_state_context (&err, dfa, &follows, 0); if (BE (dest_states[i] == NULL && err != REG_NOERROR, 0)) goto out_free; /* If the new state has context constraint, build appropriate states for these contexts. */ if (dest_states[i]->has_constraint) { dest_states_word[i] = re_acquire_state_context (&err, dfa, &follows, CONTEXT_WORD); if (BE (dest_states_word[i] == NULL && err != REG_NOERROR, 0)) goto out_free; if (dest_states[i] != dest_states_word[i] && dfa->mb_cur_max > 1) need_word_trtable = 1; dest_states_nl[i] = re_acquire_state_context (&err, dfa, &follows, CONTEXT_NEWLINE); if (BE (dest_states_nl[i] == NULL && err != REG_NOERROR, 0)) goto out_free; } else { dest_states_word[i] = dest_states[i]; dest_states_nl[i] = dest_states[i]; } bitset_merge (acceptable, dests_ch[i]); } if (!BE (need_word_trtable, 0)) { /* We don't care about whether the following character is a word character, or we are in a single-byte character set so we can discern by looking at the character code: allocate a 256-entry transition table. */ trtable = state->trtable = (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX); if (BE (trtable == NULL, 0)) goto out_free; /* For all characters ch...: */ for (i = 0; i < BITSET_WORDS; ++i) for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1; elem; mask <<= 1, elem >>= 1, ++ch) if (BE (elem & 1, 0)) { /* There must be exactly one destination which accepts character ch. See group_nodes_into_DFAstates. */ for (j = 0; (dests_ch[j][i] & mask) == 0; ++j) ; /* j-th destination accepts the word character ch. */ if (dfa->word_char[i] & mask) trtable[ch] = dest_states_word[j]; else trtable[ch] = dest_states[j]; } } else { /* We care about whether the following character is a word character, and we are in a multi-byte character set: discern by looking at the character code: build two 256-entry transition tables, one starting at trtable[0] and one starting at trtable[SBC_MAX]. */ trtable = state->word_trtable = (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), 2 * SBC_MAX); if (BE (trtable == NULL, 0)) goto out_free; /* For all characters ch...: */ for (i = 0; i < BITSET_WORDS; ++i) for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1; elem; mask <<= 1, elem >>= 1, ++ch) if (BE (elem & 1, 0)) { /* There must be exactly one destination which accepts character ch. See group_nodes_into_DFAstates. */ for (j = 0; (dests_ch[j][i] & mask) == 0; ++j) ; /* j-th destination accepts the word character ch. */ trtable[ch] = dest_states[j]; trtable[ch + SBC_MAX] = dest_states_word[j]; } } /* new line */ if (bitset_contain (acceptable, NEWLINE_CHAR)) { /* The current state accepts newline character. */ for (j = 0; j < ndests; ++j) if (bitset_contain (dests_ch[j], NEWLINE_CHAR)) { /* k-th destination accepts newline character. */ trtable[NEWLINE_CHAR] = dest_states_nl[j]; if (need_word_trtable) trtable[NEWLINE_CHAR + SBC_MAX] = dest_states_nl[j]; /* There must be only one destination which accepts newline. See group_nodes_into_DFAstates. */ break; } } if (dest_states_malloced) free (dest_states); re_node_set_free (&follows); for (i = 0; i < ndests; ++i) re_node_set_free (dests_node + i); if (dests_node_malloced) free (dests_alloc); return 1; } /* Group all nodes belonging to STATE into several destinations. Then for all destinations, set the nodes belonging to the destination to DESTS_NODE[i] and set the characters accepted by the destination to DEST_CH[i]. This function return the number of destinations. */ static int internal_function group_nodes_into_DFAstates (const re_dfa_t *dfa, const re_dfastate_t *state, re_node_set *dests_node, bitset_t *dests_ch) { reg_errcode_t err; int result; int i, j, k; int ndests; /* Number of the destinations from `state'. */ bitset_t accepts; /* Characters a node can accept. */ const re_node_set *cur_nodes = &state->nodes; bitset_empty (accepts); ndests = 0; /* For all the nodes belonging to `state', */ for (i = 0; i < cur_nodes->nelem; ++i) { re_token_t *node = &dfa->nodes[cur_nodes->elems[i]]; re_token_type_t type = node->type; unsigned int constraint = node->constraint; /* Enumerate all single byte character this node can accept. */ if (type == CHARACTER) bitset_set (accepts, node->opr.c); else if (type == SIMPLE_BRACKET) { bitset_merge (accepts, node->opr.sbcset); } else if (type == OP_PERIOD) { #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) bitset_merge (accepts, dfa->sb_char); else #endif bitset_set_all (accepts); if (!(dfa->syntax & RE_DOT_NEWLINE)) bitset_clear (accepts, '\n'); if (dfa->syntax & RE_DOT_NOT_NULL) bitset_clear (accepts, '\0'); } #ifdef RE_ENABLE_I18N else if (type == OP_UTF8_PERIOD) { memset (accepts, '\xff', sizeof (bitset_t) / 2); if (!(dfa->syntax & RE_DOT_NEWLINE)) bitset_clear (accepts, '\n'); if (dfa->syntax & RE_DOT_NOT_NULL) bitset_clear (accepts, '\0'); } #endif else continue; /* Check the `accepts' and sift the characters which are not match it the context. */ if (constraint) { if (constraint & NEXT_NEWLINE_CONSTRAINT) { bool accepts_newline = bitset_contain (accepts, NEWLINE_CHAR); bitset_empty (accepts); if (accepts_newline) bitset_set (accepts, NEWLINE_CHAR); else continue; } if (constraint & NEXT_ENDBUF_CONSTRAINT) { bitset_empty (accepts); continue; } if (constraint & NEXT_WORD_CONSTRAINT) { bitset_word_t any_set = 0; if (type == CHARACTER && !node->word_char) { bitset_empty (accepts); continue; } #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) for (j = 0; j < BITSET_WORDS; ++j) any_set |= (accepts[j] &= (dfa->word_char[j] | ~dfa->sb_char[j])); else #endif for (j = 0; j < BITSET_WORDS; ++j) any_set |= (accepts[j] &= dfa->word_char[j]); if (!any_set) continue; } if (constraint & NEXT_NOTWORD_CONSTRAINT) { bitset_word_t any_set = 0; if (type == CHARACTER && node->word_char) { bitset_empty (accepts); continue; } #ifdef RE_ENABLE_I18N if (dfa->mb_cur_max > 1) for (j = 0; j < BITSET_WORDS; ++j) any_set |= (accepts[j] &= ~(dfa->word_char[j] & dfa->sb_char[j])); else #endif for (j = 0; j < BITSET_WORDS; ++j) any_set |= (accepts[j] &= ~dfa->word_char[j]); if (!any_set) continue; } } /* Then divide `accepts' into DFA states, or create a new state. Above, we make sure that accepts is not empty. */ for (j = 0; j < ndests; ++j) { bitset_t intersec; /* Intersection sets, see below. */ bitset_t remains; /* Flags, see below. */ bitset_word_t has_intersec, not_subset, not_consumed; /* Optimization, skip if this state doesn't accept the character. */ if (type == CHARACTER && !bitset_contain (dests_ch[j], node->opr.c)) continue; /* Enumerate the intersection set of this state and `accepts'. */ has_intersec = 0; for (k = 0; k < BITSET_WORDS; ++k) has_intersec |= intersec[k] = accepts[k] & dests_ch[j][k]; /* And skip if the intersection set is empty. */ if (!has_intersec) continue; /* Then check if this state is a subset of `accepts'. */ not_subset = not_consumed = 0; for (k = 0; k < BITSET_WORDS; ++k) { not_subset |= remains[k] = ~accepts[k] & dests_ch[j][k]; not_consumed |= accepts[k] = accepts[k] & ~dests_ch[j][k]; } /* If this state isn't a subset of `accepts', create a new group state, which has the `remains'. */ if (not_subset) { bitset_copy (dests_ch[ndests], remains); bitset_copy (dests_ch[j], intersec); err = re_node_set_init_copy (dests_node + ndests, &dests_node[j]); if (BE (err != REG_NOERROR, 0)) goto error_return; ++ndests; } /* Put the position in the current group. */ result = re_node_set_insert (&dests_node[j], cur_nodes->elems[i]); if (BE (result < 0, 0)) goto error_return; /* If all characters are consumed, go to next node. */ if (!not_consumed) break; } /* Some characters remain, create a new group. */ if (j == ndests) { bitset_copy (dests_ch[ndests], accepts); err = re_node_set_init_1 (dests_node + ndests, cur_nodes->elems[i]); if (BE (err != REG_NOERROR, 0)) goto error_return; ++ndests; bitset_empty (accepts); } } return ndests; error_return: for (j = 0; j < ndests; ++j) re_node_set_free (dests_node + j); return -1; } #ifdef RE_ENABLE_I18N /* Check how many bytes the node `dfa->nodes[node_idx]' accepts. Return the number of the bytes the node accepts. STR_IDX is the current index of the input string. This function handles the nodes which can accept one character, or one collating element like '.', '[a-z]', opposite to the other nodes can only accept one byte. */ static int internal_function check_node_accept_bytes (const re_dfa_t *dfa, int node_idx, const re_string_t *input, int str_idx) { const re_token_t *node = dfa->nodes + node_idx; int char_len, elem_len; int i; if (BE (node->type == OP_UTF8_PERIOD, 0)) { unsigned char c = re_string_byte_at (input, str_idx), d; if (BE (c < 0xc2, 1)) return 0; if (str_idx + 2 > input->len) return 0; d = re_string_byte_at (input, str_idx + 1); if (c < 0xe0) return (d < 0x80 || d > 0xbf) ? 0 : 2; else if (c < 0xf0) { char_len = 3; if (c == 0xe0 && d < 0xa0) return 0; } else if (c < 0xf8) { char_len = 4; if (c == 0xf0 && d < 0x90) return 0; } else if (c < 0xfc) { char_len = 5; if (c == 0xf8 && d < 0x88) return 0; } else if (c < 0xfe) { char_len = 6; if (c == 0xfc && d < 0x84) return 0; } else return 0; if (str_idx + char_len > input->len) return 0; for (i = 1; i < char_len; ++i) { d = re_string_byte_at (input, str_idx + i); if (d < 0x80 || d > 0xbf) return 0; } return char_len; } char_len = re_string_char_size_at (input, str_idx); if (node->type == OP_PERIOD) { if (char_len <= 1) return 0; /* FIXME: I don't think this if is needed, as both '\n' and '\0' are char_len == 1. */ /* '.' accepts any one character except the following two cases. */ if ((!(dfa->syntax & RE_DOT_NEWLINE) && re_string_byte_at (input, str_idx) == '\n') || ((dfa->syntax & RE_DOT_NOT_NULL) && re_string_byte_at (input, str_idx) == '\0')) return 0; return char_len; } elem_len = re_string_elem_size_at (input, str_idx); if ((elem_len <= 1 && char_len <= 1) || char_len == 0) return 0; if (node->type == COMPLEX_BRACKET) { const re_charset_t *cset = node->opr.mbcset; # ifdef _LIBC const unsigned char *pin = ((const unsigned char *) re_string_get_buffer (input) + str_idx); int j; uint32_t nrules; # endif /* _LIBC */ int match_len = 0; wchar_t wc = ((cset->nranges || cset->nchar_classes || cset->nmbchars) ? re_string_wchar_at (input, str_idx) : 0); /* match with multibyte character? */ for (i = 0; i < cset->nmbchars; ++i) if (wc == cset->mbchars[i]) { match_len = char_len; goto check_node_accept_bytes_match; } /* match with character_class? */ for (i = 0; i < cset->nchar_classes; ++i) { wctype_t wt = cset->char_classes[i]; if (__iswctype (wc, wt)) { match_len = char_len; goto check_node_accept_bytes_match; } } # ifdef _LIBC nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules != 0) { unsigned int in_collseq = 0; const int32_t *table, *indirect; const unsigned char *weights, *extra; const char *collseqwc; /* This #include defines a local function! */ # include /* match with collating_symbol? */ if (cset->ncoll_syms) extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); for (i = 0; i < cset->ncoll_syms; ++i) { const unsigned char *coll_sym = extra + cset->coll_syms[i]; /* Compare the length of input collating element and the length of current collating element. */ if (*coll_sym != elem_len) continue; /* Compare each bytes. */ for (j = 0; j < *coll_sym; j++) if (pin[j] != coll_sym[1 + j]) break; if (j == *coll_sym) { /* Match if every bytes is equal. */ match_len = j; goto check_node_accept_bytes_match; } } if (cset->nranges) { if (elem_len <= char_len) { collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC); in_collseq = __collseq_table_lookup (collseqwc, wc); } else in_collseq = find_collation_sequence_value (pin, elem_len); } /* match with range expression? */ for (i = 0; i < cset->nranges; ++i) if (cset->range_starts[i] <= in_collseq && in_collseq <= cset->range_ends[i]) { match_len = elem_len; goto check_node_accept_bytes_match; } /* match with equivalence_class? */ if (cset->nequiv_classes) { const unsigned char *cp = pin; table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); weights = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB); int32_t idx = findidx (&cp); if (idx > 0) for (i = 0; i < cset->nequiv_classes; ++i) { int32_t equiv_class_idx = cset->equiv_classes[i]; size_t weight_len = weights[idx & 0xffffff]; if (weight_len == weights[equiv_class_idx & 0xffffff] && (idx >> 24) == (equiv_class_idx >> 24)) { int cnt = 0; idx &= 0xffffff; equiv_class_idx &= 0xffffff; while (cnt <= weight_len && (weights[equiv_class_idx + 1 + cnt] == weights[idx + 1 + cnt])) ++cnt; if (cnt > weight_len) { match_len = elem_len; goto check_node_accept_bytes_match; } } } } } else # endif /* _LIBC */ { /* match with range expression? */ #if __GNUC__ >= 2 wchar_t cmp_buf[] = {L'\0', L'\0', wc, L'\0', L'\0', L'\0'}; #else wchar_t cmp_buf[] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'}; cmp_buf[2] = wc; #endif for (i = 0; i < cset->nranges; ++i) { cmp_buf[0] = cset->range_starts[i]; cmp_buf[4] = cset->range_ends[i]; if (wcscoll (cmp_buf, cmp_buf + 2) <= 0 && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0) { match_len = char_len; goto check_node_accept_bytes_match; } } } check_node_accept_bytes_match: if (!cset->non_match) return match_len; else { if (match_len > 0) return 0; else return (elem_len > char_len) ? elem_len : char_len; } } return 0; } # ifdef _LIBC static unsigned int internal_function find_collation_sequence_value (const unsigned char *mbs, size_t mbs_len) { uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules == 0) { if (mbs_len == 1) { /* No valid character. Match it as a single byte character. */ const unsigned char *collseq = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB); return collseq[mbs[0]]; } return UINT_MAX; } else { int32_t idx; const unsigned char *extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); int32_t extrasize = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB + 1) - extra; for (idx = 0; idx < extrasize;) { int mbs_cnt, found = 0; int32_t elem_mbs_len; /* Skip the name of collating element name. */ idx = idx + extra[idx] + 1; elem_mbs_len = extra[idx++]; if (mbs_len == elem_mbs_len) { for (mbs_cnt = 0; mbs_cnt < elem_mbs_len; ++mbs_cnt) if (extra[idx + mbs_cnt] != mbs[mbs_cnt]) break; if (mbs_cnt == elem_mbs_len) /* Found the entry. */ found = 1; } /* Skip the byte sequence of the collating element. */ idx += elem_mbs_len; /* Adjust for the alignment. */ idx = (idx + 3) & ~3; /* Skip the collation sequence value. */ idx += sizeof (uint32_t); /* Skip the wide char sequence of the collating element. */ idx = idx + sizeof (uint32_t) * (extra[idx] + 1); /* If we found the entry, return the sequence value. */ if (found) return *(uint32_t *) (extra + idx); /* Skip the collation sequence value. */ idx += sizeof (uint32_t); } return UINT_MAX; } } # endif /* _LIBC */ #endif /* RE_ENABLE_I18N */ /* Check whether the node accepts the byte which is IDX-th byte of the INPUT. */ static int internal_function check_node_accept (const re_match_context_t *mctx, const re_token_t *node, int idx) { unsigned char ch; ch = re_string_byte_at (&mctx->input, idx); switch (node->type) { case CHARACTER: if (node->opr.c != ch) return 0; break; case SIMPLE_BRACKET: if (!bitset_contain (node->opr.sbcset, ch)) return 0; break; #ifdef RE_ENABLE_I18N case OP_UTF8_PERIOD: if (ch >= 0x80) return 0; /* FALLTHROUGH */ #endif case OP_PERIOD: if ((ch == '\n' && !(mctx->dfa->syntax & RE_DOT_NEWLINE)) || (ch == '\0' && (mctx->dfa->syntax & RE_DOT_NOT_NULL))) return 0; break; default: return 0; } if (node->constraint) { /* The node has constraints. Check whether the current context satisfies the constraints. */ unsigned int context = re_string_context_at (&mctx->input, idx, mctx->eflags); if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context)) return 0; } return 1; } /* Extend the buffers, if the buffers have run out. */ static reg_errcode_t internal_function extend_buffers (re_match_context_t *mctx) { reg_errcode_t ret; re_string_t *pstr = &mctx->input; /* Double the lengthes of the buffers. */ ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2); if (BE (ret != REG_NOERROR, 0)) return ret; if (mctx->state_log != NULL) { /* And double the length of state_log. */ /* XXX We have no indication of the size of this buffer. If this allocation fail we have no indication that the state_log array does not have the right size. */ re_dfastate_t **new_array = re_realloc (mctx->state_log, re_dfastate_t *, pstr->bufs_len + 1); if (BE (new_array == NULL, 0)) return REG_ESPACE; mctx->state_log = new_array; } /* Then reconstruct the buffers. */ if (pstr->icase) { #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) { ret = build_wcs_upper_buffer (pstr); if (BE (ret != REG_NOERROR, 0)) return ret; } else #endif /* RE_ENABLE_I18N */ build_upper_buffer (pstr); } else { #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) build_wcs_buffer (pstr); else #endif /* RE_ENABLE_I18N */ { if (pstr->trans != NULL) re_string_translate_buffer (pstr); } } return REG_NOERROR; } /* Functions for matching context. */ /* Initialize MCTX. */ static reg_errcode_t internal_function match_ctx_init (re_match_context_t *mctx, int eflags, int n) { mctx->eflags = eflags; mctx->match_last = -1; if (n > 0) { mctx->bkref_ents = re_malloc (struct re_backref_cache_entry, n); mctx->sub_tops = re_malloc (re_sub_match_top_t *, n); if (BE (mctx->bkref_ents == NULL || mctx->sub_tops == NULL, 0)) return REG_ESPACE; } /* Already zero-ed by the caller. else mctx->bkref_ents = NULL; mctx->nbkref_ents = 0; mctx->nsub_tops = 0; */ mctx->abkref_ents = n; mctx->max_mb_elem_len = 1; mctx->asub_tops = n; return REG_NOERROR; } /* Clean the entries which depend on the current input in MCTX. This function must be invoked when the matcher changes the start index of the input, or changes the input string. */ static void internal_function match_ctx_clean (re_match_context_t *mctx) { int st_idx; for (st_idx = 0; st_idx < mctx->nsub_tops; ++st_idx) { int sl_idx; re_sub_match_top_t *top = mctx->sub_tops[st_idx]; for (sl_idx = 0; sl_idx < top->nlasts; ++sl_idx) { re_sub_match_last_t *last = top->lasts[sl_idx]; re_free (last->path.array); re_free (last); } re_free (top->lasts); if (top->path) { re_free (top->path->array); re_free (top->path); } free (top); } mctx->nsub_tops = 0; mctx->nbkref_ents = 0; } /* Free all the memory associated with MCTX. */ static void internal_function match_ctx_free (re_match_context_t *mctx) { /* First, free all the memory associated with MCTX->SUB_TOPS. */ match_ctx_clean (mctx); re_free (mctx->sub_tops); re_free (mctx->bkref_ents); } /* Add a new backreference entry to MCTX. Note that we assume that caller never call this function with duplicate entry, and call with STR_IDX which isn't smaller than any existing entry. */ static reg_errcode_t internal_function match_ctx_add_entry (re_match_context_t *mctx, int node, int str_idx, int from, int to) { if (mctx->nbkref_ents >= mctx->abkref_ents) { struct re_backref_cache_entry* new_entry; new_entry = re_realloc (mctx->bkref_ents, struct re_backref_cache_entry, mctx->abkref_ents * 2); if (BE (new_entry == NULL, 0)) { re_free (mctx->bkref_ents); return REG_ESPACE; } mctx->bkref_ents = new_entry; memset (mctx->bkref_ents + mctx->nbkref_ents, '\0', sizeof (struct re_backref_cache_entry) * mctx->abkref_ents); mctx->abkref_ents *= 2; } if (mctx->nbkref_ents > 0 && mctx->bkref_ents[mctx->nbkref_ents - 1].str_idx == str_idx) mctx->bkref_ents[mctx->nbkref_ents - 1].more = 1; mctx->bkref_ents[mctx->nbkref_ents].node = node; mctx->bkref_ents[mctx->nbkref_ents].str_idx = str_idx; mctx->bkref_ents[mctx->nbkref_ents].subexp_from = from; mctx->bkref_ents[mctx->nbkref_ents].subexp_to = to; /* This is a cache that saves negative results of check_dst_limits_calc_pos. If bit N is clear, means that this entry won't epsilon-transition to an OP_OPEN_SUBEXP or OP_CLOSE_SUBEXP for the N+1-th subexpression. If it is set, check_dst_limits_calc_pos_1 will recurse and try to find one such node. A backreference does not epsilon-transition unless it is empty, so set to all zeros if FROM != TO. */ mctx->bkref_ents[mctx->nbkref_ents].eps_reachable_subexps_map = (from == to ? ~0 : 0); mctx->bkref_ents[mctx->nbkref_ents++].more = 0; if (mctx->max_mb_elem_len < to - from) mctx->max_mb_elem_len = to - from; return REG_NOERROR; } /* Search for the first entry which has the same str_idx, or -1 if none is found. Note that MCTX->BKREF_ENTS is already sorted by MCTX->STR_IDX. */ static int internal_function search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx) { int left, right, mid, last; last = right = mctx->nbkref_ents; for (left = 0; left < right;) { mid = (left + right) / 2; if (mctx->bkref_ents[mid].str_idx < str_idx) left = mid + 1; else right = mid; } if (left < last && mctx->bkref_ents[left].str_idx == str_idx) return left; else return -1; } /* Register the node NODE, whose type is OP_OPEN_SUBEXP, and which matches at STR_IDX. */ static reg_errcode_t internal_function match_ctx_add_subtop (re_match_context_t *mctx, int node, int str_idx) { #ifdef DEBUG assert (mctx->sub_tops != NULL); assert (mctx->asub_tops > 0); #endif if (BE (mctx->nsub_tops == mctx->asub_tops, 0)) { int new_asub_tops = mctx->asub_tops * 2; re_sub_match_top_t **new_array = re_realloc (mctx->sub_tops, re_sub_match_top_t *, new_asub_tops); if (BE (new_array == NULL, 0)) return REG_ESPACE; mctx->sub_tops = new_array; mctx->asub_tops = new_asub_tops; } mctx->sub_tops[mctx->nsub_tops] = calloc (1, sizeof (re_sub_match_top_t)); if (BE (mctx->sub_tops[mctx->nsub_tops] == NULL, 0)) return REG_ESPACE; mctx->sub_tops[mctx->nsub_tops]->node = node; mctx->sub_tops[mctx->nsub_tops++]->str_idx = str_idx; return REG_NOERROR; } /* Register the node NODE, whose type is OP_CLOSE_SUBEXP, and which matches at STR_IDX, whose corresponding OP_OPEN_SUBEXP is SUB_TOP. */ static re_sub_match_last_t * internal_function match_ctx_add_sublast (re_sub_match_top_t *subtop, int node, int str_idx) { re_sub_match_last_t *new_entry; if (BE (subtop->nlasts == subtop->alasts, 0)) { int new_alasts = 2 * subtop->alasts + 1; re_sub_match_last_t **new_array = re_realloc (subtop->lasts, re_sub_match_last_t *, new_alasts); if (BE (new_array == NULL, 0)) return NULL; subtop->lasts = new_array; subtop->alasts = new_alasts; } new_entry = calloc (1, sizeof (re_sub_match_last_t)); if (BE (new_entry != NULL, 1)) { subtop->lasts[subtop->nlasts] = new_entry; new_entry->node = node; new_entry->str_idx = str_idx; ++subtop->nlasts; } return new_entry; } static void internal_function sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts, re_dfastate_t **limited_sts, int last_node, int last_str_idx) { sctx->sifted_states = sifted_sts; sctx->limited_states = limited_sts; sctx->last_node = last_node; sctx->last_str_idx = last_str_idx; re_node_set_init_empty (&sctx->limits); } direwolf-1.5+dfsg/rpack.h000066400000000000000000000033261347750676600154130ustar00rootroot00000000000000 /*------------------------------------------------------------------ * * File: rpack.h * * Purpose: Definition of Garmin Rino message format. * * References: http://www.radio-active.net.au/web3/APRS/Resources/RINO * * http://www.radio-active.net.au/web3/APRS/Resources/RINO/OnAir * *---------------------------------------------------------------*/ #ifndef RPACK_H #define RPACK_H 1 #define RPACK_FRAME_LEN 168 #ifdef RPACK_C /* Expose private details */ // Transmission order is LSB first. struct __attribute__((__packed__)) rpack_s { int lat; // Latitude. // Signed integer. Scaled by 2**30/90. int lon; // Longitude. Same encoding. char unknown1; // Unproven theory: altitude. char unknown2; unsigned name0:6; // 10 character name. unsigned name1:6; // Bit packing is implementation dependent. unsigned name2:6; // Should rewrite to be more portable. unsigned name3:6; unsigned name4:6; unsigned name5:6; unsigned name6:6; unsigned name7:6; unsigned name8:6; unsigned name9:6; unsigned symbol:5; unsigned unknown3:7; // unsigned crc:16; // Safe bet this is CRC for error checking. unsigned char crc1; unsigned char crc2; char dummy[3]; // Total size should be 24 bytes if no gaps. }; #else /* Show only public interface. */ struct rpack_s { char stuff[24]; }; #endif void rpack_set_bit (struct rpack_s *rp, int position, int value); int rpack_is_valid (struct rpack_s *rp); int rpack_get_bit (struct rpack_s *rp, int position); double rpack_get_lat (struct rpack_s *rp); double rpack_get_lon (struct rpack_s *rp); int rpack_get_symbol (struct rpack_s *rp); void rpack_get_name (struct rpack_s *rp, char *str); #endif /* end rpack.h */ direwolf-1.5+dfsg/rrbb.c000066400000000000000000000265541347750676600152450ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /******************************************************************************** * * File: rrbb.c * * Purpose: Raw Received Bit Buffer. * An array of bits used to hold data out of * the demodulator before feeding it into the HLDC decoding. * * Version 1.2: Save initial state of 9600 baud descrambler so we can * attempt bit fix up on G3RUH/K9NG scrambled data. * * Version 1.3: Store as bytes rather than packing 8 bits per byte. * *******************************************************************************/ #define RRBB_C #include "direwolf.h" #include #include #include #include #include "textcolor.h" #include "ax25_pad.h" #include "rrbb.h" #define MAGIC1 0x12344321 #define MAGIC2 0x56788765 static int new_count = 0; static int delete_count = 0; /*********************************************************************************** * * Name: rrbb_new * * Purpose: Allocate space for an array of samples. * * Inputs: chan - Radio channel from whence it came. * * subchan - Which demodulator of the channel. * * slice - multiple thresholds per demodulator. * * is_scrambled - Is data scrambled? (true, false) * * descram_state - State of data descrambler. * * prev_descram - Previous descrambled bit. * * Returns: Handle to be used by other functions. * * Description: * ***********************************************************************************/ rrbb_t rrbb_new (int chan, int subchan, int slice, int is_scrambled, int descram_state, int prev_descram) { rrbb_t result; assert (chan >= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); result = malloc(sizeof(struct rrbb_s)); result->magic1 = MAGIC1; result->chan = chan; result->subchan = subchan; result->slice = slice; result->magic2 = MAGIC2; new_count++; if (new_count > delete_count + 100) { text_color_set(DW_COLOR_ERROR); dw_printf ("MEMORY LEAK, rrbb_new, new_count=%d, delete_count=%d\n", new_count, delete_count); } rrbb_clear (result, is_scrambled, descram_state, prev_descram); return (result); } /*********************************************************************************** * * Name: rrbb_clear * * Purpose: Clear by setting length to zero, etc. * * Inputs: b -Handle for sample array. * * is_scrambled - Is data scrambled? (true, false) * * descram_state - State of data descrambler. * * prev_descram - Previous descrambled bit. * ***********************************************************************************/ void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); assert (is_scrambled == 0 || is_scrambled == 1); assert (prev_descram == 0 || prev_descram == 1); b->nextp = NULL; b->alevel.rec = 9999; // TODO: was there some reason for this instead of 0 or -1? b->alevel.mark = 9999; b->alevel.space = 9999; b->len = 0; b->is_scrambled = is_scrambled; b->descram_state = descram_state; b->prev_descram = prev_descram; } /*********************************************************************************** * * Name: rrbb_append_bit * * Purpose: Append another bit to the end. * * Inputs: Handle for sample array. * Value for the sample. * ***********************************************************************************/ /* Definition in header file so it can be inlined. */ /*********************************************************************************** * * Name: rrbb_chop8 * * Purpose: Remove 8 from the length. * * Inputs: Handle for bit array. * * Description: Back up after appending the flag sequence. * ***********************************************************************************/ void rrbb_chop8 (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); if (b->len >= 8) { b->len -= 8; } } /*********************************************************************************** * * Name: rrbb_get_len * * Purpose: Get number of bits in the array. * * Inputs: Handle for bit array. * ***********************************************************************************/ int rrbb_get_len (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); return (b->len); } /*********************************************************************************** * * Name: rrbb_get_bit * * Purpose: Get value of bit in specified position. * * Inputs: Handle for sample array. * Index into array. * ***********************************************************************************/ /* Definition in header file so it can be inlined. */ /*********************************************************************************** * * Name: rrbb_flip_bit * * Purpose: Complement the value of bit in specified position. * * Inputs: Handle for bit array. * Index into array. * ***********************************************************************************/ //void rrbb_flip_bit (rrbb_t b, unsigned int ind) //{ // unsigned int di, mi; // // assert (b != NULL); // assert (b->magic1 == MAGIC1); // assert (b->magic2 == MAGIC2); // // assert (ind < b->len); // // di = ind / SOI; // mi = ind % SOI; // // b->data[di] ^= masks[mi]; //} /*********************************************************************************** * * Name: rrbb_delete * * Purpose: Free the storage associated with the bit array. * * Inputs: Handle for bit array. * ***********************************************************************************/ void rrbb_delete (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); b->magic1 = 0; b->magic2 = 0; free (b); delete_count++; } /*********************************************************************************** * * Name: rrbb_set_netxp * * Purpose: Set the nextp field, used to maintain a queue. * * Inputs: b Handle for bit array. * np New value for nextp. * ***********************************************************************************/ void rrbb_set_nextp (rrbb_t b, rrbb_t np) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); b->nextp = np; } /*********************************************************************************** * * Name: rrbb_get_netxp * * Purpose: Get value of nextp field. * * Inputs: b Handle for bit array. * ***********************************************************************************/ rrbb_t rrbb_get_nextp (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); return (b->nextp); } /*********************************************************************************** * * Name: rrbb_get_chan * * Purpose: Get channel from which bit buffer was received. * * Inputs: b Handle for bit array. * ***********************************************************************************/ int rrbb_get_chan (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); assert (b->chan >= 0 && b->chan < MAX_CHANS); return (b->chan); } /*********************************************************************************** * * Name: rrbb_get_subchan * * Purpose: Get subchannel from which bit buffer was received. * * Inputs: b Handle for bit array. * ***********************************************************************************/ int rrbb_get_subchan (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); assert (b->subchan >= 0 && b->subchan < MAX_SUBCHANS); return (b->subchan); } /*********************************************************************************** * * Name: rrbb_get_slice * * Purpose: Get slice number from which bit buffer was received. * * Inputs: b Handle for bit array. * ***********************************************************************************/ int rrbb_get_slice (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); assert (b->slice >= 0 && b->slice < MAX_SLICERS); return (b->slice); } /*********************************************************************************** * * Name: rrbb_set_audio_level * * Purpose: Set audio level at time the frame was received. * * Inputs: b Handle for bit array. * alevel Audio level. * ***********************************************************************************/ void rrbb_set_audio_level (rrbb_t b, alevel_t alevel) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); b->alevel = alevel; } /*********************************************************************************** * * Name: rrbb_get_audio_level * * Purpose: Get audio level at time the frame was received. * * Inputs: b Handle for bit array. * ***********************************************************************************/ alevel_t rrbb_get_audio_level (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); return (b->alevel); } /*********************************************************************************** * * Name: rrbb_get_is_scrambled * * Purpose: Find out if using scrambled data. * * Inputs: b Handle for bit array. * * Returns: True (for 9600 baud) or false (for slower AFSK). * ***********************************************************************************/ int rrbb_get_is_scrambled (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); return (b->is_scrambled); } /*********************************************************************************** * * Name: rrbb_get_descram_state * * Purpose: Get data descrambler state before first data bit of frame. * * Inputs: b Handle for bit array. * ***********************************************************************************/ int rrbb_get_descram_state (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); return (b->descram_state); } /*********************************************************************************** * * Name: rrbb_get_prev_descram * * Purpose: Get previous descrambled bit before first data bit of frame. * * Inputs: b Handle for bit array. * ***********************************************************************************/ int rrbb_get_prev_descram (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); return (b->prev_descram); } /* end rrbb.c */ direwolf-1.5+dfsg/rrbb.h000066400000000000000000000042341347750676600152410ustar00rootroot00000000000000 #ifndef RRBB_H #define RRBB_H #define FASTER13 1 // Don't pack 8 samples per byte. //typedef short slice_t; /* * Maximum size (in bytes) of an AX.25 frame including the 2 octet FCS. */ #define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2) /* * Maximum number of bits in AX.25 frame excluding the flags. * Adequate for extreme case of bit stuffing after every 5 bits * which could never happen. */ #define MAX_NUM_BITS (MAX_FRAME_LEN * 8 * 6 / 5) typedef struct rrbb_s { int magic1; struct rrbb_s* nextp; /* Next pointer to maintain a queue. */ int chan; /* Radio channel from which it was received. */ int subchan; /* Which modem when more than one per channel. */ int slice; /* Which slicer. */ alevel_t alevel; /* Received audio level at time of frame capture. */ unsigned int len; /* Current number of samples in array. */ int is_scrambled; /* Is data scrambled G3RUH / K9NG style? */ int descram_state; /* Descrambler state before first data bit of frame. */ int prev_descram; /* Previous descrambled bit. */ unsigned char fdata[MAX_NUM_BITS]; int magic2; } *rrbb_t; rrbb_t rrbb_new (int chan, int subchan, int slice, int is_scrambled, int descram_state, int prev_descram); void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram); static inline /*__attribute__((always_inline))*/ void rrbb_append_bit (rrbb_t b, const unsigned char val) { if (b->len >= MAX_NUM_BITS) { return; /* Silently discard if full. */ } b->fdata[b->len] = val; b->len++; } static inline /*__attribute__((always_inline))*/ unsigned char rrbb_get_bit (const rrbb_t b, const int ind) { return (b->fdata[ind]); } void rrbb_chop8 (rrbb_t b); int rrbb_get_len (rrbb_t b); //void rrbb_flip_bit (rrbb_t b, unsigned int ind); void rrbb_delete (rrbb_t b); void rrbb_set_nextp (rrbb_t b, rrbb_t np); rrbb_t rrbb_get_nextp (rrbb_t b); int rrbb_get_chan (rrbb_t b); int rrbb_get_subchan (rrbb_t b); int rrbb_get_slice (rrbb_t b); void rrbb_set_audio_level (rrbb_t b, alevel_t alevel); alevel_t rrbb_get_audio_level (rrbb_t b); int rrbb_get_is_scrambled (rrbb_t b); int rrbb_get_descram_state (rrbb_t b); int rrbb_get_prev_descram (rrbb_t b); #endif direwolf-1.5+dfsg/sdr.conf000066400000000000000000000015011347750676600155720ustar00rootroot00000000000000# # Sample configuration for SDR read-only IGate. # # We might not have an audio output device so set to null. # We will override the input half on the command line. ADEVICE null null CHANNEL 0 MYCALL xxx # First you need to specify the name of a Tier 2 server. # The current preferred way is to use one of these regional rotate addresses: # noam.aprs2.net - for North America # soam.aprs2.net - for South America # euro.aprs2.net - for Europe and Africa # asia.aprs2.net - for Asia # aunz.aprs2.net - for Oceania IGSERVER noam.aprs2.net # You also need to specify your login name and passcode. # Contact the author if you can't figure out how to generate the passcode. IGLOGIN xxx 123456 # That's all you need for a receive only IGate which relays # messages from the local radio channel to the global servers. direwolf-1.5+dfsg/search_sdks.sh000077500000000000000000000055151347750676600167740ustar00rootroot00000000000000#!/bin/bash # # This file is part of Dire Wolf, an amateur radio packet TNC. # # Bash script to search for SDKs on various MacOSX versions. # # Copyright (C) 2015 Robert Stiles # # 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, see . # FILENAME="./use_this_sdk" selected_sdk="" valid_flag=0 system_sdk="" if [ -f $FILENAME ]; then selected_sdk=`cat $FILENAME` if [ -d $selected_sdk ]; then valid_flag=1 fi fi if [ $valid_flag -eq "0" ]; then echo " " >&2 echo " " >&2 echo "Searching for SDKs.... (Wait for results)" >&2 echo " " >&2 echo "Enter the number and press Enter/Return Key" >&2 echo " " >&2 echo " " >&2 prompt="Select SDK to use:" loc1=( $(find /Applications/Xcode.app -type l -name "MacOSX10.*.sdk") ) loc2=( $(find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.*.sdk") ) options=("${loc1[@]}" "${loc2[@]}") if [ "${#options[@]}" -lt "2" ]; then echo "$options" fi PS3="$prompt " select opt in "${options[@]}" "Do not use any SDK" ; do if (( REPLY == 1 + ${#options[@]} )) ; then echo " " break elif (( REPLY > 0 && REPLY <= ${#options[@]} )) ; then selected_sdk="$opt" break fi done if [ ! -z "$selected_sdk" ]; then echo "$selected_sdk" > $FILENAME else echo " " > $FILENAME fi fi if [ ! -z "$selected_sdk" ]; then temp_str="$selected_sdk" min_str="" flag=true # Search for the last MacOSX in the string. while [ "${#temp_str}" -gt 4 ]; do temp_str="${temp_str#*MacOSX}" temp_str="${temp_str%%.sdk}" min_str="$temp_str" temp_str="${temp_str:1}" done # Remove the "u" if 10.4u Universal SDK is used. min_str="${min_str%%u}" system_sdk="-isystem ${selected_sdk} -mmacosx-version-min=${min_str}" else system_sdk=" " fi echo " " >&2 echo "*******************************************************************" >&2 if [ -z "${system_sdk}" ]; then echo "SDK Selected: None" >&2 else echo "SDK Selected: ${system_sdk}" >&2 fi echo "To change SDK version execute 'make clean' followed by 'make'." >&2 echo "*******************************************************************" >&2 echo " " >&2 echo ${system_sdk} direwolf-1.5+dfsg/serial_port.c000066400000000000000000000263201347750676600166300ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2014, 2015, 2017 John Langner, WB2OSZ // // 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, see . // //#define DEBUG 1 /*------------------------------------------------------------------ * * Module: serial.c * * Purpose: Interface to serial port, hiding operating system differences. * *---------------------------------------------------------------*/ #include "direwolf.h" // should be first #include #if __WIN32__ #include #else #include #include #include #include #include #include #include #endif #include #include #include "textcolor.h" #include "serial_port.h" /*------------------------------------------------------------------- * * Name: serial_port_open * * Purpose: Open serial port. * * Inputs: devicename - For Windows, usually like COM5. * For Linux, usually /dev/tty... * "COMn" also allowed and converted to /dev/ttyS(n-1) * Could be /dev/rfcomm0 for Bluetooth. * * baud - Speed. 1200, 4800, 9600 bps, etc. * If 0, leave it alone. * * Returns Handle for serial port or MYFDERROR for error. * *---------------------------------------------------------------*/ MYFDTYPE serial_port_open (char *devicename, int baud) { #if __WIN32__ MYFDTYPE fd; DCB dcb; int ok; char bettername[50]; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("serial_port_open ( '%s', %d )\n", devicename, baud); #endif // Reference: http://www.robbayer.com/files/serial-win.pdf // Need to use FILE_FLAG_OVERLAPPED for full duplex operation. // Without it, write blocks when waiting on read. // Read http://support.microsoft.com/kb/156932 // Bug fix in release 1.1 - Need to munge name for COM10 and up. // http://support.microsoft.com/kb/115831 strlcpy (bettername, devicename, sizeof(bettername)); if (strncasecmp(devicename, "COM", 3) == 0) { int n; n = atoi(devicename+3); if (n >= 10) { strlcpy (bettername, "\\\\.\\", sizeof(bettername)); strlcat (bettername, devicename, sizeof(bettername)); } } fd = CreateFile(bettername, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (fd == MYFDERROR) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Could not open serial port %s.\n", devicename); return (MYFDERROR); } /* Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363201(v=vs.85).aspx */ memset (&dcb, 0, sizeof(dcb)); dcb.DCBlength = sizeof(DCB); ok = GetCommState (fd, &dcb); if (! ok) { text_color_set(DW_COLOR_ERROR); dw_printf ("serial_port_open: GetCommState failed.\n"); } /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */ dcb.DCBlength = sizeof(DCB); switch (baud) { case 0: /* Leave it alone. */ break; case 1200: dcb.BaudRate = CBR_1200; break; case 2400: dcb.BaudRate = CBR_2400; break; case 4800: dcb.BaudRate = CBR_4800; break; case 9600: dcb.BaudRate = CBR_9600; break; case 19200: dcb.BaudRate = CBR_19200; break; case 38400: dcb.BaudRate = CBR_38400; break; case 57600: dcb.BaudRate = CBR_57600; break; case 115200: dcb.BaudRate = CBR_115200; break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("serial_port_open: Unsupported speed %d. Using 4800.\n", baud); dcb.BaudRate = CBR_4800; break; } dcb.fBinary = 1; dcb.fParity = 0; dcb.fOutxCtsFlow = 0; dcb.fOutxDsrFlow = 0; dcb.fDtrControl = DTR_CONTROL_DISABLE; dcb.fDsrSensitivity = 0; dcb.fOutX = 0; dcb.fInX = 0; dcb.fErrorChar = 0; dcb.fNull = 0; /* Don't drop nul characters! */ dcb.fRtsControl = 0; dcb.ByteSize = 8; dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT; ok = SetCommState (fd, &dcb); if (! ok) { text_color_set(DW_COLOR_ERROR); dw_printf ("serial_port_open: SetCommState failed.\n"); } //text_color_set(DW_COLOR_INFO); //dw_printf("Successful serial port open on %s.\n", devicename); // Some devices, e.g. KPC-3+, can't turn off hardware flow control and need RTS. EscapeCommFunction(fd,SETRTS); EscapeCommFunction(fd,SETDTR); #else /* Linux version. */ int fd; struct termios ts; int e; char linuxname[50]; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("serial_port_open ( '%s' )\n", devicename); #endif /* Translate Windows device name into Linux name. */ /* COM1 -> /dev/ttyS0, etc. */ strlcpy (linuxname, devicename, sizeof(linuxname)); if (strncasecmp(devicename, "COM", 3) == 0) { int n = atoi (devicename + 3); text_color_set(DW_COLOR_INFO); dw_printf ("Converted serial port name '%s'", devicename); if (n < 1) n = 1; snprintf (linuxname, sizeof(linuxname), "/dev/ttyS%d", n-1); dw_printf (" to Linux equivalent '%s'\n", linuxname); } fd = open (linuxname, O_RDWR); if (fd == MYFDERROR) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Could not open serial port %s.\n", linuxname); return (MYFDERROR); } e = tcgetattr (fd, &ts); if (e != 0) { perror ("tcgetattr"); } cfmakeraw (&ts); ts.c_cc[VMIN] = 1; /* wait for at least one character */ ts.c_cc[VTIME] = 0; /* no fancy timing. */ switch (baud) { case 0: /* Leave it alone. */ break; case 1200: cfsetispeed (&ts, B1200); cfsetospeed (&ts, B1200); break; case 2400: cfsetispeed (&ts, B2400); cfsetospeed (&ts, B2400); break; case 4800: cfsetispeed (&ts, B4800); cfsetospeed (&ts, B4800); break; case 9600: cfsetispeed (&ts, B9600); cfsetospeed (&ts, B9600); break; case 19200: cfsetispeed (&ts, B19200); cfsetospeed (&ts, B19200); break; case 38400: cfsetispeed (&ts, B38400); cfsetospeed (&ts, B38400); break; #ifndef __APPLE__ // Not defined for Mac OSX. // https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2072 case 57600: cfsetispeed (&ts, B57600); cfsetospeed (&ts, B57600); break; case 115200: cfsetispeed (&ts, B115200); cfsetospeed (&ts, B115200); break; #endif default: text_color_set(DW_COLOR_ERROR); dw_printf ("serial_port_open: Unsupported speed %d. Using 4800.\n", baud); cfsetispeed (&ts, B4800); cfsetospeed (&ts, B4800); break; } e = tcsetattr (fd, TCSANOW, &ts); if (e != 0) { perror ("tcsetattr"); } //text_color_set(DW_COLOR_INFO); //dw_printf("Successfully opened serial port %s.\n", devicename); #endif return (fd); } /*------------------------------------------------------------------- * * Name: serial_port_write * * Purpose: Send characters to serial port. * * Inputs: fd - Handle from open. * str - Pointer to array of bytes. * len - Number of bytes to write. * * Returns Number of bytes written. Should be the same as len. * -1 if error. * *---------------------------------------------------------------*/ int serial_port_write (MYFDTYPE fd, char *str, int len) { if (fd == MYFDERROR) { return (-1); } #if __WIN32__ DWORD nwritten; /* Without this, write blocks while we are waiting on a read. */ static OVERLAPPED ov_wr; memset (&ov_wr, 0, sizeof(ov_wr)); if ( ! WriteFile (fd, str, len, &nwritten, &ov_wr)) { int err = GetLastError(); if (err != ERROR_IO_PENDING) { text_color_set(DW_COLOR_ERROR); dw_printf ("Error writing to serial port. Error %d.\n\n", err); } } else if ((int)nwritten != len) { // Do we want this message here? // Or rely on caller to check and provide something more meaningful for the usage? //text_color_set(DW_COLOR_ERROR); //dw_printf ("Error writing to serial port. Only %d of %d written.\n\n", (int)nwritten, len); } return (nwritten); #else int written; written = write (fd, str, (size_t)len); if (written != len) { // Do we want this message here? // Or rely on caller to check and provide something more meaningful for the usage? //text_color_set(DW_COLOR_ERROR); //dw_printf ("Error writing to serial port. err=%d\n\n", written); return (-1); } return (written); #endif } /* serial_port_write */ /*------------------------------------------------------------------- * * Name: serial_port_get1 * * Purpose: Get one byte from the serial port. Wait if not ready. * * Inputs: fd - Handle from open. * * Returns: Value of byte in range of 0 to 255. * -1 if error. * *--------------------------------------------------------------------*/ int serial_port_get1 (MYFDTYPE fd) { unsigned char ch; #if __WIN32__ /* Native Windows version. */ DWORD n; static OVERLAPPED ov_rd; memset (&ov_rd, 0, sizeof(ov_rd)); ov_rd.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL); /* Overlapped I/O makes reading rather complicated. */ /* See: http://msdn.microsoft.com/en-us/library/ms810467.aspx */ /* It seems that the read completes OK with a count */ /* of 0 every time we send a message to the serial port. */ n = 0; /* Number of characters read. */ while (n == 0) { if ( ! ReadFile (fd, &ch, 1, &n, &ov_rd)) { int err1 = GetLastError(); if (err1 == ERROR_IO_PENDING) { /* Wait for completion. */ if (WaitForSingleObject (ov_rd.hEvent, INFINITE) == WAIT_OBJECT_0) { if ( ! GetOverlappedResult (fd, &ov_rd, &n, 1)) { int err3 = GetLastError(); text_color_set(DW_COLOR_ERROR); dw_printf ("Serial Port GetOverlappedResult error %d.\n\n", err3); } else { /* Success! n should be 1 */ } } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Serial port read error %d.\n", err1); return (-1); } } } /* end while n==0 */ CloseHandle(ov_rd.hEvent); if (n != 1) { //text_color_set(DW_COLOR_ERROR); //dw_printf ("Serial port failed to get one byte. n=%d.\n\n", (int)n); return (-1); } #else /* Linux version */ int n; n = read(fd, &ch, (size_t)1); if (n != 1) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("serial_port_get1(%d) returns -1 for error.\n", fd); return (-1); } #endif #if DEBUGx text_color_set(DW_COLOR_DEBUG); if (isprint(ch)) { dw_printf ("serial_port_get1(%d) returns 0x%02x = '%c'\n", fd, ch, ch); } else { dw_printf ("serial_port_get1(%d) returns 0x%02x\n", fd, ch); } #endif return (ch); } /*------------------------------------------------------------------- * * Name: serial_port_close * * Purpose: Close the device. * * Inputs: fd - Handle from open. * * Returns: None. * *--------------------------------------------------------------------*/ void serial_port_close (MYFDTYPE fd) { #if __WIN32__ CloseHandle (fd); #else close (fd); #endif } /* end serial_port.c */ direwolf-1.5+dfsg/serial_port.h000066400000000000000000000007131347750676600166330ustar00rootroot00000000000000/* serial_port.h */ #ifndef SERIAL_PORT_H #define SERIAL_PORT_H 1 #if __WIN32__ #include typedef HANDLE MYFDTYPE; #define MYFDERROR INVALID_HANDLE_VALUE #else typedef int MYFDTYPE; #define MYFDERROR (-1) #endif extern MYFDTYPE serial_port_open (char *devicename, int baud); extern int serial_port_write (MYFDTYPE fd, char *str, int len); extern int serial_port_get1 (MYFDTYPE fd); extern void serial_port_close (MYFDTYPE fd); #endifdirewolf-1.5+dfsg/server.c000066400000000000000000001633441347750676600156230ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: server.c * * Purpose: Provide service to other applications via "AGW TCPIP Socket Interface". * * Input: * * Outputs: * * Description: This provides a TCP socket for communication with a client application. * It implements a subset of the AGW socket interface. * * Commands from application recognized: * * 'R' Request for version number. * (See below for response.) * * 'G' Ask about radio ports. * (See below for response.) * * 'g' Capabilities of a port. (new in 0.8) * (See below for response.) * * 'k' Ask to start receiving RAW AX25 frames. * * 'm' Ask to start receiving Monitor AX25 frames. * * 'V' Transmit UI data frame. * Generate audio for transmission. * * 'H' Report recently heard stations. Not implemented yet. * * 'K' Transmit raw AX.25 frame. * * 'X' Register CallSign * * 'x' Unregister CallSign * * 'y' Ask Outstanding frames waiting on a Port (new in 1.2) * * 'Y' How many frames waiting for transmit for a particular station (new in 1.5) * * 'C' Connect, Start an AX.25 Connection (new in 1.4) * * 'v' Connect VIA, Start an AX.25 circuit thru digipeaters (new in 1.4) * * 'c' Connection with non-standard PID (new in 1.4) * * 'D' Send Connected Data (new in 1.4) * * 'd' Disconnect, Terminate an AX.25 Connection (new in 1.4) * * * A message is printed if any others are received. * * TODO: Should others be implemented? * * * Messages sent to client application: * * 'R' Reply to Request for version number. * Currently responds with major 1, minor 0. * * 'G' Reply to Ask about radio ports. * * 'g' Reply to capabilities of a port. (new in 0.8) * * 'K' Received AX.25 frame in raw format. * (Enabled with 'k' command.) * * 'U' Received AX.25 frame in monitor format. * (Enabled with 'm' command.) * * 'y' Outstanding frames waiting on a Port (new in 1.2) * * 'Y' How many frames waiting for transmit for a particular station (new in 1.5) * * 'C' AX.25 Connection Received (new in 1.4) * * 'D' Connected AX.25 Data (new in 1.4) * * 'd' Disconnected (new in 1.4) * * * * References: AGWPE TCP/IP API Tutorial * http://uz7ho.org.ua/includes/agwpeapi.htm * * Getting Started with Winsock * http://msdn.microsoft.com/en-us/library/windows/desktop/bb530742(v=vs.85).aspx * * * Major change in 1.1: * * Formerly a single client was allowed. * Now we can have multiple concurrent clients. * *---------------------------------------------------------------*/ /* * Native Windows: Use the Winsock interface. * Linux: Use the BSD socket interface. * Cygwin: Can use either one. */ #include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h #if __WIN32__ #include #include // _WIN32_WINNT must be set to 0x0501 before including this #else #include #include #include #include #include #ifdef __OpenBSD__ #include #else #include #endif #endif #include #include #include #include #include #include #include "tq.h" #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "server.h" #include "dlq.h" /* * Previously, we allowed only one network connection at a time to each port. * In version 1.1, we allow multiple concurrent client apps to attach with the AGW network protocol. * The default is a limit of 3 client applications at the same time. * You can increase the limit by changing the line below. * A larger number consumes more resources so don't go crazy by making it larger than needed. */ #define MAX_NET_CLIENTS 3 static int client_sock[MAX_NET_CLIENTS]; /* File descriptor for socket for */ /* communication with client application. */ /* Set to -1 if not connected. */ /* (Don't use SOCKET type because it is unsigned.) */ static int enable_send_raw_to_client[MAX_NET_CLIENTS]; /* Should we send received packets to client app in raw form? */ /* Note that it starts as false for a new connection. */ /* the client app must send a command to enable this. */ static int enable_send_monitor_to_client[MAX_NET_CLIENTS]; /* Should we send received packets to client app in monitor form? */ /* Note that it starts as false for a new connection. */ /* the client app must send a command to enable this. */ // TODO: define in one place, use everywhere. // TODO: Macro to terminate thread when no point to go on. #if __WIN32__ #define THREAD_F unsigned __stdcall #else #define THREAD_F void * #endif static THREAD_F connect_listen_thread (void *arg); static THREAD_F cmd_listen_thread (void *arg); /* * Message header for AGW protocol. * Multibyte numeric values require rearranging for big endian cpu. */ /* * With MinGW version 4.6, obviously x86. * or Linux gcc version 4.9, Linux ARM. * * $ gcc -E -dM - < /dev/null | grep END * #define __ORDER_LITTLE_ENDIAN__ 1234 * #define __FLOAT_WORD_ORDER__ __ORDER_LITTLE_ENDIAN__ * #define __ORDER_PDP_ENDIAN__ 3412 * #define __ORDER_BIG_ENDIAN__ 4321 * #define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ * * This is for standard OpenWRT on MIPS. * * #define __ORDER_LITTLE_ENDIAN__ 1234 * #define __FLOAT_WORD_ORDER__ __ORDER_BIG_ENDIAN__ * #define __ORDER_PDP_ENDIAN__ 3412 * #define __ORDER_BIG_ENDIAN__ 4321 * #define __BYTE_ORDER__ __ORDER_BIG_ENDIAN__ * * This was reported for an old Mac with PowerPC processor. * (Newer versions have x86.) * * $ gcc -E -dM - < /dev/null | grep END * #define __BIG_ENDIAN__ 1 * #define _BIG_ENDIAN 1 */ #if defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) // gcc >= 4.2 has __builtin_swap32() but might not be compatible with older versions or other compilers. #define host2netle(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) ) #define netle2host(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) ) #else #define host2netle(x) (x) #define netle2host(x) (x) #endif struct agwpe_s { unsigned char portx; /* 0 for first, 1 for second, etc. */ unsigned char reserved1; unsigned char reserved2; unsigned char reserved3; unsigned char datakind; /* message type, usually written as a letter. */ unsigned char reserved4; unsigned char pid; unsigned char reserved5; char call_from[10]; char call_to[10]; int data_len_NETLE; /* Number of data bytes following. */ /* _NETLE suffix is reminder to convert for network byte order. */ int user_reserved_NETLE; }; static void send_to_client (int client, void *reply_p); /*------------------------------------------------------------------- * * Name: debug_print * * Purpose: Print message to/from client for debugging. * * Inputs: fromto - Direction of message. * client - client number, 0 .. MAX_NET_CLIENTS-1 * pmsg - Address of the message block. * msg_len - Length of the message. * *--------------------------------------------------------------------*/ static int debug_client = 0; /* Debug option: Print information flowing from and to client. */ void server_set_debug (int n) { debug_client = n; } void hex_dump (unsigned char *p, int len) { int n, i, offset; offset = 0; while (len > 0) { n = len < 16 ? len : 16; dw_printf (" %03x: ", offset); for (i=0; i>>" }; switch (fromto) { case FROM_CLIENT: strlcpy (direction, "from", sizeof(direction)); /* from the client application */ switch (pmsg->datakind) { case 'P': strlcpy (datakind, "Application Login", sizeof(datakind)); break; case 'X': strlcpy (datakind, "Register CallSign", sizeof(datakind)); break; case 'x': strlcpy (datakind, "Unregister CallSign", sizeof(datakind)); break; case 'G': strlcpy (datakind, "Ask Port Information", sizeof(datakind)); break; case 'm': strlcpy (datakind, "Enable Reception of Monitoring Frames", sizeof(datakind)); break; case 'R': strlcpy (datakind, "AGWPE Version Info", sizeof(datakind)); break; case 'g': strlcpy (datakind, "Ask Port Capabilities", sizeof(datakind)); break; case 'H': strlcpy (datakind, "Callsign Heard on a Port", sizeof(datakind)); break; case 'y': strlcpy (datakind, "Ask Outstanding frames waiting on a Port", sizeof(datakind)); break; case 'Y': strlcpy (datakind, "Ask Outstanding frames waiting for a connection", sizeof(datakind)); break; case 'M': strlcpy (datakind, "Send UNPROTO Information", sizeof(datakind)); break; case 'C': strlcpy (datakind, "Connect, Start an AX.25 Connection", sizeof(datakind)); break; case 'D': strlcpy (datakind, "Send Connected Data", sizeof(datakind)); break; case 'd': strlcpy (datakind, "Disconnect, Terminate an AX.25 Connection", sizeof(datakind)); break; case 'v': strlcpy (datakind, "Connect VIA, Start an AX.25 circuit thru digipeaters", sizeof(datakind)); break; case 'V': strlcpy (datakind, "Send UNPROTO VIA", sizeof(datakind)); break; case 'c': strlcpy (datakind, "Non-Standard Connections, Connection with PID", sizeof(datakind)); break; case 'K': strlcpy (datakind, "Send data in raw AX.25 format", sizeof(datakind)); break; case 'k': strlcpy (datakind, "Activate reception of Frames in raw format", sizeof(datakind)); break; default: strlcpy (datakind, "**INVALID**", sizeof(datakind)); break; } break; case TO_CLIENT: default: strlcpy (direction, "to", sizeof(direction)); /* sent to the client application. */ switch (pmsg->datakind) { case 'R': strlcpy (datakind, "Version Number", sizeof(datakind)); break; case 'X': strlcpy (datakind, "Callsign Registration", sizeof(datakind)); break; case 'G': strlcpy (datakind, "Port Information", sizeof(datakind)); break; case 'g': strlcpy (datakind, "Capabilities of a Port", sizeof(datakind)); break; case 'y': strlcpy (datakind, "Frames Outstanding on a Port", sizeof(datakind)); break; case 'Y': strlcpy (datakind, "Frames Outstanding on a Connection", sizeof(datakind)); break; case 'H': strlcpy (datakind, "Heard Stations on a Port", sizeof(datakind)); break; case 'C': strlcpy (datakind, "AX.25 Connection Received", sizeof(datakind)); break; case 'D': strlcpy (datakind, "Connected AX.25 Data", sizeof(datakind)); break; case 'd': strlcpy (datakind, "Disconnected", sizeof(datakind)); break; case 'M': strlcpy (datakind, "Monitored Connected Information", sizeof(datakind)); break; case 'S': strlcpy (datakind, "Monitored Supervisory Information", sizeof(datakind)); break; case 'U': strlcpy (datakind, "Monitored Unproto Information", sizeof(datakind)); break; case 'T': strlcpy (datakind, "Monitoring Own Information", sizeof(datakind)); break; case 'K': strlcpy (datakind, "Monitored Information in Raw Format", sizeof(datakind)); break; default: strlcpy (datakind, "**INVALID**", sizeof(datakind)); break; } } text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); dw_printf ("%s %s %s AGWPE client application %d, total length = %d\n", prefix[(int)fromto], datakind, direction, client, msg_len); dw_printf ("\tportx = %d, datakind = '%c', pid = 0x%02x\n", pmsg->portx, pmsg->datakind, pmsg->pid); dw_printf ("\tcall_from = \"%s\", call_to = \"%s\"\n", pmsg->call_from, pmsg->call_to); dw_printf ("\tdata_len = %d, user_reserved = %d, data =\n", netle2host(pmsg->data_len_NETLE), netle2host(pmsg->user_reserved_NETLE)); hex_dump ((unsigned char*)pmsg + sizeof(struct agwpe_s), netle2host(pmsg->data_len_NETLE)); if (msg_len < 36) { text_color_set (DW_COLOR_ERROR); dw_printf ("AGWPE message length, %d, is shorter than minumum 36.\n", msg_len); } if (msg_len != netle2host(pmsg->data_len_NETLE) + 36) { text_color_set (DW_COLOR_ERROR); dw_printf ("AGWPE message length, %d, inconsistent with data length %d.\n", msg_len, netle2host(pmsg->data_len_NETLE)); } } /*------------------------------------------------------------------- * * Name: server_init * * Purpose: Set up a server to listen for connection requests from * an application such as Xastir. * * Inputs: mc->agwpe_port - TCP port for server. * Main program has default of 8000 but allows * an alternative to be specified on the command line * * 0 means disable. New in version 1.2. * * Outputs: * * Description: This starts at least two threads: * * one to listen for a connection from client app. * * one or more to listen for commands from client app. * so the main application doesn't block while we wait for these. * *--------------------------------------------------------------------*/ static struct audio_s *save_audio_config_p; void server_init (struct audio_s *audio_config_p, struct misc_config_s *mc) { int client; #if __WIN32__ HANDLE connect_listen_th; HANDLE cmd_listen_th[MAX_NET_CLIENTS]; #else pthread_t connect_listen_tid; pthread_t cmd_listen_tid[MAX_NET_CLIENTS]; int e; #endif int server_port = mc->agwpe_port; /* Usually 8000 but can be changed. */ #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("server_init ( %d )\n", server_port); debug_a = 1; #endif save_audio_config_p = audio_config_p; for (client=0; clientai_family, ai->ai_socktype, ai->ai_protocol); if (listen_sock == INVALID_SOCKET) { text_color_set(DW_COLOR_ERROR); dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError()); return (0); } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("Binding to port %s ... \n", server_port_str); #endif err = bind( listen_sock, ai->ai_addr, (int)ai->ai_addrlen); if (err == SOCKET_ERROR) { text_color_set(DW_COLOR_ERROR); dw_printf("Bind failed with error: %d\n", WSAGetLastError()); // TODO: translate number to text? dw_printf("Some other application is probably already using port %s.\n", server_port_str); dw_printf("Try using a different port number with AGWPORT in the configuration file.\n"); freeaddrinfo(ai); closesocket(listen_sock); WSACleanup(); return (0); } freeaddrinfo(ai); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("opened socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, server_port_str ); #endif while (1) { int client; int c; client = -1; for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) { if (client_sock[c] <= 0) { client = c; } } /* * Listen for connection if we have not reached maximum. */ if (client >= 0) { if(listen(listen_sock, MAX_NET_CLIENTS) == SOCKET_ERROR) { text_color_set(DW_COLOR_ERROR); dw_printf("Listen failed with error: %d\n", WSAGetLastError()); return (0); } text_color_set(DW_COLOR_INFO); dw_printf("Ready to accept AGW client application %d on port %s ...\n", client, server_port_str); client_sock[client] = accept(listen_sock, NULL, NULL); if (client_sock[client] == -1) { text_color_set(DW_COLOR_ERROR); dw_printf("Accept failed with error: %d\n", WSAGetLastError()); closesocket(listen_sock); WSACleanup(); return (0); } text_color_set(DW_COLOR_INFO); dw_printf("\nAttached to AGW client application %d ...\n\n", client); /* * The command to change this is actually a toggle, not explicit on or off. * Make sure it has proper state when we get a new connection. */ enable_send_raw_to_client[client] = 0; enable_send_monitor_to_client[client] = 0; } else { SLEEP_SEC(1); /* wait then check again if more clients allowed. */ } } #else /* End of Windows case, now Linux */ struct sockaddr_in sockaddr; /* Internet socket address stuct */ socklen_t sockaddr_size = sizeof(struct sockaddr_in); int server_port = (int)(long)arg; int listen_sock; int bcopt = 1; listen_sock= socket(AF_INET,SOCK_STREAM,0); if (listen_sock == -1) { text_color_set(DW_COLOR_ERROR); perror ("connect_listen_thread: Socket creation failed"); return (NULL); } /* Version 1.3 - as suggested by G8BPQ. */ /* Without this, if you kill the application then try to run it */ /* again quickly the port number is unavailable for a while. */ /* Don't try doing the same thing On Windows; It has a different meaning. */ /* http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t */ setsockopt (listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&bcopt, 4); sockaddr.sin_addr.s_addr = INADDR_ANY; sockaddr.sin_port = htons(server_port); sockaddr.sin_family = AF_INET; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("Binding to port %d ... \n", server_port); #endif if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr)) == -1) { text_color_set(DW_COLOR_ERROR); dw_printf("Bind failed with error: %d\n", errno); dw_printf("%s\n", strerror(errno)); dw_printf("Some other application is probably already using port %d.\n", server_port); dw_printf("Try using a different port number with AGWPORT in the configuration file.\n"); return (NULL); } getsockname( listen_sock, (struct sockaddr *)(&sockaddr), &sockaddr_size); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("opened socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) ); #endif while (1) { int client; int c; client = -1; for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) { if (client_sock[c] <= 0) { client = c; } } if (client >= 0) { if(listen(listen_sock,MAX_NET_CLIENTS) == -1) { text_color_set(DW_COLOR_ERROR); perror ("connect_listen_thread: Listen failed"); return (NULL); } text_color_set(DW_COLOR_INFO); dw_printf("Ready to accept AGW client application %d on port %d ...\n", client, server_port); client_sock[client] = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size); text_color_set(DW_COLOR_INFO); dw_printf("\nAttached to AGW client application %d...\n\n", client); /* * The command to change this is actually a toggle, not explicit on or off. * Make sure it has proper state when we get a new connection. */ enable_send_raw_to_client[client] = 0; enable_send_monitor_to_client[client] = 0; } else { SLEEP_SEC(1); /* wait then check again if more clients allowed. */ } } #endif } /*------------------------------------------------------------------- * * Name: server_send_rec_packet * * Purpose: Send a received packet to the client app. * * Inputs: chan - Channel number where packet was received. * 0 = first, 1 = second if any. * * pp - Identifier for packet object. * * fbuf - Address of raw received frame buffer. * flen - Length of raw received frame. * * * Description: Send message to client if connected. * Disconnect from client, and notify user, if any error. * * There are two different formats: * RAW - the original received frame. * MONITOR - just the information part. * *--------------------------------------------------------------------*/ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int flen) { struct { struct agwpe_s hdr; char data[1+AX25_MAX_PACKET_LEN]; } agwpe_msg; int err; int info_len; unsigned char *pinfo; int client; /* * RAW format */ for (client=0; client 0){ memset (&agwpe_msg.hdr, 0, sizeof(agwpe_msg.hdr)); agwpe_msg.hdr.portx = chan; agwpe_msg.hdr.datakind = 'K'; ax25_get_addr_with_ssid (pp, AX25_SOURCE, agwpe_msg.hdr.call_from); ax25_get_addr_with_ssid (pp, AX25_DESTINATION, agwpe_msg.hdr.call_to); agwpe_msg.hdr.data_len_NETLE = host2netle(flen + 1); /* Stick in extra byte for the "TNC" to use. */ agwpe_msg.data[0] = 0; memcpy (agwpe_msg.data + 1, fbuf, (size_t)flen); if (debug_client) { debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE)); } #if __WIN32__ err = SOCK_SEND (client_sock[client], (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE)); if (err == SOCKET_ERROR) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError %d sending message to AGW client application. Closing connection.\n\n", WSAGetLastError()); closesocket (client_sock[client]); client_sock[client] = -1; WSACleanup(); dlq_client_cleanup (client); } #else err = SOCK_SEND (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE)); if (err <= 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError sending message to AGW client application. Closing connection.\n\n"); close (client_sock[client]); client_sock[client] = -1; dlq_client_cleanup (client); } #endif } } /* MONITOR format - only for UI frames. */ for (client=0; client 0 && ax25_get_control(pp) == AX25_UI_FRAME){ time_t clock; struct tm *tm; int num_digi; clock = time(NULL); tm = localtime(&clock); // TODO: should use localtime_r memset (&agwpe_msg.hdr, 0, sizeof(agwpe_msg.hdr)); agwpe_msg.hdr.portx = chan; agwpe_msg.hdr.datakind = 'U'; ax25_get_addr_with_ssid (pp, AX25_SOURCE, agwpe_msg.hdr.call_from); ax25_get_addr_with_ssid (pp, AX25_DESTINATION, agwpe_msg.hdr.call_to); info_len = ax25_get_info (pp, &pinfo); /* http://uz7ho.org.ua/includes/agwpeapi.htm#_Toc500723812 */ /* Description mentions one CR character after timestamp but example has two. */ /* Actual observed cases have only one. */ /* Also need to add extra CR, CR, null at end. */ /* The documentation example includes these 3 extra in the Len= value */ /* but actual observed data uses only the packet info length. */ // Documentation doesn't mention anything about including the via path. // In version 1.4, we add that to match observed behaviour. // This inconsistency was reported: // Direwolf: // [AGWE-IN] 1:Fm ZL4FOX-8 To Q7P2U2 [08:25:07]`I1*l V>/"9<}[:Barts Tracker 3.83V X // AGWPE: // [AGWE-IN] 1:Fm ZL4FOX-8 To Q7P2U2 Via WIDE3-3 [08:32:14]`I0*l V>/"98}[:Barts Tracker 3.83V X num_digi = ax25_get_num_repeaters(pp); if (num_digi > 0) { char via[AX25_MAX_REPEATERS*(AX25_MAX_ADDR_LEN+1)]; char stemp[AX25_MAX_ADDR_LEN+1]; int j; ax25_get_addr_with_ssid (pp, AX25_REPEATER_1, via); for (j = 1; j < num_digi; j++) { ax25_get_addr_with_ssid (pp, AX25_REPEATER_1 + j, stemp); strlcat (via, ",", sizeof(via)); strlcat (via, stemp, sizeof(via)); } snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s Via %s [%02d:%02d:%02d]\r%s\r\r", chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to, via, ax25_get_pid(pp), info_len, tm->tm_hour, tm->tm_min, tm->tm_sec, pinfo); } else { snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s [%02d:%02d:%02d]\r%s\r\r", chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to, ax25_get_pid(pp), info_len, tm->tm_hour, tm->tm_min, tm->tm_sec, pinfo); } agwpe_msg.hdr.data_len_NETLE = host2netle(strlen(agwpe_msg.data) + 1) /* +1 to include terminating null */ ; if (debug_client) { debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE)); } #if __WIN32__ err = SOCK_SEND (client_sock[client], (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE)); if (err == SOCKET_ERROR) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError %d sending message to AGW client application %d. Closing connection.\n\n", WSAGetLastError(), client); closesocket (client_sock[client]); client_sock[client] = -1; WSACleanup(); dlq_client_cleanup (client); } #else err = SOCK_SEND (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE)); if (err <= 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError sending message to AGW client application %d. Closing connection.\n\n", client); close (client_sock[client]); client_sock[client] = -1; dlq_client_cleanup (client); } #endif } } } /* server_send_rec_packet */ /*------------------------------------------------------------------- * * Name: server_link_established * * Purpose: Send notification to client app when a link has * been established with another station. * * DL-CONNECT Confirm or DL-CONNECT Indication in the protocol spec. * * Inputs: chan - Which radio channel. * * client - Which one of potentially several clients. * * remote_call - Callsign[-ssid] of remote station. * * own_call - Callsign[-ssid] of my end. * * incoming - true if connection was initiated from other end. * false if this end started it. * *--------------------------------------------------------------------*/ void server_link_established (int chan, int client, char *remote_call, char *own_call, int incoming) { struct { struct agwpe_s hdr; char info[100]; } reply; memset (&reply, 0, sizeof(reply)); reply.hdr.portx = chan; reply.hdr.datakind = 'C'; strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from)); strlcpy (reply.hdr.call_to, own_call, sizeof(reply.hdr.call_to)); // Question: Should the via path be provided too? if (incoming) { // Other end initiated the connection. snprintf (reply.info, sizeof(reply.info), "*** CONNECTED To Station %s\r", remote_call); } else { // We started the connection. snprintf (reply.info, sizeof(reply.info), "*** CONNECTED With Station %s\r", remote_call); } reply.hdr.data_len_NETLE = host2netle(strlen(reply.info) + 1); send_to_client (client, &reply); } /* end server_link_established */ /*------------------------------------------------------------------- * * Name: server_link_terminated * * Purpose: Send notification to client app when a link with * another station has been terminated or a connection * attempt failed. * * DL-DISCONNECT Confirm or DL-DISCONNECT Indication in the protocol spec. * * Inputs: chan - Which radio channel. * * client - Which one of potentially several clients. * * remote_call - Callsign[-ssid] of remote station. * * own_call - Callsign[-ssid] of my end. * * timeout - true when no answer from other station. * How do we distinguish who asked for the * termination of an existing link? * *--------------------------------------------------------------------*/ void server_link_terminated (int chan, int client, char *remote_call, char *own_call, int timeout) { struct { struct agwpe_s hdr; char info[100]; } reply; memset (&reply, 0, sizeof(reply)); reply.hdr.portx = chan; reply.hdr.datakind = 'd'; strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from)); /* right order */ strlcpy (reply.hdr.call_to, own_call, sizeof(reply.hdr.call_to)); if (timeout) { snprintf (reply.info, sizeof(reply.info), "*** DISCONNECTED RETRYOUT With %s\r", remote_call); } else { snprintf (reply.info, sizeof(reply.info), "*** DISCONNECTED From Station %s\r", remote_call); } reply.hdr.data_len_NETLE = host2netle(strlen(reply.info) + 1); send_to_client (client, &reply); } /* end server_link_terminated */ /*------------------------------------------------------------------- * * Name: server_rec_conn_data * * Purpose: Send received connected data to the application. * * DL-DATA Indication in the protocol spec. * * Inputs: chan - Which radio channel. * * client - Which one of potentially several clients. * * remote_call - Callsign[-ssid] of remote station. * * own_call - Callsign[-ssid] of my end. * * pid - Protocol ID from I frame. * * data_ptr - Pointer to a block of bytes. * * data_len - Number of bytes. Could be zero. * *--------------------------------------------------------------------*/ void server_rec_conn_data (int chan, int client, char *remote_call, char *own_call, int pid, char *data_ptr, int data_len) { struct { struct agwpe_s hdr; char info[AX25_MAX_INFO_LEN]; // I suppose there is potential for something larger. // We'll cross that bridge if we ever come to it. } reply; memset (&reply.hdr, 0, sizeof(reply.hdr)); reply.hdr.portx = chan; reply.hdr.datakind = 'D'; reply.hdr.pid = pid; strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from)); strlcpy (reply.hdr.call_to, own_call, sizeof(reply.hdr.call_to)); if (data_len < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid length %d for connected data to client %d.\n", data_len, client); data_len = 0; } else if (data_len > AX25_MAX_INFO_LEN) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid length %d for connected data to client %d.\n", data_len, client); data_len = AX25_MAX_INFO_LEN; } memcpy (reply.info, data_ptr, data_len); reply.hdr.data_len_NETLE = host2netle(data_len); send_to_client (client, &reply); } /* end server_rec_conn_data */ /*------------------------------------------------------------------- * * Name: read_from_socket * * Purpose: Read from socket until we have desired number of bytes. * * Inputs: fd - file descriptor. * ptr - address where data should be placed. * len - desired number of bytes. * * Description: Just a wrapper for the "read" system call but it should * never return fewer than the desired number of bytes. * *--------------------------------------------------------------------*/ static int read_from_socket (int fd, char *ptr, int len) { int got_bytes = 0; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("read_from_socket (%d, %p, %d)\n", fd, ptr, len); #endif while (got_bytes < len) { int n; n = SOCK_RECV (fd, ptr + got_bytes, len - got_bytes); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("read_from_socket: n = %d\n", n); #endif if (n <= 0) { return (n); } got_bytes += n; } assert (got_bytes >= 0 && got_bytes <= len); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("read_from_socket: return %d\n", got_bytes); #endif return (got_bytes); } /*------------------------------------------------------------------- * * Name: cmd_listen_thread * * Purpose: Wait for command messages from an application. * * Inputs: arg - client number, 0 .. MAX_NET_CLIENTS-1 * * Outputs: client_sock[n] - File descriptor for communicating with client app. * * Description: Process messages from the client application. * Note that the client can go away and come back again and * re-establish communication without restarting this application. * *--------------------------------------------------------------------*/ static void send_to_client (int client, void *reply_p) { struct agwpe_s *ph; int len; int err; ph = (struct agwpe_s *) reply_p; // Replies are often hdr + other stuff. len = sizeof(struct agwpe_s) + netle2host(ph->data_len_NETLE); /* Not sure what max data length might be. */ if (netle2host(ph->data_len_NETLE) < 0 || netle2host(ph->data_len_NETLE) > 4096) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid data length %d for AGW protocol message to client %d.\n", netle2host(ph->data_len_NETLE), client); debug_print (TO_CLIENT, client, ph, len); } if (debug_client) { debug_print (TO_CLIENT, client, ph, len); } err = SOCK_SEND (client_sock[client], (char*)(ph), len); (void)err; } static THREAD_F cmd_listen_thread (void *arg) { int n; struct { struct agwpe_s hdr; /* Command header. */ char data[512]; /* Additional data used by some commands. */ /* Maximum for 'V': 1 + 8*10 + 256 */ } cmd; int client = (int)(long)arg; assert (client >= 0 && client < MAX_NET_CLIENTS); while (1) { while (client_sock[client] <= 0) { SLEEP_SEC(1); /* Not connected. Try again later. */ } n = read_from_socket (client_sock[client], (char *)(&cmd.hdr), sizeof(cmd.hdr)); if (n != sizeof(cmd.hdr)) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError getting message header from AGW client application %d.\n", client); dw_printf ("Tried to read %d bytes but got only %d.\n", (int)sizeof(cmd.hdr), n); dw_printf ("Closing connection.\n\n"); #if __WIN32__ closesocket (client_sock[client]); #else close (client_sock[client]); #endif client_sock[client] = -1; dlq_client_cleanup (client); continue; } /* * Take some precautions to guard against bad data which could cause problems later. */ if (cmd.hdr.portx < 0 || cmd.hdr.portx >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nInvalid port number, %d, in command '%c', from AGW client application %d.\n", cmd.hdr.portx, cmd.hdr.datakind, client); cmd.hdr.portx = 0; // avoid subscript out of bounds, try to keep going. } /* * Call to/from fields are 10 bytes but contents must not exceeed 9 characters. * It's not guaranteed that unused bytes will contain 0 so we * don't issue error message in this case. */ cmd.hdr.call_from[sizeof(cmd.hdr.call_from)-1] = '\0'; cmd.hdr.call_to[sizeof(cmd.hdr.call_to)-1] = '\0'; /* * Following data must fit in available buffer. * Leave room for an extra nul byte terminator at end later. */ int data_len = netle2host(cmd.hdr.data_len_NETLE); if (data_len < 0 || data_len > (int)(sizeof(cmd.data) - 1)) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nInvalid message from AGW client application %d.\n", client); dw_printf ("Data Length of %d is out of range.\n", data_len); /* This is a bad situation. */ /* If we tried to read again, the header probably won't be there. */ /* No point in trying to continue reading. */ dw_printf ("Closing connection.\n\n"); #if __WIN32__ closesocket (client_sock[client]); #else close (client_sock[client]); #endif client_sock[client] = -1; dlq_client_cleanup (client); return (0); } cmd.data[0] = '\0'; if (data_len > 0) { n = read_from_socket (client_sock[client], cmd.data, data_len); if (n != data_len) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nError getting message data from AGW client application %d.\n", client); dw_printf ("Tried to read %d bytes but got only %d.\n", data_len, n); dw_printf ("Closing connection.\n\n"); #if __WIN32__ closesocket (client_sock[client]); #else close (client_sock[client]); #endif client_sock[client] = -1; dlq_client_cleanup (client); return (0); } if (n >= 0) { cmd.data[n] = '\0'; // Tidy if we print for debug. } } /* * print & process message from client. */ if (debug_client) { debug_print (FROM_CLIENT, client, &cmd.hdr, sizeof(cmd.hdr) + data_len); } switch (cmd.hdr.datakind) { case 'R': /* Request for version number */ { struct { struct agwpe_s hdr; int major_version_NETLE; int minor_version_NETLE; } reply; memset (&reply, 0, sizeof(reply)); reply.hdr.datakind = 'R'; reply.hdr.data_len_NETLE = host2netle(sizeof(reply.major_version_NETLE) + sizeof(reply.minor_version_NETLE)); assert (netle2host(reply.hdr.data_len_NETLE) == 8); // Xastir only prints this and doesn't care otherwise. // APRSIS32 doesn't seem to care. // UI-View32 wants on 2000.15 or later. reply.major_version_NETLE = host2netle(2005); reply.minor_version_NETLE = host2netle(127); assert (sizeof(reply) == 44); send_to_client (client, &reply); } break; case 'G': /* Ask about radio ports */ { struct { struct agwpe_s hdr; char info[200]; } reply; int j, count; memset (&reply, 0, sizeof(reply)); reply.hdr.datakind = 'G'; // Xastir only prints this and doesn't care otherwise. // YAAC uses this to identify available channels. // The interface manual wants the first to be "Port1" // so channel 0 corresponds to "Port1." // We can have gaps in the numbering. // I wonder what applications will think about that. #if 1 // No other place cares about total number. count = 0; for (j=0; jachan[j].valid) { count++; } } snprintf (reply.info, sizeof(reply.info), "%d;", count); for (j=0; jachan[j].valid) { char stemp[100]; int a = ACHAN2ADEV(j); // If I was really ambitious, some description could be provided. static const char *names[8] = { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth" }; if (save_audio_config_p->adev[a].num_channels == 1) { snprintf (stemp, sizeof(stemp), "Port%d %s soundcard mono;", j+1, names[a]); strlcat (reply.info, stemp, sizeof(reply.info)); } else { snprintf (stemp, sizeof(stemp), "Port%d %s soundcard %s;", j+1, names[a], j&1 ? "right" : "left"); strlcat (reply.info, stemp, sizeof(reply.info)); } } } #else if (num_channels == 1) { snprintf (reply.info, sizeof(reply.info), "1;Port1 Single channel;"); } else { snprintf (reply.info, sizeof(reply.info), "2;Port1 Left channel;Port2 Right Channel;"); } #endif reply.hdr.data_len_NETLE = host2netle(strlen(reply.info) + 1); send_to_client (client, &reply); } break; case 'g': /* Ask about capabilities of a port. */ { struct { struct agwpe_s hdr; unsigned char on_air_baud_rate; /* 0=1200, 1=2400, 2=4800, 3=9600, ... */ unsigned char traffic_level; /* 0xff if not in autoupdate mode */ unsigned char tx_delay; unsigned char tx_tail; unsigned char persist; unsigned char slottime; unsigned char maxframe; unsigned char active_connections; int how_many_bytes_NETLE; } reply; memset (&reply, 0, sizeof(reply)); reply.hdr.portx = cmd.hdr.portx; /* Reply with same port number ! */ reply.hdr.datakind = 'g'; reply.hdr.data_len_NETLE = host2netle(12); // YAAC asks for this. // Fake it to keep application happy. // TODO: Supply real values instead of just faking it. reply.on_air_baud_rate = 0; reply.traffic_level = 1; reply.tx_delay = 0x19; reply.tx_tail = 4; reply.persist = 0xc8; reply.slottime = 4; reply.maxframe = 7; reply.active_connections = 0; reply.how_many_bytes_NETLE = host2netle(1); assert (sizeof(reply) == 48); send_to_client (client, &reply); } break; case 'H': /* Ask about recently heard stations on given port. */ /* This should send back 20 'H' frames for the most recently heard stations. */ /* If there are less available, empty frames are sent to make a total of 20. */ /* Each contains the first and last heard times. */ { #if 0 /* Currently, this information is not being collected. */ struct { struct agwpe_s hdr; char info[100]; } reply; memset (&reply.hdr, 0, sizeof(reply.hdr)); reply.hdr.datakind = 'H'; // TODO: Implement properly. reply.hdr.portx = cmd.hdr.portx strlcpy (reply.hdr.call_from, "WB2OSZ-15 Mon,01Jan2000 01:02:03 Tue,31Dec2099 23:45:56", sizeof(reply.hdr.call_from)); // or 00:00:00 00:00:00 strlcpy (agwpe_msg.data, ..., sizeof(agwpe_msg.data)); reply.hdr.data_len_NETLE = host2netle(strlen(reply.info)); send_to_client (client, &reply); #endif } break; case 'k': /* Ask to start receiving RAW AX25 frames */ // Actually it is a toggle so we must be sure to clear it for a new connection. enable_send_raw_to_client[client] = ! enable_send_raw_to_client[client]; break; case 'm': /* Ask to start receiving Monitor frames */ // Actually it is a toggle so we must be sure to clear it for a new connection. enable_send_monitor_to_client[client] = ! enable_send_monitor_to_client[client]; break; case 'V': /* Transmit UI data frame (with digipeater path) */ { // Data format is: // 1 byte for number of digipeaters. // 10 bytes for each digipeater. // data part of message. char stemp[AX25_MAX_PACKET_LEN+2]; char *p; int ndigi; int k; packet_t pp; strlcpy (stemp, cmd.hdr.call_from, sizeof(stemp)); strlcat (stemp, ">", sizeof(stemp)); strlcat (stemp, cmd.hdr.call_to, sizeof(stemp)); cmd.data[data_len] = '\0'; ndigi = cmd.data[0]; p = cmd.data + 1; for (k=0; k= 1 && ax25_get_h(pp,AX25_REPEATER_1)) { tq_append (cmd.hdr.portx, TQ_PRIO_0_HI, pp); } else { tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp); } } } break; case 'X': /* Register CallSign */ { struct { struct agwpe_s hdr; char data; /* 1 = success, 0 = failure */ } reply; int ok = 1; // The protocol spec says it is an error to register the same one more than once. // Too much trouble. Report success if the channel is valid. int chan = cmd.hdr.portx; if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].valid) { ok = 1; dlq_register_callsign (cmd.hdr.call_from, chan, client); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("AGW protocol error. Register callsign for invalid channel %d.\n", chan); ok = 0; } memset (&reply, 0, sizeof(reply)); reply.hdr.datakind = 'X'; reply.hdr.portx = cmd.hdr.portx; memcpy (reply.hdr.call_from, cmd.hdr.call_from, sizeof(reply.hdr.call_from)); reply.hdr.data_len_NETLE = host2netle(1); reply.data = ok; send_to_client (client, &reply); } break; case 'x': /* Unregister CallSign */ { int chan = cmd.hdr.portx; if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].valid) { dlq_unregister_callsign (cmd.hdr.call_from, chan, client); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("AGW protocol error. Unregister callsign for invalid channel %d.\n", chan); } } /* No reponse is expected. */ break; case 'C': /* Connect, Start an AX.25 Connection */ case 'v': /* Connect VIA, Start an AX.25 circuit thru digipeaters */ case 'c': /* Connection with non-standard PID */ { struct via_info { unsigned char num_digi; /* Expect to be in range 1 to 7. Why not up to 8? */ char dcall[7][10]; } #if 1 // October 2017. gcc ??? complained: // warning: dereferencing pointer 'v' does break strict-aliasing rules // Try adding this attribute to get rid of the warning. // If this upsets your compiler, take it out. // Let me know. Maybe we could put in a compiler version check here. __attribute__((__may_alias__)) #endif *v = (struct via_info *)cmd.data; char callsigns[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN]; int num_calls = 2; /* 2 plus any digipeaters. */ int pid = 0xf0; /* normal for AX.25 I frames. */ int j; strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE])); strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_DESTINATION])); if (cmd.hdr.datakind == 'c') { pid = cmd.hdr.pid; /* non standard for NETROM, TCP/IP, etc. */ } if (cmd.hdr.datakind == 'v') { if (v->num_digi >= 1 && v->num_digi <= 7) { if (data_len != v->num_digi * 10 + 1 && data_len != v->num_digi * 10 + 2) { // I'm getting 1 more than expected from AGWterminal. text_color_set(DW_COLOR_ERROR); dw_printf ("AGW client, connect via, has data len, %d when %d expected.\n", data_len, v->num_digi * 10 + 1); } for (j = 0; j < v->num_digi; j++) { strlcpy (callsigns[AX25_REPEATER_1 + j], v->dcall[j], sizeof(callsigns[AX25_REPEATER_1 + j])); num_calls++; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("\n"); dw_printf ("AGW client, connect via, has invalid number of digipeaters = %d\n", v->num_digi); } } dlq_connect_request (callsigns, num_calls, cmd.hdr.portx, client, pid); } break; case 'D': /* Send Connected Data */ { char callsigns[2][AX25_MAX_ADDR_LEN]; const int num_calls = 2; strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE])); strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE])); dlq_xmit_data_request (callsigns, num_calls, cmd.hdr.portx, client, cmd.hdr.pid, cmd.data, netle2host(cmd.hdr.data_len_NETLE)); } break; case 'd': /* Disconnect, Terminate an AX.25 Connection */ { char callsigns[2][AX25_MAX_ADDR_LEN]; const int num_calls = 2; strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE])); strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE])); dlq_disconnect_request (callsigns, num_calls, cmd.hdr.portx, client); } break; case 'M': /* Send UNPROTO Information (no digipeater path) */ /* Added in version 1.3. This is the same as 'V' except there is no provision for digipeaters. TODO: combine 'V' and 'M' into one case. AGWterminal sends this for beacon or ask QRA. <<< Send UNPROTO Information from AGWPE client application 0, total length = 253 portx = 0, datakind = 'M', pid = 0x00 call_from = "WB2OSZ-15", call_to = "BEACON" data_len = 217, user_reserved = 556, data = 000: 54 68 69 73 20 76 65 72 73 69 6f 6e 20 75 73 65 This version use ... <<< Send UNPROTO Information from AGWPE client application 0, total length = 37 portx = 0, datakind = 'M', pid = 0x00 call_from = "WB2OSZ-15", call_to = "QRA" data_len = 1, user_reserved = 31759424, data = 000: 0d . . There is also a report of it coming from UISS. <<< Send UNPROTO Information from AGWPE client application 0, total length = 50 portx = 0, port_hi_reserved = 0 datakind = 77 = 'M', kind_hi = 0 call_from = "JH4XSY", call_to = "APRS" data_len = 14, user_reserved = 0, data = 000: 21 22 3c 43 2e 74 71 6c 48 72 71 21 21 5f !"", sizeof(stemp)); strlcat (stemp, cmd.hdr.call_to, sizeof(stemp)); cmd.data[data_len] = '\0'; strlcat (stemp, ":", sizeof(stemp)); strlcat (stemp, cmd.data, sizeof(stemp)); //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Transmit '%s'\n", stemp); pp = ax25_from_text (stemp, 1); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create frame from AGW 'M' message.\n"); } else { tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp); } } break; case 'y': /* Ask Outstanding frames waiting on a Port */ /* Number of frames sitting in transmit queue for specified channel. */ { struct { struct agwpe_s hdr; int data_NETLE; // Little endian order. } reply; memset (&reply, 0, sizeof(reply)); reply.hdr.portx = cmd.hdr.portx; /* Reply with same port number */ reply.hdr.datakind = 'y'; reply.hdr.data_len_NETLE = host2netle(4); int n = 0; if (cmd.hdr.portx >= 0 && cmd.hdr.portx < MAX_CHANS) { n = tq_count (cmd.hdr.portx, -1, "", "", 0); } reply.data_NETLE = host2netle(n); send_to_client (client, &reply); } break; case 'Y': /* How Many Outstanding frames wait for tx for a particular station */ /* Number of frames sitting in transmit queue for given channel, */ /* source (optional) and destination addresses. */ { char source[AX25_MAX_ADDR_LEN]; char dest[AX25_MAX_ADDR_LEN]; struct { struct agwpe_s hdr; int data_NETLE; // Little endian order. } reply; strlcpy (source, cmd.hdr.call_from, sizeof(source)); strlcpy (dest, cmd.hdr.call_to, sizeof(dest)); memset (&reply, 0, sizeof(reply)); reply.hdr.portx = cmd.hdr.portx; /* Reply with same port number, addresses. */ reply.hdr.datakind = 'Y'; strlcpy (reply.hdr.call_from, source, sizeof(reply.hdr.call_from)); strlcpy (reply.hdr.call_to, dest, sizeof(reply.hdr.call_to)); reply.hdr.data_len_NETLE = host2netle(4); int n = 0; if (cmd.hdr.portx >= 0 && cmd.hdr.portx < MAX_CHANS) { n = tq_count (cmd.hdr.portx, -1, source, dest, 0); } reply.data_NETLE = host2netle(n); send_to_client (client, &reply); } break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("--- Unexpected Command from application %d using AGW protocol:\n", client); debug_print (FROM_CLIENT, client, &cmd.hdr, sizeof(cmd.hdr) + data_len); break; } } } /* end send_to_client */ /* end server.c */ direwolf-1.5+dfsg/server.h000066400000000000000000000013051347750676600156140ustar00rootroot00000000000000 /* * Name: server.h */ #include "ax25_pad.h" /* for packet_t */ #include "config.h" void server_set_debug (int n); void server_init (struct audio_s *audio_config_p, struct misc_config_s *misc_config); void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int flen); int server_callsign_registered_by_client (char *callsign); void server_link_established (int chan, int client, char *remote_call, char *own_call, int incoming); void server_link_terminated (int chan, int client, char *remote_call, char *own_call, int timeout); void server_rec_conn_data (int chan, int client, char *remote_call, char *own_call, int pid, char *data_ptr, int data_len); /* end server.h */ direwolf-1.5+dfsg/sock.c000066400000000000000000000270141347750676600152450ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: sock.c * * Purpose: Functions for TCP sockets. * * Description: These are used for connecting between different applications, * possibly on different hosts. * * New in version 1.5: * Duplicate code already exists in multiple places and I was about * to add another one. Instead, we will gather the common code here * instead of having yet another copy. * *---------------------------------------------------------------*/ #include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h #if __WIN32__ #include #include // _WIN32_WINNT must be set to 0x0501 before including this #else #include #include #include #include #include #include #include #include #include //#include #include #endif #include #include #include #include #include #include #include "textcolor.h" #include "sock.h" static void shuffle (struct addrinfo *host[], int nhosts); /*------------------------------------------------------------------- * * Name: sock_init * * Purpose: Preparation before using socket interface. * * Inputs: none * * Returns: 0 for success, -1 for error. * * Errors: Message is printed. I've never seen it fail. * * Description: Doesn't do anything for Linux. * * TODO: Use this instead of own copy in aclients.c * TODO: Use this instead of own copy in appserver.c * TODO: Use this instead of own copy in audio_win.c * TODO: Use this instead of own copy in igate.c * TODO: Use this instead of own copy in kissnet.c * TODO: Use this instead of own copy in kissutil.c * TODO: Use this instead of own copy in server.c * TODO: Use this instead of own copy in tnctest.c * TODO: Use this instead of own copy in ttcalc.c * *--------------------------------------------------------------------*/ int sock_init(void) { #if __WIN32__ WSADATA wsadata; int err; err = WSAStartup (MAKEWORD(2,2), &wsadata); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf("WSAStartup failed, error: %d\n", err); return (-1); } if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { text_color_set(DW_COLOR_ERROR); dw_printf("Could not find a usable version of Winsock.dll\n"); WSACleanup(); return (-1); } #endif return (0); } /* end sock_init */ /*------------------------------------------------------------------- * * Name: sock_connect * * Purpose: Connect to given host / port. * * Inputs: hostname - Host name or IP address. * * port - TCP port as text string. * * description - Description of the remote server to be used in error message. * e.g. "APRS-IS (Igate) Server" or "TCP KISS TNC". * * allow_ipv6 - True to allow IPv6. Otherwise only IPv4. * * debug - Print debugging information. * * Outputs: ipaddr_str - The IP address, in text form, is placed here in case * the caller wants it. Should be SOCK_IPADDR_LEN bytes. * * Returns: Socket Handle / file descriptor or -1 for error. * * Errors: (1) Can't find address for given host name. * * Print error and return -1. * * (2) Can't connect to one of the address(es). * * Silently try the next one. * * (3) Can't connect to any of the address(es). * * Nothing is printed for success. The caller might do that * to provide confirmation on what is happening. * *--------------------------------------------------------------------*/ int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char ipaddr_str[SOCK_IPADDR_LEN]) { #define MAX_HOSTS 50 struct addrinfo hints; struct addrinfo *ai_head = NULL; struct addrinfo *ai; struct addrinfo *hosts[MAX_HOSTS]; int num_hosts, n; int err; int server_sock = -1; strlcpy (ipaddr_str, "???", SOCK_IPADDR_LEN); memset (&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */ if ( ! allow_ipv6) { hints.ai_family = AF_INET; /* IPv4 only. */ } hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; /* * First, we need to look up the DNS name to get IP address. * It is possible to have multiple addresses. */ ai_head = NULL; err = getaddrinfo(hostname, port, &hints, &ai_head); if (err != 0) { text_color_set(DW_COLOR_ERROR); #if __WIN32__ dw_printf ("Can't get address for %s, %s, err=%d\n", description, hostname, WSAGetLastError()); #else dw_printf ("Can't get address for %s, %s, %s\n", description, hostname, gai_strerror(err)); #endif freeaddrinfo(ai_head); return (-1); } if (debug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("getaddrinfo returns:\n"); } num_hosts = 0; for (ai = ai_head; ai != NULL; ai = ai->ai_next) { if (debug) { text_color_set(DW_COLOR_DEBUG); sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN); dw_printf (" %s\n", ipaddr_str); } hosts[num_hosts] = ai; if (num_hosts < MAX_HOSTS) num_hosts++; } shuffle (hosts, num_hosts); if (debug) { text_color_set(DW_COLOR_DEBUG); dw_printf ("addresses for hostname:\n"); for (n=0; nai_family, hosts[n]->ai_addr, ipaddr_str, SOCK_IPADDR_LEN); dw_printf (" %s\n", ipaddr_str); } } /* * Try each address until we find one that is successful. */ for (n = 0; n < num_hosts; n++) { #if __WIN32__ SOCKET is; #else int is; #endif ai = hosts[n]; sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN); is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); #if __WIN32__ if (is == INVALID_SOCKET) { printf ("Socket creation failed, err=%d", WSAGetLastError()); WSACleanup(); is = -1; continue; } #else if (err != 0) { printf ("Socket creation failed, err=%s", gai_strerror(err)); (void) close (is); is = -1; continue; } #endif #ifndef DEBUG_DNS err = connect(is, ai->ai_addr, (int)ai->ai_addrlen); #if __WIN32__ if (err == SOCKET_ERROR) { #if DEBUGx printf("Connect to %s on %s (%s), port %s failed.\n", description, hostname, ipaddr_str, port); #endif closesocket (is); is = -1; continue; } #else if (err != 0) { #if DEBUGx printf("Connect to %s on %s (%s), port %s failed.\n", description, hostname, ipaddr_str, port); #endif (void) close (is); is = -1; continue; } /* IGate documentation says to use no delay. */ /* Does it really make a difference? */ int flag = 1; err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag)); if (err < 0) { printf("setsockopt TCP_NODELAY failed.\n"); } #endif /* Success. */ server_sock = is; #endif break; } freeaddrinfo(ai_head); // no, caller should handle this. // function should be generally be silent unless debug option. if (server_sock == -1) { text_color_set(DW_COLOR_ERROR); dw_printf("Unable to connect to %s at %s (%s), port %s\n", description, hostname, ipaddr_str, port ); } return (server_sock); } /* end sock_connect */ /*------------------------------------------------------------------- * * Name: sock_bind * * Purpose: We also have a bunch of duplicate code for the server side. * * Inputs: * * TODO: Use this instead of own copy in audio.c * TODO: Use this instead of own copy in audio_portaudio.c * TODO: Use this instead of own copy in audio_win.c * TODO: Use this instead of own copy in kissnet.c * TODO: Use this instead of own copy in server.c * *--------------------------------------------------------------------*/ // Not implemented yet. /* * Addresses don't get mixed up very well. * IPv6 always shows up last so we'd probably never * end up using any of them for APRS-IS server. * Add our own shuffle. */ static void shuffle (struct addrinfo *host[], int nhosts) { int j, k; assert (RAND_MAX >= nhosts); /* for % to work right */ if (nhosts < 2) return; srand (time(NULL)); for (j=0; j=0 && ksin_addr.S_un.S_un_b.s_b1, sa4->sin_addr.S_un.S_un_b.s_b2, sa4->sin_addr.S_un.S_un_b.s_b3, sa4->sin_addr.S_un.S_un_b.s_b4); #else inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize); #endif break; case AF_INET6: sa6 = (struct sockaddr_in6 *)pAddr; #if __WIN32__ snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x", ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7])); #else inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize); #endif break; default: snprintf (pStringBuf, StringBufSize, "Invalid address family!"); } return pStringBuf; } /* end sock_ia_to_text */ /* end sock.c */ direwolf-1.5+dfsg/sock.h000066400000000000000000000011401347750676600152420ustar00rootroot00000000000000 /* sock.h - Socket helper functions. */ #ifndef SOCK_H #define SOCK_H 1 #define SOCK_IPADDR_LEN 48 // Size of string to hold IPv4 or IPv6 address. // I think 40 would be adequate but we'll make // it a little larger just to be safe. // Use INET6_ADDRSTRLEN (from netinet/in.h) instead? int sock_init (void); int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char *ipaddr_str); /* ipaddr_str needs to be at least SOCK_IPADDR_LEN bytes */ char *sock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize); #endifdirewolf-1.5+dfsg/symbols-new.txt000066400000000000000000000431101347750676600171550ustar00rootroot00000000000000APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 3 Apr 2017 --------------------------------------------------------------------- BACKGROUND: Since 1 October 2007, overlay characters (36 per symbol) are allowed on all symbols. Since the master symbol document, http://aprs.org/symbols/symbolsX.txt page only has one line per symbol character this overlay list gives us thousands of new symbol codes. 03 Apr17: Added Methane Hazard symbol "MH" 13 Feb17: Added Ez = Emergency Power (shelter), Cars: P> = Plugin S> = Solar powered. Moved Ham club C- to Buildings Ch. Added C- to house for combined renewables and added R% Renewable to power plants 18 Oct16: Added G,Y,R for Flood gauges + N for Normal.(on H2O symbol) Added DIGIPEATERS 22 Mar16: Added A0 overlay circle for ALSTAR nodes and V0 for VOIP. Combined echolink and IRLP. P& for PSKmail node. W& for Wires-X (was W0 for WiresII). Ya for Yaesu C4FM repeaters Update 29 Oct 2015: Reorgainized list to Alphabetical Order. + Added many new Balloons (due to lost DoD radar Blimp yesterday) + Confirmed D^ for Drones was already in there since 2014 + Added R^ type aircraft for remotely piloted + Added S^ Solar Powered Aircraft + Noticed all new category \= is availalbe. Had been shown ast APRStt, but that was changed in 2009 to overlay BOX symbols. UPDATES/REVISIONS/CORRECTIONS: 2014 Added numerous OpenAPRS symbol. Changed Da to DSTAR from Dutch ARES. Added Subs to ships and lots of Aircraft overlays. 2013 Added ship overlay Jet Ski. Ham Club as C overlay on House, C- 2011 Added T and 2 overlays for TX 1 and 2 hop IGates Added overlays to (;) Portables Added Radiation Detector (RH) 2010 Byonics requested (BY) and added #A to the table 2009 Added W0 for Yaesu WIRES and APRStt symbol to overlayed BOX (#A) 2008 Added RFID R=, Babystroller B], Radio#Y, skull&Xbones XH DEPRICATION AND MAJOR REVISION: 25 Mar 08 Modified several Alternate Symbol codes for expanded Overlays. The following alternate base symbols were redefined so that the basic symbol could take on dozens of unique overlay definitions: \= - Had been undefined \0 - Several overlays for the numbered Circle \A - (BOX symbol) APRStt(DTMF), RFID users, XO (OLPC) \' - Was Crash Site. Now expanded to be INCIDENT sites \% - is an overlayed Powerplant. See definitions below \H - \H is HAZE but other H overlays are HAZARDs. WH is "H.Waste" \Y - Overlays for Radios and other APRS devices \k - Overlay Special vehicles. A = ATV for example \u - Overlay Trucks. "Tu" is a tanker. "Gu" is a gas truck, etc \< - Advisories may now have overlays \8 - Nodes with overlays. "G8" would be 802.11G \[ - \[ is wall cloud, but overlays are humans. S[ is a skier. \h - Buildings. \h is a Ham store, "Hh" is Home Depot, etc. 4 Oct 2007. ORIGINAL EXPANSION to OVERLAYS ON ALL SYMBOLS In April 2007, a proposal to expand the use of overlay bytes for the extension of the APRS symbol set was added to the draft APRS1.2 addendum web page. The following document addresses that proposal: http://aprs.org/symbols/symbols-overlays.txt For details on Upgrading your symbol set, please see the background information on Symbols prepared by Stephen Smith, WA8LMF: www.aprs.org/symbols/symbols-background.txt CONSISTANCY: Since the objective of APRS is consistent, reliable communications at the local level, there has been a hesitance to making significant changes to the APRS symbol set. The Integrity of APRS depends on everyone seeing the same information at the same time. Frequent changes to the symbol sets can actually undermine that integrity and operational utility of APRS and end up with worse outcomes due to miss-communications than the lack of any particular symbol might suggest. OVERLAY HISTORY: When the overlay symbol set was first defined for the original APRS back in 1995, it had the potential to expand the APRS symbol set from the 94 original primary symbols to a secondary set that could each have as many as 36 diffeernt overlays on each of those secondary symbols up to almost 3500 combinations. But some authors then could not easily implement these overlays, except by one-by-one exceptions to their code. For this reason, a compromise was made with those authors and then eventually written into the APRS spec to limit overlays to only a small subset of alternate symbols. Those original overlayable alternate symbols were labeled with a "#" and called "numbered" symbols. (UIview requires "No." in the symbols.ini file) STATUS OF OVERLAYS 1 OCTOBER 2007: the APRS symbol set only had a few remaining unused symbol codes that had not yet been defined: OF THE 94 Primary Symbols. The following were available: 10 symbols (/0 - /9) that mostly look like billiard balls now 4 symbols /D, /J, /Q, /z were undefined or TBD 2 were reserved OF THE 94 Alternate Symbols. The following were available: 3 undefined series \=, \Y, \Z which could do 36 overlays 8 series \1 through \8 that can support 36 overlays each 3 reserved series. ADDITIONAL OVERLAY PROPOSAL: But any of the other 79 alternate symbols could all have multiple (36) overlays if they can make sense with the existing underlying basic symbol that we have been using for that basic alternate symbol. That is, any new definition of a previously unused overlay character will have undefined results on all prior APRS systems and should be used with caution. But the symbol set is extensible with these cautions. (See the Proposal that would expand the APRS symbol set to over 3200 at the bottom of this document.) SYMBOL OVERLAY TABLES: This document will keep track of all definitions of overlays on all ALTERNATE symbols. Although these overlays were originally intended to just overlay a displayable single character on a basic symbol, there is no prohibition against taking the combination of a symbol and specific overlay, and then letting that define a new graphic just for that combination. The following tables will attempt to keep track of these and any other useful generic applications of overlay characters. AMPLIFIED some existing ALTERNATE SYMBOL Overlays: (new Aug 2014) change Flooding #W to include Avalanche, Mudslide/Landslide Update #' name to crash & incident sites Update \D (was available) to DEPOT family change overlayed car to generic Vehicle with (1-9 overlays) ADVISORIES: #< (new expansion possibilities) /< = motorcycle \< = Advisory (single gale flag) AIRCRAFT /^ = LARGE Aircraft \^ = top-view originally intended to point in direction of flight A^ = Autonomous (2015) D^ = Drone (new may 2014) E^ = Electric aircraft (2015) H^ = Hovercraft (new may 2014) J^ = JET (new may 2014) M^ = Missle (new may 2014) P^ = Prop (new Aug 2014) R^ = Remotely Piloted (new 2015) S^ = Solar Powered (new 2015) V^ = Vertical takeoff (new may 2014) X^ = Experimental (new Aug 2014) ATM Machine or CURRENCY: #$ /$ = original primary Phone \$ = Bank or ATM (generic) U$ = US dollars L$ = Brittish Pound Y$ = Japanese Yen ARRL or DIAMOND: #a /a = Ambulance Aa = ARES Da = DSTAR (had been ARES Dutch) Ga = RSGB Radio Society of Great Brittan Ra = RACES Sa = SATERN Salvation Army Wa = WinLink Ya = C4FM Yaesu repeaters BALLOONS and lighter than air #O (All new Oct 2015) /O = Original Balloon (think Ham balloon) \O = ROCKET (amateur)(2007) BO = Blimp (2015) MO = Manned Balloon (2015) TO = Teathered (2015) CO = Constant Pressure - Long duration (2015) RO = Rocket bearing Balloon (Rockoon) (2015) BOX SYMBOL: #A (and other system inputted symbols) /A = Aid station \A = numbered box 9A = Mobile DTMF user 7A = HT DTMF user HA = House DTMF user EA = Echolink DTMF report IA = IRLP DTMF report RA = RFID report AA = AllStar DTMF report DA = D-Star report XA = OLPC Laptop XO etc BUILDINGS: #h /h = Hospital \h = Ham Store ** <= now used for HAMFESTS Ch = Club (ham radio) Fh = HamFest (new Aug 2014) Hh = Home Depot etc.. CARS: #> (Vehicles) /> = normal car (side view) \> = Top view and symbol POINTS in direction of travel #> = Reserve overlays 1-9 for numbered cars (new Aug 2014) B> = Battery (was E for electric) E> = Ethanol (was electric) F> = Fuelcell or hydrogen H> = Homemade P> = Plugin-hybrid S> = Solar powered T> = Tesla (temporary) V> = GM Volt (temporary) CIVIL DEFENSE or TRIANGLE: #c /c = Incident Command Post \c = Civil Defense Dc = Decontamination (new Aug 2014) Rc = RACES Sc = SATERN mobile canteen DEPOT /D = was originally undefined \D = was drizzle (moved to ' ovlyD) AD = Airport (new Aug 2014) FD = Ferry Landing (new Aug 2014) HD = Heloport (new Aug 2014) RD = Rail Depot (new Aug 2014) BD = Bus Depot (new Aug 2014) LD = LIght Rail or Subway (new Aug 2014) SD = Seaport Depot (new Aug 2014) DIGIPEATERS /# - Generic digipeater 1# - WIDE1-1 digipeater A# - Alternate input (i.e. 144.990MHz) digipeater E# - Emergency powered (assumed full normal digi) I# - I-gate equipped digipeater L# - WIDEn-N with path length trapping P# - PacComm S# - SSn-N digipeater (includes WIDEn-N) X# - eXperimental digipeater V# - Viscous https://github.com/PhirePhly/aprx/blob/master/ViscousDigipeater.README W# - WIDEn-N, SSn-N and Trapping EMERGENCY: #! /! = Police/Sheriff, etc \! = Emergency! E! = ELT or EPIRB (new Aug 2014) V! = Volcanic Eruption or Lava (new Aug 2014) EYEBALL (EVENT) and VISIBILITY #E /E = Eyeball for special live events \E = (existing smoke) the symbol with no overlay HE = (H overlay) Haze SE = (S overlay) Smoke BE = (B overlay) Blowing Snow was \B DE = (D overlay) blowing Dust or sand was \b FE = (F overlay) Fog was \{ GATEWAYS: #& /& = HF Gateway <= the original primary table definition I& = Igate Generic (please use more specific overlay) R& = Receive only IGate (do not send msgs back to RF) P& = PSKmail node T& = TX igate with path set to 1 hop only) W& = WIRES-X as opposed to W0 for WiresII 2& = TX igate with path set to 2 hops (not generally good idea) GPS devices: #\ /\ = Triangle DF primary symbol \\ = was undefined alternate symbol A\ = Avmap G5 * <= Recommend special symbol HAZARDS: #H /H = hotel \H = Haze MH = Methane Hazard (new Apr 2017) RH = Radiation detector (new mar 2011) WH = Hazardous Waste XH = Skull&Crossbones HUMAN SYMBOL: #[ /[ = Human \[ = Wall Cloud (the original definition) B[ = Baby on board (stroller, pram etc) S[ = Skier * <= Recommend Special Symbol R[ = Runner H[ = Hiker HOUSE: #- /- = House \- = (was HF) 5- = 50 Hz if non standard 6- = 60 Hz if non standard B- = Battery or off grid C- = Combined alternatives E- = Emergency power (grid down) G- = Geothermal H- = Hydro powered O- = Operator Present S- = Solar Power W- = Wind power INCIDENT SITES: #' /' = Small Aircraft (original primary symbol) \' = Airplane Crash Site <= the original alternate deifinition A' = Automobile crash site H' = Hazardous incident M' = Multi-Vehicle crash site P' = Pileup T' = Truck wreck NUMBERED CIRCLES: #0 A0 = Allstar Node (A0) E0 = Echolink Node (E0) I0 = IRLP repeater (I0) S0 = Staging Area (S0) V0 = Echolink and IRLP (VOIP) W0 = WIRES (Yaesu VOIP) NETWORK NODES: #8 88 = 802.11 network node (88) G8 = 802.11G (G8) PORTABLE SYMBOL: #; /; = Portable operation (tent) \; = Park or Picnic F; = Field Day I; = Islands on the air S; = Summits on the air W; = WOTA POWER or ENERGY: #% /% = DX cluster <= the original primary table definition C% = Coal E% = Emergency (new Aug 2014) G% = Geothermal H% = Hydroelectric N% = Nuclear P% = Portable (new Aug 2014) R% = Renewable (hydrogen etc fuels) S% = Solar T% = Turbine W% = Wind RESTAURANTS: #R \R = Restaurant (generic) 7R = 7/11 KR = KFC MR = McDonalds TR = Taco Bell RADIOS and APRS DEVICES: #Y /Y = Yacht <= the original primary symbol \Y = <= the original alternate was undefined AY = Alinco BY = Byonics IY = Icom KY = Kenwood * <= Recommend special symbol YY = Yaesu/Standard* <= Recommend special symbol SPECIAL VEHICLES: #k /k = truck \k = SUV 4k = 4x4 Ak = ATV (all terrain vehicle) SHELTERS: #z /z = was available \z = overlayed shelter Cz = Clinic (new Aug 2014) Ez = Emergency Power Gz = Government building (new Aug 2014) Mz = Morgue (new Aug 2014) Tz = Triage (new Aug 2014) SHIPS: #s /s = Power boat (ship) side view \s = Overlay Boat (Top view) 6s = Shipwreck ("deep6") (new Aug 2014) Bs = Pleasure Boat Cs = Cargo Ds = Diving Es = Emergency or Medical transport Fs = Fishing Hs = High-speed Craft Js = Jet Ski Ls = Law enforcement Ms = Miltary Os = Oil Rig Ps = Pilot Boat (new Aug 2014) Qs = Torpedo Ss = Search and Rescue Ts = Tug (new Aug 2014) Us = Underwater ops or submarine Ws = Wing-in-Ground effect (or Hovercraft) Xs = Passenger (paX)(ferry) Ys = Sailing (large ship) TRUCKS: #u /u = Truck (18 wheeler) \u = truck with overlay Bu = Buldozer/construction/Backhoe (new Aug 2014) Gu = Gas Pu = Plow or SnowPlow (new Aug 2014) Tu = Tanker Cu = Chlorine Tanker Hu = Hazardous WATER #w /w = Water Station or other H2O \w = flooding (or Avalanche/slides) Aw = Avalanche Gw = Green Flood Gauge Mw = Mud slide Nw = Normal flood gauge (blue) Rw = Red flood gauge Sw = Snow Blockage Yw = Yellow flood gauge Anyone can use any overlay on any of the overlayable symbols for any special purpose. We are not trying to document all possible such overlays. The purpose of this document is to help keep a list of more common such definitions that have more universal use and for which multiple definitions would lead to confusion. Future APRS code writers should be aware of where we are going: ******************************************************************** PROPOSAL TO ASSIGN MANY MORE BLOCKS OF SYMBOL SETS April 2007 -------------------------------------------------------------------- www.aprs.org/symbols/symbols-overlays.txt In the initiative to upgrade APRS symbols, we are wasting some very valuable OVERLAYABLE symbol subgroups with a few nailed down legacy weather symbols. We are proposing to consolidate some of these Weather symbols to open up much more space. Since this is the first time we have considered actually CHANGING some symbol definitions, this can cause problems out there for some users of some legacy systems. That is why I am posting this plan to the APRS community. If we do this, XASTIR and UIVIEW will be able to download new symbol definitions. But some legacy clients that do not operate from external symbol files will show the wrong symbols for these. If users of those systems forsee some serious problems with the re-arrangement of these symbols, let us know the specific impact and your ideas for a workaround.. The symbols that would be impacted are as follows: First, consolidate all of the visibility symbols into the old SMOKE symbol and change its meaning to "VISIBILITY", and then differentiate them with the overlay characters. "\E" (existing smoke) the symbol with no overlay "HE" (an H overlay) will mean Haze "SE" (an S overlay) will mean Smoke "BE" (a B overlay) will mean Blowing Snow was \B "DE" (a D overlay) will mean blowing Dust or sand was \b "FE" (an F overlay) will mean Fog was \{ Another category is to expand the RAIN symbol to make it kinda like lots of angled dots coming from the sky, but with an open center so that we can use overlays for a number of common PRECIPITATIONS. The consolidations would be: "\`" (existing Rain) would be the symbol with no overlay "R`" (an R overlay) would mean Rain "F`" (an F overlay) would mean Freezing Rain was \F "H`" (an H overlay) would mean Hail was\: "D`" (an D overlay) would mean Drizzle was \D "E`" (an E overlay) would mean slEEt was \e "S`" (an S overlay) would mean Snow was \* Etc. and other particulates coming from the sky Next, I propose expanding the existing RAIN SHOWER "\I" symbol to look like some kind of cloud symbol with specks in it that can be overlayed. (It needs to look different from the next CLOUD symbol). It can then consolidate these symbols: "RI" (an R overlay) would mean Rain Shower "SI" (an S overlay) would mean Snow shower was \G "LI" (an L overlay) would mean Lightening was \J Etc. and other things related to clouds Next, I propose expanding the existing CLOUD symbol to allow definition of any number of different types of cloud. This needs to also look like a cloud but a different shape and allow for overlays (Maybe this cloud is clear): "\(" is the existing cloud symbol (would have no overlay) "P(" with P overlay would mean partly cloudy was \p "W(" with W overlay would be a wall cloud was \[ "F(" would be Funnel cloud, but the original "\f" will also be retained for now All of these initiative will free up a lot of overlayable symbol GROUPS each of which can suport up to 36 different overlays in each group for the future: #H for 36 new Hazards (was only Hail) #[ for 36 new human symbols (was only Wall Cloud) #\ for 36 new GPS or navigation equipment #B TBD. \B was only blowing snow now is BE #b TBD. \b was only blowing dust/sand now is DE #{ TBD. \{ was only fog now is FE #* TBD. \* was snow only now is S` #: TBD. \: was hail only now is H` #D TBD. \D was drizzle only now is D` #F TBD. \F was freezing rain only now is F` #e TBD. \e was sleet only now is E` #G TBD. \G was only Snow shower now is SI #J TBD. \J was only Lightening now is LI #p TBD. \p was only partly cloudy now is P( Of course future code can fully draw each of these overlays as distinct special symbols in any way they want. I especially want to hear from Dale Hugley who is a resource for weather, and Stephen Smith who will have to draw them for Uiview. And others with a stake in this... Bob Bruninga, WB4APR direwolf-1.5+dfsg/symbols.c000066400000000000000000000742321347750676600160020ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * File: symbols.c * * Purpose: Functions related to the APRS symbols * *------------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include "textcolor.h" #include "symbols.h" #include "tt_text.h" /* * APRS symbol tables. * * Derived from http://www.aprs.org/symbols/symbolsX.txt * version of 20 Oct 2009. */ /* * Primary symbol table. */ #define SYMTAB_SIZE 95 static const struct { char xy[3]; char *description; } primary_symtab[SYMTAB_SIZE] = { /* 00 */ { "~~", "--no-symbol--" }, /* ! 01 */ { "BB", "Police, Sheriff" }, /* " 02 */ { "BC", "reserved (was rain)" }, /* # 03 */ { "BD", "DIGI (white center)" }, /* $ 04 */ { "BE", "PHONE" }, /* % 05 */ { "BF", "DX CLUSTER" }, /* & 06 */ { "BG", "HF GATEway" }, /* ' 07 */ { "BH", "Small AIRCRAFT" }, /* ( 08 */ { "BI", "Mobile Satellite Station" }, /* ) 09 */ { "BJ", "Wheelchair (handicapped)" }, /* * 10 */ { "BK", "SnowMobile" }, /* + 11 */ { "BL", "Red Cross" }, /* , 12 */ { "BM", "Boy Scouts" }, /* - 13 */ { "BN", "House QTH (VHF)" }, /* . 14 */ { "BO", "X" }, /* / 15 */ { "BP", "Red Dot" }, /* 0 16 */ { "P0", "# circle (obsolete)" }, /* 1 17 */ { "P1", "TBD" }, /* 2 18 */ { "P2", "TBD" }, /* 3 19 */ { "P3", "TBD" }, /* 4 20 */ { "P4", "TBD" }, /* 5 21 */ { "P5", "TBD" }, /* 6 22 */ { "P6", "TBD" }, /* 7 23 */ { "P7", "TBD" }, /* 8 24 */ { "P8", "TBD" }, /* 9 25 */ { "P9", "TBD" }, /* : 26 */ { "MR", "FIRE" }, /* ; 27 */ { "MS", "Campground (Portable ops)" }, /* < 28 */ { "MT", "Motorcycle" }, /* = 29 */ { "MU", "RAILROAD ENGINE" }, /* > 30 */ { "MV", "CAR" }, /* ? 31 */ { "MW", "SERVER for Files" }, /* @ 32 */ { "MX", "HC FUTURE predict (dot)" }, /* A 33 */ { "PA", "Aid Station" }, /* B 34 */ { "PB", "BBS or PBBS" }, /* C 35 */ { "PC", "Canoe" }, /* D 36 */ { "PD", "" }, /* E 37 */ { "PE", "EYEBALL (Eye catcher!)" }, /* F 38 */ { "PF", "Farm Vehicle (tractor)" }, /* G 39 */ { "PG", "Grid Square (6 digit)" }, /* H 40 */ { "PH", "HOTEL (blue bed symbol)" }, /* I 41 */ { "PI", "TcpIp on air network stn" }, /* J 42 */ { "PJ", "" }, /* K 43 */ { "PK", "School" }, /* L 44 */ { "PL", "PC user" }, /* M 45 */ { "PM", "MacAPRS" }, /* N 46 */ { "PN", "NTS Station" }, /* O 47 */ { "PO", "BALLOON" }, /* P 48 */ { "PP", "Police" }, /* Q 49 */ { "PQ", "TBD" }, /* R 50 */ { "PR", "REC. VEHICLE" }, /* S 51 */ { "PS", "SHUTTLE" }, /* T 52 */ { "PT", "SSTV" }, /* U 53 */ { "PU", "BUS" }, /* V 54 */ { "PV", "ATV" }, /* W 55 */ { "PW", "National WX Service Site" }, /* X 56 */ { "PX", "HELO" }, /* Y 57 */ { "PY", "YACHT (sail)" }, /* Z 58 */ { "PZ", "WinAPRS" }, /* [ 59 */ { "HS", "Human/Person (HT)" }, /* \ 60 */ { "HT", "TRIANGLE(DF station)" }, /* ] 61 */ { "HU", "MAIL/PostOffice(was PBBS)" }, /* ^ 62 */ { "HV", "LARGE AIRCRAFT" }, /* _ 63 */ { "HW", "WEATHER Station (blue)" }, /* ` 64 */ { "HX", "Dish Antenna" }, /* a 65 */ { "LA", "AMBULANCE" }, /* b 66 */ { "LB", "BIKE" }, /* c 67 */ { "LC", "Incident Command Post" }, /* d 68 */ { "LD", "Fire dept" }, /* e 69 */ { "LE", "HORSE (equestrian)" }, /* f 70 */ { "LF", "FIRE TRUCK" }, /* g 71 */ { "LG", "Glider" }, /* h 72 */ { "LH", "HOSPITAL" }, /* i 73 */ { "LI", "IOTA (islands on the air)" }, /* j 74 */ { "LJ", "JEEP" }, /* k 75 */ { "LK", "TRUCK" }, /* l 76 */ { "LL", "Laptop" }, /* m 77 */ { "LM", "Mic-E Repeater" }, /* n 78 */ { "LN", "Node (black bulls-eye)" }, /* o 79 */ { "LO", "EOC" }, /* p 80 */ { "LP", "ROVER (puppy, or dog)" }, /* q 81 */ { "LQ", "GRID SQ shown above 128 m" }, /* r 82 */ { "LR", "Repeater" }, /* s 83 */ { "LS", "SHIP (pwr boat)" }, /* t 84 */ { "LT", "TRUCK STOP" }, /* u 85 */ { "LU", "TRUCK (18 wheeler)" }, /* v 86 */ { "LV", "VAN" }, /* w 87 */ { "LW", "WATER station" }, /* x 88 */ { "LX", "xAPRS (Unix)" }, /* y 89 */ { "LY", "YAGI @ QTH" }, /* z 90 */ { "LZ", "TBD" }, /* { 91 */ { "J1", "" }, /* | 92 */ { "J2", "TNC Stream Switch" }, /* } 93 */ { "J3", "" }, /* ~ 94 */ { "J3", "TNC Stream Switch" } }; /* * Alternate symbol table. */ static const struct { char xy[3]; char *description; } alternate_symtab[SYMTAB_SIZE] = { /* 00 */ { "~~", "--no-symbol--" }, /* ! 01 */ { "OB", "EMERGENCY (!)" }, /* " 02 */ { "OC", "reserved" }, /* # 03 */ { "OD", "OVERLAY DIGI (green star)" }, /* $ 04 */ { "OE", "Bank or ATM (green box)" }, /* % 05 */ { "OF", "Power Plant with overlay" }, /* & 06 */ { "OG", "I=Igte IGate R=RX T=1hopTX 2=2hopTX" }, /* ' 07 */ { "OH", "Crash (& now Incident sites)" }, /* ( 08 */ { "OI", "CLOUDY (other clouds w ovrly)" }, /* ) 09 */ { "OJ", "Firenet MEO, MODIS Earth Obs." }, /* * 10 */ { "OK", "SNOW (& future ovrly codes)" }, /* + 11 */ { "OL", "Church" }, /* , 12 */ { "OM", "Girl Scouts" }, /* - 13 */ { "ON", "House (H=HF) (O = Op Present)" }, /* . 14 */ { "OO", "Ambiguous (Big Question mark)" }, /* / 15 */ { "OP", "Waypoint Destination" }, /* 0 16 */ { "A0", "CIRCLE (E/I/W=IRLP/Echolink/WIRES)" }, /* 1 17 */ { "A1", "" }, /* 2 18 */ { "A2", "" }, /* 3 19 */ { "A3", "" }, /* 4 20 */ { "A4", "" }, /* 5 21 */ { "A5", "" }, /* 6 22 */ { "A6", "" }, /* 7 23 */ { "A7", "" }, /* 8 24 */ { "A8", "802.11 or other network node" }, /* 9 25 */ { "A9", "Gas Station (blue pump)" }, /* : 26 */ { "NR", "Hail (& future ovrly codes)" }, /* ; 27 */ { "NS", "Park/Picnic area" }, /* < 28 */ { "NT", "ADVISORY (one WX flag)" }, /* = 29 */ { "NU", "APRStt Touchtone (DTMF users)" }, /* > 30 */ { "NV", "OVERLAYED CAR" }, /* ? 31 */ { "NW", "INFO Kiosk (Blue box with ?)" }, /* @ 32 */ { "NX", "HURICANE/Trop-Storm" }, /* A 33 */ { "AA", "overlayBOX DTMF & RFID & XO" }, /* B 34 */ { "AB", "Blwng Snow (& future codes)" }, /* C 35 */ { "AC", "Coast Guard" }, /* D 36 */ { "AD", "Drizzle (proposed APRStt)" }, /* E 37 */ { "AE", "Smoke (& other vis codes)" }, /* F 38 */ { "AF", "Freezng rain (&future codes)" }, /* G 39 */ { "AG", "Snow Shwr (& future ovrlys)" }, /* H 40 */ { "AH", "Haze (& Overlay Hazards)" }, /* I 41 */ { "AI", "Rain Shower" }, /* J 42 */ { "AJ", "Lightening (& future ovrlys)" }, /* K 43 */ { "AK", "Kenwood HT (W)" }, /* L 44 */ { "AL", "Lighthouse" }, /* M 45 */ { "AM", "MARS (A=Army,N=Navy,F=AF)" }, /* N 46 */ { "AN", "Navigation Buoy" }, /* O 47 */ { "AO", "Rocket" }, /* P 48 */ { "AP", "Parking" }, /* Q 49 */ { "AQ", "QUAKE" }, /* R 50 */ { "AR", "Restaurant" }, /* S 51 */ { "AS", "Satellite/Pacsat" }, /* T 52 */ { "AT", "Thunderstorm" }, /* U 53 */ { "AU", "SUNNY" }, /* V 54 */ { "AV", "VORTAC Nav Aid" }, /* W 55 */ { "AW", "# NWS site (NWS options)" }, /* X 56 */ { "AX", "Pharmacy Rx (Apothicary)" }, /* Y 57 */ { "AY", "Radios and devices" }, /* Z 58 */ { "AZ", "" }, /* [ 59 */ { "DS", "W.Cloud (& humans w Ovrly)" }, /* \ 60 */ { "DT", "New overlayable GPS symbol" }, /* ] 61 */ { "DU", "" }, /* ^ 62 */ { "DV", "# Aircraft (shows heading)" }, /* _ 63 */ { "DW", "# WX site (green digi)" }, /* ` 64 */ { "DX", "Rain (all types w ovrly)" }, /* a 65 */ { "SA", "ARRL, ARES, WinLINK" }, /* b 66 */ { "SB", "Blwng Dst/Snd (& others)" }, /* c 67 */ { "SC", "CD triangle RACES/SATERN/etc" }, /* d 68 */ { "SD", "DX spot by callsign" }, /* e 69 */ { "SE", "Sleet (& future ovrly codes)" }, /* f 70 */ { "SF", "Funnel Cloud" }, /* g 71 */ { "SG", "Gale Flags" }, /* h 72 */ { "SH", "Store. or HAMFST Hh=HAM store" }, /* i 73 */ { "SI", "BOX or points of Interest" }, /* j 74 */ { "SJ", "WorkZone (Steam Shovel)" }, /* k 75 */ { "SK", "Special Vehicle SUV,ATV,4x4" }, /* l 76 */ { "SL", "Areas (box,circles,etc)" }, /* m 77 */ { "SM", "Value Sign (3 digit display)" }, /* n 78 */ { "SN", "OVERLAY TRIANGLE" }, /* o 79 */ { "SO", "small circle" }, /* p 80 */ { "SP", "Prtly Cldy (& future ovrlys)" }, /* q 81 */ { "SQ", "" }, /* r 82 */ { "SR", "Restrooms" }, /* s 83 */ { "SS", "OVERLAY SHIP/boat (top view)" }, /* t 84 */ { "ST", "Tornado" }, /* u 85 */ { "SU", "OVERLAYED TRUCK" }, /* v 86 */ { "SV", "OVERLAYED Van" }, /* w 87 */ { "SW", "Flooding" }, /* x 88 */ { "SX", "Wreck or Obstruction ->X<-" }, /* y 89 */ { "SY", "Skywarn" }, /* z 90 */ { "SZ", "OVERLAYED Shelter" }, /* { 91 */ { "Q1", "Fog (& future ovrly codes)" }, /* | 92 */ { "Q2", "TNC Stream Switch" }, /* } 93 */ { "Q3", "" }, /* ~ 94 */ { "Q4", "TNC Stream Switch" } }; // Make sure the array is null terminated. // If search order is changed, do the same in decode_aprs.c static const char *search_locations[] = { (const char *) "symbols-new.txt", #ifndef __WIN32__ (const char *) "/usr/local/share/direwolf/symbols-new.txt", (const char *) "/usr/share/direwolf/symbols-new.txt", #endif #if __APPLE__ // https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458 // Adding the /opt/local tree since macports typically installs there. Users might want their // INSTALLDIR (see Makefile.macosx) to mirror that. If so, then we need to search the /opt/local // path as well. (const char *) "/opt/local/share/direwolf/symbols-new.txt", #endif (const char *) NULL }; /*------------------------------------------------------------------ * * Function: symbols_init * * Purpose: Initialization for functions related to symbols. * * Inputs: * * Global output: * new_sym_ptr * new_sym_size * new_sym_len * * Description: The primary and alternate symbol tables are constant * so they are hardcoded. * However the "new" sysmbols, which give new meanings to * overlayed symbols, are always evolving. * For maximum flexibility, we will read the * data file at run time rather than compiling it in. * * For the most recent version, download from: * * http://www.aprs.org/symbols/symbols-new.txt * * Windows version: File must be in current working directory. * * Linux version: Search order is current working directory * then /usr/share/direwolf directory. * *------------------------------------------------------------------*/ #define NEW_SYM_INIT_SIZE 20 #define NEW_SYM_DESC_LEN 29 typedef struct new_sym_s { char overlay; char symbol; char description[NEW_SYM_DESC_LEN+1]; } new_sym_t; static new_sym_t *new_sym_ptr = NULL; /* Dynamically allocated array. */ static int new_sym_size = 0; /* Number of elements allocated. */ static int new_sym_len = 0; /* Number of elements used. */ void symbols_init (void) { FILE *fp = NULL; /* * We only care about lines with this format: * * Column 1 - overlay character of / \ upper case or digit * Column 2 - symbol in range of ! thru ~ * Column 3 - space * Column 4 - equal sign * Column 5 - space * Column 6 - Start of description. */ #define COL1_OVERLAY 0 #define COL2_SYMBOL 1 #define COL3_SP 2 #define COL4_EQUAL 3 #define COL5_SP 4 #define COL6_DESC 5 char stuff[200]; int j; #define GOOD_LINE(x) (strlen(x) > 6 && \ (x[COL1_OVERLAY] == '/' || x[COL1_OVERLAY] == '\\' || isupper(x[COL1_OVERLAY]) || isdigit(x[COL1_OVERLAY])) \ && x[COL2_SYMBOL] >= '!' && x[COL2_SYMBOL] <= '~' \ && x[COL3_SP] == ' ' && x[COL4_EQUAL] == '=' && x[COL5_SP] == ' ' && x[COL6_DESC] != ' ') if (new_sym_ptr != NULL) { return; /* was called already. */ } // If search strategy changes, be sure to keep decode_tocall in sync. fp = NULL; j = 0; do { if (search_locations[j] == NULL) break; fp = fopen(search_locations[j++], "r"); } while (fp == NULL); if (fp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Warning: Could not open 'symbols-new.txt'.\n"); dw_printf ("The \"new\" overlayed character information will not be available.\n"); new_sym_size = 1; new_sym_ptr = calloc(new_sym_size, sizeof(new_sym_t)); /* Don't try again. */ new_sym_len = 0; return; } /* * Count number of interesting lines and allocate storage. */ while (fgets(stuff, sizeof(stuff), fp) != NULL) { if (GOOD_LINE(stuff)) { new_sym_size++; } } new_sym_ptr = calloc(new_sym_size, sizeof(new_sym_t)); /* * Rewind, read again, and save contents of interesting lines. */ rewind (fp); while (fgets(stuff, sizeof(stuff), fp) != NULL) { if (GOOD_LINE(stuff)) { for (j = strlen(stuff+COL6_DESC) - 1; j>=0 && stuff[COL6_DESC+j] <= ' '; j--) { stuff[COL6_DESC+j] = '\0'; } new_sym_ptr[new_sym_len].overlay = stuff[COL1_OVERLAY]; new_sym_ptr[new_sym_len].symbol = stuff[COL2_SYMBOL]; strncpy(new_sym_ptr[new_sym_len].description, stuff+COL6_DESC, NEW_SYM_DESC_LEN); new_sym_len++; } } fclose (fp); assert (new_sym_len == new_sym_size); #if 0 for (j=0; j', /* 9 - Car */ '<', /* 10 - Motorcycle */ 'O', /* 11 - Ballon */ 'j', /* 12 - Jeep */ 'R', /* 13 - Recreational Vehicle */ 'k', /* 14 - Truck */ 'v' /* 15 - Van */ }; void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, char *symbol) { char *p; /* * This part does not apply to MIC-E format because the destination * is used to encode latitude and other information. */ if (dti != '\'' && dti != '`') { /* * For GPSCnn, nn is the index into the primary symbol table. */ if (strncmp(dest, "GPSC", 4) == 0) { int nn; nn = atoi(dest+4); if (nn >= 1 && nn <= 94) { *symtab = '/'; /* Primary */ *symbol = ' ' + nn; return; } } /* * For GPSEnn, nn is the index into the primary symbol table. */ if (strncmp(dest, "GPSE", 4) == 0) { int nn; nn = atoi(dest+4); if (nn >= 1 && nn <= 94) { *symtab = '\\'; /* Alternate. */ *symbol = ' ' + nn; return; } } /* * For GPSxy or SPCxy or SYMxy, look up xy in the translation tables. * First search the primary table. */ if (strncmp(dest, "GPS", 3) == 0 || strncmp(dest, "SPC", 3) == 0 || strncmp(dest, "SYM", 3) == 0) { char xy[3]; int nn; xy[0] = dest[3]; xy[1] = dest[4]; xy[2] = '\0'; for (nn = 1; nn <= 94; nn++) { if (strcmp(xy, primary_symtab[nn].xy) == 0) { *symtab = '/'; /* Primary. */ *symbol = ' ' + nn; return; } } } /* * Next, search the alternate table. * This time, we can have the format ...xyz, where z is an overlay character. * Only upper case letters and digits are valid overlay characters. */ if (strncmp(dest, "GPS", 3) == 0 || strncmp(dest, "SPC", 3) == 0 || strncmp(dest, "SYM", 3) == 0) { char xy[3]; char z; int nn; xy[0] = dest[3]; xy[1] = dest[4]; xy[2] = '\0'; z = dest[5]; for (nn = 1; nn <= 94; nn++) { if (strcmp(xy, alternate_symtab[nn].xy) == 0) { *symtab = '\\'; /* Alternate. */ *symbol = ' ' + nn; if (isupper((int)z) || isdigit((int)z)) { *symtab = z; } return; } } } } /* end not MIC-E */ /* * When all else fails, use source SSID. */ p = strchr (src, '-'); if (p != NULL) { int ssid; ssid = atoi(p+1); if (ssid >= 1 && ssid <= 15) { *symtab = '/'; /* All in Primary table. */ *symbol = ssid_to_sym[ssid]; return; } } } /* symbols_from_dest_or_src */ /*------------------------------------------------------------------ * * Function: symbols_into_dest * * Purpose: Encode symbol for destination field. * * Inputs: symtab /, \, 0-9, A-Z * symbol any printable character ! to ~ * * Outputs: dest 6 character "destination" of the forms * GPSCnn - primary table. * GPSEnn - alternate table. * GPSxyz - alternate with overlay. * * Returns: 0 for success, 1 for error. * *------------------------------------------------------------------*/ int symbols_into_dest (char symtab, char symbol, char *dest) { if (symbol >= '!' && symbol <= '~' && symtab == '/') { /* Primary Symbol table. */ snprintf (dest, 7, "GPSC%02d", symbol - ' '); return (0); } else if (symbol >= '!' && symbol <= '~' && symtab == '\\') { /* Alternate Symbol table. */ snprintf (dest, 7, "GPSE%02d", symbol - ' '); return (0); } else if (symbol >= '!' && symbol <= '~' && (isupper(symtab) || isdigit(symtab))) { /* Alternate Symbol table with overlay. */ snprintf (dest, 7, "GPS%s%c", alternate_symtab[symbol - ' '].xy, symtab); return (0); } text_color_set(DW_COLOR_ERROR); dw_printf ("Could not convert symbol \"%c%c\" to GPSxyz destination format.\n", symtab, symbol); strlcpy (dest, "GPS???", sizeof(dest)); /* Error. */ return (1); } /*------------------------------------------------------------------ * * Function: symbols_get_description * * Purpose: Get description for given symbol table/code/overlay. * * Inputs: symtab /, \, 0-9, A-Z * symbol any printable character ! to ~ * * desc_size Size of description provided by caller * so we can avoid buffer overflow. * * Outputs: description Text description. * "--no-symbol--" if error. * * * Description: This is used for the monitoring and the * decode_aprs utility. * *------------------------------------------------------------------*/ void symbols_get_description (char symtab, char symbol, char *description, size_t desc_size) { char tmp2[2]; int j; symbols_init(); // The symbol table identifier should be // / for symbol from primary table // \ for symbol from alternate table // 0-9,A-Z for alternate symbol table with overlay character if (symtab != '/' && symtab != '\\' && ! isdigit(symtab) && ! isupper(symtab)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Symbol table identifier is not '/' (primary), '\\' (alternate), or valid overlay character.\n"); /* Possibilities: */ /* Select primary table and keep going, or */ /* report no symbol. It IS an error. */ /* We do the latter. */ symbol = ' '; strlcpy (description, primary_symtab[symbol-' '].description, desc_size); return; } // Bounds check before using symbol as index into table. if (symbol < ' ' || symbol > '~') { text_color_set(DW_COLOR_ERROR); dw_printf ("Symbol code is not a printable character.\n"); symbol = ' '; /* Avoid subscript out of bounds. */ } // First try to match with the "new" symbols. for (j=0; jX<- sort-of... 16 Jun 06: Suggest I for 2-way IGate and R overlay for RX only. 28 Sep 05: Added /F for Farm vehicle (looks like a tractor) 18 Jan 05: Added C overlay for CERTS to "\c" symbol 3 Jan 05: Added W overlay to "\a" symbol to indicate WinLINK. 7 Dec 04: the /] PBBS symbol (typically a blue Mail Box) is renamed as "MAIL/P.O.". PBBS users should use the BBS symbol. 8 Sep 04: Added Military Affiliate MARS symbol as \M with overlays SYMBOLS for APRS 1.1 ADDENDUM are as below. As of 26 July 04, the symbols below were approved and became part of the APRS1.1 addendum. JunJul 04 to add a Rocket "\O" and an SUV as "\k" 06 May 04 to move Shelter(overlay) from PRI to ALT table 5 Jan 04 to add Destination Waypoint "\/") 29 Oct 03 to add 802.11, firenet, IncidentCommandPost & Shelter 04 Feb 04: Unassigned symbols should display the international symbol of a circle with a slash through it. Meaning "not"... 29 Jan 04: Reviewed ALL symbols in the spec. Here are all additions: \& = is not just HF, but ANY GATEWAY with overlay character /) = Wheelchair (Handicapped) useful in Marathons (blue and white) \) = Firenet MEO symbol (MODIS Earth observation) \/ = Waypoint (destination) a RED dot showing intended destination. Uses special processing to draw a line from a mobile to his destination. This was proposed 5 Jan 2004 /L = Logged-ON user. (A PC symbol showing someone on APRS-IS) /l = Laptop (lower case L) (looks like a laptop) /c = Incident Command Post \y = Skywarn (black tornado, orange background with white surround) \z = Shelter (with overlay) (A red house with peaked roof) JUST-MOBILE-SYMBOLS: The following two lists of symbols were defined as "mobile" symbols for the purposess of filtering etc. This list has been published in APRS1.1 for over a decade. As of Nov08, this list was reviewd and updated: WAS: Pri: '<=>()*0COPRSUXY[^abefgjkpsuv Alt: /0>AKOS[^knsuv IS NOW: Pri: !'<=>()*0123456789CFOPRSUXY[\^abefgjkpsuv <== [added !F\ ] Alt: >KOSY[^ksuv\ <==[removed /0An] SYMBOLS.TXT APRS DISPLAY SYMBOLS APRSdos ORIGINAL ====================================================================== Document dated: 28 Apr 99 FInal APRSdos symbol spec **************: This file Remains CUrrent and is Updated Frequently **************: See date and updates at the top of this file. Author(s): Bob Bruninga, WB4APR ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The character after the latitude "N/S" in an APRS position report is a TABLE character and the character following the longitude "E/W" is the APRS symbol character. The TABLE character selects one of two symbol tables or may be used as an alphanumeric overlay over some symbols: TABLE RESULT & RESERVED for possible AUXILLIARY tables (Aug 09) / Primary symbol Table (Mostly stations) \ Alternate symbol table (Mostly Objects) 0-9 Alternate OVERLAY symbols with 0-9 overlayed A-Z Alternate OVERLAY symbols with A-Z overlayed For ease of reference we refer to these as the SYMBOL CHARACTERS and often abbreviate them as "/$" which refers to the Table character "/" and symbol character "$". Press F1-S in APRSdos to see these symbols. Some symbols may have a numeric overlay character 0-9 or A-Z. These are shown on the F1-S display with the numeral "3" overlayed. The original overlayable symbols through Oct 2007 were: CIRCLE, SQUARE, CAR, TRUCK, VAN, DIGIS, GATES Civil-Defense(RACES), NWS sites, WX stations, Triangle After 2007, provisions should be made in all software to allow for overlays on *any/all* alternate symbols for use in the future. SYMBOLS WITH STAND-ALONE GPS TRACKERS: Stand-alone devices that transmit raw GPS have no method to convey their symbol. For this unique application, the symbol can be designated in the UNPROTO TOADDRESS of the form GPSxyz. The X points to a subgroup table and the Y is the actual symbol. Z is an overlay character if used. Actually there are four TOCALLS that will provide the same symbol. GPSxyz is for stand alone trackers SPCxyz is for stand alone trackers at SPECIAL events in SPCL mode SYMxyz is for other TNC-only stations such as WX stations Notice that both the /$ method and GPSxyz method have a one-for-one corrspondence for all numeric and alphabetic symbols of both upper and lower case which should make them easy to remember. For the GPSxyz method, we have broken the PRIMARY and ALTERNATE tables into sub tables with different names to make them easier to remember. For example, "/C" is a CANOE in the PRIMARY table which can be referred to as PC in the XYZ format and the "\C" ALTERNATE table symbol is for Coast Guard which could also be referred to in the GPSxyz format as AC. Simillarly, you can think of the lower case symbols /c or \c as being LC for lower case C and SC for "secondary" table "c". The Following Symbol table shows the two types of identifiers for all APRS ICONS. The primary symbols are on the left and the alternate table is on the right. The first column of each is labeled /$ and \$ for the primary and alternate tables. These are the chacacters you will see in an APRS formatted position report. The XYZ columns are for the stand-alone trackers described above. /$ XYZ BASIC SYMBOL TABLE \$ XYZ OTHER SYMBOL TABLE (\) -- --- ------------------------ -- --- ---------------------- /! BB Police, Sheriff \! OBO EMERGENCY (and overlays) /" BC reserved (was rain) \" OC reserved /# BD DIGI (white center) \# OD# OVERLAY DIGI (green star) /$ BE PHONE \$ OEO Bank or ATM (green box) /% BF DX CLUSTER \% OFO Power Plant with overlay /& BG HF GATEway \& OG# I=Igte R=RX T=1hopTX 2=2hopTX /' BH Small AIRCRAFT (SSID-11) \' OHO Crash (& now Incident sites) /( BI Mobile Satellite Station \( OIO CLOUDY (other clouds w ovrly) /) BJ Wheelchair (handicapped) \) OJO Firenet MEO, MODIS Earth Obs. /* BK SnowMobile \* OK AVAIL (SNOW moved to ` ovly S) /+ BL Red Cross \+ OL Church /, BM Boy Scouts \, OM Girl Scouts /- BN House QTH (VHF) \- ONO House (H=HF) (O = Op Present) /. BO X \. OO Ambiguous (Big Question mark) // BP Red Dot \/ OP Waypoint Destination See APRSdos MOBILE.txt /$ XYZ PRIMARY SYMBOL TABLE \$ XYZ ALTERNATE SYMBOL TABLE (\) -- --- ------------------------ -- --- -------------------------- /0 P0 # circle (obsolete) \0 A0# CIRCLE (IRLP/Echolink/WIRES) /1 P1 TBD (these were numbered) \1 A1 AVAIL /2 P2 TBD (circles like pool) \2 A2 AVAIL /3 P3 TBD (balls. But with) \3 A3 AVAIL /4 P4 TBD (overlays, we can) \4 A4 AVAIL /5 P5 TBD (put all #'s on one) \5 A5 AVAIL /6 P6 TBD (So 1-9 are available)\6 A6 AVAIL /7 P7 TBD (for new uses?) \7 A7 AVAIL /8 P8 TBD (They are often used) \8 A8O 802.11 or other network node /9 P9 TBD (as mobiles at events)\9 A9 Gas Station (blue pump) /: MR FIRE \: NR AVAIL (Hail ==> ` ovly H) /; MS Campground (Portable ops) \; NSO Park/Picnic + overlay events /< MT Motorcycle (SSID-10) \< NTO ADVISORY (one WX flag) /= MU RAILROAD ENGINE \= NUO avail. symbol overlay group /> MV CAR (SSID-9) \> NV# OVERLAYED CARs & Vehicles /? MW SERVER for Files \? NW INFO Kiosk (Blue box with ?) /@ MX HC FUTURE predict (dot) \@ NX HURICANE/Trop-Storm /A PA Aid Station \A AA# overlayBOX DTMF & RFID & XO /B PB BBS or PBBS \B AB AVAIL (BlwngSnow ==> E ovly B /C PC Canoe \C AC Coast Guard /D PD \D ADO DEPOTS (Drizzle ==> ' ovly D) /E PE EYEBALL (Events, etc!) \E AE Smoke (& other vis codes) /F PF Farm Vehicle (tractor) \F AF AVAIL (FrzngRain ==> `F) /G PG Grid Square (6 digit) \G AG AVAIL (Snow Shwr ==> I ovly S) /H PH HOTEL (blue bed symbol) \H AHO \Haze (& Overlay Hazards) /I PI TcpIp on air network stn \I AI Rain Shower /J PJ \J AJ AVAIL (Lightening ==> I ovly L) /K PK School \K AK Kenwood HT (W) /L PL PC user (Jan 03) \L AL Lighthouse /M PM MacAPRS \M AMO MARS (A=Army,N=Navy,F=AF) /N PN NTS Station \N AN Navigation Buoy /O PO BALLOON (SSID-11) \O AO Overlay Balloon (Rocket = \O) /P PP Police \P AP Parking /Q PQ TBD \Q AQ QUAKE /R PR REC. VEHICLE (SSID-13) \R ARO Restaurant /S PS SHUTTLE \S AS Satellite/Pacsat /T PT SSTV \T AT Thunderstorm /U PU BUS (SSID-2) \U AU SUNNY /V PV ATV \V AV VORTAC Nav Aid /W PW National WX Service Site \W AW# # NWS site (NWS options) /X PX HELO (SSID-6) \X AX Pharmacy Rx (Apothicary) /Y PY YACHT (sail) (SSID-5) \Y AYO Radios and devices /Z PZ WinAPRS \Z AZ AVAIL /[ HS Human/Person (SSID-7) \[ DSO W.Cloud (& humans w Ovrly) /\ HT TRIANGLE(DF station) \\ DTO New overlayable GPS symbol /] HU MAIL/PostOffice(was PBBS) \] DU AVAIL /^ HV LARGE AIRCRAFT \^ DV# other Aircraft ovrlys (2014) /_ HW WEATHER Station (blue) \_ DW# # WX site (green digi) /` HX Dish Antenna \` DX Rain (all types w ovrly) /$ XYZ LOWER CASE SYMBOL TABLE \$ XYZ SECONDARY SYMBOL TABLE (\) -- --- ------------------------ -- --- -------------------------- /a LA AMBULANCE (SSID-1) \a SA#O ARRL,ARES,WinLINK,Dstar, etc /b LB BIKE (SSID-4) \b SB AVAIL(Blwng Dst/Snd => E ovly) /c LC Incident Command Post \c SC#O CD triangle RACES/SATERN/etc /d LD Fire dept \d SD DX spot by callsign /e LE HORSE (equestrian) \e SE Sleet (& future ovrly codes) /f LF FIRE TRUCK (SSID-3) \f SF Funnel Cloud /g LG Glider \g SG Gale Flags /h LH HOSPITAL \h SHO Store. or HAMFST Hh=HAM store /i LI IOTA (islands on the air) \i SI# BOX or points of Interest /j LJ JEEP (SSID-12) \j SJ WorkZone (Steam Shovel) /k LK TRUCK (SSID-14) \k SKO Special Vehicle SUV,ATV,4x4 /l LL Laptop (Jan 03) (Feb 07) \l SL Areas (box,circles,etc) /m LM Mic-E Repeater \m SM Value Sign (3 digit display) /n LN Node (black bulls-eye) \n SN# OVERLAY TRIANGLE /o LO EOC \o SO small circle /p LP ROVER (puppy, or dog) \p SP AVAIL (PrtlyCldy => ( ovly P /q LQ GRID SQ shown above 128 m \q SQ AVAIL /r LR Repeater (Feb 07) \r SR Restrooms /s LS SHIP (pwr boat) (SSID-8) \s SS# OVERLAY SHIP/boats /t LT TRUCK STOP \t ST Tornado /u LU TRUCK (18 wheeler) \u SU# OVERLAYED TRUCK /v LV VAN (SSID-15) \v SV# OVERLAYED Van /w LW WATER station \w SWO Flooding (Avalanches/Slides) /x LX xAPRS (Unix) \x SX Wreck or Obstruction ->X<- /y LY YAGI @ QTH \y SY Skywarn /z LZ TBD \z SZ# OVERLAYED Shelter /{ J1 \{ Q1 AVAIL? (Fog ==> E ovly F) /| J2 TNC Stream Switch \| Q2 TNC Stream Switch /} J3 \} Q3 AVAIL? (maybe) /~ J4 TNC Stream Switch \~ Q4 TNC Stream Switch HEADING SYMBOLS: Although all symbols are supposed to have a heading line showing the direction of movement with a length proportional to the log of speed, some symbols were desiged as top-down views so that they could be displayed actually always POINTING in the direction of movement. Now All symbols should be oriented (if practical). These original special symbols were: \> OVERLAYED CAR \s Overlayed Ship \^ Overlayed Aircraft /^ Aircraft /g Glider \n Overlayed Triangle AREA SYMBOLS! The special symbol \l (lower case L) was special. It indicates an area definition. You can define these as a BOX, CIRCLE, LINE or TRIANGLE area in all colors, either open or filled in, any size from 60 feet to 100 miles. In APRSdos they were generated auto- matically by simply moving the cursor to the location, press HOME, move the cursor to the lower right corner of the AREA and hit INPUT- ADD-OBJECTS-AREA. Enter the type of area, and color. NOTE that AREA shapes can only be defined by selecting the upper left corner first, then the lower right second. The line is an exception. It is still top to bottom, but the lower point can be to the left of the beginning point. Further, in the line option you may specify a "width" either side of the central line. These AREAS are useful for real-time events such as for a search-and- rescue, or adding a special ROAD or ROUTE for a special event. Be cautious in using the color fill option, since all other objects in that area that occur earlier in your PLIST will be obscured. AND you do NOT know the order of other stations P-lists. AREAS FORMAT: Use of the special AREAS symbol (/l) triggers special processing of the next 7 bytes normally used for CSE and SPD. In this special case the processing is as follows: $CSE/SPD... Normal Field description lTyy/Cxx... Where: l (lower case L) is symbol for "LOCATION SHAPES" T is Type of shape: 0=circle, 1=line, 2=elipse 3=triangle 4=box add 5 to these => color-in C is color from 0 to 15. For colors geater than 9, / is replaced with a 1. yy is sqroot of the latitude offset in 1/100ths xx is sqroot of the longitude offset These offsets are ALWAYS positive to the right and down, except for the special case of a lower right quadrant line, these are given the Type of 6 and are drawn down and to the left. HURRICANES, TROPICAL STORMS and DEPRESSIONS: These symbols will be differentiated by colors red, yellos, and blue. Additionally a radius of Huricane and also Tropical storm winds will also be shown if the special format detailed in WX.txt is used. SYMBOLS ON MAPS! APRS can also be permanently embedded in maps. To embed a symbol in a map, simply make the first four characters of the label be a # followed by the dual symbol character, followed by a hex number from 1 to F that indicates the color for the symbol. The remaining 8 characters can be used for a conventional label at the same location. An example are the VORTAC nav-aids in Alaska. The Anchorage VORTAC appears as ANC on all maps below 128 miles. The label entry is #\VFANC,LAT,LONG,128. VALUE SIGNPOSTS: This is another special handling Symbol. Signposts trigger a display as a yellow box with a 1-3 letter overlay on them. The use of this symbol (\m) triggers a search for a 1-3 letter string of characters encolsed in braces in the comment field. Thus a VALUE Signpost with {55} in the comment field would appear as a sign with 55 on it, designed for posting the speed of traffic past speed measuring devices. APRSdos has a version named APRStfc.EXE that monitors traffic speed and posts these speed signs objects when traffic slows in these areas. To avoid cluttering the map, they ONLY appear at 8 miles and below AND they do not show any callsign or name. Only the yellow box and the 3 letters or numbers. Select them from the OBJECT menu under VALUE... APRS 1.2 OVERLAY TYPE SYMBOLS EXPANSION! [April 2007]: ------------------------------------------------------- All alternate symbols have the potential to be overlayed. This was the original intent and was only limited to a few due to limitations in Mac and WinAPRS. Those original "numbered" symbols are marked with a # in the table above. But by 2007, it was time to move on. In APRS 1.2 it was proposed that any ALTENATE symbol can have overlays. Kenwood has already responded with the new D710 that can now display these overlays on all symbols. To help define these hundreds of new symbol combinations, we have added a new file called: http:aprs.org/symbols/symbols-new.txt The overlay symbols may be used in two ways. First, simply as an overlay on a basic symbol type. Most uses of these symbols will be in this manner. But special overlays on some special characters may also be re-drawn as entirely new graphics for clarity if desired. The above NEW-Overlay document attempts to define those special combinations that may rate their own special symbol or where multiple use of an overlay character for different purposes would be confusing. Bob, WB4APR direwolf-1.5+dfsg/telemetry-toolkit/000077500000000000000000000000001347750676600176335ustar00rootroot00000000000000direwolf-1.5+dfsg/telemetry-toolkit/telem-balloon.conf000066400000000000000000000064671347750676600232510ustar00rootroot00000000000000# Sample configuration for demonstration of sending telemetry. # Here we try to replicate actual data heard for a balloon. CHANNEL 0 MYCALL M0XER-3 # These will send the beacons to the transmitter (which you disconnected, right?) # First the metadata. # Channel 1: Battery voltage, Volts, scaled up by 100 # Channel 2: Solar voltage, Volts, scaled up by 100 # Channel 3: Temperature, degrees C, sent as Kelvin x 10 # Channel 4: Number of satellites, no units # Note: When using Strawberry perl, as specified in the example, Windows knows # that the .pl file type is associated with it. When using a different implementation # of perl, which doesn't make this association of file type to application, it might # be necessary to use something like this instead: # # ... infocmd="c:\strawberry\perl\bin\perl.exe telem-parm.pl M0XER-3 Vbat Vsolar Temp Sat" # Here we use the generic scripts to generate the messages with metadata. # The "infocmd=..." option means use the result for the info part of the packet. CBEACON delay=0:10 every=1:00 infocmd="telem-parm.pl M0XER-3 Vbat Vsolar Temp Sat" CBEACON delay=0:12 every=1:00 infocmd="telem-unit.pl M0XER-3 V V C """" m" CBEACON delay=0:14 every=1:00 infocmd="telem-eqns.pl M0XER-3 0 0.001 0 0 0.001 0 0 0.1 -273.2 0 1 0 0 1 0" CBEACON delay=0:16 every=1:00 infocmd="telem-bits.pl M0XER-3 11111111 ""10mW research balloon""" # Now the telemetry data. # In a real situation, the location and telemetry data would come from sensors. # Here we have just hardcoded 3 sets of historical data as a demonstration. # telem-balloon.pl accumulates the data then invokes telem-data91.pl to convert # it to the compressed format. This is inserted into the position comment with "commentcmd=..." PBEACON compress=1 delay=0:20 every=1:00 via=WIDE2-1 symbol=Balloon lat=61^34.2876N lon=155^40.0931W alt=12953 commentcmd="telem-balloon.pl 3307 4.383 0.436 -34.6 12" PBEACON compress=1 delay=0:22 every=1:00 via=WIDE2-1 symbol=Balloon lat=51^07.4402N lon=124^14.4472W alt=12563 commentcmd="telem-balloon.pl 6524 4.515 0.653 -1.3 7" PBEACON compress=1 delay=0:24 every=1:00 via=WIDE2-1 symbol=Balloon lat=55^58.5558N lon=122^28.5933W alt=12680 commentcmd="telem-balloon.pl 7458 4.521 0.587 -8.3 7" # Now we do the same thing again. # This time, add the SENDTO=R0 option to simulate reception. # These will be sent to any attached applications so you can see how they process the data. CBEACON SENDTO=R0 delay=0:30 every=1:00 infocmd="telem-parm.pl M0XER-3 Vbat Vsolar Temp Sat" CBEACON SENDTO=R0 delay=0:32 every=1:00 infocmd="telem-unit.pl M0XER-3 V V C """" m" CBEACON SENDTO=R0 delay=0:34 every=1:00 infocmd="telem-eqns.pl M0XER-3 0 0.001 0 0 0.001 0 0 0.1 -273.2 0 1 0 0 1 0" CBEACON SENDTO=R0 delay=0:36 every=1:00 infocmd="telem-bits.pl M0XER-3 11111111 ""10mW research balloon""" PBEACON SENDTO=R0 compress=1 delay=0:40 every=1:00 via=WIDE2-1 symbol=Balloon lat=61^34.2876N lon=155^40.0931W alt=12953 commentcmd="telem-balloon.pl 3307 4.383 0.436 -34.6 12" PBEACON SENDTO=R0 compress=1 delay=0:42 every=1:00 via=WIDE2-1 symbol=Balloon lat=51^07.4402N lon=124^14.4472W alt=12563 commentcmd="telem-balloon.pl 6524 4.515 0.653 -1.3 7" PBEACON SENDTO=R0 compress=1 delay=0:44 every=1:00 via=WIDE2-1 symbol=Balloon lat=55^58.5558N lon=122^28.5933W alt=12680 commentcmd="telem-balloon.pl 7458 4.521 0.587 -8.3 7" direwolf-1.5+dfsg/telemetry-toolkit/telem-balloon.pl000066400000000000000000000025431347750676600227260ustar00rootroot00000000000000#!/usr/bin/perl # Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 # In a real situation this would obtain data from sensors. # For demonstration purposes we use historical data supplied on the command line. if ($#ARGV+1 != 5) { print STDERR "5 command line arguments must be provided.\n"; usage(); } ($seq,$vbat,$vsolar,$temp,$sat) = @ARGV; # Scale to integer in range of 0 to 8280. # This must be the inverse of the mapping in the EQNS message. $vbat = int(($vbat * 1000) + 0.5); $vsolar = int(($vsolar * 1000) + 0.5); $temp = int((($temp + 273.2) * 10) + 0.5); exit system("telem-data91.pl $seq $vbat $vsolar $temp $sat"); sub usage () { print STDERR "\n"; print STDERR "balloon.pl - Format data into Compressed telemetry format.\n"; print STDERR "\n"; print STDERR "In a real situation this would obtain data from sensors.\n"; print STDERR "For demonstration purposes we use historical data supplied on the command line.\n"; print STDERR "\n"; print STDERR "Usage: balloon.pl seq vbat vsolar temp sat\n"; print STDERR "\n"; print STDERR "Where,\n"; print STDERR " seq is a sequence number.\n"; print STDERR " vbat is battery voltage.\n"; print STDERR " vsolar is solar cell voltage.\n"; print STDERR " temp is temperature, degrees C.\n"; print STDERR " sat is number of GPS satellites visible.\n"; exit 1; }direwolf-1.5+dfsg/telemetry-toolkit/telem-bits.pl000066400000000000000000000016121347750676600222350ustar00rootroot00000000000000#!/usr/bin/perl # Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 if ($#ARGV+1 < 2 || $#ARGV+1 > 3) { print STDERR "A callsign, bit sense string, and optional project title must be provided.\n"; usage(); } # Separate out call and pad to 9 characters. $call = shift @ARGV; $call = substr($call . " ", 0, 9); if ( ! ($ARGV[0] =~ m/^[01]{8}$/)) { print STDERR "The bit-sense value must be 8 binary digits.\n"; usage(); } print ":$call:BITS." . join (',', @ARGV) . "\n"; exit 0; sub usage () { print STDERR "\n"; print STDERR "telem-bits.pl - Generate BITS message with bit polarity and optional project title.\n"; print STDERR "\n"; print STDERR "Usage: telem-bits.pl call bit-sense [ project-title ]\n"; print STDERR "\n"; print STDERR "Bit-sense is string of 8 binary digits.\n"; print STDERR "If project title contains any spaces, enclose it in quotes.\n"; exit 1; }direwolf-1.5+dfsg/telemetry-toolkit/telem-data.pl000066400000000000000000000013661347750676600222130ustar00rootroot00000000000000#!/usr/bin/perl # Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 if ($#ARGV+1 < 2 || $#ARGV+1 > 7) { print STDERR "2 to 7 command line arguments must be provided.\n"; usage(); } if ($#ARGV+1 == 7) { if ( ! ($ARGV[6] =~ m/^[01]{8}$/)) { print STDERR "The sixth value must be 8 binary digits.\n"; usage(); } } print "T#" . join (',', @ARGV) . "\n"; exit 0; sub usage () { print STDERR "\n"; print STDERR "telem-data.pl - Format data into Telemetry Report format.\n"; print STDERR "\n"; print STDERR "Usage: telem-data.pl sequence value1 [ value2 ... ]\n"; print STDERR "\n"; print STDERR "A sequence number and up to 5 analog values can be specified.\n"; print STDERR "Any sixth value must be 8 binary digits.\n"; exit 1; }direwolf-1.5+dfsg/telemetry-toolkit/telem-data91.pl000066400000000000000000000025371347750676600223660ustar00rootroot00000000000000#!/usr/bin/perl # Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 # For explanation of encoding see: # http://he.fi/doc/aprs-base91-comment-telemetry.txt if ($#ARGV+1 < 2 || $#ARGV+1 > 7) { print STDERR "2 to 7 command line arguments must be provided.\n"; usage(); } if ($#ARGV+1 == 7) { if ( ! ($ARGV[6] =~ m/^[01]{8}$/)) { print STDERR "The sixth value must be 8 binary digits.\n"; usage(); } # Convert binary digits to value. $ARGV[6] = oct("0b" . reverse($ARGV[6])); } $result = "|"; for ($n = 0 ; $n <= $#ARGV; $n++) { #print $n . " = " . $ARGV[$n] . "\n"; $v = $ARGV[$n]; if ($v != int($v) || $v < 0 || $v > 8280) { print STDERR "$v is not an integer in range of 0 to 8280.\n"; usage(); } $result .= base91($v); } $result .= "|"; print "$result\n"; exit 0; sub base91 () { my $x = @_[0]; my $d1 = int ($x / 91); my $d2 = $x % 91; return chr($d1+33) . chr($d2+33); } sub usage () { print STDERR "\n"; print STDERR "telem-data91.pl - Format data into compressed base 91 telemetry.\n"; print STDERR "\n"; print STDERR "Usage: telem-data91.pl sequence value1 [ value2 ... ]\n"; print STDERR "\n"; print STDERR "A sequence number and up to 5 analog values can be specified.\n"; print STDERR "Any sixth value must be 8 binary digits.\n"; print STDERR "Values must be integers in range of 0 to 8280.\n"; exit 1; }direwolf-1.5+dfsg/telemetry-toolkit/telem-eqns.pl000066400000000000000000000014571347750676600222510ustar00rootroot00000000000000#!/usr/bin/perl # Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 if ($#ARGV+1 != 4 && $#ARGV+1 != 7 && $#ARGV+1 != 10 && $#ARGV+1 != 13 && $#ARGV+1 != 16) { print STDERR "A callsign and 1 to 5 sets of 3 coefficients must be provided.\n"; usage(); } # Separate out call and pad to 9 characters. $call = shift @ARGV; $call = substr($call . " ", 0, 9); print ":$call:EQNS." . join (',', @ARGV) . "\n"; exit 0; sub usage () { print STDERR "\n"; print STDERR "telem-eqns.pl - Generate EQNS message with scaling coefficients\n"; print STDERR "\n"; print STDERR "Usage: telem-eqns.pl call a1 b1 c1 [ a2 b2 c2 ... ]\n"; print STDERR "\n"; print STDERR "Specify a callsign and 1 to 5 sets of 3 coefficients.\n"; print STDERR "See APRS protocol reference for their meaning.\n"; exit 1; }direwolf-1.5+dfsg/telemetry-toolkit/telem-m0xer-3.txt000066400000000000000000000006201347750676600226710ustar00rootroot000000000000002E0TOY>APRS::M0XER-3 :BITS.11111111,10mW research balloon 2E0TOY>APRS::M0XER-3 :PARM.Vbat,Vsolar,Temp,Sat 2E0TOY>APRS::M0XER-3 :EQNS.0,0.001,0,0,0.001,0,0,0.1,-273.2,0,1,0,0,1,0 2E0TOY>APRS::M0XER-3 :UNIT.V,V,C,,m M0XER-3>APRS63,WIDE2-1:!//Bap'.ZGO JHAE/A=042496|E@Q0%i;5!-| M0XER-3>APRS63,WIDE2-1:!/4\;u/)K$O J]YD/A=041216|h`RY(1>q!(| M0XER-3>APRS63,WIDE2-1:!/23*f/R$UO Jf'x/A=041600|rxR_'J>+!(|direwolf-1.5+dfsg/telemetry-toolkit/telem-parm.pl000066400000000000000000000013051347750676600222320ustar00rootroot00000000000000#!/usr/bin/perl # Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 if ($#ARGV+1 < 2 || $#ARGV+1 > 14) { print STDERR "A callsign and 1 to 13 channel names must be provided.\n"; usage(); } # Separate out call and pad to 9 characters. $call = shift @ARGV; $call = substr($call . " ", 0, 9); print ":$call:PARM." . join (',', @ARGV) . "\n"; exit 0; sub usage () { print STDERR "\n"; print STDERR "telem-parm.pl - Generate PARM message with channel names.\n"; print STDERR "\n"; print STDERR "Usage: telem-parm.pl call aname1 ... aname5 dname1 .,, dname8\n"; print STDERR "\n"; print STDERR "Specify a callsign and up to 13 names for the analog & digital channels.\n"; exit 1; }direwolf-1.5+dfsg/telemetry-toolkit/telem-seq.sh000066400000000000000000000003011347750676600220550ustar00rootroot00000000000000#!/bin/sh # Generate sequence number as described here: # https://github.com/wb2osz/direwolf/issues/9 # SEQ=`cat /tmp/seq 2>/dev/null` SEQ=$(expr \( $SEQ + 1 \) % 1000) echo $SEQ | tee /tmp/seqdirewolf-1.5+dfsg/telemetry-toolkit/telem-unit.pl000066400000000000000000000012741347750676600222570ustar00rootroot00000000000000#!/usr/bin/perl # Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 if ($#ARGV+1 < 2 || $#ARGV+1 > 14) { print STDERR "A callsign and 1 to 13 units/labels must be provided.\n"; usage(); } # Separate out call and pad to 9 characters. $call = shift @ARGV; $call = substr($call . " ", 0, 9); print ":$call:UNIT." . join (',', @ARGV) . "\n"; exit 0; sub usage () { print STDERR "\n"; print STDERR "telem-unit.pl - Generate UNIT message with channel units/labels.\n"; print STDERR "\n"; print STDERR "Usage: telem-unit.pl call unit1 ... unit5 label1 .,, label8\n"; print STDERR "\n"; print STDERR "Specify a callsign and up to 13 names for the units/labels.\n"; exit 1; }direwolf-1.5+dfsg/telemetry-toolkit/telem-volts.conf000066400000000000000000000020701347750676600227540ustar00rootroot00000000000000# Sample configuration for demonstration of sending telemetry. # Here we take a voltage from an analog to digital converter (ADC). ADEVICE plughw:1,0 MYCALL MYCALL-9 # For demonstration purposes, the metadata will be sent each minute and # voltage data every 10 seconds. In actual practice, it would be much less frequent. # First the metadata. # The "infocmd=..." option means use the result for the info part of the packet. CBEACON delay=0:10 every=1:00 via=WIDE2-1 infocmd="telem-parm.pl MYCALL-9 Supply" CBEACON delay=0:11 every=1:00 via=WIDE2-1 infocmd="telem-unit.pl MYCALL-9 Volts" # Now the telemetry data. # First we use telem-volts.py to read a volage from the A/D converter. # This is supplied to telem-data.pl as a command line argument. # The result is used as the info part of a custom beacon. # Sequence numbers are generated as suggested here: # https://github.com/wb2osz/direwolf/issues/9 CBEACON delay=0:15 every=0:10 via=WIDE2-1 infocmd="telem-data.pl `telem-seq.sh` `PYTHONPATH=~/Adafruit-Raspberry-Pi-Python-Code/Adafruit_ADS1x15 telem-volts.py`" direwolf-1.5+dfsg/telemetry-toolkit/telem-volts.py000066400000000000000000000017251347750676600224650ustar00rootroot00000000000000#!/usr/bin/python # Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 # Derived from # https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_ADS1x15/ads1x15_ex_singleended.py import time, signal, sys from Adafruit_ADS1x15 import ADS1x15 ADS1015 = 0x00 # 12-bit ADC ADS1115 = 0x01 # 16-bit ADC # Set ADC full scale to 2048 mV. # Can't use original 4096 with 3.3V supply. gain = 2048 # Select the sample time, 1/sps second. # Longer is better to average out noise. sps = 64 # Set this to ADS1015 or ADS1115 depending on the ADC you are using! adc = ADS1x15(ic=ADS1115) # Values for voltage divider on ADC input. r1 = 1000000. r2 = 100000. # Read channel 0 in single-ended mode using the settings above volts = adc.readADCSingleEnded(0, gain, sps) * 0.001 * (r1+r2) / r2 # Calibration correction specific to my hardware. # (multiply by expected value, divide by uncalibrated result.) #volts = volts * 4.98 / 4.889 print "%.3f" % (volts) direwolf-1.5+dfsg/telemetry.c000066400000000000000000001204321347750676600163160ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2014, 2015 John Langner, WB2OSZ // // 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, see . // //#define DEBUG1 1 /* Parsing of original human readable format. */ //#define DEBUG2 1 /* Parsing of base 91 compressed format. */ //#define DEBUG3 1 /* Parsing of special messages. */ //#define DEBUG4 1 /* Resulting display form. */ #if TEST #define DEBUG1 1 // Activate debug out when testing. #define DEBUG2 1 // #define DEBUG3 1 // #define DEBUG4 1 // #endif /*------------------------------------------------------------------ * * Module: telemetry.c * * Purpose: Decode telemetry information. * Point out where it violates the protocol spec and * other applications might not interpret it properly. * * References: APRS Protocol, chapter 13. * http://www.aprs.org/doc/APRS101.PDF * * Base 91 compressed format * http://he.fi/doc/aprs-base91-comment-telemetry.txt * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include "ax25_pad.h" // for packet_t, AX25_MAX_ADDR_LEN #include "decode_aprs.h" // for decode_aprs_t, G_UNKNOWN #include "textcolor.h" #include "telemetry.h" #define MAX(x,y) ((x)>(y) ? (x) : (y)) #define T_NUM_ANALOG 5 /* Number of analog channels. */ #define T_NUM_DIGITAL 8 /* Number of digital channnels. */ #define T_STR_LEN 16 /* Max len for labels and units. */ #define MAGIC1 0x5a1111a5 /* For checking storage allocation problems. */ #define MAGIC2 0x5a2222a5 #define C_A 0 /* Scaling coefficient positions. */ #define C_B 1 #define C_C 2 /* * Metadata for telemetry data. */ struct t_metadata_s { int magic1; struct t_metadata_s * pnext; /* Next in linked list. */ char station[AX25_MAX_ADDR_LEN]; /* Station name with optional SSID. */ char project[40]; /* Description for data. */ /* "Project Name" or "project title" in the spec. */ char name[T_NUM_ANALOG+T_NUM_DIGITAL][T_STR_LEN]; /* Names for channels. e.g. Battery, Temperature */ char unit[T_NUM_ANALOG+T_NUM_DIGITAL][T_STR_LEN]; /* Units for channels. e.g. Volts, Deg.C */ float coeff[T_NUM_ANALOG][3]; /* a, b, c coefficients for scaling. */ int coeff_ndp[T_NUM_ANALOG][3]; /* Number of decimal places for above. */ int sense[T_NUM_DIGITAL]; /* Polarity for digital channels. */ int magic2; }; static struct t_metadata_s * md_list_head = NULL; static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_ANALOG], int ndp[T_NUM_ANALOG], int draw[T_NUM_DIGITAL], char *output, size_t outputsize); /*------------------------------------------------------------------- * * Name: t_get_metadata * * Purpose: Obtain pointer to metadata for specified station. * If not found, allocate a fresh one and initialize with defaults. * * Inputs: station - Station name with optional SSID. * * Returns: Pointer to metadata. * *--------------------------------------------------------------------*/ static struct t_metadata_s * t_get_metadata (char *station) { struct t_metadata_s *p; int n; #if DEBUG3 text_color_set(DW_COLOR_DEBUG); dw_printf ("t_get_metadata (station=%s)\n", station); #endif for (p = md_list_head; p != NULL; p = p->pnext) { if (strcmp(station, p->station) == 0) { if (p->magic1 != MAGIC1 || p->magic2 != MAGIC2) { text_color_set(DW_COLOR_ERROR); dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__); } return (p); } } p = malloc (sizeof (struct t_metadata_s)); memset (p, 0, sizeof (struct t_metadata_s)); p->magic1 = MAGIC1; strlcpy (p->station, station, sizeof(p->station)); for (n = 0; n < T_NUM_ANALOG; n++) { snprintf (p->name[n], sizeof(p->name[n]), "A%d", n+1); } for (n = 0; n < T_NUM_DIGITAL; n++) { snprintf (p->name[T_NUM_ANALOG+n], sizeof(p->name[T_NUM_ANALOG+n]), "D%d", n+1); } for (n = 0; n < T_NUM_ANALOG; n++) { p->coeff[n][C_A] = 0.; p->coeff[n][C_B] = 1.; p->coeff[n][C_C] = 0.; p->coeff_ndp[n][C_A] = 0; p->coeff_ndp[n][C_B] = 0; p->coeff_ndp[n][C_C] = 0; } for (n = 0; n < T_NUM_DIGITAL; n++) { p->sense[n] = 1; } p->magic2 = MAGIC2; p->pnext = md_list_head; md_list_head = p; assert (p->magic1 == MAGIC1); assert (p->magic2 == MAGIC2); return (p); } /* end t_get_metadata */ /*------------------------------------------------------------------- * * Name: t_ndp * * Purpose: Count number of digits after any decimal point. * * Inputs: str - Number in text format. * * Returns: Number digits after decimal point. Examples, in --> out. * * 1 --> 0 * 1. --> 0 * 1.2 --> 1 * 1.23 --> 2 * etc. * *--------------------------------------------------------------------*/ static int t_ndp (char *str) { char *p; p = strchr(str,'.'); if (p == NULL) { return (0); } else { return (strlen(p+1)); } } /*------------------------------------------------------------------- * * Name: telemetry_data_original * * Purpose: Interpret telemetry data in the original format. * * Inputs: station - Name of station reporting telemetry. * info - Pointer to packet Information field. * quiet - suppress error messages. * * Outputs: output - Decoded telemetry in human readable format. * TODO: How big does it need to be? (buffer overflow?) * comment - Any comment after the data. * * Description: The first character, after the "T" data type indicator, must be "#" * followed by a sequence number. Up to 5 analog and 8 digital channel * values are specified as in this example from the protocol spec. * * T#005,199,000,255,073,123,01101001 * * The analog values are supposed to be 3 digit integers in the * range of 000 to 255 in fixed columns. After reading the discussion * groups it seems that few adhere to those restrictions. When I * started to look for some local signals, this was the first one * to appear: * * KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000 * * Not integers. Not fixed width fields. * * Originally I printed a warning if values were not in range of 000 to 255 * but later took it out because no one pays attention to that original * restriction anymore. * *--------------------------------------------------------------------*/ void telemetry_data_original (char *station, char *info, int quiet, char *output, size_t outputsize, char *comment, size_t commentsize) { int n; int seq; char stemp[256]; char *next; char *p; float araw[T_NUM_ANALOG]; int ndp[T_NUM_ANALOG]; int draw[T_NUM_DIGITAL]; struct t_metadata_s *pm; #if DEBUG1 text_color_set(DW_COLOR_DEBUG); dw_printf ("\n%s\n\n", info); #endif strlcpy (output, "", outputsize); strlcpy (comment, "", commentsize); pm = t_get_metadata(station); if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) { text_color_set(DW_COLOR_ERROR); dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__); } seq = 0; for (n = 0; n < T_NUM_ANALOG; n++) { araw[n] = G_UNKNOWN; ndp[n] = 0; } for (n = 0; n < T_NUM_DIGITAL; n++) { draw[n] = G_UNKNOWN; } if (strncmp(info, "T#", 2) != 0) { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Error: Information part of telemetry packet must begin with \"T#\"\n"); } return; } /* * Make a copy of the input string (excluding T#) because this will alter it. * Remove any trailing CR/LF. */ strlcpy (stemp, info+2, sizeof(stemp)); for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) { *p = '\0'; } next = stemp; p = strsep(&next,","); if (p == NULL) { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Nothing after \"T#\" for telemetry data.\n"); } return; } seq = atoi(p); n = 0; while ((p = strsep(&next,",")) != NULL) { if (n < T_NUM_ANALOG) { if (strlen(p) > 0) { araw[n] = atof(p); ndp[n] = t_ndp(p); } // Version 1.3: Suppress this message. // No one pays attention to the original 000 to 255 range. // BTW, this doesn't trap values like 0.0 or 1.0 //if (strlen(p) != 3 || araw[n] < 0 || araw[n] > 255 || araw[n] != (int)(araw[n])) { // if ( ! quiet) { // text_color_set(DW_COLOR_ERROR); // dw_printf("Telemetry analog values should be 3 digit integer values in range of 000 to 255.\n"); // dw_printf("Some applications might not interpret \"%s\" properly.\n", p); // } //} n++; } if (n == T_NUM_ANALOG && next != NULL) { /* We expect to have 8 digits of 0 and 1. */ /* Anything left over is a comment. */ int k; if (strlen(next) < 8) { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Expected to find 8 binary digits after \"%s\" for the digital values.\n", p); } } // TODO: test this! if (strlen(next) > 8) { strlcpy (comment, next+8, commentsize); next[8] = '\0'; } for (k = 0; k < (int)(strlen(next)); k++) { if (next[k] == '0') { draw[k] = 0; } else if (next[k] == '1') { draw[k] = 1; } else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Found \"%c\" when expecting 0 or 1 for digital value %d.\n", next[k], k+1); } } } n++; } } if (n < T_NUM_ANALOG+1) { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Found fewer than expected number of telemetry data values.\n"); } } /* * Now process the raw data with any metadata available. */ #if DEBUG1 text_color_set(DW_COLOR_DECODED); dw_printf ("%d: %.3f %.3f %.3f %.3f %.3f \n", seq, araw[0], araw[1], araw[2], araw[3], araw[4]); dw_printf ("%d %d %d %d %d %d %d %d \"%s\"\n", draw[0], draw[1], draw[2], draw[3], draw[4], draw[5], draw[6], draw[7], comment); #endif t_data_process (pm, seq, araw, ndp, draw, output, outputsize); } /* end telemtry_data_original */ /*------------------------------------------------------------------- * * Name: telemetry_data_base91 * * Purpose: Interpret telemetry data in the base 91 compressed format. * * Inputs: station - Name of station reporting telemetry. * cdata - Compressed data as character string. * * Outputs: output - Telemetry in human readable form. * * Description: We are expecting from 2 to 7 pairs of base 91 digits. * The first pair is the sequence number. * Next we have 1 to 5 analog values. * If digital values are present, all 5 analog values must be present. * *--------------------------------------------------------------------*/ /* Range of digits for Base 91 representation. */ #define B91_MIN '!' #define B91_MAX '{' #define isdigit91(c) ((c) >= B91_MIN && (c) <= B91_MAX) static int two_base91_to_i (char *c) { int result = 0; assert (B91_MAX - B91_MIN == 90); if (isdigit91(c[0])) { result = (c[0] - B91_MIN) * 91; } else { text_color_set(DW_COLOR_DEBUG); dw_printf ("\"%c\" is not a valid character for base 91 telemetry data.\n", c[0]); return (G_UNKNOWN); } if (isdigit91(c[1])) { result += (c[1] - B91_MIN); } else { text_color_set(DW_COLOR_DEBUG); dw_printf ("\"%c\" is not a valid character for base 91 telemetry data.\n", c[1]); return (G_UNKNOWN); } return (result); } void telemetry_data_base91 (char *station, char *cdata, char *output, size_t outputsize) { int n; int seq; char *p; float araw[T_NUM_ANALOG]; int ndp[T_NUM_ANALOG]; int draw[T_NUM_DIGITAL]; struct t_metadata_s *pm; #if DEBUG2 text_color_set(DW_COLOR_DEBUG); dw_printf ("\n%s\n\n", cdata); #endif strlcpy (output, "", outputsize); pm = t_get_metadata(station); if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) { text_color_set(DW_COLOR_ERROR); dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__); } seq = 0; for (n = 0; n < T_NUM_ANALOG; n++) { araw[n] = G_UNKNOWN; ndp[n] = 0; } for (n = 0; n < T_NUM_DIGITAL; n++) { draw[n] = G_UNKNOWN; } if (strlen(cdata) < 4 || strlen(cdata) > 14 || (strlen(cdata) & 1)) { text_color_set(DW_COLOR_ERROR); dw_printf("Internal error: Expected even number of 2 to 14 characters but got \"%s\"\n", cdata); return; } seq = two_base91_to_i (cdata); for (n=0, p=cdata+2; n>= 1; } } } /* * Now process the raw data with any metadata available. */ #if DEBUG2 text_color_set(DW_COLOR_DECODED); dw_printf ("%d: %.3f %.3f %.3f %.3f %.3f \n", seq, araw[0], araw[1], araw[2], araw[3], araw[4]); dw_printf ("%d %d %d %d %d %d %d %d \n", draw[0], draw[1], draw[2], draw[3], draw[4], draw[5], draw[6], draw[7]); #endif t_data_process (pm, seq, araw, ndp, draw, output, outputsize); } /* end telemtry_data_base91 */ /*------------------------------------------------------------------- * * Name: telemetry_name_message * * Purpose: Interpret message with names for analog and digital channels. * * Inputs: station - Name of station reporting telemetry. * In this case it is the destination for the message, * not the sender. * msg - Rest of message after "PARM." * * Outputs: Stored for future use when data values are received. * * Description: The first 5 characters of the message are "PARM." and the * rest is a variable length list of comma separated names. * * The original spec has different maximum lengths for different * fields which we will ignore. * * TBD: What should we do if some, but not all, names are specified? * Clear the others or keep the defaults? * *--------------------------------------------------------------------*/ void telemetry_name_message (char *station, char *msg) { int n; char stemp[256]; char *next; char *p; struct t_metadata_s *pm; #if DEBUG3 text_color_set(DW_COLOR_DEBUG); dw_printf ("\n%s\n\n", msg); #endif /* * Make a copy of the input string because this will alter it. * Remove any trailing CR LF. */ strlcpy (stemp, msg, sizeof(stemp)); for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) { *p = '\0'; } pm = t_get_metadata(station); if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) { text_color_set(DW_COLOR_ERROR); dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__); } next = stemp; n = 0; while ((p = strsep(&next,",")) != NULL) { if (n < T_NUM_ANALOG + T_NUM_DIGITAL) { if (strlen(p) > 0 && strcmp(p,"-") != 0) { strlcpy (pm->name[n], p, sizeof(pm->name[n])); } n++; } } #if DEBUG3 text_color_set(DW_COLOR_DEBUG); dw_printf ("names:\n"); for (n = 0; n < T_NUM_ANALOG + T_NUM_DIGITAL; n++) { dw_printf ("%d=\"%s\"\n", n, pm->name[n]); } #endif } /* end telemetry_name_message */ /*------------------------------------------------------------------- * * Name: telemetry_unit_label_message * * Purpose: Interpret message with units/labels for analog and digital channels. * * Inputs: station - Name of station reporting telemetry. * In this case it is the destination for the message, * not the sender. * msg - Rest of message after "UNIT." * * Outputs: Stored for future use when data values are received. * * Description: The first 5 characters of the message are "UNIT." and the * rest is a variable length list of comma separated units/labels. * * The original spec has different maximum lengths for different * fields which we will ignore. * *--------------------------------------------------------------------*/ void telemetry_unit_label_message (char *station, char *msg) { int n; char stemp[256]; char *next; char *p; struct t_metadata_s *pm; #if DEBUG3 text_color_set(DW_COLOR_DEBUG); dw_printf ("\n%s\n\n", msg); #endif /* * Make a copy of the input string because this will alter it. * Remove any trailing CR LF. */ strlcpy (stemp, msg, sizeof(stemp)); for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) { *p = '\0'; } pm = t_get_metadata(station); if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) { text_color_set(DW_COLOR_ERROR); dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__); } next = stemp; n = 0; while ((p = strsep(&next,",")) != NULL) { if (n < T_NUM_ANALOG + T_NUM_DIGITAL) { if (strlen(p) > 0) { strlcpy (pm->unit[n], p, sizeof(pm->unit[n])); } n++; } } #if DEBUG3 text_color_set(DW_COLOR_DEBUG); dw_printf ("units/labels:\n"); for (n = 0; n < T_NUM_ANALOG + T_NUM_DIGITAL; n++) { dw_printf ("%d=\"%s\"\n", n, pm->unit[n]); } #endif } /* end telemetry_unit_label_message */ /*------------------------------------------------------------------- * * Name: telemetry_coefficents_message * * Purpose: Interpret message with scaling coefficents for analog channels. * * Inputs: station - Name of station reporting telemetry. * In this case it is the destination for the message, * not the sender. * msg - Rest of message after "EQNS." * quiet - suppress error messages. * * Outputs: Stored for future use when data values are received. * * Description: The first 5 characters of the message are "EQNS." and the * rest is a comma separated list of 15 floating point values. * * The spec appears to require all 15 so we will issue an * error if fewer found. * *--------------------------------------------------------------------*/ void telemetry_coefficents_message (char *station, char *msg, int quiet) { int n; char stemp[256]; char *next; char *p; struct t_metadata_s *pm; #if DEBUG3 text_color_set(DW_COLOR_DEBUG); dw_printf ("\n%s\n\n", msg); #endif /* * Make a copy of the input string because this will alter it. * Remove any trailing CR LF. */ strlcpy (stemp, msg, sizeof(stemp)); for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) { *p = '\0'; } pm = t_get_metadata(station); if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) { text_color_set(DW_COLOR_ERROR); dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__); } next = stemp; n = 0; while ((p = strsep(&next,",")) != NULL) { if (n < T_NUM_ANALOG * 3) { // Keep default (or earlier value) for an empty field. if (strlen(p) > 0) { pm->coeff[n/3][n%3] = atof (p); pm->coeff_ndp[n/3][n%3] = t_ndp (p); } else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf ("Equation coefficent position A%d%c is empty.\n", n/3+1, n%3+'a'); dw_printf ("Some applications might not handle this correctly.\n"); } } } n++; } if (n != T_NUM_ANALOG * 3) { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf ("Found %d equation coefficents when 15 were expected.\n", n); dw_printf ("Some applications might not handle this correctly.\n"); } } #if DEBUG3 text_color_set(DW_COLOR_DEBUG); dw_printf ("coeff:\n"); for (n = 0; n < T_NUM_ANALOG; n++) { dw_printf ("A%d a=%.*f b=%.*f c=%.*f\n", n+1, pm->coeff_ndp[n][C_A], pm->coeff[n][C_A], pm->coeff_ndp[n][C_B], pm->coeff[n][C_B], pm->coeff_ndp[n][C_C], pm->coeff[n][C_C]); } #endif } /* end telemetry_coefficents_message */ /*------------------------------------------------------------------- * * Name: telemetry_bit_sense_message * * Purpose: Interpret message with scaling coefficents for analog channels. * * Inputs: station - Name of station reporting telemetry. * In this case it is the destination for the message, * not the sender. * msg - Rest of message after "BITS." * quiet - suppress error messages. * * Outputs: Stored for future use when data values are received. * * Description: The first 5 characters of the message are "BITS." * It should contain eight binary digits for the digital active states. * Anything left over is the project name or title. * *--------------------------------------------------------------------*/ void telemetry_bit_sense_message (char *station, char *msg, int quiet) { int n; struct t_metadata_s *pm; #if DEBUG3 text_color_set(DW_COLOR_DEBUG); dw_printf ("\n%s\n\n", msg); #endif pm = t_get_metadata(station); if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) { text_color_set(DW_COLOR_ERROR); dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__); } if (strlen(msg) < 8) { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf ("The telemetry bit sense message should have at least 8 characters.\n"); } } for (n = 0; n < T_NUM_DIGITAL && n < (int)(strlen(msg)); n++) { if (msg[n] == '1') { pm->sense[n] = 1; } else if (msg[n] == '0') { pm->sense[n] = 0; } else { if ( ! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf ("Bit position %d sense value was \"%c\" when 0 or 1 was expected.\n", n+1, msg[n]); } } } /* * Skip comma if first character of comment field. * * The protocol spec is inconsistent here. * The definition shows the Project Title immediately after a fixed width field of 8 binary digits. * The example has a comma in there. * * The toolkit telem-bits.pl script does insert the comma because it seems more sensible. * Here we accept it either way. i.e. Discard first character after data values if it is comma. */ if (msg[n] == ',') n++; strlcpy (pm->project, msg+n, sizeof(pm->project)); #if DEBUG3 text_color_set(DW_COLOR_DEBUG); dw_printf ("bit sense, project:\n"); dw_printf ("%d %d %d %d %d %d %d %d \"%s\"\n", pm->sense[0], pm->sense[1], pm->sense[2], pm->sense[3], pm->sense[4], pm->sense[5], pm->sense[6], pm->sense[7], pm->project); #endif } /* end telemetry_bit_sense_message */ /*------------------------------------------------------------------- * * Name: t_data_process * * Purpose: Interpret telemetry data in the original format. * * Inputs: pm - Pointer to metadata. * seq - Sequence number. * araw - 5 analog raw values. * ndp - Number of decimal points for each. * draw - 8 digial raw vales. * * Outputs: output - Decoded telemetry in human readable format. * * Description: Process raw data according to any metadata available * and put into human readable form. * *--------------------------------------------------------------------*/ #define VAL_STR_SIZE 64 static void fval_to_str (float x, int ndp, char str[VAL_STR_SIZE]) { if (x == G_UNKNOWN) { strlcpy (str, "?", VAL_STR_SIZE); } else { snprintf (str, VAL_STR_SIZE, "%.*f", ndp, x); } } static void ival_to_str (int x, char str[VAL_STR_SIZE]) { if (x == G_UNKNOWN) { strlcpy (str, "?", VAL_STR_SIZE); } else { snprintf (str, VAL_STR_SIZE, "%d", x); } } static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_ANALOG], int ndp[T_NUM_ANALOG], int draw[T_NUM_DIGITAL], char *output, size_t outputsize) { int n; char val_str[VAL_STR_SIZE]; assert (pm != NULL); if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) { text_color_set(DW_COLOR_ERROR); dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__); } strlcpy (output, "", outputsize); if (strlen(pm->project) > 0) { strlcpy (output, pm->project, outputsize); strlcat (output, ": ", outputsize); } ival_to_str (seq, val_str); strlcat (output, "Seq=", outputsize); strlcat (output, val_str, outputsize); for (n = 0; n < T_NUM_ANALOG; n++) { // Display all or only defined values? Only defined for now. if (araw[n] != G_UNKNOWN) { float fval; int fndp; strlcat (output, ", ", outputsize); strlcat (output, pm->name[n], outputsize); strlcat (output, "=", outputsize); // Scaling and suitable number of decimal places for display. if (araw[n] == G_UNKNOWN) { fval = G_UNKNOWN; fndp = 0; } else { int z; fval = pm->coeff[n][C_A] * araw[n] * araw[n] + pm->coeff[n][C_B] * araw[n] + pm->coeff[n][C_C]; z = pm->coeff_ndp[n][C_A] == 0 ? 0 : pm->coeff_ndp[n][C_A] + ndp[n] + ndp[n]; fndp = MAX (z, MAX(pm->coeff_ndp[n][C_B] + ndp[n], pm->coeff_ndp[n][C_C])); } fval_to_str (fval, fndp, val_str); strlcat (output, val_str, outputsize); if (strlen(pm->unit[n]) > 0) { strlcat (output, " ", outputsize); strlcat (output, pm->unit[n], outputsize); } } } for (n = 0; n < T_NUM_DIGITAL; n++) { // Display all or only defined values? Only defined for now. if (draw[n] != G_UNKNOWN) { int dval; strlcat (output, ", ", outputsize); strlcat (output, pm->name[T_NUM_ANALOG+n], outputsize); strlcat (output, "=", outputsize); // Possible inverting for bit sense. if (draw[n] == G_UNKNOWN) { dval = G_UNKNOWN; } else { dval = draw[n] ^ ! pm->sense[n]; } ival_to_str (dval, val_str); if (strlen(pm->unit[T_NUM_ANALOG+n]) > 0) { strlcat (output, " ", outputsize); strlcat (output, pm->unit[T_NUM_ANALOG+n], outputsize); } strlcat (output, val_str, outputsize); } } #if DEBUG4 text_color_set(DW_COLOR_DEBUG); dw_printf ("%s\n", output); #endif } /* end t_data_process */ /*------------------------------------------------------------------- * * Unit test. Run with: * * make etest * * *--------------------------------------------------------------------*/ #if TEST int main ( ) { char result[120]; char comment[40]; int errors = 0; strlcpy (result, "", sizeof(result)); strlcpy (comment, "", sizeof(comment)); text_color_set(DW_COLOR_INFO); dw_printf ("Unit test for telemetry decoding functions...\n"); #if DEBUG1 text_color_set(DW_COLOR_INFO); dw_printf ("part 1\n"); // From protocol spec. telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101001", 0, result, sizeof(result), comment, sizeof(comment)); if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A4=73, A5=123, D1=0, D2=1, D3=1, D4=0, D5=1, D6=0, D7=0, D8=1") != 0 || strcmp(comment, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 101\n"); } // Try adding a comment. telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101001Comment,with,commas", 0, result, sizeof(result), comment, sizeof(comment)); if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A4=73, A5=123, D1=0, D2=1, D3=1, D4=0, D5=1, D6=0, D7=0, D8=1") != 0 || strcmp(comment, "Comment,with,commas") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 102\n"); } // Error handling - Try shortening or omitting parts. telemetry_data_original ("WB2OSZ", "T005,199,000,255,073,123,0110", 0, result, sizeof(result), comment, sizeof(comment)); if (strcmp(result, "") != 0 || strcmp(comment, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 103\n"); } telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,0110", 0, result, sizeof(result), comment, sizeof(comment)); if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A4=73, A5=123, D1=0, D2=1, D3=1, D4=0") != 0 || strcmp(comment, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 104\n"); } telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123", 0, result, sizeof(result), comment, sizeof(comment)); if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A4=73, A5=123") != 0 || strcmp(comment, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 105\n"); } telemetry_data_original ("WB2OSZ", "T#005,199,000,255,,123,01101001", 0, result, sizeof(result), comment, sizeof(comment)); if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A5=123, D1=0, D2=1, D3=1, D4=0, D5=1, D6=0, D7=0, D8=1") != 0 || strcmp(comment, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 106\n"); } telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101009", 0, result, sizeof(result), comment, sizeof(comment)); if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A4=73, A5=123, D1=0, D2=1, D3=1, D4=0, D5=1, D6=0, D7=0") != 0 || strcmp(comment, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 107\n"); } // Local observation. telemetry_data_original ("WB2OSZ", "T#491,4.9,0.3,25.0,0.0,1.0,00000000", 0, result, sizeof(result), comment, sizeof(comment)); if (strcmp(result, "Seq=491, A1=4.9, A2=0.3, A3=25.0, A4=0.0, A5=1.0, D1=0, D2=0, D3=0, D4=0, D5=0, D6=0, D7=0, D8=0") != 0 || strcmp(comment, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 108\n"); } #endif #if DEBUG2 text_color_set(DW_COLOR_INFO); dw_printf ("part 2\n"); // From protocol spec. telemetry_data_base91 ("WB2OSZ", "ss11", result, sizeof(result)); if (strcmp(result, "Seq=7544, A1=1472") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 201\n"); } telemetry_data_base91 ("WB2OSZ", "ss11223344{{!\"", result, sizeof(result)); if (strcmp(result, "Seq=7544, A1=1472, A2=1564, A3=1656, A4=1748, A5=8280, D1=1, D2=0, D3=0, D4=0, D5=0, D6=0, D7=0, D8=0") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 202\n"); } // Error cases. Should not happen in practice because function // should be called only with valid data that matches the pattern. telemetry_data_base91 ("WB2OSZ", "ss11223344{{!\"x", result, sizeof(result)); if (strcmp(result, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 203\n"); } telemetry_data_base91 ("WB2OSZ", "ss1", result, sizeof(result)); if (strcmp(result, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 204\n"); } telemetry_data_base91 ("WB2OSZ", "ss11223344{{!", result, sizeof(result)); if (strcmp(result, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 205\n"); } telemetry_data_base91 ("WB2OSZ", "s |1", result, sizeof(result)); if (strcmp(result, "Seq=?") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 206\n"); } #endif #if DEBUG3 text_color_set(DW_COLOR_INFO); dw_printf ("part 3\n"); telemetry_name_message ("N0QBF-11", "Battery,Btemp,ATemp,Pres,Alt,Camra,Chut,Sun,10m,ATV"); struct t_metadata_s *pm; pm = t_get_metadata("N0QBF-11"); if (strcmp(pm->name[0], "Battery") != 0 || strcmp(pm->name[1], "Btemp") != 0 || strcmp(pm->name[2], "ATemp") != 0 || strcmp(pm->name[3], "Pres") != 0 || strcmp(pm->name[4], "Alt") != 0 || strcmp(pm->name[5], "Camra") != 0 || strcmp(pm->name[6], "Chut") != 0 || strcmp(pm->name[7], "Sun") != 0 || strcmp(pm->name[8], "10m") != 0 || strcmp(pm->name[9], "ATV") != 0 || strcmp(pm->name[10], "D6") != 0 || strcmp(pm->name[11], "D7") != 0 || strcmp(pm->name[12], "D8") != 0 ) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 301\n"); } telemetry_unit_label_message ("N0QBF-11", "v/100,deg.F,deg.F,Mbar,Kft,Click,OPEN,on,on,hi"); pm = t_get_metadata("N0QBF-11"); if (strcmp(pm->unit[0], "v/100") != 0 || strcmp(pm->unit[1], "deg.F") != 0 || strcmp(pm->unit[2], "deg.F") != 0 || strcmp(pm->unit[3], "Mbar") != 0 || strcmp(pm->unit[4], "Kft") != 0 || strcmp(pm->unit[5], "Click") != 0 || strcmp(pm->unit[6], "OPEN") != 0 || strcmp(pm->unit[7], "on") != 0 || strcmp(pm->unit[8], "on") != 0 || strcmp(pm->unit[9], "hi") != 0 || strcmp(pm->unit[10], "") != 0 || strcmp(pm->unit[11], "") != 0 || strcmp(pm->unit[12], "") != 0 ) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 302\n"); } telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,2,3", 0); pm = t_get_metadata("N0QBF-11"); if (pm->coeff[0][0] != 0 || pm->coeff[0][1] < 5.1999 || pm->coeff[0][1] > 5.2001 || pm->coeff[0][2] != 0 || pm->coeff[1][0] != 0 || pm->coeff[1][1] < .52999 || pm->coeff[1][1] > .53001 || pm->coeff[1][2] != -32 || pm->coeff[2][0] != 3 || pm->coeff[2][1] < 4.3899 || pm->coeff[2][1] > 4.3901 || pm->coeff[2][2] != 49 || pm->coeff[3][0] != -32 || pm->coeff[3][1] != 3 || pm->coeff[3][2] != 18 || pm->coeff[4][0] != 1 || pm->coeff[4][1] != 2 || pm->coeff[4][2] != 3) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 303c\n"); } if (pm->coeff_ndp[0][0] != 0 || pm->coeff_ndp[0][1] != 1 || pm->coeff_ndp[0][2] != 0 || pm->coeff_ndp[1][0] != 0 || pm->coeff_ndp[1][1] != 2 || pm->coeff_ndp[1][2] != 0 || pm->coeff_ndp[2][0] != 0 || pm->coeff_ndp[2][1] != 2 || pm->coeff_ndp[2][2] != 0 || pm->coeff_ndp[3][0] != 0 || pm->coeff_ndp[3][1] != 0 || pm->coeff_ndp[3][2] != 0 || pm->coeff_ndp[4][0] != 0 || pm->coeff_ndp[4][1] != 0 || pm->coeff_ndp[4][2] != 0 ) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 303n\n"); } // Error if less than 15 or empty field. // Notice that we keep the previous value in this case. telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,2", 0); pm = t_get_metadata("N0QBF-11"); if (pm->coeff[0][0] != 0 || pm->coeff[0][1] < 5.1999 || pm->coeff[0][1] > 5.2001 || pm->coeff[0][2] != 0 || pm->coeff[1][0] != 0 || pm->coeff[1][1] < .52999 || pm->coeff[1][1] > .53001 || pm->coeff[1][2] != -32 || pm->coeff[2][0] != 3 || pm->coeff[2][1] < 4.3899 || pm->coeff[2][1] > 4.3901 || pm->coeff[2][2] != 49 || pm->coeff[3][0] != -32 || pm->coeff[3][1] != 3 || pm->coeff[3][2] != 18 || pm->coeff[4][0] != 1 || pm->coeff[4][1] != 2 || pm->coeff[4][2] != 3) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 304c\n"); } if (pm->coeff_ndp[0][0] != 0 || pm->coeff_ndp[0][1] != 1 || pm->coeff_ndp[0][2] != 0 || pm->coeff_ndp[1][0] != 0 || pm->coeff_ndp[1][1] != 2 || pm->coeff_ndp[1][2] != 0 || pm->coeff_ndp[2][0] != 0 || pm->coeff_ndp[2][1] != 2 || pm->coeff_ndp[2][2] != 0 || pm->coeff_ndp[3][0] != 0 || pm->coeff_ndp[3][1] != 0 || pm->coeff_ndp[3][2] != 0 || pm->coeff_ndp[4][0] != 0 || pm->coeff_ndp[4][1] != 0 || pm->coeff_ndp[4][2] != 0 ) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 304n\n"); } telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,,3", 0); pm = t_get_metadata("N0QBF-11"); if (pm->coeff[0][0] != 0 || pm->coeff[0][1] < 5.1999 || pm->coeff[0][1] > 5.2001 || pm->coeff[0][2] != 0 || pm->coeff[1][0] != 0 || pm->coeff[1][1] < .52999 || pm->coeff[1][1] > .53001 || pm->coeff[1][2] != -32 || pm->coeff[2][0] != 3 || pm->coeff[2][1] < 4.3899 || pm->coeff[2][1] > 4.3901 || pm->coeff[2][2] != 49 || pm->coeff[3][0] != -32 || pm->coeff[3][1] != 3 || pm->coeff[3][2] != 18 || pm->coeff[4][0] != 1 || pm->coeff[4][1] != 2 || pm->coeff[4][2] != 3) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 305c\n"); } if (pm->coeff_ndp[0][0] != 0 || pm->coeff_ndp[0][1] != 1 || pm->coeff_ndp[0][2] != 0 || pm->coeff_ndp[1][0] != 0 || pm->coeff_ndp[1][1] != 2 || pm->coeff_ndp[1][2] != 0 || pm->coeff_ndp[2][0] != 0 || pm->coeff_ndp[2][1] != 2 || pm->coeff_ndp[2][2] != 0 || pm->coeff_ndp[3][0] != 0 || pm->coeff_ndp[3][1] != 0 || pm->coeff_ndp[3][2] != 0 || pm->coeff_ndp[4][0] != 0 || pm->coeff_ndp[4][1] != 0 || pm->coeff_ndp[4][2] != 0 ) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 305n\n"); } telemetry_bit_sense_message ("N0QBF-11", "10110000,N0QBF's Big Balloon", 0); pm = t_get_metadata("N0QBF-11"); if (pm->sense[0] != 1 || pm->sense[1] != 0 || pm->sense[2] != 1 || pm->sense[3] != 1 || pm->sense[4] != 0 || pm->sense[5] != 0 || pm->sense[6] != 0 || pm->sense[7] != 0 || strcmp(pm->project, "N0QBF's Big Balloon") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 306\n"); } // Too few and invalid digits. telemetry_bit_sense_message ("N0QBF-11", "1011000", 0); pm = t_get_metadata("N0QBF-11"); if (pm->sense[0] != 1 || pm->sense[1] != 0 || pm->sense[2] != 1 || pm->sense[3] != 1 || pm->sense[4] != 0 || pm->sense[5] != 0 || pm->sense[6] != 0 || pm->sense[7] != 0 || strcmp(pm->project, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 307\n"); } telemetry_bit_sense_message ("N0QBF-11", "10110008", 0); pm = t_get_metadata("N0QBF-11"); if (pm->sense[0] != 1 || pm->sense[1] != 0 || pm->sense[2] != 1 || pm->sense[3] != 1 || pm->sense[4] != 0 || pm->sense[5] != 0 || pm->sense[6] != 0 || pm->sense[7] != 0 || strcmp(pm->project, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 308\n"); } #endif text_color_set(DW_COLOR_INFO); dw_printf ("part 4\n"); telemetry_coefficents_message ("M0XER-3", "0,0.001,0,0,0.001,0,0,0.1,-273.2,0,1,0,0,1,0", 0); telemetry_bit_sense_message ("M0XER-3", "11111111,10mW research balloon", 0); telemetry_name_message ("M0XER-3", "Vbat,Vsolar,Temp,Sat"); telemetry_unit_label_message ("M0XER-3", "V,V,C,,m"); telemetry_data_base91 ("M0XER-3", "DyR.&^b!+", result, sizeof(result)); if (strcmp(result, "10mW research balloon: Seq=7022, Vbat=4.509 V, Vsolar=0.662 V, Temp=-2.8 C, Sat=10") != 0 || strcmp(comment, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 403\n"); } telemetry_data_base91 ("M0XER-3", "x&G=!(8s!,", result, sizeof(result)); if (strcmp(result, "10mW research balloon: Seq=7922, Vbat=3.486 V, Vsolar=0.007 V, Temp=-55.7 C, Sat=11") != 0 || strcmp(comment, "") != 0) { errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 404\n"); } /* final score. */ if (errors != 0) { text_color_set (DW_COLOR_ERROR); dw_printf ("\nTEST FAILED with %d errors.\n", errors); exit (EXIT_FAILURE); } text_color_set (DW_COLOR_REC); dw_printf ("\nTEST WAS SUCCESSFUL.\n"); exit (EXIT_SUCCESS); } /* A more complete test can be performed by placing the following in a text file and feeding it into the "decode_aprs" utility. 2E0TOY>APRS::M0XER-3 :BITS.11111111,10mW research balloon 2E0TOY>APRS::M0XER-3 :PARM.Vbat,Vsolar,Temp,Sat 2E0TOY>APRS::M0XER-3 :EQNS.0,0.001,0,0,0.001,0,0,0.1,-273.2,0,1,0,0,1,0 2E0TOY>APRS::M0XER-3 :UNIT.V,V,C,,m M0XER-3>APRS63,WIDE2-1:!//Bap'.ZGO JHAE/A=042496|E@Q0%i;5!-| M0XER-3>APRS63,WIDE2-1:!/4\;u/)K$O J]YD/A=041216|h`RY(1>q!(| M0XER-3>APRS63,WIDE2-1:!/23*f/R$UO Jf'x/A=041600|rxR_'J>+!(| The interpretation should look something like this: 10mW research balloon: Seq=3307, Vbat=4.383 V, Vsolar=0.436 V, Temp=-34.6 C, Sat=12 */ #endif /* end telemetry.c */ direwolf-1.5+dfsg/telemetry.h000066400000000000000000000010101347750676600163110ustar00rootroot00000000000000 /* telemetry.h */ void telemetry_data_original (char *station, char *info, int quiet, char *output, size_t outputsize, char *comment, size_t commentsize); void telemetry_data_base91 (char *station, char *cdata, char *output, size_t outputsize); void telemetry_name_message (char *station, char *msg); void telemetry_unit_label_message (char *station, char *msg); void telemetry_coefficents_message (char *station, char *msg, int quiet); void telemetry_bit_sense_message (char *station, char *msg, int quiet); direwolf-1.5+dfsg/textcolor.c000066400000000000000000000241251347750676600163310ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------- * * Name: textcolor.c * * Purpose: Originally this would only set color of text * and we used printf everywhere. * Now we also have a printf replacement that can * be used to redirect all output to the desired place. * This opens the door to using ncurses, a GUI, or * running as a daemon. * * Description: For Linux and Cygwin use the ANSI escape sequences. * In earlier versions of Windows, the cmd window and ANSI.SYS * could interpret this but it doesn't seem to be available * anymore so we use a different interface. * * References: * http://en.wikipedia.org/wiki/ANSI_escape_code * http://academic.evergreen.edu/projects/biophysics/technotes/program/ansi_esc.htm * * >>>> READ THIS PART!!! <<<< * * * Problem: The ANSI escape sequences, used on Linux, allow 8 basic colors. * Unfortunately, white is not one of them. We only have dark * white, also known as light gray. To get brighter colors, * we need to apply an attribute. On some systems, the bold * attribute produces a brighter color rather than a bold font. * On other systems, we need to use the blink attribute to get * bright colors, including white. However on others, blink * does actually produce blinking characters. * * Several people have also complained that bright green is * very hard to read against a light background. The current * implementation does not allow easy user customization of colors. * * Currently, the only option is to put "-t 0" on the command * line to disable all text color. This is more readable but * makes it harder to distinguish different types of * information, e.g. received packets vs. error messages. * * A few people have suggested ncurses. This needs to * be investigated for a future version. The foundation has * already been put in place. All of the printf's should have been * replaced by dw_printf, defined in this file. All of the * text output is now being funneled thru this one function * so it should be easy to send it to the user by some * other means. * *--------------------------------------------------------------------*/ #include "direwolf.h" // Should be first. includes windows.h #include #include #include #if __WIN32__ #define BACKGROUND_WHITE (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY) #elif __CYGWIN__ /* Cygwin */ /* For Cygwin we need "blink" (5) rather than the */ /* expected bright/bold (1) to get bright white background. */ /* Makes no sense but I stumbled across that somewhere. */ static const char background_white[] = "\e[5;47m"; /* Whenever a dark color is used, the */ /* background is reset and needs to be set again. */ static const char black[] = "\e[0;30m" "\e[5;47m"; static const char red[] = "\e[1;31m"; static const char green[] = "\e[1;32m"; static const char yellow[] = "\e[1;33m"; static const char blue[] = "\e[1;34m"; static const char magenta[] = "\e[1;35m"; static const char cyan[] = "\e[1;36m"; static const char dark_green[] = "\e[0;32m" "\e[5;47m"; /* Clear from cursor to end of screen. */ static const char clear_eos[] = "\e[0J"; #elif __arm__ /* Linux on Raspberry Pi or similar */ /* We need "blink" (5) rather than the */ /* expected bright/bold (1) to get bright white background. */ /* Makes no sense but I stumbled across that somewhere. */ /* If you do get blinking, remove all references to "\e[5;47m" */ static const char background_white[] = "\e[5;47m"; /* Whenever a dark color is used, the */ /* background is reset and needs to be set again. */ static const char black[] = "\e[0;30m" "\e[5;47m"; static const char red[] = "\e[1;31m" "\e[5;47m"; static const char green[] = "\e[1;32m" "\e[5;47m"; //static const char yellow[] = "\e[1;33m" "\e[5;47m"; static const char blue[] = "\e[1;34m" "\e[5;47m"; static const char magenta[] = "\e[1;35m" "\e[5;47m"; //static const char cyan[] = "\e[1;36m" "\e[5;47m"; static const char dark_green[] = "\e[0;32m" "\e[5;47m"; /* Clear from cursor to end of screen. */ static const char clear_eos[] = "\e[0J"; #else /* Other Linux */ #if 1 /* new in version 1.2, as suggested by IW2DHW */ /* Test done using gnome-terminal and xterm */ static const char background_white[] = "\e[48;2;255;255;255m"; /* Whenever a dark color is used, the */ /* background is reset and needs to be set again. */ static const char black[] = "\e[0;30m" "\e[48;2;255;255;255m"; static const char red[] = "\e[0;31m" "\e[48;2;255;255;255m"; static const char green[] = "\e[0;32m" "\e[48;2;255;255;255m"; //static const char yellow[] = "\e[0;33m" "\e[48;2;255;255;255m"; static const char blue[] = "\e[0;34m" "\e[48;2;255;255;255m"; static const char magenta[] = "\e[0;35m" "\e[48;2;255;255;255m"; //static const char cyan[] = "\e[0;36m" "\e[48;2;255;255;255m"; static const char dark_green[] = "\e[0;32m" "\e[48;2;255;255;255m"; #else /* from version 1.1 */ static const char background_white[] = "\e[47;1m"; /* Whenever a dark color is used, the */ /* background is reset and needs to be set again. */ static const char black[] = "\e[0;30m" "\e[1;47m"; static const char red[] = "\e[1;31m" "\e[1;47m"; static const char green[] = "\e[1;32m" "\e[1;47m"; //static const char yellow[] = "\e[1;33m" "\e[1;47m"; static const char blue[] = "\e[1;34m" "\e[1;47m"; static const char magenta[] = "\e[1;35m" "\e[1;47m"; //static const char cyan[] = "\e[1;36m" "\e[1;47m"; static const char dark_green[] = "\e[0;32m" "\e[1;47m"; #endif /* Clear from cursor to end of screen. */ static const char clear_eos[] = "\e[0J"; #endif /* end Linux */ #include "textcolor.h" /* * g_enable_color: * 0 = disable text colors. * 1 = normal. * others... future possibility. */ static int g_enable_color = 1; void text_color_init (int enable_color) { g_enable_color = enable_color; #if __WIN32__ if (g_enable_color) { HANDLE h; CONSOLE_SCREEN_BUFFER_INFO csbi; WORD attr = BACKGROUND_WHITE; DWORD length; COORD coord; DWORD nwritten; h = GetStdHandle(STD_OUTPUT_HANDLE); if (h != NULL && h != INVALID_HANDLE_VALUE) { GetConsoleScreenBufferInfo (h, &csbi); length = csbi.dwSize.X * csbi.dwSize.Y; coord.X = 0; coord.Y = 0; FillConsoleOutputAttribute (h, attr, length, coord, &nwritten); } } #else if (g_enable_color) { //printf ("%s", clear_eos); printf ("%s", background_white); printf ("%s", clear_eos); printf ("%s", black); } #endif } #if __WIN32__ /* Seems that ANSI.SYS is no longer available. */ void text_color_set ( enum dw_color_e c ) { WORD attr; HANDLE h; if (g_enable_color == 0) { return; } switch (c) { default: case DW_COLOR_INFO: attr = BACKGROUND_WHITE; break; case DW_COLOR_ERROR: attr = FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_WHITE; break; case DW_COLOR_REC: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_WHITE; break; case DW_COLOR_DECODED: attr = FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_WHITE; break; case DW_COLOR_XMIT: attr = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_WHITE; break; case DW_COLOR_DEBUG: attr = FOREGROUND_GREEN | BACKGROUND_WHITE; break; } h = GetStdHandle(STD_OUTPUT_HANDLE); if (h != NULL && h != INVALID_HANDLE_VALUE) { SetConsoleTextAttribute (h, attr); } } #else void text_color_set ( enum dw_color_e c ) { if (g_enable_color == 0) { return; } switch (c) { default: case DW_COLOR_INFO: printf ("%s", black); break; case DW_COLOR_ERROR: printf ("%s", red); break; case DW_COLOR_REC: printf ("%s", green); break; case DW_COLOR_DECODED: printf ("%s", blue); break; case DW_COLOR_XMIT: printf ("%s", magenta); break; case DW_COLOR_DEBUG: printf ("%s", dark_green); break; } } #endif /*------------------------------------------------------------------- * * Name: dw_printf * * Purpose: printf replacement that allows us to send all text * output to stdout or other desired destination. * * Inputs: fmt - C language format. * ... - Addtional arguments, just like printf. * * * Returns: Number of characters in result. * * Bug: Fixed size buffer. * I'd rather not do a malloc for each print. * *--------------------------------------------------------------------*/ // TODO: replace all printf, look for stderr, perror // TODO: $ grep printf *.c | grep -v dw_printf | grep -v fprintf | gawk '{ print $1 }' | sort -u int dw_printf (const char *fmt, ...) { #define BSIZE 1000 va_list args; char buffer[BSIZE]; int len; va_start (args, fmt); len = vsnprintf (buffer, BSIZE, fmt, args); va_end (args); // TODO: other possible destinations... fputs (buffer, stdout); return (len); } #if TESTC main () { printf ("Initial condition\n"); text_color_init (1); printf ("After text_color_init\n"); text_color_set(DW_COLOR_INFO); printf ("Info\n"); text_color_set(DW_COLOR_ERROR); printf ("Error\n"); text_color_set(DW_COLOR_REC); printf ("Rec\n"); text_color_set(DW_COLOR_DECODED); printf ("Decoded\n"); text_color_set(DW_COLOR_XMIT); printf ("Xmit\n"); text_color_set(DW_COLOR_DEBUG); printf ("Debug\n"); } #endif /* end textcolor.c */ direwolf-1.5+dfsg/textcolor.h000066400000000000000000000020671347750676600163370ustar00rootroot00000000000000 /*------------------------------------------------------------------- * * Name: textcolor.h * * Purpose: Set color of text. * *--------------------------------------------------------------------*/ #ifndef TEXTCOLOR_H #define TEXTCOLOR_H 1 enum dw_color_e { DW_COLOR_INFO, /* black */ DW_COLOR_ERROR, /* red */ DW_COLOR_REC, /* green */ DW_COLOR_DECODED, /* blue */ DW_COLOR_XMIT, /* magenta */ DW_COLOR_DEBUG /* dark_green */ }; typedef enum dw_color_e dw_color_t; void text_color_init (int enable_color); void text_color_set (dw_color_t c); void text_color_term (void); /* Degree symbol. */ #if __WIN32__ //#define CH_DEGREE "\xc2\xb0" /* UTF-8. */ #define CH_DEGREE " " #else /* Maybe we could change this based on LANG environment variable. */ //#define CH_DEGREE "\xc2\xb0" /* UTF-8. */ #define CH_DEGREE " " #endif int dw_printf (const char *fmt, ...) #if __WIN32__ __attribute__((format(ms_printf,1,2))); /* Win C lib. */ #else __attribute__((format(printf,1,2))); /* gnu C lib. */ #endif #endif direwolf-1.5+dfsg/tnc-test-cd-results.png000066400000000000000000000232441347750676600204750ustar00rootroot00000000000000‰PNG  IHDRa}ÕëÙ7sRGB®ÎégAMA± üa pHYsÃÃÇo¨d&9IDATx^íëÎìÄ™¨¹®¾‡‘æ*úR²¤üjEÊ¿½Gš ßÂaÁŒ¦G£™=H á 9d…EH8Ÿ"EÈã×v¹Ëå×nÛÝ]®Ãó W‹öÙÕUO×W.WÝW4ÜûðÏÅï^ü}ññÇýë_{ñé§Ÿxó­ê_‰Ï>û¬Ÿþy'¾øâ‹N|ùå—øê«¯:ñõ×_÷â›o¾éÄ·ß~Û‰ï¾û®ßÿýÕâîŸ>P—çkßÿãwîT±ôó¹Á÷Îý»åÞõ‚ë Í-®\?¹þzëíCÇošMh.ýË_þR¼òêëÅÛ‡w*÷Þ÷ö;ïoÞ-ž~æ—Åëoü¡Í¸oüáÍ*Ìg°o¾õv/ä"í“Øq(ÏeÇ;ï¾×‰wß{¿ï½ÿÇN¼ÿÇ»øãÝ?uB2 ‘`ܽ[…äÅEŸ‰¤Â-÷®\ohnqýãúÉõ—ë7Í®'%ÿI¸>×>óËçªãV5á?þ¤ª‹¡«LÛd`±¶ýYj ¯ý Ø¿î/‰ûKãþi¿Vî/šû‹çþ"j¿š— ùòµå¹Äš÷oòàÒÏ—¾j¶ß4Úµ`“Å¡âRû³¸öµ×ß(þXþ`Të‹¥e…„ÙØTŸÍg$L!Ô–çÜ?¶ý¦9P“°ëQãYq®¸W•°ÙÉ9¨+aù÷½÷¥Éàý⥗_éÅ˯¼:¯¼úÚÉxõµ×GC~M|Åó/üV]žKpÿÜ¿¶<„ÐÜ`‡æ74GÙñì¯~Ýsœñß'Ÿ|Ò“°„æÒ“–ª²©>K<ùä“Åý÷ß_<ðÀmܾ}»xô±;Å'ÿµxèáGªø?ÿ÷Ÿ‚ ² ã?qá£=^<øàCWŠ;%Ä£âSãד¶«Ñrùט]~žzú™âÞ‡?üðƒì=þŸŠŸÿâ—Å‹¿©u¦ükú¨xü‰¡ °iI¸¹}{™„¥ýàÃ?«ó‘gjÿì‘e–‡q÷?pSæ#ûüì‘G—Iø÷/½\½Ë@Â+‚„V ¬X$ °"'%,s̽òÚëÕ•Ò%MBVJüòÙ_!a€306^5žçŠ{© @Ëa¿-î»ï¾*6»C³ÎæˆEŠÝ¦Îˆ“b»ovÛ{ùf[4kºXÝìÚã°/vÛM÷x÷•Ÿ·»b˜S8ÞK,LM§½óÝØ±Y’® åµlµã7±)óÃn¶Ø»Íñz‘ðE@‹X(.K®&ÔŒ|BÂvAÐcSL/HØþ*Ïq‰â=+”ïFÍnùµ¹?± ø‡nPÂWøþr _ˆI5µ +µá wŲ)×Ûç:”ºmYæH¸Ï¤{¹4v!>Gt ³ÓÉþnœD?”µä­ýCµôúFîÑþ3Þ¨_&Iøß_N á 1WÂÛíñÿ{ÛIØ©I]«Â½„—¤Óˆ„kºM ‹ÒåÄ=î·gßHøò á 1W›]Y»jþ¿W°}Ž«5 ”L¹©¹m:ÍÒnZþªl^×$­k7Û6ëmù¸±DF‹Ò餄Kìm–ˆæ„¨N¥û%Ó\°Ï×»eë^íkÑ®qê÷7åšr _ˆ)âr3öà>ÖvÇÂÑm»<Ç¿—òOù‘Bçþ ØÇêEss—•ðÂt²Û3’Á® ª„6s،ݣ½®#¥ë¥ùXžµìugK¸e d¾gK¸Äm7“p {w›þ›`u_Òó2õÔÂ)…Í>ýAÆF(ÏÝîSÌúÛͪmÌ1ìë´eSJÈ¢þg6³ÓiDÂ>Þ˜“kjF¹ZšÛ÷ܦyÝcḼ{-Ó~ ïoV>È$|!.!áN!m¢_ãêV=®'᪰8»r?v¸ÇÖ~„Óò$3Ó©#¤á|–^Q'=4‘»Rl_/ÍGÒHÄÞü¿½Ïäh{›™ù 7ð…°3g·Öea²¡mÕ˜&ƒË´ÿöQ½ý%Fû:}/Z»^Y˜”'ÞÚu?—?Qím¥öÔ¬ZÈät*·…©Ú¾Ùv)§$ìH±{ï>Ó\j¬ÇšmG£‰áïoÞ5åX$ °"H`ENJ˜9殇‘°ñ*sÌx„æ€AÂ+‚„V ¬X$ °"H`E0ÀŠ a€AÂ+‚„V ¬X$ °"yJ¸™'«7™=Ñ¢;XgFk~­¡åÈNÂÕŒ°¥}åß®„eF[#ÞzÂÅ£PeÒCkÒÍjÂFù<´¼ù p‚l›#úî"3wj»ÎƲÿf³Ñ—ca˜V©kÂf}5¼³q;-¼² ÀTNJ8Õ9æÆ$ìJ÷¾¹¹)~ô£µñãÿ¸ó™ ˆ¸BÊôŒ„W³™cnH•\‡rš„eMÂK›#äK€ü 9¢«næî7óy?°|¾ƒ‘0@¦d'á¶ÛF#ÍJ îºmÑ*ÙéŠÖºzhùL0@žd[ $ 'H80@ž á@@Ây‚„ ä $ 'H80@ž á@@Ây‚„ ä $ 'H80@ž á@@Ây¨„eÆŠãx ÓÂç!B0@ž á@@ÂylsÄa¿/övìšÑÊ6Ûb×.ß5²^6|dH a€<‰¤M¸žnH•­‚ré’€„ò$ ›æ ­Éal]< a€<9)á0昳ڈ·»âÐÎdq(öÛf®7$ b$l¼ìsíÄšCAsDH$Í‚]ëíÆ¦päÏå0@¦D$á#‡Ã¡ê=!ÿ¦È“(%œ"H Oâ’ða_ì¶›b³‘Øe}¸éO äI<Þo)éëÞf ûMäok a€<‰DÂæe ygdÜtI3/kT5ãxAÂy‰„í2Ü—3xYâ È“èš#¶í ="]é;\/§9b$ªsõ9=x01„K½€#eY@Ây•„[xc!N 'È“@%lz<Ì zG@| á@@Âylss̤ÅÓÿø«F¨DÒ&|á9æšîn½]œnpnoCëÆö™†ÔÑÄè3B% ½7ï¹êåŽÒ¾òoWÂrKò•ÜÍç¡ucûÌ Cêhbô¡rR©Î1ד°Ôhª±lSÕl‡ÖmÊsí3$ ©£‰Ñg„Š‘°ñj6s̹®ŽïC–‰P‡Öiç5ûœâææ¦¯‰[·nu>Dj¡‰Ñgh×tÉ2½„Hš#„ËÎ1·¶„]äKHMŒ>#T"’ð‘KÌ17E¦iah&a³Ï\0¤Ž&FŸ*ÑIøp¨»«m·Ûb·7+fâJ¸4m÷¡šýyhÝ~dŸ™ø°–1}ä–'|F¨DÕa†³tcÎ>m ¶ KšNw³Îa‡Öí3$ ©£å Ÿ*ÑH¸mƒ­B&ú´?/k $ ©£å Ÿ*‘HØšcΑm+gu?‰–1}ä–'|F¨D"á±2ÆÖņÔÑò„Ï$H O¢kŽf‡½iw¨Þ˜;.§9bMŒ>úDó`Îí…àæN£‰Ñg@Ÿx$\28Ç\ä0@žD%á– ¼1H O"’p-Þ}Û ,˜eóÇŽ $ 'ÑHxh¬ÓO˜6áÓhbôÐ' Oè¢FhbôÐ' ÓOø$š}ô‰DÂV?á­5rý„g¡‰Ñg@ŸxÌÑOøl41ú èsRÂaÌ1WsØ ô^:~d@ a€<16^ vŽ9›K̬H OâiŽH$ 'qIø°/vÛz@÷Mõ N{#N0@žDõ`®Û\wI3½#x0wMŒ>úD"akf­‘qÓ/¸š\³üLµ“hbôÐ' Û/d¸/gð²ÆT41ú 胄 äItÍÛýÎ’.oÌÍA£Ï€>Q=˜«ÈéÁƒ¹ÓhbôÐ' — άÁs“ÐÄè3 OTná¹EhbôÐ'®šp#ß½ü›Ž+0@žD áC±¸ç¾û6Å6I>$ 'ÁK¸íý0´ OB£Ï€>aKØê±Ùîšfõ8f}ìFÂy´„‡&÷´™²M a€< XÂæM¸M1ÚìËØ“ÑÄè3 O>õ:2¯-OE£Ï€>HØÆy+¯óÞÒuAÂyrRÂëÍ1gäZÆÆ ä®…‘ß¹–óYMU3‡9¦¶Î|[7$ 'FÂÆ«Í1gIxR\QÂRÓuüÉÁªÆ;¶nH OnŽÒ¶ÅvrȘjgbò9R?ì6=ÑÊ2íØº1nnn*ñš¸uëVçó5B£ÏЮ‰ R )ÓKXÂþ©„*ò52näz »È—xm41ú 胄 Ò¬ÐéæVa,~ÕDkšÆÖÍ ä 6ô$\·IW~u¶ÙŸÇÖÍ ä ¶hß¾k¢S›uº¡u*¿cë&‚„ò È$H Op a€­›È·&üø;UˆsÕæˆ»wïVáWÂ>™Ô¦¥¦ëÈY„]ÕxÇÖÍ ä‰-aã×A ËŠÕ%,c «Ñ¬_ŠÔ`¥Fm5+¹v›žhe™ˆvlÝ0@žhî´ ËF¶„%dcï¾öP–ÕñícÔíÐâ×kHøææ¦¯‰[·nu>WH™^ÂÉs²‘‘°Y¹†„»Ì”8w¢Ïž„ësIØ49Œ­›ƒ|‰‘´ ›ÞòÀËü-ÌJ‚òÙá|ê‘ÚÚÃØØìÿ¦®›È“HÚ„mñv%Ü]w&•@åXut¼îtC›¼n"H O"i¶Ekƶk¢f]¼ a€<‰®MXj™jûð¹mÂ+ƒ„ò$’6á’òOþª YUý-kƱ…ç·Á†È“HÚ„uLá@ÂyI›py ͸Ái(·È“HÚ„/Ø"P0@žDÒ&lzD,ëþH Oâi>ÇÞ0ž0$B$m¦9b,è' ñM›°ñ„× äI$N$ 'H80@žœ”ðzsÌiÝÒL³DÜMH OŒ„WšcN—pªý…‘0@žÜ„ }p a€<‰@ÂeTíÀÍçÎ2 e ñ‡„'Eܵc$>Oÿã?¬&KøPìµ×“#îÖpøhbô&K8/pøhbô&H8pøhbô&H8pøhbô&H8pøhbô&H8pøhbô&H8pøhbô&H8pøhbô&H8pøhbô&H8pøhbô&qIø°/v[{¬ˆò¿ý¾Øïc~W® ‡&FŸi„÷Ûb£Œ±ßÖŸ7»¸EŒ„ÃG£Ï€4‰D‡b׌ &ÓÝ×2nì9ìêÏŒ¢WF£Ï€4‰DÂö8Âî˜ÂiŒ1Œ„ÃG£Ï€49)áõ昳A°>š}¤‰‘°ñj@sÌÙ›#¶û%]î²^~ÉæˆÃnÓœ«Y ì·õyšè´A­›MŒ>Ò$ªs¶èܸ؃9ic.…¾+å~”°Ô¶7E{ŠªÚ|[7$>š}¤I<.9ôzH4îTYÏAjܵ@¥†ÝV~œsÈúJücëf€„ÃG£Ï€4‰JÂ-‡ºð¡ü÷’H3„‘§-áªy­ÙvlÝpøhbô&qJøTÍLJ{×–ðÍÍM%^·nÝê|& MŒ>C»&"œ2½„¸$|Í7æÚœ‡DkšÆÖÍA¾DMŒ>Ò$ªs>ߘ³kÂ¥i»ÛìÏcëf€„ÃG£Ï€4‰DÂþߘëHXpjÊ“×M ‡&FŸi‰„yYÖG£Ï€4A€„ÃG£Ï€4‰®9ÂÇsk€„ÃG£Ï€4‰êÁœÝîêÆ¥Ìù ‡&FŸi„K®ÿÆÜz áðÑÄè3 M¢’p˕ޘ[$>š}¤I4®_ÊVà4AÂᣉÑg@šD"á4z@Œ„ÃG£Ï€4‰DÂvïˆfQb áðÑÄè3 Mâi>˜®i›ê­¹­»¸íŒ„ÃG£Ï€4‰¬9b,xY®‹&FŸirR¡Ì1'ƒ­×£§ Å ÃUÑÄè3 MŒ„Wƒšc.¡h'AÂᣉÑg@šÚaÄ™!!MM8îÚîH8|41ú H“@%œæ =c áðÑÄè3 M¯ Ï ÌÁuÑÄè3 M•pÉÀ8Äáºhbô&áJ¸ÂÌ!×Ìž!mÄÍëËý`5¸.š}¤Ià6HóDý`ޱ#`-41ú H“H$ìp(k¾‰Ù ‡&FŸi¡„Óì)„ÃG£Ï€4A€„ÃG£Ï€4A€„ÃG£Ï€4A€„ÃG£Ï€4‰òÁ\Ým-­'sH8|41ú H“h$ÌôF°6š}¤I$fìXMŒ>Ò$ 3½¬&FŸiO›0ÓÁÊhbô&‘5GŒEÜMH8|41ú H“h$ÌôF°6š}¤ÉI ‡1Ç\ú áðÑÄè3 MŒ„WƒšcNã ƒ÷ìêvàÝnŸÌš}¤I$>Ö‚]Áµr>{À÷}±mQŸïx.éaf~.9ÈLÇ™ ‡×M ‡&FŸi‰„ÇÞ˜[·‘{+a©é:Mûm#é±u3@ÂᣉÑg@š a•º&lÜZÕ¶ÑI­ãææ¦¯‰[·nu>á…&FŸ¡]NH™^BtÍÒìРv8TµÎË4GqÅz »È—a£‰Ñg@šDó`®ú³¿ªñê1WzCTRu„®‰Ö49Œ­›MŒ>Ò$ —J×Sß_GÀU­Új…û°Íþ<¶nH8|41ú H“¨$Ür(k 2¶ð%ßÔ¨äé ÞjgvjâW­›MŒ>Ò$ 3¨;¬&FŸi‰„¯Ó -$pøhbô&‘HøØ;bÉŸú1€„ÃG£Ï€4‰§MøÀ î°.š}¤IdÍcwSMŒ>Ò$ 3¨;¬&FŸiOsDâ áðÑÄè3 M¢‘0]Ô`m41ú H“ÈÚ„é¢롉Ñg@šœ”psÌÑE ÖG£Ï€416^ wŽ9º¨@‚DÖ1tQ€øˆFÂtQ€‰DÂ郄ò$h ×ÝÒNwJ›º]È a€< XÂZ·4é%Q7=•›F÷5$ '‘Ixê²ø@Ây‚„ ä $ 'H¸ŒªX¢ù¬.CÂqHxR aˆ€%|(öÚëɃ÷kH O–p^ a€t¯nšÔŸÝôœúÝ^$+ ÓdF«æ×‘©[G d7Ã4gys<·Ü»™Z-Œê¾"™L}}§ö¿œ„'¦“Šr¯Ú9&¦ñy×2‡éÇ›'a7m‡—OMWÊ5ÓÝH8VÔŒf>;Ü•îPæìãºûµèǯÿäsãxþ$÷ØÇkí^7î³·]ôtª¥aß—¹ÆÎ½N=‡à¤±0õ;»Ci,ËÛs¹÷3p=õuÏ‘°Y7ž&õ6$<î× ÇŠ&a“±Ü?‡2W+mã½ÉÌõyNϹß®èÝ£r Sï³³u“ö·$!ëªtqîuè§ÒØ0ù;»J¦ÙÀõÔÛÌ“pʼn4©÷w¿s=u®óùoH`E0ÀŠœ”0sÌ\#aãUæ˜ðÍ+‚„V ¬X$ °"H`E0 ¼ÖéÒ¼æ9ðšlt^9>•^0éØ ç€3^Dÿýt%Ü@bÑ=èc äéxp☂ÓÍóuA0"ž&á8©ï­;ø‹,›?ÍÀ°‰Õà1#ƒË$éx=pʨ#”õIWÂý¡#Ó¤ežò ¯ NuèH…£„›ÂÖD· ô›#ªýªÏcû ;í6¾„ß\×”?›•&ã3fn'¶;çžÌò}“ž®Pt‘i?€îùÜôl÷±®¹ÝÆÖñâ$/”Ž:H8Yô̪QgFÉlÇLl–wpµŸUPšÌ{ÌðJ lsÅLmÓıóõ®¹¤YÖî6§§m+ÛmdÖ“®Èªë³–ÉçÑë(QÓ]˜zÍ /“ŽH8Yj OÉ8uft2boÿa »å²[ ¦_ǵh"áÞö¾¹tîcŽ<”cJZmv»2-ìm§¤MÿÇÔ¤{w?ý>êm»5ĥޗIG$œ,Óå§g0736Ÿ­Â7”1ëå¦4ûM¼–ëq¼ŽÎµŒÔr:÷1KfßcÚì·õ6"¤6IöíÒ—B7}šësÜ1ñs / N–þ/ÿnF¯q3móy¶„k:µ(e4éb®o¨Ð vÁ›)z{³\ÎYßs•6ÍɪÿªQ¶iÕ„uZúšëëíW…r}gC:^ $œ,}iQgÆKKx@´m&XÀ7Â’BI§Î•Gywí_"²¯I·öÿëôìOûÎÜïA1,k³ëC:^$œ2v!A—¦›iû[ÍÄ%6@Áç‡îu÷ §ay[fÙ_žï¸ŸHE¶7ÿ6‹õåaËʤãe@‰Seà2S¹eC–›ŒVgÆ¥–ã[Ù¿|¶öÑŽs5ªkqî«) ýÂØM£Þ²!y -šóoË´>»¾ÿÍFKóZöµ™ïï´<”kzé¿Òñ2é8ÀI 3Ç\T™¸É„MØ™½ÎtK%\îg dÌ[b2u>l8qï-½í¦Ô®j:÷×-¹µ œcµÛ» %8i)ç«bm[ïß—G…{—JkÒñjyÖHØx•9æ`2u&vå s 9ƒ„Î Ãb0Àùœ%á×^£øçÿwu ˜ÏY~ëíCqûÁ‡ªÀ|¤2ûÐÃ?[&á{÷>,»óDu ˜ô2»ýàƒË$,ñèãwŠÏ>û¼:Lç‡~(þíßÿ£xà–KøÁ‡*^øí‹Õ`:xó­â¥—_)î¿ÿþå–ŸûÕóŽ?¬ §‘„g~ñlåÔ³%üÑGÿõßOûÛߪƒÀ0Ò ñÔÓÏ|ðAëQãÔI¾{÷nöÎï½ÿ~%â?þ¤: ô‘°øÒ«F¼âQãÕ“~üÎUŸ~úiU#þųÏ/þþ¥ÊôpDºõJĽ{÷*gjÇž”°„½³‘° Ùö_ÿíß‹ç~ý|Õ™ "çxþ7/TN” ªíKףƯ£–0;J<ôÐCÕ¤‹…777庇‹G}¬G{|4{üÎh<~牓qç‰'Gã‰'ÿ… ˆÄC+ûvhîpCsšÃì°Ý'/cܾ}»çIq§„xÔ8ÕxvTÂvs„„ù,Ý-Œá?ûì³^|þùçøâ‹/:ñå—_v⫯¾êÄ×_Ý‹o¾ù¦ß~ûm'¾ûî»N|ÿý÷W‹»ú@]žKpÿÜ¿¶|p˽ëך[\ÿ¸~rý%M ¶ß4?J yT•ð½ÿ\¼üÊkÕ ©*Û;ÛŸ‘0…P[î#L\úùÁ÷„m¿iÔ$lšwíÏFÂï¾÷~-a¹È_<û«VÂf'#a 9(¦jË}„—|¾Dðý#aÛošMHþ“p}*®}þ…ߟ–ûT^ãÍJ²²Òì`‡ Sµå¾Âdà¥ŸÏ ¾$lûMs ñ£ Í¥âØ»wÿTüæ·¿«ÜÛJXºœ½úÚ•E¶Z¼üêkêò¡Ðž"Î ¹ñPBFCÒ–çÜ?÷¯-!4·ÌWfúo(^øÝ‹Å¯Ÿ¡øûßÿ^š·(þ'Ô¸~ÀyêIEND®B`‚direwolf-1.5+dfsg/tocalls.txt000066400000000000000000000241561347750676600163500ustar00rootroot00000000000000APRS TO-CALL VERSION NUMBERS 12 Dec 2017 ------------------------------------------------------------------- WB4APR 12 Dec 17 Added APHWxx for use in "HamWAN 11 Dec 17 Added APDVxx for OE6PLD's SSTV with APRS status exchange 20 Nov 17 added APPICO DB1NTO' PicoAPRS 18 Oct 17 Added APBMxx BrandMeister DMR Server for R3ABM 25 Sep 17 added APP6xx for APRSlib 05 Sep 17 Chged APTAxx to APTBxx for TinyAPRS by BG5HHP 17 Aug 17 Added APOCSG for POCSAG - N0AGI's APRS to Pagers 21 Jun 17 Added APCSMS for Cosmos (used for sending commands @USNA 08 Jun 17 Added APPMxx for DL1MX's RTL-SDR pytohon Igate 01 Jun 17 added APOFF digi off on PSAT,PSAT2 and APDTMF digi off mode on QIKCOM2 and DTMF ON and APRSON digi ON for PSAT and APDIGI digi ON for PSAT2 and QIKCOM-2 and APSAT digi ON for QIKCOM-1 20 Mar 17 added APTBxx for TinyAPRS by BG5HHP 06 Feb 17 added APIExx for W7KMV's PiAPRS system 25 Jan 17 added APSFxx F5OPV embedded devices - was APZ40 16 Dec 16 added APYSxx for W2GMD's Python APRS 14 Nov 16 Added APINxx for PinPoint by AB0WV 09 Nov 16 added APNICx for SQ5EKU http://sq5eku.blogspot.com/ 24 Oct 16 added APTKPT TrackPoint N0LP, removed APZTKP 24 Aug 16 added APK004 for Kenwood THD-74 29 Apr 16 added APFPRS for FreeDV by Jeroen PE1RXQ 25 Feb 16 Added APCDS0 for Leon Lessing ZS6LMG's cell tracker 21 Jan 16 added APDNOx for APRSduino by DO3SWW In 2015 Added APSTPO,APAND1,APDRxx,APZ247,APHTxx,APMTxx,APZMAJ APB2MF,APR2MF,APAVT5 In APRS, the AX.25 Destination address is not used for packet routing as is normally done in AX.25. So APRS uses it for two things. The initial APxxxx is used as a group identifier to make APRS packets instanantly recognizable on shared channels. Most applicaitons ignore all non APRS packets. The remaining 4 xxxx bytes of the field are available to indicate the software version number or application. The following applications have requested a TOCALL number series: APn 3rd digit is a number AP1WWX TAPR T-238+ WX station AP1MAJ Martyn M1MAJ DeLorme inReach Tracker AP4Rxy APRS4R software interface APnnnD Painter Engineering uSmartDigi D-Gate DSTAR Gateway APnnnU Painter Engineering uSmartDigi Digipeater APA APAFxx AFilter. APAGxx AGATE APAGWx SV2AGW's AGWtracker APALxx Alinco DR-620/635 internal TNC digis. "Hachi" ,JF1AJE APAXxx AFilterX. APAHxx AHub APAND1 APRSdroid (pre-release) http://aprsdroid.org/ APAMxx Altus Metrum GPS trackers APAVT5 SainSonic AP510 which is a 1watt tracker APAWxx AGWPE APB APBxxx Beacons or Rabbit TCPIP micros? APB2MF DL2MF - MF2APRS Radiosonde for balloons APBLxx BigRedBee BeeLine APBLO MOdel Rocketry K7RKT APBPQx John G8BPQ Digipeater/IGate APBMxx BrandMeister DMR Server for R3ABM APC APCxxx Cellular applications APCBBx VE7UDP Blackberry Applications APCDS0 Leon Lessing ZS6LMG's cell tracker APCLEY EYTraker GPRS/GSM tracker by ZS6EY APCLWX EYWeather GPRS/GSM WX station by ZS6EY APCLEZ Telit EZ10 GSM application ZS6CEY APCSMS for Cosmos (used for sending commands @USNA) APCWP8 John GM7HHB, WinphoneAPRS APCYxx Cybiko applications APD APD4xx UP4DAR platform APDDxx DV-RPTR Modem and Control Center Software APDFxx Automatic DF units APDGxx D-Star Gateways by G4KLX ircDDB APDHxx WinDV (DUTCH*Star DV Node for Windows) APDInn DIXPRS - Bela, HA5DI APDIGI Used by PSAT2 to indicate the digi is ON APDIGI digi ON for PSAT2 and QIKCOM-2 APDKxx KI4LKF g2_ircddb Dstar gateway software APDNOx APRSduino by DO3SWW APDOxx ON8JL Standalone DStar Node APDPRS D-Star originated posits APDRxx APRSdroid Android App http://aprsdroid.org/ APDSXX SP9UOB for dsDigi and ds-tracker APDTxx APRStouch Tone (DTMF) APDTMF digi off mode on QIKCOM2 and DTMF ON APDUxx U2APRS by JA7UDE APDVxx OE6PLD's SSTV with APRS status exchange APDWxx DireWolf, WB2OSZ APE APExxx Telemetry devices APECAN Pecan Pico APRS Balloon Tracker APERXQ Experimental tracker by PE1RXQ APF APFxxx Firenet APFGxx Flood Gage (KP4DJT) APFIxx for APRS.FI OH7LZB, Hessu APFPRS for FreeDV by Jeroen PE1RXQ APG APGxxx Gates, etc APGOxx for AA3NJ PDA application APH APHKxx for LA1BR tracker/digipeater APHAXn SM2APRS by PY2UEP APHTxx HMTracker by IU0AAC APHWxx for use in "HamWAN API APICQx for ICQ APICxx HA9MCQ's Pic IGate APIExx W7KMV's PiAPRS system APINxx PinPoint by AB0WV APJ APJAxx JavAPRS APJExx JeAPRS APJIxx jAPRSIgate APJSxx javAPRSSrvr APJYnn KA2DDO Yet another APRS system APK APK0xx Kenwood TH-D7's APK003 Kenwood TH-D72 APK004 Kenwood TH-D74 APK1xx Kenwood D700's APK102 Kenwood D710 APKRAM KRAMstuff.com - Mark. G7LEU APL APLQRU Charlie - QRU Server APLMxx WA0TQG transceiver controller APM APMxxx MacAPRS, APMGxx MiniGate - Alex, AB0TJ APMIxx SQ3PLX http://microsat.com.pl/ APMTxx LZ1PPL for tracker APN APNxxx Network nodes, digis, etc APN3xx Kantronics KPC-3 rom versions APN9xx Kantronics KPC-9612 Roms APNAxx WB6ZSU's APRServe APNDxx DIGI_NED APNICx SQ5EKU http://sq5eku.blogspot.com/ APNK01 Kenwood D700 (APK101) type APNK80 KAM version 8.0 APNKMP KAM+ APNMxx MJF TNC roms APNPxx Paccom TNC roms APNTxx SV2AGW's TNT tnc as a digi APNUxx UIdigi APNXxx TNC-X (K6DBG) APNWxx SQ3FYK.com WX/Digi and SQ3PLX http://microsat.com.pl/ APO APRSpoint APOFF Used by PSAT and PSAT2 to indicate the digi is OFF APOLUx for OSCAR satellites for AMSAT-LU by LU9DO APOAxx OpenAPRS - Greg Carter APOCSG For N0AGI's APRS to POCSAG project APOTxx Open Track APOD1w Open Track with 1 wire WX APOU2k Open Track for Ultimeter APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO APP APP6xx for APRSlib APPICx DB1NTO' PicoAPRS APPMxx DL1MX's RTL-SDR pytohon Igate APPTxx KetaiTracker by JF6LZE, Takeki (msg capable) APQ APQxxx Earthquake data APR APR8xx APRSdos versions 800+ APR2MF DL2MF - MF2APRS Radiosonde WX reporting APRDxx APRSdata, APRSdr APRGxx aprsg igate software, OH2GVE APRHH2 HamHud 2 APRKxx APRStk APRNOW W5GGW ipad application APRRTx RPC electronics APRS Generic, (obsolete. Digis should use APNxxx instead) APRSON Used by PSAT to indicate the DIGI is ON APRXxx >40 APRSmax APRXxx <39 for OH2MQK's igate APRTLM used in MIM's and Mic-lites, etc APRtfc APRStraffic APRSTx APRStt (Touch tone) APS APSxxx APRS+SA, etc APSARx ZL4FOX's SARTRACK APSAT digi ON for QIKCOM-1 APSCxx aprsc APRS-IS core server (OH7LZB, OH2MQK) APSFxx F5OPV embedded devices - was APZ40 APSK63 APRS Messenger -over-PSK63 APSK25 APRS Messenger GMSK-250 APSMSx Paul Dufresne's SMSGTE - SMS Gateway APSTMx for W7QO's Balloon trackers APSTPO for N0AGI Satellite Tracking and Operations APT APT2xx Tiny Track II APT3xx Tiny Track III APTAxx K4ATM's tiny track APTBxx TinyAPRS by BG5HHP Was APTAxx till Sep 2017 APTIGR TigerTrack APTKPT TrackPoint N0LP APTTxx Tiny Track APTWxx Byons WXTrac APTVxx for ATV/APRN and SSTV applications APU APU1xx UIview 16 bit applications APU2xx UIview 32 bit apps APU3xx UIview terminal program APUDRx NW Digital Radio's UDR (APRS/Dstar) APV APVxxx Voice over Internet applications APVRxx for IRLP APVLxx for I-LINK APVExx for ECHO link APW APWxxx WinAPRS, etc APWAxx APRSISCE Android version APWSxx DF4IAN's WS2300 WX station APWMxx APRSISCE KJ4ERJ APWWxx APRSISCE win32 version APX APXnnn Xastir APXRnn Xrouter APY APYxxx Yeasu APY008 Yaesu VX-8 series APY350 Yaesu FTM-350 series APYTxx for YagTracker APYSxx for W2GMD's Python APRS APZ APZxxx Experimental APZ247 for UPRS NR0Q APZ0xx Xastir (old versions. See APX) APZMAJ Martyn M1MAJ DeLorme inReach Tracker APZMDR for HaMDR trackers - hessu * hes.iki.fi] APZPAD Smart Palm APZTKP TrackPoint, Nick N0LP (Balloon tracking)(depricated) APZWIT MAP27 radio (Mountain Rescue) EI7IG APZWKR GM1WKR NetSked application Authors with similar alphabetic requirements are encouraged to share their address space with other software. Work out agreements amongst yourselves and keep me informed. REGISTERED ALTNETS: ------------------- ALTNETS are uses of the AX-25 tocall to distinguish specialized traffic that may be flowing on the APRS-IS, but that are not intended to be part of normal APRS distribution to all normal APRS software operating in normal (default) modes. Proper APRS software that honors this design are supposed to IGNORE all ALTNETS unless the particular operator has selected an ALTNET to monitor for. An example is when testing; an author may want to transmit objects all over his map for on-air testing, but does not want these to clutter everyone's maps or databases. He could use the ALTNET of "TEST" and client APRS software that respects the ALTNET concept should ignore these packets. An ALTNET is defined to be ANY AX.25 TOCALL that is NOT one of the normal APRS TOCALL's. The normal TOCALL's that APRS is supposed to process are: ALL, BEACON, CQ, QST, GPSxxx and of course APxxxx. The following is a list of ALTNETS that may be of interest to other users. This list is by no means complete, since ANY combination of characters other than APxxxx are considered an ALTNET. But this list can give consisntecy to ALTNETS that may be using the global APRS-IS and need some special recognition: TEST - A generic ALTNET for use during testing PSKAPR - PSKmail . But it is not AX.25 anyway de WB4APR, Bob direwolf-1.5+dfsg/tq.c000066400000000000000000000651671347750676600147450ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2014, 2015, 2016 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: tq.c * * Purpose: Transmit queue - hold packets for transmission until the channel is clear. * * Description: Producers of packets to be transmitted call tq_append and then * go merrily on their way, unconcerned about when the packet might * actually get transmitted. * * Another thread waits until the channel is clear and then removes * packets from the queue and transmits them. * * Revisions: 1.2 - Enhance for multiple audio devices. * *---------------------------------------------------------------*/ #define TQ_C 1 #include "direwolf.h" #include #include #include #include #include #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "tq.h" #include "dedupe.h" static packet_t queue_head[MAX_CHANS][TQ_NUM_PRIO]; /* Head of linked list for each queue. */ static dw_mutex_t tq_mutex; /* Critical section for updating queues. */ /* Just one for all queues. */ #if __WIN32__ static HANDLE wake_up_event[MAX_CHANS]; /* Notify transmit thread when queue not empty. */ #else static pthread_cond_t wake_up_cond[MAX_CHANS]; /* Notify transmit thread when queue not empty. */ static pthread_mutex_t wake_up_mutex[MAX_CHANS]; /* Required by cond_wait. */ static int xmit_thread_is_waiting[MAX_CHANS]; #endif static int tq_is_empty (int chan); /*------------------------------------------------------------------- * * Name: tq_init * * Purpose: Initialize the transmit queue. * * Inputs: audio_config_p - Audio device configuration. * * Outputs: * * Description: Initialize the queue to be empty and set up other * mechanisms for sharing it between different threads. * * We have different timing rules for different types of * packets so they are put into different queues. * * High Priority - * * Packets which are being digipeated go out first. * Latest recommendations are to retransmit these * immdediately (after no one else is heard, of course) * rather than waiting random times to avoid collisions. * The KPC-3 configuration option for this is "UIDWAIT OFF". * * Low Priority - * * Other packets are sent after a random wait time * (determined by PERSIST & SLOTTIME) to help avoid * collisions. * * Each audio channel has its own queue. * *--------------------------------------------------------------------*/ static struct audio_s *save_audio_config_p; void tq_init (struct audio_s *audio_config_p) { int c, p; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_init ( )\n"); #endif save_audio_config_p = audio_config_p; for (c=0; cachan[c].valid) { wake_up_event[c] = CreateEvent (NULL, 0, 0, NULL); if (wake_up_event[c] == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("tq_init: CreateEvent: can't create transmit wake up event, c=%d", c); exit (1); } } } #else int err; for (c = 0; c < MAX_CHANS; c++) { xmit_thread_is_waiting[c] = 0; if (audio_config_p->achan[c].valid) { err = pthread_cond_init (&(wake_up_cond[c]), NULL); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("tq_init: pthread_cond_init c=%d err=%d", c, err); perror (""); exit (1); } dw_mutex_init(&(wake_up_mutex[c])); } } #endif } /* end tq_init */ /*------------------------------------------------------------------- * * Name: tq_append * * Purpose: Add an APRS packet to the end of the specified transmit queue. * * Connected mode is a little different. Use lm_data_request instead. * * Inputs: chan - Channel, 0 is first. * * prio - Priority, use TQ_PRIO_0_HI for digipeated or * TQ_PRIO_1_LO for normal. * * pp - Address of packet object. * Caller should NOT make any references to * it after this point because it could * be deleted at any time. * * Outputs: * * Description: Add packet to end of linked list. * Signal the transmit thread if the queue was formerly empty. * * Note that we have a transmit thread each audio channel. * Two channels can share one audio output device. * * IMPORTANT! Don't make an further references to the packet object after * giving it to tq_append. * *--------------------------------------------------------------------*/ void tq_append (int chan, int prio, packet_t pp) { packet_t plast; packet_t pnext; #if DEBUG unsigned char *pinfo; int info_len = ax25_get_info (pp, &pinfo); if (info_len > 10) info_len = 10; text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_append (chan=%d, prio=%d, pp=%p) \"%*s\"\n", chan, prio, pp, info_len, (char*)pinfo); #endif assert (prio >= 0 && prio < TQ_NUM_PRIO); if (pp == NULL) { text_color_set(DW_COLOR_DEBUG); dw_printf ("INTERNAL ERROR: tq_append NULL packet pointer. Please report this!\n"); return; } #if AX25MEMDEBUG if (ax25memdebug_get()) { text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_append (chan=%d, prio=%d, seq=%d)\n", chan, prio, ax25memdebug_seq(pp)); } #endif if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); dw_printf ("This is probably a client application error, not a problem with direwolf.\n"); dw_printf ("AX.25 for Linux is known to transmit on channels 2 & 8 sometimes when it shouldn't.\n"); ax25_delete(pp); return; } /* * Is transmit queue out of control? * * There is no technical reason to limit the transmit packet queue length, it just seemed like a good * warning that something wasn't right. * When this was written, I was mostly concerned about APRS where packets would only be sent * occasionally and they can be discarded if they can't be sent out in a reasonable amount of time. * * If a large file is being sent, with TCP/IP, it is perfectly reasonable to have a large number * of packets waiting for transmission. * * Ideally, the application should be able to throttle the transmissions so the queue doesn't get too long. * If using the KISS interface, there is no way to get this information from the TNC back to the client app. * The AGW network interface does have a command 'y' to query about the number of frames waiting for transmission. * This was implemented in version 1.2. * * I'd rather not take out the queue length check because it is a useful sanity check for something going wrong. * Maybe the check should be performed only for APRS packets. * The check would allow an unlimited number of other types. * * Limit was 20. Changed to 100 in version 1.2 as a workaround. * * Implementing the 6PACK protocol is probably the proper solution. */ if (ax25_is_aprs(pp) && tq_count(chan,prio,"","",0) > 100) { text_color_set(DW_COLOR_ERROR); dw_printf ("Transmit packet queue for channel %d is too long. Discarding packet.\n", chan); dw_printf ("Perhaps the channel is so busy there is no opportunity to send.\n"); ax25_delete(pp); return; } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_append: enter critical section\n"); #endif dw_mutex_lock (&tq_mutex); if (queue_head[chan][prio] == NULL) { queue_head[chan][prio] = pp; } else { plast = queue_head[chan][prio]; while ((pnext = ax25_get_nextp(plast)) != NULL) { plast = pnext; } ax25_set_nextp (plast, pp); } dw_mutex_unlock (&tq_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_append: left critical section\n"); dw_printf ("tq_append (): about to wake up xmit thread.\n"); #endif #if __WIN32__ SetEvent (wake_up_event[chan]); #else if (xmit_thread_is_waiting[chan]) { int err; dw_mutex_lock (&(wake_up_mutex[chan])); err = pthread_cond_signal (&(wake_up_cond[chan])); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("tq_append: pthread_cond_signal err=%d", err); perror (""); exit (1); } dw_mutex_unlock (&(wake_up_mutex[chan])); } #endif } /* end tq_append */ /*------------------------------------------------------------------- * * Name: lm_data_request * * Purpose: Add an AX.25 frame to the end of the specified transmit queue. * * Use tq_append instead for APRS. * * Inputs: chan - Channel, 0 is first. * * prio - Priority, use TQ_PRIO_0_HI for priority (expedited) * or TQ_PRIO_1_LO for normal. * * pp - Address of packet object. * Caller should NOT make any references to * it after this point because it could * be deleted at any time. * * Outputs: A packet object is added to transmit queue. * * Description: 5.4. * * LM-DATA Request. The Data-link State Machine uses this primitive to pass * frames of any type (SABM, RR, UI, etc.) to the Link Multiplexer State Machine. * * LM-EXPEDITED-DATA Request. The data-link machine uses this primitive to * request transmission of each digipeat or expedite data frame. * * C2a.1 * * PH-DATA Request. This primitive from the Link Multiplexer State Machine * provides an AX.25 frame of any type (UI, SABM, I, etc.) that is to be transmitted. An * unlimited number of frames may be provided. If the transmission exceeds the 10- * minute limit or the anti-hogging time limit, the half-duplex Physical State Machine * automatically relinquishes the channel for use by the other stations. The * transmission is automatically resumed at the next transmission opportunity * indicated by the CSMA/p-persistence contention algorithm. * * PH-EXPEDITED-DATA Request. This primitive from the Link Multiplexer State * Machine provides the AX.25 frame that is to be transmitted immediately. The * simplex Physical State Machine gives preference to priority frames over normal * frames, and will take advantage of the PRIACK window. Priority frames can be * provided by the link multiplexer at any time; a PH-SEIZE Request and subsequent * PH Release Request are not employed for priority frames. * * C3.1 * * LM-DATA Request. This primitive from the Data-link State Machine provides a * AX.25 frame of any type (UI, SABM, I, etc.) that is to be transmitted. An unlimited * number of frames may be provided. The Link Multiplexer State Machine * accumulates the frames in a first-in, first-out queue until it is time to transmit them. * * C4.2 * * LM-DATA Request. This primitive is used by the Data link State Machines to pass * frames of any type (SABM, RR, UI, etc.) to the Link Multiplexer State Machine. * * LM-EXPEDITED-DATA Request. This primitive is used by the Data link State * Machine to pass expedited data to the link multiplexer. * * * Implementation: Add packet to end of linked list. * Signal the transmit thread if the queue was formerly empty. * * Note that we have a transmit thread each audio channel. * Two channels can share one audio output device. * * IMPORTANT! Don't make an further references to the packet object after * giving it to lm_data_request. * *--------------------------------------------------------------------*/ // TODO: FIXME: this is a copy of tq_append. Need to fine tune and explain why. void lm_data_request (int chan, int prio, packet_t pp) { packet_t plast; packet_t pnext; #if DEBUG unsigned char *pinfo; int info_len = ax25_get_info (pp, &pinfo); if (info_len > 10) info_len = 10; text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_data_request (chan=%d, prio=%d, pp=%p) \"%*s\"\n", chan, prio, pp, info_len, (char*)pinfo); #endif assert (prio >= 0 && prio < TQ_NUM_PRIO); if (pp == NULL) { text_color_set(DW_COLOR_DEBUG); dw_printf ("INTERNAL ERROR: lm_data_request NULL packet pointer. Please report this!\n"); return; } #if AX25MEMDEBUG if (ax25memdebug_get()) { text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_data_request (chan=%d, prio=%d, seq=%d)\n", chan, prio, ax25memdebug_seq(pp)); } #endif if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); ax25_delete(pp); return; } /* * Is transmit queue out of control? */ if (tq_count(chan,prio,"","",0) > 250) { text_color_set(DW_COLOR_ERROR); dw_printf ("Warning: Transmit packet queue for channel %d is extremely long.\n", chan); dw_printf ("Perhaps the channel is so busy there is no opportunity to send.\n"); } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_data_request: enter critical section\n"); #endif dw_mutex_lock (&tq_mutex); if (queue_head[chan][prio] == NULL) { queue_head[chan][prio] = pp; } else { plast = queue_head[chan][prio]; while ((pnext = ax25_get_nextp(plast)) != NULL) { plast = pnext; } ax25_set_nextp (plast, pp); } dw_mutex_unlock (&tq_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_data_request: left critical section\n"); #endif // Appendix C2a, from the Ax.25 protocol spec, says that a priority frame // will start transmission. If not already transmitting, normal frames // will pile up until LM-SEIZE Request starts transmission. // Erratum: It doesn't take long for that to fail. // We send SABM(e) frames to the transmit queue and the transmitter doesn't get activated. //NO! if (prio == TQ_PRIO_0_HI) { #if DEBUG dw_printf ("lm_data_request (): about to wake up xmit thread.\n"); #endif #if __WIN32__ SetEvent (wake_up_event[chan]); #else if (xmit_thread_is_waiting[chan]) { int err; dw_mutex_lock (&(wake_up_mutex[chan])); err = pthread_cond_signal (&(wake_up_cond[chan])); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("lm_data_request: pthread_cond_signal err=%d", err); perror (""); exit (1); } dw_mutex_unlock (&(wake_up_mutex[chan])); } #endif //NO! } } /* end lm_data_request */ /*------------------------------------------------------------------- * * Name: lm_seize_request * * Purpose: Force start of transmit even if transmit queue is empty. * * Inputs: chan - Channel, 0 is first. * * Description: 5.4. * * LM-SEIZE Request. The Data-link State Machine uses this primitive to request the * Link Multiplexer State Machine to arrange for transmission at the next available * opportunity. The Data-link State Machine uses this primitive when an * acknowledgement must be made; the exact frame in which the acknowledgement * is sent will be chosen when the actual time for transmission arrives. * * C2a.1 * * PH-SEIZE Request. This primitive requests the simplex state machine to begin * transmitting at the next available opportunity. When that opportunity has been * identified (according to the CSMA/p-persistence algorithm included within), the * transmitter started, a parameterized window provided for the startup of a * conventional repeater (if required), and a parameterized time allowed for the * synchronization of the remote station's receiver (known as TXDELAY in most * implementations), then a PH-SEIZE Confirm primitive is returned to the link * multiplexer. * * C3.1 * * LM-SEIZE Request. This primitive requests the Link Multiplexer State Machine to * arrange for transmission at the next available opportunity. The Data-link State * Machine uses this primitive when an acknowledgment must be made, but the exact * frame in which the acknowledgment will be sent will be chosen when the actual * time for transmission arrives. The Link Multiplexer State Machine uses the LMSEIZE * Confirm primitive to indicate that the transmission opportunity has arrived. * After the Data-link State Machine has provided the acknowledgment, the Data-link * State Machine gives permission to stop transmission with the LM Release Request * primitive. * * C4.2 * * LM-SEIZE Request. This primitive is used by the Data link State Machine to * request the Link Multiplexer State Machine to arrange for transmission at the next * available opportunity. The Data link State Machine uses this primitive when an * acknowledgment must be made, but the exact frame in which the acknowledgment * is sent will be chosen when the actual time for transmission arrives. * * * Implementation: Add a null frame (i.e. length of 0) to give the process a kick. * xmit.c needs to be smart enough to discard it. * *--------------------------------------------------------------------*/ void lm_seize_request (int chan) { packet_t pp; int prio = TQ_PRIO_1_LO; packet_t plast; packet_t pnext; #if DEBUG unsigned char *pinfo; text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_seize_request (chan=%d)\n", chan); #endif if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); return; } pp = ax25_new(); #if AX25MEMDEBUG if (ax25memdebug_get()) { text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_seize_request (chan=%d, seq=%d)\n", chan, ax25memdebug_seq(pp)); } #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_seize_request: enter critical section\n"); #endif dw_mutex_lock (&tq_mutex); if (queue_head[chan][prio] == NULL) { queue_head[chan][prio] = pp; } else { plast = queue_head[chan][prio]; while ((pnext = ax25_get_nextp(plast)) != NULL) { plast = pnext; } ax25_set_nextp (plast, pp); } dw_mutex_unlock (&tq_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("lm_seize_request: left critical section\n"); #endif #if DEBUG dw_printf ("lm_seize_request (): about to wake up xmit thread.\n"); #endif #if __WIN32__ SetEvent (wake_up_event[chan]); #else if (xmit_thread_is_waiting[chan]) { int err; dw_mutex_lock (&(wake_up_mutex[chan])); err = pthread_cond_signal (&(wake_up_cond[chan])); if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("lm_seize_request: pthread_cond_signal err=%d", err); perror (""); exit (1); } dw_mutex_unlock (&(wake_up_mutex[chan])); } #endif } /* end lm_seize_request */ /*------------------------------------------------------------------- * * Name: tq_wait_while_empty * * Purpose: Sleep while the transmit queue is empty rather than * polling periodically. * * Inputs: chan - Audio device number. * * Description: We have one transmit thread for each audio device. * This handles 1 or 2 channels. * *--------------------------------------------------------------------*/ void tq_wait_while_empty (int chan) { int is_empty; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_wait_while_empty (%d) : enter critical section\n", chan); #endif assert (chan >= 0 && chan < MAX_CHANS); dw_mutex_lock (&tq_mutex); #if DEBUG //text_color_set(DW_COLOR_DEBUG); //dw_printf ("tq_wait_while_empty (%d): after pthread_mutex_lock\n", chan); #endif is_empty = tq_is_empty(chan); dw_mutex_unlock (&tq_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_wait_while_empty (%d) : left critical section\n", chan); #endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_wait_while_empty (%d): is_empty = %d\n", chan, is_empty); #endif if (is_empty) { #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_wait_while_empty (%d): SLEEP - about to call cond wait\n", chan); #endif #if __WIN32__ WaitForSingleObject (wake_up_event[chan], INFINITE); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_wait_while_empty (): returned from wait\n"); #endif #else dw_mutex_lock (&(wake_up_mutex[chan])); xmit_thread_is_waiting[chan] = 1; int err; err = pthread_cond_wait (&(wake_up_cond[chan]), &(wake_up_mutex[chan])); xmit_thread_is_waiting[chan] = 0; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_wait_while_empty (%d): WOKE UP - returned from cond wait, err = %d\n", chan, err); #endif if (err != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("tq_wait_while_empty (%d): pthread_cond_wait err=%d", chan, err); perror (""); exit (1); } dw_mutex_unlock (&(wake_up_mutex[chan])); #endif } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_wait_while_empty (%d) returns\n", chan); #endif } /*------------------------------------------------------------------- * * Name: tq_remove * * Purpose: Remove a packet from the head of the specified transmit queue. * * Inputs: chan - Channel, 0 is first. * * prio - Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO. * * Returns: Pointer to packet object. * Caller should destroy it with ax25_delete when finished with it. * *--------------------------------------------------------------------*/ packet_t tq_remove (int chan, int prio) { packet_t result_p; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_remove(%d,%d) enter critical section\n", chan, prio); #endif dw_mutex_lock (&tq_mutex); if (queue_head[chan][prio] == NULL) { result_p = NULL; } else { result_p = queue_head[chan][prio]; queue_head[chan][prio] = ax25_get_nextp(result_p); ax25_set_nextp (result_p, NULL); } dw_mutex_unlock (&tq_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_remove(%d,%d) leave critical section, returns %p\n", chan, prio, result_p); #endif #if AX25MEMDEBUG if (ax25memdebug_get() && result_p != NULL) { text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_remove (chan=%d, prio=%d) seq=%d\n", chan, prio, ax25memdebug_seq(result_p)); } #endif return (result_p); } /* end tq_remove */ /*------------------------------------------------------------------- * * Name: tq_peek * * Purpose: Take a peek at the next frame in the queue but don't remove it. * * Inputs: chan - Channel, 0 is first. * * prio - Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO. * * Returns: Pointer to packet object or NULL. * * Caller should NOT destroy it because it is still in the queue. * *--------------------------------------------------------------------*/ packet_t tq_peek (int chan, int prio) { packet_t result_p; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_peek(%d,%d) enter critical section\n", chan, prio); #endif // I don't think we need critical region here. //dw_mutex_lock (&tq_mutex); result_p = queue_head[chan][prio]; // Just take a peek at the head. Don't remove it. //dw_mutex_unlock (&tq_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_remove(%d,%d) leave critical section, returns %p\n", chan, prio, result_p); #endif #if AX25MEMDEBUG if (ax25memdebug_get() && result_p != NULL) { text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_remove (chan=%d, prio=%d) seq=%d\n", chan, prio, ax25memdebug_seq(result_p)); } #endif return (result_p); } /* end tq_peek */ /*------------------------------------------------------------------- * * Name: tq_is_empty * * Purpose: Test if queues for specified channel are empty. * * Inputs: chan Channel * * Returns: True if nothing in the queue. * *--------------------------------------------------------------------*/ static int tq_is_empty (int chan) { int p; assert (chan >= 0 && chan < MAX_CHANS); for (p=0; p= 0 && p < TQ_NUM_PRIO); if (queue_head[chan][p] != NULL) return (0); } return (1); } /* end tq_is_empty */ /*------------------------------------------------------------------- * * Name: tq_count * * Purpose: Return count of the number of packets (or bytes) in the specified transmit queue. * This is used only for queries from KISS or AWG client applications. * * Inputs: chan - Channel, 0 is first. * * prio - Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO. * Specify -1 for total of both. * * source - If specified, count only those with this source address. * * dest - If specified, count only those with this destination address. * * bytes - If true, return number of bytes rather than packets. * * Returns: Number of items in specified queue. * *--------------------------------------------------------------------*/ int tq_count (int chan, int prio, char *source, char *dest, int bytes) { packet_t pp; int n; if (prio == -1) { return (tq_count(chan, TQ_PRIO_0_HI, source, dest, bytes) + tq_count(chan, TQ_PRIO_1_LO, source, dest, bytes)); } // Array bounds check. FIXME: TODO: should have internal error instead of dying. if (chan < 0 || chan >= MAX_CHANS || prio < 0 || prio >= TQ_NUM_PRIO) { text_color_set(DW_COLOR_DEBUG); dw_printf ("INTERNAL ERROR - tq_count(%d, %d, \"%s\", \"%s\", %d)\n", chan, prio, source, dest, bytes); return (0); } if (queue_head[chan][prio] == 0) { return (0); } // Don't want lists being rearranged while we are traversing them. dw_mutex_lock (&tq_mutex); n = 0; pp = queue_head[chan][prio]; while (pp != NULL) { int count_it = 1; if (source != NULL && *source != '\0') { char frame_source[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pp, AX25_SOURCE, frame_source); if (strcmp(source,frame_source) != 0) count_it = 0; } if (count_it && dest != NULL && *dest != '\0') { char frame_dest[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pp, AX25_DESTINATION, frame_dest); if (strcmp(dest,frame_dest) != 0) count_it = 0; } if (count_it) { if (bytes) { n += ax25_get_frame_len(pp); } else { n++; } } pp = ax25_get_nextp(pp); } dw_mutex_unlock (&tq_mutex); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_count(%d, %d, \"%s\", \"%s\", %d) returns %d\n", chan, prio, source, dest, bytes, n); #endif return (n); } /* end tq_count */ /* end tq.c */ direwolf-1.5+dfsg/tq.h000066400000000000000000000015121347750676600147320ustar00rootroot00000000000000 /*------------------------------------------------------------------ * * Module: tq.h * * Purpose: Transmit queue - hold packets for transmission until the channel is clear. * *---------------------------------------------------------------*/ #ifndef TQ_H #define TQ_H 1 #include "ax25_pad.h" #include "audio.h" #define TQ_NUM_PRIO 2 /* Number of priorities. */ #define TQ_PRIO_0_HI 0 #define TQ_PRIO_1_LO 1 void tq_init (struct audio_s *audio_config_p); void tq_append (int chan, int prio, packet_t pp); void lm_data_request (int chan, int prio, packet_t pp); void lm_seize_request (int chan); void tq_wait_while_empty (int chan); packet_t tq_remove (int chan, int prio); packet_t tq_peek (int chan, int prio); int tq_count (int chan, int prio, char *source, char *dest, int bytes); #endif /* end tq.h */ direwolf-1.5+dfsg/tt_text.c000066400000000000000000001304371347750676600160050ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: tt_text.c * * Purpose: Translate between text and touch tone representation. * * Description: Letters can be represented by different touch tone * keypad sequences. * * References: This is based upon APRStt (TM) documents but not 100% * compliant due to ambiguities and inconsistencies in * the specifications. * * http://www.aprs.org/aprstt.html * *---------------------------------------------------------------*/ /* * There are two different encodings called: * * * Two-key * * Digits are represented by a single key press. * Letters (or space) are represented by the corresponding * key followed by A, B, C, or D depending on the position * of the letter. * * * Multi-press * * Letters are represented by one or more key presses * depending on their position. * e.g. on 5/JKL key, J = 1 press, K = 2, etc. * The digit is the number of letters plus 1. * In this case, press 5 key four times to get digit 5. * When two characters in a row use the same key, * use the "A" key as a separator. * * Examples: * * Character Multipress Two Key Comments * --------- ---------- ------- -------- * 0 00 0 Space is handled like a letter. * 1 1 1 No letters on 1 button. * 2 2222 2 3 letters -> 4 key presses * 9 99999 9 * W 9 9A * X 99 9B * Y 999 9C * Z 9999 9D * space 0 0A 0A was used in an APRStt comment example. * * * Note that letters can occur in callsigns and comments. * Everywhere else they are simply digits. * * * * New fixed length callsign format * * * The "QIKcom-2" project adds a new format where callsigns are represented by * a fixed length string of only digits. The first 6 digits are the buttons corresponding * to the letters. The last 4 take a little calculation. Example: * * W B 4 A P R original. * 9 2 4 2 7 7 corresponding button. * 1 2 0 1 1 2 character position on key. 0 for the digit. * * Treat the last line as a base 4 number. * Convert it to base 10 and we get 1558 for the last four digits. */ /* * Everything is based on this table. * Changing it will change everything. * In other words, don't mess with it. * The world will come crumbling down. */ static const char translate[10][4] = { /* A B C D */ /* --- --- --- --- */ /* 0 */ { ' ', 0, 0, 0 }, /* 1 */ { 0, 0, 0, 0 }, /* 2 */ { 'A', 'B', 'C', 0 }, /* 3 */ { 'D', 'E', 'F', 0 }, /* 4 */ { 'G', 'H', 'I', 0 }, /* 5 */ { 'J', 'K', 'L', 0 }, /* 6 */ { 'M', 'N', 'O', 0 }, /* 7 */ { 'P', 'Q', 'R', 'S' }, /* 8 */ { 'T', 'U', 'V', 0 }, /* 9 */ { 'W', 'X', 'Y', 'Z' } }; /* * This is for the new 10 character fixed length callsigns for APRStt 3. * Notice that it uses an old keypad layout with Q & Z on the 1 button. * The TH-D72A and all telephones that I could find all have * four letters each on the 7 and 9 buttons. * This inconsistency is sure to cause confusion but the 6+4 scheme won't * be possible with more than 4 characters assigned to one button. * 4**6-1 = 4096 which fits in 4 decimal digits. * 5**6-1 = 15624 would not fit. * * The column is a two bit code packed into the last 4 digits. */ static const char call10encoding[10][4] = { /* 0 1 2 3 */ /* --- --- --- --- */ /* 0 */ { '0', ' ', 0, 0 }, /* 1 */ { '1', 'Q', 'Z', 0 }, /* 2 */ { '2', 'A', 'B', 'C' }, /* 3 */ { '3', 'D', 'E', 'F' }, /* 4 */ { '4', 'G', 'H', 'I' }, /* 5 */ { '5', 'J', 'K', 'L' }, /* 6 */ { '6', 'M', 'N', 'O' }, /* 7 */ { '7', 'P', 'R', 'S' }, /* 8 */ { '8', 'T', 'U', 'V' }, /* 9 */ { '9', 'W', 'X', 'Y' } }; /* * Special satellite 4 digit gridsquares to cover "99.99% of the world's population." */ static const char grid[10][10][3] = { { "AP", "BP", "AO", "BO", "CO", "DO", "EO", "FO", "GO", "OJ" }, // 0 - Canada { "CN", "DN", "EN", "FN", "GN", "CM", "DM", "EM", "FM", "OI" }, // 1 - USA { "DL", "EL", "FL", "DK", "EK", "FK", "EJ", "FJ", "GJ", "PI" }, // 2 - C. America { "FI", "GI", "HI", "FH", "GH", "HH", "FG", "GG", "FF", "GF" }, // 3 - S. America { "JP", "IO", "JO", "KO", "IN", "JN", "KN", "IM", "JM", "KM" }, // 4 - Europe { "LO", "MO", "NO", "OO", "PO", "QO", "RO", "LN", "MN", "NN" }, // 5 - Russia { "ON", "PN", "QN", "OM", "PM", "QM", "OL", "PL", "OK", "PK" }, // 6 - Japan, China { "LM", "MM", "NM", "LL", "ML", "NL", "LK", "MK", "NK", "LJ" }, // 7 - India { "PH", "QH", "OG", "PG", "QG", "OF", "PF", "QF", "RF", "RE" }, // 8 - Aus / NZ { "IL", "IK", "IJ", "JJ", "JI", "JH", "JG", "KG", "JF", "KF" } }; // 9 - Africa #include "direwolf.h" #include #include #include #include #include #include #include "textcolor.h" #include "tt_text.h" #if defined(ENC_MAIN) || defined(DEC_MAIN) void text_color_set (dw_color_t c) { return; } int dw_printf (const char *fmt, ...) { va_list args; int len; va_start (args, fmt); len = vprintf (fmt, args); va_end (args); return (len); } #endif /*------------------------------------------------------------------ * * Name: tt_text_to_multipress * * Purpose: Convert text to the multi-press representation. * * Inputs: text - Input string. * Should contain only digits, letters, or space. * All other punctuation is treated as space. * * quiet - True to suppress error messages. * * Outputs: buttons - Sequence of buttons to press. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_text_to_multipress (const char *text, int quiet, char *buttons) { const char *t = text; char *b = buttons; char c; int row, col; int errors = 0; int found; int n; *b = '\0'; while ((c = *t++) != '\0') { if (isdigit(c)) { /* Count number of other characters assigned to this button. */ /* Press that number plus one more. */ n = 1; row = c - '0'; for (col=0; col<4; col++) { if (translate[row][col] != 0) { n++; } } if (buttons[0] != '\0' && *(b-1) == row + '0') { *b++ = 'A'; } while (n--) { *b++ = row + '0'; *b = '\0'; } } else { if (isupper(c)) { ; } else if (islower(c)) { c = toupper(c); } else if (c != ' ') { errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Text to multi-press: Only letters, digits, and space allowed.\n"); } c = ' '; } /* Search for everything else in the translation table. */ /* Press number of times depending on column where found. */ found = 0; for (row=0; row<10 && ! found; row++) { for (col=0; col<4 && ! found; col++) { if (c == translate[row][col]) { /* Stick in 'A' if previous character used same button. */ if (buttons[0] != '\0' && *(b-1) == row + '0') { *b++ = 'A'; } n = col + 1; while (n--) { *b++ = row + '0'; *b = '\0'; found = 1; } } } } if (! found) { errors++; text_color_set (DW_COLOR_ERROR); dw_printf ("Text to multi-press: INTERNAL ERROR. Should not be here.\n"); } } } return (errors); } /* end tt_text_to_multipress */ /*------------------------------------------------------------------ * * Name: tt_text_to_two_key * * Purpose: Convert text to the two-key representation. * * Inputs: text - Input string. * Should contain only digits, letters, or space. * All other punctuation is treated as space. * * quiet - True to suppress error messages. * * Outputs: buttons - Sequence of buttons to press. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_text_to_two_key (const char *text, int quiet, char *buttons) { const char *t = text; char *b = buttons; char c; int row, col; int errors = 0; int found; *b = '\0'; while ((c = *t++) != '\0') { if (isdigit(c)) { /* Digit is single key press. */ *b++ = c; *b = '\0'; } else { if (isupper(c)) { ; } else if (islower(c)) { c = toupper(c); } else if (c != ' ') { errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Text to two key: Only letters, digits, and space allowed.\n"); } c = ' '; } /* Search for everything else in the translation table. */ found = 0; for (row=0; row<10 && ! found; row++) { for (col=0; col<4 && ! found; col++) { if (c == translate[row][col]) { *b++ = '0' + row; *b++ = 'A' + col; *b = '\0'; found = 1; } } } if (! found) { errors++; text_color_set (DW_COLOR_ERROR); dw_printf ("Text to two-key: INTERNAL ERROR. Should not be here.\n"); } } } return (errors); } /* end tt_text_to_two_key */ /*------------------------------------------------------------------ * * Name: tt_letter_to_two_digits * * Purpose: Convert one letter to 2 digit representation. * * Inputs: c - One letter. * * quiet - True to suppress error messages. * * Outputs: buttons - Sequence of two buttons to press. * "00" for error because this is probably * being used to build up a fixed length * string where positions are signficant. * Must be at least 3 bytes. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ // TODO: need to test this. int tt_letter_to_two_digits (char c, int quiet, char buttons[3]) { int row, col; int errors = 0; int found; strlcpy(buttons, "", 3); if (islower(c)) { c = toupper(c); } if ( ! isupper(c)) { errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Letter to two digits: \"%c\" found where a letter is required.\n", c); } strlcpy (buttons, "00", 3); return (errors); } /* Search in the translation table. */ found = 0; for (row=0; row<10 && ! found; row++) { for (col=0; col<4 && ! found; col++) { if (c == translate[row][col]) { buttons[0] = '0' + row; buttons[1] = '1' + col; buttons[2] = '\0'; found = 1; } } } if (! found) { errors++; text_color_set (DW_COLOR_ERROR); dw_printf ("Letter to two digits: INTERNAL ERROR. Should not be here.\n"); strlcpy (buttons, "00", 3); } return (errors); } /* end tt_letter_to_two_digits */ /*------------------------------------------------------------------ * * Name: tt_text_to_call10 * * Purpose: Convert text to the 10 character callsign format. * * Inputs: text - Input string. * Should contain from 1 to 6 letters and digits. * * quiet - True to suppress error messages. * * Outputs: buttons - Sequence of buttons to press. * Should be exactly 10 unless error. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_text_to_call10 (const char *text, int quiet, char *buttons) { const char *t; char *b; char c; int packed; /* two bits per character */ int row, col; int errors = 0; int found; char padded[8]; char stemp[11]; strcpy (buttons, ""); /* Quick validity check. */ if (strlen(text) < 1 || strlen(text) > 6) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Text to callsign 6+4: Callsign \"%s\" not between 1 and 6 characters.\n", text); } errors++; return (errors); } for (t = text; *t != '\0'; t++) { if (! isalnum(*t)) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Text to callsign 6+4: Callsign \"%s\" can contain only letters and digits.\n", text); } errors++; return (errors); } } /* Append spaces if less than 6 characters. */ strcpy (padded, text); while (strlen(padded) < 6) { strcat (padded, " "); } b = buttons; packed = 0; for (t = padded; *t != '\0'; t++) { c = *t; if (islower(c)) { c = toupper(c); } /* Search in the translation table. */ found = 0; for (row=0; row<10 && ! found; row++) { for (col=0; col<4 && ! found; col++) { if (c == call10encoding[row][col]) { *b++ = '0' + row; *b = '\0'; packed = packed * 4 + col; /* base 4 to binary */ found = 1; } } } if (! found) { /* Earlier check should have caught any character not in translation table. */ errors++; text_color_set (DW_COLOR_ERROR); dw_printf ("Text to callsign 6+4: INTERNAL ERROR 0x%02x. Should not be here.\n", c); } } /* Binary to decimal for the columns. */ snprintf (stemp, sizeof(stemp), "%04d", packed); strcat (buttons, stemp); return (errors); } /* end tt_text_to_call10 */ /*------------------------------------------------------------------ * * Name: tt_text_to_satsq * * Purpose: Convert Special Satellite Gridsquare to 4 digit DTMF representation. * * Inputs: text - Input string. * Should be two letters (A thru R) and two digits. * * quiet - True to suppress error messages. * * Outputs: buttons - Sequence of buttons to press. * Should be 4 digits unless error. * * Returns: Number of errors detected. * * Example: "FM19" is converted to "1819." * "AA00" is converted to empty string and error return code. * *----------------------------------------------------------------*/ int tt_text_to_satsq (const char *text, int quiet, char *buttons, size_t buttonsize) { int row, col; int errors = 0; int found; char uc[3]; strlcpy (buttons, "", buttonsize); /* Quick validity check. */ if (strlen(text) < 1 || strlen(text) > 4) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Satellite Gridsquare to DTMF: Gridsquare \"%s\" must be 4 characters.\n", text); } errors++; return (errors); } /* Changing to upper case makes things easier later. */ uc[0] = islower(text[0]) ? toupper(text[0]) : text[0]; uc[1] = islower(text[1]) ? toupper(text[1]) : text[1]; uc[2] = '\0'; if (uc[0] < 'A' || uc[0] > 'R' || uc[1] < 'A' || uc[1] > 'R') { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Satellite Gridsquare to DTMF: First two characters \"%s\" must be letters in range of A to R.\n", text); } errors++; return (errors); } if (! isdigit(text[2]) || ! isdigit(text[3])) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Satellite Gridsquare to DTMF: Last two characters \"%s\" must digits.\n", text); } errors++; return (errors); } /* Search in the translation table. */ found = 0; for (row=0; row<10 && ! found; row++) { for (col=0; col<10 && ! found; col++) { if (strcmp(uc,grid[row][col]) == 0) { char btemp[8]; btemp[0] = row + '0'; btemp[1] = col + '0'; btemp[2] = text[2]; btemp[3] = text[3]; btemp[4] = '\0'; strlcpy (buttons, btemp, buttonsize); found = 1; } } } if (! found) { /* Sorry, Greenland, and half of Africa, and ... */ errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Satellite Gridsquare to DTMF: Sorry, your location can't be converted to DTMF.\n"); } } return (errors); } /* end tt_text_to_satsq */ /*------------------------------------------------------------------ * * Name: tt_text_to_ascii2d * * Purpose: Convert text to the two digit per ascii character representation. * * Inputs: text - Input string. * Any printable ASCII characters. * * quiet - True to suppress error messages. * * Outputs: buttons - Sequence of buttons to press. * * Returns: Number of errors detected. * * Description: The standard comment format uses the multipress * encoding which allows only single case letters, digits, * and the space character. * This is a more flexible format that can handle all * printable ASCII characters. We take the character code, * subtract 32 and convert to two decimal digits. i.e. * space = 00 * ! = 01 * " = 02 * ... * ~ = 94 * * This is mostly for internal use, so macros can generate * comments with all characters. * *----------------------------------------------------------------*/ int tt_text_to_ascii2d (const char *text, int quiet, char *buttons) { const char *t = text; char *b = buttons; char c; int errors = 0; *b = '\0'; while ((c = *t++) != '\0') { int n; /* "isprint()" might depend on locale so use brute force. */ if (c < ' ' || c > '~') c = '?'; n = c - 32; *b++ = (n / 10) + '0'; *b++ = (n % 10) + '0'; *b = '\0'; } return (errors); } /* end tt_text_to_ascii2d */ /*------------------------------------------------------------------ * * Name: tt_multipress_to_text * * Purpose: Convert the multi-press representation to text. * * Inputs: buttons - Input string. * Should contain only 0123456789A. * * quiet - True to suppress error messages. * * Outputs: text - Converted to letters, digits, space. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_multipress_to_text (const char *buttons, int quiet, char *text) { const char *b = buttons; char *t = text; char c; int row, col; int errors = 0; int maxspan; int n; *t = '\0'; while ((c = *b++) != '\0') { if (isdigit(c)) { /* Determine max that can occur in a row. */ /* = number of other characters assigned to this button + 1. */ maxspan = 1; row = c - '0'; for (col=0; col<4; col++) { if (translate[row][col] != 0) { maxspan++; } } /* Count number of consecutive same digits. */ n = 1; while (c == *b) { b++; n++; } if (n < maxspan) { *t++ = translate[row][n-1]; *t = '\0'; } else if (n == maxspan) { *t++ = c; *t = '\0'; } else { errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Multi-press to text: Maximum of %d \"%c\" can occur in a row.\n", maxspan, c); } /* Treat like the maximum length. */ *t++ = c; *t = '\0'; } } else if (c == 'A' || c == 'a') { /* Separator should occur only if digit before and after are the same. */ if (b == buttons + 1 || *b == '\0' || *(b-2) != *b) { errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Multi-press to text: \"A\" can occur only between two same digits.\n"); } } } else { /* Completely unexpected character. */ errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Multi-press to text: \"%c\" not allowed.\n", c); } } } return (errors); } /* end tt_multipress_to_text */ /*------------------------------------------------------------------ * * Name: tt_two_key_to_text * * Purpose: Convert the two key representation to text. * * Inputs: buttons - Input string. * Should contain only 0123456789ABCD. * * quiet - True to suppress error messages. * * Outputs: text - Converted to letters, digits, space. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_two_key_to_text (const char *buttons, int quiet, char *text) { const char *b = buttons; char *t = text; char c; int row, col; int errors = 0; *t = '\0'; while ((c = *b++) != '\0') { if (isdigit(c)) { /* Letter (or space) if followed by ABCD. */ row = c - '0'; col = -1; if (*b >= 'A' && *b <= 'D') { col = *b++ - 'A'; } else if (*b >= 'a' && *b <= 'd') { col = *b++ - 'a'; } if (col >= 0) { if (translate[row][col] != 0) { *t++ = translate[row][col]; *t = '\0'; } else { errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Two key to text: Invalid combination \"%c%c\".\n", c, col+'A'); } } } else { *t++ = c; *t = '\0'; } } else if ((c >= 'A' && c <= 'D') || (c >= 'a' && c <= 'd')) { /* ABCD not expected here. */ errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Two-key to text: A, B, C, or D in unexpected location.\n"); } } else { /* Completely unexpected character. */ errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Two-key to text: Invalid character \"%c\".\n", c); } } } return (errors); } /* end tt_two_key_to_text */ /*------------------------------------------------------------------ * * Name: tt_two_digits_to_letter * * Purpose: Convert the two digit representation to one letter. * * Inputs: buttons - Input string. * Should contain exactly two digits. * * quiet - True to suppress error messages. * * textsiz - Size of result storage. Typically 2. * * Outputs: text - Converted to string which should contain one upper case letter. * Empty string on error. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_two_digits_to_letter (const char *buttons, int quiet, char *text, size_t textsiz) { char c1 = buttons[0]; char c2 = buttons[1]; int row, col; int errors = 0; char stemp2[2]; strlcpy (text, "", textsiz); if (c1 >= '2' && c1 <= '9') { if (c2 >= '1' && c2 <= '4') { row = c1 - '0'; col = c2 - '1'; if (translate[row][col] != 0) { stemp2[0] = translate[row][col]; stemp2[1] = '\0'; strlcpy (text, stemp2, textsiz); } else { errors++; strlcpy (text, "", textsiz); if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Two digits to letter: Invalid combination \"%c%c\".\n", c1, c2); } } } else { errors++; strlcpy (text, "", textsiz); if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Two digits to letter: Second character \"%c\" must be in range of 1 through 4.\n", c2); } } } else { errors++; strlcpy (text, "", textsiz); if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Two digits to letter: First character \"%c\" must be in range of 2 through 9.\n", c1); } } return (errors); } /* end tt_two_digits_to_letter */ /*------------------------------------------------------------------ * * Name: tt_call10_to_text * * Purpose: Convert the 10 digit callsign representation to text. * * Inputs: buttons - Input string. * Should contain only ten digits. * * quiet - True to suppress error messages. * * Outputs: text - Converted to callsign with upper case letters and digits. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_call10_to_text (const char *buttons, int quiet, char *text) { const char *b; char *t; char c; int packed; /* from last 4 digits */ int row, col; int errors = 0; int k; t = text; *t = '\0'; /* result */ /* Validity check. */ if (strlen(buttons) != 10) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Callsign 6+4 to text: Encoded Callsign \"%s\" must be exactly 10 digits.\n", buttons); } errors++; return (errors); } for (b = buttons; *b != '\0'; b++) { if (! isdigit(*b)) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Callsign 6+4 to text: Encoded Callsign \"%s\" can contain only digits.\n", buttons); } errors++; return (errors); } } packed = atoi(buttons+6); for (k = 0; k < 6; k++) { c = buttons[k]; row = c - '0'; col = (packed >> ((5 - k) *2)) & 3; if (row < 0 || row > 9 || col < 0 || col > 3) { text_color_set (DW_COLOR_ERROR); dw_printf ("Callsign 6+4 to text: INTERNAL ERROR %d %d. Should not be here.\n", row, col); errors++; row = 0; col = 1; } if (call10encoding[row][col] != 0) { *t++ = call10encoding[row][col]; *t = '\0'; } else { errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Callsign 6+4 to text: Invalid combination: button %d, position %d.\n", row, col); } } } /* Trim any trailing spaces. */ k = strlen(text) - 1; /* should be 6 - 1 = 5 */ while (k >= 0 && text[k] == ' ') { text[k] = '\0'; k--; } return (errors); } /* end tt_call10_to_text */ /*------------------------------------------------------------------ * * Name: tt_call5_suffix_to_text * * Purpose: Convert the 5 digit APRStt 3 style callsign suffix * representation to text. * * Inputs: buttons - Input string. * Should contain exactly 5 digits. * * quiet - True to suppress error messages. * * Outputs: text - Converted to 3 upper case letters and/or digits. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_call5_suffix_to_text (const char *buttons, int quiet, char *text) { const char *b; char *t; char c; int packed; /* from last 4 digits */ int row, col; int errors = 0; int k; t = text; *t = '\0'; /* result */ /* Validity check. */ if (strlen(buttons) != 5) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Callsign 3+2 suffix to text: Encoded Callsign \"%s\" must be exactly 5 digits.\n", buttons); } errors++; return (errors); } for (b = buttons; *b != '\0'; b++) { if (! isdigit(*b)) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Callsign 3+2 suffix to text: Encoded Callsign \"%s\" can contain only digits.\n", buttons); } errors++; return (errors); } } packed = atoi(buttons+3); for (k = 0; k < 3; k++) { c = buttons[k]; row = c - '0'; col = (packed >> ((2 - k) * 2)) & 3; if (row < 0 || row > 9 || col < 0 || col > 3) { text_color_set (DW_COLOR_ERROR); dw_printf ("Callsign 3+2 suffix to text: INTERNAL ERROR %d %d. Should not be here.\n", row, col); errors++; row = 0; col = 1; } if (call10encoding[row][col] != 0) { *t++ = call10encoding[row][col]; *t = '\0'; } else { errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Callsign 3+2 suffix to text: Invalid combination: button %d, position %d.\n", row, col); } } } if (errors > 0) { strcpy (text, ""); return (errors); } return (errors); } /* end tt_call5_suffix_to_text */ /*------------------------------------------------------------------ * * Name: tt_mhead_to_text * * Purpose: Convert the DTMF representation of * Maidenhead Grid Square Locator to normal text representation. * * Inputs: buttons - Input string. * Must contain 4, 6, 10, or 12, 16, or 18 digits. * * quiet - True to suppress error messages. * * Outputs: text - Converted to gridsquare with upper case letters and digits. * Length should be 2, 4, 6, or 8 with alternating letter or digit pairs. * Zero length if any error. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ #define MAXMHPAIRS 6 static const struct { char *position; char min_ch; char max_ch; } mhpair[MAXMHPAIRS] = { { "first", 'A', 'R' }, { "second", '0', '9' }, { "third", 'A', 'X' }, { "fourth", '0', '9' }, { "fifth", 'A', 'X' }, { "sixth", '0', '9' } }; int tt_mhead_to_text (const char *buttons, int quiet, char *text, size_t textsiz) { const char *b; int errors = 0; strlcpy (text, "", textsiz); /* Validity check. */ if (strlen(buttons) != 4 && strlen(buttons) != 6 && strlen(buttons) != 10 && strlen(buttons) != 12 && strlen(buttons) != 16 && strlen(buttons) != 18) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("DTMF to Maidenhead Gridsquare Locator: Input \"%s\" must be exactly 4, 6, 10, or 12 digits.\n", buttons); } errors++; strlcpy (text, "", textsiz); return (errors); } for (b = buttons; *b != '\0'; b++) { if (! isdigit(*b)) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("DTMF to Maidenhead Gridsquare Locator: Input \"%s\" can contain only digits.\n", buttons); } errors++; strlcpy (text, "", textsiz); return (errors); } } /* Convert DTMF to normal representation. */ b = buttons; int n; for (n = 0; n < 6 && b < buttons+strlen(buttons); n++) { if ((n % 2) == 0) { /* Convert pairs of digits to letter. */ char t2[2]; errors += tt_two_digits_to_letter (b, quiet, t2, sizeof(t2)); strlcat (text, t2, textsiz); b += 2; errors += tt_two_digits_to_letter (b, quiet, t2, sizeof(t2)); strlcat (text, t2, textsiz); b += 2; } else { /* Copy the digits. */ char d3[3]; d3[0] = *b++; d3[1] = *b++; d3[2] = '\0'; strlcat (text, d3, textsiz); } } if (errors != 0) { strlcpy (text, "", textsiz); } return (errors); } /* end tt_mhead_to_text */ /*------------------------------------------------------------------ * * Name: tt_text_to_mhead * * Purpose: Convert normal text Maidenhead Grid Square Locator to DTMF representation. * * Inputs: text - Maidenhead Grid Square locator in usual format. * Length should be 1 to 6 pairs with alternating letter or digit pairs. * * quiet - True to suppress error messages. * * buttonsize - space available for 'buttons' result. * * Outputs: buttons - Result with 4, 6, 10, 12, 16, 18 digits. * Each letter is replaced by two digits. * Digits are simply copied. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_text_to_mhead (const char *text, int quiet, char *buttons, size_t buttonsize) { int errors = 0; int np, i; strlcpy (buttons, "", buttonsize); np = strlen(text) / 2; if ((strlen(text) % 2) != 0) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Maidenhead Gridsquare Locator to DTMF: Input \"%s\" must be even number of characters.\n", text); } errors++; return (errors); } if (np < 1 || np > MAXMHPAIRS) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("Maidenhead Gridsquare Locator to DTMF: Input \"%s\" must be 1 to %d pairs of characters.\n", text, np); } errors++; return (errors); } for (i = 0; i < np; i++) { char t0 = text[i*2]; char t1 = text[i*2+1]; if (toupper(t0) < mhpair[i].min_ch || toupper(t0) > mhpair[i].max_ch || toupper(t1) < mhpair[i].min_ch || toupper(t1) > mhpair[i].max_ch) { if (! quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("The %s pair of characters in Maidenhead locator \"%s\" must be in range of %c thru %c.\n", mhpair[i].position, text, mhpair[i].min_ch, mhpair[i].max_ch); } strlcpy (buttons, "", buttonsize); errors++; return(errors); } if (mhpair[i].min_ch == 'A') { /* Should be letters */ char b3[3]; errors += tt_letter_to_two_digits (t0, quiet, b3); strlcat (buttons, b3, buttonsize); errors += tt_letter_to_two_digits (t1, quiet, b3); strlcat (buttons, b3, buttonsize); } else { /* Should be digits */ char b3[3]; b3[0] = t0; b3[1] = t1; b3[2] = '\0'; strlcat (buttons, b3, buttonsize); } } if (errors != 0) strlcpy (buttons, "", buttonsize); return (errors); } /* tt_text_to_mhead */ /*------------------------------------------------------------------ * * Name: tt_satsq_to_text * * Purpose: Convert the 4 digit DTMF special Satellite gridsquare to normal 2 letters and 2 digits. * * Inputs: buttons - Input string. * Should contain 4 digits. * * quiet - True to suppress error messages. * * Outputs: text - Converted to gridsquare with upper case letters and digits. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_satsq_to_text (const char *buttons, int quiet, char *text) { const char *b; int row, col; int errors = 0; strcpy (text, ""); /* Validity check. */ if (strlen(buttons) != 4) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("DTMF to Satellite Gridsquare: Input \"%s\" must be exactly 4 digits.\n", buttons); } errors++; return (errors); } for (b = buttons; *b != '\0'; b++) { if (! isdigit(*b)) { if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("DTMF to Satellite Gridsquare: Input \"%s\" can contain only digits.\n", buttons); } errors++; return (errors); } } row = buttons[0] - '0'; col = buttons[1] - '0'; strcpy (text, grid[row][col]); strcat (text, buttons+2); return (errors); } /* end tt_satsq_to_text */ /*------------------------------------------------------------------ * * Name: tt_ascii2d_to_text * * Purpose: Convert the two digit ascii representation back to normal text. * * Inputs: buttons - Input string. * Should contain pairs of digits in range 00 to 94. * * quiet - True to suppress error messages. * * Outputs: text - Converted to any printable ascii characters. * * Returns: Number of errors detected. * *----------------------------------------------------------------*/ int tt_ascii2d_to_text (const char *buttons, int quiet, char *text) { const char *b = buttons; char *t = text; char c1, c2; int errors = 0; *t = '\0'; while (*b != '\0') { c1 = *b++; if (*b != '\0') { c2 = *b++; } else { c2 = ' '; } if (isdigit(c1) && isdigit(c2)) { int n; n = (c1 - '0') * 10 + (c2 - '0'); *t++ = n + 32; *t = '\0'; } else { /* Unexpected character. */ errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); dw_printf ("ASCII2D to text: Invalid character pair \"%c%c\".\n", c1, c2); } } } return (errors); } /* end tt_ascii2d_to_text */ /*------------------------------------------------------------------ * * Name: tt_guess_type * * Purpose: Try to guess which encoding we have. * * Inputs: buttons - Input string. * Should contain only 0123456789ABCD. * * Returns: TT_MULTIPRESS - Looks like multipress. * TT_TWO_KEY - Looks like two key. * TT_EITHER - Could be either one. * *----------------------------------------------------------------*/ typedef enum { TT_EITHER, TT_MULTIPRESS, TT_TWO_KEY } tt_enc_t; tt_enc_t tt_guess_type (char *buttons) { char text[256]; int err_mp; int err_tk; /* If it contains B, C, or D, it can't be multipress. */ if (strchr (buttons, 'B') != NULL || strchr (buttons, 'b') != NULL || strchr (buttons, 'C') != NULL || strchr (buttons, 'c') != NULL || strchr (buttons, 'D') != NULL || strchr (buttons, 'd') != NULL) { return (TT_TWO_KEY); } /* Try parsing quietly and see if one gets errors and the other doesn't. */ err_mp = tt_multipress_to_text (buttons, 1, text); err_tk = tt_two_key_to_text (buttons, 1, text); if (err_mp == 0 && err_tk > 0) { return (TT_MULTIPRESS); } else if (err_tk == 0 && err_mp > 0) { return (TT_TWO_KEY); } /* Could be either one. */ return (TT_EITHER); } /* end tt_guess_type */ /*------------------------------------------------------------------ * * Name: main * * Purpose: Utility program for testing the encoding. * *----------------------------------------------------------------*/ #if ENC_MAIN int checksum (char *tt) { int cs = 10; /* Assume leading 'A'. */ /* Doesn't matter due to mod 10 at the end. */ char *p; for (p = tt; *p != '\0'; p++) { if (isdigit(*p)) { cs += *p - '0'; } else if (isupper(*p)) { cs += *p - 'A' + 10; } else if (islower(*p)) { cs += *p - 'a' + 10; } } return (cs % 10); } int main (int argc, char *argv[]) { char text[1000], buttons[2000]; int n; int cs; text_color_set (DW_COLOR_INFO); if (argc < 2) { text_color_set (DW_COLOR_ERROR); dw_printf ("Supply text string on command line.\n"); exit (1); } strcpy (text, argv[1]); for (n = 2; n < argc; n++) { strcat (text, " "); strcat (text, argv[n]); } dw_printf ("Push buttons for multi-press method:\n"); n = tt_text_to_multipress (text, 0, buttons); cs = checksum (buttons); dw_printf ("\"%s\" checksum for call = %d\n", buttons, cs); dw_printf ("Push buttons for two-key method:\n"); n = tt_text_to_two_key (text, 0, buttons); cs = checksum (buttons); dw_printf ("\"%s\" checksum for call = %d\n", buttons, cs); n = tt_text_to_call10 (text, 1, buttons); if (n == 0) { dw_printf ("Push buttons for fixed length 10 digit callsign:\n"); dw_printf ("\"%s\"\n", buttons); } n = tt_text_to_mhead (text, 1, buttons, sizeof(buttons)); if (n == 0) { dw_printf ("Push buttons for Maidenhead Grid Square Locator:\n"); dw_printf ("\"%s\"\n", buttons); } n = tt_text_to_satsq (text, 1, buttons, sizeof(buttons)); if (n == 0) { dw_printf ("Push buttons for satellite gridsquare:\n"); dw_printf ("\"%s\"\n", buttons); } return(0); } /* end main */ #endif /* encoding */ /*------------------------------------------------------------------ * * Name: main * * Purpose: Utility program for testing the decoding. * *----------------------------------------------------------------*/ #if DEC_MAIN int main (int argc, char *argv[]) { char buttons[2000], text[1000]; int n; text_color_set (DW_COLOR_INFO); if (argc < 2) { text_color_set (DW_COLOR_ERROR); dw_printf ("Supply button sequence on command line.\n"); exit (1); } strcpy (buttons, argv[1]); for (n = 2; n < argc; n++) { strlcat (buttons, argv[n], sizeof(buttons)); } switch (tt_guess_type(buttons)) { case TT_MULTIPRESS: dw_printf ("Looks like multi-press encoding.\n"); break; case TT_TWO_KEY: dw_printf ("Looks like two-key encoding.\n"); break; default: dw_printf ("Could be either type of encoding.\n"); break; } dw_printf ("Decoded text from multi-press method:\n"); n = tt_multipress_to_text (buttons, 0, text); dw_printf ("\"%s\"\n", text); dw_printf ("Decoded text from two-key method:\n"); n = tt_two_key_to_text (buttons, 0, text); dw_printf ("\"%s\"\n", text); n = tt_call10_to_text (buttons, 1, text); if (n == 0) { dw_printf ("Decoded callsign from 10 digit method:\n"); dw_printf ("\"%s\"\n", text); } n = tt_mhead_to_text (buttons, 1, text, sizeof(text)); if (n == 0) { dw_printf ("Decoded Maidenhead Locator from DTMF digits:\n"); dw_printf ("\"%s\"\n", text); } n = tt_satsq_to_text (buttons, 1, text); if (n == 0) { dw_printf ("Decoded satellite gridsquare from 4 DTMF digits:\n"); dw_printf ("\"%s\"\n", text); } return(0); } /* end main */ #endif /* decoding */ #if TTT_TEST /* gcc -g -DTTT_TEST tt_text.c textcolor.o misc.a && ./a.exe */ /* Quick unit test. */ static int error_count; static void test_text2tt (char *text, char *expect_mp, char *expect_2k, char *expect_c10, char *expect_loc, char *expect_sat) { char buttons[100]; text_color_set(DW_COLOR_INFO); dw_printf ("\nConvert from text \"%s\" to tone sequence.\n", text); tt_text_to_multipress (text, 0, buttons); if (strcmp(buttons, expect_mp) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected multi-press \"%s\" but got \"%s\"\n", expect_mp, buttons); } tt_text_to_two_key (text, 0, buttons); if (strcmp(buttons, expect_2k) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected two-key \"%s\" but got \"%s\"\n", expect_2k, buttons); } tt_text_to_call10 (text, 0, buttons); if (strcmp(buttons, expect_c10) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected call 6+4 \"%s\" but got \"%s\"\n", expect_c10, buttons); } tt_text_to_mhead (text, 0, buttons, sizeof(buttons)); if (strcmp(buttons, expect_loc) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected Maidenhead \"%s\" but got \"%s\"\n", expect_loc, buttons); } tt_text_to_satsq (text, 0, buttons, sizeof(buttons)); if (strcmp(buttons, expect_sat) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected Sat Sq \"%s\" but got \"%s\"\n", expect_sat, buttons); } } static void test_tt2text (char *buttons, char *expect_mp, char *expect_2k, char *expect_c10, char *expect_loc, char *expect_sat) { char text[100]; text_color_set(DW_COLOR_INFO); dw_printf ("\nConvert tone sequence \"%s\" to text.\n", buttons); tt_multipress_to_text (buttons, 0, text); if (strcmp(text, expect_mp) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected multi-press \"%s\" but got \"%s\"\n", expect_mp, text); } tt_two_key_to_text (buttons, 0, text); if (strcmp(text, expect_2k) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected two-key \"%s\" but got \"%s\"\n", expect_2k, text); } tt_call10_to_text (buttons, 0, text); if (strcmp(text, expect_c10) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected call 6+4 \"%s\" but got \"%s\"\n", expect_c10, text); } tt_mhead_to_text (buttons, 0, text, sizeof(text)); if (strcmp(text, expect_loc) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected Maidenhead \"%s\" but got \"%s\"\n", expect_loc, text); } tt_satsq_to_text (buttons, 0, text); if (strcmp(text, expect_sat) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected Sat Sq \"%s\" but got \"%s\"\n", expect_sat, text); } } int main (int argc, char *argv[]) { text_color_set (DW_COLOR_INFO); dw_printf ("Test conversions between normal text and DTMF representation.\n"); dw_printf ("Some error messages are normal. Just look for number of errors at end.\n"); error_count = 0; /* original text multipress two-key call10 mhead satsq */ test_text2tt ("abcdefg 0123", "2A22A2223A33A33340A00122223333", "2A2B2C3A3B3C4A0A0123", "", "", ""); test_text2tt ("WB4APR", "922444427A777", "9A2B42A7A7C", "9242771558", "", ""); test_text2tt ("EM29QE78", "3362222999997733777778888", "3B6A297B3B78", "", "326129723278", ""); test_text2tt ("FM19", "3336199999", "3C6A19", "3619003333", "336119", "1819"); /* tone_seq multipress two-key call10 mhead satsq */ test_tt2text ("2A22A2223A33A33340A00122223333", "ABCDEFG 0123", "A2A222D3D3334 00122223333", "", "", ""); test_tt2text ("9242771558", "WAGAQ1KT", "9242771558", "WB4APR", "", ""); test_tt2text ("326129723278", "DAM1AWPADAPT", "326129723278", "", "EM29QE78", ""); test_tt2text ("1819", "1T1W", "1819", "", "", "FM19"); if (error_count > 0) { text_color_set (DW_COLOR_ERROR); dw_printf ("\nERROR: %d tests failed.\n", error_count); exit (EXIT_FAILURE); } text_color_set (DW_COLOR_REC); dw_printf ("\nSUCCESS! All tests passed.\n"); exit (EXIT_SUCCESS); } /* end main */ #endif /* end tt_text.c */ direwolf-1.5+dfsg/tt_text.h000066400000000000000000000021321347750676600160000ustar00rootroot00000000000000 /* tt_text.h */ /* Encode normal human readable to DTMF representation. */ int tt_text_to_multipress (const char *text, int quiet, char *buttons); int tt_text_to_two_key (const char *text, int quiet, char *buttons); int tt_text_to_call10 (const char *text, int quiet, char *buttons); int tt_text_to_mhead (const char *text, int quiet, char *buttons, size_t buttonsiz); int tt_text_to_satsq (const char *text, int quiet, char *buttons, size_t buttonsiz); int tt_text_to_ascii2d (const char *text, int quiet, char *buttons); /* Decode DTMF to normal human readable form. */ int tt_multipress_to_text (const char *buttons, int quiet, char *text); int tt_two_key_to_text (const char *buttons, int quiet, char *text); int tt_call10_to_text (const char *buttons, int quiet, char *text); int tt_call5_suffix_to_text (const char *buttons, int quiet, char *text); int tt_mhead_to_text (const char *buttons, int quiet, char *text, size_t textsiz); int tt_satsq_to_text (const char *buttons, int quiet, char *text); int tt_ascii2d_to_text (const char *buttons, int quiet, char *text); /* end tt_text.h */direwolf-1.5+dfsg/tt_user.c000066400000000000000000000753741347750676600160070ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: tt-user.c * * Purpose: Keep track of the APRStt users. * * Description: This maintains a list of recently heard APRStt users * and prepares "object" format packets for transmission. * * References: This is based upon APRStt (TM) documents but not 100% * compliant due to ambiguities and inconsistencies in * the specifications. * * http://www.aprs.org/aprstt.html * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include "version.h" #include "ax25_pad.h" #include "textcolor.h" #include "aprs_tt.h" #include "tt_text.h" #include "dedupe.h" #include "tq.h" #include "igate.h" #include "tt_user.h" #include "encode_aprs.h" #include "latlong.h" #include "server.h" #include "kiss.h" #include "kissserial.h" #include "kissnet.h" #include "kiss_frame.h" /* * Information kept about local APRStt users. * * For now, just use a fixed size array for simplicity. */ #if TT_MAIN #define MAX_TT_USERS 3 #else #define MAX_TT_USERS 100 #endif #define MAX_CALLSIGN_LEN 9 /* "Object Report" names can be up to 9 characters. */ #define MAX_COMMENT_LEN 43 /* Max length of comment in "Object Report." */ //#define G_UNKNOWN -999999 /* Should be in one place. */ #define NUM_XMITS 3 #define XMIT_DELAY_1 5 #define XMIT_DELAY_2 8 #define XMIT_DELAY_3 13 static struct tt_user_s { char callsign[MAX_CALLSIGN_LEN+1]; /* Callsign of station heard. */ /* Does not include the "-12" SSID added later. */ /* Possibly other tactical call / object label. */ /* Null string indicates table position is not used. */ int count; /* Number of times we received information for this object. */ /* Value 1 means first time and could be used to send */ /* a welcome greeting. */ int ssid; /* SSID to add. */ /* Default of 12 but not always. */ char overlay; /* Overlay character. Should be 0-9, A-Z. */ /* Could be / or \ for general object. */ char symbol; /* 'A' for traditional. */ /* Can be any symbol for extended objects. */ char digit_suffix[3+1]; /* Suffix abbreviation as 3 digits. */ time_t last_heard; /* Timestamp when last heard. */ /* User information will be deleted at some */ /* point after last time being heard. */ int xmits; /* Number of remaining times to transmit info */ /* about the user. This is set to 3 when */ /* a station is heard and decremented each time */ /* an object packet is sent. The idea is to send */ /* 3 within 30 seconds to improve chances of */ /* being heard while using digipeater duplicate */ /* removal. */ // TODO: I think implementation is different. time_t next_xmit; /* Time for next transmit. Meaningful only */ /* if xmits > 0. */ int corral_slot; /* If location is known, set this to 0. */ /* Otherwise, this is a display offset position */ /* from the gateway. */ char loc_text[24]; /* Text representation of location when a single */ /* lat/lon point would be deceptive. e.g. */ /* 32TPP8049 */ /* 32TPP8179549363 */ /* 32T 681795 4849363 */ /* EM29QE78 */ double latitude, longitude; /* Location either from user or generated */ /* position in the corral. */ int ambiguity; /* Number of digits to omit from location. */ /* Default 0, max. 4. */ char freq[12]; /* Frequency in format 999.999MHz */ char ctcss[5]; /* CTCSS tone. Exactly 3 digits for integer part. */ /* For example 74.4 Hz becomes "074". */ char comment[MAX_COMMENT_LEN+1]; /* Free form comment from user. */ /* Comment sent in final object report includes */ /* other information besides this. */ char mic_e; /* Position status. */ /* Should be a character in range of '1' to '9' for */ /* the predefined status strings or '0' for none. */ char dao[8]; /* Enhanced position information. */ } tt_user[MAX_TT_USERS]; static void clear_user(int i); static void xmit_object_report (int i, int first_time); static void tt_setenv (int i); #if __WIN32__ // setenv is missing on Windows! int setenv(const char *name, const char *value, int overwrite) { char etemp[1000]; snprintf (etemp, sizeof(etemp), "%s=%s", name, value); putenv (etemp); return (0); } #endif /*------------------------------------------------------------------ * * Name: tt_user_init * * Purpose: Initialize the APRStt gateway at system startup time. * * Inputs: Configuration options gathered by config.c. * * Global out: Make our own local copy of the structure here. * * Returns: None * * Description: The main program needs to call this at application * start up time after reading the configuration file. * * TT_MAIN is defined for unit testing. * *----------------------------------------------------------------*/ static struct audio_s *save_audio_config_p; static struct tt_config_s *save_tt_config_p; void tt_user_init (struct audio_s *p_audio_config, struct tt_config_s *p_tt_config) { int i; save_audio_config_p = p_audio_config; save_tt_config_p = p_tt_config; for (i=0; i= 0) or -1 if not found. * This happens to be an index into an array but * the implementation could change so the caller should * not make any assumptions. * *----------------------------------------------------------------*/ int tt_3char_suffix_search (char *suffix, char *callsign) { int i; /* * Look for suffix in list of known calls. */ for (i=0; i= 3 && len <= 6 && strcmp(tt_user[i].callsign + len - 3, suffix) == 0) { strlcpy (callsign, tt_user[i].callsign, MAX_CALLSIGN_LEN+1); return (i); } } /* * Not found. */ strlcpy (callsign, "", MAX_CALLSIGN_LEN+1); return (-1); } /* end tt_3char_suffix_search */ /*------------------------------------------------------------------ * * Name: clear_user * * Purpose: Clear specified user table entry. * * Inputs: handle for user table entry. * *----------------------------------------------------------------*/ static void clear_user(int i) { assert (i >= 0 && i < MAX_TT_USERS); memset (&(tt_user[i]), 0, sizeof (struct tt_user_s)); } /* end clear_user */ /*------------------------------------------------------------------ * * Name: find_avail * * Purpose: Find an available user table location. * * Inputs: none * * Returns: Handle for refering to table position. * * Description: If table is already full, this should delete the * least recently heard user to make room. * *----------------------------------------------------------------*/ static int find_avail (void) { int i; int i_oldest; for (i=0; i= 1 not already in use. * *----------------------------------------------------------------*/ static int corral_slot (void) { int slot, i, used; for (slot=1; ; slot++) { used = 0;; for (i=0; i= 0 && i < MAX_TT_USERS); strlcpy (tt_user[i].callsign, callsign, sizeof(tt_user[i].callsign)); tt_user[i].count = 1; tt_user[i].ssid = ssid; tt_user[i].overlay = overlay; tt_user[i].symbol = symbol; digit_suffix(tt_user[i].callsign, tt_user[i].digit_suffix); strlcpy (tt_user[i].loc_text, loc_text, sizeof(tt_user[i].loc_text)); if (latitude != G_UNKNOWN && longitude != G_UNKNOWN) { /* We have specific location. */ tt_user[i].corral_slot = 0; tt_user[i].latitude = latitude; tt_user[i].longitude = longitude; } else { /* Unknown location, put it in the corral. */ tt_user[i].corral_slot = corral_slot(); } tt_user[i].ambiguity = ambiguity; strlcpy (tt_user[i].freq, freq, sizeof(tt_user[i].freq)); strlcpy (tt_user[i].ctcss, ctcss, sizeof(tt_user[i].ctcss)); strlcpy (tt_user[i].comment, comment, sizeof(tt_user[i].comment)); tt_user[i].mic_e = mic_e; strlcpy(tt_user[i].dao, dao, sizeof(tt_user[i].dao)); } else { /* * Known user. Update with any new information. * Keep any old values where not being updated. */ assert (i >= 0 && i < MAX_TT_USERS); tt_user[i].count++; /* Any reason to look at ssid here? */ /* Update the symbol if not the default. */ if (overlay != APRSTT_DEFAULT_SYMTAB || symbol != APRSTT_DEFAULT_SYMBOL) { tt_user[i].overlay = overlay; tt_user[i].symbol = symbol; } if (strlen(loc_text) > 0) { strlcpy (tt_user[i].loc_text, loc_text, sizeof(tt_user[i].loc_text)); } if (latitude != G_UNKNOWN && longitude != G_UNKNOWN) { /* We have specific location. */ tt_user[i].corral_slot = 0; tt_user[i].latitude = latitude; tt_user[i].longitude = longitude; } if (ambiguity != G_UNKNOWN) { tt_user[i].ambiguity = ambiguity; } if (freq[0] != '\0') { strlcpy (tt_user[i].freq, freq, sizeof(tt_user[i].freq)); } if (ctcss[0] != '\0') { strlcpy (tt_user[i].ctcss, ctcss, sizeof(tt_user[i].ctcss)); } if (comment[0] != '\0') { strlcpy (tt_user[i].comment, comment, MAX_COMMENT_LEN); tt_user[i].comment[MAX_COMMENT_LEN] = '\0'; } if (mic_e != ' ') { tt_user[i].mic_e = mic_e; } if (strlen(dao) > 0) { strlcpy(tt_user[i].dao, dao, sizeof(tt_user[i].dao)); } } /* * In both cases, note last time heard and schedule object report transmission. */ tt_user[i].last_heard = time(NULL); tt_user[i].xmits = 0; tt_user[i].next_xmit = tt_user[i].last_heard + save_tt_config_p->xmit_delay[0]; /* * Send to applications and IGate immediately. */ xmit_object_report (i, 1); /* * Put properties into environment variables in preparation * for calling a user-specified script. */ tt_setenv (i); return (0); /* Success! */ } /* end tt_user_heard */ /*------------------------------------------------------------------ * * Name: tt_user_background * * Purpose: * * Inputs: * * Outputs: Append to transmit queue. * * Returns: None * * Description: ...... TBD * *----------------------------------------------------------------*/ void tt_user_background (void) { time_t now = time(NULL); int i; //text_color_set(DW_COLOR_DEBUG); //dw_printf ("tt_user_background() now = %d\n", (int)now); for (i=0; i= 0 && i < MAX_TT_USERS); if (tt_user[i].callsign[0] != '\0') { if (tt_user[i].xmits < save_tt_config_p->num_xmits && tt_user[i].next_xmit <= now) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("tt_user_background() now = %d\n", (int)now); //tt_user_dump (); xmit_object_report (i, 0); /* Increase count of number times this one was sent. */ tt_user[i].xmits++; if (tt_user[i].xmits < save_tt_config_p->num_xmits) { /* Schedule next one. */ tt_user[i].next_xmit += save_tt_config_p->xmit_delay[tt_user[i].xmits]; } //tt_user_dump (); } } } /* * Purge if too old. */ for (i=0; iretain_time < now) { //dw_printf ("debug: purging expired user %d\n", i); clear_user (i); } } } } /*------------------------------------------------------------------ * * Name: xmit_object_report * * Purpose: Create object report packet and put into transmit queue. * * Inputs: i - Index into user table. * * first_time - Is this being called immediately after the tone sequence * was received or after some delay? * For the former, we send to any attached applications * and the IGate. * For the latter, we transmit over radio. * * Outputs: Append to transmit queue. * * Returns: None * * Description: Details for specified user are converted to * "Object Report Format" and added to the transmit queue. * * If the user did not report a position, we have to make * up something so the corresponding object will show up on * the map or other list of nearby stations. * * The traditional approach is to put them in different * positions in the "corral" by applying increments of an * offset from the starting position. This has two * unfortunate properties. It gives the illusion we know * where the person is located. Being in the ,,, * *----------------------------------------------------------------*/ static void xmit_object_report (int i, int first_time) { char object_name[20]; // xxxxxxxxx or xxxxxx-nn char info_comment[200]; // usercomment [locationtext] /status !DAO! char object_info[250]; // info part of Object Report packet char stemp[300]; // src>dest,path:object_info double olat, olong; int oambig; // Position ambiguity. packet_t pp; char c4[4]; //text_color_set(DW_COLOR_DEBUG); //printf ("xmit_object_report (index = %d, first_time = %d) rx = %d, tx = %d\n", i, first_time, // save_tt_config_p->obj_recv_chan, save_tt_config_p->obj_xmit_chan); assert (i >= 0 && i < MAX_TT_USERS); /* * Prepare the object name. * Tack on "-12" if it is a callsign. */ strlcpy (object_name, tt_user[i].callsign, sizeof(object_name)); if (strlen(object_name) <= 6 && tt_user[i].ssid != 0) { char stemp8[8]; snprintf (stemp8, sizeof(stemp8), "-%d", tt_user[i].ssid); strlcat (object_name, stemp8, sizeof(object_name)); } if (tt_user[i].corral_slot == 0) { /* * Known location. */ olat = tt_user[i].latitude; olong = tt_user[i].longitude; oambig = tt_user[i].ambiguity; if (oambig == G_UNKNOWN) oambig = 0; } else { /* * Use made up position in the corral. */ double c_lat = save_tt_config_p->corral_lat; // Corral latitude. double c_long = save_tt_config_p->corral_lon; // Corral longitude. double c_offs = save_tt_config_p->corral_offset; // Corral (latitude) offset. olat = c_lat - (tt_user[i].corral_slot - 1) * c_offs; olong = c_long; oambig = 0; } /* * Build comment field from various information. * * usercomment [locationtext] /status !DAO! * * Any frequency is inserted at beginning later. */ strlcpy (info_comment, "", sizeof(info_comment)); if (strlen(tt_user[i].comment) != 0) { strlcat (info_comment, tt_user[i].comment, sizeof(info_comment)); } if (strlen(tt_user[i].loc_text) > 0) { if (strlen(info_comment) > 0) { strlcat (info_comment, " ", sizeof(info_comment)); } strlcat (info_comment, "[", sizeof(info_comment)); strlcat (info_comment, tt_user[i].loc_text, sizeof(info_comment)); strlcat (info_comment, "]", sizeof(info_comment)); } if (tt_user[i].mic_e >= '1' && tt_user[i].mic_e <= '9') { if (strlen(info_comment) > 0) { strlcat (info_comment, " ", sizeof(info_comment)); } // Insert "/" if status does not already begin with it. if (save_tt_config_p->status[tt_user[i].mic_e - '0'][0] != '/') { strlcat (info_comment, "/", sizeof(info_comment)); } strlcat (info_comment, save_tt_config_p->status[tt_user[i].mic_e - '0'], sizeof(info_comment)); } if (strlen(tt_user[i].dao) > 0) { if (strlen(info_comment) > 0) { strlcat (info_comment, " ", sizeof(info_comment)); } strlcat (info_comment, tt_user[i].dao, sizeof(info_comment)); } /* Official limit is 43 characters. */ //info_comment[MAX_COMMENT_LEN] = '\0'; /* * Packet header is built from mycall (of transmit channel) and software version. */ if (save_tt_config_p->obj_xmit_chan >= 0) { strlcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_xmit_chan].mycall, sizeof(stemp)); } else { strlcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_recv_chan].mycall, sizeof(stemp)); } strlcat (stemp, ">", sizeof(stemp)); strlcat (stemp, APP_TOCALL, sizeof(stemp)); c4[0] = '0' + MAJOR_VERSION; c4[1] = '0' + MINOR_VERSION; c4[2] = '\0'; strlcat (stemp, c4, sizeof(stemp)); /* * Append via path, for transmission, if specified. */ if ( ! first_time && save_tt_config_p->obj_xmit_via[0] != '\0') { strlcat (stemp, ",", sizeof(stemp)); strlcat (stemp, save_tt_config_p->obj_xmit_via, sizeof(stemp)); } strlcat (stemp, ":", sizeof(stemp)); encode_object (object_name, 0, tt_user[i].last_heard, olat, olong, oambig, tt_user[i].overlay, tt_user[i].symbol, 0,0,0,NULL, G_UNKNOWN, G_UNKNOWN, /* PHGD, Course/Speed */ strlen(tt_user[i].freq) > 0 ? atof(tt_user[i].freq) : G_UNKNOWN, strlen(tt_user[i].ctcss) > 0 ? atof(tt_user[i].ctcss) : G_UNKNOWN, G_UNKNOWN, /* CTCSS */ info_comment, object_info, sizeof(object_info)); strlcat (stemp, object_info, sizeof(stemp)); #if TT_MAIN printf ("---> %s\n\n", stemp); #else if (first_time) { text_color_set(DW_COLOR_DEBUG); dw_printf ("[APRStt] %s\n", stemp); } /* * Convert text to packet. */ pp = ax25_from_text (stemp, 1); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("\"%s\"\n", stemp); return; } /* * Send to one or more of the following depending on configuration: * Transmit queue. * Any attached application(s). * IGate. * * When transmitting over the radio, it gets sent multipe times, to help * probablity of being heard, with increasing delays between. * * The other methods are reliable so we only want to send it once. */ if (first_time && save_tt_config_p->obj_send_to_app) { unsigned char fbuf[AX25_MAX_PACKET_LEN]; int flen; // TODO1.3: Put a wrapper around this so we only call one function to send by all methods. // We see the same sequence in direwolf.c. flen = ax25_pack(pp, fbuf); server_send_rec_packet (save_tt_config_p->obj_recv_chan, pp, fbuf, flen); kissnet_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); kissserial_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); kisspt_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); } if (first_time && save_tt_config_p->obj_send_to_ig) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("xmit_object_report (): send to IGate\n"); igate_send_rec_packet (save_tt_config_p->obj_recv_chan, pp); } if ( ! first_time && save_tt_config_p->obj_xmit_chan >= 0) { /* Remember it so we don't digipeat our own. */ dedupe_remember (pp, save_tt_config_p->obj_xmit_chan); tq_append (save_tt_config_p->obj_xmit_chan, TQ_PRIO_1_LO, pp); } else { ax25_delete (pp); } #endif } static const char *letters[26] = { "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India", "Juliet", "Kilo", "Lima", "Mike", "November", "Oscar", "Papa", "Quebec", "Romeo", "Sierra", "Tango", "Uniform", "Victor", "Whiskey", "X-ray", "Yankee", "Zulu" }; static const char *digits[10] = { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" }; /*------------------------------------------------------------------ * * Name: tt_setenv * * Purpose: Put information in environment variables in preparation * for calling a user-supplied script for custom processing. * * Inputs: i - Index into tt_user table. * * Description: Timestamps displayed relative to current time. * *----------------------------------------------------------------*/ static void tt_setenv (int i) { char stemp[256]; char t2[2]; char *p; assert (i >= 0 && i < MAX_TT_USERS); setenv ("TTCALL", tt_user[i].callsign, 1); strlcpy (stemp, "", sizeof(stemp)); t2[1] = '\0'; for (p = tt_user[i].callsign; *p != '\0'; p++) { t2[0] = *p; strlcat (stemp, t2, sizeof(stemp)); if (p[1] != '\0') strlcat (stemp, " ", sizeof(stemp)); } setenv ("TTCALLSP", stemp, 1); strlcpy (stemp, "", sizeof(stemp)); for (p = tt_user[i].callsign; *p != '\0'; p++) { if (isupper(*p)) { strlcat (stemp, letters[*p - 'A'], sizeof(stemp)); } else if (islower(*p)) { strlcat (stemp, letters[*p - 'a'], sizeof(stemp)); } else if (isdigit(*p)) { strlcat (stemp, digits[*p - '0'], sizeof(stemp)); } else { t2[0] = *p; strlcat (stemp, t2, sizeof(stemp)); } if (p[1] != '\0') strlcat (stemp, " ", sizeof(stemp)); } setenv ("TTCALLPH", stemp, 1); snprintf (stemp, sizeof(stemp), "%d", tt_user[i].ssid); setenv ("TTSSID",stemp , 1); snprintf (stemp, sizeof(stemp), "%d", tt_user[i].count); setenv ("TTCOUNT",stemp , 1); snprintf (stemp, sizeof(stemp), "%c%c", tt_user[i].overlay, tt_user[i].symbol); setenv ("TTSYMBOL",stemp , 1); snprintf (stemp, sizeof(stemp), "%.6f", tt_user[i].latitude); setenv ("TTLAT",stemp , 1); snprintf (stemp, sizeof(stemp), "%.6f", tt_user[i].longitude); setenv ("TTLON",stemp , 1); setenv ("TTFREQ", tt_user[i].freq, 1); // TODO: Should convert to actual frequency. e.g. 074 becomes 74.4 // There is some code for this in decode_aprs.c but not broken out // into a function that we could use from here. // TODO: Document this environment variable after converting. setenv ("TTCTCSS", tt_user[i].ctcss, 1); setenv ("TTCOMMENT", tt_user[i].comment, 1); setenv ("TTLOC", tt_user[i].loc_text, 1); if (tt_user[i].mic_e >= '1' && tt_user[i].mic_e <= '9') { setenv ("TTSTATUS", save_tt_config_p->status[tt_user[i].mic_e - '0'], 1); } else { setenv ("TTSTATUS", "", 1); } setenv ("TTDAO", tt_user[i].dao, 1); } /* end tt_setenv */ /*------------------------------------------------------------------ * * Name: tt_user_dump * * Purpose: Print information about known users for debugging. * * Inputs: None. * * Description: Timestamps displayed relative to current time. * *----------------------------------------------------------------*/ void tt_user_dump (void) { int i; time_t now = time(NULL); printf ("call ov suf lsthrd xmit nxt cor lat long freq ctcss m comment\n"); for (i=0; i. // /*------------------------------------------------------------------ * * Module: ttcalc.c * * Purpose: Simple Touch Tone to Speech calculator. * * Description: Demonstration of how Dire Wolf can be used * as a DTMF / Speech interface for ham radio applications. * * Usage: Start up direwolf with configuration: * - DTMF decoder enabled. * - Text-to-speech enabled. * - Listening to standard port 8000 for a client application. * * Run this in a different window. * * User sends formulas such as: * * 2 * 3 * 4 # * * with the touch tone pad. * The result is sent back with speech, e.g. "Twenty Four." * *---------------------------------------------------------------*/ #include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h #if __WIN32__ #include #include // _WIN32_WINNT must be set to 0x0501 before including this #else #include #include #include #include #include #include #include #include #include #endif #include #include #include #include #include #include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" struct agwpe_s { short portx; /* 0 for first, 1 for second, etc. */ short port_hi_reserved; short kind_lo; /* message type */ short kind_hi; char call_from[10]; char call_to[10]; int data_len; /* Number of data bytes following. */ int user_reserved; }; static int calculator (char *str); static int connect_to_server (char *hostname, char *port); static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize); /*------------------------------------------------------------------ * * Name: main * *---------------------------------------------------------------*/ int main (int argc, char *argv[]) { int server_sock = -1; struct agwpe_s mon_cmd; char data[1024]; char hostname[30] = "localhost"; char port[10] = "8000"; int err; #if __WIN32__ #else setlinebuf (stdout); #endif assert (calculator("12a34#") == 46); assert (calculator("2*3A4#") == 10); assert (calculator("5*100A3#") == 503); assert (calculator("6a4*5#") == 50); /* * Try to attach to Dire Wolf. */ server_sock = connect_to_server (hostname, port); if (server_sock == -1) { exit (1); } /* * Send command to toggle reception of frames in raw format. * * Note: Monitor format is only for UI frames. */ memset (&mon_cmd, 0, sizeof(mon_cmd)); mon_cmd.kind_lo = 'k'; err = SOCK_SEND (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd)); (void)err; /* * Print all of the monitored packets. */ while (1) { int n; n = SOCK_RECV (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd)); if (n != sizeof(mon_cmd)) { printf ("Read error, received %d command bytes.\n", n); exit (1); } assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < (int)(sizeof(data))); if (mon_cmd.data_len > 0) { n = SOCK_RECV (server_sock, data, mon_cmd.data_len); if (n != mon_cmd.data_len) { printf ("Read error, client received %d data bytes when %d expected. Terminating.\n", n, mon_cmd.data_len); exit (1); } } /* * Print it. */ if (mon_cmd.kind_lo == 'K') { packet_t pp; char *pinfo; int info_len; char result[400]; char *p; alevel_t alevel; int chan; chan = mon_cmd.portx; memset (&alevel, 0, sizeof(alevel)); pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, alevel); ax25_format_addrs (pp, result); info_len = ax25_get_info (pp, (unsigned char **)(&pinfo)); pinfo[info_len] = '\0'; strcat (result, pinfo); for (p=result; *p!='\0'; p++) { if (! isprint(*p)) *p = ' '; } printf ("[%d] %s\n", chan, result); /* * Look for Special touch tone packet with "t" in first position of the Information part. */ if (*pinfo == 't') { int n; char reply_text[200]; packet_t reply_pp; struct { struct agwpe_s hdr; char extra; unsigned char frame[AX25_MAX_PACKET_LEN]; } xmit_raw; /* * Send touch tone sequence to calculator and get the answer. * * Put your own application here instead. Here are some ideas: * * http://www.tapr.org/pipermail/aprssig/2015-January/044069.html */ n = calculator (pinfo+1); printf ("\nCalculator returns %d\n\n", n); /* * Convert to AX.25 frame. * Notice that the special destination will cause it to be spoken. */ snprintf (reply_text, sizeof(reply_text), "N0CALL>SPEECH:%d", n); reply_pp = ax25_from_text(reply_text, 1); /* * Send it to the TNC. * In this example we are transmitting speech on the same channel * where the tones were heard. We could also send AX.25 frames to * other radio channels. */ memset (&xmit_raw, 0, sizeof(xmit_raw)); xmit_raw.hdr.portx = chan; xmit_raw.hdr.kind_lo = 'K'; xmit_raw.hdr.data_len = 1 + ax25_pack (reply_pp, xmit_raw.frame); err = SOCK_SEND (server_sock, (char*)(&xmit_raw), sizeof(xmit_raw.hdr)+xmit_raw.hdr.data_len); ax25_delete (reply_pp); } ax25_delete (pp); } } } /* main */ /*------------------------------------------------------------------ * * Name: calculator * * Purpose: Simple calculator to demonstrate Touch Tone to Speech * application tool kit. * * Inputs: str - Sequence of touch tone characters: 0-9 A-D * # * It should be terminated with #. * * Returns: Numeric result of calculation. * * Description: This is a simple calculator that recognizes * numbers, * * for multiply * A for add * # for equals result * * Adding functions to B, C, and D is left as an * exercise for the reader. * * Examples: 2 * 3 A 4 # Ten * 5 * 1 0 0 A 3 # Five Hundred Three * *---------------------------------------------------------------*/ #define DO_LAST_OP \ switch (lastop) { \ case NONE: result = num; num = 0; break; \ case ADD: result += num; num = 0; break; \ case SUB: result -= num; num = 0; break; \ case MUL: result *= num; num = 0; break; \ case DIV: result /= num; num = 0; break; \ } static int calculator (char *str) { int result; int num; enum { NONE, ADD, SUB, MUL, DIV } lastop; char *p; result = 0; num = 0; lastop = NONE; for (p = str; *p != '\0'; p++) { if (isdigit(*p)) { num = num * 10 + *p - '0'; } else if (*p == '*') { DO_LAST_OP; lastop = MUL; } else if (*p == 'A' || *p == 'a') { DO_LAST_OP; lastop = ADD; } else if (*p == '#') { DO_LAST_OP; return (result); } } return (result); // not expected. } /*------------------------------------------------------------------ * * Name: connect_to_server * * Purpose: Connect to Dire Wolf TNC server. * * Inputs: hostname * port * * Returns: File descriptor or -1 for error. * *---------------------------------------------------------------*/ static int connect_to_server (char *hostname, char *port) { #if __WIN32__ #else //int e; #endif #define MAX_HOSTS 30 struct addrinfo hints; struct addrinfo *ai_head = NULL; struct addrinfo *ai; struct addrinfo *hosts[MAX_HOSTS]; int num_hosts, n; int err; char ipaddr_str[46]; /* text form of IP address */ #if __WIN32__ WSADATA wsadata; #endif /* * File descriptor for socket to server. * Set to -1 if not connected. * (Don't use SOCKET type because it is unsigned.) */ int server_sock = -1; #if __WIN32__ err = WSAStartup (MAKEWORD(2,2), &wsadata); if (err != 0) { printf("WSAStartup failed: %d\n", err); exit (1); } if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { printf("Could not find a usable version of Winsock.dll\n"); WSACleanup(); exit (1); } #endif memset (&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */ // hints.ai_family = AF_INET; /* IPv4 only. */ hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; /* * Connect to specified hostname & port. */ ai_head = NULL; err = getaddrinfo(hostname, port, &hints, &ai_head); if (err != 0) { #if __WIN32__ printf ("Can't get address for server %s, err=%d\n", hostname, WSAGetLastError()); #else printf ("Can't get address for server %s, %s\n", hostname, gai_strerror(err)); #endif freeaddrinfo(ai_head); exit (1); } num_hosts = 0; for (ai = ai_head; ai != NULL; ai = ai->ai_next) { hosts[num_hosts] = ai; if (num_hosts < MAX_HOSTS) num_hosts++; } // Try each address until we find one that is successful. for (n=0; nai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); #if __WIN32__ if (is == INVALID_SOCKET) { printf ("Socket creation failed, err=%d", WSAGetLastError()); WSACleanup(); is = -1; continue; } #else if (err != 0) { printf ("Socket creation failed, err=%s", gai_strerror(err)); (void) close (is); is = -1; continue; } #endif err = connect(is, ai->ai_addr, (int)ai->ai_addrlen); #if __WIN32__ if (err == SOCKET_ERROR) { closesocket (is); is = -1; continue; } #else if (err != 0) { (void) close (is); is = -1; continue; } int flag = 1; err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag)); if (err < 0) { printf("setsockopt TCP_NODELAY failed.\n"); } #endif /* * Success. */ printf("Client app now connected to %s (%s), port %s\n", hostname, ipaddr_str, port); server_sock = is; break; } freeaddrinfo(ai_head); if (server_sock == -1) { printf("Unnable to connect to %s (%s), port %s\n", hostname, ipaddr_str, port); } return (server_sock); } /* end connect_to_server */ /*------------------------------------------------------------------ * * Name: ia_to_text * * Purpose: Convert Internet address to text. * Can't use InetNtop because it is supported only * on Windows Vista and later. * *---------------------------------------------------------------*/ static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize) { struct sockaddr_in *sa4; struct sockaddr_in6 *sa6; switch (Family) { case AF_INET: sa4 = (struct sockaddr_in *)pAddr; #if __WIN32__ snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1, sa4->sin_addr.S_un.S_un_b.s_b2, sa4->sin_addr.S_un.S_un_b.s_b3, sa4->sin_addr.S_un.S_un_b.s_b4); #else inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize); #endif break; case AF_INET6: sa6 = (struct sockaddr_in6 *)pAddr; #if __WIN32__ snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x", ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7])); #else inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize); #endif break; default: snprintf (pStringBuf, StringBufSize, "Invalid address family!"); } return pStringBuf; } /* end ia_to_text */ /* end ttcalc.c */ direwolf-1.5+dfsg/tune.h000066400000000000000000000000021347750676600152520ustar00rootroot00000000000000 direwolf-1.5+dfsg/utm2ll.c000066400000000000000000000060111347750676600155170ustar00rootroot00000000000000/* UTM to Latitude / Longitude conversion */ #include "direwolf.h" #include #include #include #include #include #include "utm.h" #include "mgrs.h" #include "usng.h" #include "error_string.h" #define D2R(d) ((d) * M_PI / 180.) #define R2D(r) ((r) * 180. / M_PI) static void usage(); int main (int argc, char *argv[]) { double easting; double northing; double lat, lon; char szone[100]; long lzone; char *zlet; char hemi; long err; char message[300]; if (argc == 4) { // 3 command line arguments for UTM strlcpy (szone, argv[1], sizeof(szone)); lzone = strtoul(szone, &zlet, 10); if (*zlet == '\0') { hemi = 'N'; } else { if (islower(*zlet)) { *zlet = toupper(*zlet); } if (strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) == NULL) { fprintf (stderr, "Latitudinal band must be one of CDEFGHJKLMNPQRSTUVWX.\n\n"); usage(); } if (*zlet >= 'N') { hemi = 'N'; } else { hemi = 'S'; } } easting = atof(argv[2]); northing = atof(argv[3]); err = Convert_UTM_To_Geodetic(lzone, hemi, easting, northing, &lat, &lon); if (err == 0) { lat = R2D(lat); lon = R2D(lon); printf ("from UTM, latitude = %.6f, longitude = %.6f\n", lat, lon); } else { utm_error_string (err, message); fprintf (stderr, "Conversion from UTM failed:\n%s\n\n", message); } } else if (argc == 2) { // One command line argument, USNG or MGRS. // TODO: continue here. err = Convert_USNG_To_Geodetic (argv[1], &lat, &lon); if (err == 0) { lat = R2D(lat); lon = R2D(lon); printf ("from USNG, latitude = %.6f, longitude = %.6f\n", lat, lon); } else { usng_error_string (err, message); fprintf (stderr, "Conversion from USNG failed:\n%s\n\n", message); } err = Convert_MGRS_To_Geodetic (argv[1], &lat, &lon); if (err == 0) { lat = R2D(lat); lon = R2D(lon); printf ("from MGRS, latitude = %.6f, longitude = %.6f\n", lat, lon); } else { mgrs_error_string (err, message); fprintf (stderr, "Conversion from MGRS failed:\n%s\n\n", message); } } else { usage(); } exit (0); } static void usage (void) { fprintf (stderr, "UTM to Latitude / Longitude conversion\n"); fprintf (stderr, "\n"); fprintf (stderr, "Usage:\n"); fprintf (stderr, "\tutm2ll zone easting northing\n"); fprintf (stderr, "\n"); fprintf (stderr, "where,\n"); fprintf (stderr, "\tzone is UTM zone 1 thru 60 with optional latitudinal band.\n"); fprintf (stderr, "\teasting is x coordinate in meters\n"); fprintf (stderr, "\tnorthing is y coordinate in meters\n"); fprintf (stderr, "\n"); fprintf (stderr, "or:\n"); fprintf (stderr, "\tutm2ll x\n"); fprintf (stderr, "\n"); fprintf (stderr, "where,\n"); fprintf (stderr, "\tx is USNG or MGRS location.\n"); fprintf (stderr, "\n"); fprintf (stderr, "Examples:\n"); fprintf (stderr, "\tutm2ll 19T 306130 4726010\n"); fprintf (stderr, "\tutm2ll 19TCH06132600\n"); exit (1); }direwolf-1.5+dfsg/version.h000066400000000000000000000002151347750676600157720ustar00rootroot00000000000000 /* Dire Wolf version 1.5 */ #define APP_TOCALL "APDW" #define MAJOR_VERSION 1 #define MINOR_VERSION 5 //#define EXTRA_VERSION "Beta Test" direwolf-1.5+dfsg/walk96.c000066400000000000000000000116141347750676600154220ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2015 John Langner, WB2OSZ // // 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, see . // //#define DEBUG 1 /*------------------------------------------------------------------ * * Module: walk96.c * * Purpose: Quick hack to read GPS location and send very frequent * position reports frames to a KISS TNC. * * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include "config.h" #include "ax25_pad.h" #include "textcolor.h" #include "latlong.h" #include "dwgps.h" #include "encode_aprs.h" #include "serial_port.h" #include "kiss_frame.h" #define MYCALL "WB2OSZ" /************ Change this if you use it!!! ***************/ #define HOWLONG 20 /* Run for 20 seconds then quit. */ static MYFDTYPE tnc; static void walk96 (int fix, double lat, double lon, float knots, float course, float alt); int main (int argc, char *argv[]) { struct misc_config_s config; char cmd[100]; int debug_gps = 0; int n; // TD-D72A USB - Look for Silicon Labs CP210x. // Just happens to be same on desktop & laptop. tnc = serial_port_open ("COM5", 9600); if (tnc == MYFDERROR) { text_color_set (DW_COLOR_ERROR); dw_printf ("Can't open serial port to KISS TNC.\n"); exit (EXIT_FAILURE); // defined in stdlib.h } strlcpy (cmd, "\r\rhbaud 9600\rkiss on\rrestart\r", sizeof(cmd)); serial_port_write (tnc, cmd, strlen(cmd)); // USB GPS happens to be COM22 memset (&config, 0, sizeof(config)); strlcpy (config.gpsnmea_port, "COM22", sizeof(config.nmea_port)); dwgps_init (&config, debug_gps); SLEEP_SEC(1); /* Wait for sample before reading. */ for (n=0; n DWFIX_2D) { walk96 (fix, info.dlat, info.dlon, info.speed_knots, info.track, info.altitude); } else if (fix < 0) { text_color_set (DW_COLOR_ERROR); dw_printf ("Can't communicate with GPS receiver.\n"); exit (EXIT_FAILURE); } else { text_color_set (DW_COLOR_ERROR); dw_printf ("GPS fix not available.\n"); } SLEEP_SEC(1); } // Exit out of KISS mode. serial_port_write (tnc, "\xc0\xff\xc0", 3); SLEEP_MS(100); exit (EXIT_SUCCESS); } /* Should be called once per second. */ static void walk96 (int fix, double lat, double lon, float knots, float course, float alt) { static int sequence = 0; char comment[50]; sequence++; snprintf (comment, sizeof(comment), "Sequence number %04d", sequence); /* * Construct the packet in normal monitoring format. */ int messaging = 0; int compressed = 0; char info[AX25_MAX_INFO_LEN]; int info_len; char position_report[AX25_MAX_PACKET_LEN]; // TODO (high, bug): Why do we see !4237.13N/07120.84W=PHG0000... when all values set to unknown. info_len = encode_position (messaging, compressed, lat, lon, (int)(DW_METERS_TO_FEET(alt)), '/', '=', G_UNKNOWN, G_UNKNOWN, G_UNKNOWN, "", // PHGd (int)roundf(course), (int)roundf(knots), 445.925, 0, 0, comment, info, sizeof(info)); snprintf (position_report, sizeof(position_report), "%s>WALK96:%s", MYCALL, info); text_color_set (DW_COLOR_XMIT); dw_printf ("%s\n", position_report); /* * Convert it into AX.25 frame. */ packet_t pp; unsigned char ax25_frame[AX25_MAX_PACKET_LEN]; int frame_len; pp = ax25_from_text (position_report, 1); if (pp == NULL) { text_color_set (DW_COLOR_ERROR); dw_printf ("Unexpected error in ax25_from_text. Quitting.\n"); exit (EXIT_FAILURE); // defined in stdlib.h } ax25_frame[0] = 0; // Insert channel before KISS encapsulation. frame_len = ax25_pack (pp, ax25_frame+1); ax25_delete (pp); /* * Encapsulate as KISS and send to TNC. */ unsigned char kiss_frame[AX25_MAX_PACKET_LEN*2]; int kiss_len; kiss_len = kiss_encapsulate (ax25_frame, frame_len+1, (unsigned char *)kiss_frame); //text_color_set (DW_COLOR_DEBUG); //dw_printf ("AX.25 frame length = %d, KISS frame length = %d\n", frame_len, kiss_len); //kiss_debug_print (1, NULL, kiss_frame, kiss_len); serial_port_write (tnc, kiss_frame, kiss_len); } /* end walk96.c */ direwolf-1.5+dfsg/waypoint.c000066400000000000000000000411711347750676600161600ustar00rootroot00000000000000// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2014, 2015, 2016 John Langner, WB2OSZ // // 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, see . // //#define DEBUG 1 /*------------------------------------------------------------------ * * Module: waypoint.c * * Purpose: Send NMEA waypoint sentences to GPS display or mapping application. * *---------------------------------------------------------------*/ #include "direwolf.h" // should be first #include #include #if __WIN32__ #include #else #include #include #include #endif #include #include #include #include "config.h" #include "textcolor.h" #include "latlong.h" #include "waypoint.h" #include "grm_sym.h" /* Garmin symbols */ #include "mgn_icon.h" /* Magellan icons */ #include "dwgpsnmea.h" #include "serial_port.h" static MYFDTYPE s_waypoint_port_fd = MYFDERROR; static int s_waypoint_formats = 0; /* which formats should we generate? */ static int s_waypoint_debug = 0; /* Print information flowing to attached device. */ static void append_checksum (char *sentence); static void send_sentence (char *sent); void waypoint_set_debug (int n) { s_waypoint_debug = n; } /*------------------------------------------------------------------- * * Name: waypoint_init * * Purpose: Initialization for waypoint output port. * * Inputs: mc - Pointer to configuration options. * * ->waypoint_port - Name of serial port. COM1, /dev/ttyS0, etc. * * * (currently none) - speed, baud. Default 4800 if not set * * * ->waypoint_formats - Set of formats enabled. * If none set, default to generic & Kenwood. * * Global output: s_waypoint_port_fd * * Description: First to see if this is shared with GPS input. * If not, open serial port. * * Restriction: MUST be done after GPS init because we might be sharing the * same serial port device. * *---------------------------------------------------------------*/ void waypoint_init (struct misc_config_s *mc) { #if DEBUG text_color_set (DW_COLOR_DEBUG); dw_printf ("waypoint_init() device=%s formats=%d\n", mc->waypoint_port, mc->waypoint_formats); #endif /* * TODO: * Are we sharing with GPS input? * First try to get fd if they have same device name. * If that fails, do own serial port open. */ if (strlen(mc->waypoint_port) > 0) { s_waypoint_port_fd = dwgpsnmea_get_fd (mc->waypoint_port, 4800); if (s_waypoint_port_fd == MYFDERROR) { s_waypoint_port_fd = serial_port_open (mc->waypoint_port, 4800); } else { text_color_set (DW_COLOR_INFO); dw_printf ("Note: Sharing same port for GPS input and waypoint output.\n"); } if (s_waypoint_port_fd == MYFDERROR) { text_color_set (DW_COLOR_ERROR); dw_printf ("Unable to open %s for waypoint output.\n", mc->waypoint_port); return; } s_waypoint_formats = mc->waypoint_formats; if (s_waypoint_formats == 0) { s_waypoint_formats = WPT_FORMAT_NMEA_GENERIC | WPT_FORMAT_KENWOOD; } if (s_waypoint_formats & WPT_FORMAT_GARMIN) { s_waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; /* See explanation below. */ } } #if DEBUG text_color_set (DW_COLOR_DEBUG); dw_printf ("end of waypoint_init: s_waypoint_port_fd = %d\n", s_waypoint_port_fd); #endif } /*------------------------------------------------------------------- * * Name: append_checksum * * Purpose: Append checksum to the sentence. * * In/out: sentence - NMEA sentence beginning with '$'. * * Description: Checksum is exclusive of characters except leading '$'. * We append '*' and an upper case two hexadecimal value. * * Don't add CR/LF at this point. * *--------------------------------------------------------------------*/ static void append_checksum (char *sentence) { char *p; int cs; assert (sentence[0] == '$'); cs = 0; for (p = sentence+1; *p != '\0'; p++) { cs ^= *p; } sprintf (p, "*%02X", cs & 0xff); } /* end append_checksum */ /*------------------------------------------------------------------- * * Name: nema_send_waypoint * * Purpose: Convert APRS position or object into NMEA waypoint sentence * for use by a GPS display or other mapping application. * * Inputs: wname_in - Name of waypoint. Max of 9 characters. * dlat - Latitude. * dlong - Longitude. * symtab - Symbol table or overlay character. * symbol - Symbol code. * alt - Altitude in meters or G_UNKNOWN. * course - Course in degrees or G_UNKNOWN for unknown. * speed - Speed in knots or G_UNKNOWN. * comment_in - Description or message. * * * Description: Currently we send multiple styles. Maybe someday there might * be an option to send a selected subset. * * $GPWPL - NMEA generic with only location and name. * $PGRMW - Garmin, adds altitude, symbol, and comment * to previously named waypoint. * $PMGNWPL - Magellan, more complete for stationary objects. * $PKWDWPL - Kenwood with APRS style symbol but missing comment. * * * AvMap G5 notes: * * https://sites.google.com/site/kd7kuj/home/files?pli=1 * https://sites.google.com/site/kd7kuj/home/files/AvMapMessaging040810.pdf?attredirects=0&d=1 * * It sends $GPGGA & $GPRMC with location. * It understands generic $GPWPL and Kenwood $PKWDWPL. * * There are some proprietary $PAVP* used only for messaging. * Messaging would be a separate project. * *--------------------------------------------------------------------*/ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symtab, char symbol, float alt, float course, float speed, char *comment_in) { char wname[12]; /* Waypoint name. Any , or * removed. */ char slat[12]; /* DDMM.mmmm */ char slat_ns[2]; /* N or S */ char slong[12]; /* DDDMM.mmmm */ char slong_ew[2]; /* E or W */ char wcomment[256]; /* Comment. Any , or * removed. */ char salt[12]; /* altitude as string, empty if unknown */ char sspeed[12]; /* speed as string, empty if unknown */ char scourse[12]; /* course as string, empty if unknown */ char *p; char sentence[500]; #if DEBUG text_color_set (DW_COLOR_DEBUG); dw_printf ("waypoint_send_sentence (\"%s\", \"%c%c\")\n", name_in, symtab, symbol); #endif /* * We need to remove any , or * from name, symbol, or comment because they are field delimiters. * Follow precedent of Geosat AvMap $PAVPMSG sentence and make the following substitutions: * * , -> | * * -> ~ * * The system on the other end would need to change them back after extracting the * fields delimited by , or *. * We will deal with the symbol in the Kenwood section. * Needs to be left intact for other icon/symbol conversions. */ strlcpy (wname, name_in, sizeof(wname)); for (p=wname; *p != '\0'; p++) { if (*p == ',') *p = '|'; if (*p == '*') *p = '~'; } strlcpy (wcomment, comment_in, sizeof(wcomment)); for (p=wcomment; *p != '\0'; p++) { if (*p == ',') *p = '|'; if (*p == '*') *p = '~'; } /* * Convert numeric values to character form. * G_UNKNOWN value will result in an empty string. */ latitude_to_nmea (dlat, slat, slat_ns); longitude_to_nmea (dlong, slong, slong_ew); if (alt == G_UNKNOWN) { strcpy (salt, ""); } else { snprintf (salt, sizeof(salt), "%.1f", alt); } if (speed == G_UNKNOWN) { strcpy (sspeed, ""); } else { snprintf (sspeed, sizeof(sspeed), "%.1f", speed); } if (course == G_UNKNOWN) { strcpy (scourse, ""); } else { snprintf (scourse, sizeof(scourse), "%.1f", course); } /* * NMEA Generic. * * Has only location and name. Rather disappointing. * * $GPWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,wname*99 * * Where, * ddmm.mmmm,ns is latitude * dddmm.mmmm,ew is longitude * wname is the waypoint name * *99 is checksum */ if (s_waypoint_formats & WPT_FORMAT_NMEA_GENERIC) { snprintf (sentence, sizeof(sentence), "$GPWPL,%s,%s,%s,%s,%s", slat, slat_ns, slong, slong_ew, wname); append_checksum (sentence); send_sentence (sentence); } /* * Garmin * * https://www8.garmin.com/support/pdf/NMEA_0183.pdf * * No location! Adds altitude, symbol, and comment to existing waypoint. * So, we should always send the NMEA generic waypoint before this one. * The init function should take care of that. * * $PGRMW,wname,alt,symbol,comment*99 * * Where, * * wname is waypoint name. Must match existing waypoint. * alt is altitude in meters. * symbol is symbol code. Hexadecimal up to FFFF. * See Garmin Device Interface Specification * 001-0063-00 for values of "symbol_type." * comment is comment for the waypoint. * *99 is checksum */ if (s_waypoint_formats & WPT_FORMAT_GARMIN) { int i = symbol - ' '; int grm_sym; /* Garmin symbol code. */ if (i >= 0 && i < SYMTAB_SIZE) { if (symtab == '/') { grm_sym = grm_primary_symtab[i]; } else { grm_sym = grm_alternate_symtab[i]; } } else { grm_sym = sym_default; } snprintf (sentence, sizeof(sentence), "$PGRMW,%s,%s,%04X,%s", wname, salt, grm_sym, wcomment); append_checksum (sentence); send_sentence (sentence); } /* * Magellan * * http://www.gpsinformation.org/mag-proto-2-11.pdf Rev 2.11, Mar 2003, P/N 21-00091-000 * http://gpsinformation.net/mag-proto.htm Rev 1.0, Aug 1999, P/N 21-00091-000 * * * $PMGNWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,alt,unit,wname,comment,icon,xx*99 * * Where, * ddmm.mmmm,ns is latitude * dddmm.mmmm,ew is longitude * alt is altitude * unit is M for meters or F for feet * wname is the waypoint name * comment is message or comment * icon is one or two letters for icon code * xx is waypoint type which is optional, not well * defined, and not used in their example * so we won't use it. * *99 is checksum * * Possible enhancement: If the "object report" has the kill option set, use $PMGNDWP * to delete that specific waypoint. */ if (s_waypoint_formats & WPT_FORMAT_MAGELLAN) { int i = symbol - ' '; char sicon[3]; /* Magellan icon string. Currently 1 or 2 characters. */ if (i >= 0 && i < SYMTAB_SIZE) { if (symtab == '/') { strlcpy (sicon, mgn_primary_symtab[i], sizeof(sicon)); } else { strlcpy (sicon, mgn_alternate_symtab[i], sizeof(sicon)); } } else { strlcpy (sicon, MGN_default, sizeof(sicon)); } snprintf (sentence, sizeof(sentence), "$PMGNWPL,%s,%s,%s,%s,%s,M,%s,%s,%s", slat, slat_ns, slong, slong_ew, salt, wname, wcomment, sicon); append_checksum (sentence); send_sentence (sentence); } /* * Kenwood * * * $PKWDWPL,hhmmss,v,ddmm.mm,ns,dddmm.mm,ew,speed,course,ddmmyy,alt,wname,ts*99 * * Where, * hhmmss is time in UTC from the clock in the transceiver. * * This will be bogus if the clock was not set properly. * It does not use the timestamp from a position * report which could be useful. * * GPS Status A = active, V = void. * It looks like this might be modeled after the GPS status values * we see in $GPRMC. i.e. Does the transceiver know its location? * I don't see how that information would be relevant in this context. * I've observed this under various conditions (No GPS, GPS with/without * fix) and it has always been "V." * (There is some information out there indicating this field * can contain "I" for invalid but I don't think that is accurate.) * * ddmm.mm,ns is latitude. N or S. * dddmm.mm,ew is longitude. E or W. * * The D710 produces two fractional digits for minutes. * This is the same resolution most often used * in APRS packets. Any additional resolution offered by * the compressed format or the DAO option is not conveyed here. * We will provide greater resolution. * * speed is speed over ground, knots. * course is course over ground, degrees. * * Empty if not available. * * ddmmyy is date. See comments for time. * * alt is altitude, meters above mean sea level. * * Empty if no altitude is available. * * wname is the waypoint name. For an Object Report, the id is the object name. * For a position report, it is the call of the sending station. * * An Object name can contain any printable characters. * What if object name contains , or * characters? * Those are field delimiter characters and it would be unfortunate * if they appeared in a NMEA sentence data field. * * If there is a comma in the name, such as "test,5" the D710A displays * it fine but we end up with an extra field. * * $PKWDWPL,150803,V,4237.14,N,07120.83,W,,,190316,,test,5,/'*30 * * If the name contains an asterisk, it doesn't show up on the * display and no waypoint sentence is generated. * We will substitute these two characters following the AvMap precedent. * * $PKWDWPL,204714,V,4237.1400,N,07120.8300,W,,,200316,,test|5,/'*61 * $PKWDWPL,204719,V,4237.1400,N,07120.8300,W,,,200316,,test~6,/'*6D * * ts are the table and symbol. * * What happens if the symbol is comma or asterisk? * , Boy Scouts / Girl Scouts * * SnowMobile / Snow * * the D710A just pushes them thru without checking. * These would not be parsed properly: * * $PKWDWPL,150753,V,4237.14,N,07120.83,W,,,190316,,test3,/,*1B * $PKWDWPL,150758,V,4237.14,N,07120.83,W,,,190316,,test4,/ **3B * * We perform the usual substitution and the other end would * need to change them back after extracting from NMEA sentence. * * $PKWDWPL,204704,V,4237.1400,N,07120.8300,W,,,200316,,test3,/|*41 * $PKWDWPL,204709,V,4237.1400,N,07120.8300,W,,,200316,,test4,/~*49 * * * *99 is checksum * * Oddly, there is no place for comment. */ if (s_waypoint_formats & WPT_FORMAT_KENWOOD) { time_t now; struct tm tm; char stime[8]; char sdate[8]; char ken_sym; /* APRS symbol with , or * substituted. */ now = time(NULL); (void)gmtime_r (&now, &tm); strftime (stime, sizeof(stime), "%H%M%S", &tm); strftime (sdate, sizeof(sdate), "%d%m%y", &tm); // A symbol code of , or * would not be good because // they are field delimiters for NMEA sentences. // The AvMap G5 to Kenwood protocol description performs a substitution // for these characters that appear in message text. // , -> | // * -> ~ // Those two are listed as "TNC Stream Switch" and are not used for symbols. // It might be reasonable assumption that this same substitution might be // used for the symbol code. if (symbol == ',') ken_sym = '|'; else if (symbol == '*') ken_sym = '~'; else ken_sym = symbol; snprintf (sentence, sizeof(sentence), "$PKWDWPL,%s,V,%s,%s,%s,%s,%s,%s,%s,%s,%s,%c%c", stime, slat, slat_ns, slong, slong_ew, sspeed, scourse, sdate, salt, wname, symtab, ken_sym); append_checksum (sentence); send_sentence (sentence); } /* * One application recognizes these. Not implemented at this time. * * $GPTLL,01,ddmm.mmmm,ns,dddmm.mmmm,ew,tname,000000.00,T,R*99 * * Where, * ddmm.mmmm,ns is latitude * dddmm.mmmm,ew is longitude * tname is the target name * 000000.00 is timestamp ??? * T is target status (S for need help) * R is reference target ??? * *99 is checksum * * * $GPTXT,01,01,tname,message*99 * * Where, * * 01 is total number of messages in transmission * 01 is message number in this transmission * tname is target name. Should match name in WPL or TTL. * message is the message. * *99 is checksum * */ } /* end waypoint_send_sentence */ /* * Append CR LF and send it. */ static void send_sentence (char *sent) { char final[256]; if (s_waypoint_port_fd == MYFDERROR) { return; } if (s_waypoint_debug) { text_color_set(DW_COLOR_XMIT); dw_printf ("%s\n", sent); } strlcpy (final, sent, sizeof(final)); strlcat (final, "\r\n", sizeof(final)); serial_port_write (s_waypoint_port_fd, final, strlen(final)); } /* send_sentence */ void waypoint_term (void) { if (s_waypoint_port_fd != MYFDERROR) { //serial_port_close (s_waypoint_port_fd); s_waypoint_port_fd = MYFDERROR; } } /* end waypoint.c */ direwolf-1.5+dfsg/waypoint.h000066400000000000000000000006511347750676600161630ustar00rootroot00000000000000 /* * Name: waypoint.h */ #include "ax25_pad.h" /* for packet_t */ #include "config.h" /* for struct misc_config_s */ void waypoint_init (struct misc_config_s *misc_config); void waypoint_set_debug (int n); void waypoint_send_sentence (char *wname_in, double dlat, double dlong, char symtab, char symbol, float alt, float course, float speed, char *comment_in); void waypoint_term (); /* end waypoint.h */ direwolf-1.5+dfsg/xid.c000066400000000000000000000616201347750676600150730ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2014, 2016, 2017 John Langner, WB2OSZ // // 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, see . /*------------------------------------------------------------------ * * Module: xid.c * * Purpose: Encode and decode the info field of XID frames. * * Description: If we originate the connection, and the other end is * capable of AX.25 version 2.2, * * - We send an XID command frame with our capabilities. * - the other sends back an XID response, possibly * reducing some values to be acceptable there. * - Both ends use the values in that response. * * If the other end originates the connection, * * - It sends XID command frame with its capabilities. * - We might decrease some of them to be acceptable. * - Send XID response. * - Both ends use values in my response. * * References: AX.25 Protocol Spec, sections 4.3.3.7 & 6.3.2. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include "textcolor.h" #include "xid.h" #define FI_Format_Indicator 0x82 #define GI_Group_Identifier 0x80 #define PI_Classes_of_Procedures 2 #define PI_HDLC_Optional_Functions 3 #define PI_I_Field_Length_Rx 6 #define PI_Window_Size_Rx 8 #define PI_Ack_Timer 9 #define PI_Retries 10 // Forget about the bit order at the physical layer (e.g. HDLC). // It doesn't matter at all here. We are dealing with bytes. // A different encoding could send the bits in the opposite order. // The bit numbers are confusing because this one table (Fig. 4.5) starts // with 1 for the LSB when everywhere else refers to the LSB as bit 0. // The first byte must be of the form 0xx0 0001 // The second byte must be of the form 0000 0000 // If we process the two byte "Classes of Procedures" like // the other multibyte numeric fields, with the more significant // byte first, we end up with the bit masks below. // The bit order would be 8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9 // (This has nothing to do with the HDLC serializing order. // I'm talking about the way we would normally write binary numbers.) #define PV_Classes_Procedures_Balanced_ABM 0x0100 #define PV_Classes_Procedures_Half_Duplex 0x2000 #define PV_Classes_Procedures_Full_Duplex 0x4000 // The first byte must be of the form 1000 0xx0 // The second byte must be of the form 1010 xx00 // The third byte must be of the form 0000 0010 // If we process the three byte "HDLC Optional Parmeters" like // the other multibyte numeric fields, with the most significant // byte first, we end up with bit masks like this. // The bit order would be 8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9 24 23 22 21 20 19 18 17 #define PV_HDLC_Optional_Functions_REJ_cmd_resp 0x020000 #define PV_HDLC_Optional_Functions_SREJ_cmd_resp 0x040000 #define PV_HDLC_Optional_Functions_Extended_Address 0x800000 #define PV_HDLC_Optional_Functions_Modulo_8 0x000400 #define PV_HDLC_Optional_Functions_Modulo_128 0x000800 #define PV_HDLC_Optional_Functions_TEST_cmd_resp 0x002000 #define PV_HDLC_Optional_Functions_16_bit_FCS 0x008000 #define PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp 0x000020 #define PV_HDLC_Optional_Functions_Segmenter 0x000040 #define PV_HDLC_Optional_Functions_Synchronous_Tx 0x000002 /*------------------------------------------------------------------- * * Name: xid_parse * * Purpose: Decode information part of XID frame into individual values. * * Inputs: info - pointer to information part of frame. * * info_len - Number of bytes in information part of frame. * Could be 0. * * desc_size - Size of desc. 100 is good. * * Outputs: result - Structure with extracted values. * * desc - Text description for troubleshooting. * * Returns: 1 for mostly successful (with possible error messages), 0 for failure. * * Description: 6.3.2 "The receipt of an XID response from the other station * establishes that both stations are using AX.25 version * 2.2 or higher and enables the use of the segmenter/reassembler * and selective reject." * *--------------------------------------------------------------------*/ int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, char *desc, int desc_size) { unsigned char *p; int group_len; char stemp[64]; strlcpy (desc, "", desc_size); // What should we do when some fields are missing? // The AX.25 v2.2 protocol spec says, for most of these, // "If this field is not present, the current values are retained." // We set the numeric values to our usual G_UNKNOWN to mean undefined and let the caller deal with it. // rej and modulo are enum so we can't use G_UNKNOWN there. result->full_duplex = G_UNKNOWN; result->srej = srej_not_specified; result->modulo = modulo_unknown; result->i_field_length_rx = G_UNKNOWN; result->window_size_rx = G_UNKNOWN; result->ack_timer = G_UNKNOWN; result->retries = G_UNKNOWN; /* Information field is optional but that seems pretty lame. */ if (info_len == 0) { return (1); } p = info; if (*p != FI_Format_Indicator) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: First byte of info field should be Format Indicator, %02x.\n", FI_Format_Indicator); dw_printf ("XID info part: %02x %02x %02x %02x %02x ... length=%d\n", info[0], info[1], info[2], info[3], info[4], info_len); return 0; } p++; if (*p != GI_Group_Identifier) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Second byte of info field should be Group Indicator, %d.\n", GI_Group_Identifier); return 0; } p++; group_len = *p++; group_len = (group_len << 8) + *p++; while (p < info + 4 + group_len) { int pind, plen, pval, j; pind = *p++; plen = *p++; // should have sanity checking if (plen < 1 || plen > 4) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Length ????? TODO ???? %d.\n", plen); return (1); // got this far. } pval = 0; for (j=0; jfull_duplex = 0; strlcat (desc, "Half-Duplex ", desc_size); } else if (pval & PV_Classes_Procedures_Full_Duplex && ! (pval & PV_Classes_Procedures_Half_Duplex)) { result->full_duplex = 1; strlcat (desc, "Full-Duplex ", desc_size); } else { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected one of Half or Full Duplex be set.\n"); result->full_duplex = 0; } break; case PI_HDLC_Optional_Functions: // Pick highest of those offered. if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) { strlcat (desc, "REJ ", desc_size); } if (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) { strlcat (desc, "SREJ ", desc_size); } if (pval & PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp) { strlcat (desc, "Multi-SREJ ", desc_size); } if (pval & PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp) { result->srej = srej_multi; } else if (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) { result->srej = srej_single; } else if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) { result->srej = srej_none; } else { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected at least one of REJ, SREJ, Multi-SREJ to be set.\n"); result->srej = srej_none; } if ((pval & PV_HDLC_Optional_Functions_Modulo_8) && ! (pval & PV_HDLC_Optional_Functions_Modulo_128)) { result->modulo = modulo_8; strlcat (desc, "modulo-8 ", desc_size); } else if ((pval & PV_HDLC_Optional_Functions_Modulo_128) && ! (pval & PV_HDLC_Optional_Functions_Modulo_8)) { result->modulo = modulo_128; strlcat (desc, "modulo-128 ", desc_size); } else { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected one of Modulo 8 or 128 be set.\n"); } if ( ! (pval & PV_HDLC_Optional_Functions_Extended_Address)) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected Extended Address to be set.\n"); } if ( ! (pval & PV_HDLC_Optional_Functions_TEST_cmd_resp)) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected TEST cmd/resp to be set.\n"); } if ( ! (pval & PV_HDLC_Optional_Functions_16_bit_FCS)) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected 16 bit FCS to be set.\n"); } if ( ! (pval & PV_HDLC_Optional_Functions_Synchronous_Tx)) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Expected Synchronous Tx to be set.\n"); } break; case PI_I_Field_Length_Rx: result->i_field_length_rx = pval / 8; snprintf (stemp, sizeof(stemp), "I-Field-Length-Rx=%d ", result->i_field_length_rx); strlcat (desc, stemp, desc_size); if (pval & 0x7) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: I Field Length Rx, %d, is not a whole number of bytes.\n", pval); } break; case PI_Window_Size_Rx: result->window_size_rx = pval; snprintf (stemp, sizeof(stemp), "Window-Size-Rx=%d ", result->window_size_rx); strlcat (desc, stemp, desc_size); if (pval < 1 || pval > 127) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Window Size Rx, %d, is not in range of 1 thru 127.\n", pval); result->window_size_rx = 127; // Let the caller deal with modulo 8 consideration. } //continue here with more error checking. break; case PI_Ack_Timer: result->ack_timer = pval; snprintf (stemp, sizeof(stemp), "Ack-Timer=%d ", result->ack_timer); strlcat (desc, stemp, desc_size); break; case PI_Retries: // Is it retrys or retries? result->retries = pval; snprintf (stemp, sizeof(stemp), "Retries=%d ", result->retries); strlcat (desc, stemp, desc_size); break; default: break; // Ignore anything we don't recognize. } } if (p != info + info_len) { text_color_set (DW_COLOR_ERROR); dw_printf ("XID error: Frame / Group Length mismatch.\n"); } return (1); } /* end xid_parse */ /*------------------------------------------------------------------- * * Name: xid_encode * * Purpose: Encode the information part of an XID frame. * * Inputs: param-> * full_duplex - As command, am I capable of full duplex operation? * When a response, are we both? * 0 = half duplex. * 1 = full duplex. * * srej - Level of selective reject. * srej_none (use REJ), srej_single, srej_multi * As command, offer a menu of what I can handle. (i.e. perhaps multiple bits set) * As response, take minimum of what is offered and what I can handle. (one bit set) * * modulo - 8 or 128. * * i_field_length_rx - Maximum number of bytes I can handle in info part. * Default is 256. * Up to 8191 will fit into the field. * Use G_UNKNOWN to omit this. * * window_size_rx - Maximum window size ("k") that I can handle. * Defaults are are 4 for modulo 8 and 32 for modulo 128. * * ack_timer - Acknowledge timer in milliseconds. * *** describe meaning. *** * Default is 3000. * Use G_UNKNOWN to omit this. * * retries - Allows negotiation of retries. * Default is 10. * Use G_UNKNOWN to omit this. * * cr - Is it a command or response? * * Outputs: info - Information part of XID frame. * Does not include the control byte. * Use buffer of 40 bytes just to be safe. * * Returns: Number of bytes in the info part. Should be at most 27. * Again, provide a larger space just to be safe in case this ever changes. * * Description: 6.3.2 "Parameter negotiation occurs at any time. It is accomplished by sending * the XID command frame and receiving the XID response frame. Implementations of * AX.25 prior to version 2.2 respond to an XID command frame with a FRMR response * frame. The TNC receiving the FRMR uses a default set of parameters compatible * with previous versions of AX.25." * * "This version of AX.25 implements the negotiation or notification of six AX.25 * parameters. Notification simply tells the distant TNC some limit that cannot be exceeded. * The distant TNC can choose to use the limit or some other value that is within the * limits. Notification is used with the Window Size Receive (k) and Information * Field Length Receive (N1) parameters. Negotiation involves both TNCs choosing a * value that is mutually acceptable. The XID command frame contains a set of values * acceptable to the originating TNC. The distant TNC chooses to accept the values * offered, or other acceptable values, and places these values in the XID response. * Both TNCs set themselves up based on the values used in the XID response. Negotiation * is used by Classes of Procedures, HDLC Optional Functions, Acknowledge Timer and Retries." * * Comment: I have a problem with "... occurs at any time." What if we were in the middle * of transferring a large file with k=32 then along comes XID which says switch to modulo 8? * * Insight: Or is it Erratum? * After reading the base standards documents, it seems that the XID command should offer * up a menu of all the acceptable choices. e.g. REJ, SREJ, Multi-SREJ. One or more bits * can be set. The XID response, would set a single bit which is the desired choice from * among those offered. * Should go back and review half/full duplex and modulo. * *--------------------------------------------------------------------*/ int xid_encode (struct xid_param_s *param, unsigned char *info, cmdres_t cr) { unsigned char *p; int len; int x; int m = 0; p = info; *p++ = FI_Format_Indicator; *p++ = GI_Group_Identifier; *p++ = 0; m = 4; // classes of procedures m += 5; // HDLC optional features if (param->i_field_length_rx != G_UNKNOWN) m += 4; if (param->window_size_rx != G_UNKNOWN) m += 3; if (param->ack_timer != G_UNKNOWN) m += 4; if (param->retries != G_UNKNOWN) m += 3; *p++ = m; // 0x17 if all present. // "Classes of Procedures" has half / full duplex. // We always send this. *p++ = PI_Classes_of_Procedures; *p++ = 2; x = PV_Classes_Procedures_Balanced_ABM; if (param->full_duplex == 1) x |= PV_Classes_Procedures_Full_Duplex; else // includes G_UNKNOWN x |= PV_Classes_Procedures_Half_Duplex; *p++ = (x >> 8) & 0xff; *p++ = x & 0xff; // "HDLC Optional Functions" contains REJ/SREJ & modulo 8/128. // We always send this. // Watch out for unknown values and do something reasonable. *p++ = PI_HDLC_Optional_Functions; *p++ = 3; x = PV_HDLC_Optional_Functions_Extended_Address | PV_HDLC_Optional_Functions_TEST_cmd_resp | PV_HDLC_Optional_Functions_16_bit_FCS | PV_HDLC_Optional_Functions_Synchronous_Tx; //text_color_set (DW_COLOR_ERROR); //dw_printf ("****** XID temp hack - test no SREJ ******\n"); // param->srej = srej_none; if (cr == cr_cmd) { // offer a "menu" of acceptable choices. i.e. 1, 2 or 3 bits set. switch (param->srej) { case srej_none: default: x |= PV_HDLC_Optional_Functions_REJ_cmd_resp; break; case srej_single: x |= PV_HDLC_Optional_Functions_REJ_cmd_resp | PV_HDLC_Optional_Functions_SREJ_cmd_resp; break; case srej_multi: x |= PV_HDLC_Optional_Functions_REJ_cmd_resp | PV_HDLC_Optional_Functions_SREJ_cmd_resp | PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp; break; } } else { // for response, set only a single bit. switch (param->srej) { case srej_none: default: x |= PV_HDLC_Optional_Functions_REJ_cmd_resp; break; case srej_single: x |= PV_HDLC_Optional_Functions_SREJ_cmd_resp; break; case srej_multi: x |= PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp; break; } } if (param->modulo == modulo_128) x |= PV_HDLC_Optional_Functions_Modulo_128; else // includes modulo_8 and modulo_unknown x |= PV_HDLC_Optional_Functions_Modulo_8; *p++ = (x >> 16) & 0xff; *p++ = (x >> 8) & 0xff; *p++ = x & 0xff; // The rest are skipped if undefined values. // "I Field Length Rx" - max I field length acceptable to me. // This is in bits. 8191 would be max number of bytes to fit in field. if (param->i_field_length_rx != G_UNKNOWN) { *p++ = PI_I_Field_Length_Rx; *p++ = 2; x = param->i_field_length_rx * 8; *p++ = (x >> 8) & 0xff; *p++ = x & 0xff; } // "Window Size Rx" if (param->window_size_rx != G_UNKNOWN) { *p++ = PI_Window_Size_Rx; *p++ = 1; *p++ = param->window_size_rx; } // "Ack Timer" milliseconds. We could handle up to 65535 here. if (param->ack_timer != G_UNKNOWN) { *p++ = PI_Ack_Timer; *p++ = 2; *p++ = (param->ack_timer >> 8) & 0xff; *p++ = param->ack_timer & 0xff; } // "Retries." if (param->retries != G_UNKNOWN) { *p++ = PI_Retries; *p++ = 1; *p++ = param->retries; } len = p - info; return (len); } /* end xid_encode */ /*------------------------------------------------------------------- * * Name: main * * Purpose: Unit test for other functions here. * * Description: Run with: * * gcc -DXIDTEST -g xid.c textcolor.o && ./a * * Result should be: * * XID test: Success. * * with no error messages. * *--------------------------------------------------------------------*/ #if XIDTEST /* From Figure 4.6. Typical XID frame, from AX.25 protocol spec, v. 2.2 */ /* This is the info part after a control byte of 0xAF. */ static unsigned char example[27] = { /* FI */ 0x82, /* Format indicator */ /* GI */ 0x80, /* Group Identifier - parameter negotiation */ /* GL */ 0x00, /* Group length - all of the PI/PL/PV fields */ /* GL */ 0x17, /* (2 bytes) */ /* PI */ 0x02, /* Parameter Indicator - classes of procedures */ /* PL */ 0x02, /* Parameter Length */ #if 0 // Erratum: Example in the protocol spec looks wrong. /* PV */ 0x00, /* Parameter Variable - Half Duplex, Async, Balanced Mode */ /* PV */ 0x20, /* */ #else // I think it should be like this instead. /* PV */ 0x21, /* Parameter Variable - Half Duplex, Async, Balanced Mode */ /* PV */ 0x00, /* Reserved */ #endif /* PI */ 0x03, /* Parameter Indicator - optional functions */ /* PL */ 0x03, /* Parameter Length */ /* PV */ 0x86, /* Parameter Variable - SREJ/REJ, extended addr */ /* PV */ 0xA8, /* 16-bit FCS, TEST cmd/resp, Modulo 128 */ /* PV */ 0x02, /* synchronous transmit */ /* PI */ 0x06, /* Parameter Indicator - Rx I field length (bits) */ /* PL */ 0x02, /* Parameter Length */ // Erratum: The text does not say anything about the byte order for multibyte // numeric values. In the example, we have two cases where 16 bit numbers are // sent with the more significant byte first. /* PV */ 0x04, /* Parameter Variable - 1024 bits (128 octets) */ /* PV */ 0x00, /* */ /* PI */ 0x08, /* Parameter Indicator - Rx window size */ /* PL */ 0x01, /* Parameter length */ /* PV */ 0x02, /* Parameter Variable - 2 frames */ /* PI */ 0x09, /* Parameter Indicator - Timer T1 */ /* PL */ 0x02, /* Parameter Length */ /* PV */ 0x10, /* Parameter Variable - 4096 MSec */ /* PV */ 0x00, /* */ /* PI */ 0x0A, /* Parameter Indicator - Retries (N1) */ /* PL */ 0x01, /* Parameter Length */ /* PV */ 0x03 /* Parameter Variable - 3 retries */ }; int main (int argc, char *argv[]) { struct xid_param_s param; struct xid_param_s param2; int n; unsigned char info[40]; // Currently max of 27 but things can change. char desc[150]; // I've seen 109. /* parse example. */ n = xid_parse (example, sizeof(example), ¶m, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (n==1); assert (param.full_duplex == 0); assert (param.srej == srej_single); assert (param.modulo == modulo_128); assert (param.i_field_length_rx == 128); assert (param.window_size_rx == 2); assert (param.ack_timer == 4096); assert (param.retries == 3); /* encode and verify it comes out the same. */ n = xid_encode (¶m, info, cr_cmd); assert (n == sizeof(example)); n = memcmp(info, example, 27); //for (n=0; n<27; n++) { // dw_printf ("%2d %02x %02x\n", n, example[n], info[n]); //} assert (n == 0); /* try a couple different values, no srej. */ param.full_duplex = 1; param.srej = srej_none; param.modulo = modulo_8; param.i_field_length_rx = 2048; param.window_size_rx = 3; param.ack_timer = 1234; param.retries = 12; n = xid_encode (¶m, info, cr_cmd); n = xid_parse (info, n, ¶m2, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (param2.full_duplex == 1); assert (param2.srej == srej_none); assert (param2.modulo == modulo_8); assert (param2.i_field_length_rx == 2048); assert (param2.window_size_rx == 3); assert (param2.ack_timer == 1234); assert (param2.retries == 12); /* Other values, single srej. */ param.full_duplex = 0; param.srej = srej_single; param.modulo = modulo_8; param.i_field_length_rx = 61; param.window_size_rx = 4; param.ack_timer = 5555; param.retries = 9; n = xid_encode (¶m, info, cr_cmd); n = xid_parse (info, n, ¶m2, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (param2.full_duplex == 0); assert (param2.srej == srej_single); assert (param2.modulo == modulo_8); assert (param2.i_field_length_rx == 61); assert (param2.window_size_rx == 4); assert (param2.ack_timer == 5555); assert (param2.retries == 9); /* Other values, multi srej. */ param.full_duplex = 0; param.srej = srej_multi; param.modulo = modulo_128; param.i_field_length_rx = 61; param.window_size_rx = 4; param.ack_timer = 5555; param.retries = 9; n = xid_encode (¶m, info, cr_cmd); n = xid_parse (info, n, ¶m2, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (param2.full_duplex == 0); assert (param2.srej == srej_multi); assert (param2.modulo == modulo_128); assert (param2.i_field_length_rx == 61); assert (param2.window_size_rx == 4); assert (param2.ack_timer == 5555); assert (param2.retries == 9); /* Specify some and not others. */ param.full_duplex = 0; param.srej = srej_single; param.modulo = modulo_8; param.i_field_length_rx = G_UNKNOWN; param.window_size_rx = G_UNKNOWN; param.ack_timer = 999; param.retries = G_UNKNOWN; n = xid_encode (¶m, info, cr_cmd); n = xid_parse (info, n, ¶m2, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (param2.full_duplex == 0); assert (param2.srej == srej_single); assert (param2.modulo == modulo_8); assert (param2.i_field_length_rx == G_UNKNOWN); assert (param2.window_size_rx == G_UNKNOWN); assert (param2.ack_timer == 999); assert (param2.retries == G_UNKNOWN); /* Default values for empty info field. */ n = 0; n = xid_parse (info, n, ¶m2, desc, sizeof(desc)); text_color_set (DW_COLOR_DEBUG); dw_printf ("%d: %s\n", __LINE__, desc); fflush (stdout); SLEEP_SEC (1); text_color_set (DW_COLOR_ERROR); assert (param2.full_duplex == G_UNKNOWN); assert (param2.srej == srej_not_specified); assert (param2.modulo == modulo_unknown); assert (param2.i_field_length_rx == G_UNKNOWN); assert (param2.window_size_rx == G_UNKNOWN); assert (param2.ack_timer == G_UNKNOWN); assert (param2.retries == G_UNKNOWN); text_color_set (DW_COLOR_REC); dw_printf ("XID test: Success.\n"); exit (0); } #endif /* end xid.c */ direwolf-1.5+dfsg/xid.h000066400000000000000000000013161347750676600150740ustar00rootroot00000000000000 /* xid.h */ #include "ax25_pad.h" // for enum ax25_modulo_e struct xid_param_s { int full_duplex; // Order is important because negotiation keeps the lower value of // REJ (srej_none), SREJ (default without negotiation), Multi-SREJ (if both agree). enum srej_e { srej_none=0, srej_single=1, srej_multi=2, srej_not_specified=3 } srej; enum ax25_modulo_e modulo; int i_field_length_rx; /* In bytes. XID has it in bits. */ int window_size_rx; int ack_timer; /* "T1" in mSec. */ int retries; /* "N1" */ }; int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, char *desc, int desc_size); int xid_encode (struct xid_param_s *param, unsigned char *info, cmdres_t cr);direwolf-1.5+dfsg/xmit.c000066400000000000000000001157061347750676600152750ustar00rootroot00000000000000 // // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017 John Langner, WB2OSZ // // 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, see . // /*------------------------------------------------------------------ * * Module: xmit.c * * Purpose: Transmit queued up packets when channel is clear. * * Description: Producers of packets to be transmitted call tq_append and then * go merrily on their way, unconcerned about when the packet might * actually get transmitted. * * This thread waits until the channel is clear and then removes * packets from the queue and transmits them. * * * Usage: (1) The main application calls xmit_init. * * This will initialize the transmit packet queue * and create a thread to empty the queue when * the channel is clear. * * (2) The application queues up packets by calling tq_append. * * Packets that are being digipeated should go in the * high priority queue so they will go out first. * * Other packets should go into the lower priority queue. * * (3) xmit_thread removes packets from the queue and transmits * them when other signals are not being heard. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "tq.h" #include "xmit.h" #include "hdlc_send.h" #include "hdlc_rec.h" #include "ptt.h" #include "dtime_now.h" #include "morse.h" #include "dtmf.h" #include "xid.h" #include "dlq.h" /* * Parameters for transmission. * Each channel can have different timing values. * * These are initialized once at application startup time * and some can be changed later by commands from connected applications. */ static int xmit_slottime[MAX_CHANS]; /* Slot time in 10 mS units for persistance algorithm. */ static int xmit_persist[MAX_CHANS]; /* Sets probability for transmitting after each */ /* slot time delay. Transmit if a random number */ /* in range of 0 - 255 <= persist value. */ /* Otherwise wait another slot time and try again. */ static int xmit_txdelay[MAX_CHANS]; /* After turning on the transmitter, */ /* send "flags" for txdelay * 10 mS. */ static int xmit_txtail[MAX_CHANS]; /* Amount of time to keep transmitting after we */ /* are done sending the data. This is to avoid */ /* dropping PTT too soon and chopping off the end */ /* of the frame. Again 10 mS units. */ static int xmit_fulldup[MAX_CHANS]; /* Full duplex if non-zero. */ static int xmit_bits_per_sec[MAX_CHANS]; /* Data transmission rate. */ /* Often called baud rate which is equivalent for */ /* 1200 & 9600 cases but could be different with other */ /* modulation techniques. */ static int g_debug_xmit_packet; /* print packet in hexadecimal form for debugging. */ // TODO: When this was first written, bits/sec was same as baud. // Need to revisit this for PSK modes where they are not the same. #if 0 // Added during 1.5 beta test static int BITS_TO_MS (int b, int ch) { int bits_per_symbol; switch (save_audio_config_p->achan[ch].modem_type) { case MODEM_QPSK: bits_per_symbol = 2; break; case MODEM_8PSK: bits_per_symbol = 3; break; case default: bits_per_symbol = 1; break; } return ( (b * 1000) / (xmit_bits_per_sec[(ch)] * bits_per_symbol) ); } static int MS_TO_BITS (int ms, int ch) { int bits_per_symbol; switch (save_audio_config_p->achan[ch].modem_type) { case MODEM_QPSK: bits_per_symbol = 2; break; case MODEM_8PSK: bits_per_symbol = 3; break; case default: bits_per_symbol = 1; break; } return ( (ms * xmit_bits_per_sec[(ch)] * bits_per_symbol) / 1000 ); TODO... } #else // OK for 1200, 9600 but wrong for PSK #define BITS_TO_MS(b,ch) (((b)*1000)/xmit_bits_per_sec[(ch)]) #define MS_TO_BITS(ms,ch) (((ms)*xmit_bits_per_sec[(ch)])/1000) #endif #define MAXX(a,b) (((a)>(b)) ? (a) : (b)) #if __WIN32__ static unsigned __stdcall xmit_thread (void *arg); #else static void * xmit_thread (void *arg); #endif /* * When an audio device is in stereo mode, we can have two * different channels that want to transmit at the same time. * We are not clever enough to multiplex them so use this * so only one is activte at the same time. */ static dw_mutex_t audio_out_dev_mutex[MAX_ADEVS]; static int wait_for_clear_channel (int channel, int slotttime, int persist, int fulldup); static void xmit_ax25_frames (int c, int p, packet_t pp, int max_bundle); static int send_one_frame (int c, int p, packet_t pp); static void xmit_speech (int c, packet_t pp); static void xmit_morse (int c, packet_t pp, int wpm); static void xmit_dtmf (int c, packet_t pp, int speed); /*------------------------------------------------------------------- * * Name: xmit_init * * Purpose: Initialize the transmit process. * * Inputs: modem - Structure with modem and timing parameters. * * * Outputs: Remember required information for future use. * * Description: Initialize the queue to be empty and set up other * mechanisms for sharing it between different threads. * * Start up xmit_thread(s) to actually send the packets * at the appropriate time. * * Version 1.2: We now allow multiple audio devices with one or two channels each. * Each audio channel has its own thread. * *--------------------------------------------------------------------*/ static struct audio_s *save_audio_config_p; void xmit_init (struct audio_s *p_modem, int debug_xmit_packet) { int j; int ad; #if __WIN32__ HANDLE xmit_th[MAX_CHANS]; #else //pthread_attr_t attr; //struct sched_param sp; pthread_t xmit_tid[MAX_CHANS]; #endif //int e; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init ( ... )\n"); #endif save_audio_config_p = p_modem; g_debug_xmit_packet = debug_xmit_packet; /* * Push to Talk (PTT) control. */ #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init: about to call ptt_init \n"); #endif ptt_init (p_modem); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init: back from ptt_init \n"); #endif /* * Save parameters for later use. * TODO1.2: Any reason to use global config rather than making a copy? */ for (j=0; jachan[j].baud; xmit_slottime[j] = p_modem->achan[j].slottime; xmit_persist[j] = p_modem->achan[j].persist; xmit_txdelay[j] = p_modem->achan[j].txdelay; xmit_txtail[j] = p_modem->achan[j].txtail; xmit_fulldup[j] = p_modem->achan[j].fulldup; } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init: about to call tq_init \n"); #endif tq_init (p_modem); for (ad = 0; ad < MAX_ADEVS; ad++) { dw_mutex_init (&(audio_out_dev_mutex[ad])); } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init: about to create threads \n"); #endif //TODO: xmit thread should be higher priority to avoid // underrun on the audio output device. for (j=0; jachan[j].valid) { #if __WIN32__ xmit_th[j] = (HANDLE)_beginthreadex (NULL, 0, xmit_thread, (void*)(long)j, 0, NULL); if (xmit_th[j] == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create xmit thread %d\n", j); return; } #else int e; #if 0 //TODO: not this simple. probably need FIFO policy. pthread_attr_init (&attr); e = pthread_attr_getschedparam (&attr, &sp); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("pthread_attr_getschedparam"); } text_color_set(DW_COLOR_ERROR); dw_printf ("Default scheduling priority = %d, min=%d, max=%d\n", sp.sched_priority, sched_get_priority_min(SCHED_OTHER), sched_get_priority_max(SCHED_OTHER)); sp.sched_priority--; e = pthread_attr_setschedparam (&attr, &sp); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("pthread_attr_setschedparam"); } e = pthread_create (&(xmit_tid[j]), &attr, xmit_thread, (void *)(long)j); pthread_attr_destroy (&attr); #else e = pthread_create (&(xmit_tid[j]), NULL, xmit_thread, (void *)(long)j); #endif if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create xmit thread for audio device"); return; } #endif } } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init: finished \n"); #endif } /* end tq_init */ /*------------------------------------------------------------------- * * Name: xmit_set_txdelay * xmit_set_persist * xmit_set_slottime * xmit_set_txtail * xmit_set_fulldup * * * Purpose: The KISS protocol, and maybe others, can specify * transmit timing parameters. If the application * specifies these, they will override what was read * from the configuration file. * * Inputs: channel - should be 0 or 1. * * value - time values are in 10 mSec units. * * * Outputs: Remember required information for future use. * * Question: Should we have an option to enable or disable the * application changing these values? * * Bugs: No validity checking other than array subscript out of bounds. * *--------------------------------------------------------------------*/ void xmit_set_txdelay (int channel, int value) { if (channel >= 0 && channel < MAX_CHANS) { xmit_txdelay[channel] = value; } } void xmit_set_persist (int channel, int value) { if (channel >= 0 && channel < MAX_CHANS) { xmit_persist[channel] = value; } } void xmit_set_slottime (int channel, int value) { if (channel >= 0 && channel < MAX_CHANS) { xmit_slottime[channel] = value; } } void xmit_set_txtail (int channel, int value) { if (channel >= 0 && channel < MAX_CHANS) { xmit_txtail[channel] = value; } } void xmit_set_fulldup (int channel, int value) { if (channel >= 0 && channel < MAX_CHANS) { xmit_fulldup[channel] = value; } } /*------------------------------------------------------------------- * * Name: frame_flavor * * Purpose: Separate frames into different flavors so we can decide * which can be bundled into a single transmission and which should * be sent separately. * * Inputs: pp - Packet object. * * Returns: Flavor, one of: * * FLAVOR_SPEECH - Destination address is SPEECH. * FLAVOR_MORSE - Destination address is MORSE. * FLAVOR_DTMF - Destination address is DTMF. * FLAVOR_APRS_NEW - APRS original, i.e. not digipeating. * FLAVOR_APRS_DIGI - APRS digipeating. * FLAVOR_OTHER - Anything left over, i.e. connected mode. * *--------------------------------------------------------------------*/ typedef enum flavor_e { FLAVOR_APRS_NEW, FLAVOR_APRS_DIGI, FLAVOR_SPEECH, FLAVOR_MORSE, FLAVOR_DTMF, FLAVOR_OTHER } flavor_t; static flavor_t frame_flavor (packet_t pp) { if (ax25_is_aprs (pp)) { // UI frame, PID 0xF0. // It's unfortunate APRS did not use its own special PID. char dest[AX25_MAX_ADDR_LEN]; ax25_get_addr_no_ssid(pp, AX25_DESTINATION, dest); if (strcmp(dest, "SPEECH") == 0) { return (FLAVOR_SPEECH); } if (strcmp(dest, "MORSE") == 0) { return (FLAVOR_MORSE); } if (strcmp(dest, "DTMF") == 0) { return (FLAVOR_DTMF); } /* Is there at least one digipeater AND has first one been used? */ /* I could be the first in the list or later. Doesn't matter. */ if (ax25_get_num_repeaters(pp) >= 1 && ax25_get_h(pp,AX25_REPEATER_1)) { return (FLAVOR_APRS_DIGI); } return (FLAVOR_APRS_NEW); } return (FLAVOR_OTHER); } /* end frame_flavor */ /*------------------------------------------------------------------- * * Name: xmit_thread * * Purpose: Process transmit queue for one channel. * * Inputs: transmit packet queue. * * Outputs: * * Description: We have different timing rules for different types of * packets so they are put into different queues. * * High Priority - * * Packets which are being digipeated go out first. * Latest recommendations are to retransmit these * immdediately (after no one else is heard, of course) * rather than waiting random times to avoid collisions. * The KPC-3 configuration option for this is "UIDWAIT OFF". (?) * * AX.25 connected mode also has a couple cases * where "expedited" frames are sent. * * Low Priority - * * Other packets are sent after a random wait time * (determined by PERSIST & SLOTTIME) to help avoid * collisions. * * If more than one audio channel is being used, a separate * pair of transmit queues is used for each channel. * * * * Version 1.2: Allow more than one audio device. * each channel has its own thread. * Add speech capability. * * Version 1.4: Rearranged logic for bundling multiple frames into a single transmission. * * The rule is that Speech, Morse Code, DTMF, and APRS digipeated frames * are all sent separately. The rest can be bundled. * *--------------------------------------------------------------------*/ #if __WIN32__ static unsigned __stdcall xmit_thread (void *arg) #else static void * xmit_thread (void *arg) #endif { int chan = (int)(long)arg; // channel number. packet_t pp; int prio; int ok; while (1) { tq_wait_while_empty (chan); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread, channel %d: woke up\n", c); #endif // Does this extra loop offer any benefit? while (tq_peek(chan, TQ_PRIO_0_HI) != NULL || tq_peek(chan, TQ_PRIO_1_LO) != NULL) { /* * Wait for the channel to be clear. * If there is something in the high priority queue, begin transmitting immediately. * Otherwise, wait a random amount of time, in hopes of minimizing collisions. */ ok = wait_for_clear_channel (chan, xmit_slottime[chan], xmit_persist[chan], xmit_fulldup[chan]); prio = TQ_PRIO_1_LO; pp = tq_remove (chan, TQ_PRIO_0_HI); if (pp != NULL) { prio = TQ_PRIO_0_HI; } else { pp = tq_remove (chan, TQ_PRIO_1_LO); } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", chan, prio, pp); #endif // Shouldn't have NULL here but be careful. if (pp != NULL) { if (ok) { /* * Channel is clear and we have lock on output device. * * If destination is "SPEECH" send info part to speech synthesizer. * If destination is "MORSE" send as morse code. * If destination is "DTMF" send as Touch Tones. */ int ssid, wpm, speed; switch (frame_flavor(pp)) { case FLAVOR_SPEECH: xmit_speech (chan, pp); break; case FLAVOR_MORSE: ssid = ax25_get_ssid(pp, AX25_DESTINATION); wpm = (ssid > 0) ? (ssid * 2) : MORSE_DEFAULT_WPM; // This is a bit of a hack so we don't respond too quickly for APRStt. // It will be sent in high priority queue while a beacon wouldn't. // Add a little delay so user has time release PTT after sending #. // This and default txdelay would give us a second. if (prio == TQ_PRIO_0_HI) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("APRStt morse xmit delay hack...\n"); SLEEP_MS (700); } xmit_morse (chan, pp, wpm); break; case FLAVOR_DTMF: speed = ax25_get_ssid(pp, AX25_DESTINATION); if (speed == 0) speed = 5; // default half of maximum if (speed > 10) speed = 10; xmit_dtmf (chan, pp, speed); break; case FLAVOR_APRS_DIGI: xmit_ax25_frames (chan, prio, pp, 1); /* 1 means don't bundle */ break; case FLAVOR_APRS_NEW: case FLAVOR_OTHER: default: xmit_ax25_frames (chan, prio, pp, 256); break; } // Corresponding lock is in wait_for_clear_channel. dw_mutex_unlock (&(audio_out_dev_mutex[ACHAN2ADEV(chan)])); } else { /* * Timeout waiting for clear channel. * Discard the packet. * Display with ERROR color rather than XMIT color. */ char stemp[1024]; /* max size needed? */ int info_len; unsigned char *pinfo; text_color_set(DW_COLOR_ERROR); dw_printf ("Waited too long for clear channel. Discarding packet below.\n"); ax25_format_addrs (pp, stemp); info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_INFO); dw_printf ("[%d%c] ", chan, (prio==TQ_PRIO_0_HI) ? 'H' : 'L'); dw_printf ("%s", stemp); /* stations followed by : */ ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); dw_printf ("\n"); ax25_delete (pp); } /* wait for clear channel error. */ } /* Have pp */ } /* while queue not empty */ } /* while 1 */ return 0; /* unreachable but quiet the warning. */ } /* end xmit_thread */ /*------------------------------------------------------------------- * * Name: xmit_ax25_frames * * Purpose: After we have a clear channel, and possibly waited a random time, * we transmit one or more frames. * * Inputs: chan - Channel number. * * prio - Priority of the first frame. * Subsequent frames could be different. * * pp - Packet object pointer. * It will be deleted so caller should not try * to reference it after this. * * max_bundle - Max number of frames to bundle into one transmission. * * Description: Turn on transmitter. * Send flags for TXDELAY time. * Send the first packet, given by pp. * Possibly send more packets from either queue. * Send flags for TXTAIL time. * Turn off transmitter. * * * How many frames in one transmission? (for APRS) * * Should we send multiple frames in one transmission if we * have more than one sitting in the queue? At first I was thinking * this would help reduce channel congestion. I don't recall seeing * anything in the APRS specifications allowing or disallowing multiple * frames in one transmission. I can think of some scenarios * where it might help. I can think of some where it would * definitely be counter productive. * * What to others have to say about this topic? * * "For what it is worth, the original APRSdos used a several second random * generator each time any kind of packet was generated... This is to avoid * bundling. Because bundling, though good for connected packet, is not good * on APRS. Sometimes the digi begins digipeating the first packet in the * bundle and steps all over the remainder of them. So best to make sure each * packet is isolated in time from others..." * * Bob, WB4APR * * * Version 0.9: Earlier versions always sent one frame per transmission. * This was fine for APRS but more and more people are now * using this as a KISS TNC for connected protocols. * Rather than having a configuration file item, * we try setting the maximum number automatically. * 1 for digipeated frames, 7 for others. * * Version 1.4: Lift the limit. We could theoretically have a window size up to 127. * If another section pumps out that many quickly we shouldn't * break it up here. Empty out both queues with some exceptions. * * Digipeated APRS, Speech, and Morse code should have * their own separate transmissions. * Everything else can be bundled together. * Different priorities can share a single transmission. * Once we have control of the channel, we might as well keep going. * [High] Priority frames will always go to head of the line, * * Version 1.5: Add full duplex option. * *--------------------------------------------------------------------*/ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) { int pre_flags, post_flags; int num_bits; /* Total number of bits in transmission */ /* including all flags and bit stuffing. */ int duration; /* Transmission time in milliseconds. */ int already; int wait_more; int numframe = 0; /* Number of frames sent during this transmission. */ /* * These are for timing of a transmission. * All are in usual unix time (seconds since 1/1/1970) but higher resolution */ double time_ptt; /* Time when PTT is turned on. */ double time_now; /* Current time. */ int nb; /* * Turn on transmitter. * Start sending leading flag bytes. */ time_ptt = dtime_now (); // TODO: This was written assuming bits/sec = baud. // Does it is need to be scaled differently for PSK? #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", chan, xmit_bits_per_sec[chan]); #endif ptt_set (OCTYPE_PTT, chan, 1); // Inform data link state machine that we are now transmitting. dlq_seize_confirm (chan); // C4.2. "This primitive indicates, to the Data-link State // machine, that the transmission opportunity has arrived." pre_flags = MS_TO_BITS(xmit_txdelay[chan] * 10, chan) / 8; num_bits = hdlc_send_flags (chan, pre_flags, 0); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", xmit_txdelay[chan], pre_flags, num_bits); #endif SLEEP_MS (10); // Give data link state machine a chance to // to stuff more frames into the transmit queue, // in response to dlq_seize_confirm, so // we don't run off the end too soon. /* * Transmit the frame. */ nb = send_one_frame (chan, prio, pp); num_bits += nb; if (nb > 0) numframe++; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe); #endif ax25_delete (pp); /* * See if we can bundle additional frames into this transmission. */ int done = 0; while (numframe < max_bundle && ! done) { /* * Peek at what is available. * Don't remove from queue yet because it might not be eligible. */ prio = TQ_PRIO_1_LO; pp = tq_peek (chan, TQ_PRIO_0_HI); if (pp != NULL) { prio = TQ_PRIO_0_HI; } else { pp = tq_peek (chan, TQ_PRIO_1_LO); } if (pp != NULL) { switch (frame_flavor(pp)) { case FLAVOR_SPEECH: case FLAVOR_MORSE: case FLAVOR_DTMF: case FLAVOR_APRS_DIGI: default: done = 1; // not eligible for bundling. break; case FLAVOR_APRS_NEW: case FLAVOR_OTHER: pp = tq_remove (chan, prio); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", chan, prio, pp); #endif nb = send_one_frame (chan, prio, pp); num_bits += nb; if (nb > 0) numframe++; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe); #endif ax25_delete (pp); break; } } else { done = 1; } } /* * Need TXTAIL because we don't know exactly when the sound is done. */ post_flags = MS_TO_BITS(xmit_txtail[chan] * 10, chan) / 8; nb = hdlc_send_flags (chan, post_flags, 1); num_bits += nb; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", xmit_txtail[chan], post_flags, nb, num_bits); #endif /* * While demodulating is CPU intensive, generating the tones is not. * Example: on the RPi model 1, with 50% of the CPU taken with two receive * channels, a transmission of more than a second is generated in * about 40 mS of elapsed real time. */ audio_wait(ACHAN2ADEV(chan)); /* * Ideally we should be here just about the time when the audio is ending. * However, the innards of "audio_wait" are not satisfactory in all cases. * * Calculate how long the frame(s) should take in milliseconds. */ duration = BITS_TO_MS(num_bits, chan); /* * See how long it has been since PTT was turned on. * Wait additional time if necessary. */ time_now = dtime_now(); already = (int) ((time_now - time_ptt) * 1000.); wait_more = duration - already; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: xmit duration=%d, %d already elapsed since PTT, wait %d more\n", duration, already, wait_more ); #endif if (wait_more > 0) { SLEEP_MS(wait_more); } else if (wait_more < -100) { /* If we run over by 10 mSec or so, it's nothing to worry about. */ /* However, if PTT is still on about 1/10 sec after audio */ /* should be done, something is wrong. */ /* Looks like a bug with the RPi audio system. Never an issue with Ubuntu. */ /* This runs over randomly sometimes. TODO: investigate more fully sometime. */ #ifndef __arm__ text_color_set(DW_COLOR_ERROR); dw_printf ("Transmit timing error: PTT is on %d mSec too long.\n", -wait_more); #endif } /* * Turn off transmitter. */ #if DEBUG text_color_set(DW_COLOR_DEBUG); time_now = dtime_now(); dw_printf ("xmit_thread: Turn off PTT now. Actual time on was %d mS, vs. %d desired\n", (int) ((time_now - time_ptt) * 1000.), duration); #endif ptt_set (OCTYPE_PTT, chan, 0); } /* end xmit_ax25_frames */ /*------------------------------------------------------------------- * * Name: send_one_frame * * Purpose: Send one AX.25 frame. * * Inputs: c - Channel number. * * p - Priority. * * pp - Packet object pointer. Caller will delete it. * * Returns: Number of bits transmitted. * * Description: Caller is responsible for activiating PTT, TXDELAY, * deciding how many frames can be in one transmission, * deactivating PTT. * *--------------------------------------------------------------------*/ static int send_one_frame (int c, int p, packet_t pp) { unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; int flen; char stemp[1024]; /* max size needed? */ int info_len; unsigned char *pinfo; int nb; if (ax25_is_null_frame(pp)) { // Issue 132 - We could end up in a situation where: // Transmitter is already on. // Application wants to send a frame. // dl_seize_request turns into this null frame. // It was being ignored here so the data got stuck in the queue. // I think the solution is to send back a seize confirm here. // It shouldn't hurt if we send it redundantly. // Added for 1.5 beta test 4. dlq_seize_confirm (c); // C4.2. "This primitive indicates, to the Data-link State // machine, that the transmission opportunity has arrived." SLEEP_MS (10); // Give data link state machine a chance to // to stuff more frames into the transmit queue, // in response to dlq_seize_confirm, so // we don't run off the end too soon. return(0); } char ts[100]; // optional time stamp. if (strlen(save_audio_config_p->timestamp_format) > 0) { char tstmp[100]; timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); strlcpy (ts, " ", sizeof(ts)); // space after channel. strlcat (ts, tstmp, sizeof(ts)); } else { strlcpy (ts, "", sizeof(ts)); } ax25_format_addrs (pp, stemp); info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_XMIT); dw_printf ("[%d%c%s] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L', ts); dw_printf ("%s", stemp); /* stations followed by : */ /* Demystify non-APRS. Use same format for received frames in direwolf.c. */ if ( ! ax25_is_aprs(pp)) { ax25_frame_type_t ftype; cmdres_t cr; char desc[80]; int pf; int nr; int ns; ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns); dw_printf ("(%s)", desc); if (ftype == frame_type_U_XID) { struct xid_param_s param; char info2text[150]; xid_parse (pinfo, info_len, ¶m, info2text, sizeof(info2text)); dw_printf (" %s\n", info2text); } else { ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); dw_printf ("\n"); } } else { ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); dw_printf ("\n"); } (void)ax25_check_addresses (pp); /* Optional hex dump of packet. */ if (g_debug_xmit_packet) { text_color_set(DW_COLOR_DEBUG); dw_printf ("------\n"); ax25_hex_dump (pp); dw_printf ("------\n"); } /* * Transmit the frame. */ flen = ax25_pack (pp, fbuf); assert (flen >= 1 && flen <= (int)(sizeof(fbuf))); int send_invalid_fcs2 = 0; if (save_audio_config_p->xmit_error_rate != 0) { float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0 if (save_audio_config_p->xmit_error_rate / 100.0 > r) { send_invalid_fcs2 = 1; text_color_set(DW_COLOR_INFO); dw_printf ("Intentionally sending invalid CRC for frame above. Xmit Error rate = %d per cent.\n", save_audio_config_p->xmit_error_rate); } } nb = hdlc_send_frame (c, fbuf, flen, send_invalid_fcs2); return (nb); } /* end send_one_frame */ /*------------------------------------------------------------------- * * Name: xmit_speech * * Purpose: After we have a clear channel, and possibly waited a random time, * we transmit information part of frame as speech. * * Inputs: c - Channel number. * * pp - Packet object pointer. * It will be deleted so caller should not try * to reference it after this. * * Description: Turn on transmitter. * Invoke the text-to-speech script. * Turn off transmitter. * *--------------------------------------------------------------------*/ static void xmit_speech (int c, packet_t pp) { int info_len; unsigned char *pinfo; /* * Print spoken packet. Prefix by channel. */ char ts[100]; // optional time stamp. if (strlen(save_audio_config_p->timestamp_format) > 0) { char tstmp[100]; timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); strlcpy (ts, " ", sizeof(ts)); // space after channel. strlcat (ts, tstmp, sizeof(ts)); } else { strlcpy (ts, "", sizeof(ts)); } info_len = ax25_get_info (pp, &pinfo); (void)info_len; text_color_set(DW_COLOR_XMIT); dw_printf ("[%d.speech%s] \"%s\"\n", c, ts, pinfo); if (strlen(save_audio_config_p->tts_script) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Text-to-speech script has not been configured.\n"); ax25_delete (pp); return; } /* * Turn on transmitter. */ ptt_set (OCTYPE_PTT, c, 1); /* * Invoke the speech-to-text script. */ xmit_speak_it (save_audio_config_p->tts_script, c, (char*)pinfo); /* * Turn off transmitter. */ ptt_set (OCTYPE_PTT, c, 0); ax25_delete (pp); } /* end xmit_speech */ /* Broken out into separate function so configuration can validate it. */ /* Returns 0 for success. */ int xmit_speak_it (char *script, int c, char *orig_msg) { int err; char cmd[2000]; char *p; char msg[2000]; /* Remove any quotes because it will mess up command line argument parsing. */ strlcpy (msg, orig_msg, sizeof(msg)); for (p=msg; *p!='\0'; p++) { if (*p == '"') *p = ' '; } #if __WIN32__ snprintf (cmd, sizeof(cmd), "%s %d \"%s\" >nul", script, c, msg); #else snprintf (cmd, sizeof(cmd), "%s %d \"%s\"", script, c, msg); #endif //text_color_set(DW_COLOR_DEBUG); //dw_printf ("cmd=%s\n", cmd); err = system (cmd); if (err != 0) { char cwd[1000]; char path[3000]; char *ignore; text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to run text-to-speech script, %s\n", script); ignore = getcwd (cwd, sizeof(cwd)); (void)ignore; strlcpy (path, getenv("PATH"), sizeof(path)); dw_printf ("CWD = %s\n", cwd); dw_printf ("PATH = %s\n", path); } return (err); } /*------------------------------------------------------------------- * * Name: xmit_morse * * Purpose: After we have a clear channel, and possibly waited a random time, * we transmit information part of frame as Morse code. * * Inputs: c - Channel number. * * pp - Packet object pointer. * It will be deleted so caller should not try * to reference it after this. * * wpm - Speed in words per minute. * * Description: Turn on transmitter. * Send text as Morse code. * A small amount of quiet padding will appear at start and end. * Turn off transmitter. * *--------------------------------------------------------------------*/ static void xmit_morse (int c, packet_t pp, int wpm) { int info_len; unsigned char *pinfo; int length_ms, wait_ms; double start_ptt, wait_until, now; char ts[100]; // optional time stamp. if (strlen(save_audio_config_p->timestamp_format) > 0) { char tstmp[100]; timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); strlcpy (ts, " ", sizeof(ts)); // space after channel. strlcat (ts, tstmp, sizeof(ts)); } else { strlcpy (ts, "", sizeof(ts)); } info_len = ax25_get_info (pp, &pinfo); (void)info_len; text_color_set(DW_COLOR_XMIT); dw_printf ("[%d.morse%s] \"%s\"\n", c, ts, pinfo); ptt_set (OCTYPE_PTT, c, 1); start_ptt = dtime_now(); // make txdelay at least 300 and txtail at least 250 ms. length_ms = morse_send (c, (char*)pinfo, wpm, MAXX(xmit_txdelay[c] * 10, 300), MAXX(xmit_txtail[c] * 10, 250)); // there is probably still sound queued up in the output buffers. wait_until = start_ptt + length_ms * 0.001; now = dtime_now(); wait_ms = (int) ( ( wait_until - now ) * 1000 ); if (wait_ms > 0) { SLEEP_MS(wait_ms); } ptt_set (OCTYPE_PTT, c, 0); ax25_delete (pp); } /* end xmit_morse */ /*------------------------------------------------------------------- * * Name: xmit_dtmf * * Purpose: After we have a clear channel, and possibly waited a random time, * we transmit information part of frame as DTMF tones. * * Inputs: c - Channel number. * * pp - Packet object pointer. * It will be deleted so caller should not try * to reference it after this. * * speed - Button presses per second. * * Description: Turn on transmitter. * Send text as touch tones. * A small amount of quiet padding will appear at start and end. * Turn off transmitter. * *--------------------------------------------------------------------*/ static void xmit_dtmf (int c, packet_t pp, int speed) { int info_len; unsigned char *pinfo; int length_ms, wait_ms; double start_ptt, wait_until, now; char ts[100]; // optional time stamp. if (strlen(save_audio_config_p->timestamp_format) > 0) { char tstmp[100]; timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); strlcpy (ts, " ", sizeof(ts)); // space after channel. strlcat (ts, tstmp, sizeof(ts)); } else { strlcpy (ts, "", sizeof(ts)); } info_len = ax25_get_info (pp, &pinfo); (void)info_len; text_color_set(DW_COLOR_XMIT); dw_printf ("[%d.dtmf%s] \"%s\"\n", c, ts, pinfo); ptt_set (OCTYPE_PTT, c, 1); start_ptt = dtime_now(); // make txdelay at least 300 and txtail at least 250 ms. length_ms = dtmf_send (c, (char*)pinfo, speed, MAXX(xmit_txdelay[c] * 10, 300), MAXX(xmit_txtail[c] * 10, 250)); // there is probably still sound queued up in the output buffers. wait_until = start_ptt + length_ms * 0.001; now = dtime_now(); wait_ms = (int) ( ( wait_until - now ) * 1000 ); if (wait_ms > 0) { SLEEP_MS(wait_ms); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Oops. CPU too slow to keep up with DTMF generation.\n"); } ptt_set (OCTYPE_PTT, c, 0); ax25_delete (pp); } /* end xmit_dtmf */ /*------------------------------------------------------------------- * * Name: wait_for_clear_channel * * Purpose: Wait for the radio channel to be clear and any * additional time for collision avoidance. * * Inputs: chan - Radio channel number. * * slottime - Amount of time to wait for each iteration * of the waiting algorithm. 10 mSec units. * * persist - Probability of transmitting. * * fulldup - Full duplex. Just start sending immediately. * * Returns: 1 for OK. 0 for timeout. * * Description: New in version 1.2: also obtain a lock on audio out device. * * New in version 1.5: full duplex. * Just start transmitting rather than waiting for clear channel. * This would only be appropriate when transmit and receive are * using different radio freqencies. e.g. VHF up, UHF down satellite. * * Transmit delay algorithm: * * Wait for channel to be clear. * If anything in high priority queue, bail out of the following. * * Wait slottime * 10 milliseconds. * Generate an 8 bit random number in range of 0 - 255. * If random number <= persist value, return. * Otherwise repeat. * * Example: * * For typical values of slottime=10 and persist=63, * * Delay Probability * ----- ----------- * 100 .25 = 25% * 200 .75 * .25 = 19% * 300 .75 * .75 * .25 = 14% * 400 .75 * .75 * .75 * .25 = 11% * 500 .75 * .75 * .75 * .75 * .25 = 8% * 600 .75 * .75 * .75 * .75 * .75 * .25 = 6% * 700 .75 * .75 * .75 * .75 * .75 * .75 * .25 = 4% * etc. ... * *--------------------------------------------------------------------*/ /* Give up if we can't get a clear channel in a minute. */ /* That's a long time to wait for APRS. */ /* Might need to revisit some day for connected mode file transfers. */ #define WAIT_TIMEOUT_MS (60 * 1000) #define WAIT_CHECK_EVERY_MS 10 static int wait_for_clear_channel (int chan, int slottime, int persist, int fulldup) { int n = 0; /* * For dull duplex we skip the channel busy check and random wait. * We still need to wait if operating in stereo and the other audio * half is busy. */ if ( ! fulldup) { start_over_again: while (hdlc_rec_data_detect_any(chan)) { SLEEP_MS(WAIT_CHECK_EVERY_MS); n++; if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) { return 0; } } //TODO: rethink dwait. /* * Added in version 1.2 - for transceivers that can't * turn around fast enough when using squelch and VOX. */ if (save_audio_config_p->achan[chan].dwait > 0) { SLEEP_MS (save_audio_config_p->achan[chan].dwait * 10); } if (hdlc_rec_data_detect_any(chan)) { goto start_over_again; } /* * Wait random time. * Proceed to transmit sooner if anything shows up in high priority queue. */ while (tq_peek(chan, TQ_PRIO_0_HI) == NULL) { int r; SLEEP_MS (slottime * 10); if (hdlc_rec_data_detect_any(chan)) { goto start_over_again; } r = rand() & 0xff; if (r <= persist) { break; } } } /* * This is to prevent two channels from transmitting at the same time * thru a stereo audio device. * We are not clever enough to combine two audio streams. * They must go out one at a time. * Documentation recommends using separate audio device for each channel rather than stereo. * That also allows better use of multiple cores for receiving. */ // TODO: review this. while ( ! dw_mutex_try_lock(&(audio_out_dev_mutex[ACHAN2ADEV(chan)]))) { SLEEP_MS(WAIT_CHECK_EVERY_MS); n++; if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) { return 0; } } return 1; } /* end wait_for_clear_channel */ /* end xmit.c */ direwolf-1.5+dfsg/xmit.h000066400000000000000000000010111347750676600152610ustar00rootroot00000000000000 #ifndef XMIT_H #define XMIT_H 1 #include "audio.h" /* for struct audio_s */ extern void xmit_init (struct audio_s *p_modem, int debug_xmit_packet); extern void xmit_set_txdelay (int channel, int value); extern void xmit_set_persist (int channel, int value); extern void xmit_set_slottime (int channel, int value); extern void xmit_set_txtail (int channel, int value); extern void xmit_set_fulldup (int channel, int value); extern int xmit_speak_it (char *script, int c, char *msg); #endif /* end xmit.h */