commit 8978f2de6c3aed3847678e20377f26f4c724cd38 Author: WB2OSZ Date: Sun Jul 26 20:35:07 2015 -0400 Version 1.0 - Initial commit Changes to be committed: new file: .gitattributes new file: .gitignore new file: APRStt-Implementation-Notes.pdf new file: CHANGES.txt new file: LICENSE-dire-wolf.txt new file: LICENSE-other.txt new file: Makefile.linux new file: Makefile.win new file: Quick-Start-Guide-Windows.pdf new file: Raspberry-Pi-APRS.pdf new file: User-Guide.pdf new file: aclients.c new file: aprs_tt.c new file: aprs_tt.h new file: atest.c new file: audio.c new file: audio.h new file: audio_win.c new file: ax25_pad.c new file: ax25_pad.h new file: beacon.c new file: beacon.h new file: config.c new file: config.h new file: decode_aprs.c new file: decode_aprs.h new file: dedupe.c new file: dedupe.h new file: demod.c new file: demod.h new file: demod_9600.c new file: demod_9600.h new file: demod_afsk.c new file: demod_afsk.h new file: digipeater.c new file: digipeater.h new file: direwolf.c new file: direwolf.conf new file: direwolf.desktop new file: direwolf.h new file: dsp.c new file: dsp.h new file: dtmf.c new file: dtmf.h new file: dw-icon.ico new file: dw-icon.png new file: dw-icon.rc new file: dw-start.sh new file: dwgps.c new file: dwgps.h new file: encode_aprs.c new file: encode_aprs.h new file: fcs_calc.c new file: fcs_calc.h new file: fsk_demod_agc.h new file: fsk_demod_state.h new file: fsk_filters.h new file: fsk_gen_filter.h new file: gen_packets.c new file: gen_tone.c new file: gen_tone.h new file: hdlc_rec.c new file: hdlc_rec.h new file: hdlc_rec2.c new file: hdlc_rec2.h new file: hdlc_send.c new file: hdlc_send.h new file: igate.c new file: igate.h new file: kiss.c new file: kiss.h new file: kiss_frame.c new file: kiss_frame.h new file: kissnet.c new file: kissnet.h new file: latlong.c new file: latlong.h new file: ll2utm.c new file: misc/README-dire-wolf.txt new file: misc/strcasestr.c new file: misc/strsep.c new file: misc/strtok_r.c new file: morse.c new file: multi_modem.c new file: multi_modem.h new file: ptt.c new file: ptt.h new file: pttest.c new file: rdq.c new file: rdq.h new file: redecode.c new file: redecode.h new file: regex/COPYING new file: regex/INSTALL new file: regex/LICENSES new file: regex/NEWS new file: regex/README new file: regex/README-dire-wolf.txt new file: regex/re_comp.h new file: regex/regcomp.c new file: regex/regex.c new file: regex/regex.h new file: regex/regex_internal.c new file: regex/regex_internal.h new file: regex/regexec.c new file: rrbb.c new file: rrbb.h new file: server.c new file: server.h new file: symbols-new.txt new file: symbols.c new file: symbols.h new file: symbolsX.txt new file: textcolor.c new file: textcolor.h new file: tocalls.txt new file: tq.c new file: tq.h new file: tt_text.c new file: tt_text.h new file: tt_user.c new file: tt_user.h new file: tune.h new file: udp_test.c new file: utm/LatLong-UTMconversion.c new file: utm/LatLong-UTMconversion.h new file: utm/README.txt new file: utm/SwissGrid.cpp new file: utm/UTMConversions.cpp new file: utm/constants.h new file: utm2ll.c new file: version.h new file: xmit.c new file: xmit.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdb0cab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2322864 --- /dev/null +++ b/.gitignore @@ -0,0 +1,85 @@ +# Custom +*.docx +z* +*.log +*bak* +*~ +*.xlsx +*.stackdump + +# 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 + +# ========================= +# 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 diff --git a/APRStt-Implementation-Notes.pdf b/APRStt-Implementation-Notes.pdf new file mode 100644 index 0000000..7ac41bc Binary files /dev/null and b/APRStt-Implementation-Notes.pdf differ diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..228f172 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,167 @@ +---------------- +Revision history +---------------- + + +----------- +Version 1.0a May 2014 +----------- + +* Bug fix: + +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. + + + +----------- +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 +----------- + + +* 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. + + +* Bugs Fixed: + +None. + + + +* 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 +----------- + + +More error checking and messages for invalid APRS data. + + +----------- +Version 0.4 +----------- + +First general availability. + diff --git a/LICENSE-dire-wolf.txt b/LICENSE-dire-wolf.txt new file mode 100644 index 0000000..b8b347f --- /dev/null +++ b/LICENSE-dire-wolf.txt @@ -0,0 +1,281 @@ + 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 + diff --git a/LICENSE-other.txt b/LICENSE-other.txt new file mode 100644 index 0000000..c4ec456 --- /dev/null +++ b/LICENSE-other.txt @@ -0,0 +1,5 @@ +The 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. \ No newline at end of file diff --git a/Makefile.linux b/Makefile.linux new file mode 100644 index 0000000..d7c29e2 --- /dev/null +++ b/Makefile.linux @@ -0,0 +1,289 @@ +# +# Makefile for Linux version of Dire Wolf. +# + +all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients + +CC = gcc + +# +# 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. +# +# Let's look at impact of various optimization levels. +# +# Benchmark results with Ubuntu gcc version 4.6.3, 32 bit platform. +# Intel(R) Celeron(R) CPU 2.53GHz. Appears to have only 32 bit instructions. +# +# seconds options, comments +# ------ ----------------- +# 123 -O2 +# 128 -O3 Slower than -O2 ? +# 123 -Ofast (should be same as -O3 -ffastmath) +# 126 -Ofast -march=pentium +# 88 -Ofast -msse +# 108 -Ofast -march=pentium -msse +# 88 -Ofast -march=pentium3 (this implies -msse) +# 89 -Ofast -march=pentium3 -msse +# +# +# Raspberry Pi, ARM11 (ARMv6 + VFP2) +# gcc (Debian 4.6.3-14+rpi1) 4.6.3 +# +# seconds options, comments +# ------ ----------------- +# 1015 -O2 +# 948 -O3 +# 928 -Ofast +# 937 -Ofast -fmpu=vfp (worse, no option for vfp2) +# +# Are we getting any vectorizing? +# + + + +# +# Release 0.9 added a new feature than can consume a lot of CPU +# power: multiple AFSK demodulators running in parallel. +# These spend a lot of time spinning around in little loops +# calculating the sums of products for the DSP filters. +# +# 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. +# Here we find out if the gcc compiler is generating code +# for the i386. If so, we add the option to assume we will +# have at least a Pentium 3 to run on. +# +# When generating code for the x86_64 target, it is automatically +# assumed that the SSE instructions are available. +# +# If you are using gcc version 4.6 or later, you might get a +# small improvement by using the new "-Ofast" option that is +# not available in older compilers. +# "-O3" is used here for compatibility with older compilers. +# +# You might also get some improvement by using "-march=native" +# to fine tune the application for your particular type of +# hardware. +# +# If you are planning to distribute the binary version to +# other people (in some ham radio software collection), avoid +# fine tuning it for your particular computer. It could +# cause compatibility issues for those with older computers. +# + +arch := $(shell echo | gcc -E -dM - | grep __i386__) + +ifneq ($(arch),) +CFLAGS := -DUSE_ALSA -O3 -march=pentium3 -pthread +else +CFLAGS := -DUSE_ALSA -O3 -pthread +endif + + +# Uncomment following lines to enable GPS interface. +# DO NOT USE THIS. Still needs more work. +#CFLAGS += -DENABLE_GPS +#LDLIBS += -lgps + + +# Name of current directory. +# Used to generate zip file name for distribution. + +z=$(notdir ${CURDIR}) + + +# Main application. + +direwolf : direwolf.o config.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \ + hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o \ + fcs_calc.o ax25_pad.o \ + decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \ + gen_tone.o audio.o digipeater.o dedupe.o tq.o xmit.o \ + ptt.o beacon.o dwgps.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 \ + utm.a + $(CC) $(CFLAGS) -o $@ $^ -lpthread -lrt -lasound $(LDLIBS) -lm + + +# Optimization for slow processors. + +demod.o : fsk_fast_filter.h + +demod_afsk.o : fsk_fast_filter.h + + +fsk_fast_filter.h : demod_afsk.c + $(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c -lm + ./gen_fff > fsk_fast_filter.h + + + +utm.a : LatLong-UTMconversion.o + ar -cr $@ $^ + +LatLong-UTMconversion.o : utm/LatLong-UTMconversion.c + $(CC) $(CFLAGS) -c -o $@ $^ + + +# Optional install step. +# TODO: Review file locations. +# TODO: Handle Linux variations correctly. +# The Raspberry Pi has ~/Desktop but Ubuntu does not. +# For now, just put reference to it at the end so only last step fails. + +install : direwolf decode_aprs tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop + sudo install direwolf /usr/local/bin + sudo install decode_aprs /usr/local/bin + sudo install text2tt /usr/local/bin + sudo install tt2text /usr/local/bin + sudo install ll2utm /usr/local/bin + sudo install utm2ll /usr/local/bin + sudo install aclients /usr/local/bin + sudo install -D --mode=644 tocalls.txt /usr/share/direwolf/tocalls.txt + sudo install -D --mode=644 symbols-new.txt /usr/share/direwolf/symbols-new.txt + sudo install -D --mode=644 symbolsX.txt /usr/share/direwolf/symbolsX.txt + sudo install -D --mode=644 dw-icon.png /usr/share/direwolf/dw-icon.png + sudo install -D --mode=644 direwolf.desktop /usr/share/applications/direwolf.desktop + cp direwolf.conf ~ + cp dw-start.sh ~ + sudo install -D --mode=644 CHANGES.txt /usr/local/share/doc/direwolf/CHANGES.txt + sudo install -D --mode=644 LICENSE-dire-wolf.txt /usr/local/share/doc/direwolf/LICENSE-dire-wolf.txt + sudo install -D --mode=644 LICENSE-other.txt /usr/local/share/doc/direwolf/LICENSE-other.txt + sudo install -D --mode=644 User-Guide.pdf /usr/local/share/doc/direwolf/User-Guide.pdf + sudo install -D --mode=644 Raspberry-Pi-APRS.pdf /usr/local/share/doc/direwolf/Raspberry-Pi-APRS.pdf + sudo install -D --mode=644 APRStt-Implementation-Notes.pdf /usr/local/share/doc/direwolf/APRStt-Implementation-Notes.pdf + sudo install -D --mode=644 Quick-Start-Guide-Windows.pdf /usr/local/share/doc/direwolf/Quick-Start-Guide-Windows.pdf + ln -f -s /usr/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop + + +# Separate application to decode raw data. + +decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c + $(CC) $(CFLAGS) -o decode_aprs -DTEST $^ -lm + + + +# Convert between text and touch tone representation. + +text2tt : tt_text.c + $(CC) $(CFLAGS) -DENC_MAIN -o text2tt tt_text.c + +tt2text : tt_text.c + $(CC) $(CFLAGS) -DDEC_MAIN -o tt2text tt_text.c + + +# Convert between Latitude/Longitude and UTM coordinates. + +ll2utm : ll2utm.c utm.a + $(CC) $(CFLAGS) -I utm -o $@ $^ -lm + +utm2ll : utm2ll.c utm.a + $(CC) $(CFLAGS) -I utm -o $@ $^ -lm + + + +# Test application to generate sound. + +gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c textcolor.c + $(CC) $(CFLAGS) -o $@ $^ -lasound -lm + +demod.o : tune.h +demod_afsk.o : tune.h +demod_9600.o : 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 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 AFSK demodulator + + +atest : 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 symbols.c textcolor.c + $(CC) $(CFLAGS) -o $@ $^ -lm + time ./atest ../direwolf-0.2/02_Track_2.wav + +# Unit test for inner digipeater algorithm + + +dtest : digipeater.c ax25_pad.c dedupe.c fcs_calc.c tq.c textcolor.c + $(CC) $(CFLAGS) -DTEST -o $@ $^ + ./dtest + + +# 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 -lrt + ./udptest + + +# 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 $@ $^ + + +SRCS = direwolf.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c multi_modem.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c \ + server.c kiss.c kissnet.c kiss_frame.c hdlc_send.c fcs_calc.c gen_tone.c audio.c \ + digipeater.c dedupe.c tq.c xmit.c beacon.c encode_aprs.c latlong.c encode_aprs.c latlong.c + + +depend : $(SRCS) + makedepend $(INCLUDES) $^ + + +clean : + rm -f direwolf decode_aprs text2tt tt2text ll2utm utm2ll fsk_fast_filter.h *.o *.a + echo " " > tune.h + + +# Package it up for distribution. + +dist-src : CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf Raspberry-Pi-APRS.pdf \ + direwolf.desktop dw-start.sh + rm -f fsk_fast_filter.h + echo " " > tune.h + rm -f ../$z-src.zip + (cd .. ; zip $z-src.zip $z/CHANGES.txt $z/LICENSE* \ + $z/User-Guide.pdf $z/Quick-Start-Guide-Windows.pdf $z/Raspberry-Pi-APRS.pdf \ + $z/Makefile* $z/*.c $z/*.h $z/regex/* $z/misc/* $z/utm/* \ + $z/*.conf $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ + $z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \ + $z/direwolf.desktop $z/dw-start.sh ) + + +#User-Guide.pdf : User-Guide.docx +# echo "***** User-Guide.pdf is out of date *****" + +#Quick-Start-Guide-Windows.pdf : Quick-Start-Guide-Windows.docx +# echo "***** Quick-Start-Guide-Windows.pdf is out of date *****" + +#Raspberry-Pi-APRS.pdf : Raspberry-Pi-APRS.docx +# echo "***** Raspberry-Pi-APRS.pdf is out of date *****" + + +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 + diff --git a/Makefile.win b/Makefile.win new file mode 100644 index 0000000..043a38a --- /dev/null +++ b/Makefile.win @@ -0,0 +1,326 @@ +# +# 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 + + +# 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. + +#TODO: put -Ofast back in. + +CC = gcc +#CFLAGS = -g -Wall -Ofast -march=pentium3 -msse -Iregex -mthreads -DUSE_REGEX_STATIC +CFLAGS = -g -Wall -march=pentium3 -msse -Iregex -mthreads -DUSE_REGEX_STATIC +AR = ar + + +# +# 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. +# + +# Name of zip file for distribution. + +z=$(notdir ${CURDIR}) + + + +# Main application. + +demod.o : fsk_demod_state.h +demod_9600.o : fsk_demod_state.h +demod_afsk.o : fsk_demod_state.h + + +direwolf : direwolf.o config.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \ + hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o \ + fcs_calc.o ax25_pad.o \ + decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \ + gen_tone.o audio_win.o digipeater.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 \ + dw-icon.o regex.a misc.a utm.a + $(CC) $(CFLAGS) -g -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 : demod_afsk.c + $(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c + ./gen_fff > fsk_fast_filter.h + + +utm.a : LatLong-UTMconversion.o + ar -cr $@ $^ + +LatLong-UTMconversion.o : utm/LatLong-UTMconversion.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 +# + +regex.a : regex.o + ar -cr $@ $^ + +regex.o : regex/regex.c + $(CC) $(CFLAGS) -Dbool=int -Dtrue=1 -Dfalse=0 -c -o $@ $^ + + +# There are also a couple other functions in the misc +# subdirectory that are missing on Windows. + +misc.a : strsep.o strtok_r.o strcasestr.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 $@ $^ + + + +# Separate application to decode raw data. + +decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c regex.a misc.a + $(CC) $(CFLAGS) -o decode_aprs -DTEST $^ + + +# Convert between text and touch tone representation. + +text2tt : tt_text.c + $(CC) $(CFLAGS) -DENC_MAIN -o text2tt tt_text.c + +tt2text : tt_text.c + $(CC) $(CFLAGS) -DDEC_MAIN -o tt2text tt_text.c + + +# Convert between Latitude/Longitude and UTM coordinates. + +ll2utm : ll2utm.c utm.a + $(CC) $(CFLAGS) -I utm -o $@ $^ + +utm2ll : utm2ll.c utm.a + $(CC) $(CFLAGS) -I utm -o $@ $^ + + +# Test application to generate sound. + +gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o textcolor.o misc.a regex.a + $(CC) $(CFLAGS) -o $@ $^ + +# For tweaking the demodulator. + +demod.o : tune.h +demod_9600.o : tune.h +demod_afsk.o : tune.h + + +testagc : atest.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 regex.a misc.a \ + fsk_demod_agc.h + rm -f atest.exe + $(CC) $(CFLAGS) -DNOFIX -o atest $^ + ./atest ../direwolf-0.2/02_Track_2.wav | grep "packets decoded in" >atest.out + + +noisy3.wav : gen_packets + ./gen_packets -B 300 -n 100 -o noisy3.wav + +testagc3 : atest.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 regex.a misc.a \ + tune.h + rm -f atest.exe + $(CC) $(CFLAGS) -o atest $^ + ./atest -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out + + +noisy96.wav : gen_packets + ./gen_packets -B 9600 -n 100 -o noisy96.wav + +testagc9 : atest.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 regex.a misc.a \ + tune.h + rm -f atest.exe + $(CC) $(CFLAGS) -o atest $^ + ./atest -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out + #./atest -B 9600 noisy96.wav | grep "packets decoded in" >atest.out + + +# Unit test for AFSK demodulator + + +atest : atest.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 misc.a regex.a \ + fsk_fast_filter.h + $(CC) $(CFLAGS) -o $@ $^ + echo " " > tune.h + ./atest ..\\direwolf-0.2\\02_Track_2.wav + +atest9 : atest.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 misc.a regex.a \ + fsk_fast_filter.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 + + +dtest : digipeater.c ax25_pad.c dedupe.c fcs_calc.c tq.c textcolor.c misc.a regex.a + $(CC) $(CFLAGS) -DTEST -o $@ $^ + ./dtest + rm dtest.exe + +# Unit test for APRStt. + +ttest : aprs_tt.c tt_text.c misc.a utm.a + $(CC) $(CFLAGS) -DTT_MAIN -o ttest aprs_tt.c tt_text.c misc.a utm.a + + +# Unit test for IGate + +itest : igate.c textcolor.c ax25_pad.c fcs_calc.c misc.a regex.a + $(CC) $(CFLAGS) -DITEST -g -o $@ $^ -lwinmm -lws2_32 + + +# 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 -lrt + ./udptest + + +# 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) -g -o $@ $^ -lwinmm -lws2_32 + + +SRCS = direwolf.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c \ + hdlc_rec2.c multi_modem.c redecode.c rdq.c rrbb.c \ + fcs_calc.c ax25_pad.c decode_aprs.c symbols.c \ + server.c kiss.c kissnet.c kiss_frame.c hdlc_send.c fcs_calc.c gen_tone.c audio_win.c \ + digipeater.c dedupe.c tq.c xmit.c beacon.c \ + encode_aprs.c latlong.c \ + dtmf.c aprs_tt.c tt_text.c igate.c + + +depend : $(SRCS) + makedepend $(INCLUDES) $^ + + +clean : + rm -f *.o *.a *.exe fsk_fast_filter.h noisy96.wav + echo " " > tune.h + + +# Package it up for distribution: Prebuilt Windows & source versions. + + +dist-win : direwolf.exe decode_aprs.exe CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf \ + Raspberry-Pi-APRS.pdf APRStt-Implementation-Notes.pdf + rm -f ../$z-win.zip + zip ../$z-win.zip CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf \ + Raspberry-Pi-APRS.pdf APRStt-Implementation-Notes.pdf LICENSE* *.conf \ + direwolf.exe decode_aprs.exe tocalls.txt symbols-new.txt symbolsX.txt \ + text2tt.exe tt2text.exe ll2utm.exe utm2ll.exe aclients.exe + +dist-src : CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf Raspberry-Pi-APRS.pdf \ + APRStt-Implementation-Notes.pdf \ + direwolf.desktop dw-start.sh \ + tocalls.txt symbols-new.txt symbolsX.txt + rm -f fsk_fast_filter.h + echo " " > tune.h + rm -f ../$z-src.zip + (cd .. ; zip $z-src.zip \ + $z/CHANGES.txt $z/LICENSE* \ + $z/User-Guide.pdf $z/Quick-Start-Guide-Windows.pdf \ + $z/Raspberry-Pi-APRS.pdf $z/APRStt-Implementation-Notes.pdf \ + $z/Makefile* $z/*.c $z/*.h $z/regex/* $z/misc/* $z/utm/* \ + $z/*.conf $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ + $z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \ + $z/direwolf.desktop $z/dw-start.sh ) + + + +User-Guide.pdf : User-Guide.docx + echo "***** User-Guide.pdf is out of date *****" + +Quick-Start-Guide-Windows.pdf : Quick-Start-Guide-Windows.docx + echo "***** Quick-Start-Guide-Windows.pdf is out of date *****" + +Raspberry-Pi-APRS.pdf : Raspberry-Pi-APRS.docx + echo "***** Raspberry-Pi-APRS.pdf is out of date *****" + +APRStt-Implementation-Notes.pdf : APRStt-Implementation-Notes.docx + echo "***** APRStt-Implementation-Notes.pdf is out of date *****" + + +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 + + + diff --git a/Quick-Start-Guide-Windows.pdf b/Quick-Start-Guide-Windows.pdf new file mode 100644 index 0000000..6d155f0 Binary files /dev/null and b/Quick-Start-Guide-Windows.pdf differ diff --git a/Raspberry-Pi-APRS.pdf b/Raspberry-Pi-APRS.pdf new file mode 100644 index 0000000..ef01cce Binary files /dev/null and b/Raspberry-Pi-APRS.pdf differ diff --git a/User-Guide.pdf b/User-Guide.pdf new file mode 100644 index 0000000..ff3b46f Binary files /dev/null and b/User-Guide.pdf differ diff --git a/aclients.c b/aclients.c new file mode 100644 index 0000000..1389ebd --- /dev/null +++ b/aclients.c @@ -0,0 +1,825 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 . +// + + +/*------------------------------------------------------------------ + * + * 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 8000=AGWPE 8002=DireWolf COM1=D710A + * + * This will connect to multiple physical or virtual + * TNCs, read packets from them, and display results. + * + *---------------------------------------------------------------*/ + + + +/* + * Native Windows: Use the Winsock interface. + * Linux: Use the BSD socket interface. + * Cygwin: Can use either one. + */ + + +#if __WIN32__ + +#include +// default is 0x0400 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */ +#include +#else +//#define __USE_XOPEN2KXSI 1 +//#define __USE_XOPEN 1 +#include +#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" +#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__ + sprintf (pStringBuf, "%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__ + sprintf (pStringBuf, "%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: + sprintf (pStringBuf, "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: aclients 8000=AGWPE 8002=DireWolf COM1=D710A + * + * Each command line argument is TCP port number or a + * serial port name. Follow by = and a text description + * of what is connected. + * + * For now, everything is assumed to be on localhost. + * Maybe someday we might recognize host:port=description. + * + *---------------------------------------------------------------*/ + +#define MAX_CLIENTS 6 + +/* Obtained from the command line. */ + +static int num_clients; + +static char hostname[MAX_CLIENTS][50]; +static char port[MAX_CLIENTS][30]; +static char description[MAX_CLIENTS][50]; + +#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'; + +#if __WIN32__ + send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0); +#else + (void)write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd)); +#endif + + +/* + * Print all of the monitored packets. + */ + + while (1) { + int n; + +#if __WIN32__ + n = recv (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0); +#else + n = read (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd)); +#endif + + if (n != sizeof(mon_cmd)) { + printf ("Read error, client %d received %d command bytes.\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 < sizeof(data)); + + if (mon_cmd.data_len > 0) { +#if __WIN32__ + n = recv (server_sock, data, mon_cmd.data_len, 0); +#else + n = read (server_sock, data, mon_cmd.data_len); +#endif + + 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; + + //printf ("server %d, portx = %d\n", my_index, mon_cmd.portx); + + use_chan == mon_cmd.portx; + pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, -1); + 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 = ' '; + } +#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; + + + 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.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 */ diff --git a/aprs_tt.c b/aprs_tt.c new file mode 100644 index 0000000..c68b2eb --- /dev/null +++ b/aprs_tt.c @@ -0,0 +1,1436 @@ +// +// 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: aprs_tt.c + * + * Purpose: APRStt gateway. + * + * Description: Transfer touch tone messages into the APRS network. + * + * References: This is based upon APRStt (TM) documents with some + * artistic freedom. + * + * http://www.aprs.org/aprstt.html + * + *---------------------------------------------------------------*/ + +// TODO: clean up terminolgy. +// "Message" has a specific meaning in APRS and this is not it. +// Touch Tone sequence might be appropriate. +// What do we call the parts separated by * key? Entry? Field? + + +#include +#include +#include +#include +#include + + +#include +#include + +#include "direwolf.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" + + +#if __WIN32__ +char *strtok_r(char *str, const char *delim, char **saveptr); +#endif + +#include "utm/LatLong-UTMconversion.h" + + +//TODO: #include "tt_user.h" + + + +/* + * Touch Tone sequences are accumulated here until # terminator found. + * Kept separate for each audio channel. + */ + +#define MAX_MSG_LEN 100 + +static char msg_str[MAX_CHANS][MAX_MSG_LEN+1]; +static int msg_len[MAX_CHANS]; + +static void aprs_tt_message (int chan, char *msg); +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_location (char *e); +static int parse_comment (char *e); +static int expand_macro (char *e); +static void raw_tt_data_to_app (int chan, char *msg); +static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr); + + + + +/*------------------------------------------------------------------ + * + * 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_MACRO, "xxyyy", .macro.definition = "B9xx*AB166*AA2B4C5B3B0Ayyy" }, +}; +#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 + memcpy (&tt_config, p, sizeof(struct tt_config_s)); +#endif + for (c=0; c= 0 && chan < MAX_CHANS); + + +// 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 == '#') { + +/* Process complete message. */ + + aprs_tt_message (chan, msg_str[chan]); + msg_len[chan] = 0; + msg_str[chan][0] = '\0'; + } + } + else { + +/* Idle time. Poll occasionally for processing. */ + + poll_period++; + if (poll_period >= 39) { + poll_period = 0; + tt_user_background (); + } + } + +} /* end aprs_tt_button */ + +#endif + +/*------------------------------------------------------------------ + * + * Name: aprs_tt_message + * + * 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 message. + * It should have one or more fields separatedy by * + * and terminated by a final # like these: + * + * callsign # + * entry1 * callsign # + * entry1 * entry * callsign # + * + *----------------------------------------------------------------*/ + +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; + +static double m_longitude; +static double m_latitude; +static char m_comment[200]; +static char m_freq[12]; +static char m_mic_e; +static char m_dao[6]; +static int m_ssid; + +//#define G_UNKNOWN -999999 + + + +void aprs_tt_message (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; + +/* + * This takes the place of the usual line with audio level. + * Do it here, rather than in process_rec_frame, so any + * error messages are associated with the DTMF message + * rather than the most recent regular AX.25 frame. + */ + +#ifndef TT_MAIN + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\nDTMF message\n"); +#endif + +/* + * The parse functions will fill these in. + */ + strcpy (m_callsign, ""); + m_symtab_or_overlay = '\\'; + m_symbol_code = 'A'; + m_longitude = G_UNKNOWN; + m_latitude = G_UNKNOWN; + strcpy (m_comment, ""); + strcpy (m_freq, ""); + m_mic_e = ' '; + strcpy (m_dao, "!T !"); /* start out unknown */ + m_ssid = 12; + +/* + * Send raw touch tone data to application. + */ + raw_tt_data_to_app (chan, msg); + +/* + * Parse the touch tone sequence. + */ + err = parse_fields (msg); + +#if defined(DEBUG) || defined(TT_MAIN) + 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); +#endif + + + if (err == 0) { + +/* + * Digested successfully. Add to our list of users and schedule transmissions. + */ + +#ifndef TT_MAIN + err = tt_user_heard (m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_latitude, m_longitude, + m_freq, m_comment, m_mic_e, m_dao); +#endif + } + + // TODO send response to user. + // err == 0 OK, others, suitable error response. + + +} /* end aprs_tt_message */ + + +/*------------------------------------------------------------------ + * + * 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 separatedy 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; + + strcpy (stemp, msg); + e = strtok_r (stemp, "*#", &save); + while (e != NULL) { + + switch (*e) { + + case 'A': + + switch (e[1]) { + case 'A': + parse_object_name (e); + break; + case 'B': + parse_symbol (e); + break; + default: + parse_callsign (e); + break; + } + break; + + case 'B': + parse_location (e); + break; + + case 'C': + parse_comment (e); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + expand_macro (e); + 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 ("Entry does not start with A, B, C, or digit: \"%s\"\n", msg); + return (TT_ERROR_D_MSG); + + } + + e = strtok_r (NULL, "*#", &save); + } + + 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. + * + *----------------------------------------------------------------*/ + +static int expand_macro (char *e) +{ + int len; + int ipat; + char xstr[20], ystr[20], zstr[20], bstr[20], dstr[20]; + char stemp[MAX_MSG_LEN+1]; + char *d, *s; + + + 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); + + if (ipat >= 0) { + + 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 ("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. + */ + + strcpy (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': + strcat (stemp, xstr); + break; + case 'y': + strcat (stemp, ystr); + break; + case 'z': + strcat (stemp, zstr); + break; + default: + { + char c1[2]; + c1[0] = *d; + c1[1] = '\0'; + strcat (stemp, c1); + } + 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 callsign or object name from touch tone message. + * + * 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'. + * + * 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; + int c_length; + 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])) { + strcpy (m_callsign, e+1); + 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 = 'A'; + m_symtab_or_overlay = stemp[0]; + } + else { + m_symbol_code = 'A'; + 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 = 'A'; + 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 = 'A'; + 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 message. + * + * 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], 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 message. + * + * 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[4]; + 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_location + * + * Purpose: Extract location from touch tone message. + * + * Inputs: e - An "entry" extracted from a complete + * APRStt messsage. + * In this case, it should start with "B". + * + * Outputs: m_latitude + * m_longitude + * + * 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 4 groups: + * + * * points + * * vector + * * grid + * * utm + * + *----------------------------------------------------------------*/ + + + +/* Average radius of earth in meters. */ +#define R 6371000. + +/* Convert between degrees and radians. */ + +#define D2R(a) ((a) * 2. * M_PI / 360.) +#define R2D(a) ((a) * 360. / (2*M_PI)) + + +static int parse_location (char *e) +{ + int len; + int ipat; + char xstr[20], ystr[20], zstr[20], bstr[20], dstr[20]; + double x, y, dist, bearing; + double lat0, lon0; + double lat9, lon9; + double easting, northing; + + assert (*e == 'B'); + + m_dao[2] = e[0]; + m_dao[3] = e[1]; /* Type of location. e.g. !TB6! */ + /* Will be changed by point types. */ + + len = strlen(e); + + ipat = find_ttloc_match (e, xstr, ystr, zstr, bstr, dstr); + 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. */ + + 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 */ + + 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)))); + break; + + case TTLOC_GRID: + + if (strlen(xstr) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Missing X coordinate.\n"); + strcpy (xstr, "0"); + } + if (strlen(ystr) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Missing Y coordinate.\n"); + strcpy (ystr, "0"); + } + + 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.); + + 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. */ + strcpy (xstr, "5"); + } + 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. */ + strcpy (ystr, "5"); + } + + 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; + + UTMtoLL (WSG84, northing, easting, tt_config.ttloc_ptr[ipat].utm.zone, + &m_latitude, &m_longitude); + break; + + default: + assert (0); + } + return (0); + } + + /* Send reject sound. */ + /* Does not match any location specification. */ + + 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". + * + * 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) +{ + int ipat; /* Index into patterns from configuration file */ + int len; /* Length of pattern we are trying to match. */ + int match; + char mc; + int k; + +//TODO: remove dw_printf ("find_ttloc_match: e=%s\n", e); + + for (ipat=0; ipat 0) { + for (c=m_callsign, s=src; *c != '\0' && strlen(src) < 6; c++) { + if (isupper(*c) || isdigit(*c)) { + *s++ = *c; + *s = '\0'; + } + } + } + else { + strcpy (src, "APRSTT"); + } + + + // TODO: test this. + + err= symbols_into_dest (m_symtab_or_overlay, m_symbol_code, dest); + if (err) { + /* Error message was already printed. */ + /* Set reasonable default rather than keeping "GPS???" which */ + /* is invalid and causes trouble later. */ + + strcpy (dest, "GPSAA"); + } + + sprintf (raw_tt_msg, "%s>%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. + */ + + app_process_rec_packet (chan, -1, pp, -2, RETRY_NONE, "tt"); + +#endif +} + + + +/*------------------------------------------------------------------ + * + * Name: main + * + * Purpose: Unit test for this file. + * + * Description: Run unit test like this: + * + * rm a.exe ; gcc tt_text.c -DTT_MAIN -DDEBUG aprs_tt.c strtok_r.o utm/LatLong-UTMconversion.c ; ./a.exe + * + * + * Bugs: No automatic checking. + * Just eyeball it to see if things look right. + * + *----------------------------------------------------------------*/ + + +#if TT_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); +} + + + +int main (int argc, char *argv[]) +{ + char text[256], buttons[256]; + int n; + + dw_printf ("Hello, world!\n"); + + aprs_tt_init (NULL); + + //if (argc < 2) { + //dw_printf ("Supply text string on command line.\n"); + //exit (1); + //} + +/* Callsigns & abbreviations. */ + + aprs_tt_message (0, "A9A2B42A7A7C71#"); /* WB4APR/7 */ + aprs_tt_message (0, "A27773#"); /* abbreviated form */ + /* Example in http://www.aprs.org/aprstt/aprstt-coding24.txt has a bad checksum! */ + aprs_tt_message (0, "A27776#"); /* Expect error message. */ + + aprs_tt_message (0, "A2A7A7C71#"); /* Spelled suffix, overlay, checksum */ + aprs_tt_message (0, "A27773#"); /* Suffix digits, overlay, checksum */ + + aprs_tt_message (0, "A9A2B26C7D9D71#"); /* WB2OSZ/7 numeric overlay */ + aprs_tt_message (0, "A67979#"); /* abbreviated form */ + + aprs_tt_message (0, "A9A2B26C7D9D5A9#"); /* WB2OSZ/J letter overlay */ + aprs_tt_message (0, "A6795A7#"); /* abbreviated form */ + + aprs_tt_message (0, "A277#"); /* Tactical call "277" no overlay and no checksum */ + +/* Locations */ + + aprs_tt_message (0, "B01*A67979#"); + aprs_tt_message (0, "B988*A67979#"); + + /* expect about 52.79 +0.83 */ + aprs_tt_message (0, "B51000125*A67979#"); + + /* 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 */ + + aprs_tt_message (0, "B5206070*A67979#"); + + aprs_tt_message (0, "B21234*A67979#"); + aprs_tt_message (0, "B533686*A67979#"); + + +/* Comments */ + + aprs_tt_message (0, "C1"); + aprs_tt_message (0, "C2"); + aprs_tt_message (0, "C146520"); + aprs_tt_message (0, "C7788444222550227776669660333666990122223333"); + +/* Macros */ + + aprs_tt_message (0, "88345"); + + return(0); + +} /* end main */ + + + + +#endif + +/* end aprs_tt.c */ + diff --git a/aprs_tt.h b/aprs_tt.h new file mode 100644 index 0000000..3279179 --- /dev/null +++ b/aprs_tt.h @@ -0,0 +1,100 @@ + +/* 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_MACRO } type; + + char pattern[20]; /* e.g. B998, B5bbbdddd, B2xxyy, Byyyxxx */ + /* 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 { + char zone[8]; + double scale; + double x_offset; + double y_offset; + } utm; + + struct { + char *definition; + } macro; + }; +}; + +/* + * Configuratin options for APRStt. + */ + +#define TT_MAX_XMITS 10 + +struct tt_config_s { + + int obj_xmit_chan; /* Channel to transmit object report. */ + char obj_xmit_header[AX25_MAX_ADDRS*AX25_MAX_ADDR_LEN]; + /* e.g. "WB2OSZ-5>APDW07,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. */ + + 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; +}; + + +void aprs_tt_init (struct tt_config_s *p_config); + +void aprs_tt_button (int chan, char button); + +/* Error codes for sending responses to user. */ + +#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. */ + + +#endif + +/* end aprs_tt.h */ \ No newline at end of file diff --git a/atest.c b/atest.c new file mode 100644 index 0000000..b6c6237 --- /dev/null +++ b/atest.c @@ -0,0 +1,447 @@ + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011,2012,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: atest.c + * + * Purpose: Unit test 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. + * This results in .WMA files. + * + * (4) Upload the .WMA file(s) to http://media.io/ and + * convert to .WAV format. + * + * + * Comparison to others: + * + * Here are some other scores from Track 2 of the TNC Test CD: + * http://sites.google.com/site/ki4mcw/Home/arduino-tnc + * + * 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. + * + * Version 0.4 decoded 870 packets. + * + * After a little tweaking, version 0.5 decodes 931 packets. + * + * After more tweaking, version 0.6 gets 965 packets. + * This is without the option to retry after getting a bad FCS. + * + *--------------------------------------------------------------------*/ + +// #define X 1 + + +#include +#include +//#include +#include +#include +#include +#include +#include + + +#define ATEST_C 1 + +#include "audio.h" +#include "demod.h" +// #include "fsk_demod_agc.h" +#include "textcolor.h" +#include "ax25_pad.h" +#include "hdlc_rec2.h" + + + +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 struct wav_header header; +static FILE *fp; +static int e_o_f; +static int packets_decoded = 0; +static int decimate = 1; /* Reduce that sampling rate. */ + /* 1 = normal, 2 = half, etc. */ + + +int main (int argc, char *argv[]) +{ + + //int err; + int c; + struct audio_s modem; + int channel; + time_t start_time; + + text_color_init(1); + text_color_set(DW_COLOR_INFO); + +/* + * First apply defaults. + */ + + memset (&modem, 0, sizeof(modem)); + + modem.num_channels = DEFAULT_NUM_CHANNELS; + modem.samples_per_sec = DEFAULT_SAMPLES_PER_SEC; + modem.bits_per_sample = DEFAULT_BITS_PER_SAMPLE; + + /* TODO: should have a command line option for this. */ + /* Results v0.9: 971/69, 990/64, 992/65, 992/67, 1004/476 */ + + modem.fix_bits = RETRY_NONE; + modem.fix_bits = RETRY_SINGLE; + modem.fix_bits = RETRY_DOUBLE; + //modem.fix_bits = RETRY_TRIPLE; + //modem.fix_bits = RETRY_TWO_SEP; + + for (channel=0; channel 10000) { + fprintf (stderr, "Use a more reasonable bit rate in range of 100 - 10000.\n"); + exit (EXIT_FAILURE); + } + if (modem.baud[0] < 600) { + modem.modem_type[0] = AFSK; + modem.mark_freq[0] = 1600; + modem.space_freq[0] = 1800; + } + else if (modem.baud[0] > 2400) { + modem.modem_type[0] = SCRAMBLE; + modem.mark_freq[0] = 0; + modem.space_freq[0] = 0; + printf ("Using scrambled baseband signal rather than AFSK.\n"); + } + else { + modem.modem_type[0] = AFSK; + modem.mark_freq[0] = 1200; + modem.space_freq[0] = 2200; + } + break; + + case 'P': /* -P for modem profile. */ + + printf ("Demodulator profile set to \"%s\"\n", optarg); + strcpy (modem.profiles[0], optarg); + break; + + case 'D': /* -D reduce sampling rate for lower CPU usage. */ + + decimate = atoi(optarg); + printf ("Decimate factor = %d\n", decimate); + modem.decimate[0] = decimate; + break; + + case '?': + + /* Unknown option message was already printed. */ + //usage (argv); + break; + + default: + + /* Should not be here. */ + printf("?? getopt returned character code 0%o ??\n", c); + //usage (argv); + } + } + + if (optind >= argc) { + printf ("Specify .WAV file name on command line.\n"); + exit (1); + } + + fp = fopen(argv[optind], "rb"); + if (fp == NULL) { + text_color_set(DW_COLOR_ERROR); + fprintf (stderr, "Couldn't open file for read: %s\n", argv[optind]); + //perror ("more info?"); + exit (1); + } + + start_time = time(NULL); + + +/* + * Read the file header. + */ + + fread (&header, sizeof(header), (size_t)1, fp); + + assert (header.nchannels == 1 || header.nchannels == 2); + assert (header.wbitspersample == 8 || header.wbitspersample == 16); + + modem.samples_per_sec = header.nsamplespersec; + modem.samples_per_sec = modem.samples_per_sec; + modem.bits_per_sample = header.wbitspersample; + modem.num_channels = header.nchannels; + + text_color_set(DW_COLOR_INFO); + printf ("%d samples per second\n", modem.samples_per_sec); + printf ("%d bits per sample\n", modem.bits_per_sample); + printf ("%d audio channels\n", modem.num_channels); + printf ("%d audio bytes in file\n", (int)(header.datasize)); + + +/* + * Initialize the AFSK demodulator and HDLC decoder. + */ + multi_modem_init (&modem); + + + e_o_f = 0; + while ( ! e_o_f) + { + + + int audio_sample; + int c; + + for (c=0; c= 256 * 256) + e_o_f = 1; + +#define ONE_CHAN 1 /* only use one audio channel. */ + +#if ONE_CHAN + if (c != 0) continue; +#endif + + 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); + printf ("\n\n"); + printf ("%d packets decoded in %d seconds.\n", packets_decoded, (int)(time(NULL) - start_time)); + + exit (0); +} + + +/* + * Simulate sample from the audio device. + */ + +int audio_get (void) +{ + int ch; + + ch = getc(fp); + + if (ch < 0) 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; + int alevel; + int subchan; + + + chan = rrbb_get_chan(rrbb); + subchan = rrbb_get_subchan(rrbb); + alevel = rrbb_get_audio_level(rrbb); + + hdlc_rec2_try_to_fix_later (rrbb, chan, subchan, alevel); +} + + +/* + * This is called when we have a good frame. + */ + +void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, retry_t retries, char *spectrum) +{ + + //int err; + //char *p; + char stemp[500]; + unsigned char *pinfo; + int info_len; + int h; + char heard[20]; + //packet_t pp; + + + packets_decoded++; + + + ax25_format_addrs (pp, stemp); + + info_len = ax25_get_info (pp, &pinfo); + + /* Print so we can see what is going on. */ + +#if 1 + /* Display audio input level. */ + /* Who are we hearing? Original station or digipeater. */ + + h = ax25_get_heard(pp); + ax25_get_addr_with_ssid(pp, h, heard); + + text_color_set(DW_COLOR_DEBUG); + printf ("\n"); + + if (h != AX25_SOURCE) { + printf ("Digipeater "); + } + printf ("%s audio level = %d [%s] %s\n", heard, alevel, retry_text[(int)retries], spectrum); + + +#endif + +// Display non-APRS packets in a different color. + + if (ax25_is_aprs(pp)) { + text_color_set(DW_COLOR_REC); + printf ("[%d] ", chan); + } + else { + text_color_set(DW_COLOR_DEBUG); + printf ("[%d] ", chan); + } + + printf ("%s", stemp); /* stations followed by : */ + ax25_safe_print ((char *)pinfo, info_len, 0); + printf ("\n"); + + ax25_delete (pp); + +} /* end app_process_rec_packet */ + + +/* end atest.c */ diff --git a/audio.c b/audio.c new file mode 100644 index 0000000..e800f6e --- /dev/null +++ b/audio.c @@ -0,0 +1,1307 @@ + +// Remove next line to eliminate annoying debug messages every 100 seconds. +#define STATISTICS 1 + + +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * 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: Fabrice FAURE contributed code for the SDR UDP interface. + * + * Discussion here: http://gqrx.dk/doc/streaming-audio-over-udp + * + * + * Future: Will probably rip out the OSS code. + * ALSA was added to Linux kernel 10 years ago. + * Cygwin doesn't have it but I see no reason to support Cygwin + * now that we have a native Windows version. + * + *---------------------------------------------------------------*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#if USE_ALSA +#include +#else +#include +#endif + +#include "direwolf.h" +#include "audio.h" +#include "textcolor.h" + + +#if USE_ALSA +static snd_pcm_t *audio_in_handle = NULL; +static snd_pcm_t *audio_out_handle = NULL; + +static int bytes_per_frame; /* number of bytes for a sample from all channels. */ + /* e.g. 4 for stereo 16 bit. */ + +static int set_alsa_params (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 oss_audio_device_fd = -1; /* Single device, both directions. */ + +#endif + +static int inbuf_size_in_bytes = 0; /* number of bytes allocated */ +static unsigned char *inbuf_ptr = NULL; +static int inbuf_len = 0; /* number byte of actual data available. */ +static int inbuf_next = 0; /* index of next to remove. */ + +static int outbuf_size_in_bytes = 0; +static unsigned char *outbuf_ptr = NULL; +static int outbuf_len = 0; + +#define ONE_BUF_TIME 40 + +static enum audio_in_type_e audio_in_type; + +// UDP socket used for receiving data + +static int udp_sock; + + +#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff) +#define calcbufsize(rate,chans,bits) roundup1k( ( (rate)*(chans)*(bits) / 8 * ONE_BUF_TIME)/1000 ) + + +/*------------------------------------------------------------------ + * + * 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; + +#if USE_ALSA + + char audio_in_name[30]; + char audio_out_name[30]; + + assert (audio_in_handle == NULL); + assert (audio_out_handle == NULL); + +#else + + assert (oss_audio_device_fd == -1); +#endif + +/* + * Fill in defaults for any missing values. + */ + if (pa -> num_channels == 0) + pa -> num_channels = DEFAULT_NUM_CHANNELS; + + if (pa -> samples_per_sec == 0) + pa -> samples_per_sec = DEFAULT_SAMPLES_PER_SEC; + + if (pa -> bits_per_sample == 0) + pa -> bits_per_sample = DEFAULT_BITS_PER_SAMPLE; + + for (chan=0; chan mark_freq[chan] == 0) + pa -> mark_freq[chan] = DEFAULT_MARK_FREQ; + + if (pa -> space_freq[chan] == 0) + pa -> space_freq[chan] = DEFAULT_SPACE_FREQ; + + if (pa -> baud[chan] == 0) + pa -> baud[chan] = DEFAULT_BAUD; + + if (pa->num_subchan[chan] == 0) + pa->num_subchan[chan] = 1; + } + +/* + * Open audio device. + */ + + udp_sock = -1; + + inbuf_size_in_bytes = 0; + inbuf_ptr = NULL; + inbuf_len = 0; + inbuf_next = 0; + + outbuf_size_in_bytes = 0; + outbuf_ptr = NULL; + outbuf_len = 0; + +#if USE_ALSA + +/* + * Determine the type of audio input. + */ + audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; + + if (strcasecmp(pa->adevice_in, "stdin") == 0 || strcmp(pa->adevice_in, "-") == 0) { + audio_in_type = AUDIO_IN_TYPE_STDIN; + /* Change - to stdin for readability. */ + strcpy (pa->adevice_in, "stdin"); + } + else if (strncasecmp(pa->adevice_in, "udp:", 4) == 0) { + audio_in_type = AUDIO_IN_TYPE_SDR_UDP; + /* Supply default port if none specified. */ + if (strcasecmp(pa->adevice_in,"udp") == 0 || + strcasecmp(pa->adevice_in,"udp:") == 0) { + sprintf (pa->adevice_in, "udp:%d", DEFAULT_UDP_AUDIO_PORT); + } + } + +/* Let user know what is going on. */ + + /* If not specified, the device names should be "default". */ + + strcpy (audio_in_name, pa->adevice_in); + strcpy (audio_out_name, pa->adevice_out); + + 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\n", audio_in_name); + } + else { + dw_printf ("Audio input device for receive: %s\n", audio_in_name); + dw_printf ("Audio out device for transmit: %s\n", audio_out_name); + } + +/* + * Now attempt actual opens. + */ + +/* + * Input device. + */ + switch (audio_in_type) { + +/* + * Soundcard - ALSA. + */ + case AUDIO_IN_TYPE_SOUNDCARD: + + err = snd_pcm_open (&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); + } + + inbuf_size_in_bytes = set_alsa_params (audio_in_handle, pa, audio_in_name, "input"); + 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 ((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(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; + } + } + inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN; + + break; + +/* + * stdin. + */ + case AUDIO_IN_TYPE_STDIN: + + /* Do we need to adjust any properties of stdin? */ + + 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. + */ + err = snd_pcm_open (&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); + } + + outbuf_size_in_bytes = set_alsa_params (audio_out_handle, pa, audio_out_name, "output"); + + if (inbuf_size_in_bytes <= 0 || outbuf_size_in_bytes <= 0) { + return (-1); + } + + + + +#else /* end of ALSA case */ + + +#error OSS support will probably be removed. Complain if you still care about OSS. + + oss_audio_device_fd = open (pa->adevice_in, O_RDWR); + + if (oss_audio_device_fd < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("%s:\n", pa->adevice_in); + sprintf (message, "Could not open audio device %s", pa->adevice_in); + perror (message); + return (-1); + } + + outbuf_size_in_bytes = inbuf_size_in_bytes = set_oss_params (oss_audio_device_fd, pa); + + if (inbuf_size_in_bytes <= 0 || outbuf_size_in_bytes <= 0) { + return (-1); + } + + + +#endif /* end of OSS case */ + + +/* + * Finally allocate buffer for each direction. + */ + inbuf_ptr = malloc(inbuf_size_in_bytes); + assert (inbuf_ptr != NULL); + inbuf_len = 0; + inbuf_next = 0; + + outbuf_ptr = malloc(outbuf_size_in_bytes); + assert (outbuf_ptr != NULL); + outbuf_len = 0; + + 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 (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->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->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->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->samples_per_sec) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("Asked for %d samples/sec but got %d.\n", + + pa->samples_per_sec, val); + dw_printf ("for %s %s.\n", devname, inout); + + pa->samples_per_sec = val; + + } + + /* Guessing around 20 reads/sec might be good. */ + /* Period too long = too much latency. */ + /* Period too short = too much overhead of many small transfers. */ + + fpp = pa->samples_per_sec / 20; + +#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. */ + + bytes_per_frame = snd_pcm_frames_to_bytes (handle, 1); + assert (bytes_per_frame == pa->num_channels * pa->bits_per_sample / 8); + + + buf_size_in_bytes = fpp * 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", bytes_per_frame, (int)fpp, buf_size_in_bytes); +#endif + + 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 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->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->samples_per_sec; + + err = ioctl (fd, SNDCTL_DSP_SPEED, &(pa->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->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->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->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 + * + * 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->samples_per_sec, pa->num_channels, pa->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 + + assert (ossbuf_size_in_bytes >= 256 && ossbuf_size_in_bytes <= 32768); + + + return (ossbuf_size_in_bytes); + +} /* end set_oss_params */ + + +#endif + + + +/*------------------------------------------------------------------ + * + * Name: audio_get + * + * Purpose: Get one byte from the 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 (void) +{ + int n; + int retries = 0; + +#if STATISTICS + /* Gather numbers for read from audio device. */ + + static int duration = 100; /* report every 100 seconds. */ + static time_t last_time = 0; + time_t this_time; + static int sample_count; + static int error_count; +#endif + +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + + dw_printf ("audio_get():\n"); + +#endif + + assert (inbuf_size_in_bytes >= 100 && inbuf_size_in_bytes <= 32768); + + +#if USE_ALSA + + switch (audio_in_type) { + +/* + * Soundcard - ALSA + */ + case AUDIO_IN_TYPE_SOUNDCARD: + + while (inbuf_next >= inbuf_len) { + + assert (audio_in_handle != NULL); +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_get(): readi asking for %d frames\n", inbuf_size_in_bytes / bytes_per_frame); +#endif + n = snd_pcm_readi (audio_in_handle, inbuf_ptr, inbuf_size_in_bytes / bytes_per_frame); + +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_get(): readi asked for %d and got %d frames\n", + inbuf_size_in_bytes / bytes_per_frame, n); +#endif + +#if STATISTICS + if (last_time == 0) { + last_time = time(NULL); + sample_count = 0; + error_count = 0; + } + else { + if (n > 0) { + sample_count += n; + } + else { + error_count++; + } + this_time = time(NULL); + if (this_time >= last_time + duration) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\nPast %d seconds, %d audio samples, %d errors.\n\n", + duration, sample_count, error_count); + last_time = this_time; + sample_count = 0; + error_count = 0; + } + } +#endif + + if (n > 0) { + + /* Success */ + + inbuf_len = n * bytes_per_frame; /* convert to number of bytes */ + inbuf_next = 0; + } + 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); + + inbuf_len = 0; + 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 error: %s\n", snd_strerror(n)); + + /* Try to recover a few times and eventually give up. */ + if (++retries > 10) { + inbuf_len = 0; + inbuf_next = 0; + return (-1); + } + + if (n == -EPIPE) { + + /* EPIPE means overrun */ + + snd_pcm_recover (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 (audio_in_handle, n, 1); + } + } + } + break; + +/* + * UDP. + */ + + case AUDIO_IN_TYPE_SDR_UDP: + + while (inbuf_next >= inbuf_len) { + int ch, res,i; + + assert (udp_sock > 0); + res = recv(udp_sock, inbuf_ptr, 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); + inbuf_len = 0; + inbuf_next = 0; + return (-1); + } + + inbuf_len = res; + inbuf_next = 0; + } + break; + +/* + * stdin. + */ + case AUDIO_IN_TYPE_STDIN: + + while (inbuf_next >= inbuf_len) { + int ch, res,i; + + res = read(STDIN_FILENO, inbuf_ptr, (size_t)inbuf_size_in_bytes); + if (res <= 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("\nEnd of file on stdin. Exiting.\n"); + exit (0); + } + + inbuf_len = res; + inbuf_next = 0; + } + + break; + } + + + +#else /* end ALSA, begin OSS */ + + while (audio_in_type == AUDIO_IN_TYPE_SOUNDCARD && inbuf_next >= inbuf_len) { + assert (oss_audio_device_fd > 0); + n = read (oss_audio_device_fd, inbuf_ptr, inbuf_size_in_bytes); + //text_color_set(DW_COLOR_DEBUG); + // dw_printf ("audio_get(): read %d returns %d\n", inbuf_size_in_bytes, n); + if (n < 0) { + text_color_set(DW_COLOR_ERROR); + perror("Can't read from audio device"); + inbuf_len = 0; + inbuf_next = 0; + return (-1); + } + inbuf_len = n; + inbuf_next = 0; + } + +#endif /* USE_ALSA */ + + + + if (inbuf_next < inbuf_len) + n = inbuf_ptr[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: 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 c) +{ + /* Should never be full at this point. */ + assert (outbuf_len < outbuf_size_in_bytes); + + outbuf_ptr[outbuf_len++] = c; + + if (outbuf_len == outbuf_size_in_bytes) { + return (audio_flush()); + } + + 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 (void) +{ +#if USE_ALSA + int k; + char *psound; + int retries = 10; + snd_pcm_status_t *status; + + assert (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 (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 (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 = outbuf_ptr; + + while (retries-- > 0) { + + k = snd_pcm_writei (audio_out_handle, psound, outbuf_len / bytes_per_frame); +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n", + outbuf_len / 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 (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 (audio_out_handle, k, 1); + } + else if (k != outbuf_len / bytes_per_frame) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio write took %d frames rather than %d.\n", + k, outbuf_len / bytes_per_frame); + + /* Go around again with the rest of it. */ + + psound += k * bytes_per_frame; + outbuf_len -= k * bytes_per_frame; + } + else { + /* Success! */ + outbuf_len = 0; + return (0); + } + } + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio write error retry count exceeded.\n"); + + outbuf_len = 0; + return (-1); + +#else /* OSS */ + + int k; + unsigned char *ptr; + int len; + + ptr = outbuf_ptr; + len = outbuf_len; + + while (len > 0) { + assert (oss_audio_device_fd > 0); + k = write (oss_audio_device_fd, ptr, len); +#if DEBUG + 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"); + outbuf_len = 0; + return (-1); + } + if (k < len) { + /* presumably full but didn't block. */ + usleep (10000); + } + ptr += k; + len -= k; + } + + outbuf_len = 0; + return (0); +#endif + +} /* end audio_flush */ + + +/*------------------------------------------------------------------ + * + * Name: audio_wait + * + * Purpose: Wait until all the queued up audio out has been played. + * + * Inputs: duration - hint at number of milliseconds to wait. + * + * Returns: Normally non-negative. + * -1 for any type of error. + * + * Description: In our particular application, we would want to make sure + * that the entire packet has been sent out before turning + * off the transmitter PTT control. + * + * In an ideal world: + * + * We would like to ask the hardware when all the queued + * up sound has actually come out the speaker. + * There is an OSS system call for this but it doesn't work + * on Cygwin. The application crashes at a later time. + * + * Haven't yet verified correct operation with ALSA. + * + * In reality: + * + * 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) Add (1) and (2) resulting in when PTT should be turned off. + * (4) Take difference between current time and PPT off time + * and provide this as the additional delay required. + * + *----------------------------------------------------------------*/ + +int audio_wait (int duration) +{ + int err = 0; + + audio_flush (); +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_wait(): before sync, fd=%d\n", oss_audio_device_fd); +#endif + +#if USE_ALSA + + //double t_enter, t_leave; + //int drain_ms; + + //t_enter = dtime_now(); + + /* For playback, this should wait for all pending frames */ + /* to be played and then stop. */ + + snd_pcm_drain (audio_out_handle); + + //t_leave = dtime_now(); + //drain_ms = (int)((t_leave - t_enter) * 1000.); + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("audio_wait(): suggested delay = %d ms, actual = %d\n", + // duration, drain_ms); + + /* + * 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. + * + * Keep the sleep delay so PTT is not turned off too soon. + */ + + if (duration > 0) { + SLEEP_MS (duration); + } + +#else + + assert (oss_audio_device_fd > 0); + + + // This causes a crash later on Cygwin. + // Haven't tried it on Linux yet. + + // err = ioctl (oss_audio_device_fd, SNDCTL_DSP_SYNC, NULL); + + if (duration > 0) { + SLEEP_MS (duration); + } + +#endif + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_wait(): after sync, status=%d\n", err); +#endif + + return (err); + +} /* end audio_wait */ + + +/*------------------------------------------------------------------ + * + * Name: audio_close + * + * Purpose: Close the audio device. + * + * Returns: Normally non-negative. + * -1 for any type of error. + * + * + *----------------------------------------------------------------*/ + +int audio_close (void) +{ + int err = 0; + +#if USE_ALSA + assert (audio_in_handle != NULL); + assert (audio_out_handle != NULL); + + audio_wait (0); + + snd_pcm_close (audio_in_handle); + snd_pcm_close (audio_out_handle); + +#else + assert (oss_audio_device_fd > 0); + + audio_wait (0); + + close (oss_audio_device_fd); + + oss_audio_device_fd = -1; +#endif + free (inbuf_ptr); + free (outbuf_ptr); + + inbuf_size_in_bytes = 0; + inbuf_ptr = NULL; + inbuf_len = 0; + inbuf_next = 0; + + outbuf_size_in_bytes = 0; + outbuf_ptr = NULL; + outbuf_len = 0; + + return (err); + +} /* end audio_close */ + + +/* end audio.c */ + diff --git a/audio.h b/audio.h new file mode 100644 index 0000000..a9e25dc --- /dev/null +++ b/audio.h @@ -0,0 +1,208 @@ + +/*------------------------------------------------------------------ + * + * Module: audio.h + * + * Purpose: Interface to audio device commonly called a "sound card." + * + *---------------------------------------------------------------*/ + + +#ifndef AUDIO_H +#define AUDIO_H 1 + +#include "direwolf.h" /* for MAX_CHANS used throughout the application. */ +#include "hdlc_rec2.h" /* for enum retry_e */ + + +/* + * 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 purpos I/O. */ + +typedef enum ptt_method_e ptt_method_t; + +enum ptt_line_e { PTT_LINE_RTS = 1, PTT_LINE_DTR = 2 }; +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 }; + +struct audio_s { + + /* Properites of the sound device. */ + + 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). */ + + enum audio_in_type_e audio_in_type; + /* Where is input (receive) audio coming from? */ + + /* Common to all channels. */ + + enum retry_e fix_bits; /* Level of effort to recover from */ + /* a bad FCS on the frame. */ + + /* Properties for each audio channel, common to receive and transmit. */ + /* Can be different for each radio channel. */ + + enum modem_t {AFSK, NONE, SCRAMBLE} modem_type[MAX_CHANS]; + /* Usual AFSK. */ + /* Baseband signal. */ + /* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */ + + int decimate[MAX_CHANS]; /* Reduce AFSK sample rate by this factor to */ + /* decrease computational requirements. */ + + int mark_freq[MAX_CHANS]; /* Two tones for AFSK modulation, in Hz. */ + int space_freq[MAX_CHANS]; /* Standard tones are 1200 and 2200 for 1200 baud. */ + + int baud[MAX_CHANS]; /* Data bits (more accurately, symbols) per second. */ + /* Standard rates are 1200 for VHF and 300 for HF. */ + + char profiles[MAX_CHANS][16]; /* 1 or more of ABC etc. */ + + int num_freq[MAX_CHANS]; /* Number of different frequency pairs for decoders. */ + + int offset[MAX_CHANS]; /* Spacing between filter frequencies. */ + + int num_subchan[MAX_CHANS]; /* Total number of modems / hdlc decoders for each channel. */ + /* Potentially it could be product of strlen(profiles) * num_freq. */ + /* Currently can't use both at once. */ + + + /* Additional properties for transmit. */ + + ptt_method_t ptt_method[MAX_CHANS]; /* serial port or GPIO. */ + + char ptt_device[MAX_CHANS][20]; /* Serial device name for PTT. e.g. COM1 or /dev/ttyS0 */ + + ptt_line_t ptt_line[MAX_CHANS]; /* Control line wehn using serial port. */ + /* PTT_RTS, PTT_DTR. */ + + int ptt_gpio[MAX_CHANS]; /* GPIO number. */ + + int ptt_invert[MAX_CHANS]; /* Invert the output. */ + + int slottime[MAX_CHANS]; /* Slot time in 10 mS units for persistance algorithm. */ + /* Typical value is 10 meaning 100 milliseconds. */ + + int 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. */ + /* Default value is 63 for 25% probability. */ + + int txdelay[MAX_CHANS]; /* After turning on the transmitter, */ + /* send "flags" for txdelay * 10 mS. */ + /* Default value is 30 meaning 300 milliseconds. */ + + int 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. */ + /* At this point, I'm thinking of 10 as the default. */ +}; + +#if __WIN32__ +#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 /* Formerly 44100. */ + /* Software defined radio often uses 48000. */ + +#define DEFAULT_BITS_PER_SAMPLE 16 + +#define DEFAULT_FIX_BITS RETRY_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 + + + +/* + * Typical transmit timings for VHF. + */ + +#define DEFAULT_SLOTTIME 10 +#define DEFAULT_PERSIST 63 +#define DEFAULT_TXDELAY 30 +#define DEFAULT_TXTAIL 10 /* not sure yet. */ + + +/* + * 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 (void); + +int audio_put (int c); + +int audio_flush (void); + +int audio_wait (int duration); + +int audio_close (void); + + +#endif /* ifdef AUDIO_H */ + + +/* end audio.h */ + diff --git a/audio_win.c b/audio_win.c new file mode 100644 index 0000000..56a2678 --- /dev/null +++ b/audio_win.c @@ -0,0 +1,1044 @@ + +#define DEBUGUDP 1 + + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011,2012,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 . +// + + +/*------------------------------------------------------------------ + * + * Module: audio.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 + * + *---------------------------------------------------------------*/ + + +#include +#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 +#define _WIN32_WINNT 0x0501 +#include + + +#include "direwolf.h" +#include "audio.h" +#include "textcolor.h" +#include "ptt.h" + + + +/* + * 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. + */ + +#define TOTAL_BUF_TIME 1000 +#define ONE_BUF_TIME 40 + +#define NUM_IN_BUF ((TOTAL_BUF_TIME)/(ONE_BUF_TIME)) +#define NUM_OUT_BUF ((TOTAL_BUF_TIME)/(ONE_BUF_TIME)) + +static enum audio_in_type_e audio_in_type; + +/* + * UDP socket for receiving audio stream. + * Buffer, length, and pointer for UDP or stdin. + */ + +static SOCKET udp_sock; +static char stream_data[SDR_UDP_BUF_MAXLEN]; +static int stream_len; +static int stream_next; + + +#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff) +#define calcbufsize(rate,chans,bits) roundup1k( ( (rate)*(chans)*(bits) / 8 * ONE_BUF_TIME)/1000 ) + + +/* 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. */ + +static HWAVEOUT audio_out_handle = 0; + +static volatile WAVEHDR out_wavehdr[NUM_OUT_BUF]; +static int out_current; /* index to above. */ +static int outbuf_size; + + +/* For sound input. */ +/* In this case dwUser is index of next available byte to remove. */ + +static HWAVEIN audio_in_handle = 0; +static WAVEHDR in_wavehdr[NUM_IN_BUF]; +static volatile WAVEHDR *in_headp; /* head of queue to process. */ +static CRITICAL_SECTION in_cs; + + + + + + + +/*------------------------------------------------------------------ + * + * Name: print_capabilities + * + * Purpose: Display capabilities of the available audio devices. + * + * Example: + * + * + * Available audio input devices for receive (*=selected): + * 0: Microphone (Realtek High Defini mono: 11 22 44 96 stereo: 11 22 44 96 + * 1: Microphone (Bluetooth SCO Audio mono: 11 22 44 96 stereo: 11 22 44 96 + * 2: Microphone (Bluetooth AV Audio) mono: 11 22 44 96 stereo: 11 22 44 96 + * 3: Microphone (USB PnP Sound Devic mono: 11 22 44 96 stereo: 11 22 44 96 + * Available audio output devices for transmit (*=selected): + * 0: Speakers (Realtek High Definiti mono: 11 22 44 96 stereo: 11 22 44 96 + * 1: Speakers (Bluetooth SCO Audio) mono: 11 22 44 96 stereo: 11 22 44 96 + * 2: Realtek Digital Output (Realtek mono: 11 22 44 96 stereo: 11 22 44 96 + * 3: Realtek Digital Output(Optical) mono: 11 22 44 96 stereo: 11 22 44 96 + * 4: Speakers (Bluetooth AV Audio) mono: 11 22 44 96 stereo: 11 22 44 96 + * 5: Speakers (USB PnP Sound Device) mono: 11 22 44 96 stereo: 11 22 44 96 + * + * + * History: Removed in version 0.9. + * + * Post Mortem discussion: + * + * It turns out to be quite bogus and perhaps deceiving. + * + * The chip (http://www.szlnst.com/Uploadfiles/HS100.pdf) in the cheap + * USB Audio device is physically capable of only 44.1 and 48 KHz + * sampling rates. Input is mono only. Output is stereo only. + * There is discussion of this in the Raspberry Pi document. + * + * Here, it looks like it has much more general capabilities. + * It seems the audio system puts some virtual layer on top of + * it to provide resampling for different rates and silent + * right channel for stereo input. + * + * + *----------------------------------------------------------------*/ + +#if 0 +static void print_capabilities (DWORD formats) +{ + dw_printf (" mono:"); + dw_printf ("%s", (formats & WAVE_FORMAT_1M16) ? " 11" : " "); + dw_printf ("%s", (formats & WAVE_FORMAT_2M16) ? " 22" : " "); + dw_printf ("%s", (formats & WAVE_FORMAT_4M16) ? " 44" : " "); + dw_printf ("%s", (formats & WAVE_FORMAT_96M16) ? " 96" : " "); + + dw_printf (" stereo:"); + dw_printf ("%s", (formats & WAVE_FORMAT_1S16) ? " 11" : " "); + dw_printf ("%s", (formats & WAVE_FORMAT_2S16) ? " 22" : " "); + dw_printf ("%s", (formats & WAVE_FORMAT_4S16) ? " 44" : " "); + dw_printf ("%s", (formats & WAVE_FORMAT_96S16) ? " 96" : " "); +} +#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. + * + * 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 err; + int chan; + int n; + int in_dev_no; + int out_dev_no; + + WAVEFORMATEX wf; + + int num_devices; + WAVEINCAPS wic; + WAVEOUTCAPS woc; + + assert (audio_in_handle == 0); + assert (audio_out_handle == 0); + + +/* + * Fill in defaults for any missing values. + */ + if (pa -> num_channels == 0) + pa -> num_channels = DEFAULT_NUM_CHANNELS; + + if (pa -> samples_per_sec == 0) + pa -> samples_per_sec = DEFAULT_SAMPLES_PER_SEC; + + if (pa -> bits_per_sample == 0) + pa -> bits_per_sample = DEFAULT_BITS_PER_SAMPLE; + + for (chan=0; chan mark_freq[chan] == 0) + pa -> mark_freq[chan] = DEFAULT_MARK_FREQ; + + if (pa -> space_freq[chan] == 0) + pa -> space_freq[chan] = DEFAULT_SPACE_FREQ; + + if (pa -> baud[chan] == 0) + pa -> baud[chan] = DEFAULT_BAUD; + + if (pa->num_subchan[chan] == 0) + pa->num_subchan[chan] = 1; + } + + wf.wFormatTag = WAVE_FORMAT_PCM; + wf.nChannels = pa -> num_channels; + wf.nSamplesPerSec = pa -> samples_per_sec; + wf.wBitsPerSample = pa -> bits_per_sample; + wf.nBlockAlign = (wf.wBitsPerSample / 8) * wf.nChannels; + wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; + wf.cbSize = 0; + + outbuf_size = calcbufsize(wf.nSamplesPerSec,wf.nChannels,wf.wBitsPerSample); + + + udp_sock = INVALID_SOCKET; + + in_dev_no = WAVE_MAPPER; /* = -1 */ + out_dev_no = WAVE_MAPPER; + +/* + * Determine the type of audio input and select device. + */ + + if (strcasecmp(pa->adevice_in, "stdin") == 0 || strcmp(pa->adevice_in, "-") == 0) { + audio_in_type = AUDIO_IN_TYPE_STDIN; + /* Change - to stdin for readability. */ + strcpy (pa->adevice_in, "stdin"); + } + else if (strncasecmp(pa->adevice_in, "udp:", 4) == 0) { + audio_in_type = AUDIO_IN_TYPE_SDR_UDP; + /* Supply default port if none specified. */ + if (strcasecmp(pa->adevice_in,"udp") == 0 || + strcasecmp(pa->adevice_in,"udp:") == 0) { + sprintf (pa->adevice_in, "udp:%d", DEFAULT_UDP_AUDIO_PORT); + } + } + else { + audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; + + /* Does config file have a number? */ + /* If so, it is an index into list of devices. */ + + if (strlen(pa->adevice_in) == 1 && isdigit(pa->adevice_in[0])) { + in_dev_no = atoi(pa->adevice_in); + } + + /* Otherwise, does it have search string? */ + + if (in_dev_no == WAVE_MAPPER && strlen(pa->adevice_in) >= 1) { + num_devices = waveInGetNumDevs(); + for (n=0 ; nadevice_in) != NULL) { + in_dev_no = n; + } + } + } + if (in_dev_no == WAVE_MAPPER) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\"%s\" doesn't match any of the input devices.\n", pa->adevice_in); + } + } + } + +/* + * Select output device. + */ + if (strlen(pa->adevice_out) == 1 && isdigit(pa->adevice_out[0])) { + out_dev_no = atoi(pa->adevice_out); + } + + if (out_dev_no == WAVE_MAPPER && strlen(pa->adevice_out) >= 1) { + num_devices = waveOutGetNumDevs(); + for (n=0 ; nadevice_out) != NULL) { + out_dev_no = n; + } + } + } + if (out_dev_no == WAVE_MAPPER) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adevice_out); + } + } + + +/* + * Display what is available and anything selected. + */ + text_color_set(DW_COLOR_INFO); + dw_printf ("Available audio input devices for receive (*=selected):\n"); + + num_devices = waveInGetNumDevs(); + if (in_dev_no < -1 || in_dev_no >= num_devices) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid input (receive) audio device number %d.\n", in_dev_no); + in_dev_no = WAVE_MAPPER; + } + text_color_set(DW_COLOR_INFO); + for (n=0; nadevice_in); + } + + 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(); + if (out_dev_no < -1 || out_dev_no >= num_devices) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid output (transmit) audio device number %d.\n", out_dev_no); + out_dev_no = WAVE_MAPPER; + } + text_color_set(DW_COLOR_INFO); + for (n=0; nadevice_in + 4)); + si_me.sin_addr.s_addr = htonl(INADDR_ANY); + + // Bind to the socket + + if (bind(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; + } + stream_next= 0; + stream_len = 0; + } + + break; + +/* + * stdin. + */ + case AUDIO_IN_TYPE_STDIN: + + setmode (STDIN_FILENO, _O_BINARY); + stream_next= 0; + 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) +{ + if (msg == WIM_DATA) { + + WAVEHDR *p = (WAVEHDR*)param1; + + p->dwUser = -1; /* needs to be unprepared. */ + p->lpNext = NULL; + + EnterCriticalSection (&in_cs); + + if (in_headp == NULL) { + in_headp = p; /* first one in list */ + } + else { + WAVEHDR *last = (WAVEHDR*)in_headp; + + while (last->lpNext != NULL) { + last = last->lpNext; + } + last->lpNext = p; /* append to last one */ + } + + LeaveCriticalSection (&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. + * + * 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 (void) +{ + WAVEHDR *p; + int n; + int sample; + +#if DEBUGUDP + /* Gather numbers for read from UDP stream. */ + + static int duration = 100; /* report every 100 seconds. */ + static time_t last_time = 0; + time_t this_time; + static int sample_count; + static int error_count; +#endif + + switch (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 (in_headp == NULL) { + SLEEP_MS (ONE_BUF_TIME / 5); + timeout--; + if (timeout <= 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio input failure.\n"); + return (-1); + } + } + + p = (WAVEHDR*)in_headp; /* no need to be volatile at this point */ + + if (p->dwUser == -1) { + waveInUnprepareHeader(audio_in_handle, p, sizeof(WAVEHDR)); + p->dwUser = 0; /* Index for next byte. */ + } + + 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 (&in_cs); + in_headp = p->lpNext; + LeaveCriticalSection (&in_cs); + + p->dwFlags = 0; + waveInPrepareHeader(audio_in_handle, p, sizeof(WAVEHDR)); + waveInAddBuffer(audio_in_handle, p, sizeof(WAVEHDR)); + } + break; +/* + * UDP. + */ + case AUDIO_IN_TYPE_SDR_UDP: + + while (stream_next >= stream_len) { + int res; + + assert (udp_sock > 0); + + res = recv (udp_sock, stream_data, SDR_UDP_BUF_MAXLEN, 0); + if (res <= 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't read from udp socket, errno %d", WSAGetLastError()); + stream_len = 0; + stream_next = 0; + return (-1); + } + +#if DEBUGUDP + if (last_time == 0) { + last_time = time(NULL); + sample_count = 0; + error_count = 0; + } + else { + if (res > 0) { + sample_count += res/2; + } + else { + error_count++; + } + this_time = time(NULL); + if (this_time >= last_time + duration) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\nPast %d seconds, %d audio samples, %d errors.\n\n", + duration, sample_count, error_count); + last_time = this_time; + sample_count = 0; + error_count = 0; + } + } +#endif + stream_len = res; + stream_next = 0; + } + sample = stream_data[stream_next] & 0xff; + stream_next++; + return (sample); + break; +/* + * stdin. + */ + case AUDIO_IN_TYPE_STDIN: + + while (stream_next >= stream_len) { + int res; + + res = read(STDIN_FILENO, stream_data, 1024); + if (res <= 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("\nEnd of file on stdin. Exiting.\n"); + exit (0); + } + + stream_len = res; + stream_next = 0; + } + return (stream_data[stream_next++] & 0xff); + break; + } + + return (-1); + +} /* end audio_get */ + + +/*------------------------------------------------------------------ + * + * Name: audio_put + * + * Purpose: Send one byte to the audio device. + * + * Inputs: 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 c) +{ + WAVEHDR *p; + +/* + * Wait if no buffers are available. + * Don't use p yet because compiler might might consider dwFlags a loop invariant. + */ + + int timeout = 10; + while ( out_wavehdr[out_current].dwUser == DWU_PLAYING) { + SLEEP_MS (ONE_BUF_TIME); + timeout--; + if (timeout <= 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio output failure waiting for buffer.\n"); + ptt_term (); + return (-1); + } + } + + p = (LPWAVEHDR)(&(out_wavehdr[out_current])); + + if (p->dwUser == DWU_DONE) { + waveOutUnprepareHeader (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 < outbuf_size); + + p->lpData[p->dwBufferLength++] = c; + + if (p->dwBufferLength == outbuf_size) { + return (audio_flush()); + } + + return (0); + +} /* end audio_put */ + + +/*------------------------------------------------------------------ + * + * Name: audio_flush + * + * Purpose: Send current buffer to the audio output system. + * + * Returns: Normally non-negative. + * -1 for any type of error. + * + * See Also: audio_flush + * audio_wait + * + *----------------------------------------------------------------*/ + +int audio_flush (void) +{ + WAVEHDR *p; + MMRESULT e; + + + p = (LPWAVEHDR)(&(out_wavehdr[out_current])); + + if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) { + + p->dwUser = DWU_PLAYING; + + waveOutPrepareHeader(audio_out_handle, p, sizeof(WAVEHDR)); + + e = waveOutWrite(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); + } + out_current = (out_current + 1) % NUM_OUT_BUF; + } + return (0); + +} /* end audio_flush */ + + +/*------------------------------------------------------------------ + * + * Name: audio_wait + * + * Purpose: Wait until all the queued up audio out has been played. + * + * Inputs: duration - hint at number of milliseconds to wait. + * + * Returns: Normally non-negative. + * -1 for any type of error. + * + * Description: In our particular application, we want to make sure + * that the entire packet has been sent out before turning + * off the transmitter PTT control. + * + * In an ideal world: + * + * We would like to ask the hardware when all the queued + * up sound has actually come out the speaker. + * + * The original implementation (on Cygwin) tried using: + * + * ioctl (audio_device_fd, SNDCTL_DSP_SYNC, NULL); + * + * but this caused the application to crash at a later time. + * + * This might be revisited someday for the Windows version, + * but for now, we continue to use the work-around because it + * works fine. + * + * In reality: + * + * 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) Add (1) and (2) resulting in when PTT should be turned off. + * (4) Take difference between current time and PPT off time + * and provide this as the additional delay required. + * + *----------------------------------------------------------------*/ + +int audio_wait (int duration) +{ + int err = 0; + + audio_flush (); +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_wait(): before sync, fd=%d\n", audio_device_fd); +#endif + + + if (duration > 0) { + SLEEP_MS (duration); + } + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_wait(): after sync, status=%d\n", err); +#endif + + return (err); + +} /* end audio_wait */ + + +/*------------------------------------------------------------------ + * + * Name: audio_close + * + * Purpose: Close the audio device. + * + * Returns: Normally non-negative. + * -1 for any type of error. + * + * + *----------------------------------------------------------------*/ + +int audio_close (void) +{ + int err = 0; + + int n; + + + assert (audio_in_handle != 0); + assert (audio_out_handle != 0); + + audio_wait (0); + +/* Shutdown audio input. */ + + waveInReset(audio_in_handle); + waveInStop(audio_in_handle); + waveInClose(audio_in_handle); + audio_in_handle = 0; + + for (n = 0; n < NUM_IN_BUF; n++) { + + waveInUnprepareHeader (audio_in_handle, (LPWAVEHDR)(&(in_wavehdr[n])), sizeof(WAVEHDR)); + in_wavehdr[n].dwFlags = 0; + free (in_wavehdr[n].lpData); + in_wavehdr[n].lpData = NULL; + } + + DeleteCriticalSection (&in_cs); + + +/* Make sure all output buffers have been played then free them. */ + + for (n = 0; n < NUM_OUT_BUF; n++) { + if (out_wavehdr[n].dwUser == DWU_PLAYING) { + + int timeout = 2 * NUM_OUT_BUF; + while (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 (audio_out_handle, (LPWAVEHDR)(&(out_wavehdr[n])), sizeof(WAVEHDR)); + + out_wavehdr[n].dwUser = DWU_DONE; + } + free (out_wavehdr[n].lpData); + out_wavehdr[n].lpData = NULL; + } + + waveOutClose (audio_out_handle); + audio_out_handle = 0; + + return (err); + +} /* end audio_close */ + +/* end audio_win.c */ + diff --git a/ax25_pad.c b/ax25_pad.c new file mode 100644 index 0000000..247e630 --- /dev/null +++ b/ax25_pad.c @@ -0,0 +1,1722 @@ +// TODO: Shouldn't this be using dw_printf??? + + +// +// 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: ax25_pad + * + * Purpose: Packet assembler and disasembler. + * + * 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 might want to modify + * an existing packet. For instance an APRS repeater might + * want to change "WIDE2-2" to "WIDE2-1" and retransmit it. + * + * + * Description: + * + * + * A UI frame starts with 2-10 addressses (14-70 octets): + * + * * Destination Address + * * Source Address + * * 0-8 Digipeater Addresses (Could there ever be more as a result of + * digipeaters inserting their own call + * and decrementing the remaining count in + * WIDEn-n, TRACEn-n, etc.? + * NO. The limit is 8 when transmitting AX.25 over the radio. + * However, communication with an IGate server + * could have a longer VIA path but that is in text form.) + * + * Each address is composed of: + * + * * 6 upper case letters or digits, blank padded. + * These are shifted left one bit, leaving the 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 final octet of the Source has the form: + * + * C R R SSID 0, where, + * + * C = command/response = 1 + * 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. + * + * When monitoring, an asterisk is displayed after the last digipeater with + * the "H" bit set. 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 + * * 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. + * + * + * 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 +#include +#include +#include +#include +#ifndef _POSIX_C_SOURCE + +#define _POSIX_C_SOURCE 1 +#endif + +#include "regex.h" + +#if __WIN32__ +char *strtok_r(char *str, const char *delim, char **saveptr); +#endif + +#include "ax25_pad.h" +#include "textcolor.h" +#include "fcs_calc.h" + +/* + * Accumulate statistics. + * If new_count gets more than a few larger than delete_count plus the size of + * the transmit queue we have a memory leak. + */ + +static int new_count = 0; +static int delete_count = 0; + + +/*------------------------------------------------------------------------------ + * + * 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. + * + *------------------------------------------------------------------------------*/ + + +static 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 + + new_count++; + +/* + * check for memory leak. + */ + if (new_count > delete_count + 100) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Memory leak for packet objects. new=%d, delete=%d\n", new_count, delete_count); + } + + this_p = calloc(sizeof (struct packet_s), (size_t)1); + this_p->magic1 = MAGIC; + this_p->magic2 = MAGIC; + return (this_p); +} + +/*------------------------------------------------------------------------------ + * + * Name: ax25_delete + * + * Purpose: Destroy a packet object, freeing up memory it was using. + * + *------------------------------------------------------------------------------*/ + +void ax25_delete (packet_t this_p) +{ +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("ax25_delete(): before free, new=%d, delete=%d\n", new_count, delete_count); +#endif + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + memset (this_p, 0, sizeof (struct packet_s)); + delete_count++; + 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" format of a monitored packet. i.e. + * source>dest[,repeater1,repeater2,...]:information + * + * strict - True to enforce rules for packets sent over the air. + * False to be more lenient for packets from IGate server. + * + * Returns: Pointer to new packet object in the current implementation. + * + * Outputs: Use the "get" functions to retrieve information in different ways. + * + *------------------------------------------------------------------------------*/ + +packet_t ax25_from_text (char *monitor, int strict) +{ + +/* + * 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. */ + + static int first_time = 1; + static regex_t unhex_re; + int e; + char emsg[100]; +#define MAXMATCH 1 + regmatch_t match[MAXMATCH]; + int keep_going; + char temp[512]; + int ssid_temp, heard_temp; + + + + packet_t this_p = ax25_new (); + + /* Is it possible to have a nul character (zero byte) in the */ + /* information field of an AX.25 frame? */ + + strcpy (stuff, monitor); + +/* + * Translate hexadecimal values like <0xff> to non-printing characters. + * MIC-E message type uses 5 different non-printing characters. + */ + + if (first_time) + { + e = regcomp (&unhex_re, "<0x[0-9a-fA-F][0-9a-fA-F]>", 0); + if (e) { + regerror (e, &unhex_re, emsg, sizeof(emsg)); + text_color_set(DW_COLOR_ERROR); + dw_printf ("%s:%d: %s\n", __FILE__, __LINE__, emsg); + } + + first_time = 0; + } + +#if 0 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("BEFORE: %s\n", stuff); + ax25_safe_print (stuff, -1, 0); + dw_printf ("\n"); +#endif + keep_going = 1; + while (keep_going) { + if (regexec (&unhex_re, stuff, MAXMATCH, match, 0) == 0) { + int n; + char *p; + + stuff[match[0].rm_so + 5] = '\0'; + n = strtol (stuff + match[0].rm_so + 3, &p, 16); + stuff[match[0].rm_so] = n; + strcpy (temp, stuff + match[0].rm_eo); + strcpy (stuff + match[0].rm_so + 1, temp); + } + else { + keep_going = 0; + } + } +#if 0 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("AFTER: %s\n", stuff); + ax25_safe_print (stuff, -1, 0); + dw_printf ("\n"); +#endif + +/* + * Separate the addresses from the rest. + */ + pinfo = strchr (stuff, ':'); + + if (pinfo == NULL) { + ax25_delete (this_p); + return (NULL); + } + + *pinfo = '\0'; + pinfo++; + + if (strlen(pinfo) > AX25_MAX_INFO_LEN) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Warning: Information part truncated to %d characters.\n", AX25_MAX_INFO_LEN); + pinfo[AX25_MAX_INFO_LEN] = '\0'; + } + + strcpy ((char*)(this_p->the_rest + 2), pinfo); + this_p->the_rest_len = strlen(pinfo) + 2; + +/* + * Now separate the addresses. + * Note that source and destination order is swappped. + */ + + this_p->num_addr = 2; + +/* + * 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 (pa, strict, this_p->addrs[AX25_SOURCE], &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); + } + + this_p->ssid_etc[AX25_SOURCE] = SSID_H_MASK | SSID_RR_MASK; + 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 (pa, strict, this_p->addrs[AX25_DESTINATION], &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); + } + + this_p->ssid_etc[AX25_DESTINATION] = SSID_H_MASK | SSID_RR_MASK; + 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 ) { + + //char *last; + int k; + + k = this_p->num_addr; + + this_p->num_addr++; + + if ( ! ax25_parse_addr (pa, strict, this_p->addrs[k], &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); + } + + this_p->ssid_etc[k] = SSID_RR_MASK; + 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); + } + } + } + + this_p->the_rest[0] = AX25_UI_FRAME; + this_p->the_rest[1] = AX25_NO_LAYER_3; + + 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. + * + *------------------------------------------------------------------------------*/ + + +packet_t ax25_from_frame (unsigned char *fbuf, int flen, int alevel) +{ + unsigned char *pf; + //int found_last; + packet_t this_p; + + int a; + int addr_bytes; + +/* + * 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? No. + */ + + 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 (); + +/* + * Extract the addresses. + * The last one has '1' in the LSB of the last byte. + */ + +#if 1 + +/* + * 0.9 - Try new strategy that will allow KISS mode + * to handle non AX.25 frame. + */ + + this_p->num_addr = 0; /* Number of addresses extracted. */ + + addr_bytes = 0; + for (a = 0; a < flen && addr_bytes == 0; a++) { + if (fbuf[a] & 0x01) { + 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; + + for (a = 0; a < addrs; a++){ + unsigned char *pin; + char *pout; + int j; + char ch; + + pin = fbuf + a * 7; + pout = & this_p->addrs[a][0]; + + for (j=0; j<6; j++) + { + ch = *pin++ >> 1; + if (ch != ' ') + { + *pout++ = ch; + } + } + *pout = '\0'; + + this_p->ssid_etc[a] = *pin & ~ SSID_LAST_MASK; + } + } + } + + pf = fbuf + this_p->num_addr * 7; + +#else + + pf = fbuf; /* Transmitted form from here. */ + + this_p->num_addr = 0; /* Number of addresses extracted. */ + found_last = 0; + + while (this_p->num_addr < AX25_MAX_ADDRS && ! found_last) { + + unsigned char *pin; + char *pout; + int j; + char ch; + + pin = pf; + pout = & this_p->addrs[this_p->num_addr][0]; + + for (j=0; j<6; j++) + { + ch = *pin++ >> 1; + if (ch != ' ') + { + *pout++ = ch; + } + } + *pout = '\0'; + + this_p->ssid_etc[this_p->num_addr] = pf[6] & ~ SSID_LAST_MASK; + + this_p->num_addr++; + + if (pf[6] & SSID_LAST_MASK) { /* Is this the last one? */ + found_last = 1; + } + else { + pf += 7; /* Get ready for next one. */ + } + } + + if (this_p->num_addr < 2) { + int k; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Frame format error detected in ax25_from_frame, %s, line %d.\n", __FILE__, __LINE__); + dw_printf ("Did not find a minimum of two addresses at beginning of AX.25 frame.\n"); + for (k=0; k<14; k++) { + dw_printf (" %02x", fbuf[k]); + } + dw_printf ("\n"); + /* Should we keep going or delete the packet? */ + } + +/* + * pf still points to the last address (repeater or source). + * + * Verify that it has the last address bit set. + */ + if ((pf[6] & SSID_LAST_MASK) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Last address in header does not have LSB set.\n"); + ax25_delete (this_p); + return (NULL); + } + + pf += 7; + +#endif + + if (this_p->num_addr * 7 > flen - 1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Frame too short to include control field.\n"); + ax25_delete (this_p); + return (NULL); + } + + + + +/* + * pf should now point to control field. + * Previously we separated out control, PID, and Info here. + * + * Now (version 0.8) we save control, PID, and info together. + * This makes it easier to act as a dumb KISS TNC + * for AX.25-based protocols other than APRS. + */ + this_p->the_rest_len = flen - (pf - fbuf); + + assert (this_p->the_rest_len >= 1); + + memcpy (this_p->the_rest, pf, (size_t)this_p->the_rest_len); + this_p->the_rest[this_p->the_rest_len] = '\0'; + + 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. + * + * + *------------------------------------------------------------------------------*/ + + +packet_t ax25_dup (packet_t copy_from) +{ + + packet_t this_p; + + + this_p = ax25_new (); + + memcpy (this_p, copy_from, sizeof (struct packet_s)); + + return (this_p); + +} + + +/*------------------------------------------------------------------------------ + * + * Name: ax25_parse_addr + * + * Purpose: Parse address with optional ssid. + * + * Inputs: in_addr - Input such as "WB2OSZ-15*" + * + * strict - 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:... + * + * 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. + * + * + *------------------------------------------------------------------------------*/ + + +int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard) +{ + char *p; + char sstr[4]; + int i, j, k; + int maxlen; + + strcpy (out_addr, ""); + *out_ssid = 0; + *out_heard = 0; + + maxlen = strict ? 6 : (AX25_MAX_ADDR_LEN-1); + p = in_addr; + i = 0; + for (p = in_addr; isalnum(*p); p++) { + if (i >= maxlen) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Address is too long. \"%s\" has more than %d characters.\n", in_addr, maxlen); + return 0; + } + out_addr[i++] = *p; + out_addr[i] = '\0'; + if (strict && islower(*p)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Address has lower case letters. \"%s\" must be all upper case.\n", in_addr); + return 0; + } + } + + strcpy (sstr, ""); + j = 0; + if (*p == '-') { + for (p++; isalnum(*p); p++) { + if (j >= 2) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("SSID is too long. SSID part of \"%s\" has more than 2 characters.\n", in_addr); + return 0; + } + sstr[j++] = *p; + sstr[j] = '\0'; + if (strict && ! isdigit(*p)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("SSID must be digits. \"%s\" has letters in SSID.\n", in_addr); + return 0; + } + } + k = atoi(sstr); + if (k < 0 || k > 15) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("SSID out of range. SSID of \"%s\" not in range of 0 to 15.\n", in_addr); + return 0; + } + *out_ssid = k; + } + + if (*p == '*') { + *out_heard = 1; + p++; + } + + if (*p != '\0') { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid character \"%c\" found in address \"%s\".\n", *p, in_addr); + return 0; + } + + return (1); + +} /* end ax25_parse_addr */ + + +/*------------------------------------------------------------------------------ + * + * 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); + + result_pp = ax25_from_text((char *)info_p + 1, 0); + + 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. + * 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; + + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + assert (n >= 0 && n < AX25_MAX_ADDRS); + assert (strlen(ad) < AX25_MAX_ADDR_LEN); + + if (n+1 > this_p->num_addr) { + this_p->num_addr = n+1; + this_p->ssid_etc[n] = SSID_RR_MASK; + } + + ax25_parse_addr (ad, 0, this_p->addrs[n], &ssid_temp, &heard_temp); + ax25_set_ssid (this_p, n, ssid_temp); +} + + +/*------------------------------------------------------------------------------ + * + * 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 k; + int ssid_temp, heard_temp; + + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + assert (n >= AX25_REPEATER_1 && n < AX25_MAX_ADDRS); + assert (strlen(ad) < AX25_MAX_ADDR_LEN); + + /* 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; + } + + /* Shift the current occupant and others up. */ + + for (k=this_p->num_addr; k>n; k--) { + strcpy (this_p->addrs[k], this_p->addrs[k-1]); + this_p->ssid_etc[k] = this_p->ssid_etc[k-1]; + } + + this_p->num_addr++; + + ax25_parse_addr (ad, 0, this_p->addrs[n], &ssid_temp, &heard_temp); + this_p->ssid_etc[n] = SSID_RR_MASK; + ax25_set_ssid (this_p, n, ssid_temp); +} + + +/*------------------------------------------------------------------------------ + * + * 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 k; + + 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. */ + + this_p->num_addr--; + + for (k = n; k < this_p->num_addr; k++) { + strcpy (this_p->addrs[k], this_p->addrs[k+1]); + this_p->ssid_etc[k] = this_p->ssid_etc[k+1]; + } +} + + +/*------------------------------------------------------------------------------ + * + * 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) +{ + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + 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" + * + * 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[4]; + + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + +/* + * This assert failure popped up once and it is not clear why. + * Let's print out more information about the situation so we + * might have a clue about the root cause. + * Try to keep going instead of dying at this point. + */ + //assert (n >= 0 && n < this_p->num_addr); + + 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); + strcpy (station, "??????"); + 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); + strcpy (station, "??????"); + return; + } + + strcpy (station, this_p->addrs[n]); + + ssid = ax25_get_ssid (this_p, n); + if (ssid != 0) { + sprintf (sstr, "-%d", ssid); + strcat (station, sstr); + } +} + + +/*------------------------------------------------------------------------------ + * + * 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. + * + * Warning: No bounds checking is performed. Be careful. + * + * Assumption: ax25_from_text or ax25_from_frame was called first. + * + * Returns: Substation id, as integer 0 .. 15. + * + * Bugs: Rewrite to keep call and SSID separate internally. + * + *------------------------------------------------------------------------------*/ + +int ax25_get_ssid (packet_t this_p, int n) +{ + + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + assert (n >= 0 && n < this_p->num_addr); + + return ((this_p->ssid_etc[n] & SSID_SSID_MASK) >> SSID_SSID_SHIFT); +} + + +/*------------------------------------------------------------------------------ + * + * 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. + * + * Warning: No bounds checking is performed. Be careful. + * + * 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); + assert (n >= 0 && n < this_p->num_addr); + + this_p->ssid_etc[n] = (this_p->ssid_etc[n] & ~ SSID_SSID_MASK) | + ((ssid << SSID_SSID_SHIFT) & SSID_SSID_MASK) ; +} + + +/*------------------------------------------------------------------------------ + * + * 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); + + return ((this_p->ssid_etc[n] & SSID_H_MASK) >> SSID_H_SHIFT); +} + + +/*------------------------------------------------------------------------------ + * + * 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); + assert (n >= 0 && n < this_p->num_addr); + + this_p->ssid_etc[n] |= SSID_H_MASK; +} + + +/*------------------------------------------------------------------------------ + * + * 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_info + * + * Purpose: Obtain Information part of current packet. + * + * Inputs: None. + * + * Outputs: paddr - Starting address 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) +{ + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + + if (this_p->num_addr >= 2) { + *paddr = this_p->the_rest + ax25_get_info_offset(this_p); + return (ax25_get_num_info(this_p)); + } + + /* Not AX.25. Whole packet is info. */ + + *paddr = this_p->the_rest; + return (this_p->the_rest_len); +} + + +/*------------------------------------------------------------------------------ + * + * 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->the_rest[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); +} + + + +/*------------------------------------------------------------------ + * + * 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. + * + * + *------------------------------------------------------------------*/ + +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_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]) +{ + int j, k; + unsigned char *pout; + int len; + + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + + pout = result; + + for (j=0; jnum_addr; j++) { + + char *s; + + memset (pout, ' ' << 1, (size_t)6); + + s = this_p->addrs[j]; + for (k=0; *s != '\0'; k++, s++) { + pout[k] = *s << 1; + } + + if (j == this_p->num_addr - 1) { + pout[6] = this_p->ssid_etc[j] | SSID_LAST_MASK; + } + else { + pout[6] = this_p->ssid_etc[j] & ~ SSID_LAST_MASK; + } + pout += 7; + } + + memcpy (pout, this_p->the_rest, (size_t)this_p->the_rest_len); + pout += this_p->the_rest_len; + + len = pout - result; + + assert (len <= AX25_MAX_PACKET_LEN); + + return (len); +} + + +/*------------------------------------------------------------------ + * + * 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) +{ + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + + return (this_p->num_addr >= 2 && + ax25_get_control(this_p) == AX25_UI_FRAME && + ax25_get_pid(this_p) == AX25_NO_LAYER_3); +} + +/*------------------------------------------------------------------ + * + * Function: ax25_get_control + * + * 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->num_addr >= 2) { + return (this_p->the_rest[ax25_get_control_offset(this_p)]); + } + return (-1); +} + +/*------------------------------------------------------------------ + * + * 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. + * + *------------------------------------------------------------------*/ + + +int ax25_get_pid (packet_t this_p) +{ + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + + if (this_p->num_addr >= 2) { + return (this_p->the_rest[ax25_get_pid_offset(this_p)]); + } + return (-1); +} + + +/*------------------------------------------------------------------------------ + * + * 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. + * + *------------------------------------------------------------------------------*/ + +unsigned short ax25_dedupe_crc (packet_t pp) +{ + unsigned short crc; + 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); + + 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 - Maximum length if not -1. + * + * 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? + * + *------------------------------------------------------------------*/ + +#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 && *pstr != '\0') + { + ch = *((unsigned char *)pstr); + + 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. */ + + sprintf (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--; + } + + dw_printf ("%s", safe_str); + +} /* end ax25_safe_print */ + +/* end ax25_pad.c */ + + diff --git a/ax25_pad.h b/ax25_pad.h new file mode 100644 index 0000000..490e6e6 --- /dev/null +++ b/ax25_pad.h @@ -0,0 +1,298 @@ +/*------------------------------------------------------------------- + * + * 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. */ + +#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_NO_LAYER_3 0xf0 /* protocol ID */ + + + +#ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */ + +struct packet_s { + + int magic1; /* for error checking. */ + +#define MAGIC 0x41583235 + + struct packet_s *nextp; /* Pointer to next in queue. */ + + int num_addr; /* Number of elements used in two below. */ + /* Range of 0 .. AX25_MAX_ADDRS. */ + + char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN]; + /* Contains the address without the ssid. */ + /* Why is it larger than 7? */ + /* Messages from an IGate server can have longer */ + /* addresses after qAC. Up to 9 observed so far. */ + + /* usual human readable form. e.g. WB20SZ-15 */ + + unsigned char ssid_etc[AX25_MAX_ADDRS]; /* SSID octet from each address. */ + + /* + * 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 and is normally 1. + * + * 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 the_rest_len; /* Frame length minus the address part. */ + + unsigned char the_rest[2 + 3 + AX25_MAX_INFO_LEN + 1]; + /* The rest after removing the addresses. */ + /* Includes control, protocol ID, Information, */ + /* and throw in one more for a character */ + /* string nul terminator. */ + + int magic2; /* Will get stomped on if above overflows. */ +}; + + + + +#else /* Public view. */ + +struct packet_s { + int secret; +}; + +#endif + + +typedef struct packet_s *packet_t; + + + +#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 + * "modulo 128 operation" is in effect. Unfortunately, it seems + * this can be determined only by examining the XID frames and + * keeping this information for each connection. + * We can assume 1 for our purposes. + */ + +static inline int ax25_get_control_offset (packet_t this_p) +{ + return (0); +} + +static inline int ax25_get_num_control (packet_t this_p) +{ + return (1); +} + + +/* + * 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_num_control(this_p)); +} + +static int ax25_get_num_pid (packet_t this_p) +{ + int c; + int pid; + + c = this_p->the_rest[ax25_get_control_offset(this_p)]; + + if ( (c & 0x01) == 0 || /* I xxxx xxx0 */ + c == 0x03 || c == 0x13) { /* UI 000x 0011 */ + + pid = this_p->the_rest[ax25_get_pid_offset(this_p)]; + if (pid == 0xff) { + return (2); /* pid 1111 1111 means another follows. */ + } + return (1); + } + return (0); +} + + +/* + * APRS always has an Information field with at least one octet for the + * Data Type Indicator. AX.25 has this for only 5 frame types depending + * on the control field. + * xxxx xxx0 I + * 000x 0011 UI + * 101x 1111 XID + * 111x 0011 TEST + * 100x 0111 FRMR + */ + +static inline int ax25_get_info_offset (packet_t this_p) +{ + return (ax25_get_num_control(this_p) + ax25_get_num_pid(this_p)); +} + +static int ax25_get_num_info (packet_t this_p) +{ + int len; + + len = this_p->the_rest_len - ax25_get_num_control(this_p) - ax25_get_num_pid(this_p); + if (len < 0) { + len = 0; /* print error? */ + } + return (len); +} + +#endif + + + + + +//static packet_t ax25_new (void); + +extern void ax25_delete (packet_t pp); + +extern void ax25_clear (packet_t pp); + +extern packet_t ax25_from_text (char *, int strict); + +extern packet_t ax25_from_frame (unsigned char *data, int len, int alevel); + +extern packet_t ax25_dup (packet_t copy_from); + +extern int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard); + +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 *); + +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_info (packet_t pp, unsigned char **paddr); + +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_format_addrs (packet_t pp, char *); + +extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]); + +extern int ax25_is_aprs (packet_t pp); + +extern int ax25_get_control (packet_t this_p); + +extern int ax25_get_pid (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); + + +#endif /* AX25_PAD_H */ + +/* end ax25_pad.h */ + + diff --git a/beacon.c b/beacon.c new file mode 100644 index 0000000..7dab39b --- /dev/null +++ b/beacon.c @@ -0,0 +1,681 @@ +//#define DEBUG 1 +//#define DEBUG_SIM 1 + + +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * Module: beacon.c + * + * Purpose: Transmit messages on a fixed schedule. + * + * Description: Transmit periodic messages as specified in the config file. + * + *---------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include + +#include +#if __WIN32__ +#include +#endif + +#include "direwolf.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "audio.h" +#include "tq.h" +#include "xmit.h" +#include "config.h" +#include "digipeater.h" +#include "version.h" +#include "encode_aprs.h" +#include "beacon.h" +#include "latlong.h" +#include "dwgps.h" + + + +/* + * Are we using GPS data? + * Incremented if tracker beacons configured. + * Cleared if dwgps_init fails. + */ + +static int g_using_gps = 0; + +/* + * Save pointers to configuration settings. + */ + +static struct misc_config_s *g_misc_config_p; +static struct digi_config_s *g_digi_config_p; + + + +#if __WIN32__ +static unsigned __stdcall beacon_thread (void *arg); +#else +static void * beacon_thread (void *arg); +#endif + + + +/*------------------------------------------------------------------- + * + * Name: beacon_init + * + * Purpose: Initialize the beacon process. + * + * Inputs: pconfig - misc. configuration from config file. + * pdigi - digipeater configuration from config file. + * Use to obtain "mycall" for each channel. + * + * + * 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 to actually send the packets + * at the appropriate time. + * + *--------------------------------------------------------------------*/ + + + +void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi) +{ + time_t now; + 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_misc_config_p = pconfig; + g_digi_config_p = pdigi; + +/* + * 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].chan; + + if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */ + + if (chan < pdigi->num_chans) { + + if (strlen(pdigi->mycall[chan]) > 0 && strcasecmp(pdigi->mycall[chan], "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: + +#if defined(GPS_ENABLED) || defined(DEBUG_SIM) + g_using_gps++; +#else + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: GPS tracker feature is not enabled.\n", g_misc_config_p->beacon[j].lineno); + g_misc_config_p->beacon[j].btype = BEACON_IGNORE; + continue; +#endif + break; + + case BEACON_CUSTOM: + + /* INFO is required. */ + + if (g_misc_config_p->beacon[j].custom_info == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: INFO 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_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 next time for each beacon. + */ + + now = time(NULL); + + for (j=0; jnum_beacons; j++) { +#if DEBUG + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("beacon[%d] chan=%d, delay=%d, every=%d\n", + j, + g_misc_config_p->beacon[j].chan, + g_misc_config_p->beacon[j].delay, + g_misc_config_p->beacon[j].every); +#endif + g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].delay; + } + + +/* + * Connect to GPS receiver if any tracker beacons are configured. + * If open fails, disable all tracker beacons. + */ + +#if DEBUG_SIM + + g_using_gps = 1; + +#elif ENABLE_GPS + + if (g_using_gps > 0) { + int err; + + err = dwgps_init(); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("All tracker beacons disabled.\n"); + g_using_gps = 0; + + for (j=0; jnum_beacons; j++) { + if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) { + g_misc_config_p->beacon[j].btype = BEACON_IGNORE; + } + } + } + + } +#endif + + +/* + * 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 KNOTS_TO_MPH 1.150779 + +#define MIN(x,y) ((x) < (y) ? (x) : (y)) + + +/* Difference between two angles. */ + +static inline float heading_change (float a, float b) +{ + float diff; + + diff = fabs(a - b); + if (diff <= 180.) + return (diff); + else + return (360. - diff); +} + + +#if __WIN32__ +static unsigned __stdcall beacon_thread (void *arg) +#else +static void * beacon_thread (void *arg) +#endif +{ + int j; + time_t earliest; + time_t now; + +/* + * Information from GPS. + */ + int fix = 0; /* 0 = none, 2 = 2D, 3 = 3D */ + double my_lat = 0; /* degrees */ + double my_lon = 0; + float my_course = 0; /* degrees */ + float my_speed_knots = 0; + float my_speed_mph = 0; + float my_alt = 0; /* meters */ + +/* + * SmartBeaconing state. + */ + time_t sb_prev_time = 0; /* Time of most recent transmission. */ + float sb_prev_course = 0; /* Most recent course reported. */ + //float sb_prev_speed_mph; /* Most recent speed reported. */ + int sb_every; /* Calculated time between transmissions. */ + + +#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 + now = time(NULL); + + while (1) { + + assert (g_misc_config_p->num_beacons >= 1); + +/* + * Sleep until time for the earliest scheduled or + * the soonest we could transmit due to corner pegging. + */ + + earliest = g_misc_config_p->beacon[0].next; + for (j=1; jnum_beacons; j++) { + if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE) + continue; + earliest = MIN(g_misc_config_p->beacon[j].next, earliest); + } + + if (g_misc_config_p->sb_configured && g_using_gps) { + 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 DEBUG_SIM + FILE *fp; + char cs[40]; + + fp = fopen ("c:\\cygwin\\tmp\\cs", "r"); + if (fp != NULL) { + fscanf (fp, "%f %f", &my_course, &my_speed_knots); + fclose (fp); + } + else { + fprintf (stderr, "Can't read /tmp/cs.\n"); + } + fix = 3; + my_speed_mph = KNOTS_TO_MPH * my_speed_knots; + my_lat = 42.99; + my_lon = 71.99; + my_alt = 100; +#else + if (g_using_gps) { + + fix = dwgps_read (&my_lat, &my_lon, &my_speed_knots, &my_course, &my_alt); + my_speed_mph = KNOTS_TO_MPH * my_speed_knots; + + /* Don't complain here for no fix. */ + /* Possibly at the point where about to transmit. */ + } +#endif + +/* + * Run SmartBeaconing calculation if configured and GPS data available. + */ + if (g_misc_config_p->sb_configured && g_using_gps && fix >= 2) { + + if (my_speed_mph > g_misc_config_p->sb_fast_speed) { + sb_every = g_misc_config_p->sb_fast_rate; + } + else if (my_speed_mph < g_misc_config_p->sb_slow_speed) { + sb_every = g_misc_config_p->sb_slow_rate; + } + else { + /* Can't divide by 0 assuming sb_slow_speed > 0. */ + sb_every = ( g_misc_config_p->sb_fast_rate * g_misc_config_p->sb_fast_speed ) / my_speed_mph; + } + +#if DEBUG_SIM + text_color_set(DW_COLOR_DEBUG); + dw_printf ("SB: fast %d %d slow %d %d speed=%.1f every=%d\n", + g_misc_config_p->sb_fast_speed, g_misc_config_p->sb_fast_rate, + g_misc_config_p->sb_slow_speed, g_misc_config_p->sb_slow_rate, + my_speed_mph, sb_every); +#endif + +/* + * Test for "Corner Pegging" if moving. + */ + if (my_speed_mph >= 1.0) { + int turn_threshold = g_misc_config_p->sb_turn_angle + + g_misc_config_p->sb_turn_slope / my_speed_mph; + +#if DEBUG_SIM + text_color_set(DW_COLOR_DEBUG); + dw_printf ("SB-moving: course %.0f prev %.0f thresh %d\n", + my_course, sb_prev_course, turn_threshold); +#endif + if (heading_change(my_course, sb_prev_course) > turn_threshold && + now >= sb_prev_time + g_misc_config_p->sb_turn_time) { + + /* Send it now. */ + for (j=0; jnum_beacons; j++) { + if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) { + g_misc_config_p->beacon[j].next = now; + } + } + } /* significant change in direction */ + } /* is moving */ + } /* apply SmartBeaconing */ + + + for (j=0; jnum_beacons; j++) { + + if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE) + continue; + + if (g_misc_config_p->beacon[j].next <= now) { + + 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]; + +/* + * 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. + */ + strcpy (mycall, "NOCALL"); + + if (g_misc_config_p->beacon[j].chan == -1) { + strcpy (mycall, g_digi_config_p->mycall[0]); + } + else { + strcpy (mycall, g_digi_config_p->mycall[g_misc_config_p->beacon[j].chan]); + } + + 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", g_misc_config_p->beacon[j].lineno); + continue; + } + +/* + * Prepare the monitor format header. + */ + + strcpy (beacon_text, mycall); + strcat (beacon_text, ">"); + sprintf (stemp, "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION); + strcat (beacon_text, stemp); + if (g_misc_config_p->beacon[j].via) { + strcat (beacon_text, ","); + strcat (beacon_text, g_misc_config_p->beacon[j].via); + } + strcat (beacon_text, ":"); + +/* + * Add the info part depending on beacon type. + */ + switch (g_misc_config_p->beacon[j].btype) { + + case BEACON_POSITION: + + encode_position (g_misc_config_p->beacon[j].compress, g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon, + g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol, + g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir, + 0, 0, /* course, speed */ + g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, + g_misc_config_p->beacon[j].comment, + info); + strcat (beacon_text, info); + g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every; + break; + + case BEACON_OBJECT: + + encode_object (g_misc_config_p->beacon[j].objname, g_misc_config_p->beacon[j].compress, 0, g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon, + g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol, + g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir, + 0, 0, /* course, speed */ + g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, g_misc_config_p->beacon[j].comment, + info); + strcat (beacon_text, info); + g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every; + break; + + case BEACON_TRACKER: + + if (fix >= 2) { + int coarse; /* APRS encoder wants 1 - 360. */ + /* 0 means none or unknown. */ + + coarse = (int)roundf(my_course); + if (coarse == 0) { + coarse = 360; + } + encode_position (g_misc_config_p->beacon[j].compress, + my_lat, my_lon, + g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol, + g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir, + coarse, (int)roundf(my_speed_knots), + g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, + g_misc_config_p->beacon[j].comment, + info); + strcat (beacon_text, info); + + /* Remember most recent tracker beacon. */ + + sb_prev_time = now; + sb_prev_course = my_course; + //sb_prev_speed_mph = my_speed_mph; + + /* Calculate time for next transmission. */ + if (g_misc_config_p->sb_configured) { + g_misc_config_p->beacon[j].next = now + sb_every; + } + else { + g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every; + } + } + else { + g_misc_config_p->beacon[j].next = now + 2; + continue; /* No fix. Try again in a couple seconds. */ + } + break; + + case BEACON_CUSTOM: + + if (g_misc_config_p->beacon[j].custom_info != NULL) { + strcat (beacon_text, g_misc_config_p->beacon[j].custom_info); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error. custom_info is null. %s %d\n", __FILE__, __LINE__); + continue; + } + g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every; + break; + + case BEACON_IGNORE: + default: + break; + + } /* switch beacon type. */ + +/* + * Parse monitor format into form for transmission. + */ + pp = ax25_from_text (beacon_text, strict); + + if (pp != NULL) { + + /* Send to IGate server or radio. */ + + if (g_misc_config_p->beacon[j].chan == -1) { +#if 1 + text_color_set(DW_COLOR_XMIT); + dw_printf ("[ig] %s\n", beacon_text); +#endif + igate_send_rec_packet (0, pp); + ax25_delete (pp); + } + else { + tq_append (g_misc_config_p->beacon[j].chan, TQ_PRIO_1_LO, pp); + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: Failed to parse packet constructed from line %d.\n", g_misc_config_p->beacon[j].lineno); + dw_printf ("%s\n", beacon_text); + } + + } /* if time to send it */ + + } /* for each configured beacon */ + + } /* do forever */ + +} /* end beacon_thread */ + +/* end beacon.c */ diff --git a/beacon.h b/beacon.h new file mode 100644 index 0000000..ed8d198 --- /dev/null +++ b/beacon.h @@ -0,0 +1,4 @@ + +/* beacon.h */ + +void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi); diff --git a/config.c b/config.c new file mode 100644 index 0000000..8dcd6f8 --- /dev/null +++ b/config.c @@ -0,0 +1,2462 @@ +// +// 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 . +// + + +// #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 +#include +#include +#include +#include +#include + +#if __WIN32__ +#include "pthreads/pthread.h" +#else +#include +#endif + +#include "ax25_pad.h" +#include "textcolor.h" +#include "audio.h" +#include "digipeater.h" +#include "config.h" +#include "aprs_tt.h" +#include "igate.h" +#include "latlong.h" +#include "symbols.h" + + +//#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 (sizeof(units) / sizeof(struct units_s)) + +static int beacon_options(char *cmd, struct beacon_s *b, int line); + + +/*------------------------------------------------------------------ + * + * 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. */ + +#define DEG1 '^' +#define DEG2 0xb0 /* ISO Latin1 */ +#define DEG3 0xf8 /* Microsoft code page 437 */ + +// TODO: recognize UTF-8 degree symbol. + + +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. + */ + strcpy (stemp, str); + 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); +} + +#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°30", LAT); + + 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: config_init + * + * Purpose: Read configuration file when application starts up. + * + * Inputs: fname - Name of configuration file. + * + * Outputs: p_modem - Radio channel parameters stored here. + * + * p_digi_config - 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_modem, + struct digi_config_s *p_digi_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 stuff[256]; + //char *p; + //int c, p; + //int err; + int line; + int channel; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("config_init ( %s )\n", fname); +#endif + +/* + * First apply defaults. + */ + + memset (p_modem, 0, sizeof(struct audio_s)); + + strcpy (p_modem->adevice_in, DEFAULT_ADEVICE); + strcpy (p_modem->adevice_out, DEFAULT_ADEVICE); + + p_modem->num_channels = DEFAULT_NUM_CHANNELS; /* -2 stereo */ + p_modem->samples_per_sec = DEFAULT_SAMPLES_PER_SEC; /* -r option */ + p_modem->bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* -8 option for 8 instead of 16 bits */ + p_modem->fix_bits = DEFAULT_FIX_BITS; + + for (channel=0; channelmodem_type[channel] = AFSK; + p_modem->mark_freq[channel] = DEFAULT_MARK_FREQ; /* -m option */ + p_modem->space_freq[channel] = DEFAULT_SPACE_FREQ; /* -s option */ + p_modem->baud[channel] = DEFAULT_BAUD; /* -b option */ + + /* None. Will set default later based on other factors. */ + strcpy (p_modem->profiles[channel], ""); + + p_modem->num_freq[channel] = 1; + p_modem->num_subchan[channel] = 1; + p_modem->offset[channel] = 0; + + // temp test. + // p_modem->num_subchan[channel] = 9; + // p_modem->offset[channel] = 60; + + p_modem->ptt_method[channel] = PTT_METHOD_NONE; + strcpy (p_modem->ptt_device[channel], ""); + p_modem->ptt_line[channel] = PTT_LINE_RTS; + p_modem->ptt_gpio[channel] = 0; + p_modem->ptt_invert[channel] = 0; + + p_modem->slottime[channel] = DEFAULT_SLOTTIME; + p_modem->persist[channel] = DEFAULT_PERSIST; + p_modem->txdelay[channel] = DEFAULT_TXDELAY; + p_modem->txtail[channel] = DEFAULT_TXTAIL; + } + + memset (p_digi_config, 0, sizeof(struct digi_config_s)); + p_digi_config->num_chans = p_modem->num_channels; + p_digi_config->dedupe_time = DEFAULT_DEDUPE; + + memset (p_tt_config, 0, sizeof(struct tt_config_s)); + 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 */ + + p_tt_config->retain_time = 80 * 60; + p_tt_config->num_xmits = 7; + 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; + + memset (p_misc_config, 0, sizeof(struct misc_config_s)); + p_misc_config->num_channels = p_modem->num_channels; + 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 = 6; + p_igate_config->tx_limit_5 = 20; + + + /* People find this confusing. */ + /* Ideally we'd like to figure out if com0com is installed */ + /* and automatically enable this. */ + + //strcpy (p_misc_config->nullmodem, DEFAULT_NULLMODEM); + strcpy (p_misc_config->nullmodem, ""); + + +/* + * Try to extract options from a file. + * + * Windows: File must be in current working directory. + * + * Linux: Search current directory then home directory. + */ + + + channel = 0; + + fp = fopen (fname, "r"); +#ifndef __WIN32__ + if (fp == NULL && strcmp(fname, "direwolf.conf") == 0) { + /* Failed to open the default location. Try home dir. */ + char *p; + + p = getenv("HOME"); + if (p != NULL) { + strcpy (stuff, p); + strcat (stuff, "/direwolf.conf"); + fp = fopen (stuff, "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", fname); + dw_printf ("Try using -c command line option for alternate location.\n"); + return; + } + + + line = 0; + while (fgets(stuff, sizeof(stuff), fp) != NULL) { + char *t; + + line++; + + + t = strtok (stuff, " ,\t\n\r"); + if (t == NULL) { + continue; + } + + if (*t == '#' || *t == '*') { + continue; + } + + + +/* + * ==================== Audio device parameters ==================== + */ + +/* + * ADEVICE - Name of input sound device, and optionally output, if different. + */ + + /* Note that ALSA name can contain comma such as hw:1,0 */ + + if (strcasecmp(t, "ADEVICE") == 0) { + t = strtok (NULL, " \t\n\r"); + 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; + } + strncpy (p_modem->adevice_in, t, sizeof(p_modem->adevice_in)-1); + strncpy (p_modem->adevice_out, t, sizeof(p_modem->adevice_out)-1); + + t = strtok (NULL, " \t\n\r"); + if (t != NULL) { + strncpy (p_modem->adevice_out, t, sizeof(p_modem->adevice_out)-1); + } + } + +/* + * ARATE - Audio samples per second, 11025, 22050, 44100, etc. + */ + + else if (strcasecmp(t, "ARATE") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + 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_modem->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: 1 or 2 + */ + + else if (strcasecmp(t, "ACHANNELS") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + 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 <= MAX_CHANS) { + p_modem->num_channels = n; + p_digi_config->num_chans = p_modem->num_channels; + p_misc_config->num_channels = p_modem->num_channels; + } + 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 = strtok (NULL, " ,\t\n\r"); + 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; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Audio channel number must be 0 or 1.\n", line); + channel = 0; + } + } + +/* + * MYCALL station + */ + else if (strcasecmp(t, "mycall") == 0) { + t = strtok (NULL, " ,\t\n\r"); + 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; + + strncpy (p_digi_config->mycall[channel], t, sizeof(p_digi_config->mycall[channel])-1); + + for (p = p_digi_config->mycall[channel]; *p != '\0'; p++) { + if (islower(*p)) { + *p = toupper(*p); /* silently force upper case. */ + } + } + // TODO: additional checks if valid + } + } + + +/* + * MODEM - Replaces former HBAUD, MARK, SPACE, and adds new multi modem capability. + * + * MODEM baud [ mark space [A][B][C] [ num-decoders spacing ] ] + */ + + else if (strcasecmp(t, "MODEM") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing date transmission rate for MODEM command.\n", line); + continue; + } + n = atoi(t); + if (n >= 100 && n <= 10000) { + p_modem->baud[channel] = n; + if (n != 300 && n != 1200 && n != 9600) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Warning: Non-standard baud rate. Are you sure?\n", line); + } + } + else { + p_modem->baud[channel] = DEFAULT_BAUD; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unreasonable baud rate. Using %d.\n", + line, p_modem->baud[channel]); + } + + + + /* Get mark frequency. */ + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Note: Using scrambled baseband rather than AFSK modem.\n"); + p_modem->modem_type[channel] = SCRAMBLE; + p_modem->mark_freq[channel] = 0; + p_modem->space_freq[channel] = 0; + continue; + } + + 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_modem->mark_freq[channel] = n; + } + else { + p_modem->mark_freq[channel] = DEFAULT_MARK_FREQ; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d.\n", + line, p_modem->mark_freq[channel]); + } + + /* Get space frequency */ + + t = strtok (NULL, " ,\t\n\r"); + 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_modem->space_freq[channel] = n; + } + else { + p_modem->space_freq[channel] = DEFAULT_SPACE_FREQ; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unreasonable space tone frequency. Using %d.\n", + line, p_modem->space_freq[channel]); + } + + /* New feature in 0.9 - Optional filter profile(s). */ + + /* First, set a default based on platform and baud. */ + + if (p_modem->baud[channel] < 600) { + + /* "D" is a little better at 300 baud. */ + + strcpy (p_modem->profiles[channel], "D"); + } + else { +#if __arm__ + /* We probably don't have a lot of CPU power available. */ + + if (p_modem->baud[channel] == DEFAULT_BAUD && + p_modem->mark_freq[channel] == DEFAULT_MARK_FREQ && + p_modem->space_freq[channel] == DEFAULT_SPACE_FREQ && + p_modem->samples_per_sec == DEFAULT_SAMPLES_PER_SEC) { + + strcpy (p_modem->profiles[channel], "F"); + } + else { + strcpy (p_modem->profiles[channel], "A"); + } +#else + strcpy (p_modem->profiles[channel], "C"); +#endif + } + + t = strtok (NULL, " ,\t\n\r"); + if (t != NULL) { + if (isalpha(t[0])) { + // TODO: should check all letters. + strncpy (p_modem->profiles[channel], t, sizeof(p_modem->profiles[channel])); + p_modem->num_subchan[channel] = strlen(p_modem->profiles[channel]); + t = strtok (NULL, " ,\t\n\r"); + if (strlen(p_modem->profiles[channel]) > 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 modems is out of range. Using 3.\n", line); + n = 3; + } + p_modem->num_freq[channel] = n; + p_modem->num_subchan[channel] = n; + + t = strtok (NULL, " ,\t\n\r"); + if (t != NULL) { + n = atoi(t); + if (n < 5 || n > abs(p_modem->mark_freq[channel] - p_modem->space_freq[channel])/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_modem->offset[channel] = n; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing frequency offset between modems. Using 50 Hz.\n", line); + p_modem->offset[channel] = 50; + } + +// TODO: power saver + } + } + +/* + * (deprecated) HBAUD - Set data bits per second. Standard values are 300 & 1200 for AFSK + * and 9600 for baseband with scrambling. + */ + + else if (strcasecmp(t, "HBAUD") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing date transmission rate for HBAUD command.\n", line); + continue; + } + n = atoi(t); + if (n >= 100 && n <= 10000) { + p_modem->baud[channel] = n; + if (n != 300 && n != 1200 && n != 9600) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Warning: Non-standard baud rate. Are you sure?\n", line); + } + if (n == 9600) { + /* TODO: should be separate option to keep it more general. */ + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Note: Using scrambled baseband for 9600 baud.\n", line); + p_modem->modem_type[channel] = SCRAMBLE; + } + } + else { + p_modem->baud[channel] = DEFAULT_BAUD; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unreasonable baud rate. Using %d.\n", + line, p_modem->baud[channel]); + } + } + +/* + * (deprecated) MARK - Mark tone frequency. + */ + + else if (strcasecmp(t, "MARK") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing tone frequency for MARK command.\n", line); + continue; + } + n = atoi(t); + if (n >= 300 && n <= 3000) { + p_modem->mark_freq[channel] = n; + } + else { + p_modem->mark_freq[channel] = DEFAULT_MARK_FREQ; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d.\n", + line, p_modem->mark_freq[channel]); + } + } + +/* + * (deprecated) SPACE - Space tone frequency. + */ + + else if (strcasecmp(t, "SPACE") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing tone frequency for SPACE command.\n", line); + continue; + } + n = atoi(t); + if (n >= 300 && n <= 3000) { + p_modem->space_freq[channel] = n; + } + else { + p_modem->space_freq[channel] = DEFAULT_SPACE_FREQ; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unreasonable space tone frequency. Using %d.\n", + line, p_modem->space_freq[channel]); + } + } + +/* + * PTT - Push To Talk signal line. + * + * PTT serial-port [-]rts-or-dtr + * PTT GPIO [-]gpio-num + */ + + else if (strcasecmp(t, "PTT") == 0) { + //int n; + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: Missing serial port name for PTT command.\n", + line); + continue; + } + + if (strcasecmp(t, "GPIO") != 0) { + +/* serial port case. */ + + strncpy (p_modem->ptt_device[channel], t, sizeof(p_modem->ptt_device[channel])); + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: Missing RTS or DTR after PTT device name.\n", + line); + continue; + } + + if (strcasecmp(t, "rts") == 0) { + p_modem->ptt_line[channel] = PTT_LINE_RTS; + p_modem->ptt_invert[channel] = 0; + } + else if (strcasecmp(t, "dtr") == 0) { + p_modem->ptt_line[channel] = PTT_LINE_DTR; + p_modem->ptt_invert[channel] = 0; + } + else if (strcasecmp(t, "-rts") == 0) { + p_modem->ptt_line[channel] = PTT_LINE_RTS; + p_modem->ptt_invert[channel] = 1; + } + else if (strcasecmp(t, "-dtr") == 0) { + p_modem->ptt_line[channel] = PTT_LINE_DTR; + p_modem->ptt_invert[channel] = 1; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: Expected RTS or DTR after PTT device name.\n", + line); + continue; + } + + p_modem->ptt_method[channel] = PTT_METHOD_SERIAL; + } + else { + +/* GPIO case, Linux only. */ + +// TODO: +#if 0 +//#if __WIN32__ + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: PTT with GPIO is only available on Linux.\n", line); +#else + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: Missing GPIO number.\n", line); + continue; + } + + if (*t == '-') { + p_modem->ptt_gpio[channel] = atoi(t+1); + p_modem->ptt_invert[channel] = 1; + } + else { + p_modem->ptt_gpio[channel] = atoi(t); + p_modem->ptt_invert[channel] = 0; + } + p_modem->ptt_method[channel] = PTT_METHOD_GPIO; +#endif + } + } + + +/* + * SLOTTIME - For non-digipeat transmit delay timing. + */ + + else if (strcasecmp(t, "SLOTTIME") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + 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_modem->slottime[channel] = n; + } + else { + p_modem->slottime[channel] = DEFAULT_SLOTTIME; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Invalid delay time for persist algorithm. Using %d.\n", + line, p_modem->slottime[channel]); + } + } + +/* + * PERSIST - For non-digipeat transmit delay timing. + */ + + else if (strcasecmp(t, "PERSIST") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + 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_modem->persist[channel] = n; + } + else { + p_modem->persist[channel] = DEFAULT_PERSIST; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Invalid probability for persist algorithm. Using %d.\n", + line, p_modem->persist[channel]); + } + } + +/* + * TXDELAY - For transmit delay timing. + */ + + else if (strcasecmp(t, "TXDELAY") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + 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_modem->txdelay[channel] = n; + } + else { + p_modem->txdelay[channel] = DEFAULT_TXDELAY; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Invalid time for transmit delay. Using %d.\n", + line, p_modem->txdelay[channel]); + } + } + +/* + * TXTAIL - For transmit timing. + */ + + else if (strcasecmp(t, "TXTAIL") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + 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_modem->txtail[channel] = n; + } + else { + p_modem->txtail[channel] = DEFAULT_TXTAIL; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Invalid time for transmit timing. Using %d.\n", + line, p_modem->txtail[channel]); + } + } + +/* + * ==================== Digipeater parameters ==================== + */ + + else if (strcasecmp(t, "digipeat") == 0) { + int from_chan, to_chan; + int e; + char message[100]; + + + t = strtok (NULL, " ,\t\n\r"); + 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 > p_digi_config->num_chans-1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", + p_digi_config->num_chans-1, line); + continue; + } + + t = strtok (NULL, " ,\t\n\r"); + 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 > p_digi_config->num_chans-1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", + p_digi_config->num_chans-1, line); + continue; + } + + t = strtok (NULL, " ,\t\n\r"); + 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 = strtok (NULL, " ,\t\n\r"); + 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 = strtok (NULL, " ,\t\n\r"); + if (t != NULL) { + if (strcasecmp(t, "OFF") == 0) { + p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF; + } + else if (strcasecmp(t, "DROP") == 0) { + p_digi_config->preempt[from_chan][to_chan] = PREEMPT_DROP; + } + else if (strcasecmp(t, "MARK") == 0) { + p_digi_config->preempt[from_chan][to_chan] = PREEMPT_MARK; + } + else if (strcasecmp(t, "TRACE") == 0) { + p_digi_config->preempt[from_chan][to_chan] = PREEMPT_TRACE; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: Expected OFF, DROP, MARK, or TRACE on line %d.\n", line); + } + + } + } + +/* + * DEDUPE - Time to suppress digipeating of duplicate packets. + */ + + else if (strcasecmp(t, "DEDUPE") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + 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); + } + } + + +/* + * ==================== APRStt gateway ==================== + */ + +/* + * TTCORRAL - How to handle unknown positions + * + * TTCORRAL latitude longitude offset-or-ambiguity + */ + + else if (strcasecmp(t, "TTCORRAL") == 0) { + //int n; + t = strtok (NULL, " ,\t\n\r"); + 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 = strtok (NULL, " ,\t\n\r"); + 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 = strtok (NULL, " ,\t\n\r"); + 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); + + /* 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; + strcpy(tl->pattern, ""); + tl->point.lat = 0; + tl->point.lon = 0; + + /* Pattern: B and digits */ + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing pattern for TTPOINT command.\n", line); + continue; + } + strcpy (tl->pattern, t); + + 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; jpoint.lat = parse_ll(t,LAT,line); + + /* Longitude */ + + t = strtok (NULL, " ,\t\n\r"); + 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; + strcpy(tl->pattern, ""); + tl->vector.lat = 0; + tl->vector.lon = 0; + tl->vector.scale = 1; + + /* Pattern: B5bbbd... */ + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing pattern for TTVECTOR command.\n", line); + continue; + } + strcpy (tl->pattern, t); + + 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; jvector.lat = parse_ll(t,LAT,line); + + /* Longitude */ + + t = strtok (NULL, " ,\t\n\r"); + 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 = strtok (NULL, " ,\t\n\r"); + 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 = strtok (NULL, " ,\t\n\r"); + 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; + strcpy(tl->pattern, ""); + tl->grid.lat0 = 0; + tl->grid.lon0 = 0; + tl->grid.lat9 = 0; + tl->grid.lon9 = 0; + + /* Pattern: B [digit] x... y... */ + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing pattern for TTGRID command.\n", line); + continue; + } + strcpy (tl->pattern, t); + + 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; jgrid.lat0 = parse_ll(t,LAT,line); + + /* Minimum Longitude - all zeros in received data */ + + t = strtok (NULL, " ,\t\n\r"); + 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 = strtok (NULL, " ,\t\n\r"); + 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 = strtok (NULL, " ,\t\n\r"); + 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; + int znum; + char *zlet; + + 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; + strcpy(tl->pattern, ""); + strcpy(tl->utm.zone, ""); + tl->utm.scale = 1; + tl->utm.x_offset = 0; + tl->utm.y_offset = 0; + + /* Pattern: B [digit] x... y... */ + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing pattern for TTUTM command.\n", line); + continue; + } + strcpy (tl->pattern, t); + + if (t[0] != 'B') { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: TTUTM pattern must begin with upper case 'B'.\n", line); + } + for (j=1; jutm.zone, 0, sizeof (tl->utm.zone)); + strncpy (tl->utm.zone, t, sizeof (tl->utm.zone) - 1); + + znum = strtoul(tl->utm.zone, &zlet, 10); + + if (znum < 1 || znum > 60) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Zone number is out of range.\n\n", line); + continue; + } + + if (*zlet != '\0' && strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Latitudinal band must be one of CDEFGHJKLMNPQRSTUVWX.\n\n", line); + continue; + } + + /* Optional scale. */ + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + continue; + } + tl->utm.scale = atof(t); + + /* Optional x offset. */ + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + continue; + } + tl->utm.x_offset = atof(t); + + /* Optional y offset. */ + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + continue; + } + tl->utm.y_offset = atof(t); + } + +/* + * TTMACRO - Define compact message format with full expansion + * + * TTMACRO pattern definition + */ + else if (strcasecmp(t, "TTMACRO") == 0) { + + struct ttloc_s *tl; + int j; + //char ch; + int p_count[3], d_count[3]; + + 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; + strcpy(tl->pattern, ""); + + /* Pattern: Any combination of digits, x, y, and z. */ + /* Also make note of which letters are used in pattern and defintition. */ + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing pattern for TTMACRO command.\n", line); + continue; + } + strcpy (tl->pattern, t); + + p_count[0] = p_count[1] = p_count[2] = 0; + + for (j=0; j= 'x' && t[j] <= 'z') { + p_count[t[j]-'x']++; + } + } + + /* Now gather up the definition. */ + /* It can contain touch tone characters and lower case x, y, z for substitutions. */ + + t = strtok (NULL, " ,\t\n\r"); + 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. */ + continue; + } + tl->macro.definition = strdup(t); + + d_count[0] = d_count[1] = d_count[2] = 0; + + for (j=0; j= 'x' && t[j] <= 'z') { + d_count[t[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); + } + } + } + +/* + * TTOBJ - TT Object Report options. + * + * TTOBJ xmit-chan header + */ + + +// TODO: header can be generated automatically. Should not be in config file. + + + else if (strcasecmp(t, "TTOBJ") == 0) { + int n; + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing transmit channel for TTOBJ command.\n", line); + continue; + } + + n = atoi(t); + if (n < 0 || n > p_digi_config->num_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", + p_digi_config->num_chans-1, line); + continue; + } + p_tt_config->obj_xmit_chan = n; + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing object header for TTOBJ command.\n", line); + continue; + } + // TODO: Should do some validity checking. + + strncpy (p_tt_config->obj_xmit_header, t, sizeof(p_tt_config->obj_xmit_header)); + + } + +/* + * ==================== 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 = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing IGate server name for IGSERVER command.\n", line); + continue; + } + strncpy (p_igate_config->t2_server_name, t, sizeof(p_igate_config->t2_server_name)-1); + + /* 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 = strtok (NULL, " ,\t\n\r"); + 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); + } + } + //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 = strtok (NULL, " ,\t\n\r"); + 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. + strncpy (p_igate_config->t2_login, t, sizeof(p_igate_config->t2_login)-1); + + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing passcode for IGLOGIN command.\n", line); + continue; + } + strncpy (p_igate_config->t2_passcode, t, sizeof(p_igate_config->t2_passcode)-1); + } + +/* + * IGTXVIA - Transmit channel and VIA path for messages from IGate server + * + * IGTXVIA channel [ path ] + */ + + else if (strcasecmp(t, "IGTXVIA") == 0) { + int n; + + t = strtok (NULL, " ,\t\n\r"); + 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 > p_digi_config->num_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", + p_digi_config->num_chans-1, line); + continue; + } + p_igate_config->tx_chan = n; + + t = strtok (NULL, " \t\n\r"); + if (t != NULL) { + char *p; + p_igate_config->tx_via[0] = ','; + strncpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-2); + for (p = p_igate_config->tx_via; *p != '\0'; p++) { + if (islower(*p)) { + *p = toupper(*p); /* silently force upper case. */ + } + } + } + } + +/* + * IGFILTER - Filter for messages from IGate server + * + * IGFILTER filter-spec ... + */ + + else if (strcasecmp(t, "IGFILTER") == 0) { + //int n; + + t = strtok (NULL, "\n\r"); /* Take rest of line as one string. */ + + 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 = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing one minute limit for IGTXLIMIT command.\n", line); + continue; + } + + /* limits of 20 and 100 are unfriendly but not insane. */ + + n = atoi(t); + if (n >= 1 && n <= 20) { + p_igate_config->tx_limit_1 = n; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Invalid one minute transmit limit. Using %d.\n", + line, p_igate_config->tx_limit_1); + } + + t = strtok (NULL, " ,\t\n\r"); + 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 && n <= 100) { + p_igate_config->tx_limit_5 = n; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Invalid one minute transmit limit. Using %d.\n", + line, p_igate_config->tx_limit_5); + } + } + +/* + * ==================== All the left overs ==================== + */ + +/* + * AGWPORT - Port number for "AGW TCPIP Socket Interface" + */ + + else if (strcasecmp(t, "AGWPORT") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + 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) { + 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 = strtok (NULL, " ,\t\n\r"); + 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) { + 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 - Device name for our end of the virtual "null modem" + */ + else if (strcasecmp(t, "nullmodem") == 0) { + t = strtok (NULL, " ,\t\n\r"); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: Missing device name for my end of the 'null modem' on line %d.\n", line); + continue; + } + else { + strncpy (p_misc_config->nullmodem, t, sizeof(p_misc_config->nullmodem)-1); + } + } + +/* + * FIX_BITS - Attempt to fix frames with bad FCS. + */ + + else if (strcasecmp(t, "FIX_BITS") == 0) { + int n; + t = strtok (NULL, " ,\t\n\r"); + 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_TWO_SEP) { + p_modem->fix_bits = (retry_t)n; + } + else { + p_modem->fix_bits = DEFAULT_FIX_BITS; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Invalid value for FIX_BITS. Using %d.\n", + line, p_modem->fix_bits); + } + } + +/* + * 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); + + } + + +/* + * PBEACON keyword=value ... + * OBEACON keyword=value ... + * TBEACON keyword=value ... + * CBEACON 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) { + + 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 { + 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_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 + */ + + else if (strcasecmp(t, "SMARTBEACON") == 0 || + strcasecmp(t, "SMARTBEACONING") == 0) { + + int n; + +#define SB_NUM(name,sbvar,minn,maxx,unit) \ + t = strtok (NULL, " ,\t\n\r"); \ + if (t == NULL) { \ + 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 = strtok (NULL, " ,\t\n\r"); \ + 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; + } + +/* + * 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; inum_chans; i++) { + for (j=0; jnum_chans; j++) { + + if (p_digi_config->enabled[i][j]) { + + if (strcmp(p_digi_config->mycall[i], "NOCALL") == 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_digi_config->mycall[j], "NOCALL") == 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[p_misc_config->num_beacons].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", i); + p_digi_config->enabled[i][j] = 0; + } + } + } + + if (strlen(p_igate_config->t2_login) > 0) { + + if (strcmp(p_digi_config->mycall[i], "NOCALL") == 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); + strcpy (p_igate_config->t2_login, ""); + } + if (p_igate_config->tx_chan >= 0 && + strcmp(p_digi_config->mycall[p_igate_config->tx_chan], "NOCALL") == 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; + } + } + + } + +} /* 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) +{ + char options[1000]; + char *o; + char *t; + char *p; + int q; + char temp_symbol[100]; + int ok; + + strcpy (temp_symbol, ""); + + b->chan = 0; + b->delay = 60; + b->every = 600; + //b->delay = 6; // TODO: temp. remove + //b->every = 3600; + b->lat = G_UNKNOWN; + b->lon = G_UNKNOWN; + b->symtab = '/'; + b->symbol = '-'; /* house */ + +/* + * cmd should be rest of command line after ?BEACON was removed. + * + * Quoting is required for any values containing spaces. + * This could happen for an object name, comment, symbol description, ... + * To prevent strtok from stopping at those spaces, change them to + * non-breaking space character temporarily. After spliting everything + * up at white space, change them back to normal spaces. + */ + +#define NBSP (' ' + 0x80) + + p = cmd; /* Process from here. */ + o = options; /* to here. */ + q = 0; /* Keep track of whether in quoted part. */ + + for ( ; *p != '\0' ; p++) { + + switch (*p) { + + case '"': + if (!q) { /* opening quote */ + if (*(p-1) != '=') { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: line %d: Suspicious use of \" not after =.\n", line); + dw_printf ("Suggestion: Double it and quote entire value.\n"); + *o++ = '"'; /* Treat as regular character. */ + } + else { + q = 1; + } + } + else { /* embedded or closing quote */ + if (*(p+1) == '"') { + *o++ = '"'; /* reduce double to single */ + p++; + } + else if (isspace(*(p+1)) || *(p+1) == '\0') { + q = 0; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: line %d: Suspicious use of \" not at end of value.\n", line); + dw_printf ("Suggestion: Double it and quote entire value.\n"); + *o++ = '"'; /* Treat as regular character. */ + } + } + break; + + case ' ': + + *o++ = q ? NBSP : ' '; + break; + + default: + *o++ = *p; + break; + } + } + *o = '\0'; + + for (t = strtok (options, " \t\n\r"); t != NULL; t = strtok (NULL, " \t\n\r")) { + + char keyword[20]; + char value[200]; + char *e; + char *p; + //int q; + + + 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'; + strcpy (keyword, t); + strcpy (value, e+1); + +/* Put back normal spaces. */ + + for (p = value; *p != '\0'; p++) { + // char is signed for MinGW! + if (((int)(*p) & 0xff) == NBSP) *p = ' '; + } + + if (strcasecmp(keyword, "DELAY") == 0) { + b->delay = parse_interval(value,line); + } + 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->chan = -1; + } + else { + b->chan = atoi(value); + } + } + else if (strcasecmp(keyword, "VIA") == 0) { + b->via = strdup(value); + for (p = b->via; *p != '\0'; p++) { + if (islower(*p)) { + *p = toupper(*p); /* silently force upper case. */ + } + } + } + else if (strcasecmp(keyword, "INFO") == 0) { + b->custom_info = strdup(value); + } + else if (strcasecmp(keyword, "OBJNAME") == 0) { + strncpy(b->objname, value, 9); + } + 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, "SYMBOL") == 0) { + /* Defer processing in case overlay appears later. */ + strcpy (temp_symbol, value); + } + 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) { + strncpy(b->dir, value, 2); + } + 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, "COMPRESS") == 0 || strcasecmp(keyword, "COMPRESSED") == 0) { + b->compress = atoi(value); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: Invalid option keyword, %s.\n", line, keyword); + return (0); + } + } + +/* + * 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); + } + } + } + + return (1); +} + +/* end config.c */ diff --git a/config.h b/config.h new file mode 100644 index 0000000..7eb8ca4 --- /dev/null +++ b/config.h @@ -0,0 +1,130 @@ + +/*---------------------------------------------------------------------------- + * + * 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 "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 }; + +#define MAX_BEACONS 30 + +struct misc_config_s { + + int num_channels; /* Number of radio channels. */ + + int agwpe_port; /* Port number for the “AGW TCPIP Socket Interface” */ + int kiss_port; /* Port number for the “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 nullmodem[40]; /* Serial port name for our end of the */ + /* virtual null modem for native Windows apps. */ + + 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 */ + + + 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. */ + + int chan; /* Send to Channel for transmission. -1 for IGate. */ + + int delay; /* Seconds to delay before first transmission. */ + + 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. */ + + 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. */ + + double lat; /* Latitude and longitude. */ + double lon; + + 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. */ + + + } 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 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 */ + + diff --git a/decode_aprs.c b/decode_aprs.c new file mode 100644 index 0000000..f049b73 --- /dev/null +++ b/decode_aprs.c @@ -0,0 +1,3948 @@ +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * 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 +#include +#include +#include /* for atof */ +#include /* for strtok */ +#if __WIN32__ +char *strsep(char **stringp, const char *delim); +#endif +#include /* for pow */ +#include /* for isdigit */ +#include + +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 1 +#endif +#include "regex.h" + +#include "direwolf.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "symbols.h" +#include "latlong.h" + +#define TRUE 1 +#define FALSE 0 + + +#define METERS_TO_FEET(x) ((x) * 3.2808399) +#define KNOTS_TO_MPH(x) ((x) * 1.15077945) +#define KM_TO_MILES(x) ((x) * 0.621371192) +#define MBAR_TO_INHG(x) ((x) * 0.0295333727) + + +/* 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; + + +static void print_decoded (void); + +static void aprs_ll_pos (unsigned char *, int); +static void aprs_ll_pos_time (unsigned char *, int); +static void aprs_raw_nmea (unsigned char *, int); +static void aprs_mic_e (packet_t, unsigned char *, int); +//static void aprs_compressed_pos (unsigned char *, int); +static void aprs_message (unsigned char *, int); +static void aprs_object (unsigned char *, int); +static void aprs_item (unsigned char *, int); +static void aprs_station_capabilities (char *, int); +static void aprs_status_report (char *, int); +static void aprs_telemetry (char *, int); +static void aprs_raw_touch_tone (char *, int); +static void aprs_morse_code (char *, int); +static void aprs_positionless_weather_report (unsigned char *, int); +static void weather_data (char *wdata, int wind_prefix); +static void aprs_ultimeter (char *, int); +static void third_party_header (char *, int); + + +static void decode_position (position_t *ppos); +static void decode_compressed_position (compressed_position_t *ppos); + +static double get_latitude_8 (char *p); +static double get_longitude_9 (char *p); + +static double get_latitude_nmea (char *pstr, char *phemi); +static double get_longitude_nmea (char *pstr, char *phemi); + +static time_t get_timestamp (char *p); +static int get_maidenhead (char *p); + +static int data_extension_comment (char *pdext); +static void decode_tocall (char *dest); +//static void get_symbol (char dti, char *src, char *dest); +static void process_comment (char *pstart, int clen); + + +/* + * Information extracted from the message. + */ + +/* for unknown values. */ + +//#define G_UNKNOWN -999999 + + +static char g_msg_type[30]; /* Message type. */ + +static 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 */ + + +static 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). */ + +static double g_lat, g_lon; /* Location, degrees. Negative for South or West. */ + /* Set to G_UNKNOWN if missing or error. */ + +static char g_maidenhead[9]; /* 4 or 6 (or 8?) character maidenhead locator. */ + +static char g_name[20]; /* Object or item name. */ + +static float g_speed; /* Speed in MPH. */ + +static float g_course; /* 0 = North, 90 = East, etc. */ + +static int g_power; /* Transmitter power in watts. */ + +static int g_height; /* Antenna height above average terrain, feet. */ + +static int g_gain; /* Antenna gain in dB. */ + +static char g_directivity[10]; /* Direction of max signal strength */ + +static float g_range; /* Precomputed radio range in miles. */ + +static float g_altitude; /* Feet above median sea level. */ + +static char g_mfr[80]; /* Manufacturer or application. */ + +static char g_mic_e_status[30]; /* MIC-E message. */ + +static char g_freq[40]; /* Frequency, tone, xmit offset */ + +static char g_comment[256]; /* Comment. */ + +/*------------------------------------------------------------------ + * + * Function: decode_aprs + * + * Purpose: Optionally print packet then decode it. + * + * Inputs: src - Source Station. + * + * The SSID is used as a last resort for the + * displayed symbol if not specified in any other way. + * + * dest - Destination Station. + * + * Certain destinations (GPSxxx, SPCxxx, SYMxxx) can + * be used to specify the display symbol. + * For the MIC-E format (used by Kenwood D7, D700), the + * "destination" is really the latitude. + * + * pinfo - pointer to information field. + * info_len - length of the information field. + * + * Outputs: Variables above: + * + * g_symbol_table, g_symbol_code, + * g_lat, g_lon, + * g_speed, g_course, g_altitude, + * g_comment + * ... and others... + * + * Other functions are then called to retrieve the information. + * + * Bug: This is not thread-safe because it uses static data and strtok. + * + *------------------------------------------------------------------*/ + +void decode_aprs (packet_t pp) +{ + //int naddr; + //int err; + char src[AX25_MAX_ADDR_LEN], dest[AX25_MAX_ADDR_LEN]; + //char *p; + //int ssid; + unsigned char *pinfo; + int info_len; + + + info_len = ax25_get_info (pp, &pinfo); + + sprintf (g_msg_type, "Unknown message type %c", *pinfo); + + g_symbol_table = '/'; + g_symbol_code = ' '; /* What should we have for default? */ + + g_lat = G_UNKNOWN; + g_lon = G_UNKNOWN; + strcpy (g_maidenhead, ""); + + strcpy (g_name, ""); + g_speed = G_UNKNOWN; + g_course = G_UNKNOWN; + + g_power = G_UNKNOWN; + g_height = G_UNKNOWN; + g_gain = G_UNKNOWN; + strcpy (g_directivity, ""); + + g_range = G_UNKNOWN; + g_altitude = G_UNKNOWN; + strcpy(g_mfr, ""); + strcpy(g_mic_e_status, ""); + strcpy(g_freq, ""); + strcpy (g_comment, ""); + +/* + * Extract source and destination including the SSID. + */ + + ax25_get_addr_with_ssid (pp, AX25_SOURCE, src); + ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest); + + + 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 ((char*)pinfo, info_len); + } + else + { + aprs_ll_pos (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 ((char*)pinfo, info_len); + } + else + { + aprs_raw_nmea (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 (pp, pinfo, info_len); + break; + + case ')': /* Item. */ + + aprs_item (pinfo, info_len); + break; + + case '/': /* Position with timestamp (no APRS messaging) */ + case '@': /* Position with timestamp (with APRS messaging) */ + + aprs_ll_pos_time (pinfo, info_len); + break; + + + case ':': /* Message */ + + aprs_message (pinfo, info_len); + break; + + case ';': /* Object */ + + aprs_object (pinfo, info_len); + break; + + case '<': /* Station Capabilities */ + + aprs_station_capabilities ((char*)pinfo, info_len); + break; + + case '>': /* Status Report */ + + aprs_status_report ((char*)pinfo, info_len); + break; + + //case '?': /* Query */ + //break; + + case 'T': /* Telemetry */ + aprs_telemetry ((char*)pinfo, info_len); + break; + + case '_': /* Positionless Weather Report */ + + aprs_positionless_weather_report (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 (pinfo, info_len); + } + else if (strncmp((char*)pinfo, "{mc", 3) == 0) { + aprs_morse_code ((char*)pinfo, info_len); + } + else { + //aprs_user_defined (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 ((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 ((char*)pinfo, info_len); + break; + + case '}': /* third party header */ + + third_party_header ((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 (g_symbol_table == ' ' || g_symbol_code == ' ') { + + symbols_from_dest_or_src (*pinfo, src, dest, &g_symbol_table, &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 (dest); + break; + } + +/* + * Print it all out in human readable format. + */ + print_decoded (); +} + + +static void print_decoded (void) { + + 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 + */ + strcpy (stemp, g_msg_type); + + if (strlen(g_name) > 0) { + strcat (stemp, ", \""); + strcat (stemp, g_name); + strcat (stemp, "\""); + } + + symbols_get_description (g_symbol_table, g_symbol_code, symbol_description); + strcat (stemp, ", "); + strcat (stemp, symbol_description); + + if (strlen(g_mfr) > 0) { + strcat (stemp, ", "); + strcat (stemp, g_mfr); + } + + if (strlen(g_mic_e_status) > 0) { + strcat (stemp, ", "); + strcat (stemp, g_mic_e_status); + } + + + if (g_power > 0) { + char phg[100]; + + sprintf (phg, ", %d W height=%d %ddBi %s", g_power, g_height, g_gain, g_directivity); + strcat (stemp, phg); + } + + if (g_range > 0) { + char rng[100]; + + sprintf (rng, ", range=%.1f", g_range); + strcat (stemp, rng); + } + 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 + * + * Bug: This does not check for invalid values. + */ + + if (strlen(g_maidenhead) > 0) { + dw_printf("Grid square = %s, ", g_maidenhead); + + if (g_lat == G_UNKNOWN && g_lon == G_UNKNOWN) { + + g_lon = (toupper(g_maidenhead[0]) - 'A') * 20 - 180; + g_lat = (toupper(g_maidenhead[1]) - 'A') * 10 - 90; + + g_lon += (g_maidenhead[2] - '0') * 2; + g_lat += (g_maidenhead[3] - '0'); + + if (strlen(g_maidenhead) >=6) { + g_lon += (toupper(g_maidenhead[4]) - 'A') * 5.0 / 60.0; + g_lat += (toupper(g_maidenhead[5]) - 'A') * 2.5 / 60.0; + + g_lon += 2.5 / 60.0; /* Move from corner to center of square */ + g_lat += 1.25 / 60.0; + } + else { + g_lon += 1.0; /* Move from corner to center of square */ + g_lat += 0.5; + } + } + } + + strcpy (stemp, ""); + + if (g_lat != G_UNKNOWN || g_lon != G_UNKNOWN) { + +// Have location but it is posible one part is invalid. + + if (g_lat != G_UNKNOWN) { + + if (g_lat >= 0) { + absll = g_lat; + news = 'N'; + } + else { + absll = - g_lat; + news = 'S'; + } + deg = (int) absll; + min = (absll - deg) * 60.0; + sprintf (s_lat, "%c %02d%s%07.4f", news, deg, CH_DEGREE, min); + } + else { + strcpy (s_lat, "Invalid Latitude"); + } + + if (g_lon != G_UNKNOWN) { + + if (g_lon >= 0) { + absll = g_lon; + news = 'E'; + } + else { + absll = - g_lon; + news = 'W'; + } + deg = (int) absll; + min = (absll - deg) * 60.0; + sprintf (s_lon, "%c %03d%s%07.4f", news, deg, CH_DEGREE, min); + } + else { + strcpy (s_lon, "Invalid Longitude"); + } + + sprintf (stemp, "%s, %s", s_lat, s_lon); + } + + if (g_speed != G_UNKNOWN) { + char spd[20]; + + if (strlen(stemp) > 0) strcat (stemp, ", "); + sprintf (spd, "%.0f MPH", g_speed); + strcat (stemp, spd); + }; + + if (g_course != G_UNKNOWN) { + char cse[20]; + + if (strlen(stemp) > 0) strcat (stemp, ", "); + sprintf (cse, "course %.0f", g_course); + strcat (stemp, cse); + }; + + if (g_altitude != G_UNKNOWN) { + char alt[20]; + + if (strlen(stemp) > 0) strcat (stemp, ", "); + sprintf (alt, "alt %.0f ft", g_altitude); + strcat (stemp, alt); + }; + + if (strlen(g_freq) > 0) { + strcat (stemp, ", "); + strcat (stemp, g_freq); + } + + + if (strlen (stemp) > 0) { + text_color_set(DW_COLOR_DECODED); + dw_printf("%s\n", stemp); + } + + +/* + * Third line has: + * - comment or weather + * + * 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(g_comment); + if (n >= 1 && g_comment[n-1] == '\n') { + g_comment[n-1] = '\0'; + n--; + } + if (n >= 1 && g_comment[n-1] == '\r') { + g_comment[n-1] = '\0'; + n--; + } + if (n > 0) { + int j; + + ax25_safe_print (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. + */ + for (j=0; jpos.lat[0]))) /* Human-readable location. */ + { + decode_position (&(p->pos)); + + if (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. */ + + strcpy (g_msg_type, "Weather Report"); + weather_data (p->comment, TRUE); + } + else { + /* Regular position report. */ + + data_extension_comment (p->comment); + } + } + else /* Compressed location. */ + { + decode_compressed_position (&(q->cpos)); + + if (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. */ + + strcpy (g_msg_type, "Weather Report"); + weather_data (q->comment, FALSE); + } + else { + /* Regular position report. */ + + process_comment (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: g_lat, g_lon, g_symbol_table, g_symbol_code, g_speed, g_course, g_altitude. + * + * 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 (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; + + + strcpy (g_msg_type, "Position with time"); + + 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 (p->time_stamp); + decode_position (&(p->pos)); + + if (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. */ + + strcpy (g_msg_type, "Weather Report"); + weather_data (p->comment, TRUE); + } + else { + /* Regular position report. */ + + data_extension_comment (p->comment); + } + } + else /* Compressed location. */ + { + ts = get_timestamp (p->time_stamp); + + decode_compressed_position (&(q->cpos)); + + if (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. */ + + strcpy (g_msg_type, "Weather Report"); + weather_data (q->comment, FALSE); + } + else { + /* Regular position report. */ + + process_comment (q->comment, -1); + } + } + +} + + +/*------------------------------------------------------------------ + * + * Function: aprs_raw_nmea + * + * Purpose: Decode "Raw NMEA Position Report" + * + * Inputs: info - Pointer to Information field. + * ilen - Information field length. + * + * Outputs: ??? TBD + * + * 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 + * + * 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 nmea_checksum (char *sent) +{ + char *p; + char *next; + 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) { + text_color_set (DW_COLOR_INFO); + dw_printf("Missing GPS checksum.\n"); + return; + } + if (cs != strtoul(p+1, NULL, 16)) { + text_color_set (DW_COLOR_ERROR); + dw_printf("GPS checksum error. Expected %02x but found %s.\n", cs, p+1); + return; + } + *p = '\0'; // Remove the checksum. +} + +static void aprs_raw_nmea (unsigned char *info, int ilen) +{ + char stemp[256]; + char *ptype; + char *next; + + + strcpy (g_msg_type, "Raw NMEA"); + + strncpy (stemp, (char *)info, ilen); + stemp[ilen] = '\0'; + nmea_checksum (stemp); + + next = stemp; + ptype = strsep(&next, ","); + + if (strcmp(ptype, "$GPGGA") == 0) + { + char *ptime; /* Time, hhmmss[.sss] */ + char *plat; /* Latitude */ + char *pns; /* North/South */ + char *plon; /* Longitude */ + char *pew; /* East/West */ + char *pquality; /* Fix Quality: 0=invalid, 1=GPS, 2=DGPS */ + char *pnsat; /* Number of satellites. */ + char *phdop; /* Horizontal dilution of precision. */ + char *paltitude; /* Altitude, meters above mean sea level. */ + char *pm; /* "M" = meters */ + /* Various other stuff... */ + + + ptime = strsep(&next, ","); + plat = strsep(&next, ","); + pns = strsep(&next, ","); + plon = strsep(&next, ","); + pew = strsep(&next, ","); + pquality = strsep(&next, ","); + pnsat = strsep(&next, ","); + phdop = strsep(&next, ","); + paltitude = strsep(&next, ","); + pm = strsep(&next, ","); + + /* Process time??? */ + + if (plat != NULL && strlen(plat) > 0) { + g_lat = get_latitude_nmea(plat, pns); + } + if (plon != NULL && strlen(plon) > 0) { + g_lon = get_longitude_nmea(plon, pew); + } + if (paltitude != NULL && strlen(paltitude) > 0) { + g_altitude = METERS_TO_FEET(atof(paltitude)); + } + } + else if (strcmp(ptype, "$GPGLL") == 0) + { + char *plat; /* Latitude */ + char *pns; /* North/South */ + char *plon; /* Longitude */ + char *pew; /* East/West */ + /* optional Time hhmmss[.sss] */ + /* optional 'A' for data valid */ + + plat = strsep(&next, ","); + pns = strsep(&next, ","); + plon = strsep(&next, ","); + pew = strsep(&next, ","); + + if (plat != NULL && strlen(plat) > 0) { + g_lat = get_latitude_nmea(plat, pns); + } + if (plon != NULL && strlen(plon) > 0) { + g_lon = get_longitude_nmea(plon, pew); + } + + } + else if (strcmp(ptype, "$GPRMC") == 0) + { + //char *ptime, *pstatus, *plat, *pns, *plon, *pew, *pspeed, *ptrack, *pdate; + + 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 */ + + 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, ","); + + /* process time ??? date ??? */ + + if (plat != NULL && strlen(plat) > 0) { + g_lat = get_latitude_nmea(plat, pns); + } + if (plon != NULL && strlen(plon) > 0) { + g_lon = get_longitude_nmea(plon, pew); + } + if (pknots != NULL && strlen(pknots) > 0) { + g_speed = KNOTS_TO_MPH(atof(pknots)); + } + if (pcourse != NULL && strlen(pcourse) > 0) { + g_course = atof(pcourse); + } + } + else if (strcmp(ptype, "$GPVTG") == 0) + { + + /* Speed and direction but NO location! */ + + char *ptcourse; /* True course, degrees. */ + char *pt; /* "T" */ + char *pmcourse; /* Magnetic course, degrees. */ + char *pm; /* "M" */ + char *pknots; /* Ground speed, knots. */ + char *pn; /* "N" = Knots */ + char *pkmh; /* Ground speed, km/hr */ + char *pk; /* "K" = Kilometers per hour */ + char *pmode; /* New in NMEA 0183 version 3.0 */ + /* Mode: A=Autonomous, D=Differential, */ + + ptcourse = strsep(&next, ","); + pt = strsep(&next, ","); + pmcourse = strsep(&next, ","); + pm = strsep(&next, ","); + pknots = strsep(&next, ","); + pn = strsep(&next, ","); + pkmh = strsep(&next, ","); + pk = strsep(&next, ","); + pmode = strsep(&next, ","); + + if (pknots != NULL && strlen(pknots) > 0) { + g_speed = KNOTS_TO_MPH(atof(pknots)); + } + if (ptcourse != NULL && strlen(ptcourse) > 0) { + g_course = atof(ptcourse); + } + + } + else if (strcmp(ptype, "$GPWPL") == 0) + { + //char *plat, *pns, *plon, *pew, *pident; + + char *plat; /* Latitude */ + char *pns; /* North/South */ + char *plon; /* Longitude */ + char *pew; /* East/West */ + char *pident; /* Identifier for Waypoint. rules??? */ + /* checksum */ + + plat = strsep(&next, ","); + pns = strsep(&next, ","); + plon = strsep(&next, ","); + pew = strsep(&next, ","); + pident = strsep(&next, ","); + + if (plat != NULL && strlen(plat) > 0) { + g_lat = get_latitude_nmea(plat, pns); + } + if (plon != NULL && strlen(plon) > 0) { + g_lon = get_longitude_nmea(plon, pew); + } + + /* do something with identifier? */ + + } +} + + + +/*------------------------------------------------------------------ + * + * 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 + * + * 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. + * + *------------------------------------------------------------------*/ + +static int mic_e_digit (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); + } + + 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 (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; + + strcpy (g_msg_type, "MIC-E"); + + 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); + + g_lat = mic_e_digit(dest[0], 4, &std_msg, &cust_msg) * 10 + + mic_e_digit(dest[1], 2, &std_msg, &cust_msg) + + (mic_e_digit(dest[2], 1, &std_msg, &cust_msg) * 1000 + + mic_e_digit(dest[3], 0, &std_msg, &cust_msg) * 100 + + mic_e_digit(dest[4], 0, &std_msg, &cust_msg) * 10 + + mic_e_digit(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 */ + g_lat = ( - g_lat); + } + else if (dest[3] >= 'P' && dest[3] <= 'Z') + { + /* North */ + } + else + { + 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; + 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) + { + g_lon = ch - 118; /* 0 - 9 degrees */ + } + else if ( ! offset && ch >= 38 && ch <= 127) + { + g_lon = (ch - 38) + 10; /* 10 - 99 degrees */ + } + else if (offset && ch >= 108 && ch <= 117) + { + g_lon = (ch - 108) + 100; /* 100 - 109 degrees */ + } + else if (offset && ch >= 38 && ch <= 107) + { + g_lon = (ch - 38) + 110; /* 110 - 179 degrees */ + } + else + { + g_lon = G_UNKNOWN; + 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 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 (g_lon != G_UNKNOWN) + { + ch = p->lon[1]; + + if (ch >= 88 && ch <= 97) + { + g_lon += (ch - 88) / 60.0; /* 0 - 9 minutes*/ + } + else if (ch >= 38 && ch <= 87) + { + g_lon += ((ch - 38) + 10) / 60.0; /* 10 - 59 minutes */ + } + else { + g_lon = G_UNKNOWN; + 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 (g_lon != G_UNKNOWN) + { + ch = p->lon[2]; + + if (ch >= 28 && ch <= 127) + { + g_lon += ((ch - 28) + 0) / 6000.0; /* 0 - 99 hundredths of minutes*/ + } + else { + g_lon = G_UNKNOWN; + 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. */ + + if ((dest[5] >= '0' && dest[5] <= '9') || dest[5] == 'L') { + /* East */ + } + else if (dest[5] >= 'P' && dest[5] <= 'Z') + { + /* West */ + if (g_lon != G_UNKNOWN) { + g_lon = ( - g_lon); + } + } + else + { + 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. */ + + g_symbol_table = p->sym_table_id; + g_symbol_code = p->symbol_code; + + if (g_symbol_table != '/' && g_symbol_table != '\\' + && ! isupper(g_symbol_table) && ! isdigit(g_symbol_table)) + { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid symbol table code not one of / \\ A-Z 0-9\n"); + g_symbol_table = '/'; + } + +/* Message type from two 3-bit codes. */ + + if (std_msg == 0 && cust_msg == 0) { + strcpy (g_mic_e_status, "Emergency"); + } + else if (std_msg == 0 && cust_msg != 0) { + strcpy (g_mic_e_status, cust_text[cust_msg]); + } + else if (std_msg != 0 && cust_msg == 0) { + strcpy (g_mic_e_status, std_text[std_msg]); + } + else { + strcpy (g_mic_e_status, "Unknown MIC-E Message Type"); + } + +/* 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; + + g_speed = 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) + g_course = G_UNKNOWN; + else if (n == 360) + g_course = 0; + else + g_course = n; + + +/* Now try to pick out manufacturer and other optional items. */ +/* The telemetry field, in the original spec, is no longer used. */ + + 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. */ + + if (*plast == '\r') plast--; + + if (*pfirst == ' ' || *pfirst == '>' || *pfirst == ']' || *pfirst == '`' || *pfirst == '\'') { + + if (*pfirst == ' ') { strcpy (g_mfr, "Original MIC-E"); pfirst++; } + + else if (*pfirst == '>' && *plast == '=') { strcpy (g_mfr, "Kenwood TH-D72"); pfirst++; plast--; } + else if (*pfirst == '>') { strcpy (g_mfr, "Kenwood TH-D7A"); pfirst++; } + + else if (*pfirst == ']' && *plast == '=') { strcpy (g_mfr, "Kenwood TM-D710"); pfirst++; plast--; } + else if (*pfirst == ']') { strcpy (g_mfr, "Kenwood TM-D700"); pfirst++; } + + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strcpy (g_mfr, "Yaesu VX-8"); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strcpy (g_mfr, "Yaesu FTM-350"); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strcpy (g_mfr, "Yaesu VX-8G"); pfirst++; plast-=2; } + else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strcpy (g_mfr, "Byonics TinyTrack3"); pfirst++; plast-=2; } + else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strcpy (g_mfr, "Byonics TinyTrack4"); pfirst++; plast-=2; } + + else if (*(plast-1) == '\\') { strcpy (g_mfr, "Hamhud ?"); pfirst++; plast-=2; } + else if (*(plast-1) == '/') { strcpy (g_mfr, "Argent ?"); pfirst++; plast-=2; } + else if (*(plast-1) == '^') { strcpy (g_mfr, "HinzTec anyfrog"); pfirst++; plast-=2; } + else if (*(plast-1) == '~') { strcpy (g_mfr, "OTHER"); pfirst++; plast-=2; } + + else if (*pfirst == '`') { strcpy (g_mfr, "Mic-Emsg"); pfirst++; plast-=2; } + else if (*pfirst == '\'') { strcpy (g_mfr, "McTrackr"); 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] == '}') { + + g_altitude = METERS_TO_FEET((pfirst[0]-33)*91*91 + (pfirst[1]-33)*91 + (pfirst[2]-33) - 10000); + + if (pfirst[0] < '!' || pfirst[0] > '{' || + pfirst[1] < '!' || pfirst[1] > '{' || + pfirst[2] < '!' || pfirst[2] > '{' ) + { + 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", g_altitude); + g_altitude = G_UNKNOWN; + } + + pfirst += 4; + } + + process_comment ((char*)pfirst, (int)(plast - pfirst) + 1); + +} + + +/*------------------------------------------------------------------ + * + * Function: aprs_message + * + * Purpose: Decode "Message Format" + * + * Inputs: info - Pointer to Information field. + * ilen - Information field length. + * + * Outputs: ??? TBD + * + * Description: An APRS message is a text string with a specifed addressee. + * + * It's a lot more complicated with different types of addressees + * and replies with acknowledgement or rejection. + * + * Displaying and logging these messages could be useful. + * + * Examples: + * + * + *------------------------------------------------------------------*/ + +static void aprs_message (unsigned char *info, int ilen) +{ + + struct aprs_message_s { + char dti; /* : */ + char addressee[9]; + char colon; /* : */ + char message[73]; /* 0-67 characters for message */ + /* { followed by 1-5 characters for message number */ + } *p; + + + p = (struct aprs_message_s *)info; + + sprintf (g_msg_type, "APRS Message for \"%9.9s\"", p->addressee); + + /* No location so don't use process_comment () */ + + strcpy (g_comment, p->message); + +} + + + +/*------------------------------------------------------------------ + * + * Function: aprs_object + * + * Purpose: Decode "Object Report Format" + * + * Inputs: info - Pointer to Information field. + * ilen - Information field length. + * + * Outputs: g_object_name, g_lat, g_lon, g_symbol_table, g_symbol_code, g_speed, g_course, g_altitude. + * + * 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 (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; + + strncpy (g_name, p->name, 9); + g_name[9] = '\0'; + i = strlen(g_name) - 1; + while (i >= 0 && g_name[i] == ' ') { + g_name[i--] = '\0'; + } + + if (p->live_killed == '*') + strcpy (g_msg_type, "Object"); + else if (p->live_killed == '_') + strcpy (g_msg_type, "Killed Object"); + else + strcpy (g_msg_type, "Object - invalid live/killed"); + + ts = get_timestamp (p->time_stamp); + + if (isdigit((unsigned char)(p->pos.lat[0]))) /* Human-readable location. */ + { + decode_position (&(p->pos)); + + if (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. */ + + strcpy (g_msg_type, "Weather Report with Object"); + weather_data (p->comment, TRUE); + } + else { + /* Regular object. */ + + data_extension_comment (p->comment); + } + } + else /* Compressed location. */ + { + decode_compressed_position (&(q->cpos)); + + if (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. */ + + strcpy (g_msg_type, "Weather Report with Object"); + weather_data (q->comment, FALSE); + } + else { + /* Regular position report. */ + + process_comment (q->comment, -1); + } + } + +} + + +/*------------------------------------------------------------------ + * + * Function: aprs_item + * + * Purpose: Decode "Item Report Format" + * + * Inputs: info - Pointer to Information field. + * ilen - Information field length. + * + * Outputs: g_object_name, g_lat, g_lon, g_symbol_table, g_symbol_code, g_speed, g_course, g_altitude. + * + * 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 (unsigned char *info, int ilen) +{ + + struct aprs_item_s { + char dti; /* ) */ + char name[9]; /* Actually variable length 3 - 9 bytes. */ + 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[9]; /* Actually variable length 3 - 9 bytes. */ + char live_killed; /* ! for live or _ for killed */ + compressed_position_t cpos; + char comment[40]; /* No data extension in this case. */ + } *q; + + + time_t ts = 0; + int i; + char *ppos; + + + p = (struct aprs_item_s *)info; + q = (struct aprs_compressed_item_s *)info; + + i = 0; + while (i < 9 && p->name[i] != '!' && p->name[i] != '_') { + g_name[i] = p->name[i]; + i++; + g_name[i] = '\0'; + } + + if (p->name[i] == '!') + strcpy (g_msg_type, "Item"); + else if (p->name[i] == '_') + strcpy (g_msg_type, "Killed Item"); + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Item name too long or not followed by ! or _.\n"); + strcpy (g_msg_type, "Object - invalid live/killed"); + } + + ppos = p->name + i + 1; + + if (isdigit(*ppos)) /* Human-readable location. */ + { + decode_position ((position_t*) ppos); + + data_extension_comment (ppos + sizeof(position_t)); + } + else /* Compressed location. */ + { + decode_compressed_position ((compressed_position_t*)ppos); + + process_comment (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 (char *info, int ilen) +{ + + strcpy (g_msg_type, "Station Capabilities"); + + // Is process_comment() applicable? + + strcpy (g_comment, info+1); +} + + + + +/*------------------------------------------------------------------ + * + * 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 (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; + + + strcpy (g_msg_type, "Status Report"); + + 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') { + + strcpy (g_comment, pt->comment); + } + +/* + * Do we have format with 6 character Maidenhead locator? + */ + else if (get_maidenhead (pm6->mhead6) == 6) { + + strncpy (g_maidenhead, pm6->mhead6, 6); + g_maidenhead[6] = '\0'; + + g_symbol_table = pm6->sym_table_id; + g_symbol_code = pm6->symbol_code; + + if (g_symbol_table != '/' && g_symbol_table != '\\' + && ! isupper(g_symbol_table) && ! isdigit(g_symbol_table)) + { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", g_symbol_table); + g_symbol_table = '/'; + } + + if (pm6->space != ' ' && pm6->space != '\0') { + text_color_set(DW_COLOR_ERROR); + dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm6->space); + } + + strcpy (g_comment, pm6->comment); + } + +/* + * Do we have format with 4 character Maidenhead locator? + */ + else if (get_maidenhead (pm4->mhead4) == 4) { + + strncpy (g_maidenhead, pm4->mhead4, 4); + g_maidenhead[4] = '\0'; + + g_symbol_table = pm4->sym_table_id; + g_symbol_code = pm4->symbol_code; + + if (g_symbol_table != '/' && g_symbol_table != '\\' + && ! isupper(g_symbol_table) && ! isdigit(g_symbol_table)) + { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", g_symbol_table); + g_symbol_table = '/'; + } + + if (pm4->space != ' ' && pm4->space != '\0') { + text_color_set(DW_COLOR_ERROR); + dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm4->space); + } + + strcpy (g_comment, pm4->comment); + } + +/* + * Whole thing is status text. + */ + else { + strcpy (g_comment, ps->comment); + } + + +/* + * Last 3 characters can represent beam heading and ERP. + */ + + if (strlen(g_comment) >= 3) { + char *hp = g_comment + strlen(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: put result somewhere. + // could use g_directivity and need new variable for erp. + + *hp = '\0'; + } + } +} + + +/*------------------------------------------------------------------ + * + * Function: aprs_Telemetry + * + * Purpose: Decode "Telemetry" + * + * Inputs: info - Pointer to Information field. + * ilen - Information field length. + * + * Outputs: ??? + * + * Description: TBD. + * + * Examples from specification: + * + * + * TBD + * + *------------------------------------------------------------------*/ + +static void aprs_telemetry (char *info, int ilen) +{ + + strcpy (g_msg_type, "Telemetry"); + + /* It's pretty much human readable already. */ + /* Just copy the info field. */ + + strcpy (g_comment, info); + + +} /* 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 (char *info, int ilen) +{ + + strcpy (g_msg_type, "Raw Touch Tone Data"); + + /* Just copy the info field without the message type. */ + + if (*info == '{') + strcpy (g_comment, info+3); + else + strcpy (g_comment, info+1); + + +} /* 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 (char *info, int ilen) +{ + + strcpy (g_msg_type, "Morse Code Data"); + + /* Just copy the info field without the message type. */ + + if (*info == '{') + strcpy (g_comment, info+3); + else + strcpy (g_comment, info+1); + + +} /* 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: g_symbol_table, g_symbol_code. + * + * Description: Type identifier '_' is a weather report without a position. + * + *------------------------------------------------------------------*/ + + + +static void aprs_positionless_weather_report (unsigned char *info, int ilen) +{ + + struct aprs_positionless_weather_s { + char dti; /* _ */ + char time_stamp[8]; /* MDHM format */ + char comment[99]; + } *p; + + + strcpy (g_msg_type, "Positionless Weather Report"); + + time_t ts = 0; + + + p = (struct aprs_positionless_weather_s *)info; + + // not yet implemented for 8 character format // ts = get_timestamp (p->time_stamp); + + weather_data (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: g_course - Wind info for compressed location. + * g_speed + * + * Outputs: g_comment + * + * 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 g_course and g_speed. + * 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]; + 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); + } + } + + strncpy (stemp, (*wpp)+1, dlen); + stemp[dlen] = '\0'; + *val = atof(stemp); + + //dw_printf("debug: getwdata returning %f\n", *val); + + *wpp += 1 + dlen; + return (1); +} + +static void weather_data (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. + g_course = n; + } + if (sscanf (wp+4, "%3d", &n)) + { + g_speed = KNOTS_TO_MPH(n); /* yes, in knots */ + } + wp += 7; + } + else if ( g_speed == G_UNKNOWN) { + + if ( ! getwdata (&wp, 'c', 3, &g_course)) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Didn't find wind direction in form c999.\n"); + } + if ( ! getwdata (&wp, 's', 3, &g_speed)) { /* MPH here */ + 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 (g_speed != G_UNKNOWN) { + char ctemp[30]; + + sprintf (g_comment, "wind %.1f mph", g_speed); + if (g_course != G_UNKNOWN) { + sprintf (ctemp, ", direction %.0f", g_course); + strcat (g_comment, ctemp); + } + } + + /* We don't want this to show up on the location line. */ + g_speed = G_UNKNOWN; + 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[30]; + sprintf (ctemp, ", gust %.0f", fval); + strcat (g_comment, ctemp); + } + } + else { + 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[30]; + sprintf (ctemp, ", temperature %.0f", fval); + strcat (g_comment, ctemp); + } + } + else { + 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[30]; + sprintf (ctemp, ", rain %.2f in last hour", fval / 100.); + strcat (g_comment, ctemp); + } + } + else if (getwdata (&wp, 'p', 3, &fval)) { + + /* p = rainfall, 1/100 inch, last 24 hours */ + + if (fval != G_UNKNOWN) { + char ctemp[30]; + sprintf (ctemp, ", rain %.2f in last 24 hours", fval / 100.); + strcat (g_comment, ctemp); + } + } + else if (getwdata (&wp, 'P', 3, &fval)) { + + /* P = rainfall, 1/100 inch, since midnight */ + + if (fval != G_UNKNOWN) { + char ctemp[30]; + sprintf (ctemp, ", rain %.2f since midnight", fval / 100.); + strcat (g_comment, ctemp); + } + } + else if (getwdata (&wp, 'h', 2, &fval)) { + + /* h = humidity %, 00 means 100% */ + + if (fval != G_UNKNOWN) { + char ctemp[30]; + if (fval == 0) fval = 100; + sprintf (ctemp, ", humidity %.0f", fval); + strcat (g_comment, ctemp); + } + } + 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[30]; + fval = MBAR_TO_INHG(fval * 0.1); + sprintf (ctemp, ", barometer %.2f", fval); + strcat (g_comment, ctemp); + } + } + else if (getwdata (&wp, 'L', 3, &fval)) { + + /* L = Luminosity, watts/ sq meter, 000-999 */ + + if (fval != G_UNKNOWN) { + char ctemp[30]; + sprintf (ctemp, ", %.0f watts/m^2", fval); + strcat (g_comment, ctemp); + } + } + else if (getwdata (&wp, 'l', 3, &fval)) { + + /* l = Luminosity, watts/ sq meter, 1000-1999 */ + + if (fval != G_UNKNOWN) { + char ctemp[30]; + sprintf (ctemp, ", %.0f watts/m^2", fval + 1000); + strcat (g_comment, ctemp); + } + } + 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[30]; + sprintf (ctemp, ", %.1f snow in 24 hours", fval); + strcat (g_comment, ctemp); + } + } + else if (getwdata (&wp, 's', 3, &fval)) { + + /* # = Raw rain counter */ + + if (fval != G_UNKNOWN) { + char ctemp[30]; + sprintf (ctemp, ", raw rain counter %.f", fval); + strcat (g_comment, ctemp); + } + } + 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[30]; + sprintf (ctemp, ", nuclear Radiation %.f", fval); + strcat (g_comment, ctemp); + } + } + +// 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} + */ + + strcat (g_comment, ", \""); + strcat (g_comment, wp); +/* + * Drop any CR / LF character at the end. + */ + n = strlen(g_comment); + if (n >= 1 && g_comment[n-1] == '\n') { + g_comment[n-1] = '\0'; + } + + n = strlen(g_comment); + if (n >= 1 && g_comment[n-1] == '\r') { + g_comment[n-1] = '\0'; + } + + strcat (g_comment, "\""); + + return; +} + + +/*------------------------------------------------------------------ + * + * Function: aprs_ultimeter + * + * Purpose: Decode Peet Brothers ULTIMETER Weather Station Info. + * + * Inputs: info - Pointer to Information field. + * ilen - Information field length. + * + * Outputs: g_comment + * + * 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 (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; + + strcpy (g_msg_type, "Ultimeter"); + + 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 = KM_TO_MILES(h_windpeak * 0.1); + wdir = (h_wdir & 0xff) * 360. / 256.; + otemp = h_otemp * 0.1; + baro = MBAR_TO_INHG(h_baro * 0.1); + ohumid = h_ohumid * 0.1; + + sprintf (g_comment, "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 = KM_TO_MILES(h_windpeak * 0.1); + wdir = (h_wdir & 0xff) * 360. / 256.; + otemp = h_otemp * 0.1; + + sprintf (g_comment, "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: g_comment + * + * Description: + * + *------------------------------------------------------------------*/ + +static void third_party_header (char *info, int ilen) +{ + int n; + + strcpy (g_msg_type, "Third Party Header"); + + /* 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: g_lat + * g_lon + * g_symbol_table + * 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 (position_t *ppos) +{ + + g_lat = get_latitude_8 (ppos->lat); + g_lon = get_longitude_9 (ppos->lon); + + g_symbol_table = ppos->sym_table_id; + 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: g_lat + * g_lon + * g_symbol_table + * g_symbol_code + * + * One of the following: + * g_course & g_speeed + * g_altitude + * 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 (compressed_position_t *pcpos) +{ + if (pcpos->y[0] >= '!' && pcpos->y[0] <= '{' && + pcpos->y[1] >= '!' && pcpos->y[1] <= '{' && + pcpos->y[2] >= '!' && pcpos->y[2] <= '{' && + pcpos->y[3] >= '!' && pcpos->y[3] <= '{' ) + { + 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 + { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in compressed latitude. Must be in range of '!' to '{'.\n"); + g_lat = G_UNKNOWN; + } + + if (pcpos->x[0] >= '!' && pcpos->x[0] <= '{' && + pcpos->x[1] >= '!' && pcpos->x[1] <= '{' && + pcpos->x[2] >= '!' && pcpos->x[2] <= '{' && + pcpos->x[3] >= '!' && pcpos->x[3] <= '{' ) + { + 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 + { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in compressed longitude. Must be in range of '!' to '{'.\n"); + 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. */ + 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. */ + g_symbol_table = pcpos->sym_table_id - 'a' + '0'; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid symbol table id for compressed position.\n"); + g_symbol_table = '/'; + } + + g_symbol_code = pcpos->symbol_code; + + if (pcpos->c == ' ') { + ; /* ignore other two bytes */ + } + else if (((pcpos->t - 33) & 0x18) == 0x10) { + g_altitude = pow(1.002, (pcpos->c - 33) * 91 + pcpos->s - 33); + } + else if (pcpos->c == '{') + { + 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. */ + g_course = (pcpos->c - 33) * 4; + g_speed = 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) +{ + 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 { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in latitude. Expected 0-9 for tens of degrees.\n"); + return (G_UNKNOWN); + } + + if (isdigit(plat->deg[1])) + result += ((plat->deg[1]) - '0') * 1; + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in latitude. Expected 0-9 for degrees.\n"); + 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 { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in latitude. Expected 0-5 for tens of minutes.\n"); + return (G_UNKNOWN); + } + + if (isdigit(plat->minn[1])) + result += ((plat->minn[1]) - '0') * (1. / 60.); + else if (plat->minn[1] == ' ') + ; + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in latitude. Expected 0-9 for minutes.\n"); + return (G_UNKNOWN); + } + + if (plat->dot != '.') { + 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 { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in latitude. Expected 0-9 for tenths of minutes.\n"); + return (G_UNKNOWN); + } + + if (isdigit(plat->hmin[1])) + result += ((plat->hmin[1]) - '0') * (0.01 / 60.); + else if (plat->hmin[1] == ' ') + ; + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in latitude. Expected 0-9 for hundredths of minutes.\n"); + 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') { + 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') { + 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 { + 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) +{ + 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 { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in longitude. Expected 0 or 1 for hundreds of degrees.\n"); + return (G_UNKNOWN); + } + + if (isdigit(plon->deg[1])) + result += ((plon->deg[1]) - '0') * 10; + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in longitude. Expected 0-9 for tens of degrees.\n"); + return (G_UNKNOWN); + } + + if (isdigit(plon->deg[2])) + result += ((plon->deg[2]) - '0') * 1; + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in longitude. Expected 0-9 for degrees.\n"); + 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 { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in longitude. Expected 0-5 for tens of minutes.\n"); + return (G_UNKNOWN); + } + + if (isdigit(plon->minn[1])) + result += ((plon->minn[1]) - '0') * (1. / 60.); + else if (plon->minn[1] == ' ') + ; + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in longitude. Expected 0-9 for minutes.\n"); + return (G_UNKNOWN); + } + + if (plon->dot != '.') { + 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 { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in longitude. Expected 0-9 for tenths of minutes.\n"); + return (G_UNKNOWN); + } + + if (isdigit(plon->hmin[1])) + result += ((plon->hmin[1]) - '0') * (0.01 / 60.); + else if (plon->hmin[1] == ' ') + ; + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character in longitude. Expected 0-9 for hundredths of minutes.\n"); + 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') { + 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') { + 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 { + 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 (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 (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])) { + 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])) { + 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: get_latitude_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. + * Could we use special "NaN" code? + * + *------------------------------------------------------------------*/ + + +static double get_latitude_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. + + 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: get_longitude_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. + * Could we use special "NaN" code? + * + *------------------------------------------------------------------*/ + + +static double get_longitude_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: 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: + * + * g_course + * g_speed + * g_power + * g_height + * g_gain + * g_directivity + * g_range + * + * Anything left over will be put in + * + * g_comment + * + * Description: + * + * + * + *------------------------------------------------------------------*/ + +const char *dir[9] = { "omni", "NE", "E", "SE", "S", "SW", "W", "NW", "N" }; + +static int data_extension_comment (char *pdext) +{ + int n; + + if (strlen(pdext) < 7) { + strcpy (g_comment, pdext); + return 0; + } + +/* Tyy/Cxx - Area object descriptor. */ + + if (pdext[0] == 'T' && + pdext[3] == '/' && + pdext[4] == 'C') + { + /* not decoded at this time */ + process_comment (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)) + { + g_course = n; + } + if (sscanf (pdext+4, "%3d", &n)) + { + g_speed = KNOTS_TO_MPH(n); + } + + /* Bearing and Number/Range/Quality? */ + + if (pdext[7] == '/' && pdext[11] == '/') + { + process_comment (pdext + 7 + 8, -1); + } + else { + process_comment (pdext+7, -1); + } + return 1; + } + +/* check for Station power, height, gain. */ + + if (strncmp(pdext, "PHG", 3) == 0) + { + g_power = (pdext[3] - '0') * (pdext[3] - '0'); + g_height = (1 << (pdext[4] - '0')) * 10; + g_gain = pdext[5] - '0'; + if (pdext[6] >= '0' && pdext[6] <= '8') { + strcpy (g_directivity, dir[pdext[6]-'0']); + } + + process_comment (pdext+7, -1); + return 1; + } + +/* check for precalculated radio range. */ + + if (strncmp(pdext, "RNG", 3) == 0) + { + if (sscanf (pdext+3, "%4d", &n)) + { + g_range = n; + } + process_comment (pdext+7, -1); + return 1; + } + +/* DF signal strength, */ + + if (strncmp(pdext, "DFS", 3) == 0) + { + //g_strength = pdext[3] - '0'; + g_height = (1 << (pdext[4] - '0')) * 10; + g_gain = pdext[5] - '0'; + if (pdext[6] >= '0' && pdext[6] <= '8') { + strcpy (g_directivity, dir[pdext[6]-'0']); + } + + process_comment (pdext+7, -1); + return 1; + } + + process_comment (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: 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/share/direwolf directory. + * + *------------------------------------------------------------------*/ + +#define MAX_TOCALLS 150 + +static struct tocalls_s { + unsigned char len; + char prefix[7]; + char *description; +} tocalls[MAX_TOCALLS]; + +static int num_tocalls = 0; + +static int tocall_cmp (const struct tocalls_s *x, const struct tocalls_s *y) +{ + if (x->len != y->len) return (y->len - x->len); + return (strcmp(x->prefix, y->prefix)); +} + +static void decode_tocall (char *dest) +{ + FILE *fp; + int n; + static int first_time = 1; + char stuff[100]; + char *p; + char *r; + + //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) { + + fp = fopen("tocalls.txt", "r"); +#ifndef __WIN32__ + if (fp == NULL) { + fp = fopen("/usr/share/direwolf/tocalls.txt", "r"); + } +#endif + 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'; + } + + // 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 '%s' -> '%s'\n", 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 '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); + + num_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 __WIN32__ + 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 { + 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=0)?1:(-1)) + +static void process_comment (char *pstart, int clen) +{ + static int first_time = 1; + static regex_t freq_re; /* These must be static! */ + static regex_t dao_re; /* These must be static! */ + static regex_t alt_re; /* These must be static! */ + int e; + char emsg[100]; +#define MAXMATCH 1 + regmatch_t match[MAXMATCH]; + char temp[256]; + +/* + * No sense in recompiling the patterns and freeing every time. + */ + if (first_time) + { +/* + * Present, frequency must be at the at the beginning. + * Others can be anywhere in the comment. + */ + /* incomplete */ + 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); + if (e) { + regerror (e, &freq_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][!-} ][!-} ])!", 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); + } + + first_time = 0; + } + + if (clen >= 0) { + assert (clen < sizeof(g_comment)); + memcpy (g_comment, pstart, (size_t)clen); + g_comment[clen] = '\0'; + } + else { + strcpy (g_comment, pstart); + } + //dw_printf("\nInitial comment='%s'\n", g_comment); + + +/* + * Frequency. + * Just pull it out from comment. + * No futher interpretation at this time. + */ + + if (regexec (&freq_re, g_comment, MAXMATCH, match, 0) == 0) + { + + //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); + + strcpy (temp, g_comment + match[0].rm_eo); + + g_comment[match[0].rm_eo] = '\0'; + strcpy (g_freq, g_comment + match[0].rm_so); + + strcpy (g_comment + match[0].rm_so, temp); + } + +/* + * 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. + */ + + if (regexec (&dao_re, g_comment, MAXMATCH, match, 0) == 0) + { + + int d = g_comment[match[0].rm_so+1]; + int a = g_comment[match[0].rm_so+2]; + int o = g_comment[match[0].rm_so+3]; + + //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); + + 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)) { + g_lat += (a - '0') / 60000.0 * sign(g_lat); + } + if (isdigit(o)) { + g_lon += (o - '0') / 60000.0 * sign(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. + */ + if (a >= '!' && a <= '}') { + g_lat += (a - '!') * 1.1 / 600000.0 * sign(g_lat); + } + if (o >= '!' && o <= '}') { + g_lon += (o - '!') * 1.1 / 600000.0 * sign(g_lon); + } + } + + strcpy (temp, g_comment + match[0].rm_eo); + strcpy (g_comment + match[0].rm_so, temp); + } + +/* + * Altitude in feet. /A=123456 + */ + + if (regexec (&alt_re, g_comment, MAXMATCH, match, 0) == 0) + { + + //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); + + strcpy (temp, g_comment + match[0].rm_eo); + + g_comment[match[0].rm_eo] = '\0'; + g_altitude = atoi(g_comment + match[0].rm_so + 3); + + strcpy (g_comment + match[0].rm_so, temp); + } + + //dw_printf("Final comment='%s'\n", g_comment); + +} + +/* 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 + * + * + * Outputs: stdout + * + * Description: Compile like this to make a standalone test program. + * + * gcc -o decode_aprs -DTEST 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. + * + * + *------------------------------------------------------------------*/ + +#if TEST + + +int main (int argc, char *argv[]) +{ + char stuff[300]; + char *p; + packet_t pp; + +#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 + 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); + + pp = ax25_from_text(stuff, 1); + if (pp != NULL) + { + decode_aprs (pp); + ax25_delete (pp); + } + else + { + text_color_set(DW_COLOR_ERROR); + dw_printf("\n%s\n", "ERROR - Could not parse input!\n"); + } + } + } + return (0); +} + +#endif /* TEST */ + +/* end decode_aprs.c */ diff --git a/decode_aprs.h b/decode_aprs.h new file mode 100644 index 0000000..66fbc31 --- /dev/null +++ b/decode_aprs.h @@ -0,0 +1,5 @@ + +/* decode_aprs.h */ + +extern void decode_aprs (packet_t pp); + diff --git a/dedupe.c b/dedupe.c new file mode 100644 index 0000000..d7c69d7 --- /dev/null +++ b/dedupe.c @@ -0,0 +1,243 @@ +// +// 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 +#include +#include +#include +#include + + +#include "ax25_pad.h" +#include "dedupe.h" +#include "fcs_calc.h" +#include "textcolor.h" + + +/*------------------------------------------------------------------------------ + * + * 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; + } +} + + +/*------------------------------------------------------------------------------ + * + * 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 */ diff --git a/dedupe.h b/dedupe.h new file mode 100644 index 0000000..9c0613c --- /dev/null +++ b/dedupe.h @@ -0,0 +1,10 @@ + + +void dedupe_init (int ttl); + +void dedupe_remember (packet_t pp, int chan); + +int dedupe_check (packet_t pp, int chan); + + +/* end dedupe.h */ diff --git a/demod.c b/demod.c new file mode 100644 index 0000000..abcb6f8 --- /dev/null +++ b/demod.c @@ -0,0 +1,570 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011,2012,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 . +// + + +// #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.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 +#include +#include +#include +#include +#include +#include +#include + +#include "direwolf.h" +#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" + + + +// Properties of the radio channels. + +static struct audio_s modem; + +// Current state of all the decoders. + +static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS]; + + +#define UPSAMPLE 2 + +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 modem_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. */ + int subchan; /* for each modem for channel. */ + char profile; + //float fc; + + struct demodulator_state_s *D; + + +/* + * Save parameters for later use. + */ + memcpy (&modem, pa, sizeof(modem)); + + for (chan = 0; chan < modem.num_channels; chan++) { + + assert (chan >= 0 && chan < MAX_CHANS); + + switch (modem.modem_type[chan]) { + + case AFSK: +/* + * Pick a good default demodulator if none specified. + */ + if (strlen(modem.profiles[chan]) == 0) { + + if (modem.baud[chan] < 600) { + + /* This has been optimized for 300 baud. */ + + strcpy (modem.profiles[chan], "D"); + if (modem.samples_per_sec > 40000) { + modem.decimate[chan] = 3; + } + } + else { +#if __arm__ + /* We probably don't have a lot of CPU power available. */ + + if (modem.baud[chan] == FFF_BAUD && + modem.mark_freq[chan] == FFF_MARK_FREQ && + modem.space_freq[chan] == FFF_SPACE_FREQ && + modem.samples_per_sec == FFF_SAMPLES_PER_SEC) { + + modem.profiles[chan][0] = FFF_PROFILE; + modem.profiles[chan][1] = '\0'; + } + else { + strcpy (modem.profiles[chan], "A"); + } +#else + strcpy (modem.profiles[chan], "C"); +#endif + } + } + + if (modem.decimate[chan] == 0) modem.decimate[chan] = 1; + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Channel %d: %d baud, AFSK %d & %d Hz, %s, %d sample rate", + chan, modem.baud[chan], + modem.mark_freq[chan], modem.space_freq[chan], + modem.profiles[chan], + modem.samples_per_sec); + if (modem.decimate[chan] != 1) + dw_printf (" / %d", modem.decimate[chan]); + dw_printf (".\n"); + + if (strlen(modem.profiles[chan]) > 1) { + +/* + * Multiple profiles, usually for 1200 baud. + */ + assert (modem.num_subchan[chan] == strlen(modem.profiles[chan])); + + for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { + + int mark, space; + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + D = &demodulator_state[chan][subchan]; + + profile = modem.profiles[chan][subchan]; + mark = modem.mark_freq[chan]; + space = modem.space_freq[chan]; + + if (modem.num_subchan[chan] != 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %d.%d: %c %d & %d\n", chan, subchan, profile, mark, space); + } + + demod_afsk_init (modem.samples_per_sec / modem.decimate[chan], modem.baud[chan], + mark, space, + profile, + D); + } + } + else { +/* + * Possibly multiple frequency pairs. + */ + + assert (modem.num_freq[chan] == modem.num_subchan[chan]); + assert (strlen(modem.profiles[chan]) == 1); + + for (subchan = 0; subchan < modem.num_freq[chan]; subchan++) { + + int mark, space, k; + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + D = &demodulator_state[chan][subchan]; + + profile = modem.profiles[chan][0]; + + k = subchan * modem.offset[chan] - ((modem.num_subchan[chan] - 1) * modem.offset[chan]) / 2; + mark = modem.mark_freq[chan] + k; + space = modem.space_freq[chan] + k; + + if (modem.num_subchan[chan] != 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf (" %d.%d: %c %d & %d\n", chan, subchan, profile, mark, space); + } + + demod_afsk_init (modem.samples_per_sec / modem.decimate[chan], modem.baud[chan], + mark, space, + profile, + D); + + } /* for subchan */ + } + break; + + default: + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Channel %d: %d baud, %d sample rate x %d.\n", + chan, modem.baud[chan], + modem.samples_per_sec, UPSAMPLE); + + subchan = 0; + D = &demodulator_state[chan][subchan]; + + demod_9600_init (UPSAMPLE * modem.samples_per_sec, modem.baud[chan], D); + + break; + + } /* switch on modulation type. */ + + } /* for chan ... */ + + + + for (chan=0; chan= 0 && subchan < MAX_SUBCHANS); + + sample_sum[chan][subchan] = 0; + sample_count[chan][subchan] = subchan; /* stagger */ + + D = &demodulator_state[chan][subchan]; + +/* For collecting input signal level. */ + + D->lev_period = modem.samples_per_sec * 0.100; // Samples in 0.100 seconds. + + } + } + + return (0); + +} /* end demod_init */ + + + +/*------------------------------------------------------------------ + * + * Name: demod_get_sample + * + * Purpose: Get one audio sample fromt the sound input source. + * + * Returns: -32768 .. 32767 for a valid audio sample. + * 256*256 for end of file or other error. + * + * Global In: modem.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 (void) +{ + int x1, x2; + signed short sam; /* short to force sign extention. */ + + + assert (modem.bits_per_sample == 8 || modem.bits_per_sample == 16); + + + if (modem.bits_per_sample == 8) { + + x1 = audio_get(); + 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(); /* lower byte first */ + if (x1 < 0) return(FSK_READ_ERR); + + x2 = audio_get(); + 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, abs_fsam; + int k; + + +#if DEBUG4 + static FILE *demod_log_fp = NULL; + static int seq = 0; /* for log file name */ +#endif + + int j; + int demod_data; + struct demodulator_state_s *D; + + assert (chan >= 0 && chan < MAX_CHANS); + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + + D = &demodulator_state[chan][subchan]; + + +#if 1 /* TODO: common level detection. */ + + /* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */ + + fsam = sam / 16384.0; + +/* + * Accumulate measure of the input signal level. + */ + abs_fsam = fsam >= 0 ? fsam : -fsam; + + if (abs_fsam > D->lev_peak_acc) { + D->lev_peak_acc = abs_fsam; + } + D->lev_sum_acc += abs_fsam; + + D->lev_count++; + if (D->lev_count >= D->lev_period) { + D->lev_prev_peak = D->lev_last_peak; + D->lev_last_peak = D->lev_peak_acc; + D->lev_peak_acc = 0; + + D->lev_prev_ave = D->lev_last_ave; + D->lev_last_ave = D->lev_sum_acc / D->lev_count; + D->lev_sum_acc = 0; + + D->lev_count = 0; + } + +#endif + +/* + * Select decoder based on modulation type. + */ + + switch (modem.modem_type[chan]) { + + case AFSK: + + if (modem.decimate[chan] > 1) { + + sample_sum[chan][subchan] += sam; + sample_count[chan][subchan]++; + if (sample_count[chan][subchan] >= modem.decimate[chan]) { + demod_afsk_process_sample (chan, subchan, sample_sum[chan][subchan] / modem.decimate[chan], D); + sample_sum[chan][subchan] = 0; + sample_count[chan][subchan] = 0; + } + } + else { + demod_afsk_process_sample (chan, subchan, sam, D); + } + break; + + default: + +#define ZEROSTUFF 1 + + +#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); + + D = &demodulator_state[chan][subchan]; + + dw_printf ("%d\n", (int)((D->lev_last_peak + D->lev_prev_peak)*50)); + + + + //dw_printf ("Peak= %.2f, %.2f Ave= %.2f, %.2f AGC M= %.2f / %.2f S= %.2f / %.2f\n", + // D->lev_last_peak, D->lev_prev_peak, D->lev_last_ave, D->lev_prev_ave, + // D->m_peak, D->m_valley, D->s_peak, D->s_valley); + +} +#endif + +/* Resulting scale is 0 to almost 100. */ +/* Cranking up the input level produces no more than 97 or 98. */ +/* We currently produce a message when this goes over 90. */ + +int demod_get_audio_level (int chan, int subchan) +{ + struct demodulator_state_s *D; + + + assert (chan >= 0 && chan < MAX_CHANS); + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + + D = &demodulator_state[chan][subchan]; + + return ( (int) ((D->lev_last_peak + D->lev_prev_peak) * 50 ) ); +} + + +/* end demod.c */ diff --git a/demod.h b/demod.h new file mode 100644 index 0000000..d47fced --- /dev/null +++ b/demod.h @@ -0,0 +1,16 @@ + + +/* demod.h */ + +#include "audio.h" /* for struct audio_s */ + + +int demod_init (struct audio_s *pa); + +int demod_get_sample (void); + +void demod_process_sample (int chan, int subchan, int sam); + +void demod_print_agc (int chan, int subchan); + +int demod_get_audio_level (int chan, int subchan); \ No newline at end of file diff --git a/demod_9600.c b/demod_9600.c new file mode 100644 index 0000000..eef4e15 --- /dev/null +++ b/demod_9600.c @@ -0,0 +1,463 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011,2012,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 . +// + + +// #define DEBUG5 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 +#include +#include +#include +#include +#include +#include +#include + +#include "direwolf.h" +#include "tune.h" +#include "fsk_demod_state.h" +#include "hdlc_rec.h" +#include "demod_9600.h" +#include "textcolor.h" +#include "dsp.h" + + +/* Add sample to buffer and shift the rest down. */ + +__attribute__((hot)) +static inline void push_sample (float val, float *buff, int size) +{ + int j; + + // TODO: memmove any faster? + for (j = size - 1; j >= 1; j--) { + buff[j] = buff[j-1]; + } + buff[0] = val; +} + + +/* FIR filter kernel. */ + +__attribute__((hot)) +static inline float convolve (const float *data, const float *filter, int filter_size) +{ + float sum = 0; + int j; + + for (j=0; j= *ppeak) { + *ppeak = in * fast_attack + *ppeak * (1. - fast_attack); + } + else { + *ppeak = in * slow_decay + *ppeak * (1. - slow_decay); + } + + if (in <= *pvalley) { + *pvalley = in * fast_attack + *pvalley * (1. - fast_attack); + } + else { + *pvalley = in * slow_decay + *pvalley * (1. - slow_decay); + } + + if (*ppeak > *pvalley) { + return ((in - 0.5 * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); + } + return (0.0); +} + + +/*------------------------------------------------------------------ + * + * Name: demod_9600_init + * + * Purpose: Initialize the 9600 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; + + memset (D, 0, sizeof(struct demodulator_state_s)); + + //dw_printf ("demod_9600_init(rate=%d, baud=%d, D ptr)\n", samples_per_sec, baud); + + D->pll_step_per_sample = + (int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec); + + D->filter_len_bits = 72 * 9600.0 / (44100.0 * 2.0); + D->lp_filter_size = (int) (( D->filter_len_bits * (float)samples_per_sec / baud) + 0.5); +#if TUNE_LP_FILTER_SIZE + D->lp_filter_size = TUNE_LP_FILTER_SIZE; +#endif + + D->lpf_baud = 0.59; +#ifdef TUNE_LPF_BAUD + D->lpf_baud = TUNE_LPF_BAUD; +#endif + + D->agc_fast_attack = 0.080; +#ifdef TUNE_AGC_FAST + D->agc_fast_attack = TUNE_AGC_FAST; +#endif + + D->agc_slow_decay = 0.00012; +#ifdef TUNE_AGC_SLOW + D->agc_slow_decay = TUNE_AGC_SLOW; +#endif + + D->pll_locked_inertia = 0.88; + D->pll_searching_inertia = 0.67; + +#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING) + D->pll_locked_inertia = TUNE_PLL_LOCKED; + 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, BP_WINDOW_HAMMING); + +} /* 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 + * + * + * TODO: This works in a simulated environment but it has not yet + * been successfully tested for interoperability with + * other systems over the air. + * That's why it is not mentioned in documentation. + * + * The signal from the radio speaker does NOT have + * enough bandwidth and the waveform is hopelessly distorted. + * It will be necessary to obtain a signal right after + * the discriminator of the receiver. + * It will probably also be necessary to tap directly into + * the modulator, bypassing the microphone amplifier. + * + *--------------------------------------------------------------------*/ + + +__attribute__((hot)) +void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D) +{ + + float fsam; + float abs_fsam; + float amp; + float demod_out; + +#if DEBUG5 + static FILE *demod_log_fp = NULL; + static int seq = 0; /* for log file name */ +#endif + + int j; + int subchan = 0; + int demod_data; /* Still scrambled. */ + static int descram; /* Data bit de-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, range -1.0 to +1.0. */ + + fsam = sam / 32768.0; + + 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); + +/* + * 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 = 2.0 * agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley)); + +//dw_printf ("peak=%.2f valley=%.2f amp=%.2f norm=%.2f\n", D->m_peak, D->m_valley, amp, norm); + + /* Throw in a little Hysteresis??? */ + /* (Not to be confused with Hysteria.) */ + + if (demod_out > 0.01) { + demod_data = 1; + } + else if (demod_out < -0.01) { + demod_data = 0; + } + else { + demod_data = D->prev_demod_data; + } + + +/* + * Next, 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. + * + * This was optimized for 1200 baud AFSK. There might be some opportunity + * for improvement here. + */ + D->prev_d_c_pll = D->data_clock_pll; + D->data_clock_pll += D->pll_step_per_sample; + + if (D->data_clock_pll < 0 && D->prev_d_c_pll > 0) { + + /* Overflow. */ + +/* + * At this point, we need to descramble the data as + * in hardware based designs by G3RUH and K9NG. + * + * http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif + */ + + //assert (modem.modem_type[chan] == SCRAMBLE); + + //if (modem.modem_type[chan] == SCRAMBLE) { + + +// TODO: This needs to be rearranged to allow attempted "fixing" +// of corrupted bits later. We need to store the original +// received bits and do the descrambling after attempted +// repairs. However, we also need to descramble now to +// detect the flag sequences. + + + descram = descramble (demod_data, &(D->lfsr)); +#if SLICENDICE + // TODO: Needs more thought. + // Does it even make sense to remember demod_out in this case? + // We would need to do the re-thresholding before descrambling. + //hdlc_rec_bit_sam (chan, subchan, descram, descram ? 1.0 : -1.0); +#else + +// TODO: raw received bit and true later. + + hdlc_rec_bit (chan, subchan, descram, 0, D->lfsr); + +#endif + + //D->prev_descram = descram; + //} + //else { + /* Baseband signal for completeness - not in common use. */ +#if SLICENDICE + //hdlc_rec_bit_sam (chan, subchan, demod_data, demod_data ? 1.0 : -1.0); +#else + //hdlc_rec_bit (chan, subchan, demod_data); +#endif + //} + } + + if (demod_data != D->prev_demod_data) { + + // Note: Test for this demodulator, not overall for channel. + + if (hdlc_rec_data_detect_1 (chan, subchan)) { + D->data_clock_pll = (int)(D->data_clock_pll * D->pll_locked_inertia); + } + else { + D->data_clock_pll = (int)(D->data_clock_pll * D->pll_searching_inertia); + } + } + + +#if DEBUG5 + + //if (chan == 0) { + if (hdlc_rec_data_detect_1 (chan,subchan)) { + + char fname[30]; + + + if (demod_log_fp == NULL) { + seq++; + sprintf (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.5 * fsam + 3.5, + 0.5 * D->m_peak + 3.5, + 0.5 * D->m_valley + 3.5, + 0.5 * 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->prev_demod_data = demod_data; + +} /* end demod_9600_process_sample */ + + + +/* end demod_9600.c */ diff --git a/demod_9600.h b/demod_9600.h new file mode 100644 index 0000000..39bbcb8 --- /dev/null +++ b/demod_9600.h @@ -0,0 +1,21 @@ + + +/* demod_9600.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); +} diff --git a/demod_afsk.c b/demod_afsk.c new file mode 100644 index 0000000..091f823 --- /dev/null +++ b/demod_afsk.c @@ -0,0 +1,977 @@ +// +// 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 . +// + + +// #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 +#include +#include +#include +#include +#include +#include +#include + +#include "direwolf.h" +#include "audio.h" +//#include "fsk_demod.h" +//#include "gen_tone.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)) + + + + +/* Quick approximation to sqrt(x*x+y*y) */ +/* No benefit for regular PC. */ +/* Should help with microcomputer platform. */ + + +__attribute__((hot)) +static inline float z (float x, float y) +{ + x = fabsf(x); + y = fabsf(y); + + if (x > y) { + return (x * .941246 + y * .41); + } + else { + return (y * .941246 + x * .41); + } +} + +/* Add sample to buffer and shift the rest down. */ + +__attribute__((hot)) +static inline void push_sample (float val, float *buff, int size) +{ + int j; + + // TODO: memmove any faster? + for (j = size - 1; j >= 1; j--) { + buff[j] = buff[j-1]; + } + buff[0] = val; +} + + +/* FIR filter kernel. */ + +__attribute__((hot)) +static inline float convolve (const float *data, const float *filter, int filter_size) +{ + float sum = 0; + int j; + + for (j=0; j= *ppeak) { + *ppeak = in * fast_attack + *ppeak * (1. - fast_attack); + } + else { + *ppeak = in * slow_decay + *ppeak * (1. - slow_decay); + } + + if (in <= *pvalley) { + *pvalley = in * fast_attack + *pvalley * (1. - fast_attack); + } + else { + *pvalley = in * slow_decay + *pvalley * (1. - slow_decay); + } + + if (*ppeak > *pvalley) { + return ((in - 0.5 * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); + } + return (0.0); +} + + + +/*------------------------------------------------------------------ + * + * 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)); + +#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 (toupper(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'; + } + } + + if (profile == 'a' || profile == 'A' || profile == 'f' || profile == '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->filter_len_bits = 1.415; /* 52 @ 44100, 1200 */ + D->bp_window = BP_WINDOW_TRUNCATED; + D->lpf_use_fir = 0; + D->lpf_iir = 0.195; + D->lpf_baud = 0; + 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; + } + else if (profile == 'b' || profile == 'B') { + + /* Original bandpass. Use FIR lowpass instead. */ + + D->filter_len_bits = 1.415; /* 52 @ 44100, 1200 */ + D->bp_window = BP_WINDOW_TRUNCATED; + D->lpf_use_fir = 1; + D->lpf_iir = 0; + D->lpf_baud = 1.09; + 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; + } + else if (profile == 'c' || profile == 'C') { + + /* Cosine window, 76 taps for bandpass, FIR lowpass. */ + + D->filter_len_bits = 2.068; /* 76 @ 44100, 1200 */ + D->bp_window = BP_WINDOW_COSINE; + D->lpf_use_fir = 1; + D->lpf_iir = 0; + D->lpf_baud = 1.09; + 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; + } + else if (profile == 'd' || profile == 'D') { + + /* Prefilter, Cosine window, FIR lowpass. Tweeked for 300 baud. */ + + D->use_prefilter = 1; /* first, a bandpass filter. */ + D->prefilter_baud = 0.87; /* Cosine window. */ + D->filter_len_bits = 1.857; /* 91 @ 44100/3, 300 */ + D->bp_window = BP_WINDOW_COSINE; + D->lpf_use_fir = 1; + D->lpf_iir = 0; + D->lpf_baud = 1.10; + 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; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid filter profile = %c\n", profile); + exit (1); + } + + +#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)); + + +/* + * My initial guess at length of filter was about one bit time. + * By trial and error, the optimal value was found to somewhat longer. + * This was optimized for 44,100 sample rate, 1200 baud, 1200/2200 Hz. + * More experimentation is needed for other situations. + */ + + D->ms_filter_size = (int) round( D->filter_len_bits * (float)samples_per_sec / (float)baud ); + +/* Experiment with other sizes. */ + +#if defined(TUNE_MS_FILTER_SIZE) + D->ms_filter_size = TUNE_MS_FILTER_SIZE; +#endif + D->lp_filter_size = D->ms_filter_size; + + assert (D->ms_filter_size >= 4); + + 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); + } + + +/* + * For narrow AFSK (e.g. 200 Hz shift), it might be beneficial to + * have a bandpass filter before the mark/space detector. + * For now, make it the same number of taps for simplicity. + */ + + 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->ms_filter_size, BP_WINDOW_HAMMING); + //gen_bandpass (f1, f2, D->pre_filter, D->ms_filter_size, BP_WINDOW_BLACKMAN); + gen_bandpass (f1, f2, D->pre_filter, D->ms_filter_size, BP_WINDOW_COSINE); + } + +/* + * 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 + + for (j=0; jms_filter_size; j++) { + float am; + float center; + float shape = 1; /* 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.5 * (D->ms_filter_size - 1); + am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2 * M_PI); + + shape = window (D->bp_window, D->ms_filter_size, j); + + D->m_sin_table[j] = sin(am) * shape; + D->m_cos_table[j] = cos(am) * shape; + +#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 + } + + +#if DEBUG1 + text_color_set(DW_COLOR_DEBUG); + + dw_printf ("Space\n"); + dw_printf (" j shape S sin S cos\n"); +#endif + for (j=0; jms_filter_size; j++) { + float as; + float center; + float shape = 1; + + center = 0.5 * (D->ms_filter_size - 1); + as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2 * M_PI); + + shape = window (D->bp_window, D->ms_filter_size, j); + + D->s_sin_table[j] = sin(as) * shape; + D->s_cos_table[j] = cos(as) * shape; + +#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 + } + + +/* Do we want to normalize for unity gain? */ + + +/* + * 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, BP_WINDOW_TRUNCATED); + } + +/* + * 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 + +} /* fsk_gen_filter */ + + +#if GEN_FFF + + + +// Properties of the radio channels. + +static struct audio_s modem; + + +// Filters will be stored here. + +static struct demodulator_state_s ds; + + +#define SPARSE 3 + + +static void emit_macro (char *name, int size, float *coeff) +{ + int i; + + dw_printf ("#define %s(x) \\\n", name); + + for (i=SPARSE/2; i= 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.0; + +/* + * Accumulate measure of the input signal level. + */ + abs_fsam = fsam >= 0 ? fsam : -fsam; + +// TODO: move to common code + + if (abs_fsam > D->lev_peak_acc) { + D->lev_peak_acc = abs_fsam; + } + D->lev_sum_acc += abs_fsam; + + D->lev_count++; + if (D->lev_count >= D->lev_period) { + D->lev_prev_peak = D->lev_last_peak; + D->lev_last_peak = D->lev_peak_acc; + D->lev_peak_acc = 0; + + D->lev_prev_ave = D->lev_last_ave; + D->lev_last_ave = D->lev_sum_acc / D->lev_count; + D->lev_sum_acc = 0; + + D->lev_count = 0; + } + +/* + * Optional bandpass filter before the mark/space discriminator. + */ + + if (D->use_prefilter) { + float cleaner; + + push_sample (fsam, D->raw_cb, D->ms_filter_size); + cleaner = convolve (D->raw_cb, D->pre_filter, D->ms_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. + */ + + + +// TODO: How do we test for profile F here? + + if (0) { + //if (toupper(modem.profiles[chan][subchan]) == 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.0 - D->lpf_iir) * D->m_amp_prev; + D->m_amp_prev = m_amp; + + s_amp = D->lpf_iir * s_amp + (1.0 - D->lpf_iir) * D->s_amp_prev; + D->s_amp_prev = s_amp; + } + +/* + * Which tone is stronger? + * + * 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 have different amounts of DC bias. + * + * Try to compensate for this by normalizing them separately with automatic gain + * control (AGC). 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. + */ + + /* Fast attack and slow decay. */ + /* Numbers were obtained by trial and error from actual */ + /* recorded less-than-optimal signals. */ + + /* See agc.c and 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)); + + /* 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->prev_demod_data; + } + + +/* + * Finally, 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. + * + * 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->prev_d_c_pll = D->data_clock_pll; + D->data_clock_pll += 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->data_clock_pll < 0 && D->prev_d_c_pll > 0) { + + /* Overflow. */ +#if SLICENDICE + hdlc_rec_bit_sam (chan, subchan, demod_data, demod_out); +#else + hdlc_rec_bit (chan, subchan, demod_data, 0, -1); +#endif + } + + if (demod_data != D->prev_demod_data) { + + // Note: Test for this demodulator, not overall for channel. + + if (hdlc_rec_data_detect_1 (chan, subchan)) { + D->data_clock_pll = (int)(D->data_clock_pll * D->pll_locked_inertia); + } + else { + D->data_clock_pll = (int)(D->data_clock_pll * D->pll_searching_inertia); + } + } + + +#if DEBUG4 + + if (chan == 0) { + if (hdlc_rec_data_detect_1 (chan, subchan)) { + char fname[30]; + + + if (demod_log_fp == NULL) { + seq++; + sprintf (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 + + +/* + * Remember demodulator output so we can compare next time. + */ + D->prev_demod_data = demod_data; + + +} /* end demod_afsk_process_sample */ + +#endif /* GEN_FFF */ + + +#if 0 + +/*------------------------------------------------------------------- + * + * Name: fsk_demod_print_agc + * + * Purpose: Print information about input signal amplitude. + * This will be useful for adjusting transmitter audio levels. + * We also want to avoid having an input level so high + * that the A/D converter "clips" the signal. + * + * + * Inputs: chan - Audio channel. 0 for left, 1 for right. + * + * Returns: None + * + * Descripion: Not sure what to use for final form. + * For now display the AGC peaks for both tones. + * This will be called at the end of a frame. + * + * Future: Come up with a sensible scale and add command line option. + * Probably makes more sense to return a single number + * and let the caller print it. + * Just an experiment for now. + * + *--------------------------------------------------------------------*/ + +#if 0 +void fsk_demod_print_agc (int chan, int subchan) +{ + + struct demodulator_state_s *D; + + + assert (chan >= 0 && chan < MAX_CHANS); + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + + D = &demodulator_state[chan][subchan]; + + dw_printf ("%d\n", (int)((D->lev_last_peak + D->lev_prev_peak)*50)); + + + + //dw_printf ("Peak= %.2f, %.2f Ave= %.2f, %.2f AGC M= %.2f / %.2f S= %.2f / %.2f\n", + // D->lev_last_peak, D->lev_prev_peak, D->lev_last_ave, D->lev_prev_ave, + // D->m_peak, D->m_valley, D->s_peak, D->s_valley); + +} +#endif + +/* Resulting scale is 0 to almost 100. */ +/* Cranking up the input level produces no more than 97 or 98. */ +/* We currently produce a message when this goes over 90. */ + +int fsk_demod_get_audio_level (int chan, int subchan) +{ + struct demodulator_state_s *D; + + + assert (chan >= 0 && chan < MAX_CHANS); + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + + D = &demodulator_state[chan][subchan]; + + return ( (int) ((D->lev_last_peak + D->lev_prev_peak) * 50 ) ); +} + + + + +#endif /* 0 */ + +/* end demod_afsk.c */ diff --git a/demod_afsk.h b/demod_afsk.h new file mode 100644 index 0000000..e44a44e --- /dev/null +++ b/demod_afsk.h @@ -0,0 +1,8 @@ + +/* 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); diff --git a/digipeater.c b/digipeater.c new file mode 100644 index 0000000..990b329 --- /dev/null +++ b/digipeater.c @@ -0,0 +1,772 @@ +// +// 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: digipeater.c + * + * Purpose: Act as an APRS digital repeater. + * + * + * 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 +#include +#include +#include +//#include /* for isdigit */ +#include "regex.h" +#include + +#include "direwolf.h" +#include "ax25_pad.h" +#include "digipeater.h" +#include "textcolor.h" +#include "dedupe.h" +#include "tq.h" + + +static packet_t digipeat_match (packet_t pp, char *mycall_rec, char *mycall_xmit, + regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt); + +/* + * Set by digipeater_init and used later. + */ + + +static struct digi_config_s my_config; + + +/*------------------------------------------------------------------------------ + * + * Name: digipeater_init + * + * Purpose: Initialize with stuff from configuration file. + * + * Input: p_digi_config - Address of structure with all the + * necessary configuration details. + * + * Outputs: Make local copy for later use. + * + * Description: Called once at application startup time. + * + *------------------------------------------------------------------------------*/ + +void digipeater_init (struct digi_config_s *p_digi_config) +{ + memcpy (&my_config, p_digi_config, sizeof(my_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; + packet_t result; + + + // dw_printf ("digipeater()\n"); + + assert (from_chan >= 0 && from_chan < my_config.num_chans); + + +/* + * First pass: Look at packets being digipeated to same channel. + * + * We want these to get out quickly. + */ + + for (to_chan=0; to_chan 0) { + ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]); + ax25_set_ssid(pp, AX25_DESTINATION, 0); + /* Continue with general case, below. */ + } + +/* + * 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. + * 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. + */ + + if (strcmp(repeater, mycall_rec) == 0) { + result = ax25_dup (pp); + /* If using multiple radio channels, they */ + /* could have different calls. */ + ax25_set_addr (result, r, mycall_xmit); + ax25_set_h (result, r); + return (result); + } + +/* + * Next try to avoid retransmitting redundant information. + * Duplicates are detected by comparing only: + * - source + * - destination + * - info part + * - but none of the digipeaters + * 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 cause confusion. */ + + text_color_set(DW_COLOR_INFO); + dw_printf ("Digipeater: Drop redundant packet.\n"); +//#endif + assert (result == NULL); + return NULL; + } + +/* + * For the alias pattern, we unconditionally digipeat it once. + * i.e. Just replace it with MYCALL don't even look at the ssid. + */ + err = regexec(alias,repeater,0,NULL,0); + if (err == 0) { + result = ax25_dup (pp); + 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) { + + result = ax25_dup (pp); + 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; + } + + 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) { + result = ax25_dup (pp); + ax25_set_addr (result, r, mycall_xmit); + ax25_set_h (result, r); + return (result); + } + + if (ssid >= 2 && ssid <= 7) { + result = ax25_dup (pp); + 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. + */ + assert (result == NULL); + return NULL; +} + + + +/*------------------------------------------------------------------------- + * + * Name: main + * + * Purpose: Standalone test case for this funtionality. + * + * Usage: make -f Makefile. dtest + * ./dtest + * + *------------------------------------------------------------------------*/ + +#if TEST + +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; + //int should_repeat; + char rec[256]; + char xmit[256]; + unsigned char *pinfo; + int info_len; + unsigned char frame[AX25_MAX_PACKET_LEN]; + int frame_len; + + 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); + strcat (rec, (char*)pinfo); + + if (strcmp(in, rec) != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Text/internal/text error %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); + + pp = ax25_from_frame (frame, frame_len, 50); + ax25_format_addrs (pp, rec); + info_len = ax25_get_info (pp, &pinfo); + strcat (rec, (char*)pinfo); + + if (strcmp(in, rec) != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("internal/frame/internal/text error %s -> %s\n", in, rec); + } + +/* + * On with the digipeater test. + */ + + text_color_set(DW_COLOR_REC); + dw_printf ("Rec\t%s\n", rec); + + result = digipeat_match (pp, mycall, mycall, &alias_re, &wide_re, 0, preempt); + + if (result != NULL) { + + dedupe_remember (result, 0); + ax25_format_addrs (result, xmit); + info_len = ax25_get_info (result, &pinfo); + strcat (xmit, (char*)pinfo); + ax25_delete (result); + } + else { + strcpy (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. + */ + test ( "W1ABC>TEST-3:", + "W1ABC>TEST,WB2OSZ-9*,WIDE3-2:"); + + 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. + */ + + 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", + ""); + +/* + * 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? + */ + + 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 TEST */ + +/* end digipeater.c */ diff --git a/digipeater.h b/digipeater.h new file mode 100644 index 0000000..cd5593e --- /dev/null +++ b/digipeater.h @@ -0,0 +1,62 @@ + + +#ifndef DIGIPEATER_H +#define DIGIPEATER_H 1 + +#include "regex.h" + +#include "direwolf.h" /* for MAX_CHANS */ +#include "ax25_pad.h" /* for packet_t */ + +/* + * 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 num_chans; + + char mycall[MAX_CHANS][AX25_MAX_ADDR_LEN]; /* Call associated */ + /* with each of the radio channels. */ + /* Could be the same or different. */ + + 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]; + +}; + +/* + * Call once at application start up time. + */ + +extern void digipeater_init (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); + +#endif + +/* end digipeater.h */ + diff --git a/direwolf.c b/direwolf.c new file mode 100644 index 0000000..42b074b --- /dev/null +++ b/direwolf.c @@ -0,0 +1,885 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011, 2012, 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 . +// + + +/*------------------------------------------------------------------ + * + * 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) + * + * + *---------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include + +#if __WIN32__ +#else +#include +#include +#include +#include +#include +#include +#include +#include +#endif + + +#define DIREWOLF_C 1 + +#include "direwolf.h" +#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 "decode_aprs.h" +#include "textcolor.h" +#include "server.h" +#include "kiss.h" +#include "kissnet.h" +#include "gen_tone.h" +#include "digipeater.h" +#include "tq.h" +#include "xmit.h" +#include "ptt.h" +#include "beacon.h" +#include "ax25_pad.h" +#include "redecode.h" +#include "dtmf.h" +#include "aprs_tt.h" +#include "tt_user.h" +#include "igate.h" +#include "symbols.h" +#include "dwgps.h" + + +#if __WIN32__ +static BOOL cleanup_win (int); +#else +static void cleanup_linux (int); +#endif + +static void usage (char **argv); + +#if __SSE__ + +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 modem; + +static int d_u_opt = 0; /* "-d u" command line option. */ + + + +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 tt_config_s tt_config; + struct igate_config_s igate_config; + struct misc_config_s misc_config; + int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */ + char input_file[80]; + + int t_opt = 1; /* Text color option. */ + + +#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); + +#elif __CYGWIN__ + +/* + * Without this, the ISO Latin 1 characters are displayed as gray boxes. + */ + //setenv ("LANG", "C.ISO-8859-1", 1); +#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 + +/* + * This has not been very well tested in 64 bit mode. + */ + +#if 0 + if (sizeof(int) != 4 || sizeof(long) != 4 || sizeof(char *) != 4) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("------------------------------------------------------------------\n"); + dw_printf ("This might not work properly when compiled for a 64 bit target.\n"); + dw_printf ("It is recommended that you rebuild it with gcc -m32 option.\n"); + dw_printf ("------------------------------------------------------------------\n"); + } +#endif + +/* + * Default location of configuration file is current directory. + * Can be overridden by -c command line option. + * TODO: Automatically search other places. + */ + + strcpy (config_file, "direwolf.conf"); + +/* + * Look at command line options. + * So far, the only one is the configuration file location. + */ + + strcpy (input_file, ""); + while (1) { + int this_option_optind = optind ? optind : 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, "B:D:c:pxr:b:n:d:t:U", + 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 'c': /* -c for configuration file name */ + + strcpy (config_file, optarg); + 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 < 100 || B_opt > 10000) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Use a more reasonable data baud rate in range of 100 - 10000.\n"); + exit (EXIT_FAILURE); + } + 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 of -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. 1 or 2. */ + + n_opt = atoi(optarg); + if (n_opt < 1 || n_opt > MAX_CHANS) + { + 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. */ + + switch (optarg[0]) { + case 'a': server_set_debug(1); break; + case 'k': kiss_serial_set_debug (1); break; + case 'n': kiss_net_set_debug (1); break; + case 'u': d_u_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; + + + 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"); + } + + strcpy (input_file, argv[optind]); + + } + +/* + * Get all types of configuration settings from configuration file. + * + * Possibly override some by command line options. + */ + + symbols_init (); + + config_init (config_file, &modem, &digi_config, &tt_config, &igate_config, &misc_config); + + if (r_opt != 0) { + modem.samples_per_sec = r_opt; + } + if (n_opt != 0) { + modem.num_channels = n_opt; + } + if (b_opt != 0) { + modem.bits_per_sample = b_opt; + } + if (B_opt != 0) { + modem.baud[0] = B_opt; + + if (modem.baud[0] < 600) { + modem.modem_type[0] = AFSK; + modem.mark_freq[0] = 1600; + modem.space_freq[0] = 1800; + modem.decimate[0] = 3; + } + else if (modem.baud[0] > 2400) { + modem.modem_type[0] = SCRAMBLE; + modem.mark_freq[0] = 0; + modem.space_freq[0] = 0; + } + else { + modem.modem_type[0] = AFSK; + modem.mark_freq[0] = 1200; + modem.space_freq[0] = 2200; + } + } + + if (D_opt != 0) { + // Don't document. This will change. + modem.decimate[0] = D_opt; + } + + misc_config.enable_kiss_pt = enable_pseudo_terminal; + + if (strlen(input_file) > 0) { + strcpy (modem.adevice_in, input_file); + } + +/* + * 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 (&modem); + 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 AFSK demodulator and HDLC decoder. + */ + multi_modem_init (&modem); + +/* + * Initialize the touch tone decoder & APRStt gateway. + */ + dtmf_init (modem.samples_per_sec); + aprs_tt_init (&tt_config); + tt_user_init (&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 (&modem, 100); + + assert (modem.bits_per_sample == 8 || modem.bits_per_sample == 16); + assert (modem.num_channels == 1 || modem.num_channels == 2); + assert (modem.samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.samples_per_sec <= MAX_SAMPLES_PER_SEC); + +/* + * Initialize the transmit queue. + */ + + xmit_init (&modem); + +/* + * 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 = modem.baud[0] * 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 (chan, 1); + while (n-- > 0) { + + tone_gen_put_bit (chan, n & 1); + + } + ptt_set (chan, 0); + exit (0); + } + +/* + * Initialize the digipeater and IGate functions. + */ + digipeater_init (&digi_config); + igate_init (&igate_config, &digi_config); + +/* + * Provide the AGW & KISS socket interfaces for use by a client application. + */ + server_init (&misc_config); + kissnet_init (&misc_config); + +/* + * Create a pseudo terminal and KISS TNC emulator. + */ + kiss_init (&misc_config); + +/* + * Create thread for trying to salvage frames with bad FCS. + */ + redecode_init (); + +/* + * Enable beaconing. + */ + beacon_init (&misc_config, &digi_config); + + +/* + * Get sound samples and decode them. + * Use hot attribute for all functions called for every audio sample. + * TODO: separate function with __attribute__((hot)) + */ + eof = 0; + while ( ! eof) + { + + int audio_sample; + int c; + char tt; + + for (c=0; c= 256 * 256) + eof = 1; + + multi_modem_process_sample(c,audio_sample); + + + /* Previously, 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. */ + + if (tt_config.obj_xmit_header[0] != '\0') { + tt = dtmf_sample (c, audio_sample/16384.); + if (tt != ' ') { + aprs_tt_button (c, tt); + } + } + } + + /* When a complete frame is accumulated, */ + /* process_rec_frame, below, is called. */ + + } + + 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 APRStt gateway. + * 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. + * + *--------------------------------------------------------------------*/ + + +void app_process_rec_packet (int chan, int subchan, packet_t pp, int 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; + + assert (chan >= 0 && chan < MAX_CHANS); + assert (subchan >= -1 && subchan < MAX_SUBCHANS); + + + 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; + strcpy (heard, ""); + } + else { + h = ax25_get_heard(pp); + ax25_get_addr_with_ssid(pp, h, heard); + } + + if (alevel >= 0) { + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n"); + + if (h != -1 && h != AX25_SOURCE) { + dw_printf ("Digipeater "); + } + + /* 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 = %d [%s] %s\n", heard, probably_really, alevel, retry_text[(int)retries], spectrum); + } + else { + dw_printf ("%s audio level = %d [%s] %s\n", heard, alevel, retry_text[(int)retries], spectrum); + } + + /* Cranking up the input currently produces */ + /* no more than 97. Issue a warning before we */ + /* reach this saturation point. */ + + if (alevel > 90) { + 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. + + if (subchan == -1) { + text_color_set(DW_COLOR_REC); + dw_printf ("[%d.dtmf] ", chan); + } + else { + if (ax25_is_aprs(pp)) { + text_color_set(DW_COLOR_REC); + } + else { + text_color_set(DW_COLOR_DEBUG); + } + if (modem.num_subchan[chan] > 1) { + dw_printf ("[%d.%d] ", chan, subchan); + } + else { + dw_printf ("[%d] ", chan); + } + } + + dw_printf ("%s", stemp); /* stations followed by : */ + ax25_safe_print ((char *)pinfo, info_len, 0); + dw_printf ("\n"); + +// 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"); + } + } + +/* Decode the contents of APRS frames and display in human-readable form. */ + + if (ax25_is_aprs(pp)) { + decode_aprs (pp); + } + +/* Send to another application if connected. */ + + int flen; + unsigned char fbuf[AX25_MAX_PACKET_LEN]; + + flen = ax25_pack(pp, fbuf); + + server_send_rec_packet (chan, pp, fbuf, flen); + kissnet_send_rec_packet (chan, fbuf, flen); + kiss_send_rec_packet (chan, fbuf, flen); + +/* 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); + } + +/* Note that packet can be modified in place so this is the last thing we should do with it. */ +/* Again, use only those with correct CRC. */ +/* We don't want to spread corrupted data! */ +/* Single bit change appears to be safe from observations so far but be cautious. */ + + if (ax25_is_aprs(pp) && retries == RETRY_NONE) { + digipeater (chan, pp); + } + + ax25_delete (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"); + ptt_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"); + ptt_term (); + dwgps_term (); + 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]\n"); + dw_printf ("Options:\n"); + dw_printf (" -c fname Configuration file name.\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. Standard values are 300, 1200, 9600.\n"); + dw_printf (" If < 600, AFSK tones are set to 1600 & 1800.\n"); + dw_printf (" If > 2400, K9NG/G3RUH style encoding is used.\n"); + dw_printf (" Otherwise, AFSK tones are set to 1200 & 2200.\n"); + + dw_printf (" -d Debug communication with client application, one of\n"); + dw_printf (" a a = AGWPE network protocol.\n"); + dw_printf (" k k = KISS serial port.\n"); + dw_printf (" n n = KISS network.\n"); + dw_printf (" u u = Display non-ASCII text in hexadecimal.\n"); + + dw_printf (" -t n Text colors. 1=normal, 0=disabled.\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 ("\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 */ diff --git a/direwolf.conf b/direwolf.conf new file mode 100644 index 0000000..c5bb3c1 --- /dev/null +++ b/direwolf.conf @@ -0,0 +1,592 @@ +############################################################# +# # +# Configuration file for Dire Wolf # +# # +############################################################# +# +# Consult the User Guide for more details on configuration options. +# +# +# These are the most likely settings you might change: +# +# (1) MYCALL - call sign and SSID for your station. +# +# Look for lines starting with MYCALL and +# change NOCALL to your own. +# +# +# (2) PBEACON - enable position beaconing. +# +# Look for lines starting with PBEACON and +# modify for your call, location, etc. +# +# +# (3) DIGIPEATER - configure digipeating rules. +# +# Look for lines starting with DIGIPEATER. +# Most people will probably use the first example. +# Just remove the "#" from the start of the line +# to enable it. +# +# +# (4) IGSERVER, IGLOGIN - IGate server and login +# +# Configure an IGate client to relay messages between +# radio and internet servers. +# +# +# The default location is "direwolf.conf" in the current working directory. +# On Linux, the user's home directory will also be searched. +# An alternate configuration file location can be specified with the "-c" command line option. +# +# As you probably guessed by now, # indicates a comment line. +# +# Remove the # at the beginning of a line if you want to use a sample +# configuration that is currently commented out. +# +# Commands are a keyword followed by parameters. +# +# Command key words are case insensitive. i.e. upper and lower case are equivalent. +# +# Command parameters are generally case sensitive. i.e. upper and lower case are different. +# +# Example: The next two are equivalent +# +# PTT /dev/ttyS0 RTS +# ptt /dev/ttyS0 RTS +# +# But this not equivalent because device names are case sensitive. +# +# PTT /dev/TTYs0 RTS +# + + +############################################################# +# # +# AUDIO DEVICE PROPERTIES # +# # +############################################################# + +# +# Many people will simply use the default sound device. +# Some might want to use an alternative device by chosing it here. +# +# When the Windows version starts up, it displays something like +# this with the available sound devices and capabilities: +# +# Available audio input devices for receive (*=selected): +# 0: Microphone (Realtek High Defini +# 1: Microphone (Bluetooth SCO Audio +# 2: Microphone (Bluetooth AV Audio) +# 3: Microphone (USB PnP Sound Devic +# Available audio output devices for transmit (*=selected): +# 0: Speakers (Realtek High Definiti +# 1: Speakers (Bluetooth SCO Audio) +# 2: Realtek Digital Output (Realtek +# 3: Realtek Digital Output(Optical) +# 4: Speakers (Bluetooth AV Audio) +# 5: Speakers (USB PnP Sound Device) + +# Example: To use the USB Audio, use a command like this with +# the input and output device numbers. (Remove the # comment character.) + +#ADEVICE 3 5 + +# The position in the list can change when devices (e.g. USB) are added and removed. +# You can also specify devices by using part of the name. +# Here is an example of specifying the USB Audio device. +# This is case-sensitive. Upper and lower case are not treated the same. + +#ADEVICE USB + + +# Linux ALSA is complicated. See User Guide for discussion. +# To use something other than the default, generally use plughw +# and a card number reported by "arecord -l" command. Examples: + +# ADEVICE plughw:CARD=Device,DEV=0 +# ADEVICE plughw:1,0 + +# Starting with version 1.0, you can also use "-" or "stdin" to +# pipe stdout from some other application such as a software defined +# radio. You can also specify "UDP:" and an optional port for input. +# Something different must be specified for output. + +# ADEVICE - plughw:1,0 +# ADEVICE UDP:7355 default + +# +# This is the sound card audio sample rate. +# The default is 44100. Other standard values are 22050 or 11025. +# +# Change this only if your computer can't keep up. +# A lower rate means lower CPU demands but performance will be degraded. +# + +ARATE 44100 + + +# +# Number of audio channels. 1 or 2. +# If you specify 2, it is possible to attach two different transceivers +# and receive from both simultaneously. +# + +ACHANNELS 1 + +# Use this instead if you want to use two transceivers. + +#ACHANNELS 2 + + +############################################################# +# # +# CHANNEL 0 PROPERTIES # +# # +############################################################# + +CHANNEL 0 + +# +# The following will apply to the first or only channel. +# When two channels are used, this is the left audio channel. +# + + +# +# Station identifier for this channel. +# Multiple channels can have the same or different names. +# +# It can be up to 6 letters and digits with an optional ssid. +# The APRS specification requires that it be upper case. +# +# Example (don't use this unless you are me): MYCALL WB2OSZ-5 +# + +MYCALL NOCALL + +# +# VHF FM operation normally uses 1200 baud data with AFSK tones of 1200 and 2200 Hz. +# + +MODEM 1200 1200 2200 + +# +# 200 Hz shift is normally used for 300 baud HF SSB operation. +# +# Note that if you change the tones here, you will need to adjust +# your tuning dial accordingly to get the same transmitted frequencies. +# +# In the second example, we have 7 demodulators spaced 30 Hz apart +# to capture signals that are off frequency. +# If you run out of CPU power, drop the audio sample rate down to 22050. + +#MODEM 300 1600 1800 +#MODEM 300 1600 1800 7 30 + +# +# 9600 baud doesn't use AFSK so no tones are listed. +# + +#MODEM 9600 + + +# +# If not using a VOX circuit, the transmitter Push to Talk (PTT) +# control is usually wired to a serial port with a suitable interface circuit. +# DON'T connect it directly! +# +# For the PTT command, specify the device and either RTS or DTR. +# RTS or DTR may be preceded by "-" to invert the signal. +# + +#PTT COM1 RTS +#PTT COM1 -DTR +#PTT /dev/ttyUSB0 RTS + +# +# On Linux, you can also use general purpose I/O pins if +# your system is configured for user access to them. +# This would apply mostly to microprocessor boards, not a regular PC. +# See separate Raspberry Pi document for more details. +# The number may be preceded by "-" to invert the signal. +# + +#PTT GPIO 25 + + +# +# After turning on transmitter, send "flag" characters for +# TXDELAY * 10 milliseconds for transmitter to stabilize before +# sending data. 300 milliseconds is a good default. +# + +TXDELAY 30 + +# +# Keep transmitting for TXTAIL * 10 milliseconds after sending +# the data. This is needed to avoid dropping PTT too soon and +# chopping of the end of the data because we don't have +# precise control of when the sound will actually get out. +# + +TXTAIL 10 + + +############################################################# +# # +# CHANNEL 1 PROPERTIES # +# # +############################################################# + +CHANNEL 1 + +# +# The following will apply to the second (right) channel if ACHANNELS is 2. +# + +# +# The two radio channels can have the same or different station identifiers. +# +# +# Example (don't use this unless you are me): MYCALL WB2OSZ-5 +# + +MYCALL NOCALL + +MODEM 1200 1200 2200 + +# +# For this example, we use the same serial port for both +# transmitters. RTS for channel 0 and DTR for channel 1. +# + +#PTT COM1 DTR + +TXDELAY 30 +TXTAIL 10 + + + +############################################################# +# # +# VIRTUAL TNC SERVER PROPERTIES # +# # +############################################################# + +# +# Dire Wolf acts as a virtual TNC and can communicate with +# two different protocols: +# - the “AGW TCPIP Socket Interface” - default port 8000 +# - KISS TNC via serial port +# - KISS protocol over TCP socket - default port 8001 +# +# See descriptions of AGWPORT, KISSPORT, and NULLMODEM in the +# User Guide for more details. +# + +AGWPORT 8000 +KISSPORT 8001 + +# +# Some applications are designed to operate with only a physical +# TNC attached to a serial port. For these, we provide a virtual serial +# port ("pseudo terminal" in Linux) that appears to be connected to a TNC. +# +# Linux: +# Linux applications can often specify "/tmp/kisstnc" +# for the serial port name. Behind the scenes, Dire Wolf +# creates a pseudo terminal. Unfortunately we can't specify the name +# and we wouldn't want to reconfigure the application each time. +# To get around this, /tmp/kisstnc is a symbolic link to the +# non-constant pseudo terminal name. +# +# Use the -p command line option to enable this feature. +# +# Windows: +# +# Microsoft Windows applications need a serial port +# name like COM1, COM2, COM3, or COM4. +# +# Take a look at the User Guide for instructions to set up +# two virtual serial ports named COM3 and COM4 connected by +# a null modem. +# +# Using the default configuration, Dire Wolf will connect to +# COM3 and the client application will use COM4. +# +# Uncomment following line to use this feature. + +#NULLMODEM COM3 + + +# +# Version 0.6 adds a new feature where it is sometimes possible +# to recover frames with a bad FCS. Several levels of effort +# are possible. +# +# 0 [NONE] - Don't try to repair. +# 1 [SINGLE] - Attempt to fix single bit error. (default) +# 2 [DOUBLE] - Also attempt to fix two adjacent bits. +# 3 [TRIPLE] - Also attempt to fix three adjacent bits. +# 4 [TWO_SEP] - Also attempt to fix two non-adjacent (separated) bits. +# + +FIX_BITS 1 + +# +############################################################# +# # +# BEACONING PROPERTIES # +# # +############################################################# + + +# +# Beaconing is configured with these two commands: +# +# PBEACON - for a position report (usually yourself) +# OBEACON - for an object report (usually some other entity) +# +# Each has a series of keywords and values for options. +# See User Guide for details. +# +# Example: +# +# This results in a broadcast once every 10 minutes. +# Every half hour, it can travel via two digipeater hops. +# The others are kept local. +# + +#PBEACON delay=00:15 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 +#PBEACON delay=10:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" +#PBEACON delay=20:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" + +# +# Modify this for your particular situation before removing +# the # comment character from the beginning of the lines above. +# + + +############################################################# +# # +# DIGIPEATER PROPERTIES # +# # +############################################################# + +# +# Digipeating is activated with commands of the form: +# +# DIGIPEAT from-chan to-chan aliases wide [ preemptive ] +# +# where, +# +# from-chan is the channel where the packet is received. +# +# to-chan is the channel where the packet is to be re-transmitted. +# +# aliases is a pattern for digipeating ONCE. Anything matching +# this pattern is effectively treated like WIDE1-1. +# 'MYCALL' for the receiving channel is an implied +# member of this list. +# +# wide is the pattern for normal WIDEn-N digipeating +# where the ssid is decremented. +# +# preemptive is the "preemptive" digipeating option. See +# User Guide for more details. +# +# Pattern matching uses "extended regular expressions." Rather than listing +# all the different possibilities (such as "WIDE3-3,WIDE4-4,WIDE5-5,WIDE6-6,WIDE7-7"), +# a pattern can be specified such as "^WIDE[34567]-[1-7]$". This means: +# +# ^ beginning of call. Without this, leading characters +# don't need to match and ZWIDE3-3 would end up matching. +# +# WIDE is an exact literal match of upper case letters W I D E. +# +# [34567] means ANY ONE of the characters listed. +# +# - is an exact literal match of the "-" character (when not +# found inside of []). +# +# [1-7] is an alternative form where we have a range of characters +# rather than listing them all individually. +# +# $ means end of call. Without this, trailing characters don't +# need to match. As an example, we would end up matching +# WIDE3-15 besides WIDE3-1. +# +# Google "Extended Regular Expressions" for more information. +# + +# +# If the first unused digipeater field, in the received packet, +# matches the first pattern, it is treated the same way you +# would expect WIDE1-1 to behave. +# +# The digipeater name is replaced by MYCALL of the destination channel. +# +# Example: W1ABC>APRS,WIDE7-7 +# Becomes: W1ABC>APRS,WB2OSZ-5* +# +# In this example, we trap large values of N as recommended in +# http://www.aprs.org/fix14439.html +# + +# +# If not caught by the first pattern, see if it matches the second pattern. +# +# Matches will be processed with the usual WIDEn-N rules. +# +# If N >= 2, the N value is decremented and MYCALL (of the destination +# channel) is inserted if enough room. +# +# Example: W1ABC>APRS,WIDE2-2 +# Becomes: W1ABC>APRS,WB2OSZ-5*,WIDE2-1 +# +# If N = 1, we don't want to keep WIDEn-0 in the digipeater list so +# the station is replaced by MYCALL. +# +# Example: W1ABC>APRS,W9XYZ*,WIDE2-1 +# Becomes: W1ABC>APRS,W9XYZ,WB2OSZ-5* +# + +#------------------------------------------------------- +# ---------- Example 1: Typical digipeater ---------- +#------------------------------------------------------- + +# +# For most common situations, use something like this by removing +# the "#" from the beginning of the line. +# To disable digipeating, put # at the beginning of the line. +# + +# DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE + + + + +############################################################# +# # +# INTERNET GATEWAY # +# # +############################################################# + +# 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 WB2OSZ-5 123456 + +# That's all you need for a receive only IGate which relays +# messages from the local radio channel to the global servers. + +# Some might want to send an IGate client position directly to a server +# without sending it over the air and relying on someone else to +# forward it to an IGate server. This is done by using sendto=IG rather +# than a radio channel number. Overlay R for receive only, T for two way. + +#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W +#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W + + +# To relay messages from the Internet to radio, you need to add +# one more option with the transmit channel number and a VIA path. + +#IGTXVIA 0 WIDE1-1 + +# You might want to apply a filter for what packets will be obtained from the server. +# Read about filters here: http://www.aprs2.net/wiki/pmwiki.php/Main/FilterGuide +# Example: + +#IGFILTER m/50 + +# Finally, we don’t want to flood the radio channel. +# The IGate function will limit the number of packets transmitted +# during 1 minute and 5 minute intervals. If a limit would +# be exceeded, the packet is dropped and message is displayed in red. + +IGTXLIMIT 6 10 + + +############################################################# +# # +# APRStt GATEWAY # +# # +############################################################# + +# +# Dire Wolf can receive DTMF (commonly known as Touch Tone) +# messages and convert them to packet objects. +# +# See "APRStt-Implementation-Notes" document for details. +# + +# +# Sample gateway configuration based on: +# +# http://www.aprs.org/aprstt/aprstt-coding24.txt +# http://www.aprs.org/aprs-jamboree-2013.html +# + +# Define specific points. + +TTPOINT B01 37^55.37N 81^7.86W +TTPOINT B7495088 42.605237 -71.34456 +TTPOINT B934 42.605237 -71.34456 + +TTPOINT B901 42.661279 -71.364452 +TTPOINT B902 42.660411 -71.364419 +TTPOINT B903 42.659046 -71.364452 +TTPOINT B904 42.657578 -71.364602 + + +# For location at given bearing and distance from starting point. + +TTVECTOR B5bbbddd 37^55.37N 81^7.86W 0.01 mi + +# For location specified by x, y coordinates. + +TTGRID Byyyxxx 37^50.00N 81^00.00W 37^59.99N 81^09.99W + +# UTM location for Lowell-Dracut-Tyngsborough State Forest. + +TTUTM B6xxxyyy 19T 10 300000 4720000 + + + +# Location for the corral. + +TTCORRAL 37^55.50N 81^7.00W 0^0.02N + +# Compact messages - Fixed locations xx and object yyy where +# Object numbers 100 – 199 = bicycle +# Object numbers 200 – 299 = fire truck +# Others = dog + +TTMACRO xx1yy B9xx*AB166*AA2B4C5B3B0A1yy +TTMACRO xx2yy B9xx*AB170*AA3C4C7C3B0A2yy +TTMACRO xxyyy B9xx*AB180*AA3A6C4A0Ayyy + +TTMACRO z Cz + +# Transmit object reports on channel 0 with this header. + +#TTOBJ 0 WB2OSZ-5>APDW10 + +# Advertise gateway position with beacon. + +# 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" + + diff --git a/direwolf.desktop b/direwolf.desktop new file mode 100644 index 0000000..bfc0eb4 --- /dev/null +++ b/direwolf.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Exec=lxterminal -t "Dire Wolf" -e "/usr/local/bin/direwolf" +Name=Dire Wolf +Comment=APRS Soundcard TNC +Icon=/usr/share/direwolf/dw-icon.png +Path=/home/pi +#Terminal=true +Categories=HamRadio +Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25 \ No newline at end of file diff --git a/direwolf.h b/direwolf.h new file mode 100644 index 0000000..af27f76 --- /dev/null +++ b/direwolf.h @@ -0,0 +1,39 @@ + +#ifndef DIREWOLF_H +#define DIREWOLF_H 1 + + +/* + * Maximum number of radio channels. + */ + +#define MAX_CHANS 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 + + +#if __WIN32__ +#include +#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 + +#endif + +#if __WIN32__ +#define PTW32_STATIC_LIB +#include "pthreads/pthread.h" +#else +#include +#endif \ No newline at end of file diff --git a/dsp.c b/dsp.c new file mode 100644 index 0000000..65d2471 --- /dev/null +++ b/dsp.c @@ -0,0 +1,248 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011,2012,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: dsp.c + * + * Purpose: Generate the filters used by the demodulators. + * + *----------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "direwolf.h" +#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 +#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 lp_sum; + + +#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. +// + +/*------------------------------------------------------------------ + * + * 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 + * + *---------------------------------------------------------------*/ + + +#include +#include +#include +#include + +#include "direwolf.h" +#include "dtmf.h" + + +// Define for unit test. +//#define DTMF_TEST 1 + + +#if DTMF_TEST +#define TIMEOUT_SEC 1 /* short for unit test below. */ +#define DEBUG 1 +#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 decoding. + */ + +static struct { + 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]; + + struct { /* Separate for each audio channel. */ + + 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; + } C[MAX_CHANS]; +} D; + + + + +/*------------------------------------------------------------------ + * + * Name: dtmf_init + * + * Purpose: Initialize the DTMF decoder. + * This should be called once at application start up time. + * + * Inputs: sample_rate - Audio sample frequency, typically + * 44100, 22050, 8000, etc. + * + * Returns: None. + * + *----------------------------------------------------------------*/ + +void dtmf_init (int sample_rate) +{ + int j; /* Loop over all tones frequencies. */ + int c; /* Loop over all audio channels. */ + +/* + * Processing block size. + * Larger = narrower bandwidth, slower response. + */ + D.sample_rate = sample_rate; + D.block_size = (205 * sample_rate) / 8000; + +#if DEBUG + dw_printf (" freq k coef \n"); +#endif + for (j=0; j 0 && D.coef[j] < 2.0); +#if DEBUG + dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D.coef[j]); +#endif + } + + for (c=0; c 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.C[c].prev_dec) { + D.C[c].debounced = decoded; + /* Reset timeout timer. */ + if (decoded != ' ') { + D.C[c].timeout = ((TIMEOUT_SEC) * D.sample_rate) / D.block_size; + } + } + D.C[c].prev_dec = decoded; + +// Return only new button pushes. +// Also report timeout after period of inactivity. + + ret = '.'; + if (D.C[c].debounced != D.C[c].prev_debounced) { + if (D.C[c].debounced != ' ') { + ret = D.C[c].debounced; + } + } + if (ret == '.') { + if (D.C[c].timeout > 0) { + D.C[c].timeout--; + if (D.C[c].timeout == 0) { + ret = '$'; + } + } + } + D.C[c].prev_debounced = D.C[c].debounced; + +#if DEBUG + dw_printf (" dec=%c, deb=%c, ret=%c \n", + decoded, D.C[c].debounced, ret); +#endif + return (ret); + } + + return (' '); +} + + +/*------------------------------------------------------------------ + * + * Name: main + * + * Purpose: Unit test for functions above. + * + *----------------------------------------------------------------*/ + + +#if DTMF_TEST + +push_button (char button, int ms) +{ + static float phasea = 0; + static float phaseb = 0; + float fa, fb; + int i; + float input; + char x; + static char result[100]; + static int result_len = 0; + + + 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': 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': 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': 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': fa = dtmf_tones[3]; fb = dtmf_tones[7]; break; + case '?': + + if (strcmp(result, "123A456B789C*0#D123$789$") == 0) { + dw_printf ("\nSuccess!\n"); + } + else { + dw_printf ("\n *** TEST FAILED ***\n"); + dw_printf ("\"%s\"\n", result); + } + break; + + default: fa = 0; fb = 0; + } + + for (i = 0; i < (ms*D.sample_rate)/1000; i++) { + + input = sin(phasea) + sin(phaseb); + phasea += 2 * M_PI * fa / D.sample_rate; + phaseb += 2 * M_PI * fb / D.sample_rate; + + /* Make sure it is insensitive to signal amplitude. */ + + x = dtmf_sample (0, input); + //x = dtmf_sample (0, input * 1000); + //x = dtmf_sample (0, input * 0.001); + + if (x != ' ' && x != '.') { + result[result_len] = x; + result_len++; + result[result_len] = '\0'; + } + } +} + +main () +{ + + dtmf_init(44100); + + dw_printf ("\nFirst, check all button tone pairs. \n\n"); + /* Max auto dialing rate is 10 per second. */ + + push_button ('1', 50); push_button (' ', 50); + push_button ('2', 50); push_button (' ', 50); + push_button ('3', 50); push_button (' ', 50); + push_button ('A', 50); push_button (' ', 50); + + push_button ('4', 50); push_button (' ', 50); + push_button ('5', 50); push_button (' ', 50); + push_button ('6', 50); push_button (' ', 50); + push_button ('B', 50); push_button (' ', 50); + + push_button ('7', 50); push_button (' ', 50); + push_button ('8', 50); push_button (' ', 50); + push_button ('9', 50); push_button (' ', 50); + push_button ('C', 50); push_button (' ', 50); + + push_button ('*', 50); push_button (' ', 50); + push_button ('0', 50); push_button (' ', 50); + push_button ('#', 50); push_button (' ', 50); + push_button ('D', 50); push_button (' ', 50); + + dw_printf ("\nShould reject very short pulses.\n\n"); + + push_button ('1', 20); push_button (' ', 50); + push_button ('1', 20); push_button (' ', 50); + push_button ('1', 20); push_button (' ', 50); + push_button ('1', 20); push_button (' ', 50); + push_button ('1', 20); push_button (' ', 50); + + dw_printf ("\nTest timeout after inactivity.\n\n"); + /* For this test we use 1 second. */ + /* In practice, it will probably more like 10 or 20. */ + + push_button ('1', 250); push_button (' ', 500); + push_button ('2', 250); push_button (' ', 500); + push_button ('3', 250); push_button (' ', 1200); + + push_button ('7', 250); push_button (' ', 500); + push_button ('8', 250); push_button (' ', 500); + push_button ('9', 250); push_button (' ', 1200); + + /* Check for expected results. */ + + push_button ('?', 0); + +} /* end main */ + +#endif + +/* end dtmf.c */ + diff --git a/dtmf.h b/dtmf.h new file mode 100644 index 0000000..5c3c584 --- /dev/null +++ b/dtmf.h @@ -0,0 +1,10 @@ +/* dtmf.h */ + + +void dtmf_init (int sample_rate); + +char dtmf_sample (int c, float input); + + +/* end dtmf.h */ + diff --git a/dw-icon.ico b/dw-icon.ico new file mode 100644 index 0000000..00714be Binary files /dev/null and b/dw-icon.ico differ diff --git a/dw-icon.png b/dw-icon.png new file mode 100644 index 0000000..4898018 Binary files /dev/null and b/dw-icon.png differ diff --git a/dw-icon.rc b/dw-icon.rc new file mode 100644 index 0000000..ce34b40 --- /dev/null +++ b/dw-icon.rc @@ -0,0 +1 @@ +MAINICON ICON "dw-icon.ico" \ No newline at end of file diff --git a/dw-start.sh b/dw-start.sh new file mode 100644 index 0000000..b4829b4 --- /dev/null +++ b/dw-start.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# +# Run this from crontab periodically to start up +# Dire Wolf automatically. +# +# I prefer this method instead of putting something +# in ~/.config/autostart. That would start an application +# only when the desktop first starts up. +# +# This method will restart the application if it +# crashes or stops for any other reason. +# +# This script has some specifics the Raspberry Pi. +# Some adjustments might be needed for other Linux variations. +# +# First wait a little while in case we just rebooted +# and the desktop hasn't started up yet. +# + +sleep 30 + +# +# Nothing to do if it is already running. +# + +a=`ps -ef | grep direwolf | grep -v grep` +if [ "$a" != "" ] +then + #date >> /tmp/dw-start.log + #echo "Already running." >> /tmp/dw-start.log + exit +fi + +# +# 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. +# + +date >> /tmp/dw-start.log + +export DISPLAY=":0" + +v=`ps -ef | grep Xtightvnc | grep -v grep` +if [ "$v" != "" ] +then + d=`echo "$v" | sed 's/.*tightvnc *\(:[0-9]\).*/\1/'` + export DISPLAY="$d" +fi + +echo "DISPLAY=$DISPLAY" >> /tmp/dw-start.log + +echo "Start up application." >> /tmp/dw-start.log + +# +# Adjust for your particular situation: gnome-terminal, xterm, etc. +# + +if [ -x /usr/bin/lxterminal ] +then + /usr/bin/lxterminal -t "Dire Wolf" -e "/usr/local/bin/direwolf" & +elif [ -x /usr/bin/xterm ] +then + /usr/bin/xterm -bg white -fg black -e /usr/local/bin/direwolf & +elif [ -x /usr/bin/x-terminal-emulator ] +then + /usr/bin/x-terminal-emulator -e /usr/local/bin/direwolf & +else + echo "Did not find an X terminal emulator." +fi + +echo "-----------------------" >> /tmp/dw-start.log + diff --git a/dwgps.c b/dwgps.c new file mode 100644 index 0000000..2f5904a --- /dev/null +++ b/dwgps.c @@ -0,0 +1,327 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 . +// + + +/*------------------------------------------------------------------ + * + * Module: dwgps.c + * + * Purpose: Interface to location data, i.e. GPS receiver. + * + * Description: Tracker beacons need to know the current location. + * At this time, I can't think of any other reason why + * we would need this information. + * + * For Linux, we use gpsd and libgps. + * This has the extra benefit that the system clock can + * be set from the GPS signal. + * + * Not yet implemented for Windows. Not sure how yet. + * The Windows location API is new in Windows 7. + * At the end of 2013, about 1/3 of Windows users are + * still using XP so that still needs to be supported. + * + * Reference: + * + *---------------------------------------------------------------*/ + +#if TEST +#define ENABLE_GPS 1 +#endif + + +#include +#include +#include +#include +#include +#include +#include + +#if __WIN32__ +#include +#else +#if ENABLE_GPS +#include + +#if GPSD_API_MAJOR_VERSION != 5 +#error libgps API version might be incompatible. +#endif + +#endif +#endif + +#include "direwolf.h" +#include "textcolor.h" +#include "dwgps.h" + + +/* Was init successful? */ + +static enum { INIT_NOT_YET, INIT_SUCCESS, INIT_FAILED } init_status = INIT_NOT_YET; + +#if __WIN32__ +#include +#else +#if ENABLE_GPS + +static struct gps_data_t gpsdata; + +#endif +#endif + + +/*------------------------------------------------------------------- + * + * Name: dwgps_init + * + * Purpose: Intialize the GPS interface. + * + * Inputs: none. + * + * Returns: 0 = success + * -1 = failure + * + * Description: For Linux, this maps into gps_open. + * Not yet implemented for Windows. + * + *--------------------------------------------------------------------*/ + +int dwgps_init (void) +{ + +#if __WIN32__ + + text_color_set(DW_COLOR_ERROR); + dw_printf ("GPS interface not yet available in Windows version.\n"); + init_status = INIT_FAILED; + return (-1); + +#elif ENABLE_GPS + + int err; + + err = gps_open (GPSD_SHARED_MEMORY, NULL, &gpsdata); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Unable to connect to GPS receiver.\n"); + if (err == NL_NOHOST) { + dw_printf ("Shared memory interface is not enabled in libgps.\n"); + dw_printf ("Download the gpsd source and build with 'shm_export=True' option.\n"); + } + else { + dw_printf ("%s\n", gps_errstr(errno)); + } + init_status = INIT_FAILED; + return (-1); + } + init_status = INIT_SUCCESS; + return (0); +#else + + text_color_set(DW_COLOR_ERROR); + dw_printf ("GPS interface not enabled in this version.\n"); + dw_printf ("See documentation on how to rebuild with ENABLE_GPS.\n"); + init_status = INIT_FAILED; + return (-1); + +#endif + +} /* end dwgps_init */ + + + +/*------------------------------------------------------------------- + * + * Name: dwgps_read + * + * Purpose: Obtain current location from GPS receiver. + * + * Outputs: *plat - Latitude. + * *plon - Longitude. + * *pspeed - Speed, knots. + * *pcourse - Course over ground, degrees. + * *palt - Altitude, meters. + * + * Returns: -1 = error + * 0 = data not available (no fix) + * 2 = 2D fix, lat/lon, speed, and course are set. + * 3 - 3D fix, altitude is also set. + * + *--------------------------------------------------------------------*/ + +int dwgps_read (double *plat, double *plon, float *pspeed, float *pcourse, float *palt) +{ +#if __WIN32__ + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error, dwgps_read, shouldn't be here.\n"); + return (-1); + +#elif ENABLE_GPS + + int err; + + if (init_status != INIT_SUCCESS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error, dwgps_read without successful init.\n"); + return (-1); + } + + err = gps_read (&gpsdata); + +#if DEBUG + dw_printf ("gps_read returns %d bytes\n", err); +#endif + if (err > 0) { + if (gpsdata.status >= STATUS_FIX && gpsdata.fix.mode >= MODE_2D) { + + *plat = gpsdata.fix.latitude; + *plon = gpsdata.fix.longitude; + *pcourse = gpsdata.fix.track; + *pspeed = MPS_TO_KNOTS * gpsdata.fix.speed; /* libgps uses meters/sec */ + + if (gpsdata.fix.mode >= MODE_3D) { + *palt = gpsdata.fix.altitude; + return (3); + } + return (2); + } + + /* No fix. Probably temporary condition. */ + return (0); + } + else if (err == 0) { + /* No data available */ + return (0); + } + else { + /* More serious error. */ + return (-1); + } +#else + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error, dwgps_read, shouldn't be here.\n"); + return (-1); +#endif + +} /* end dwgps_read */ + + +/*------------------------------------------------------------------- + * + * Name: dwgps_term + * + * Purpose: Shut down GPS interface before exiting from application. + * + * Inputs: none. + * + * Returns: none. + * + *--------------------------------------------------------------------*/ + +void dwgps_term (void) { + +#if __WIN32__ + +#elif ENABLE_GPS + + if (init_status == INIT_SUCCESS) { + gps_close (&gpsdata); + } +#else + +#endif + +} /* end dwgps_term */ + + + + +/*------------------------------------------------------------------- + * + * Name: main + * + * Purpose: Simple unit test for other functions in this file. + * + * Description: Compile with -DTEST option. + * + * gcc -DTEST dwgps.c textcolor.c -lgps + * + *--------------------------------------------------------------------*/ + +#if TEST + +int main (int argc, char *argv[]) +{ + +#if __WIN32__ + + printf ("Not in win32 version yet.\n"); + +#elif ENABLE_GPS + int err; + int fix; + double lat; + double lon; + float speed; + float course; + float alt; + + err = dwgps_init (); + + if (err != 0) exit(1); + + while (1) { + fix = dwgps_read (&lat, &lon, &speed, &course, &alt) ; + switch (fix) { + case 3: + case 2: + dw_printf ("%.6f %.6f", lat, lon); + dw_printf (" %.1f knots %.0f degrees", speed, course); + if (fix==3) dw_printf (" altitude = %.1f meters", alt); + dw_printf ("\n"); + break; + case 0: + dw_printf ("location currently not available.\n"); + break; + default: + dw_printf ("ERROR getting GPS information.\n"); + } + sleep (3); + } + + +#else + + printf ("Test: Shouldn't be here.\n"); +#endif + +} /* end main */ + + +#endif + + + +/* end dwgps.c */ + + + diff --git a/dwgps.h b/dwgps.h new file mode 100644 index 0000000..90aa342 --- /dev/null +++ b/dwgps.h @@ -0,0 +1,15 @@ + +/* dwgps.h */ + + +int dwgps_init (void); + +int dwgps_read (double *plat, double *plon, float *pspeed, float *pcourse, float *palt); + +void dwgps_term (void); + + +/* end dwgps.h */ + + + diff --git a/encode_aprs.c b/encode_aprs.c new file mode 100644 index 0000000..65ad824 --- /dev/null +++ b/encode_aprs.c @@ -0,0 +1,797 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 . +// + +/*------------------------------------------------------------------ + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "direwolf.h" +#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. + * + * 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, position_t *presult) +{ + + latitude_to_str (dlat, 0, 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, 0, 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, 1 - 360. 0 means 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. + * + *----------------------------------------------------------------*/ + +/* 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 (course || speed) { + int c; + int s; + + c = (course + 1) / 4; + if (c < 0) c += 90; + if (c >= 90) c -= 90; + 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. Not clear if it is dBi or dBd. + * dir - Directivity: N, NE, etc., omni. + * + * Outputs: presult - Stored here. + * + * Returns: Number of characters in result. + * + *----------------------------------------------------------------*/ + + +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, 1 - 360. + * speed - knots. + * + * Outputs: presult - Stored here. + * + * Returns: Number of characters in result. + * + *----------------------------------------------------------------*/ + + +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; + + x = course; + if (x < 0) x = 0; + if (x > 360) x = 360; + sprintf (stemp, "%03d", x); + memcpy (r->cse, stemp, 3); + + r->slash = '/'; + + x = speed; + if (x < 0) x = 0; + if (x > 999) x = 999; + sprintf (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. + * + *----------------------------------------------------------------*/ + + +typedef struct freq_s { + char f[7]; /* format 999.999 */ + char mhz[3]; + char space; + } freq_t; + +typedef struct to_s { + char T; + char ttt[3]; /* format 999 (drop fraction) or 'off'. */ + char space1; + char oooo[4]; /* leading sign, 3 digits, tens of KHz. */ + char space2; + } to_t; + + +static int frequency_spec (float freq, float tone, float offset, char *presult) +{ + int result_len = 0; + + if (freq != 0) { + freq_t *f = (freq_t*)presult; + char stemp[12]; + + /* Should use letters for > 999.999. */ + sprintf (stemp, "%07.3f", freq); + memcpy (f->f, stemp, 7); + memcpy (f->mhz, "MHz", 3); + f->space = ' '; + result_len = sizeof (freq_t); + } + + if (tone != 0 || offset != 0) { + to_t *to = (to_t*)(presult + result_len); + char stemp[12]; + + to->T = 'T'; + if (tone == 0) { + memcpy(to->ttt, "off", 3); + } + else { + sprintf (stemp, "%03d", (int)tone); + memcpy (to->ttt, stemp, 3); + } + to->space1 = ' '; + sprintf (stemp, "%+04d", (int)round(offset * 100)); + memcpy (to->oooo, stemp, 4); + to->space2 = ' '; + + result_len += sizeof (to_t); + } + + return (result_len); +} + + +/*------------------------------------------------------------------ + * + * Name: encode_position + * + * Purpose: Construct info part for position report format. + * + * Inputs: compressed - Send in compressed form? + * lat - Latitude. + * lon - Longitude. + * 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, 1 - 360. 0 means none or unknown. + * speed - knots. + * + * freq - MHz. + * tone - Hz. + * offset - MHz. + * + * comment - Additional comment text. + * + * + * Outputs: presult - Stored here. Should be at least ??? bytes. + * + * 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 compressed, double lat, double lon, + 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) +{ + int result_len = 0; + + if (compressed) { + aprs_compressed_pos_t *p = (aprs_compressed_pos_t *)presult; + + p->dti = '!'; + 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 = '!'; + set_norm_position (symtab, symbol, lat, lon, &(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 || speed) { + result_len += cse_spd_data_extension (course, speed, presult + result_len); + } + else if (power || height || gain) { + 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) { + strcat (presult, comment); + result_len += strlen(comment); + } + + 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. + * 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, 1 - 360. 0 means none or unknown. + * speed - knots. + * + * freq - MHz. + * tone - Hz. + * offset - MHz. + * + * comment - Additional comment text. + * + * Outputs: presult - Stored here. Should be at least ??? bytes. + * + * 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, + 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) +{ + 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 > 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 + 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 + sprintf (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, &(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 || speed) { + result_len += cse_spd_data_extension (course, speed, presult + result_len); + } + else if (power || height || gain) { + 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) { + strcat (presult, comment); + result_len += strlen(comment); + } + + 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 ; ./a.exe + * + *----------------------------------------------------------------*/ + + +#if EN_MAIN + +void text_color_set ( enum dw_color_e c ) +{ + return; +} + +int main (int argc, char *argv[]) +{ + char result[100]; + + + +/*********** Position ***********/ + + encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', + 0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result); + dw_printf ("%s\n", result); + if (strcmp(result, "!4234.61ND07126.47W&") != 0) dw_printf ("ERROR!\n"); + +/* with PHG. */ + + encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', + 50, 100, 6, "N", 0, 0, 0, 0, 0, NULL, result); + dw_printf ("%s\n", result); + if (strcmp(result, "!4234.61ND07126.47W&PHG7368") != 0) dw_printf ("ERROR!\n"); + +/* with freq. */ + + encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', + 0, 0, 0, NULL, 0, 0, 146.955, 74.4, -0.6, NULL, result); + dw_printf ("%s\n", result); + if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 -060 ") != 0) dw_printf ("ERROR!\n"); + +/* with course/speed, freq, and comment! */ + + encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', + 0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result); + dw_printf ("%s\n", result); + if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz T074 -060 River flooding") != 0) dw_printf ("ERROR!\n"); + +/* Course speed, no tone, + offset */ + + encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', + 0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result); + dw_printf ("%s\n", result); + if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz Toff +060 River flooding") != 0) dw_printf ("ERROR!\n"); + + + + + +/*********** Compressed position. ***********/ + + encode_position (1, 42+34.61/60, -(71+26.47/60), 'D', '&', + 0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result); + dw_printf ("%s\n", result); + if (strcmp(result, "!D8yKC. +// + + +#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 ); +} + + +/* + * 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 ); +} + diff --git a/fcs_calc.h b/fcs_calc.h new file mode 100644 index 0000000..2e2b0ef --- /dev/null +++ b/fcs_calc.h @@ -0,0 +1,11 @@ + +/* 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 */ + + diff --git a/fsk_demod_agc.h b/fsk_demod_agc.h new file mode 100644 index 0000000..95c8079 --- /dev/null +++ b/fsk_demod_agc.h @@ -0,0 +1,2 @@ +#define TUNE_MS_FILTER_SIZE 140 +#define TUNE_PRE_BAUD 1.080 diff --git a/fsk_demod_state.h b/fsk_demod_state.h new file mode 100644 index 0000000..e4247e2 --- /dev/null +++ b/fsk_demod_state.h @@ -0,0 +1,172 @@ +/* fsk_demod_state.h */ + +#ifndef FSK_DEMOD_STATE_H + +/* + * Demodulator state. + * Different copy is required for each channel & subchannel being processed concurrently. + */ + + +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. + */ + +#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. */ + +/* + * FIR filter length relative to one bit time. + * Use same for both bandpass and lowpass. + */ + float filter_len_bits; + +/* + * Window type for the mark/space filters. + */ + bp_window_t bp_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; + float lpf_baud; + +/* + * Automatic gain control. Fast attack and slow decay factors. + */ + float agc_fast_attack; + float agc_slow_decay; +/* + * Hysteresis before final demodulator 0 / 1 decision. + */ + float hysteresis; + +/* + * 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[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))); + +/* + * The rest are continuously updated. + */ + 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. + +/* + * Most recent raw audio samples, before/after prefiltering. + */ + float raw_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); + +/* + * 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. + */ + + int lp_filter_size; + + 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; + + int prev_demod_data; // Previous data bit detected. + // Used to look for transitions. + + +/* These are used only for "9600" baud data. */ + + int lfsr; // Descrambler shift register. + + +/* + * Finally, try to come up with some sort of measure of the audio input level. + * Let's try gathering both the peak and average of the + * absolute value of the input signal over some period such as 100 mS. + * + */ + int lev_period; // How many samples go into one measure. + + int lev_count; // Number accumulated so far. + + float lev_peak_acc; // Highest peak so far. + + float lev_sum_acc; // Accumulated sum so far. + +/* + * These will be updated every 'lev_period' samples: + */ + float lev_last_peak; + float lev_last_ave; + float lev_prev_peak; + float lev_prev_ave; + +}; + +#define FSK_DEMOD_STATE_H 1 +#endif \ No newline at end of file diff --git a/fsk_filters.h b/fsk_filters.h new file mode 100644 index 0000000..81c4e9a --- /dev/null +++ b/fsk_filters.h @@ -0,0 +1,7 @@ +/* 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 }; diff --git a/fsk_gen_filter.h b/fsk_gen_filter.h new file mode 100644 index 0000000..e7e8fa6 --- /dev/null +++ b/fsk_gen_filter.h @@ -0,0 +1,15 @@ + + +#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); + +#endif \ No newline at end of file diff --git a/gen_packets.c b/gen_packets.c new file mode 100644 index 0000000..f37665c --- /dev/null +++ b/gen_packets.c @@ -0,0 +1,729 @@ +// +// 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: gen_packets.c + * + * Purpose: Test program for generating AFSK AX.25 frames. + * + * Description: Given messages are converted to audio and written + * to a .WAV type audio file. + * + * + * Bugs: Most options not implemented for second audio channel. + * + *------------------------------------------------------------------*/ + + + + +#include +#include +#include +#include +#include + +#include "audio.h" +#include "ax25_pad.h" +#include "hdlc_send.h" +#include "gen_tone.h" +#include "textcolor.h" + + +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; + + + +int main(int argc, char **argv) +{ + int c; + int digit_optind = 0; + int err; + unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; + int flen; + int packet_count = 0; + int i; + int chan; + +/* + * Set up default values for the modem. + */ + struct audio_s modem; + + modem.num_channels = DEFAULT_NUM_CHANNELS; /* -2 stereo */ + modem.samples_per_sec = DEFAULT_SAMPLES_PER_SEC; /* -r option */ + modem.bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* -8 for 8 instead of 16 bits */ + + for (chan = 0; chan < MAX_CHANS; chan++) { + modem.modem_type[chan] = AFSK; /* change with -g */ + modem.mark_freq[chan] = DEFAULT_MARK_FREQ; /* -m option */ + modem.space_freq[chan] = DEFAULT_SPACE_FREQ; /* -s option */ + modem.baud[chan] = DEFAULT_BAUD; /* -b option */ + } + +/* + * Set up other default values. + */ + int amplitude = 50; /* -a option */ + int leading_zeros = 12; /* -z option */ + char output_file[256]; /* -o option */ + FILE *input_fp = NULL; /* File or NULL for built-in message */ + + packet_t pp; + + + strcpy (output_file, ""); + + + while (1) { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + 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, "gm:s:a:b:B:r:n:o:z:82", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + + case 0: /* possible future use */ + + text_color_set(DW_COLOR_INFO); + dw_printf("option %s", long_options[option_index].name); + if (optarg) { + dw_printf(" with arg %s", optarg); + } + dw_printf("\n"); + break; + + case 'b': /* -b for data Bit rate */ + + modem.baud[0] = atoi(optarg); + text_color_set(DW_COLOR_INFO); + dw_printf ("Data rate set to %d bits / second.\n", modem.baud[0]); + if (modem.baud[0] < 100 || modem.baud[0] > 10000) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n"); + exit (EXIT_FAILURE); + } + break; + + case 'B': /* -B for data Bit rate */ + /* 300 implies 1600/1800 AFSK. */ + /* 1200 implies 1200/2200 AFSK. */ + /* 9600 implies scrambled. */ + + modem.baud[0] = atoi(optarg); + text_color_set(DW_COLOR_INFO); + dw_printf ("Data rate set to %d bits / second.\n", modem.baud[0]); + if (modem.baud[0] < 100 || modem.baud[0] > 10000) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n"); + exit (EXIT_FAILURE); + } + + switch (modem.baud[0]) { + case 300: + modem.mark_freq[0] = 1600; + modem.space_freq[0] = 1800; + break; + case 1200: + modem.mark_freq[0] = 1200; + modem.space_freq[0] = 2200; + break; + case 9600: + modem.modem_type[0] = SCRAMBLE; + text_color_set(DW_COLOR_INFO); + dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); + break; + } + break; + + case 'g': /* -g for g3ruh scrambling */ + + modem.modem_type[0] = 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.mark_freq[0] = atoi(optarg); + text_color_set(DW_COLOR_INFO); + dw_printf ("Mark frequency set to %d Hz.\n", modem.mark_freq[0]); + if (modem.mark_freq[0] < 300 || modem.mark_freq[0] > 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.space_freq[0] = atoi(optarg); + text_color_set(DW_COLOR_INFO); + dw_printf ("Space frequency set to %d Hz.\n", modem.space_freq[0]); + if (modem.space_freq[0] < 300 || modem.space_freq[0] > 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 > 100) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Amplitude must be in range of 0 to 100.\n"); + exit (EXIT_FAILURE); + } + break; + + case 'r': /* -r for audio sample Rate */ + + modem.samples_per_sec = atoi(optarg); + text_color_set(DW_COLOR_INFO); + dw_printf ("Audio sample rate set to %d samples / second.\n", modem.samples_per_sec); + if (modem.samples_per_sec < MIN_SAMPLES_PER_SEC || modem.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 translast 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.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.num_channels = 2; + text_color_set(DW_COLOR_INFO); + dw_printf("2 channels of sound rather than 1.\n"); + break; + + case 'o': /* -o for Output file */ + + strcpy (output_file, optarg); + text_color_set(DW_COLOR_INFO); + dw_printf ("Output file set to %s\n", output_file); + 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); + } + } + + 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); + } + + if (input_fp != stdin) { + fclose (input_fp); + } + } + else { + text_color_set(DW_COLOR_INFO); + dw_printf ("built in message...\n"); + } + + + if (strlen(output_file) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR: The -o ouput 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); + } + + + gen_tone_init (&modem, amplitude); + + assert (modem.bits_per_sample == 8 || modem.bits_per_sample == 16); + assert (modem.num_channels == 1 || modem.num_channels == 2); + assert (modem.samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.samples_per_sec <= MAX_SAMPLES_PER_SEC); + + + 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[80]; + + if (modem.modem_type[0] == SCRAMBLE) { + g_noise_level = 0.33 * (amplitude / 100.0) * ((float)i / packet_count); + } + else { + g_noise_level = (amplitude / 100.0) * ((float)i / packet_count); + } + + sprintf (stemp, "WB2OSZ-1>APRS,W1AB-9,W1ABC-10,WB1ABC-15:,Hello, world! %04d", i); + + pp = ax25_from_text (stemp, 1); + flen = ax25_pack (pp, fbuf); + for (c=0; cAPRS,W1AB-9,W1ABC-10,WB1ABC-15:,Hello, world!", 1); + flen = ax25_pack (pp, fbuf); + for (c=0; cAPRS,W1AB-9*,W1ABC-10,WB1ABC-15:,Hello, world!", 1); + flen = ax25_pack (pp, fbuf); + for (c=0; cAPRS,W1AB-9,W1ABC-10*,WB1ABC-15:,Hello, world!", 1); + flen = ax25_pack (pp, fbuf); + for (c=0; cAPRS,W1AB-9,W1ABC-10,WB1ABC-15*:,Hello, world!", 1); + flen = ax25_pack (pp, fbuf); + for (c=0; c Signal amplitude in range of 0 - 100%%. 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, 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 of audio rather than 1.\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-n message. The format should correspond to ...\n"); + dw_printf ("blah blah blah. For example,\n"); + dw_printf (" WB2OSZ-1>APDW10,WIDE2-2:!4237.14NS07120.83W#\n"); + dw_printf ("\n"); + dw_printf ("Example: %s\n", argv[0]); + 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: %s -g -b 9600\n", argv[0]); + dw_printf ("Shortcut: %s -B 9600\n", argv[0]); + dw_printf ("\n"); + dw_printf (" 9600 baud mode.\n"); + dw_printf ("\n"); + dw_printf ("Example: %s -m 1600 -s 1800 -b 300\n", argv[0]); + dw_printf ("Shortcut: %s -B 300\n", argv[0]); + 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!\" | %s -a 25 -o x.wav -\n", argv[0]); + 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 -> num_channels == 0) + pa -> num_channels = DEFAULT_NUM_CHANNELS; + + if (pa -> samples_per_sec == 0) + pa -> samples_per_sec = DEFAULT_SAMPLES_PER_SEC; + + if (pa -> bits_per_sample == 0) + pa -> 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 -> num_channels; + header.nsamplespersec = pa -> samples_per_sec; + header.wbitspersample = pa -> 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 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. */ + + r = (rand() - RAND_MAX/2.0) / (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 () +{ + 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 */ + diff --git a/gen_tone.c b/gen_tone.c new file mode 100644 index 0000000..5500204 --- /dev/null +++ b/gen_tone.c @@ -0,0 +1,334 @@ +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * Module: gen_tone.c + * + * Purpose: Convert bits to AFSK for writing to .WAV sound file + * or a sound device. + * + * + *---------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include + +#include "direwolf.h" +#include "audio.h" +#include "gen_tone.h" +#include "textcolor.h" + + + +// Properties of the digitized sound stream & modem. + +static struct audio_s modem; + +/* + * 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; /* same for all channels. */ + +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. + +static int bit_len_acc[MAX_CHANS]; // To accumulate fractional samples per bit. + +static int lfsr[MAX_CHANS]; // Shift register for scrambler. + + +/*------------------------------------------------------------------ + * + * Name: gen_tone_init + * + * Purpose: Initialize for AFSK tone generation which might + * be used for RTTY or amateur packet radio. + * + * Inputs: pp - Pointer to modem parameter structure, modem_s. + * + * The fields we care about are: + * + * samples_per_sec + * baud + * mark_freq + * space_freq + * samples_per_sec + * + * amp - Signal amplitude on scale of 0 .. 100. + * + * Returns: 0 for success. + * -1 for failure. + * + * Description: Calculate various constants for use by the direct digital synthesis + * audio tone generation. + * + *----------------------------------------------------------------*/ + +static int amp16bit; /* for 9600 baud */ + + +int gen_tone_init (struct audio_s *pp, int amp) +{ + int j; + int chan = 0; +/* + * Save away modem parameters for later use. + */ + + memcpy (&modem, pp, sizeof(struct audio_s)); + + amp16bit = (32767 * amp) / 100; + + ticks_per_sample = (int) ((TICKS_PER_CYCLE / (double)modem.samples_per_sec ) + 0.5); + + for (chan = 0; chan < modem.num_channels; chan++) { + + ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)modem.baud[chan] ) + 0.5); + + f1_change_per_sample[chan] = (int) (((double)modem.mark_freq[chan] * TICKS_PER_CYCLE / (double)modem.samples_per_sec ) + 0.5); + + f2_change_per_sample[chan] = (int) (((double)modem.space_freq[chan] * TICKS_PER_CYCLE / (double)modem.samples_per_sec ) + 0.5); + + tone_phase[chan] = 0; + + bit_len_acc[chan] = 0; + + lfsr[chan] = 0; + } + + 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 is in range of -32768 .. +32767. */ + + assert (s >= -32768 && s <= 32767); + + sine_table[j] = s; + } + + return (0); + + } /* end gen_tone_init */ + + +/*------------------------------------------------------------------- + * + * Name: gen_tone_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. + * + *--------------------------------------------------------------------*/ + +void tone_gen_put_bit (int chan, int dat) +{ + int cps = dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan]; + unsigned short sam = 0; + int x; + + + if (dat < 0) { + /* Hack to test receive PLL recovery. */ + bit_len_acc[chan] -= ticks_per_bit[chan]; + dat = 0; + } + + if (modem.modem_type[chan] == SCRAMBLE) { + x = (dat ^ (lfsr[chan] >> 16) ^ (lfsr[chan] >> 11)) & 1; + lfsr[chan] = (lfsr[chan] << 1) | (x & 1); + dat = x; + } + + do { + + if (modem.modem_type[chan] == AFSK) { + tone_phase[chan] += cps; + sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; + } + else { + // TODO: Might want to low pass filter this. + sam = dat ? amp16bit : (-amp16bit); + } + + /* Ship out an audio sample. */ + + assert (modem.num_channels == 1 || modem.num_channels == 2); + + /* Generalize to allow 8 bits someday? */ + + assert (modem.bits_per_sample == 16); + + + if (modem.num_channels == 1) + { + audio_put (sam & 0xff); + audio_put ((sam >> 8) & 0xff); + } + else if (modem.num_channels == 2) + { + if (chan == 1) + { + audio_put (0); // silent left + audio_put (0); + } + + audio_put (sam & 0xff); + audio_put ((sam >> 8) & 0xff); + //byte_count += 2; + + if (chan == 0) + { + audio_put (0); // silent right + audio_put (0); + } + } + + /* Enough for the bit time? */ + + bit_len_acc[chan] += ticks_per_sample; + + } while (bit_len_acc[chan] < ticks_per_bit[chan]); + + bit_len_acc[chan] -= ticks_per_bit[chan]; +} + + +/*------------------------------------------------------------------- + * + * 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 audio_param; + + +/* to sound card */ +/* one channel. 2 times: one second of each tone. */ + + memset (&audio_param, 0, sizeof(audio_param)); + strcpy (audio_param.adevice_in, DEFAULT_ADEVICE); + strcpy (audio_param.adevice_out, DEFAULT_ADEVICE); + + audio_open (&audio_param); + gen_tone_init (&audio_param, 100); + + for (r=0; r<2; r++) { + + for (n=0; n. +// + + +/******************************************************************************** + * + * File: hdlc_rec.c + * + * Purpose: Extract HDLC frames from a stream of bits. + * + *******************************************************************************/ + +#include +#include + +#include "direwolf.h" +#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" + + +//#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. */ + + 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. */ + + enum retry_e fix_bits; /* Level of effort to recover from */ + /* a bad FCS on the frame. */ + + rrbb_t rrbb; /* Handle for bit array for raw received bits. */ + +}; + + +static struct hdlc_state_s hdlc_state[MAX_CHANS][MAX_SUBCHANS]; + +static int num_subchan[MAX_CHANS]; + + +/*********************************************************************************** + * + * 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 j, k; + struct hdlc_state_s *H; + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("hdlc_rec_init (%p) \n", pa); + + assert (pa != NULL); + + for (j=0; jnum_channels; j++) + { + num_subchan[j] = pa->num_subchan[j]; + + assert (num_subchan[j] >= 1 && num_subchan[j] < MAX_SUBCHANS); + + for (k=0; kprev_raw = 0; + H->pat_det = 0; + H->flag4_det = 0; + H->olen = -1; + H->frame_len = 0; + H->data_detect = 0; + H->fix_bits = pa->fix_bits; + H->rrbb = rrbb_new(j, k, pa->modem_type[j] == SCRAMBLE, -1); + } + } + + was_init = 1; +} + + + +/*********************************************************************************** + * + * Name: hdlc_rec_bit + * + * Purpose: Extract HDLC frames from a stream of bits. + * + * Inputs: chan - Channel number. + * + * subchan - This allows multiple decoders per channel. + * + * raw - One bit from the demodulator. + * should be 0 or 1. + * + * is_scrambled - Is the data scrambled? + * + * descram_state - Current descrambler state. + * + * sam - Possible future: Sample from demodulator output. + * Should nominally be in roughly -1 to +1 range. + * + * Description: This is called once for each received bit. + * For each valid frame, process_rec_frame() + * is called for further processing. + * + ***********************************************************************************/ + +#if SLICENDICE +void hdlc_rec_bit_sam (int chan, int subchan, int raw, float demod_out) +#else +void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram_state) +#endif +{ + + 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); + +/* + * Different state information for each channel. + */ + H = &hdlc_state[chan][subchan]; + +/* + * Using NRZI encoding, + * A '0' bit is represented by an inversion since previous bit. + * A '1' bit is represented by no change. + */ + + 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 rather than + * tones from a modem. + * + * 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. + */ + + if (H->flag4_det == 0x7e7e7e7e) { + + + if ( ! H->data_detect) { + H->data_detect = 1; +#if DEBUG3 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("DCD%d = 1 flags\n", chan); +#endif + } + } + + if (H->flag4_det == 0x7e000000) { + + + if ( ! H->data_detect) { + H->data_detect = 1; +#if DEBUG3 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("DCD%d = 1 zero fill\n", chan); +#endif + } + } + + +/* + * Loss of signal should result in lack of transitions. + * (all '1' bits) for at least a little while. + */ + + + if (H->pat_det == 0xff) { + + if ( H->data_detect ) { + H->data_detect = 0; +#if DEBUG3 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("DCD%d = 0\n", chan); +#endif + } + } + + +/* + * End of data carrier detect. + * + * The rest is concerned with framing. + */ + +#if SLICENDICE + rrbb2_append_bit (H->rrbb, demod_out); +#else + rrbb_append_bit (H->rrbb, raw); +#endif + 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) { + int alevel = demod_get_audio_level (chan, subchan); + + multi_modem_process_rec_frame (chan, subchan, 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, %d bits in frame\n", rrbb_get_len(H->rrbb) - 1); +#endif + if (rrbb_get_len(H->rrbb) >= MIN_FRAME_LEN * 8) { + + int alevel = demod_get_audio_level (chan, subchan); + + rrbb_set_audio_level (H->rrbb, alevel); + hdlc_rec2_block (H->rrbb, H->fix_bits); + /* Now owned by someone else who will free it. */ + H->rrbb = rrbb_new (chan, subchan, is_scrambled, descram_state); /* Allocate a new one. */ + } + else { + rrbb_clear (H->rrbb, is_scrambled, descram_state); + } + + H->olen = 0; /* Allow accumulation of octets. */ + H->frame_len = 0; + +#if SLICENDICE + rrbb2_append_bit (H->rrbb, H->prev_raw ? 1.0 : -1.0); /* Last bit of flag. Needed to get first data bit. */ +#else + rrbb_append_bit (H->rrbb, H->prev_raw); /* Last bit of flag. Needed to get first data bit. */ +#endif +#endif + + } + else if (H->pat_det == 0xfe) { + +/* + * Valid data will never have 7 one bits in a row. + * + * 11111110 + * + * This indicates loss of signal. + */ + + H->olen = -1; /* Stop accumulating octets. */ + H->frame_len = 0; /* Discard anything in progress. */ + + rrbb_clear (H->rrbb, is_scrambled, descram_state); +#if SLICENDICE + rrbb2_append_bit (H->rrbb, H->prev_raw ? 1.0 : -1.0); /* Last bit of flag. Needed to get first data bit. */ +#else + rrbb_append_bit (H->rrbb, H->prev_raw); /* Last bit of flag. Needed to get first data bit. */ +#endif + } + 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_data_detect_1 + * 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. 0 for left, 1 for right. + * + * 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_1 tests a single decoder (subchan) + * and is used by the DPLL to determine how much inertia + * to use when trying to follow the incoming signal. + * + * 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. + * + *--------------------------------------------------------------------*/ + +int hdlc_rec_data_detect_1 (int chan, int subchan) +{ + assert (chan >= 0 && chan < MAX_CHANS); + + return ( hdlc_state[chan][subchan].data_detect ); + +} /* end hdlc_rec_data_detect_1 */ + + +int hdlc_rec_data_detect_any (int chan) +{ + int subchan; + + assert (chan >= 0 && chan < MAX_CHANS); + + for (subchan = 0; subchan < num_subchan[chan]; subchan++) { + + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + + if (hdlc_state[chan][subchan].data_detect) { + return (1); + } + } + return (0); + + +} /* end hdlc_rec_data_detect_any */ + + +/* end hdlc_rec.c */ + + diff --git a/hdlc_rec.h b/hdlc_rec.h new file mode 100644 index 0000000..1bc4101 --- /dev/null +++ b/hdlc_rec.h @@ -0,0 +1,24 @@ + + +#include "audio.h" + +#include "rrbb.h" /* Possibly defines SLICENDICE. */ + + +void hdlc_rec_init (struct audio_s *pa); + +#if SLICENDICE +void hdlc_rec_bit_sam (int chan, int subchan, int raw, float demod_out); +#else +void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram_state); +#endif + + +/* Provided elsewhere to process a complete frame. */ + +//void process_rec_frame (int chan, unsigned char *fbuf, int flen, int level); + +/* Transmit needs to know when someone else is transmitting. */ + +int hdlc_rec_data_detect_1 (int chan, int subchan); +int hdlc_rec_data_detect_any (int chan); diff --git a/hdlc_rec2.c b/hdlc_rec2.c new file mode 100644 index 0000000..f09bce6 --- /dev/null +++ b/hdlc_rec2.c @@ -0,0 +1,661 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011, 2012, 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 . +// + + +/******************************************************************************** + * + * 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. + * + *******************************************************************************/ + +#include +#include +#include + +#include "direwolf.h" +#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" + + +/* + * 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. */ + + 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 alevel, retry_t bits_flipped, int flip_a, int flip_b, int flip_c); +static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel, retry_t fix_bits); +static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped); +#if DEBUG +static double dtime_now (void); +#endif + +/*********************************************************************************** + * + * Name: hdlc_rec2_block + * + * Purpose: Extract HDLC frame from a stream of bits. + * + * Inputs: block - Handle for bit array. + * fix_bits - Level of effort to recover frames with bad FCS. + * + * 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. + * + * Bugs: This does not work for 9600 baud, more accurately when + * the transmitted bits are scrambled. + * + * Currently we unscramble the bits as they come from the + * receiver. Instead we need to save the original received + * bits and apply the descrambling after flipping the bits. + * + ***********************************************************************************/ + + +void hdlc_rec2_block (rrbb_t block, retry_t fix_bits) +{ + int chan = rrbb_get_chan(block); + int subchan = rrbb_get_subchan(block); + int alevel = rrbb_get_audio_level(block); + int ok; + int n; + +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n--- try to decode ---\n"); +#endif + +#if SLICENDICE +/* + * Unfinished experiment. Get back to this again someday. + * The demodulator output is (should be) roughly in the range of -1 to 1. + * Formerly we sliced it at 0 and saved only a single bit for the sample. + * Now we save the sample so we can try adjusting the slicing point. + * + * First time thru, set the slicing point to 0. + */ + + for (n = 0; n < 1 ; n++) { + + rrbb_set_slice_val (block, n); + + ok = try_decode (block, chan, subchan, alevel, RETRY_NONE, -1, -1, -1); + + if (ok) { +//#if DEBUG + text_color_set(DW_COLOR_INFO); + dw_printf ("Got it with no errors. Slice val = %d \n", n); +//#endif + rrbb_delete (block); + return; + } + } + rrbb_set_slice_val (block, 0); + +#else /* not SLICENDICE */ + + ok = try_decode (block, chan, subchan, alevel, RETRY_NONE, -1, -1, -1); + + if (ok) { +#if DEBUG + text_color_set(DW_COLOR_INFO); + dw_printf ("Got it the first time.\n"); +#endif + rrbb_delete (block); + return; + } +#endif + + if (try_to_fix_quick_now (block, chan, subchan, alevel, fix_bits)) { + rrbb_delete (block); + return; + } + +/* + * Put in queue for retrying later at lower priority. + */ + + if (fix_bits < RETRY_TWO_SEP) { + rrbb_delete (block); + return; + } + + rdq_append (block); + +} + + +static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel, retry_t fix_bits) +{ + int ok; + int len, i; + + + len = rrbb_get_len(block); + +/* + * Try fixing one bit. + */ + if (fix_bits < RETRY_SINGLE) { + return 0; + } + + for (i=0; i>= 1; + if (dbit) { + H.pat_det |= 0x80; + } + + if (H.pat_det == 0x7e) { + /* The special pattern 01111110 indicates beginning and ending of a frame. */ +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("try_decode: found flag, i=%d\n", i); +#endif + return 0; + } + else if (H.pat_det == 0xfe) { + /* Valid data will never have 7 one bits in a row. */ +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("try_decode: found abort, i=%d\n", i); +#endif + return 0; + } + 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. + */ + + 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++; + } + } + } + + } /* 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 + int j; + text_color_set(DW_COLOR_DEBUG); + dw_printf ("NEW WAY: frame len = %d\n", H.frame_len); + 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; + } + } + +/* + * The next two bytes should be 0x03 and 0xf0 for APRS. + * Checking that would mean precluding use for other types of packet operation. + * + * The next section is also assumes APRS and might discard good data + * for other protocols. + */ + + +/* + * 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 == 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 */ + + + + +// TODO: Also in xmit.c. Move to some common location. + + +/* 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 time. */ + + +#if DEBUG + +static double dtime_now (void) +{ +#if __WIN32__ + /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ + + FILETIME ft; + + GetSystemTimeAsFileTime (&ft); + + return ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + + (double)ft.dwLowDateTime ) / 10000000.) - 11644473600.); +#else + /* tv_sec is seconds from Jan 1, 1970. */ + + struct timespec ts; + + clock_gettime (CLOCK_REALTIME, &ts); + + return (ts.tv_sec + ts.tv_nsec / 1000000000.); +#endif +} + +#endif diff --git a/hdlc_rec2.h b/hdlc_rec2.h new file mode 100644 index 0000000..4fd88f4 --- /dev/null +++ b/hdlc_rec2.h @@ -0,0 +1,35 @@ + +#ifndef HDLC_REC2_H +#define HDLC_REC2_H 1 + + +#include "rrbb.h" +#include "ax25_pad.h" /* for packet_t */ + +typedef enum retry_e { + RETRY_NONE=0, + RETRY_SINGLE=1, + RETRY_DOUBLE=2, + RETRY_TRIPLE=3, + RETRY_TWO_SEP=4 } retry_t; + +#if defined(DIREWOLF_C) || defined(ATEST_C) || defined(UDPTEST_C) + +static const char * retry_text[] = { + "NONE", + "SINGLE", + "DOUBLE", + "TRIPLE", + "TWO_SEP" }; +#endif + +void hdlc_rec2_block (rrbb_t block, retry_t fix_bits); + +void hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int alevel); + +/* Provided by the top level application to process a complete frame. */ + +void app_process_rec_packet (int chan, int subchan, packet_t pp, int level, retry_t retries, char *spectrum); + + +#endif diff --git a/hdlc_send.c b/hdlc_send.c new file mode 100644 index 0000000..77d9c2f --- /dev/null +++ b/hdlc_send.c @@ -0,0 +1,215 @@ +// +// 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 . +// + + +#include + +#include "direwolf.h" +#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; + + + +/*------------------------------------------------------------- + * + * 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. + * + * 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 j, fcs; + + + number_of_bits_sent = 0; + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d )\n", chan, fbuf, flen); + fflush (stdout); +#endif + + + send_control (chan, 0x7e); /* Start frame */ + + for (j=0; j> 8) & 0xff); + + send_control (chan, 0x7e); /* End frame */ + + return (number_of_bits_sent); +} + + +/*------------------------------------------------------------- + * + * 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 = 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 = 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++; + if (stuff == 5) { + send_bit (chan, 0); + stuff = 0; + } + } else { + stuff = 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; + + if (b == 0) { + output = ! output; + } + + tone_gen_put_bit (chan, output); + + number_of_bits_sent++; +} + +/* end hdlc_send.c */ \ No newline at end of file diff --git a/hdlc_send.h b/hdlc_send.h new file mode 100644 index 0000000..41d44b1 --- /dev/null +++ b/hdlc_send.h @@ -0,0 +1,10 @@ + +/* hdlc_send.h */ + +int hdlc_send_frame (int chan, unsigned char *fbuf, int flen); + +int hdlc_send_flags (int chan, int flags, int finish); + +/* end hdlc_send.h */ + + diff --git a/igate.c b/igate.c new file mode 100644 index 0000000..3219045 --- /dev/null +++ b/igate.c @@ -0,0 +1,1491 @@ +// +// 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: 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 + * + *---------------------------------------------------------------*/ + +/*------------------------------------------------------------------ + * + * 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. + */ + + +#if __WIN32__ + +/* The goal is to support Windows XP and later. */ + +#include +// default is 0x0400 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */ +//#define _WIN32_WINNT 0x0502 /* Minimum OS version is XP with SP2. */ +//#define _WIN32_WINNT 0x0600 /* Minimum OS version is Vista. */ +#include +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#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" + + + +#if __WIN32__ +static unsigned __stdcall connnect_thread (void *arg); +static unsigned __stdcall igate_recv_thread (void *arg); +#else +static void * connnect_thread (void *arg); +static void * igate_recv_thread (void *arg); +#endif + +static void send_msg_to_server (char *msg); +static void xmit_packet (char *message); + +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 void ig_to_tx_remember (packet_t pp); +static int ig_to_tx_allow (packet_t pp); + + +/* + * 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__ + sprintf (pStringBuf, "%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__ + sprintf (pStringBuf, "%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: + sprintf (pStringBuf, "Invalid address family!"); + } + assert (strlen(pStringBuf) < StringBufSize); + return pStringBuf; +} + + +#if ITEST + +/* For unit testing. */ + +int main (int argc, char *argv[]) +{ + struct igate_config_s igate_config; + struct digi_config_s digi_config; + packet_t pp; + + memset (&igate_config, 0, sizeof(igate_config)); + + strcpy (igate_config.t2_server_name, "localhost"); + igate_config.t2_server_port = 14580; + strcpy (igate_config.t2_login, "WB2OSZ-JL"); + strcpy (igate_config.t2_passcode, "-1"); + igate_config.t2_filter = strdup ("r/1/2/3"); + + igate_config.tx_chan = 0; + strcpy (igate_config.tx_via, ",WIDE2-1"); + igate_config.tx_limit_1 = 3; + igate_config.tx_limit_5 = 5; + + memset (&digi_config, 0, sizeof(digi_config)); + digi_config.num_chans = 2; + strcpy (digi_config.mycall[0], "WB2OSZ-1"); + strcpy (digi_config.mycall[1], "WB2OSZ-2"); + + 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:?\r\n"); + } +#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 igate_config_s g_config; + +static int g_num_chans; /* Number of radio channels. */ + +static char g_mycall[MAX_CHANS][AX25_MAX_ADDR_LEN]; + /* Call-ssid associated */ + /* with each of the radio channels. */ + /* Could be the same or different. */ + + +/* + * Statistics. + * TODO: need print function. + */ + +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. */ + +static int stats_rx_igate_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_tx_igate_packets; /* Number of packets from IGate server. */ + +static int stats_rf_xmit_packets; /* Number of packets passed along to radio */ + /* after rate limiting or other restrictions. */ + + + +/*------------------------------------------------------------------- + * + * Name: igate_init + * + * Purpose: One time initialization when main application starts up. + * + * Inputs: p_igate_config - IGate configuration. + * + * p_digi_config - Digipeater configuration. All we care about is: + * - Number of radio channels. + * - Radio call and SSID for each channel. + * + * Description: This starts two threads: + * + * * to establish and maintain a connection to the server. + * * to listen for packets from the server. + * + *--------------------------------------------------------------------*/ + + +void igate_init (struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config) +{ +#if __WIN32__ + HANDLE connnect_th; + HANDLE cmd_recv_th; +#else + pthread_t connect_listen_tid; + pthread_t cmd_listen_tid; + int e; +#endif + int j; + +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("igate_init ( %s, %d, %s, %s, %s )\n", + p_igate_config->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 + + + stats_failed_connect = 0; + stats_connects = 0; + stats_connect_at = 0; + stats_rf_recv_packets = 0; + stats_rx_igate_packets = 0; + stats_uplink_bytes = 0; + stats_downlink_bytes = 0; + stats_tx_igate_packets = 0; + stats_rf_xmit_packets = 0; + + rx_to_ig_init (); + ig_to_tx_init (); +/* + * Save the arguments for later use. + */ + memcpy (&g_config, p_igate_config, sizeof (g_config)); + + g_num_chans = p_digi_config->num_chans; + assert (g_num_chans >= 1 && g_num_chans <= MAX_CHANS); + for (j=0; jmycall[j]); + } + + +/* + * 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 here. + */ + +#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 +} + + +/*------------------------------------------------------------------- + * + * 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 && kai_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", + g_config.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", + g_config.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", + g_config.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", g_config.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); + sprintf (stemp, "user %s pass %s vers Dire-Wolf %d.%d", + g_config.t2_login, g_config.t2_passcode, + MAJOR_VERSION, MINOR_VERSION); + if (g_config.t2_filter != NULL) { + strcat (stemp, " filter "); + strcat (stemp, g_config.t2_filter); + } + strcat (stemp, "\r\n"); + send_msg_to_server (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]; + + strcpy (heartbeat, "#\r\n"); + + /* This will close the socket if any error. */ + send_msg_to_server (heartbeat); + + } + } +} /* 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. + * + *--------------------------------------------------------------------*/ + +void igate_send_rec_packet (int chan, packet_t recv_pp) +{ + packet_t pp; + int n; + unsigned char *pinfo; + char *p; + char msg[520]; /* Message to IGate max 512 characters. */ + int info_len; + + + if (igate_sock == -1) { + return; /* Silently discard if not connected. */ + } + + if ( ! ok_to_send) { + return; /* Login not complete. */ + } + + /* Count only while connected. */ + stats_rf_recv_packets++; + +/* + * First make a copy of it because it might be modified in place. + */ + + pp = ax25_dup (recv_pp); + +/* + * 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 DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Do not relay with TCPIP etc. in path.\n"); +#endif + ax25_delete (pp); + return; + } + } + +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Unwrap third party message.\n"); +#endif + 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 DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Do not relay with TCPIP etc. in path.\n"); +#endif + ax25_delete (pp); + return; + } + } + +/* + * Do not relay generic query. + */ + if (ax25_get_dti(pp) == '?') { +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Do not relay generic query.\n"); +#endif + ax25_delete (pp); + return; + } + + +/* + * Cut the information part at the first CR or LF. + */ + + info_len = ax25_get_info (pp, &pinfo); + + if ((p = strchr ((char*)pinfo, '\r')) != NULL) { +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Truncated information part at CR.\n"); +#endif + *p = '\0'; + } + + if ((p = strchr ((char*)pinfo, '\n')) != NULL) { +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Truncated information part at LF.\n"); +#endif + *p = '\0'; + } + + +/* + * Someone around here occasionally sends a packet with no information part. + */ + if (strlen(pinfo) == 0) { + +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Information part length is zero.\n"); +#endif + ax25_delete (pp); + return; + } + +// TODO: Should we drop raw touch tone data object type generated here? + +/* + * Do not relay if a duplicate of something sent recently. + */ + + if ( ! rx_to_ig_allow(pp)) { +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Rx IGate: Drop duplicate of same packet seen recently.\n"); +#endif + ax25_delete (pp); + return; + } + +/* + * Finally, append ",qAR," and my call to the path. + */ + + ax25_format_addrs (pp, msg); + msg[strlen(msg)-1] = '\0'; /* Remove trailing ":" */ + strcat (msg, ",qAR,"); + strcat (msg, g_mycall[chan]); + strcat (msg, ":"); + strcat (msg, (char*)pinfo); + strcat (msg, "\r\n"); + + send_msg_to_server (msg); + stats_rx_igate_packets++; + +/* + * Remember what was sent to avoid duplicates in near future. + */ + rx_to_ig_remember (pp); + + ax25_delete (pp); + +} /* end igate_send_rec_packet */ + + + + + +/*------------------------------------------------------------------- + * + * Name: send_msg_to_server + * + * Purpose: Send to the IGate server. + * This one function should be used for login, hearbeats, + * and packets. + * + * Inputs: msg - Message. Should end with CR/LF. + * + * + * Description: Send message to IGate Server if connected. + * Disconnect from server, and notify user, if any error. + * + *--------------------------------------------------------------------*/ + + +static void send_msg_to_server (char *msg) +{ + int err; + + + if (igate_sock == -1) { + return; /* Silently discard if not connected. */ + } + + stats_uplink_bytes += strlen(msg); + +#if DEBUG + text_color_set(DW_COLOR_XMIT); + dw_printf ("[ig] "); + ax25_safe_print (msg, strlen(msg), 0); + dw_printf ("\n"); +#endif + +#if __WIN32__ + err = send (igate_sock, msg, strlen(msg), 0); + if (err == SOCKET_ERROR) + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError %d sending message 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 = write (igate_sock, msg, strlen(msg)); + if (err <= 0) + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError sending message 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. + +#if __WIN32__ + n = recv (igate_sock, (char*)(&ch), 1, 0); +#else + n = read (igate_sock, &ch, 1); +#endif + + 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 500 or so. + 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++; + + if (len < sizeof(message)) + { + message[len] = ch; + } + len++; + + } while (ch != '\n'); + +/* + * We have a complete message terminated by LF. + */ + 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. + */ +#ifndef DEBUG + if ( ! ok_to_send) { +#endif + text_color_set(DW_COLOR_REC); + dw_printf ("[ig] "); + ax25_safe_print ((char *)message, len, 0); + dw_printf ("\n"); +#ifndef DEBUG + } +#endif + } + else + { +/* + * Convert to third party packet and transmit. + */ + text_color_set(DW_COLOR_REC); + dw_printf ("\n[ig] "); + ax25_safe_print ((char *)message, len, 0); + dw_printf ("\n"); + +/* + * Remove CR LF from end. + */ + if (len >=2 && message[len-1] == '\n') { message[len-1] = '\0'; len--; } + if (len >=1 && message[len-1] == '\r') { message[len-1] = '\0'; len--; } + + xmit_packet ((char*)message); + } + + } /* while (1) */ + return (0); + +} /* end igate_recv_thread */ + + +/*------------------------------------------------------------------- + * + * Name: xmit_packet + * + * Purpose: Convert text string, from IGate server, to third party + * packet and send to transmit queue. + * + * Inputs: message - As sent by the server. + * + *--------------------------------------------------------------------*/ + +static void xmit_packet (char *message) +{ + packet_t pp3; + char payload[500]; /* what is max len? */ + char *pinfo = NULL; + int info_len; + +/* + * Is IGate to Radio direction enabled? + */ + if (g_config.tx_chan == -1) { + return; + } + + stats_tx_igate_packets++; + + assert (g_config.tx_chan >= 0 && g_config.tx_chan < MAX_CHANS); + +/* + * Try to parse it into a packet object. + * Bug: Up to 8 digipeaters are allowed in radio format. + * There is a potential of finding more 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; + } + +/* + * TODO: Discard if qAX in path??? others? + */ + +/* + * Remove the VIA path. + */ + 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, g_mycall[g_config.tx_chan]); + ax25_set_h (pp3, AX25_REPEATER_2); + +/* + * Convert to text representation. + */ + ax25_format_addrs (pp3, payload); + info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo)); + strcat (payload, pinfo); +#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. + */ + if (ig_to_tx_allow (pp3)) { + char radio [500]; + packet_t pradio; + + sprintf (radio, "%s>%s%d%d%s:}%s", + g_mycall[g_config.tx_chan], + APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, + g_config.tx_via, + payload); + + pradio = ax25_from_text (radio, 1); +#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 (g_config.tx_chan, TQ_PRIO_1_LO, pradio); +#endif + stats_rf_xmit_packets++; + ig_to_tx_remember (pp3); + } + + ax25_delete (pp3); + +} /* end xmit_packet */ + + + +/*------------------------------------------------------------------- + * + * 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 sent recently. + * + * 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. + * + *--------------------------------------------------------------------*/ + +#define RX2IG_DEDUPE_TIME 60 /* Do not send duplicate within 60 seconds. */ +#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; n= 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; + + for (j=0; j= now - RX2IG_DEDUPE_TIME && rx2ig_checksum[j] == crc) { + return 0; + } + } + 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. + * + *------------------------------------------------------------------------------ + * + * 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. + * + * 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 with one addition restriction. + * + * The typical residential Internet connection is about 10,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. + * + * Future? We might also want to avoid transmitting if the same packet + * was heard on the radio recently. If everything is kept in + * the same table, we'd need to distinguish between those from + * the IGate server and those heard on the radio. + * Those heard on the radio would not count toward the + * 1 and 5 minute rate limiting. + * Maybe even provide informative information such as - + * Tx IGate: Same packet heard recently from W1ABC and W9XYZ. + * + * Of course, the radio encapsulation would need to be removed + * and only the 3rd party packet inside compared. + * + *--------------------------------------------------------------------*/ + +#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. */ + +static int ig2tx_insert_next; +static time_t ig2tx_time_stamp[IG2TX_HISTORY_MAX]; +static unsigned short ig2tx_checksum[IG2TX_HISTORY_MAX]; + +static void ig_to_tx_init (void) +{ + int n; + for (n=0; n= IG2TX_HISTORY_MAX) { + ig2tx_insert_next = 0; + } +} + +static int ig_to_tx_allow (packet_t pp) +{ + unsigned short crc = ax25_dedupe_crc(pp); + time_t now = time(NULL); + int j; + int count_1, count_5; + + for (j=0; j= now - IG2TX_DEDUPE_TIME && ig2tx_checksum[j] == crc) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Tx IGate: Drop duplicate packet transmitted recently.\n"); + return 0; + } + } + count_1 = 0; + count_5 = 0; + for (j=0; j= now - 60) count_1++; + if (ig2tx_time_stamp[j] >= now - 300) count_5++; + } + + if (count_1 >= g_config.tx_limit_1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 1 minute.\n", g_config.tx_limit_1); + return 0; + } + if (count_5 >= g_config.tx_limit_5) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 5 minutes.\n", g_config.tx_limit_5); + return 0; + } + + return 1; + +} /* end ig_to_tx_allow */ + +/* end igate.c */ diff --git a/igate.h b/igate.h new file mode 100644 index 0000000..bbf5a6d --- /dev/null +++ b/igate.h @@ -0,0 +1,65 @@ + +/*---------------------------------------------------------------------------- + * + * Name: igate.h + * + * Purpose: Interface to the Internet Gateway functions. + * + *-----------------------------------------------------------------------------*/ + + +#ifndef IGATE_H +#define IGATE_H 1 + + +#include "ax25_pad.h" +#include "digipeater.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. */ + +/* + * For transmitting. + */ + int tx_chan; /* Radio channel for transmitting. */ + /* 0=first, etc. -1 for none. */ + + 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 tx_limit_1; /* Max. packets to transmit in 1 minute. */ + + int tx_limit_5; /* Max. packets to transmit in 5 minutes. */ +}; + +/* Call this once at startup */ + +void igate_init (struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config); + +/* Call this with each packet received from the radio. */ + +void igate_send_rec_packet (int chan, packet_t recv_pp); + + +#endif diff --git a/kiss.c b/kiss.c new file mode 100644 index 0000000..612341d --- /dev/null +++ b/kiss.c @@ -0,0 +1,923 @@ +// +// 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 . +// + + + +/*------------------------------------------------------------------ + * + * Module: kiss.c + * + * Purpose: Act as a virtual KISS TNC for use by other packet radio applications. + * + * Input: + * + * Outputs: + * + * Description: This provides a pseudo terminal for communication with a client application. + * + * 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. Always full duplex. + * + * 6 SetHardware TNC specific. Ignored. + * + * FF Return Exit KISS mode. Ignored. + * + * + * Messages sent to client application: + * + * 0 Data Frame Received AX.25 frame in raw format. + * + * + * + * Platform differences: + * + * We can use a pseudo terminal for Linux or Cygwin applications. + * However, Microsoft Windows doesn't seem to have similar functionality. + * Native Windows applications expect to see a device named COM1, + * COM2, COM3, or COM4. Some might offer more flexibility but others + * might be limited to these four choices. + * + * The documentation instucts the user to install the com0com + * “Null-modem emulator” from http://sourceforge.net/projects/com0com/ + * and configure it for COM3 & COM4. + * + * By default Dire Wolf will use COM3 (/dev/ttyS2 or /dev/com3 - lower case!) + * and the client application will use COM4 (available as /dev/ttyS or + * /dev/com4 for Cygwin applications). + * + * + * This can get confusing. + * + * If __WIN32__ is defined, + * We use the Windows interface to the specfied serial port. + * This could be a real serial port or the nullmodem driver + * connected to another application. + * + * If __CYGWIN__ is defined, + * We connect to a serial port as in the previous case but + * use the Linux I/O interface. + * We also supply a pseudo terminal for any Cygwin applications + * such as Xastir so the null modem is not needed. + * + * For the Linux case, + * We supply a pseudo terminal for use by other applications. + * + * + * Reference: http://www.robbayer.com/files/serial-win.pdf + * + *---------------------------------------------------------------*/ + +#include +#include + +#if __WIN32__ +#include +#include +#else +#define __USE_XOPEN2KXSI 1 +#define __USE_XOPEN 1 +//#define __USE_POSIX 1 +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include + +#include "direwolf.h" +#include "tq.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "kiss.h" +#include "kiss_frame.h" +#include "xmit.h" + + +#if __WIN32__ +typedef HANDLE MYFDTYPE; +#define MYFDERROR INVALID_HANDLE_VALUE +#else +typedef int MYFDTYPE; +#define MYFDERROR (-1) +#endif + + +static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */ + + +/* + * These are for a Linux/Cygwin pseudo terminal. + */ + +#if ! __WIN32__ + +static MYFDTYPE pt_master_fd = MYFDERROR; /* File descriptor for my end. */ + +static MYFDTYPE pt_slave_fd = MYFDERROR; /* File descriptor for pseudo terminal */ + /* for use by application. */ + +/* + * Symlink to pseudo terminal name which changes. + */ + +#define DEV_KISS_TNC "/tmp/kisstnc" + +#endif + +/* + * This is for native Windows applications and a virtual null modem. + */ + +#if __CYGWIN__ || __WIN32__ + +static MYFDTYPE nullmodem_fd = MYFDERROR; + +#endif + + + + +static void * kiss_listen_thread (void *arg); + + + +#if DEBUG9 +static FILE *log_fp; +#endif + + +static int kiss_debug = 0; /* Print information flowing from and to client. */ + +void kiss_serial_set_debug (int n) +{ + kiss_debug = n; +} + + +/* In server.c. Should probably move to some misc. function file. */ + +void hex_dump (unsigned char *p, int len); + + + + + +/*------------------------------------------------------------------- + * + * Name: kiss_init + * + * Purpose: Set up a pseudo terminal acting as a virtual KISS TNC. + * + * + * Inputs: mc->nullmodem - name of device for our end of nullmodem. + * + * 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 MYFDTYPE kiss_open_pt (void); +static MYFDTYPE kiss_open_nullmodem (char *device); + +void kiss_init (struct misc_config_s *mc) +{ + int e; +#if __WIN32__ + HANDLE kiss_nullmodem_listen_th; +#else + pthread_t kiss_pterm_listen_tid; + pthread_t kiss_nullmodem_listen_tid; +#endif + + memset (&kf, 0, sizeof(kf)); + +/* + * This reads messages from client. + */ + +#if ! __WIN32__ + +/* + * Pseudo terminal for Cygwin and Linux versions. + */ + pt_master_fd = MYFDERROR; + + if (mc->enable_kiss_pt) { + + pt_master_fd = kiss_open_pt (); + + if (pt_master_fd != MYFDERROR) { + e = pthread_create (&kiss_pterm_listen_tid, (pthread_attr_t*)NULL, kiss_listen_thread, (void*)(long)pt_master_fd); + 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"); + } +#endif + +#if __CYGWIN__ || __WIN32 + +/* + * Cygwin and native Windows versions have serial port connection. + */ + if (strlen(mc->nullmodem) > 0) { + +#if ! __WIN32__ + + /* Translate Windows device name into Linux name. */ + /* COM1 -> /dev/ttyS0, etc. */ + + if (strncasecmp(mc->nullmodem, "COM", 3) == 0) { + int n = atoi (mc->nullmodem + 3); + text_color_set(DW_COLOR_INFO); + dw_printf ("Converted nullmodem device '%s'", mc->nullmodem); + if (n < 1) n = 1; + sprintf (mc->nullmodem, "/dev/ttyS%d", n-1); + dw_printf (" to Linux equivalent '%s'\n", mc->nullmodem); + } +#endif + nullmodem_fd = kiss_open_nullmodem (mc->nullmodem); + + if (nullmodem_fd != MYFDERROR) { +#if __WIN32__ + kiss_nullmodem_listen_th = _beginthreadex (NULL, 0, kiss_listen_thread, (void*)(long)nullmodem_fd, 0, NULL); + if (kiss_nullmodem_listen_th == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not create kiss nullmodem thread\n"); + return; + } +#else + e = pthread_create (&kiss_nullmodem_listen_tid, NULL, kiss_listen_thread, (void*)(long)nullmodem_fd); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Could not create kiss listening thread for Windows virtual COM port."); + + } +#endif + } + } +#endif + + +#if DEBUG + text_color_set (DW_COLOR_DEBUG); +#if ! __WIN32__ + dw_printf ("end of kiss_init: pt_master_fd = %d\n", pt_master_fd); +#endif +#if __CYGWIN__ || __WIN32__ + dw_printf ("end of kiss_init: nullmodem_fd = %d\n", nullmodem_fd); +#endif + +#endif +} + + +/* + * Returns fd for master side of pseudo terminal or MYFDERROR for error. + */ + +#if ! __WIN32__ + +static MYFDTYPE kiss_open_pt (void) +{ + int fd; + char *slave_device; + struct termios ts; + int e; + //int flags; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("kiss_open_pt ( )\n"); +#endif + + + fd = posix_openpt(O_RDWR|O_NOCTTY); + + if (fd == MYFDERROR + || grantpt (fd) == MYFDERROR + || unlockpt (fd) == MYFDERROR + || (slave_device = ptsname (fd)) == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR - Could not create pseudo terminal for KISS TNC.\n"); + return (MYFDERROR); + } + + + 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"); + } + +/* + * After running for a while on Linux, the write eventually + * blocks if no one is reading from the other side of + * the pseudo terminal. We get stuck on the kiss data + * write and reception stops. + * + * I tried using ioctl(,TIOCOUTQ,) to see how much was in + * the queue but that always returned zero. (Ubuntu) + * + * Let's try using non-blocking writes and see if we get + * the EWOULDBLOCK status instead of hanging. + */ + +#if 0 // this is worse. all writes fail. errno = 0 bad file descriptor + 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"); + } +#endif +#if 0 // same + flags = 1; + e = ioctl (fd, FIONBIO, &flags); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't set pseudo terminal to nonblocking, ioctl returns %d, errno = %d\n", e, errno); + perror ("pt ioctl"); + } +#endif + text_color_set(DW_COLOR_INFO); + dw_printf("Virtual KISS TNC is available on %s\n", slave_device); + dw_printf("WARNING - Dire Wolf will hang eventually if nothing is reading from it.\n"); + +/* + * 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. + */ + +//TODO: remove symlink on exit. + unlink (DEV_KISS_TNC); + + if (symlink (slave_device, DEV_KISS_TNC) == 0) { + dw_printf ("Created symlink %s -> %s\n", DEV_KISS_TNC, slave_device); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Failed to create symlink %s\n", DEV_KISS_TNC); + perror (""); + } + +#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. + + pt_slave_fd = open(slave_device, O_RDWR|O_NOCTTY); + + if (pt_slave_fd < 0) + return MYFDERROR; +#endif + return (fd); + + +} + +#endif + +/* + * Returns fd for our side of null modem or MYFDERROR for error. + */ + + +#if __CYGWIN__ || __WIN32__ + +static MYFDTYPE kiss_open_nullmodem (char *devicename) +{ + +#if __WIN32__ + + MYFDTYPE fd; + DCB dcb; + int ok; + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename); +#endif + +#if DEBUG9 + log_fp = fopen ("kiss-debug.txt", "w"); +#endif + +// 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 + + + fd = CreateFile(devicename, 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 connect to %s side of null modem for Windows KISS TNC.\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 ("kiss_open_nullmodem: GetCommState failed.\n"); + } + + /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */ + + // dcb.BaudRate ? shouldn't matter + 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) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("kiss_open_nullmodem: SetCommState failed.\n"); + } + + text_color_set(DW_COLOR_INFO); + dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename); + +#else + +/* Cygwin version. */ + + int fd; + struct termios ts; + int e; + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename); +#endif + + fd = open (devicename, O_RDWR); + + if (fd == MYFDERROR) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR - Could not connect to %s side of null modem for Windows KISS TNC.\n", devicename); + return (MYFDERROR); + } + + e = tcgetattr (fd, &ts); + if (e != 0) { perror ("nm 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) { perror ("nm tcsetattr"); } + + text_color_set(DW_COLOR_INFO); + dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename); + +#endif + + return (fd); +} + +#endif + + + + +/*------------------------------------------------------------------- + * + * Name: kiss_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. + * + * 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. + * + * + * 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 kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen) +{ + unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN]; + int kiss_len; + int j; + int err; + +#if ! __WIN32__ + if (pt_master_fd == MYFDERROR) { + return; + } +#endif + +#if __CYGWIN__ || __WIN32__ + + if (nullmodem_fd == MYFDERROR) { + return; + } +#endif + + if (flen < 0) { + flen = strlen((char*)fbuf); + if (kiss_debug) { + kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen); + } + strcpy ((char *)kiss_buff, (char *)fbuf); + kiss_len = strlen((char *)kiss_buff); + } + else { + + kiss_len = 0; + kiss_buff[kiss_len++] = FEND; + kiss_buff[kiss_len++] = chan << 4; + + for (j=0; j change CNCB0 EmuOverrun=yes + * command> change CNCA0 EmuBR=yes + */ + +#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 (nullmodem_fd, kiss_buff, kiss_len, &nwritten, &ov_wr)) + { + err = GetLastError(); + if (err != ERROR_IO_PENDING) + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError sending KISS message to client application thru null modem. Error %d.\n\n", (int)GetLastError()); + //CloseHandle (nullmodem_fd); + //nullmodem_fd = MYFDERROR; + } + } + else if (nwritten != flen) + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError sending KISS message to client application thru null modem. Only %d of %d written.\n\n", (int)nwritten, kiss_len); + //CloseHandle (nullmodem_fd); + //nullmodem_fd = MYFDERROR; + } + +#if DEBUG + /* Could wait with GetOverlappedResult but we never */ + /* have an issues in this direction. */ + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("KISS SEND completed. wrote %d / %d\n", nwritten, kiss_len); +#endif + +#else + err = write (nullmodem_fd, kiss_buf, (size_t)kiss_len); + if (err != len) + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError sending KISS message to client application thru null modem. err=%d\n\n", err); + //close (nullmodem_fd); + //nullmodem_fd = MYFDERROR; + } +#endif + +#endif + +} /* kiss_send_rec_packet */ + + + +/*------------------------------------------------------------------- + * + * Name: kiss_listen_thread + * + * Purpose: Wait for messages from an application. + * + * Inputs: arg - File descriptor for reading. + * + * Outputs: pt_slave_fd - File descriptor for communicating with client app. + * + * Description: Process messages from the client application. + * + *--------------------------------------------------------------------*/ + +//TODO: should pass fd by reference so it can be zapped. +//BUG: If we close it here, that fact doesn't get back +// to the main receiving thread. + +/* Return one byte (value 0 - 255) or terminate thread on error. */ + + +static int kiss_get (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 ("\nKISS GetOverlappedResult error %d.\n\n", err3); + } + else + { + /* Success! n should be 1 */ + } + } + } + else + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nKISS ReadFile error %d. Closing connection.\n\n", err1); + //CloseHandle (fd); + //fd = MYFDERROR; + //pthread_exit (NULL); + } + } + + } /* end while n==0 */ + + CloseHandle(ov_rd.hEvent); + + if (n != 1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nKISS failed to get one byte. n=%d.\n\n", (int)n); + +#if DEBUG9 + fprintf (log_fp, "n=%d\n", n); +#endif + } + + +#else /* Linux/Cygwin version */ + + int n; + + n = read(fd, &ch, (size_t)1); + + if (n != 1) { + //text_color_set(DW_COLOR_ERROR); + //dw_printf ("\nError receiving kiss message from client application. Closing connection %d.\n\n", fd); + + close (fd); + + fd = MYFDERROR; + pthread_exit (NULL); + } + +#endif + +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("kiss_get(%d) returns 0x%02x\n", fd, ch); +#endif + +#if DEBUG9 + fprintf (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); +} + + + + +static void * kiss_listen_thread (void *arg) +{ + MYFDTYPE fd = (MYFDTYPE)(long)arg; + + unsigned char ch; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("kiss_listen_thread ( %d )\n", fd); +#endif + + + while (1) { + ch = kiss_get(fd); + + if (kiss_frame (&kf, ch, kiss_debug, kiss_send_rec_packet)) { + kiss_process_msg (&kf, kiss_debug); + } + } /* while (1) */ + + return (NULL); /* Unreachable but avoids compiler warning. */ +} + +/* end kiss.c */ diff --git a/kiss.h b/kiss.h new file mode 100644 index 0000000..4c037fb --- /dev/null +++ b/kiss.h @@ -0,0 +1,21 @@ + +/* + * Name: kiss.h + */ + + +#include "ax25_pad.h" /* for packet_t */ + +#include "config.h" + + + + +void kiss_init (struct misc_config_s *misc_config); + +void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen); + +void kiss_serial_set_debug (int n); + + +/* end kiss.h */ diff --git a/kiss_frame.c b/kiss_frame.c new file mode 100644 index 0000000..ee7ec66 --- /dev/null +++ b/kiss_frame.c @@ -0,0 +1,407 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 . +// + + + +/*------------------------------------------------------------------ + * + * Module: kiss_frame.c + * + * Purpose: Common code used by Serial port and network versions of KISS protocol. + * + * Description: The KISS TNS protocol is 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. Always full duplex. + * + * 6 SetHardware TNC specific. Ignored. + * + * FF Return Exit KISS mode. Ignored. + * + * + * Messages sent to client application: + * + * 0 Data Frame Received AX.25 frame in raw format. + * + *---------------------------------------------------------------*/ + +#include +#include + +#include +#include + +#include +#include + +#include "direwolf.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "kiss_frame.h" +#include "tq.h" +#include "xmit.h" + + + +/*------------------------------------------------------------------- + * + * Name: kiss_frame + * + * Purpose: Extract a KISS frame from byte stream. + * + * Inputs: kf - Current state of building a frame. + * ch - A byte from the input stream. + * debug - Activates debug output. + * sendfun - Function to send something to the client application. + * + * Outputs: kf - Current state is updated. + * + * Returns: TRUE when a complete frame is ready for processing. + * + * Bug: For send, the debug output shows exactly what is + * being sent including the surrounding FEND and any + * escapes. For receive, we don't show those. + * + *-----------------------------------------------------------------*/ + +/* + * Application might send some commands to put TNC into KISS mode. + * For example, APRSIS32 sends something like: + * + * <0x0d> + * <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. + */ + +int kiss_frame (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int)) +{ + + switch (kf->state) { + + case KS_SEARCHING: /* Searching for starting FEND. */ + + 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->state = KS_COLLECTING; + return 0; + } + + /* 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'; + } + + /* Try to appease it by sending something back. */ + if (strcasecmp("restart\r", (char*)(kf->noise)) == 0 || + strcasecmp("reset\r", (char*)(kf->noise)) == 0) { + (*sendfun) (0, (unsigned char *)"\xc0\xc0", -1); + } + else { + (*sendfun) (0, (unsigned char *)"\r\ncmd:", -1); + } + kf->noise_len = 0; + } + return 0; + + case KS_COLLECTING: /* Frame collection in progress. */ + + if (ch == FEND) { + + /* End of frame. */ + + if (kf->kiss_len == 0) { + /* Empty frame. Just go on collecting. */ + return 0; + } + + if (debug) { + kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len); + } + kf->state = KS_SEARCHING; + return 1; + } + + 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 0; + + case KS_ESCAPE: /* Expecting TFESC or TFEND. */ + + if (kf->kiss_len >= MAX_KISS_LEN) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("KISS message exceeded maximum length.\n"); + kf->state = KS_COLLECTING; + return 0; + } + + if (ch == TFESC) { + kf->kiss_msg[kf->kiss_len++] = FESC; + } + else if (ch == TFEND) { + kf->kiss_msg[kf->kiss_len++] = FEND; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("KISS protocol error. TFESC or TFEND expected.\n"); + } + + kf->state = KS_COLLECTING; + return 0; + } + + return 0; /* unreachable but suppress compiler warning. */ + +} /* end kiss_frame */ + + +/*------------------------------------------------------------------- + * + * Name: kiss_process_msg + * + * Purpose: Process a message from the KISS client. + * + * Inputs: kf - Current state of building a frame. + * Should be complete. + * + * debug - Debug option is selected. + * + *-----------------------------------------------------------------*/ + +void kiss_process_msg (kiss_frame_t *kf, int debug) +{ + int port; + int cmd; + packet_t pp; + + port = (kf->kiss_msg[0] >> 4) & 0xf; + cmd = kf->kiss_msg[0] & 0xf; + + switch (cmd) + { + case 0: /* Data Frame */ + + /* Special hack - Discard apparently bad data from Linux AX25. */ + + if ((port == 2 || port == 8) && + kf->kiss_msg[1] == 'Q' << 1 && + kf->kiss_msg[2] == 'S' << 1 && + kf->kiss_msg[3] == 'T' << 1 && + kf->kiss_msg[4] == ' ' << 1 && + kf->kiss_msg[15] == 3 && + kf->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; + } + + pp = ax25_from_frame (kf->kiss_msg+1, kf->kiss_len-1, -1); + 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 1: /* TXDELAY */ + + text_color_set(DW_COLOR_INFO); + dw_printf ("KISS protocol set TXDELAY = %d, port %d\n", kf->kiss_msg[1], port); + xmit_set_txdelay (port, kf->kiss_msg[1]); + break; + + case 2: /* Persistence */ + + text_color_set(DW_COLOR_INFO); + dw_printf ("KISS protocol set Persistence = %d, port %d\n", kf->kiss_msg[1], port); + xmit_set_persist (port, kf->kiss_msg[1]); + break; + + case 3: /* SlotTime */ + + text_color_set(DW_COLOR_INFO); + dw_printf ("KISS protocol set SlotTime = %d, port %d\n", kf->kiss_msg[1], port); + xmit_set_slottime (port, kf->kiss_msg[1]); + break; + + case 4: /* TXtail */ + + text_color_set(DW_COLOR_INFO); + dw_printf ("KISS protocol set TXtail = %d, port %d\n", kf->kiss_msg[1], port); + xmit_set_txtail (port, kf->kiss_msg[1]); + break; + + case 5: /* FullDuplex */ + + text_color_set(DW_COLOR_INFO); + dw_printf ("KISS protocol set FullDuplex = %d, port %d\n", kf->kiss_msg[1], port); + break; + + case 6: /* TNC specific */ + + text_color_set(DW_COLOR_INFO); + dw_printf ("KISS protocol set hardware - ignored.\n"); + break; + + case 15: /* End KISS mode, port should be 15. */ + /* Ignore it. */ + text_color_set(DW_COLOR_INFO); + dw_printf ("KISS protocol end KISS mode\n"); + break; + + default: + text_color_set(DW_COLOR_DEBUG); + dw_printf ("KISS Invalid command %d\n", cmd); + kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len); + break; + } + +} /* end kiss_process_msg */ + + +/*------------------------------------------------------------------- + * + * 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. + * + *--------------------------------------------------------------------*/ + + +/* In server.c. Should probably move to some misc. function file. */ + +void hex_dump (unsigned char *p, int len); + + + +void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len) +{ + 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" }; + + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n"); + + if (special == NULL) { + dw_printf ("%s %s %s KISS client application, port %d, total length = %d\n", + prefix[(int)fromto], function[pmsg[0] & 0xf], direction[(int)fromto], + (pmsg[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); + } + hex_dump ((char*)pmsg, msg_len); + +} /* end kiss_debug_print */ + + +/* end kiss_frame.c */ diff --git a/kiss_frame.h b/kiss_frame.h new file mode 100644 index 0000000..fa83bd2 --- /dev/null +++ b/kiss_frame.h @@ -0,0 +1,47 @@ + +/* kiss_frame.h */ + + +/* + * Special characters used by SLIP protocol. + */ + +#define FEND 0xC0 +#define FESC 0xDB +#define TFEND 0xDC +#define TFESC 0xDD + + + +enum kiss_state_e { + KS_SEARCHING, /* Looking for FEND to start KISS frame. */ + KS_COLLECTING, /* In process of collecting KISS frame. */ + KS_ESCAPE }; /* FESC found in frame. */ + +#define MAX_KISS_LEN 2048 /* Spec calls for at least 1024. */ + +#define MAX_NOISE_LEN 100 + +typedef struct kiss_frame_s { + + enum kiss_state_e state; + + unsigned char kiss_msg[MAX_KISS_LEN]; + int kiss_len; + + unsigned char noise[MAX_NOISE_LEN]; + int noise_len; + +} kiss_frame_t; + + + +int kiss_frame (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int)); + +void kiss_process_msg (kiss_frame_t *kf, int debug); + +typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t; + +void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len); + +/* end kiss_frame.h */ \ No newline at end of file diff --git a/kissnet.c b/kissnet.c new file mode 100644 index 0000000..d1b6d9b --- /dev/null +++ b/kissnet.c @@ -0,0 +1,671 @@ +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * 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. Always full duplex. + * + * 6 SetHardware TNC specific. Ignored. + * + * 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. + * Cygwin: Can use either one. + */ + + +#if __WIN32__ +#include +#define _WIN32_WINNT 0x0501 +#include +#else +#include +#include +#include +#include +#include +#endif + +#include +#include +#include + +#include + + +#include "direwolf.h" +#include "tq.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "audio.h" +#include "kissnet.h" +#include "kiss_frame.h" +#include "xmit.h" + + +static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */ + + +static int client_sock; /* 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 num_channels; /* Number of radio ports. */ + + +static void * connect_listen_thread (void *arg); +static void * 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 + * + * 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) +{ +#if __WIN32__ + HANDLE connect_listen_th; + HANDLE cmd_listen_th; +#else + pthread_t connect_listen_tid; + pthread_t cmd_listen_tid; +#endif + int e; + int kiss_port = mc->kiss_port; + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("kissnet_init ( %d )\n", kiss_port); +#endif + + memset (&kf, 0, sizeof(kf)); + + client_sock = -1; + num_channels = mc->num_channels; + +/* + * This waits for a client to connect and sets client_sock. + */ +#if __WIN32__ + connect_listen_th = _beginthreadex (NULL, 0, connect_listen_thread, (void *)kiss_port, 0, NULL); + if (connect_listen_th == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not create KISS socket connect listening thread\n"); + return; + } +#else + e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(long)kiss_port); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Could not create KISS socket connect listening thread"); + return; + } +#endif + +/* + * This reads messages from client when client_sock is valid. + */ +#if __WIN32__ + cmd_listen_th = _beginthreadex (NULL, 0, kissnet_listen_thread, NULL, 0, NULL); + if (cmd_listen_th == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not create KISS socket command listening thread\n"); + return; + } +#else + e = pthread_create (&cmd_listen_tid, NULL, kissnet_listen_thread, NULL); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Could not create KISS socket command listening thread"); + return; + } +#endif +} + + +/*------------------------------------------------------------------- + * + * Name: connect_listen_thread + * + * Purpose: Wait for a connection request from an application. + * + * Inputs: arg - TCP port for server. + * Main program has default of 8001 but allows + * an alternative to be specified on the command line + * + * Outputs: client_sock - File descriptor for communicating with client app. + * + * Description: Wait for connection request from client and establish + * communication. + * Note that the client can go away and come back again and + * re-establish communication without restarting this application. + * + *--------------------------------------------------------------------*/ + +static void * connect_listen_thread (void *arg) +{ +#if __WIN32__ + + struct addrinfo hints; + struct addrinfo *ai = NULL; + int err; + char kiss_port_str[12]; + + SOCKET listen_sock; + WSADATA wsadata; + + sprintf (kiss_port_str, "%d", (int)(long)arg); +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("DEBUG: kissnet port = %d = '%s'\n", (int)(long)arg, kiss_port_str); +#endif + err = WSAStartup (MAKEWORD(2,2), &wsadata); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("WSAStartup failed: %d\n", err); + return (NULL); + } + + 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 (NULL); + } + + memset (&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE; + + err = getaddrinfo(NULL, kiss_port_str, &hints, &ai); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("getaddrinfo failed: %d\n", err); + //sleep (1); + WSACleanup(); + return (NULL); + } + + listen_sock= socket(ai->ai_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 (NULL); + } + +#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()); + freeaddrinfo(ai); + closesocket(listen_sock); + WSACleanup(); + return (NULL); + } + + 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) { + + while (client_sock > 0) { + SLEEP_SEC(1); /* Already connected. Try again later. */ + } + +#define QUEUE_SIZE 5 + + if(listen(listen_sock,QUEUE_SIZE) == SOCKET_ERROR) + { + text_color_set(DW_COLOR_ERROR); + dw_printf("Listen failed with error: %d\n", WSAGetLastError()); + return (NULL); + } + + text_color_set(DW_COLOR_INFO); + dw_printf("Ready to accept KISS client application on port %s ...\n", kiss_port_str); + + client_sock = accept(listen_sock, NULL, NULL); + + if (client_sock == -1) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Accept failed with error: %d\n", WSAGetLastError()); + closesocket(listen_sock); + WSACleanup(); + return (NULL); + } + + text_color_set(DW_COLOR_INFO); + dw_printf("\nConnected to KISS client application ...\n\n"); + + } + +#else + + 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; + + 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); + } + + 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); + perror ("connect_listen_thread: Bind failed"); + return (NULL); + } + + getsockname( listen_sock, (struct sockaddr *)(&sockaddr), &sockaddr_size); + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf("opened KISS socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) ); +#endif + + while (1) { + + while (client_sock > 0) { + SLEEP_SEC(1); /* Already connected. Try again later. */ + } + +#define QUEUE_SIZE 5 + + if(listen(listen_sock,QUEUE_SIZE) == -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 client application on port %d ...\n", kiss_port); + + client_sock = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size); + + text_color_set(DW_COLOR_INFO); + dw_printf("\nConnected to KISS client application ...\n\n"); + + } +#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. + * + * fbuf - Address of raw received frame buffer + * or a text string. + * + * flen - Number of bytes for AX.25 frame. + * or -1 for a text string. + * + * + * Description: Send message to client if connected. + * Disconnect from client, and notify user, if any error. + * + *--------------------------------------------------------------------*/ + + +void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen) +{ + unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN]; + int kiss_len; + int j; + int err; + + + if (client_sock == -1) { + return; + } + if (flen < 0) { + flen = strlen((char*)fbuf); + if (kiss_debug) { + kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen); + } + strcpy ((char *)kiss_buff, (char *)fbuf); + kiss_len = strlen((char *)kiss_buff); + } + else { + + kiss_len = 0; + kiss_buff[kiss_len++] = FEND; + kiss_buff[kiss_len++] = chan << 4; + + for (j=0; j= 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: kissnet_listen_thread + * + * Purpose: Wait for KISS messages from an application. + * + * Inputs: arg - Not used. + * + * Outputs: client_sock - 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 (void) +{ + unsigned char ch; + int n; + + while (1) { + + while (client_sock <= 0) { + SLEEP_SEC(1); /* Not connected. Try again later. */ + } + + /* Just get one byte at a time. */ + + n = read_from_socket (client_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 == 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 ("\nError reading KISS byte from clent application. Closing connection.\n\n"); +#if __WIN32__ + closesocket (client_sock); +#else + close (client_sock); +#endif + client_sock = -1; + } +} + + + +static void * kissnet_listen_thread (void *arg) +{ + unsigned char ch; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("kissnet_listen_thread ( socket = %d )\n", client_sock); +#endif + + while (1) { + ch = kiss_get(); + + if (kiss_frame (&kf, ch, kiss_debug, kissnet_send_rec_packet)) { + kiss_process_msg (&kf, kiss_debug); + } + } /* while (1) */ + + return (NULL); /* to suppress compiler warning. */ + +} /* end kissnet_listen_thread */ + +/* end kissnet.c */ diff --git a/kissnet.h b/kissnet.h new file mode 100644 index 0000000..361f435 --- /dev/null +++ b/kissnet.h @@ -0,0 +1,21 @@ + +/* + * 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, unsigned char *fbuf, int flen); + +void kiss_net_set_debug (int n); + + +/* end kissnet.h */ diff --git a/latlong.c b/latlong.c new file mode 100644 index 0000000..2cb2432 --- /dev/null +++ b/latlong.c @@ -0,0 +1,281 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 . +// + + +/*------------------------------------------------------------------ + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "direwolf.h" +#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] + * + * Returns: None + * + *----------------------------------------------------------------*/ + +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.; + + sprintf (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] + * + * 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.; + + sprintf (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. + * + *----------------------------------------------------------------*/ + +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. + * + *----------------------------------------------------------------*/ + +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; +} diff --git a/latlong.h b/latlong.h new file mode 100644 index 0000000..f0748c3 --- /dev/null +++ b/latlong.h @@ -0,0 +1,13 @@ + +/* 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); diff --git a/ll2utm.c b/ll2utm.c new file mode 100644 index 0000000..409dae3 --- /dev/null +++ b/ll2utm.c @@ -0,0 +1,55 @@ +/* Latitude / Longitude to UTM conversion */ + +#include +#include + +#include "LatLong-UTMconversion.h" + + +static void usage(); + + +void main (int argc, char *argv[]) +{ + double easting; + double northing; + double lat, lon; + char zone[8]; + + if (argc != 3) usage(); + + + lat = atof(argv[1]); + if (lat < -90 || lat > 90) { + fprintf (stderr, "Latitude value is out of range.\n\n"); + usage(); + } + + lon = atof(argv[2]); + if (lon < -180 || lon > 180) { + fprintf (stderr, "Longitude value is out of range.\n\n"); + usage(); + } + + LLtoUTM (WSG84, lat, lon, &northing, &easting, zone); + + printf ("zone = %s, easting = %.0f, northing = %.0f\n", zone, easting, northing); +} + + +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); +} \ No newline at end of file diff --git a/misc/README-dire-wolf.txt b/misc/README-dire-wolf.txt new file mode 100644 index 0000000..126a296 --- /dev/null +++ b/misc/README-dire-wolf.txt @@ -0,0 +1,8 @@ +These are part of the standard C library for Linux and Cygwin. +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 + /usr/src/cygwin-1.7.10-1/newlib/libc/string/strtok_r.c + diff --git a/misc/strcasestr.c b/misc/strcasestr.c new file mode 100644 index 0000000..a418549 --- /dev/null +++ b/misc/strcasestr.c @@ -0,0 +1,64 @@ +/*- + * 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(s, find) + const char *s, *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); +} diff --git a/misc/strsep.c b/misc/strsep.c new file mode 100644 index 0000000..7d764d0 --- /dev/null +++ b/misc/strsep.c @@ -0,0 +1,22 @@ +/* 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); +} diff --git a/misc/strtok_r.c b/misc/strtok_r.c new file mode 100644 index 0000000..a86de79 --- /dev/null +++ b/misc/strtok_r.c @@ -0,0 +1,102 @@ +/* + * 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); +} diff --git a/morse.c b/morse.c new file mode 100644 index 0000000..bc7ea80 --- /dev/null +++ b/morse.c @@ -0,0 +1,381 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 . +// + + +/*------------------------------------------------------------------ + * + * Module: morse.c + * + * Purpose: Generate audio for morse code. + * + * Description: + * + * Reference: + * + *---------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include + +#if __WIN32__ +#include +#else +#include +#include +#include +#endif + +#include "direwolf.h" +#include "textcolor.h" +#include "audio.h" +#include "ptt.h" + + +#define WPM 10 +#define TIME_UNITS_TO_MS(tu,wpm) (((tu)*1200)/(wpm)) + + +// TODO : should be in .h file. + +/* + * Delay from PTT on to start of first character. + * Currently the only anticipated use for this is + * APRStt responses. In this case, we want an adequate + * delay for someone to press the # button, release + * the PTT button, and start listening for a response. + */ +#define MORSE_TXDELAY_MS 1500 + +/* + * Delay from end of last character to PTT off. + * Avoid chopping off the last element. + */ +#define MORSE_TXTAIL_MS 200 + + +static const struct morse_s { + char ch; + char enc[7]; +} 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', "-----" }, + { '-', "-...-" }, + { '.', ".-.-.-" }, + { ',', "--..--" }, + { '?', "..--.." }, + { '/', "-..-." } +}; + +#define NUM_MORSE (sizeof(morse) / sizeof(struct morse_s)) + +static void morse_tone (int tu); +static void morse_quiet (int tu); +static int morse_lookup (int ch); +static int morse_units_ch (int ch); +static int morse_units_str (char *str); + + + +/*------------------------------------------------------------------- + * + * 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; + 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 (1); + time_units++; + } + else { + morse_tone (3); + time_units += 3; + } + if (e[1] != '\0') { + morse_quiet (1); + time_units++; + } + } + } + else { + morse_quiet (1); + time_units++; + } + if (p[1] != '\0') { + morse_quiet (3); + time_units += 3; + } + } + + 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)); + } + + + return (txdelay + + TIME_UNITS_TO_MS(time_units, wpm) + + txtail); + +} /* end morse_send */ + + + +/*------------------------------------------------------------------- + * + * Name: morse_tone + * + * Purpose: Generate tone for specified number of time units. + * + * Inputs: tu - Number of time units. + * + *--------------------------------------------------------------------*/ + +static void morse_tone (int tu) { + int num_cycles; + int n; + + for (n=0; n. +// + + + +/*------------------------------------------------------------------ + * + * 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. + * + *------------------------------------------------------------------*/ + +#define DIGIPEATER_C + + +#include +#include +#include +#include +#include + +#include "direwolf.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "multi_modem.h" +#include "demod.h" +#include "hdlc_rec.h" +#include "hdlc_rec2.h" + + +// Properties of the radio channels. + +static struct audio_s modem; + + +// Candidates for further processing. + +static struct { + + packet_t packet_p; + int alevel; + retry_t retries; + int age; + unsigned int crc; + int score; + +} candidate[MAX_CHANS][MAX_SUBCHANS]; + +static unsigned int crc_of_last_to_app[MAX_CHANS]; + +#define PROCESS_AFTER_BITS 2 + +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 *pmodem) +{ + int chan; + +/* + * Save parameters for later use. + */ + memcpy (&modem, pmodem, sizeof(modem)); + + memset (candidate, 0, sizeof(candidate)); + + demod_init (pmodem); + hdlc_rec_init (pmodem); + + for (chan=0; chan 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/decoder 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, unsigned char *fbuf, int flen, int alevel, retry_t retries) +{ + packet_t pp; + + + assert (chan >= 0 && chan < MAX_CHANS); + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + + pp = ax25_from_frame (fbuf, flen, alevel); + + if (pp == NULL) { + return; /* oops! why would it fail? */ + } + +/* + * If single modem, push it thru and forget about all this foolishness. + */ + if (modem.num_subchan[chan] == 1) { + app_process_rec_packet (chan, subchan, pp, alevel, retries, ""); + return; + } + +/* + * Special handing for two separated bit errors. + * See description earlier. + * + * Not combined with others to find the best score. + * Either pass it along or drop if duplicate. + */ + + if (retries == RETRY_TWO_SEP) { + int mycrc; + char spectrum[MAX_SUBCHANS+1]; + + memset (spectrum, 0, sizeof(spectrum)); + memset (spectrum, '_', (size_t)modem.num_subchan[chan]); + spectrum[subchan] = '.'; + + mycrc = ax25_m_m_crc(pp); + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n%s\n%d.%d: ptr=%p, retry=%d, age=, crc=%04x, score= \n", + spectrum, chan, subchan, pp, (int)retries, mycrc); +#endif + if (mycrc == crc_of_last_to_app[chan]) { + /* Same as last one. Drop it. */ + ax25_delete (pp); +#if DEBUG + dw_printf ("Drop duplicate.\n"); +#endif + return; + } + +#if DEBUG + dw_printf ("Send the best one along.\n"); +#endif + app_process_rec_packet (chan, subchan, pp, alevel, retries, spectrum); + crc_of_last_to_app[chan] = mycrc; + return; + } + + +/* + * Otherwise, save them up for a few bit times so we can pick the best. + */ + if (candidate[chan][subchan].packet_p != NULL) { + /* Oops! Didn't expect it to be there. */ + ax25_delete (candidate[chan][subchan].packet_p); + candidate[chan][subchan].packet_p = NULL; + } + + candidate[chan][subchan].packet_p = pp; + candidate[chan][subchan].alevel = alevel; + candidate[chan][subchan].retries = retries; + candidate[chan][subchan].age = 0; + candidate[chan][subchan].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. + * + *--------------------------------------------------------------------*/ + + +static void pick_best_candidate (int chan) +{ + int subchan; + int best_subchan, best_score; + char spectrum[MAX_SUBCHANS+1]; + int k; + + memset (spectrum, 0, sizeof(spectrum)); + + for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { + + /* Build the spectrum display. */ + + if (candidate[chan][subchan].packet_p == NULL) { + spectrum[subchan] = '_'; + } + else if (candidate[chan][subchan].retries == RETRY_NONE) { + spectrum[subchan] = '|'; + } + else if (candidate[chan][subchan].retries == RETRY_SINGLE) { + spectrum[subchan] = ':'; + } + else { + spectrum[subchan] = '.'; + } + + /* Begining score depends on effort to get a valid frame CRC. */ + + candidate[chan][subchan].score = 5000 - ((int)candidate[chan][subchan].retries * 1000); + + /* Bump it up slightly if others nearby have the same CRC. */ + + for (k = 0; k < modem.num_subchan[chan]; k++) { + if (k != subchan && candidate[chan][k].packet_p != NULL) { + if (candidate[chan][k].crc == candidate[chan][subchan].crc) { + candidate[chan][subchan].score += (MAX_SUBCHANS+1) - abs(subchan-k); + } + } + } + } + + best_subchan = 0; + best_score = 0; + + for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { + if (candidate[chan][subchan].packet_p != NULL) { + if (candidate[chan][subchan].score > best_score) { + best_score = candidate[chan][subchan].score; + best_subchan = subchan; + } + } + } + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n%s\n", spectrum); + + for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { + + if (candidate[chan][subchan].packet_p == NULL) { + dw_printf ("%d.%d: ptr=%p\n", chan, subchan, + candidate[chan][subchan].packet_p); + } + else { + dw_printf ("%d.%d: ptr=%p, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, subchan, + candidate[chan][subchan].packet_p, + (int)(candidate[chan][subchan].retries), + candidate[chan][subchan].age, + candidate[chan][subchan].crc, + candidate[chan][subchan].score, + subchan == best_subchan ? "***" : ""); + } + } +#endif + +/* + * send the best one along. + */ + app_process_rec_packet (chan, best_subchan, + candidate[chan][best_subchan].packet_p, + candidate[chan][best_subchan].alevel, + (int)(candidate[chan][best_subchan].retries), + spectrum); + crc_of_last_to_app[chan] = candidate[chan][best_subchan].crc; + + /* Someone else will delete so don't do it below. */ + candidate[chan][best_subchan].packet_p = NULL; + + /* Clear out in preparation for next time. */ + + for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { + if (candidate[chan][subchan].packet_p != NULL) { + ax25_delete (candidate[chan][subchan].packet_p); + candidate[chan][subchan].packet_p = NULL; + } + candidate[chan][subchan].alevel = 0; + candidate[chan][subchan].retries = 0; + candidate[chan][subchan].age = 0; + candidate[chan][subchan].crc = 0; + } +} + + +/* end multi_modem.c */ diff --git a/multi_modem.h b/multi_modem.h new file mode 100644 index 0000000..f1dcdd9 --- /dev/null +++ b/multi_modem.h @@ -0,0 +1,20 @@ +/* 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); + +void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int flen, int level, retry_t retries); + + +#endif diff --git a/ptt.c b/ptt.c new file mode 100644 index 0000000..105fe09 --- /dev/null +++ b/ptt.c @@ -0,0 +1,690 @@ +// +// 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 . + + + +/*------------------------------------------------------------------ + * + * Module: ptt.c + * + * Purpose: Activate the push to talk (PTT) signal to turn on transmitter. + * + * 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 unix version suitable for either Cygwin or Linux. + * + * Version 0.9: Add ability to use GPIO pins on Linux. + * + * References: http://www.robbayer.com/files/serial-win.pdf + * + * https://www.kernel.org/doc/Documentation/gpio.txt + * + *---------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include + +#if __WIN32__ +#include +#else +#include +#include +#include +#include +#include +#include +#include + +/* So we can have more common code for fd. */ +typedef int HANDLE; +#define INVALID_HANDLE_VALUE (-1) + +#endif + +#include "direwolf.h" +#include "textcolor.h" +#include "audio.h" +#include "ptt.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); } + +#endif + + +/*------------------------------------------------------------------- + * + * Name: ptt_init + * + * Purpose: Open serial port(s) used for PTT signals and set to proper state. + * + * Inputs: modem - Structure with communication parameters. + * + * + * Outputs: Remember required information for future use. + * + * Description: + * + *--------------------------------------------------------------------*/ + +static int ptt_num_channels; + +static ptt_method_t ptt_method[MAX_CHANS]; /* 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. */ + +static char ptt_device[MAX_CHANS][20]; /* Name of serial port device. */ + /* e.g. COM1 or /dev/ttyS0. */ + +static ptt_line_t ptt_line[MAX_CHANS]; /* RTS or DTR when using serial port. */ + +static int ptt_gpio[MAX_CHANS]; /* GPIO number. Only used for Linux. */ + /* Valid only when ptt_method is PTT_METHOD_GPIO. */ + +static int ptt_invert[MAX_CHANS]; /* Invert the signal. */ + /* Normally higher voltage means transmit. */ + +static HANDLE ptt_fd[MAX_CHANS]; /* Serial port handle or fd. */ + /* Could be the same for two channels */ + /* if using both RTS and DTR. */ + + + +void ptt_init (struct audio_s *p_modem) +{ + int ch; + HANDLE fd; +#if __WIN32__ +#else + int using_gpio; +#endif + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("ptt_init ( ... )\n"); +#endif + +/* + * First copy everything from p_modem to local variables + * so it is available for later use. + * + * Maybe all the PTT stuff should have its own structure. + */ + + ptt_num_channels = p_modem->num_channels; + + assert (ptt_num_channels >= 1 && ptt_num_channels <= MAX_CHANS); + + for (ch=0; chptt_method[ch]; + strcpy (ptt_device[ch], p_modem->ptt_device[ch]); + ptt_line[ch] = p_modem->ptt_line[ch]; + ptt_gpio[ch] = p_modem->ptt_gpio[ch]; + ptt_invert[ch] = p_modem->ptt_invert[ch]; + ptt_fd[ch] = INVALID_HANDLE_VALUE; +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("ch=%d, method=%d, device=%s, line=%d, gpio=%d, invert=%d\n", + ch, + ptt_method[ch], + ptt_device[ch], + ptt_line[ch], + ptt_gpio[ch], + ptt_invert[ch]); +#endif + } + +/* + * Set up serial ports. + */ + + for (ch=0; ch /dev/ttyS0, etc. */ + + if (strncasecmp(ptt_device[ch], "COM", 3) == 0) { + int n = atoi (ptt_device[ch] + 3); + text_color_set(DW_COLOR_INFO); + dw_printf ("Converted PTT device '%s'", ptt_device[ch]); + if (n < 1) n = 1; + sprintf (ptt_device[ch], "/dev/ttyS%d", n-1); + dw_printf (" to Linux equivalent '%s'\n", ptt_device[ch]); + } +#endif + /* Can't open the same device more than once so we */ + /* need more logic to look for the case of both radio */ + /* channels using different pins of the same COM port. */ + + /* TODO: Needs to be rewritten in a more general manner */ + /* if we ever have more than 2 channels. */ + + if (ch == 1 && strcmp(ptt_device[0],ptt_device[1]) == 0) { + fd = ptt_fd[0]; + } + else { +#if __WIN32__ + + fd = CreateFile(ptt_device[ch], + GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); +#else + + /* O_NONBLOCK added in version 0.9. */ + /* https://bugs.launchpad.net/ubuntu/+source/linux/+bug/661321/comments/12 */ + + fd = open (ptt_device[ch], O_RDONLY | O_NONBLOCK); +#endif + } + + if (fd != INVALID_HANDLE_VALUE) { + ptt_fd[ch] = 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", + ptt_device[ch], ch); +#if __WIN32__ +#else + dw_printf ("%s\n", strerror(errno)); +#endif + /* Don't try using it later if device open failed. */ + + ptt_method[ch] = PTT_METHOD_NONE; + } + +/* + * Set initial state of PTT off. + * ptt_set will invert output signal if appropriate. + */ + ptt_set (ch, 0); + + } /* if serial method. */ + + } /* For each channel. */ + + +/* + * Set up GPIO - for Linux only. + */ + +#if __WIN32__ +#else + +/* + * Does any channel use GPIO? + */ + + using_gpio = 0; + for (ch=0; ch ../../devices/virtual/gpio/gpiochip0 + * --w------- 1 root root 4096 Aug 20 07:59 unexport + */ + if (geteuid() != 0) { + if ( ! (finfo.st_mode & S_IWOTH)) { + + /* Try to change protection. */ + system ("sudo chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport"); + + if (stat("/sys/class/gpio/export", &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 & S_IWOTH)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Permissions do not allow ordinary users to access GPIO.\n"); + dw_printf ("Log in as root and type this command:\n"); + dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n"); + exit (1); + } + } + } + } +/* + * We should now be able to create the device nodes for + * the pins we want to use. + */ + + for (ch=0; ch= 0 && chan < MAX_CHANS); + +/* + * Using serial port? + */ + if (ptt_method[chan] == PTT_METHOD_SERIAL && + ptt_fd[chan] != INVALID_HANDLE_VALUE) { + + if (ptt_line[chan] == PTT_LINE_RTS) { + + if (ptt) { + RTS_ON(ptt_fd[chan]); + } + else { + RTS_OFF(ptt_fd[chan]); + } + } + else if (ptt_line[chan] == PTT_LINE_DTR) { + + if (ptt) { + DTR_ON(ptt_fd[chan]); + } + else { + DTR_OFF(ptt_fd[chan]); + } + } + } + +/* + * Using GPIO? + */ + +#if __WIN32__ +#else + + if (ptt_method[chan] == PTT_METHOD_GPIO) { + int fd; + char stemp[80]; + + sprintf (stemp, "/sys/class/gpio/gpio%d/value", ptt_gpio[chan]); + + fd = open(stemp, O_WRONLY); + if (fd < 0) { + int e = errno; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Error opening %s to set PTT signal.\n", stemp); + dw_printf ("%s\n", strerror(e)); + return; + } + + sprintf (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 PTT\n", ptt_gpio[chan]); + dw_printf ("%s\n", strerror(e)); + } + close (fd); + + } +#endif + + +} /* end ptt_set */ + + + +/*------------------------------------------------------------------- + * + * Name: ptt_term + * + * Purpose: Make sure PTT is turned off when we exit. + * + * Inputs: none + * + * Description: + * + *--------------------------------------------------------------------*/ + +void ptt_term (void) +{ + int n; + + for (n = 0; n < ptt_num_channels; n++) { + ptt_set (n, 0); + if (ptt_fd[n] != INVALID_HANDLE_VALUE) { +#if __WIN32__ + CloseHandle (ptt_fd[n]); +#else + close(ptt_fd[n]); +#endif + ptt_fd[n] = INVALID_HANDLE_VALUE; + } + } +} + + + + +/* + * Quick stand-alone test for above. + * + * gcc -DTEST -o ptest ptt.c ; ./ptest + * + */ + + +#if TEST + +void text_color_set (dw_color_t c) { } + +main () +{ + struct audio_s modem; + int n; + int chan; + + memset (&modem, 0, sizeof(modem)); + + modem.num_channels = 2; + + modem.ptt_method[0] = PTT_METHOD_SERIAL; + //strcpy (modem.ptt_device[0], "COM1"); + strcpy (modem.ptt_device[0], "/dev/ttyUSB0"); + modem.ptt_line[0] = PTT_LINE_RTS; + + modem.ptt_method[1] = PTT_METHOD_SERIAL; + //strcpy (modem.ptt_device[1], "COM1"); + strcpy (modem.ptt_device[1], "/dev/ttyUSB0"); + modem.ptt_line[1] = PTT_LINE_DTR; + + +/* initialize - both off */ + + ptt_init (&modem); + + 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 (chan, 1); + SLEEP_SEC(1); + ptt_set (chan, 0); + SLEEP_SEC(1); + } + + dw_printf ("turn on DTR a few times...\n"); + + chan = 1; + for (n=0; n<3; n++) { + ptt_set (chan, 1); + SLEEP_SEC(1); + ptt_set (chan, 0); + SLEEP_SEC(1); + } + + ptt_term(); + +/* Same thing again but invert RTS. */ + + modem.ptt_invert[0] = 1; + + ptt_init (&modem); + + SLEEP_SEC(2); + + dw_printf ("INVERTED - RTS a few times...\n"); + + chan = 0; + for (n=0; n<3; n++) { + ptt_set (chan, 1); + SLEEP_SEC(1); + ptt_set (chan, 0); + SLEEP_SEC(1); + } + + dw_printf ("turn on DTR a few times...\n"); + + chan = 1; + for (n=0; n<3; n++) { + ptt_set (chan, 1); + SLEEP_SEC(1); + ptt_set (chan, 0); + SLEEP_SEC(1); + } + + ptt_term (); + + +/* Test GPIO */ + +#if __WIN32__ +#else + + memset (&modem, 0, sizeof(modem)); + modem.num_channels = 1; + modem.ptt_method[0] = PTT_METHOD_GPIO; + modem.ptt_gpio[0] = 25; + + dw_printf ("Try GPIO %d a few times...\n", modem.ptt_gpio[0]); + + ptt_init (&modem); + + SLEEP_SEC(2); + chan = 0; + for (n=0; n<3; n++) { + ptt_set (chan, 1); + SLEEP_SEC(1); + ptt_set (chan, 0); + SLEEP_SEC(1); + } + + ptt_term (); +#endif + +} + +#endif + +/* end ptt.c */ + + + diff --git a/ptt.h b/ptt.h new file mode 100644 index 0000000..014b8c0 --- /dev/null +++ b/ptt.h @@ -0,0 +1,23 @@ + + +#ifndef PTT_H +#define PTT_H 1 + + +#include "audio.h" /* for struct audio_s */ + + +void ptt_init (struct audio_s *p_modem); + +void ptt_set (int chan, int ptt); + +void ptt_term (void); + + +#endif + + +/* end ptt.h */ + + + diff --git a/pttest.c b/pttest.c new file mode 100644 index 0000000..82c7a81 --- /dev/null +++ b/pttest.c @@ -0,0 +1,287 @@ + + +/*------------------------------------------------------------------ + * + * Module: pttest.c + * + * Purpose: Test for pseudo terminal. + * + * Input: + * + * Outputs: + * + * Description: The protocol name is an acronym for Keep it Simple Stupid. + * You would expect it to be simple but this caused a lot + * of effort on Linux. The problem is that writes to a pseudo + * terminal eventually block if nothing at the other end + * is removing the data. This causes the application to + * hang and stop receiving after a while. + * + * This is an attempt to demonstrate the problem in a small + * test case and, hopefully, find a solution. + * + * + * Instructions: + * First compile like this: + * + * + * Run it, noting the name of pseudo terminal, + * typically /dev/pts/1. + * + * In another window, type: + * + * cat /dev/pts/1 + * + * This should run "forever" as long as something is + * reading from the slave side of the pseudo terminal. + * + * If nothing is removing the data, this runs for a while + * and then blocks on the write. + * For this particular application we just want to discard + * excess data if no one is listening. + * + * + * Failed experiments: + * + * Notice that ??? always returns 0 for amount of data + * in the queue. + * Define TEST1 to make the device non-blocking. + * Write fails entirely. + * + * Define TEST2 to use a different method. + * Also fails in the same way. + * + * + *---------------------------------------------------------------*/ + +#include +#include + + +#define __USE_XOPEN2KXSI 1 +#define __USE_XOPEN 1 +//#define __USE_POSIX 1 +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +//#include "direwolf.h" +//#include "tq.h" +//#include "ax25_pad.h" +//#include "textcolor.h" +//#include "kiss.h" +//#include "xmit.h" + + + +static MYFDTYPE pt_master_fd = -1; /* File descriptor for my end. */ + +static MYFDTYPE pt_slave_fd = -1; /* File descriptor for pseudo terminal */ + /* for use by application. */ +static int msg_number; +static int total_bytes; + + +/*------------------------------------------------------------------- + * + * Name: kiss_init + * + * Purpose: Set up a pseudo terminal acting as a virtual KISS TNC. + * + * + * Inputs: mc->nullmodem - name of device for our end of nullmodem. + * + * 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 kiss_open_pt (void); + + +main (int argc, char *argv) +{ + + pt_master_fd = kiss_open_pt (); + printf ("msg total qcount\n"); + + msg_number = 0; + total_bytes = 0; + +#endif +} + + +/* + * Returns fd for master side of pseudo terminal or MYFDERROR for error. + */ + + +static int kiss_open_pt (void) +{ + int fd; + char *slave_device; + struct termios ts; + int e; + int flags; + + fd = posix_openpt(O_RDWR|O_NOCTTY); + + if (fd == -1 + || grantpt (fd) == -1 + || unlockpt (fd) == -1 + || (slave_device = ptsname (fd)) == NULL) { + text_color_set(DW_COLOR_ERROR); + printf ("ERROR - Could not create pseudo terminal.\n"); + return (-1); + } + + + e = tcgetattr (fd, &ts); + if (e != 0) { + 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); + printf ("Can't set pseudo terminal attributes, err=%d\n", e); + perror ("pt tcsetattr"); + } + +/* + * After running for a while on Linux, the write eventually + * blocks if no one is reading from the other side of + * the pseudo terminal. We get stuck on the kiss data + * write and reception stops. + * + * I tried using ioctl(,TIOCOUTQ,) to see how much was in + * the queue but that always returned zero. (Ubuntu) + * + * Let's try using non-blocking writes and see if we get + * the EWOULDBLOCK status instead of hanging. + */ + +#if TEST1 + // this is worse. all writes fail. errno = ? bad file descriptor + flags = fcntl(fd, F_GETFL, 0); + e = fcntl (fd, F_SETFL, flags | O_NONBLOCK); + if (e != 0) { + printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno); + perror ("pt fcntl"); + } +#endif +#if TEST2 + // same + flags = 1; + e = ioctl (fd, FIONBIO, &flags); + if (e != 0) { + printf ("Can't set pseudo terminal to nonblocking, ioctl returns %d, errno = %d\n", e, errno); + perror ("pt ioctl"); + } +#endif + + printf("Virtual KISS TNC is available on %s\n", slave_device); + + + // Sample code shows this. Why would we open it here? + + // pt_slave_fd = open(slave_device, O_RDWR|O_NOCTTY); + + + return (fd); +} + + + +/*------------------------------------------------------------------- + * + * Name: kiss_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. + * Not including the FCS. + * + * + * 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 kiss_send_rec_packet (void) +{ + + + char kiss_buff[100]; + + int kiss_len; + int q_count = 123; + + + int j; + + strcpy (kiss_buff, "The quick brown fox jumps over the lazy dog.\n"); + kiss_len = strlen(kiss_buff); + + + if (pt_master_fd != MYFDERROR) { + int err; + + msg_number++; + total_bytes += kiss_len; + +//#if DEBUG + printf ("%3d %5d %5d\n", msg_number, total_bytes, q_count); +//#endif + err = write (pt_master_fd, kiss_buff, kiss_len); + + if (err == -1 && errno == EWOULDBLOCK) { +//#if DEBUG + printf ("Discarding message because write would block.\n"); +//#endif + } + else if (err != kiss_len) + { + printf ("\nError sending message on pseudo terminal. len=%d, write returned %d, errno = %d\n\n", + kiss_len, err, errno); + perror ("pt write"); + } + + } +//#endif + + + +} + + +/* end pttest.c */ diff --git a/rdq.c b/rdq.c new file mode 100644 index 0000000..3bfa4e3 --- /dev/null +++ b/rdq.c @@ -0,0 +1,453 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011,2012 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 +#include +#include +#include +#include + +#include "direwolf.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "audio.h" +#include "rdq.h" +#include "dedupe.h" + + + +static rrbb_t queue_head; /* Head of linked list for queue. */ + +#if __WIN32__ + +static CRITICAL_SECTION rdq_cs; /* Critical section for updating queues. */ + +static HANDLE wake_up_event; /* Notify try decode again thread when queue not empty. */ + +#else + +static pthread_mutex_t rdq_mutex; /* Critical section for updating queues. */ + +static pthread_cond_t wake_up_cond; /* Notify try decode again thread when queue not empty. */ + +static pthread_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 + +#if __WIN32__ + InitializeCriticalSection (&rdq_cs); +#else + err = pthread_mutex_init (&wake_up_mutex, NULL); + err = pthread_mutex_init (&rdq_mutex, NULL); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("rdq_init: pthread_mutex_init err=%d", err); + perror (""); + exit (1); + } +#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 +#if __WIN32__ + EnterCriticalSection (&rdq_cs); +#else + err = pthread_mutex_lock (&rdq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("rdq_append: pthread_mutex_lock err=%d", err); + perror (""); + exit (1); + } +#endif + + //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); + } + + +#if __WIN32__ + LeaveCriticalSection (&rdq_cs); +#else + err = pthread_mutex_unlock (&rdq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("rdq_append: pthread_mutex_unlock err=%d", err); + perror (""); + exit (1); + } +#endif +#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 + err = pthread_mutex_lock (&wake_up_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("rdq_append: 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 ("rdq_append: 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 ("rdq_append: pthread_mutex_unlock wu err=%d", err); + perror (""); + exit (1); + } +#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 + +#if __WIN32__ + EnterCriticalSection (&rdq_cs); +#else + err = pthread_mutex_lock (&rdq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("rdq_wait_while_empty: pthread_mutex_lock err=%d", err); + perror (""); + exit (1); + } +#endif + +#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; + + +#if __WIN32__ + LeaveCriticalSection (&rdq_cs); +#else + err = pthread_mutex_unlock (&rdq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("rdq_wait_while_empty: pthread_mutex_unlock err=%d", err); + perror (""); + exit (1); + } +#endif +#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 + err = pthread_mutex_lock (&wake_up_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("rdq_wait_while_empty: pthread_mutex_lock wu err=%d", err); + perror (""); + exit (1); + } + + 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); + } + + err = pthread_mutex_unlock (&wake_up_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("rdq_wait_while_empty: pthread_mutex_unlock wu err=%d", err); + perror (""); + exit (1); + } + +#endif + } + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("rdq_wait_while_empty () returns\n"); +#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; +#ifndef __WIN32__ + int err; +#endif + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("rdq_remove() enter critical section\n"); +#endif + +#if __WIN32__ + EnterCriticalSection (&rdq_cs); +#else + err = pthread_mutex_lock (&rdq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("rdq_remove: pthread_mutex_lock err=%d", err); + perror (""); + exit (1); + } +#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); + } + +#if __WIN32__ + LeaveCriticalSection (&rdq_cs); +#else + err = pthread_mutex_unlock (&rdq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("rdq_remove: pthread_mutex_unlock err=%d", err); + perror (""); + exit (1); + } +#endif + +#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 */ diff --git a/rdq.h b/rdq.h new file mode 100644 index 0000000..b0e430c --- /dev/null +++ b/rdq.h @@ -0,0 +1,28 @@ + +/*------------------------------------------------------------------ + * + * 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 */ diff --git a/redecode.c b/redecode.c new file mode 100644 index 0000000..1db45a8 --- /dev/null +++ b/redecode.c @@ -0,0 +1,252 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011,2012,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 . +// + + + +/*------------------------------------------------------------------ + * + * Module: redecode.c + * + * Purpose: Retry decoding frames that have a bad FCS. + * + * Description: + * + * + * Usage: (1) The main application calls redecode_init. + * + * This will initialize the retry decoding queue + * and create a thread to work on contents of the queue. + * + * (2) The application queues up frames by calling rdq_append. + * + * + * (3) redecode_thread removes raw frames from the queue and + * tries to recover from errors. + * + *---------------------------------------------------------------*/ + +#include +#include +#include +#include +#include + +//#include +#include + +#if __WIN32__ +#include +#endif + +#include "direwolf.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "audio.h" +#include "rdq.h" +#include "redecode.h" +#include "hdlc_send.h" +#include "hdlc_rec2.h" +#include "ptt.h" + + + + + + +#if __WIN32__ +static unsigned redecode_thread (void *arg); +#else +static void * redecode_thread (void *arg); +#endif + + +/*------------------------------------------------------------------- + * + * Name: redecode_init + * + * Purpose: Initialize the process to try fixing bits in frames with bad FCS. + * + * Inputs: none. + * + * Outputs: none. + * + * Description: Initialize the queue to be empty and set up other + * mechanisms for sharing it between different threads. + * + * Start up redecode_thread to actually process the + * raw frames from the queue. + * + *--------------------------------------------------------------------*/ + + + +void redecode_init (void) +{ + //int j; +#if __WIN32__ + HANDLE redecode_th; +#else + pthread_t redecode_tid; + int e; +#endif + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("redecode_init ( ... )\n"); +#endif + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("redecode_init: about to call rdq_init \n"); +#endif + rdq_init (); + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("redecode_init: about to create thread \n"); +#endif + + +#if __WIN32__ + redecode_th = _beginthreadex (NULL, 0, redecode_thread, NULL, 0, NULL); + if (redecode_th == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not create redecode thread\n"); + return; + } +#else + +//TODO: Give thread lower priority. + + e = pthread_create (&redecode_tid, NULL, redecode_thread, (void *)0); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Could not create redecode thread"); + return; + } +#endif + + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("redecode_init: finished \n"); +#endif + + +} /* end redecode_init */ + + + + + +/*------------------------------------------------------------------- + * + * Name: redecode_thread + * + * Purpose: Try to decode frames with a bad FCS. + * + * Inputs: None. + * + * Outputs: + * + * Description: Initialize the queue to be empty and set up other + * mechanisms for sharing it between different threads. + * + * + *--------------------------------------------------------------------*/ + +#if __WIN32__ +static unsigned redecode_thread (void *arg) +#else +static void * redecode_thread (void *arg) +#endif +{ + rrbb_t block; + int blen; + int chan, subchan; + int alevel; + + +#if __WIN32__ + HANDLE tid = GetCurrentThread(); + //int tp; + + //tp = GetThreadPriority (tid); + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Starting redecode thread priority=%d\n", tp); + SetThreadPriority (tid, THREAD_PRIORITY_LOWEST); + //tp = GetThreadPriority (tid); + //dw_printf ("New redecode thread priority=%d\n", tp); +#endif + + while (1) { + + rdq_wait_while_empty (); +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("redecode_thread: woke up\n"); +#endif + + block = rdq_remove (); + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("redecode_thread: rdq_remove() returned %p\n", block); +#endif + +/* Don't expect null ever but be safe. */ + + if (block != NULL) { + + chan = rrbb_get_chan(block); + subchan = rrbb_get_subchan(block); + alevel = rrbb_get_audio_level(block); + blen = rrbb_get_len(block); +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("redecode_thread: begin processing %p, from channel %d, blen=%d\n", block, chan, blen); +#endif + + hdlc_rec2_try_to_fix_later (block, chan, subchan, alevel); + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("redecode_thread: finished processing %p\n", block); +#endif + rrbb_delete (block); + } + + } + + return 0; + +} /* end redecode_thread */ + + + + + +/* end redecode.c */ + + + diff --git a/redecode.h b/redecode.h new file mode 100644 index 0000000..5d0df50 --- /dev/null +++ b/redecode.h @@ -0,0 +1,15 @@ + + +#ifndef REDECODE_H +#define REDECODE_H 1 + +#include "rrbb.h" + + +extern void redecode_init (void); + + +#endif + +/* end redecode.h */ + diff --git a/regex/COPYING b/regex/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/regex/COPYING @@ -0,0 +1,340 @@ + 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. diff --git a/regex/INSTALL b/regex/INSTALL new file mode 100644 index 0000000..8d61b3e --- /dev/null +++ b/regex/INSTALL @@ -0,0 +1,463 @@ +Installing 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. + diff --git a/regex/LICENSES b/regex/LICENSES new file mode 100644 index 0000000..b3b8899 --- /dev/null +++ b/regex/LICENSES @@ -0,0 +1,219 @@ +This 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. */ diff --git a/regex/NEWS b/regex/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/regex/README b/regex/README new file mode 100644 index 0000000..e69de29 diff --git a/regex/README-dire-wolf.txt b/regex/README-dire-wolf.txt new file mode 100644 index 0000000..95d71a2 --- /dev/null +++ b/regex/README-dire-wolf.txt @@ -0,0 +1,6 @@ +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 \ No newline at end of file diff --git a/regex/re_comp.h b/regex/re_comp.h new file mode 100644 index 0000000..4911447 --- /dev/null +++ b/regex/re_comp.h @@ -0,0 +1,26 @@ +/* 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 */ diff --git a/regex/regcomp.c b/regex/regcomp.c new file mode 100644 index 0000000..4cf1688 --- /dev/null +++ b/regex/regcomp.c @@ -0,0 +1,3801 @@ +/* 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) + { + bracket_elem_t start_elem, end_elem; + 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; + } + } +} diff --git a/regex/regex.c b/regex/regex.c new file mode 100644 index 0000000..d2d4f28 --- /dev/null +++ b/regex/regex.c @@ -0,0 +1,74 @@ +/* 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 diff --git a/regex/regex.h b/regex/regex.h new file mode 100644 index 0000000..c2a9a4c --- /dev/null +++ b/regex/regex.h @@ -0,0 +1,580 @@ +/* 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 */ diff --git a/regex/regex_internal.c b/regex/regex_internal.c new file mode 100644 index 0000000..66154e0 --- /dev/null +++ b/regex/regex_internal.c @@ -0,0 +1,1717 @@ +/* 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. */ + int prev_valid_len = pstr->valid_len; + +#ifdef RE_ENABLE_I18N + 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) +{ + int type = token.type; + 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; +} diff --git a/regex/regex_internal.h b/regex/regex_internal.h new file mode 100644 index 0000000..87fa3fc --- /dev/null +++ b/regex/regex_internal.h @@ -0,0 +1,769 @@ +/* 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 */ diff --git a/regex/regexec.c b/regex/regexec.c new file mode 100644 index 0000000..135efe7 --- /dev/null +++ b/regex/regexec.c @@ -0,0 +1,4333 @@ +/* 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. */ + +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; + + 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; + + /* 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)); + } + + str_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; + reg_errcode_t err = REG_NOERROR; + 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); +} diff --git a/rrbb.c b/rrbb.c new file mode 100644 index 0000000..65ba38e --- /dev/null +++ b/rrbb.c @@ -0,0 +1,620 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011, 2012, 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 . +// + + +/******************************************************************************** + * + * File: rrbb.c + * + * Purpose: Raw Received Bit Buffer. + * Implementation of an array of bits used to hold data out of + * the demodulator before feeding it into the HLDC decoding. + * + * Version 1.0: Let's try something new. + * Rather than storing a single bit from the demodulator + * output, let's store a value which we can try later + * comparing to threshold values besides 0. + * + *******************************************************************************/ + +#define RRBB_C + +#include +#include +#include +#include + +#include "direwolf.h" +#include "textcolor.h" +#include "ax25_pad.h" +#include "rrbb.h" + + + + +#define MAGIC1 0x12344321 +#define MAGIC2 0x56788765 + +#ifndef SLICENDICE +static const unsigned int masks[SOI] = { + 0x00000001, + 0x00000002, + 0x00000004, + 0x00000008, + 0x00000010, + 0x00000020, + 0x00000040, + 0x00000080, + 0x00000100, + 0x00000200, + 0x00000400, + 0x00000800, + 0x00001000, + 0x00002000, + 0x00004000, + 0x00008000, + 0x00010000, + 0x00020000, + 0x00040000, + 0x00080000, + 0x00100000, + 0x00200000, + 0x00400000, + 0x00800000, + 0x01000000, + 0x02000000, + 0x04000000, + 0x08000000, + 0x10000000, + 0x20000000, + 0x40000000, + 0x80000000 }; +#endif + +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. + * + * is_scrambled - Is data scrambled? (true, false) + * + * descram_state - State of data descrambler. + * + * Returns: Handle to be used by other functions. + * + * Description: + * + ***********************************************************************************/ + +rrbb_t rrbb_new (int chan, int subchan, int is_scrambled, int descram_state) +{ + rrbb_t result; + + assert (SOI == 8 * sizeof(unsigned int)); + + assert (chan >= 0 && chan < MAX_CHANS); + assert (subchan >= 0 && subchan < MAX_SUBCHANS); + + + result = malloc(sizeof(struct rrbb_s)); + + result->magic1 = MAGIC1; + result->chan = chan; + result->subchan = subchan; + result->magic2 = MAGIC2; + + new_count++; + + rrbb_clear (result, is_scrambled, descram_state); + + return (result); +} + +/*********************************************************************************** + * + * Name: rrbb_clear + * + * Purpose: Clear by setting length to zero, etc. + * + * Inputs: Handle for sample array. + * + ***********************************************************************************/ + +void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state) +{ + assert (b != NULL); + assert (b->magic1 == MAGIC1); + assert (b->magic2 == MAGIC2); + + assert (is_scrambled == 0 || is_scrambled == 1); + + b->nextp = NULL; + b->audio_level = 9999; + b->len = 0; + + b->is_scrambled = is_scrambled; + b->descram_state = descram_state; + +} + +/*********************************************************************************** + * + * Name: rrbb_append_bit + * + * Purpose: Append another bit to the end. + * + * Inputs: Handle for sample array. + * Value for the sample. + * + ***********************************************************************************/ + +#if SLICENDICE +void rrbb2_append_bit (rrbb_t b, float val) +{ + assert (b != NULL); + assert (b->magic1 == MAGIC1); + assert (b->magic2 == MAGIC2); + assert (b->len >= 0); + + if (b->len >= MAX_NUM_BITS) { + return; /* Silently discard if full. */ + } + + b->data[b->len++] = (int)(val * 1000.); +} +#else +void rrbb_append_bit (rrbb_t b, int val) +{ + unsigned int di, mi; + + assert (b != NULL); + assert (b->magic1 == MAGIC1); + assert (b->magic2 == MAGIC2); + + if (b->len >= MAX_NUM_BITS) { + return; /* Silently discard if full. */ + } + + di = b->len / SOI; + mi = b->len % SOI; + + if (val) { + b->data[di] |= masks[mi]; + } + else { + b->data[di] &= ~ masks[mi]; + } + + b->len++; +} +#endif + +/*********************************************************************************** + * + * 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_set_slice_val + * + * Purpose: Set slicing value to determine whether a sample is bit 0 or 1. + * + * Inputs: Handle for sample array. + * Slicing point value. + * + ***********************************************************************************/ + +#if SLICENDICE + +static int cmp_slice (slice_t *a, slice_t *b) +{ + return ( *a - *b ); +} + +void rrbb_set_slice_val (rrbb_t b, slice_t slice_val) +{ + slice_t sorted[MAX_NUM_BITS]; + int n, i; + int sum, ave, median, izero; + + assert (b != NULL); + assert (b->magic1 == MAGIC1); + assert (b->magic2 == MAGIC2); + + b->slice_val = slice_val; + + memcpy (sorted, b->data, b->len * sizeof(slice_t)); + + /* Typically takes 14 milliseconds on a reasonable PC. */ + qsort (sorted, (size_t)(b->len), sizeof(slice_t), cmp_slice); + + text_color_set (DW_COLOR_DEBUG); + + n = 0; + dw_printf ("[%d..%d] ", n, n+9); + for (i=n; i<=n+9; i++) dw_printf (" %d", sorted[i]); + dw_printf ("\n"); + + n = ( b->len / 2 ) - 10; + dw_printf ("m[%d..%d] ", n, n+19); + for (i=n; i<=n+19; i++) dw_printf (" %d", sorted[i]); + dw_printf ("\n"); + + n = b->len - 1 - 9; + dw_printf ("[%d..%d] ", n, n+9); + for (i=n; i<=n+9; i++) dw_printf (" %d", sorted[i]); + dw_printf ("\n"); + + sum = 0; + for (i=0; ilen; i++) { + sum += sorted[i]; + } + ave = sum / b->len; + + //b->slice_val = ave; + //b->slice_val = sorted[b->len/2]; + + /* Find first one >= 0. */ + izero = -1; + for (i=0; ilen; i++) { + if (sorted[i] >= 0) { + izero = i; + break; + } + } + + if (izero >= 0) { + n = izero - 10; + dw_printf ("z[%d..%d] ", n, n+19); + for (i=n; i<=n+19; i++) dw_printf (" %d", sorted[i]); + dw_printf ("\n"); + + b->slice_val = sorted[izero-1]; + + } + + + +} + +#endif + + +/*********************************************************************************** + * + * Name: rrbb_get_bit + * + * Purpose: Get value of bit in specified position. + * + * Inputs: Handle for sample array. + * Index into array. + * + ***********************************************************************************/ + +#if SLICENDICE +int rrbb_get_bit (rrbb_t b, unsigned int ind) +{ + assert (b != NULL); + assert (b->magic1 == MAGIC1); + assert (b->magic2 == MAGIC2); + assert (ind >= 0 && ind < b->len); + + if (b->data[ind] > b->slice_val) { + return 1; + } + else { + return 0; + } +} +#else +int rrbb_get_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; + + if (b->data[di] & masks[mi]) { + return 1; + } + else { + return 0; + } +} +#endif + + +/*********************************************************************************** + * + * 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_set_audio_level + * + * Purpose: Set audio level at time the frame was received. + * + * Inputs: b Handle for bit array. + * a Audio level. + * + ***********************************************************************************/ + +void rrbb_set_audio_level (rrbb_t b, int a) +{ + assert (b != NULL); + assert (b->magic1 == MAGIC1); + assert (b->magic2 == MAGIC2); + + b->audio_level = a; +} + + +/*********************************************************************************** + * + * Name: rrbb_get_audio_level + * + * Purpose: Get audio level at time the frame was received. + * + * Inputs: b Handle for bit array. + * + ***********************************************************************************/ + +int rrbb_get_audio_level (rrbb_t b) +{ + assert (b != NULL); + assert (b->magic1 == MAGIC1); + assert (b->magic2 == MAGIC2); + + return (b->audio_level); +} + + +/*********************************************************************************** + * + * 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); +} + + +/* end rrbb.c */ + + diff --git a/rrbb.h b/rrbb.h new file mode 100644 index 0000000..fade79a --- /dev/null +++ b/rrbb.h @@ -0,0 +1,102 @@ + +#ifndef RRBB_H + +#define RRBB_H + + +/* Try something new in version 1.0 */ +/* Get back to this later. Disable for now. */ + +//#define SLICENDICE 1 + +typedef short slice_t; + + +#ifdef RRBB_C + +/* + * 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) + +#define SOI 32 + +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 audio_level; /* 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. */ + +#if SLICENDICE + slice_t slice_val; + slice_t data[MAX_NUM_BITS]; +#else + unsigned int data[(MAX_NUM_BITS+SOI-1)/SOI]; +#endif + int magic2; +} *rrbb_t; + +#else + +/* Hide the implementation. */ + +typedef void *rrbb_t; + +#endif + + + +rrbb_t rrbb_new (int chan, int subchan, int is_scrambled, int descram_state); + +void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state); + +#if SLICENDICE +void rrbb2_append_bit (rrbb_t b, float val); +#else +void rrbb_append_bit (rrbb_t b, int val); +#endif + +void rrbb_chop8 (rrbb_t b); + +int rrbb_get_len (rrbb_t b); + +#if SLICENDICE +void rrbb_set_slice_val (rrbb_t b, slice_t slice_val); +#endif + +int rrbb_get_bit (rrbb_t b, unsigned int ind); + +//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); + +void rrbb_set_audio_level (rrbb_t b, int a); + +int rrbb_get_audio_level (rrbb_t b); + +int rrbb_get_is_scrambled (rrbb_t b); + +int rrbb_get_descram_state (rrbb_t b); + +#endif \ No newline at end of file diff --git a/server.c b/server.c new file mode 100644 index 0000000..21635e3 --- /dev/null +++ b/server.c @@ -0,0 +1,1249 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011,2012,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 . +// + + +/*------------------------------------------------------------------ + * + * 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 + * + * 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.) + * + * + * + * 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 + * + *---------------------------------------------------------------*/ + + +/* + * Native Windows: Use the Winsock interface. + * Linux: Use the BSD socket interface. + * Cygwin: Can use either one. + */ + + +#if __WIN32__ +#include +#define _WIN32_WINNT 0x0501 +#include +#else +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "direwolf.h" +#include "tq.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "audio.h" +#include "server.h" + + + +static int client_sock; /* 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; /* Should we send received packets to client app? */ +static int enable_send_monitor_to_client; + + +static int num_channels; /* Number of radio ports. */ + + +static void * connect_listen_thread (void *arg); +static void * cmd_listen_thread (void *arg); + +/* + * Message header for AGW protocol. + * Assuming little endian such as x86 or ARM. + * Byte swapping would be required for big endian cpu. + */ + +#if __GNUC__ +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ +#error This needs to be more portable to work on big endian. +#endif +#endif + +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; +}; + + +/*------------------------------------------------------------------- + * + * Name: debug_print + * + * Purpose: Print message to/from client for debugging. + * + * Inputs: fromto - Direction of message. + * pmsg - Address of the message block. + * msg_len - Length of the message. + * + *--------------------------------------------------------------------*/ + +static int debug_client = 0; /* 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: + strcpy (direction, "from"); /* from the client application */ + + switch (pmsg->kind_lo) { + case 'P': strcpy (datakind, "Application Login"); break; + case 'X': strcpy (datakind, "Register CallSign"); break; + case 'x': strcpy (datakind, "Unregister CallSign"); break; + case 'G': strcpy (datakind, "Ask Port Information"); break; + case 'm': strcpy (datakind, "Enable Reception of Monitoring Frames"); break; + case 'R': strcpy (datakind, "AGWPE Version Info"); break; + case 'g': strcpy (datakind, "Ask Port Capabilities"); break; + case 'H': strcpy (datakind, "Callsign Heard on a Port"); break; + case 'y': strcpy (datakind, "Ask Outstanding frames waiting on a Port"); break; + case 'Y': strcpy (datakind, "Ask Outstanding frames waiting for a connection"); break; + case 'M': strcpy (datakind, "Send UNPROTO Information"); break; + case 'C': strcpy (datakind, "Connect, Start an AX.25 Connection"); break; + case 'D': strcpy (datakind, "Send Connected Data"); break; + case 'd': strcpy (datakind, "Disconnect, Terminate an AX.25 Connection"); break; + case 'v': strcpy (datakind, "Connect VIA, Start an AX.25 circuit thru digipeaters"); break; + case 'V': strcpy (datakind, "Send UNPROTO VIA"); break; + case 'c': strcpy (datakind, "Non-Standard Connections, Connection with PID"); break; + case 'K': strcpy (datakind, "Send data in raw AX.25 format"); break; + case 'k': strcpy (datakind, "Activate reception of Frames in raw format"); break; + default: strcpy (datakind, "**INVALID**"); break; + } + break; + + case TO_CLIENT: + default: + strcpy (direction, "to"); /* sent to the client application. */ + + switch (pmsg->kind_lo) { + case 'R': strcpy (datakind, "Version Number"); break; + case 'X': strcpy (datakind, "Callsign Registration"); break; + case 'G': strcpy (datakind, "Port Information"); break; + case 'g': strcpy (datakind, "Capabilities of a Port"); break; + case 'y': strcpy (datakind, "Frames Outstanding on a Port"); break; + case 'Y': strcpy (datakind, "Frames Outstanding on a Connection"); break; + case 'H': strcpy (datakind, "Heard Stations on a Port"); break; + case 'C': strcpy (datakind, "AX.25 Connection Received"); break; + case 'D': strcpy (datakind, "Connected AX.25 Data"); break; + case 'M': strcpy (datakind, "Monitored Connected Information"); break; + case 'S': strcpy (datakind, "Monitored Supervisory Information"); break; + case 'U': strcpy (datakind, "Monitored Unproto Information"); break; + case 'T': strcpy (datakind, "Monitoring Own Information"); break; + case 'K': strcpy (datakind, "Monitored Information in Raw Format"); break; + default: strcpy (datakind, "**INVALID**"); break; + } + } + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n"); + + dw_printf ("%s %s %s AGWPE client application, total length = %d\n", + prefix[(int)fromto], datakind, direction, msg_len); + + dw_printf ("\tportx = %d, port_hi_reserved = %d\n", pmsg->portx, pmsg->port_hi_reserved); + dw_printf ("\tkind_lo = %d = '%c', kind_hi = %d\n", pmsg->kind_lo, pmsg->kind_lo, pmsg->kind_hi); + 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", pmsg->data_len, pmsg->user_reserved); + + hex_dump ((char*)pmsg + sizeof(struct agwpe_s), pmsg->data_len); + + 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 != pmsg->data_len + 36) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("AGWPE message length, %d, inconsistent with data length %d.\n", msg_len, pmsg->data_len); + } + +} + +/*------------------------------------------------------------------- + * + * 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 + * + * 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 server_init (struct misc_config_s *mc) +{ +#if __WIN32__ + HANDLE connect_listen_th; + HANDLE cmd_listen_th; +#else + pthread_t connect_listen_tid; + pthread_t cmd_listen_tid; +#endif + int e; + int server_port = mc->agwpe_port; + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("server_init ( %d )\n", server_port); + debug_a = 1; +#endif + client_sock = -1; + enable_send_raw_to_client = 0; + enable_send_monitor_to_client = 0; + num_channels = mc->num_channels; + +/* + * This waits for a client to connect and sets client_sock. + */ +#if __WIN32__ + connect_listen_th = _beginthreadex (NULL, 0, connect_listen_thread, (void *)server_port, 0, NULL); + if (connect_listen_th == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not create AGW connect listening thread\n"); + return; + } +#else + e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(long)server_port); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Could not create AGW connect listening thread"); + return; + } +#endif + +/* + * This reads messages from client when client_sock is valid. + */ +#if __WIN32__ + cmd_listen_th = _beginthreadex (NULL, 0, cmd_listen_thread, NULL, 0, NULL); + if (cmd_listen_th == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not create AGW command listening thread\n"); + return; + } +#else + e = pthread_create (&cmd_listen_tid, NULL, cmd_listen_thread, NULL); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Could not create AGW command listening thread"); + return; + } +#endif +} + + +/*------------------------------------------------------------------- + * + * Name: connect_listen_thread + * + * Purpose: Wait for a connection request from an application. + * + * Inputs: arg - TCP port for server. + * Main program has default of 8000 but allows + * an alternative to be specified on the command line + * + * Outputs: client_sock - File descriptor for communicating with client app. + * + * Description: Wait for connection request from client and establish + * communication. + * Note that the client can go away and come back again and + * re-establish communication without restarting this application. + * + *--------------------------------------------------------------------*/ + +static void * connect_listen_thread (void *arg) +{ +#if __WIN32__ + + struct addrinfo hints; + struct addrinfo *ai = NULL; + int err; + char server_port_str[12]; + + SOCKET listen_sock; + WSADATA wsadata; + + sprintf (server_port_str, "%d", (int)(long)arg); +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("DEBUG: serverport = %d = '%s'\n", (int)(long)arg, server_port_str); +#endif + err = WSAStartup (MAKEWORD(2,2), &wsadata); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("WSAStartup failed: %d\n", err); + return (NULL); + } + + 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 (NULL); + } + + memset (&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE; + + err = getaddrinfo(NULL, server_port_str, &hints, &ai); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("getaddrinfo failed: %d\n", err); + //sleep (1); + WSACleanup(); + return (NULL); + } + + listen_sock= socket(ai->ai_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 (NULL); + } + +#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()); + dw_printf("Some other application is probably already using port %s.\n", server_port_str); + freeaddrinfo(ai); + closesocket(listen_sock); + WSACleanup(); + return (NULL); + } + + 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) { + + while (client_sock > 0) { + SLEEP_SEC(1); /* Already connected. Try again later. */ + } + +#define QUEUE_SIZE 5 + + if(listen(listen_sock,QUEUE_SIZE) == SOCKET_ERROR) + { + text_color_set(DW_COLOR_ERROR); + dw_printf("Listen failed with error: %d\n", WSAGetLastError()); + return (NULL); + } + + text_color_set(DW_COLOR_INFO); + dw_printf("Ready to accept AGW client application on port %s ...\n", server_port_str); + + client_sock = accept(listen_sock, NULL, NULL); + + if (client_sock == -1) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Accept failed with error: %d\n", WSAGetLastError()); + closesocket(listen_sock); + WSACleanup(); + return (NULL); + } + + text_color_set(DW_COLOR_INFO); + dw_printf("\nConnected to AGW client application ...\n\n"); + +/* + * 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 = 0; + enable_send_monitor_to_client = 0; + + } + +#else + + 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; + + 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); + } + + 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); + perror ("connect_listen_thread: Bind failed"); + 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) { + + while (client_sock > 0) { + SLEEP_SEC(1); /* Already connected. Try again later. */ + } + +#define QUEUE_SIZE 5 + + if(listen(listen_sock,QUEUE_SIZE) == -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 on port %d ...\n", server_port); + + client_sock = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size); + + text_color_set(DW_COLOR_INFO); + dw_printf("\nConnected to AGW client application ...\n\n"); + +/* + * 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 = 0; + enable_send_monitor_to_client = 0; + + } +#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; + +/* + * RAW format + */ + + if (enable_send_raw_to_client + && client_sock > 0){ + + memset (&agwpe_msg.hdr, 0, sizeof(agwpe_msg.hdr)); + + agwpe_msg.hdr.portx = chan; + + agwpe_msg.hdr.kind_lo = '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 = 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, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len); + } + +#if __WIN32__ + err = send (client_sock, (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len, 0); + 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_sock = -1; + WSACleanup(); + } +#else + err = write (client_sock, &agwpe_msg, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len); + 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_sock = -1; + } +#endif + } + + +/* MONITOR format - only for UI frames. */ + + + if (enable_send_monitor_to_client + && client_sock > 0 + && ax25_get_control(pp) == AX25_UI_FRAME){ + + time_t clock; + struct tm *tm; + + clock = time(NULL); + tm = localtime(&clock); + + memset (&agwpe_msg.hdr, 0, sizeof(agwpe_msg.hdr)); + + agwpe_msg.hdr.portx = chan; + + agwpe_msg.hdr.kind_lo = '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. */ + + sprintf (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 = strlen(agwpe_msg.data) + 1 /* include null */ ; + + if (debug_client) { + debug_print (TO_CLIENT, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len); + } + +#if __WIN32__ + err = send (client_sock, (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len, 0); + 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_sock = -1; + WSACleanup(); + } +#else + err = write (client_sock, &agwpe_msg, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len); + 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_sock = -1; + } +#endif + } + +} /* server_send_rec_packet */ + + + +/*------------------------------------------------------------------- + * + * 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; + +#if __WIN32__ + +//TODO: any flags for send/recv? + + n = recv (fd, ptr + got_bytes, len - got_bytes, 0); +#else + n = read (fd, ptr + got_bytes, len - got_bytes); +#endif + +#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 - Not used. + * + * Outputs: client_sock - 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 * 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; + + while (1) { + + while (client_sock <= 0) { + SLEEP_SEC(1); /* Not connected. Try again later. */ + } + + n = read_from_socket (client_sock, (char *)(&cmd.hdr), sizeof(cmd.hdr)); + if (n != sizeof(cmd.hdr)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError getting message header from client application.\n"); + 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); +#else + close (client_sock); +#endif + client_sock = -1; + continue; + } + + assert (cmd.hdr.data_len >= 0 && cmd.hdr.data_len < sizeof(cmd.data)); + + cmd.data[0] = '\0'; + + if (cmd.hdr.data_len > 0) { + n = read_from_socket (client_sock, cmd.data, cmd.hdr.data_len); + if (n != cmd.hdr.data_len) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError getting message data from client application.\n"); + dw_printf ("Tried to read %d bytes but got only %d.\n", cmd.hdr.data_len, n); + dw_printf ("Closing connection.\n\n"); +#if __WIN32__ + closesocket (client_sock); +#else + close (client_sock); +#endif + client_sock = -1; + return NULL; + } + if (n > 0) { + cmd.data[cmd.hdr.data_len] = '\0'; + } + } + +/* + * print & process message from client. + */ + + if (debug_client) { + debug_print (FROM_CLIENT, &cmd.hdr, sizeof(cmd.hdr) + cmd.hdr.data_len); + } + + switch (cmd.hdr.kind_lo) { + + case 'R': /* Request for version number */ + { + struct { + struct agwpe_s hdr; + int major_version; + int minor_version; + } reply; + + + memset (&reply, 0, sizeof(reply)); + reply.hdr.kind_lo = 'R'; + reply.hdr.data_len = sizeof(reply.major_version) + sizeof(reply.minor_version); + assert (reply.hdr.data_len ==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 = 2005; + reply.minor_version = 127; + + assert (sizeof(reply) == 44); + + if (debug_client) { + debug_print (TO_CLIENT, &reply.hdr, sizeof(reply)); + } + +// TODO: Should have unified function instead of multiple versions everywhere. + +#if __WIN32__ + send (client_sock, (char*)(&reply), sizeof(reply), 0); +#else + write (client_sock, &reply, sizeof(reply)); +#endif + } + break; + + case 'G': /* Ask about radio ports */ + + { + struct { + struct agwpe_s hdr; + char info[100]; + } reply; + + + memset (&reply, 0, sizeof(reply)); + reply.hdr.kind_lo = 'G'; + reply.hdr.data_len = sizeof (reply.info); + + // Xastir only prints this and doesn't care otherwise. + // YAAC uses this to identify available channels. + + if (num_channels == 1) { + sprintf (reply.info, "1;Port1 Single channel;"); + } + else { + sprintf (reply.info, "2;Port1 Left channel;Port2 Right Channel;"); + } + + assert (reply.hdr.data_len == 100); + + if (debug_client) { + debug_print (TO_CLIENT, &reply.hdr, sizeof(reply)); + } + +#if __WIN32__ + send (client_sock, (char*)(&reply), sizeof(reply), 0); +#else + write (client_sock, &reply, sizeof(reply)); +#endif + } + break; + + + case 'g': /* Ask about capabilities of a port. */ + + { + struct { + struct agwpe_s hdr; + unsigned char on_air_baud_rate; /* 0=1200, 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; + } reply; + + + memset (&reply, 0, sizeof(reply)); + + reply.hdr.portx = cmd.hdr.portx; /* Reply with same port number ! */ + reply.hdr.kind_lo = 'g'; + reply.hdr.data_len = 12; + + // YAAC asks for this. + // Fake it to keep application happy. + + 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 = 1; + + assert (sizeof(reply) == 48); + + if (debug_client) { + debug_print (TO_CLIENT, &reply.hdr, sizeof(reply)); + } + +#if __WIN32__ + send (client_sock, (char*)(&reply), sizeof(reply), 0); +#else + write (client_sock, &reply, sizeof(reply)); +#endif + } + break; + + + case 'H': /* Ask about recently heard stations. */ + + { +#if 0 + struct { + struct agwpe_s hdr; + char info[100]; + } reply; + + + memset (&reply.hdr, 0, sizeof(reply.hdr)); + reply.hdr.kind_lo = 'H'; + + // TODO: Implement properly. + + reply.hdr.portx = cmd.hdr.portx + + strcpy (reply.hdr.call_from, "WB2OSZ-15"); + + strcpy (agwpe_msg.data, ...); + + reply.hdr.data_len = strlen(reply.info); + + if (debug_client) { + debug_print (TO_CLIENT, &reply.hdr, sizeof(reply.hdr) + reply.hdr.data_len); + } + +#if __WIN32__ + send (client_sock, &reply, sizeof(reply.hdr) + reply.hdr.data_len, 0); +#else + write (client_sock, &reply, sizeof(reply.hdr) + reply.hdr.data_len); +#endif + +#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 = ! enable_send_raw_to_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 = ! enable_send_monitor_to_client; + break; + + + case 'V': /* Transmit UI data frame */ + { + // Data format is: + // 1 byte for number of digipeaters. + // 10 bytes for each digipeater. + // data part of message. + + char stemp[512]; + char *p; + int ndigi; + int k; + + packet_t pp; + //unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; + //int flen; + + strcpy (stemp, cmd.hdr.call_from); + strcat (stemp, ">"); + strcat (stemp, cmd.hdr.call_to); + + cmd.data[cmd.hdr.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 */ + + /* Send success status. */ + + { + struct { + struct agwpe_s hdr; + char data; + } reply; + + + memset (&reply, 0, sizeof(reply)); + reply.hdr.kind_lo = 'X'; + memcpy (reply.hdr.call_from, cmd.hdr.call_from, sizeof(reply.hdr.call_from)); + reply.hdr.data_len = 1; + reply.data = 1; /* success */ + + // Version 1.0. + // Previously used sizeof(reply) but compiler rounded it up to next byte boundary. + // That's why more cumbersome size expression is used. + + if (debug_client) { + debug_print (TO_CLIENT, &reply.hdr, sizeof(reply.hdr) + sizeof(reply.data)); + } + +#if __WIN32__ + send (client_sock, (char*)(&reply), sizeof(reply.hdr) + sizeof(reply.data), 0); +#else + write (client_sock, &reply, sizeof(reply.hdr) + sizeof(reply.data)); +#endif + } + break; + + case 'x': /* Unregister CallSign */ + /* 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 'D': /* Send Connected Data */ + case 'd': /* Disconnect, Terminate an AX.25 Connection */ + + // Version 1.0. Better message instead of generic unexpected command. + + text_color_set(DW_COLOR_ERROR); + dw_printf ("\n"); + dw_printf ("Can't process command from AGW client app.\n"); + dw_printf ("Connected packet mode is not implemented.\n"); + + break; + +#if 0 + case 'M': /* Send UNPROTO Information */ + + Not sure what we might want to do here. + AGWterminal sends this for beacon or ask QRA. + + + <<< Send UNPROTO Information from AGWPE client application, total length = 253 + portx = 0, port_hi_reserved = 0 + kind_lo = 77 = 'M', kind_hi = 0 + call_from = "SV2AGW-1", call_to = "BEACON" + data_len = 217, user_reserved = 588, data = + 000: 54 68 69 73 20 76 65 72 73 69 6f 6e 20 75 73 65 This version use + 010: 73 20 74 68 65 20 6e 65 77 20 41 47 57 20 50 61 s the new AGW Pa + 020: 63 6b 65 74 20 45 6e 67 69 6e 65 20 77 69 6e 73 cket Engine wins + + <<< Send UNPROTO Information from AGWPE client application, total length = 37 + portx = 0, port_hi_reserved = 0 + kind_lo = 77 = 'M', kind_hi = 0 + call_from = "SV2AGW-1", call_to = "QRA" + data_len = 1, user_reserved = 32218432, data = + 000: 0d . + + break; + +#endif + default: + + text_color_set(DW_COLOR_ERROR); + dw_printf ("--- Unexpected Command from application using AGW protocol:\n"); + debug_print (FROM_CLIENT, &cmd.hdr, sizeof(cmd.hdr) + cmd.hdr.data_len); + + break; + } + + + } + +} + +/* end server.c */ diff --git a/server.h b/server.h new file mode 100644 index 0000000..03514ac --- /dev/null +++ b/server.h @@ -0,0 +1,19 @@ + +/* + * Name: server.h + */ + + +#include "ax25_pad.h" /* for packet_t */ + +#include "config.h" + + +void server_set_debug (int n); + +void server_init (struct misc_config_s *misc_config); + +void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int flen); + + +/* end server.h */ diff --git a/symbols-new.txt b/symbols-new.txt new file mode 100644 index 0000000..9172fa7 --- /dev/null +++ b/symbols-new.txt @@ -0,0 +1,409 @@ +APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 20 May 2014 +--------------------------------------------------------------------- + +BACKGROUND: This file addresses new additions proposals (OVERLAYS) +to the APRS symbol set after 1 October 2007. The master symbol +document remains on the www.aprs.org/symbols/symbolsX.txt page. + + +NOTE: There was confusion with different copies of this file on +different web pages and links. THIS file is now assumed to be the +CORRECT one. + + +UPDATES/REVISIONS/CORRECTIONS: + +20 May 14 Changed Da to DSTAR (2700 of them) from Dutch Ares +19 May 14 Added Submarine&torpedo to ships and lots of Aircraft + search for "(new may 2014)" +07 Oct 13 Added new overlays to ships such as Jet Ski, Js + Added Ham Club symbol as a C overlay on House, C- +19 Sep 11 Added T and 2 overlays for TX 1 and 2 hop IGates + Added overlays to (;) portable, to show event types +23 Mar 11 Added Radiation Detector (RH) +20 Apr 10 Byonics requested (BY) +04 Jan 10 added #A to the table (correcting earlier omission) +12 Oct 09 Added W0 for Yaesu WIRES nodes +09 Apr 09 Changed APRStt symbol to overlayed BOX (#A) +21 Aug 08 Added RFID R=, Stroller B], Radios#Y, & skull&Xbones (XH) +27 Apr 08 Added some definitions of the numbered circle #0. +25 Mar 08 Added these new definitions of overlays: + +Original Alternate Symbol codes being modified for new Overlay Use: + +\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. + +Previous edition was 4 Oct 2007. + +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: + +www.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. + +AIRCRAFT +/^ = LARGE Aircraft +\^ = top-view originally intended to point in direction of flight +D^ = Drone (new may 2014) +E^ = Enemy aircraft (too bad I cant use the original Hostile) +H^ = Hovercraft (new may 2014) +J^ = JET (new may 2014) +M^ = Missle (new may 2014) +V^ = Vertical takeoff (new may 2014) + +ATM Machine or CURRENCY: #$ +/$ = original primary Phone +\$ = Bank or ATM (generic) +U$ = US dollars +L$ = Brittish Pound +Y$ = Japanese Yen + +POWER PLANT: #% +/% = DX cluster <= the original primary table definition +C% = Coal +G% = Geothermal +H% = Hydroelectric +N% = Nuclear +S% = Solar +T% = Turbine +W% = Wind + +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) +T& = TX igate with path set to 1 hop only) +2& = TX igate with path set to 2 hops (not generally good idea) + +INCIDENT SITES: #' +\' = Airplane Crash Site <= the original primary deifinition +A' = Automobile crash site +H' = Hazardous incident +M' = Multi-Vehicle crash site +P' = Pileup +T' = Truck wreck + +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 mains power +6- = 60 Hz mains power +B- = Backup Battery Power +C- = Club, as in Ham club +E- = Emergency power +G- = Geothermal +H- = Hydro powered +O- = Operator Present +S- = Solar Powered +W- = Wind powered + +NUMBERED CIRCLES: #0 +E0 = Echolink Node (E0) +I0 = IRLP repeater (I0) +S0 = Staging Area (S0) +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 + +ADVISORIES: #< (new expansion possibilities) +/< = motorcycle +\< = Advisory (single gale flag) + +CARS: #> +/> = normal car (side view) +\> = Top view and symbol POINTS in direction of travel +E> = Electric +H> = Hybrid +S> = Solar powered +V> = GM Volt + +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 + +EYEBALL 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 \{ + +HAZARDS: #H +/H = hotel +\H = Haze +RH = Radiation detector (new mar 2011) +WH = Hazardous Waste +XH = Skull&Crossbones + +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 + +GPS devices: #\ +/\ = Triangle DF primary symbol +\\ = was undefined alternate symbol +A\ = Avmap G5 * <= Recommend special symbol + +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 + +CIVIL DEFENSE or TRIANGLE: #c +/c = Incident Command Post +\c = Civil Defense +Rc = RACES +Sc = SATERN mobile canteen + +BUILDINGS: #h +/h = Hospital +\h = Ham Store ** <= now used for HAMFESTS +Hh = Home Dept etc.. + +SPECIAL VEHICLES: #k +/k = truck +\k = SUV +4k = 4x4 +Ak = ATV (all terrain vehicle) + +SHIPS: #s +/s = Power boat (ship) side view +\s = Overlay Boat (Top view) +Cs = receive as Canoe but still transmit canoe as /C +Js = Jet Ski +Ks = Kayak +Hs = Hovercraft (new may 2014) +Ts = Torpedo (new may 2014) +Us = sUbmarine U-boat (new may 2014) + +TRUCKS: #u +/u = Truck (18 wheeler) +\u = truck with overlay +Gu = Gas +Tu = Tanker +Cu = Chlorine Tanker +Hu = Hazardous + + +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 diff --git a/symbols.c b/symbols.c new file mode 100644 index 0000000..41f5a59 --- /dev/null +++ b/symbols.c @@ -0,0 +1,898 @@ +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * File: symbols.c + * + * Purpose: Functions related to the APRS symbols + * + *------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include "textcolor.h" +#include "symbols.h" + +//#if __WIN32__ + char *strcasestr(const char *S, const char *FIND); +//#endif + +/* + * 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" } }; + + +/*------------------------------------------------------------------ + * + * 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; + struct { + char overlay; + char symbol; + char sp1; + char equal; + char sp2; + char description[150]; + } stuff; + int j; + +#define GOOD_LINE(x) ((x.overlay == '/' || x.overlay == '\\' || isupper(x.overlay) || isdigit(x.overlay)) \ + && x.symbol >= '!' && x.symbol <= '~' \ + && x.sp1 == ' ' && x.equal == '=' && x.sp2 == ' ') + + + if (new_sym_ptr != NULL) { + return; /* was called already. */ + } + +// If search strategy changes, be sure to keep decode_tocall in sync. + + + fp = fopen("symbols-new.txt", "r"); +#ifndef __WIN32__ + if (fp == NULL) { + fp = fopen("/usr/share/direwolf/symbols-new.txt", "r"); + } +#endif + 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((char*)(&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((char*)(&stuff), sizeof(stuff), fp) != NULL) { + + if (GOOD_LINE(stuff)) { + for (j = strlen(stuff.description) - 1; j>=0 && stuff.description[j] <= ' '; j--) { + stuff.description[j] = '\0'; + } + new_sym_ptr[new_sym_len].overlay = stuff.overlay; + new_sym_ptr[new_sym_len].symbol = stuff.symbol; + strncpy(new_sym_ptr[new_sym_len].description, stuff.description, 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. */ + sprintf (dest, "GPSC%02d", symbol - ' '); + return (0); + } + else if (symbol >= '!' && symbol <= '~' && symtab == '\\') { + + /* Alternate Symbol table. */ + sprintf (dest, "GPSE%02d", symbol - ' '); + return (0); + } + else if (symbol >= '!' && symbol <= '~' && (isupper(symtab) || isdigit(symtab))) { + + /* Alternate Symbol table with overlay. */ + sprintf (dest, "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); + + strcpy (dest, "GPS???"); /* 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 ~ + * + * 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) +{ + 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 = ' '; + strcpy (description, primary_symtab[symbol-' '].description); + 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 (still updated!) +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 symbol with 0-9 overlayed + A-Z Alternate OVERLAY symbol 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 that, provisions should be made in all software to allow for +overlays on any alternate symbol as they may be used 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 \! OB EMERGENCY (!) +/" 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 = 7) \' OHO Crash (& now Incident sites) +/( BI Mobile Satellite Station \( OI CLOUDY (other clouds w ovrly) +/) BJ Wheelchair (handicapped) \) OJO Firenet MEO, MODIS Earth Obs. +/* BK SnowMobile \* OK SNOW (& future ovrly codes) +/+ 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 (E/I/W=IRLP/Echolink/WIRES) +/1 P1 TBD (these were numbered) \1 A1 +/2 P2 TBD (circles like pool) \2 A2 +/3 P3 TBD (balls. But with) \3 A3 +/4 P4 TBD (overlays, we can) \4 A4 +/5 P5 TBD (put all #'s on one) \5 A5 +/6 P6 TBD (So 1-9 are available)\6 A6 +/7 P7 TBD (for new uses?) \7 A7 +/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 Hail (& future ovrly codes) +/; MS Campground (Portable ops) \; NSO Park/Picnic + overlay events +/< MT Motorcycle (SSID =10) \< NTO ADVISORY (one WX flag) +/= MU RAILROAD ENGINE \= NUO APRStt Touchtone (DTMF users) +/> MV CAR (SSID = 9) \> NV# OVERLAYED CAR +/? 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 Blwng Snow (& future codes) +/C PC Canoe \C AC Coast Guard +/D PD \D AD Drizzle (proposed APRStt) +/E PE EYEBALL (Eye catcher!) \E AE Smoke (& other vis codes) +/F PF Farm Vehicle (tractor) \F AF Freezng rain (&future codes) +/G PG Grid Square (6 digit) \G AG Snow Shwr (& future ovrlys) +/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 Lightening (& future ovrlys) +/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 Rocket (new June 2004) +/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 +/[ HS Human/Person (HT) \[ DSO W.Cloud (& humans w Ovrly) +/\ HT TRIANGLE(DF station) \\ DTO New overlayable GPS symbol +/] HU MAIL/PostOffice(was PBBS) \] DU +/^ HV LARGE AIRCRAFT \^ DV# # Aircraft (shows heading) +/_ 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 +/b LB BIKE (SSID = 4) \b SB Blwng Dst/Snd (& others) +/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 Prtly Cldy (& future ovrlys) +/q LQ GRID SQ shown above 128 m \q SQ +/r LR Repeater (Feb 07) \r SR Restrooms +/s LS SHIP (pwr boat) (SSID-8) \s SS# OVERLAY SHIP/boat (top view) +/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 SW Flooding +/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 Fog (& future ovrly codes) +/| J2 TNC Stream Switch \| Q2 TNC Stream Switch +/} J3 \} Q3 +/~ 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. These special symbols are: + +\> OVERLAYED CAR +\s Overlayed Ship +\^ Overlayed Aircraft +/^ Aircraft +/g Glider +\n Overlayed Triangle + +AREA SYMBOLS! You can define BOX/CIRCLE/LINE or TRIANGLE areas in all +colors, either open or filled in, any size from 60 feet to 100 miles. +Simply move 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: The new format for specifying special areas uses the +CSE/SPD field to provide the additional information 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 +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: Signposts display as a yellow box with a 1-3 letter +overlay on them. You specify the 1-3 letter overlay by enclosing them +in braces in the comment field. Thus a VALUE Signpost with {55} 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 [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 is 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://www.ew.usna.edu/~bruninga/aprs/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 diff --git a/textcolor.c b/textcolor.c new file mode 100644 index 0000000..eba28d0 --- /dev/null +++ b/textcolor.c @@ -0,0 +1,345 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011,2012,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: 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 + * + * 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 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 +#include +#include + + +#if __WIN32__ + +// /* Missing from MinGW header file. */ +// #define vsprintf_s vsnprintf + +//_CRTIMP int __cdecl __MINGW_NOTHROW vsprintf_s (char*, size_t, const char*, __VALIST); + +//int vsprintf_s( +// char *buffer, +// size_t numberOfElements, +// const char *format, +// va_list argptr +//); + + +#include + +#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"; + + +#else /* Linux */ + +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"; + +/* 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 */ diff --git a/textcolor.h b/textcolor.h new file mode 100644 index 0000000..33f568e --- /dev/null +++ b/textcolor.h @@ -0,0 +1,53 @@ + +/*------------------------------------------------------------------- + * + * Name: textcolor.h + * + * Purpose: Set color of text. + * + *--------------------------------------------------------------------*/ + +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 + diff --git a/tocalls.txt b/tocalls.txt new file mode 100644 index 0000000..089ac3b --- /dev/null +++ b/tocalls.txt @@ -0,0 +1,209 @@ +APRS TO-CALL VERSION NUMBERS 18 Dec 2013 +------------------------------------------------------------------- + WB4APR +18 Dec 13 added APZWKR for GM1WKR NetSked application +22 Oct 13 added APFIxx for APRS.FI OH7LZB, Hessu +23 Aug 13 added APOxxx for OSCAR satellites for AMSAT-LU by LU9DO +22 Feb 13 added APNWxx SQ3FYK.com & SQ3PLX http://microsat.com.pl/ + and APMIxx SQ3PLX http://microsat.com.pl/ +29 Jan 13 added APICxx for HA9MCQ Pic IGate +23 Jan 13 added APWAxx APRSISCE Android version +18 Jan 13 added APDGxx,APDHxx,APDOxx,APDDxx,APDKxx,APD4xx for Dstar +13 Jan 13 added APLMxx WA0TQG transceiver controller +17 Dec 12 added APAMxx Altus Metrum GPS trackers +03 Dec 12 added APUDRx NW Digital Radio's UDR (APRS/Dstar) +03 Nov 12 added APHAXn SM2APRS by PY2UEP +17 Sep 12 added APSCxx aprsc APRS-IS core server (OH7LZB, OH2MQK) +12 Sep 12 added APSARx for ZL4FOX's SARTRACK +02 Jul 12 added APDGxx D-Star Gateways by G4KLX +28 Jun 12 added APDInn DIXPRS - Bela, HA5DI +27 jun 12 added APMGxx MiniGate - Alex, AB0TJ +17 Feb 12 added APJYnn KA2DDO yet another APRS system +20 Jan 12 added APDSXX SP9UOB for dsDigi and ds-tracker + APBPQx John G8BPQ Digipeater/IGate + APLQRU Charlie - QRU Server +11 Jan 12 added APYTxx for YagTracker and updated Yaesu APY008/350 + +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 + 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 (replaced by APDRxx + APAMxx Altus Metrum GPS trackers + APAWxx AGWPE + APB APBxxx Beacons or Rabbit TCPIP micros? + APBLxx BigRedBee BeeLine + APBLO MOdel Rocketry K7RKT + APBPQx John G8BPQ Digipeater/IGate + APC APCxxx Cellular applications + APCBBx for VE7UDP Blackberry Applications + APCLEY EYTraker GPRS/GSM tracker by ZS6EY + APCLWX EYWeather GPRS/GSM WX station by ZS6EY + APCLEZ Telit EZ10 GSM application ZS6CEY + 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 + APDKxx KI4LKF g2_ircddb Dstar gateway software + APDOxx ON8JL Standalone DStar Node + APDPRS D-Star originated posits + APDRxx APrsDRoid replaces old APAND1. + APDSXX SP9UOB for dsDigi and ds-tracker + APDTxx APRStouch Tone (DTMF) + APDUxx U2APRS by JA7UDE + APDWxx DireWolf, WB2OSZ + APE APExxx Telemetry devices + APERXQ Experimental tracker by PE1RXQ + APF APFxxx Firenet + APFGxx Flood Gage (KP4DJT) + APFIxx for APRS.FI OH7LZB, Hessu + APG APGxxx Gates, etc + APGOxx for AA3NJ PDA application + APH APHKxx for LA1BR tracker/digipeater + APHAXn SM2APRS by PY2UEP + API APICQx for ICQ + APICxx for HA9MCQ Pic IGate + 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 + 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/ + APN APNxxx Network nodes, digis, etc + APN3xx Kantronics KPC-3 rom versions + APN9xx Kantronics KPC-9612 Roms + APNAxx WB6ZSU's APRServe + APNDxx DIGI_NED + 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 + APOLUx for OSCAR satellites for AMSAT-LU by LU9DO + APOAxx OpenAPRS - Greg Carter + APOTxx Open Track + APOD1w Open Track with 1 wire WX + APOU2k Open Track for Ultimeter + APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO + APP APPTxx KetaiTracker by JF6LZE, Takeki (msg capable) + APQ APQxxx Earthquake data + APR APR8xx APRSdos versions 800+ + 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) + APRXxx >40 APRSmax + APRXxx <39 for OH2MQK's RX-igate + APRTLM used in MIM's and Mic-lites, etc + APRtfc APRStraffic + APRSTx APRStt (Touch tone) + APS APRS+SA, etc + APSARx ZL4FOX's SARTRACK + APSCxx aprsc APRS-IS core server (OH7LZB, OH2MQK) + APSK63 APRS Messenger -over-PSK63 + APSK25 APRS Messenger GMSK-250 + APT APTIGR TigerTrack + APTTxx Tiny Track + APT2xx Tiny Track II + APT3xx Tiny Track III + APTAxx K4ATM's 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 + APZ APZxxx Experimental + APZ0xx Xastir (old versions. See APX) + APZMDR for HaMDR trackers - hessu * hes.iki.fi] + APZPAD Smart Palm + APZTKP TrackPoint, Nick N0LP (Balloon tracking) + 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 diff --git a/tq.c b/tq.c new file mode 100644 index 0000000..9f4a58d --- /dev/null +++ b/tq.c @@ -0,0 +1,599 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011,2012 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. + * + *---------------------------------------------------------------*/ + +#include +#include +#include +#include +#include + +#include "direwolf.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "audio.h" +#include "tq.h" +#include "dedupe.h" + + + +static int tq_num_channels; /* Set once during intialization and */ + /* should not change after that. */ + +static packet_t queue_head[MAX_CHANS][TQ_NUM_PRIO]; /* Head of linked list for each queue. */ + +#if __WIN32__ + +static CRITICAL_SECTION tq_cs; /* Critical section for updating queues. */ + +static HANDLE wake_up_event; /* Notify transmit thread when queue not empty. */ + +#else + +static pthread_mutex_t tq_mutex; /* Critical section for updating queues. */ + +static pthread_cond_t wake_up_cond; /* Notify transmit thread when queue not empty. */ + +static pthread_mutex_t wake_up_mutex; /* Required by cond_wait. */ + +static int xmit_thread_is_waiting = 0; + +#endif + +static int tq_is_empty (void); + + +/*------------------------------------------------------------------- + * + * Name: tq_init + * + * Purpose: Initialize the transmit queue. + * + * Inputs: nchan - Number of communication channels. + * + * 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. + * + * If more than one audio channel is being used, a separate + * pair of transmit queues is used for each channel. + * + *--------------------------------------------------------------------*/ + + +void tq_init (int nchan) +{ + int c, p; + int err; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_init ( %d )\n", nchan); +#endif + tq_num_channels = nchan; + assert (tq_num_channels >= 1 && tq_num_channels <= MAX_CHANS); + + for (c=0; c= 1 && tq_num_channels <= MAX_CHANS); + assert (prio >= 0 && prio < TQ_NUM_PRIO); + + if (chan < 0 || chan >= tq_num_channels) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR - Request to transmit on radio channel %d.\n", chan); + ax25_delete(pp); + return; + } + +/* Is transmit queue out of control? */ + + if (tq_count(chan,prio) > 20) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Transmit packet queue is too long. Discarding transmit request.\n"); + 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 +#if __WIN32__ + EnterCriticalSection (&tq_cs); +#else + err = pthread_mutex_lock (&tq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("tq_append: pthread_mutex_lock err=%d", err); + perror (""); + exit (1); + } +#endif + +// was_empty = 1; +// for (c=0; c= 1 && tq_num_channels <= MAX_CHANS); + + +#if __WIN32__ + EnterCriticalSection (&tq_cs); +#else + err = pthread_mutex_lock (&tq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("tq_wait_while_empty: pthread_mutex_lock err=%d", err); + perror (""); + exit (1); + } +#endif + +#if DEBUG + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("tq_wait_while_empty (): after pthread_mutex_lock\n"); +#endif + is_empty = tq_is_empty(); + +#if __WIN32__ + LeaveCriticalSection (&tq_cs); +#else + err = pthread_mutex_unlock (&tq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("tq_wait_while_empty: pthread_mutex_unlock err=%d", err); + perror (""); + exit (1); + } +#endif +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_wait_while_empty () : left critical section\n"); +#endif + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_wait_while_empty (): is_empty = %d\n", is_empty); +#endif + + if (is_empty) { +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_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 ("tq_wait_while_empty (): returned from wait\n"); +#endif + +#else + err = pthread_mutex_lock (&wake_up_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("tq_wait_while_empty: pthread_mutex_lock wu err=%d", err); + perror (""); + exit (1); + } + + xmit_thread_is_waiting = 1; + err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); + xmit_thread_is_waiting = 0; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_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 ("tq_wait_while_empty: pthread_cond_wait err=%d", err); + perror (""); + exit (1); + } + + err = pthread_mutex_unlock (&wake_up_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("tq_wait_while_empty: pthread_mutex_unlock wu err=%d", err); + perror (""); + exit (1); + } + +#endif + } + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_wait_while_empty () returns\n"); +#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; + int err; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_remove(%d,%d) enter critical section\n", chan, prio); +#endif + +#if __WIN32__ + EnterCriticalSection (&tq_cs); +#else + err = pthread_mutex_lock (&tq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("tq_remove: pthread_mutex_lock err=%d", err); + perror (""); + exit (1); + } +#endif + + 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); + } + +#if __WIN32__ + LeaveCriticalSection (&tq_cs); +#else + err = pthread_mutex_unlock (&tq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("tq_remove: pthread_mutex_unlock err=%d", err); + perror (""); + exit (1); + } +#endif + +#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 + return (result_p); +} + + +/*------------------------------------------------------------------- + * + * Name: tq_is_empty + * + * Purpose: Test queue is empty. + * + * Inputs: None - this applies to all channels and priorities. + * + * Returns: True if nothing in the queue. + * + *--------------------------------------------------------------------*/ + +static int tq_is_empty (void) +{ + int c, p; + + + for (c=0; c= 0 && c < MAX_CHANS); + assert (p >= 0 && p < TQ_NUM_PRIO); + + if (queue_head[c][p] != NULL) + return (0); + } + } + return (1); + +} /* end tq_is_empty */ + + +/*------------------------------------------------------------------- + * + * Name: tq_count + * + * Purpose: Return count of the number of packets in the specified transmit queue. + * + * Inputs: chan - Channel, 0 is first. + * + * prio - Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO. + * + * Returns: Number of items in specified queue. + * + *--------------------------------------------------------------------*/ + +int tq_count (int chan, int prio) +{ + + packet_t p; + int n; + + +/* Don't bother with critical section. */ +/* Only used for debugging a problem. */ + + n = 0; + p = queue_head[chan][prio]; + while (p != NULL) { + n++; + p = ax25_get_nextp(p); + } + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_count(%d,%d) returns %d\n", chan, prio, n); +#endif + + return (n); + +} /* end tq_count */ + +/* end tq.c */ diff --git a/tq.h b/tq.h new file mode 100644 index 0000000..ff1d73f --- /dev/null +++ b/tq.h @@ -0,0 +1,35 @@ + +/*------------------------------------------------------------------ + * + * 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 (int nchan); + +void tq_append (int chan, int prio, packet_t pp); + +void tq_wait_while_empty (void); + +packet_t tq_remove (int chan, int prio); + +int tq_count (int chan, int prio); + +#endif + +/* end tq.h */ diff --git a/tt_text.c b/tt_text.c new file mode 100644 index 0000000..3df216c --- /dev/null +++ b/tt_text.c @@ -0,0 +1,677 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 . +// + +/*------------------------------------------------------------------ + * + * 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. + */ + +/* + * Everything is based on this table. + * Changing it will change everything. + */ + +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' } }; + +#include +#include +#include +#include +#include +#include + +#include "textcolor.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 (char *text, int quiet, char *buttons) +{ + 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 (char *text, int quiet, char *buttons) +{ + 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_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 (char *buttons, int quiet, char *text) +{ + 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 (char *buttons, int quiet, char *text) +{ + char *b = buttons; + char *t = text; + char c; + int row, col; + int errors = 0; + int n; + + *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_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); + + 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++) { + strcat (buttons, argv[n]); + } + + 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); + + return(0); + +} /* end main */ + +#endif /* decoding */ + + + +/* end tt-text.c */ + diff --git a/tt_text.h b/tt_text.h new file mode 100644 index 0000000..23892da --- /dev/null +++ b/tt_text.h @@ -0,0 +1,17 @@ + +/* tt_text.h */ + + +int tt_text_to_multipress (char *text, int quiet, char *buttons); + + +int tt_text_to_two_key (char *text, int quiet, char *buttons); + + +int tt_multipress_to_text (char *buttons, int quiet, char *text); + + +int tt_two_key_to_text (char *buttons, int quiet, char *text); + + +/* end tt_text.h */ \ No newline at end of file diff --git a/tt_user.c b/tt_user.c new file mode 100644 index 0000000..fd048cf --- /dev/null +++ b/tt_user.c @@ -0,0 +1,806 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 . +// + +/*------------------------------------------------------------------ + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "direwolf.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" + +/* + * 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 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. */ + + 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. */ + + double latitude, longitude; /* Location either from user or generated */ + /* position in the corral. */ + + char freq[12]; /* Frequency in format 999.999MHz */ + + char comment[MAX_COMMENT_LEN+1]; /* Free form comment. */ + + char mic_e; /* Position status. */ + + char dao[8]; /* Enhanced position information. */ + +} tt_user[MAX_TT_USERS]; + + +static void clear_user(int i); + +static void xmit_object_report (int i, double c_lat, double c_long, int ambiguity, double c_offs); + + +/*------------------------------------------------------------------ + * + * 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 tt_config_s tt_config; + + +void tt_user_init (struct tt_config_s *p) +{ + int i; + +#if TT_MAIN + /* For unit testing. */ + + memset (&tt_config, 0, sizeof(struct tt_config_s)); + + /* Don't care about the location translation here. */ + + tt_config.retain_time = 20; /* Normally 80 minutes. */ + tt_config.num_xmits = 3; + assert (tt_config.num_xmits <= TT_MAX_XMITS); + tt_config.xmit_delay[0] = 3; /* Before initial transmission. */ + tt_config.xmit_delay[1] = 5; + tt_config.xmit_delay[2] = 5; + + tt_config.corral_lat = 42.61900; + tt_config.corral_lon = -71.34717; + tt_config.corral_offset = 0.02 / 60; + tt_config.corral_ambiguity = 0; + +#else + memcpy (&tt_config, p, sizeof(struct tt_config_s)); +#endif + + for (i=0; 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); + strncpy (tt_user[i].callsign, callsign, MAX_CALLSIGN_LEN); + tt_user[i].callsign[MAX_CALLSIGN_LEN] = '\0'; + 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); + 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(); + } + + strcpy (tt_user[i].freq, freq); + strncpy (tt_user[i].comment, comment, MAX_COMMENT_LEN); + tt_user[i].comment[MAX_COMMENT_LEN] = '\0'; + tt_user[i].mic_e = mic_e; + strncpy(tt_user[i].dao, dao, 6); + } + else { +/* + * Known user. Update with any new information. + */ + assert (i >= 0 && i < MAX_TT_USERS); + + /* Any reason to look at ssid here? */ + + 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 (freq[0] != '\0') { + strcpy (tt_user[i].freq, freq); + } + + if (comment[0] != '\0') { + strncpy (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) { + strncpy(tt_user[i].dao, dao, 6); + tt_user[i].dao[5] = '\0'; + } + } + +/* + * 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 + tt_config.xmit_delay[0]; + + 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; + + for (i=0; i= 0 && i < MAX_TT_USERS); + +/* + * Prepare the object name. + * Tack on "-12" if it is a callsign. + */ + strcpy (object_name, tt_user[i].callsign); + + if (strlen(object_name) <= 6 && tt_user[i].ssid != 0) { + char stemp8[8]; + sprintf (stemp8, "-%d", tt_user[i].ssid); + strcat (object_name, stemp8); + } + + if (tt_user[i].corral_slot == 0) { +/* + * Known location. + */ + olat = tt_user[i].latitude; + olong = tt_user[i].longitude; + } + else { +/* + * Use made up position in the corral. + */ + olat = c_lat - (tt_user[i].corral_slot - 1) * c_offs; + olong = c_long; + } + +/* + * Build comment field from various information. + */ + strcpy (info_comment, ""); + + if (strlen(tt_user[i].comment) != 0) { + strcat (info_comment, tt_user[i].comment); + } + if (tt_user[i].mic_e >= '1' && tt_user[i].mic_e <= '9') { + strcat (info_comment, mic_e_position_comment[tt_user[i].mic_e - '0']); + } + if (strlen(tt_user[i].dao) > 0) { + strcat (info_comment, tt_user[i].dao); + } + + /* Official limit is 43 characters. */ + info_comment[MAX_COMMENT_LEN] = '\0'; + +/* + * Combine with header from configuration file. + * + * (If APRStt gateway has been configured.) + */ + if (tt_config.obj_xmit_header[0] != '\0') { + +// TODO: Should take the call from radio channel configuration. +// Application version is compiled in. +// Config should have only optional via path. + + strcpy (stemp, tt_config.obj_xmit_header); + strcat (stemp, ":"); + + encode_object (object_name, 0, tt_user[i].last_heard, olat, olong, + tt_user[i].overlay, tt_user[i].symbol, + 0,0,0,NULL, 0,0, /* PHGD, C/S */ + atof(tt_user[i].freq), 0, 0, info_comment, object_info); + + strcat (stemp, object_info); + + //text_color_set(DW_COLOR_ERROR); + //printf ("\nDEBUG: %s\n\n", stemp); + + +#if TT_MAIN + + printf ("---> %s\n\n", stemp); + +#else + +/* + * Convert to packet and append to transmit queue. + */ + pp = ax25_from_text (stemp, 1); + + flen = ax25_pack (pp, fbuf); + +/* + * Process as if we heard ourself. + */ + // TODO: We need radio channel where this came from. + // It would make a difference if running two radios + // and they have different station identifiers. + + int chan = 0; + igate_send_rec_packet (chan, pp); + + /* Remember it so we don't digipeat our own. */ + + dedupe_remember (pp, tt_config.obj_xmit_chan); + + tq_append (tt_config.obj_xmit_chan, TQ_PRIO_1_LO, pp); +#endif + } +} + + + +/*------------------------------------------------------------------ + * + * 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 m comment\n"); + for (i=0; i. +// + + +/*------------------------------------------------------------------- + * + * Name: udp_test.c + * + * Purpose: Unit test for the udp reception with AFSK demodulator. + * + * Inputs: Get data by listening on a given UDP port (first parameter is the udp port) + * + * Description: This can be used to test the AFSK demodulator with udp data + * + *--------------------------------------------------------------------*/ + +// #define X 1 + + +#include +#include +//#include +#include +#include +#include +#include + +#define UDPTEST_C 1 + +#include "audio.h" +#include "demod.h" +// #include "fsk_demod_agc.h" +#include "textcolor.h" +#include "ax25_pad.h" +#include "hdlc_rec2.h" +#include +#include +#include +#include +static FILE *fp; +static int e_o_f; +static int packets_decoded = 0; +//Bytes read in the current UDP socket buffer +static int bytes_read = 0; +//Total bytes read from UDP +static int total_bytes_read = 0; +//UDP socket used for receiving data +static int sock; + +//UDP receiving port +#define DEFAULT_UDP_PORT 6667 +//Maximum size of the UDP buffer (for allowing IP routing, udp packets are often limited to 1472 bytes) +#define UDP_BUF_MAXLEN 20000 +//UDP receiving buffer , may use double or FIFO buffers in the future for better performance +unsigned char udp_buf[UDP_BUF_MAXLEN]; + +//TODO Provide cmdline parameters or config to change these values +#define DEFAULT_UDP_NUM_CHANNELS 1 +#define DEFAULT_UDP_SAMPLES_PER_SEC 48000 +#define DEFAULT_UDP_BITS_PER_SAMPLE 16 + + +int main (int argc, char *argv[]) +{ + + struct audio_s modem; + int channel; + time_t start_time; + int udp_port; + + text_color_init(1); + text_color_set(DW_COLOR_INFO); + if (argc < 2) { + udp_port = DEFAULT_UDP_PORT; + printf ("Using default UDP port : %d\n", udp_port); + } else { + udp_port = atoi(argv[1]); + } + + struct sockaddr_in si_me; + int i, slen=sizeof(si_me); + int data_size = 0; + + if ((sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { + fprintf (stderr, "Couldn't create socket %d\n", errno); + exit(errno); + } + + memset((char *) &si_me, 0, sizeof(si_me)); + si_me.sin_family = AF_INET; + si_me.sin_port = htons(6667); + si_me.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(sock, &si_me, sizeof(si_me))==-1) { + fprintf (stderr, "Couldn't bind socket %d\n", errno); + exit(errno); + } + +#ifdef DEBUG_RECEIVED_DATA + fp = fopen("udp.raw", "w"); + if (fp == NULL) { + fprintf (stderr, "Couldn't open file for read: %s\n", argv[1]); + //perror ("more info?"); + exit (1); + } +#endif + + start_time = time(NULL); + +/* + * First apply defaults. + * TODO: split config into two parts: _init (use here) and _read (only in direwolf). + */ + modem.num_channels = DEFAULT_UDP_NUM_CHANNELS; + modem.samples_per_sec = DEFAULT_UDP_SAMPLES_PER_SEC; + modem.bits_per_sample = DEFAULT_UDP_BITS_PER_SAMPLE; + + /* TODO: should have a command line option for this. */ + //modem.fix_bits = RETRY_NONE; + //modem.fix_bits = RETRY_SINGLE; + //modem.fix_bits = RETRY_DOUBLE; + //modem.fix_bits = RETRY_TRIPLE; + modem.fix_bits = RETRY_TWO_SEP; + //Only one channel for UDP + channel = 0; + + modem.modem_type[channel] = AFSK; + modem.mark_freq[channel] = DEFAULT_MARK_FREQ; + modem.space_freq[channel] = DEFAULT_SPACE_FREQ; + modem.baud[channel] = DEFAULT_BAUD; + + strcpy (modem.profiles[channel], "C"); + // temp + // strcpy (modem.profiles[channel], "F"); + modem.num_subchan[channel] = strlen(modem.profiles); + + //TODO: add -h command line option. +//#define HF 1 + +#if HF + modem.mark_freq[channel] = 1600; + modem.space_freq[channel] = 1800; + modem.baud[channel] = 300; + strcpy (modem.profiles[channel], "B"); + modem.num_subchan[channel] = strlen(modem.profiles); +#endif + + + modem.num_freq[channel] = 1; + modem.offset[channel] = 0; + + + text_color_set(DW_COLOR_INFO); + printf ("%d samples per second\n", modem.samples_per_sec); + printf ("%d bits per sample\n", modem.bits_per_sample); + printf ("%d audio channels\n", modem.num_channels); +/* + * Initialize the AFSK demodulator and HDLC decoder. + */ + multi_modem_init (&modem); + + + e_o_f = 0; + bytes_read = 0; + data_size = 0; + + while ( ! e_o_f ) + { + + int audio_sample; + int c; + + //If all the data in the udp buffer has been processed, get new data from udp socket + if (bytes_read == data_size) { + data_size = buffer_get(UDP_BUF_MAXLEN); + //Got EOF packet + if (data_size >= 0 && data_size <= 1) { + printf("Got NULL packet : terminate decoding (packet received with size %d)", data_size); + e_o_f = 1; + break; + } + + bytes_read = 0; + } + + + /* This reads either 1 or 2 bytes depending on */ + /* bits per sample. */ + + audio_sample = demod_get_sample (); + + if (audio_sample >= 256 * 256) + e_o_f = 1; + 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); + printf ("\n\n"); + printf ("%d packets decoded in %d seconds.\n", packets_decoded, (int)(time(NULL) - start_time)); +#ifdef DEBUG_RECEIVED_DATA + fclose(fp); +#endif + exit (0); +} + +int buffer_get (unsigned int size) { + struct sockaddr_in si_other; + int slen=sizeof(si_other); + int ch, res,i; + if (size > UDP_BUF_MAXLEN) { + printf("size too big %d", size); + return -1; + } + + res = recvfrom(sock, udp_buf, size, 0, &si_other, &slen); +#ifdef DEBUG_RECEIVED_DATA + fwrite(udp_buf,res,1,fp); +#endif + + return res; +} +/* + * Simulate sample from the audio device. + */ +int audio_get (void) +{ + int ch; + ch = udp_buf[bytes_read]; + bytes_read++; + total_bytes_read++; + + return (ch); +} + + + +/* + * Rather than queuing up frames with bad FCS, + * try to fix them immediately. + */ + +void rdq_append (rrbb_t rrbb) +{ + int chan; + int alevel; + int subchan; + + + chan = rrbb_get_chan(rrbb); + subchan = rrbb_get_subchan(rrbb); + alevel = rrbb_get_audio_level(rrbb); + + hdlc_rec2_try_to_fix_later (rrbb, chan, subchan, alevel); +} + + +/* + * This is called when we have a good frame. + */ + +void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, retry_t retries, char *spectrum) +{ + + //int err; + //char *p; + char stemp[500]; + unsigned char *pinfo; + int info_len; + int h; + char heard[20]; + //packet_t pp; + + + packets_decoded++; + + + ax25_format_addrs (pp, stemp); + + info_len = ax25_get_info (pp, &pinfo); + + /* Print so we can see what is going on. */ + +#if 1 + /* Display audio input level. */ + /* Who are we hearing? Original station or digipeater. */ + + h = ax25_get_heard(pp); + ax25_get_addr_with_ssid(pp, h, heard); + + text_color_set(DW_COLOR_DEBUG); + printf ("\n"); + + if (h != AX25_SOURCE) { + printf ("Digipeater "); + } + printf ("%s audio level = %d [%s] %s\n", heard, alevel, retry_text[(int)retries], spectrum); + + +#endif + +// Display non-APRS packets in a different color. + + if (ax25_is_aprs(pp)) { + text_color_set(DW_COLOR_REC); + printf ("[%d] ", chan); + } + else { + text_color_set(DW_COLOR_DEBUG); + printf ("[%d] ", chan); + } + + printf ("%s", stemp); /* stations followed by : */ + ax25_safe_print ((char *)pinfo, info_len, 0); + printf ("\n"); + + ax25_delete (pp); + +} /* end app_process_rec_packet */ + + + + + + +/* 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 time. */ + + + +double dtime_now (void) +{ +#if __WIN32__ + /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ + + FILETIME ft; + + GetSystemTimeAsFileTime (&ft); + + return ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + + (double)ft.dwLowDateTime ) / 10000000.) - 11644473600.); +#else + /* tv_sec is seconds from Jan 1, 1970. */ + + struct timespec ts; + int sec, ns; + double x1, x2; + double result; + + clock_gettime (CLOCK_REALTIME, &ts); + + //result = (double)(ts.tv_sec) + (double)(ts.tv_nsec) / 1000000000.; + //result = (double)(ts.tv_sec) + ((double)(ts.tv_nsec) * .001 * .001 *.001); + sec = (int)(ts.tv_sec); + ns = (int)(ts.tv_nsec); + x1 = (double)(sec); + //x1 = (double)(sec-1300000000); /* try to work around strange result. */ + //x2 = (double)(ns) * .001 * .001 *.001; + x2 = (double)(ns/1000000) *.001; + result = x1 + x2; + + /* Sometimes this returns NAN. How could that possibly happen? */ + /* This is REALLY BIZARRE! */ + /* Multiplying a number by a billionth often produces NAN. */ + /* Adding a fraction to a number over a billion often produces NAN. */ + + /* Hardware problem??? Need to test on different computer. */ + + if (isnan(result)) { + text_color_set(DW_COLOR_ERROR); + printf ("\ndtime_now(): %d, %d -> %.3f + %.3f -> NAN!!!\n\n", sec, ns, x1, x2); + } + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + printf ("dtime_now() returns %.3f\n", result); +#endif + + return (result); +#endif +} + + + +/* end atest.c */ diff --git a/utm/LatLong-UTMconversion.c b/utm/LatLong-UTMconversion.c new file mode 100644 index 0000000..a0c4cd9 --- /dev/null +++ b/utm/LatLong-UTMconversion.c @@ -0,0 +1,190 @@ +//LatLong- UTM conversion.c +//Lat Long - UTM, UTM - Lat Long conversions + +#include +#include +#include +#include "constants.h" +#include "LatLong-UTMconversion.h" + + +/*Reference ellipsoids derived from Peter H. Dana's website- +http://www.utexas.edu/depts/grg/gcraft/notes/datum/elist.html +Department of Geography, University of Texas at Austin +Internet: pdana@mail.utexas.edu +3/22/95 + +Source +Defense Mapping Agency. 1987b. DMA Technical Report: Supplement to Department of Defense World Geodetic System +1984 Technical Report. Part I and II. Washington, DC: Defense Mapping Agency +*/ + + + +void LLtoUTM(int ReferenceEllipsoid, const double Lat, const double Long, + double *UTMNorthing, double *UTMEasting, char* UTMZone) +{ +//converts lat/long to UTM coords. Equations from USGS Bulletin 1532 +//East Longitudes are positive, West longitudes are negative. +//North latitudes are positive, South latitudes are negative +//Lat and Long are in decimal degrees + //Written by Chuck Gantz- chuck.gantz@globalstar.com + + double a = ellipsoid[ReferenceEllipsoid].EquatorialRadius; + double eccSquared = ellipsoid[ReferenceEllipsoid].eccentricitySquared; + double k0 = 0.9996; + + double LongOrigin; + double eccPrimeSquared; + double N, T, C, A, M; + +//Make sure the longitude is between -180.00 .. 179.9 + double LongTemp = (Long+180)-(int)((Long+180)/360)*360-180; // -180.00 .. 179.9; + + double LatRad = Lat*deg2rad; + double LongRad = LongTemp*deg2rad; + double LongOriginRad; + int ZoneNumber; + + ZoneNumber = (int)((LongTemp + 180)/6) + 1; + + if( Lat >= 56.0 && Lat < 64.0 && LongTemp >= 3.0 && LongTemp < 12.0 ) + ZoneNumber = 32; + + // Special zones for Svalbard + if( Lat >= 72.0 && Lat < 84.0 ) + { + if( LongTemp >= 0.0 && LongTemp < 9.0 ) ZoneNumber = 31; + else if( LongTemp >= 9.0 && LongTemp < 21.0 ) ZoneNumber = 33; + else if( LongTemp >= 21.0 && LongTemp < 33.0 ) ZoneNumber = 35; + else if( LongTemp >= 33.0 && LongTemp < 42.0 ) ZoneNumber = 37; + } + LongOrigin = (ZoneNumber - 1)*6 - 180 + 3; //+3 puts origin in middle of zone + LongOriginRad = LongOrigin * deg2rad; + + //compute the UTM Zone from the latitude and longitude + sprintf(UTMZone, "%d%c", ZoneNumber, UTMLetterDesignator(Lat)); + + eccPrimeSquared = (eccSquared)/(1-eccSquared); + + N = a/sqrt(1-eccSquared*sin(LatRad)*sin(LatRad)); + T = tan(LatRad)*tan(LatRad); + C = eccPrimeSquared*cos(LatRad)*cos(LatRad); + A = cos(LatRad)*(LongRad-LongOriginRad); + + M = a*((1 - eccSquared/4 - 3*eccSquared*eccSquared/64 - 5*eccSquared*eccSquared*eccSquared/256)*LatRad + - (3*eccSquared/8 + 3*eccSquared*eccSquared/32 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(2*LatRad) + + (15*eccSquared*eccSquared/256 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(4*LatRad) + - (35*eccSquared*eccSquared*eccSquared/3072)*sin(6*LatRad)); + + *UTMEasting = (double)(k0*N*(A+(1-T+C)*A*A*A/6 + + (5-18*T+T*T+72*C-58*eccPrimeSquared)*A*A*A*A*A/120) + + 500000.0); + + *UTMNorthing = (double)(k0*(M+N*tan(LatRad)*(A*A/2+(5-T+9*C+4*C*C)*A*A*A*A/24 + + (61-58*T+T*T+600*C-330*eccPrimeSquared)*A*A*A*A*A*A/720))); + if(Lat < 0) + *UTMNorthing += 10000000.0; //10000000 meter offset for southern hemisphere +} + +char UTMLetterDesignator(double Lat) +{ +//This routine determines the correct UTM letter designator for the given latitude +//returns 'Z' if latitude is outside the UTM limits of 84N to 80S + //Written by Chuck Gantz- chuck.gantz@globalstar.com + char LetterDesignator; + + if((84 >= Lat) && (Lat >= 72)) LetterDesignator = 'X'; + else if((72 > Lat) && (Lat >= 64)) LetterDesignator = 'W'; + else if((64 > Lat) && (Lat >= 56)) LetterDesignator = 'V'; + else if((56 > Lat) && (Lat >= 48)) LetterDesignator = 'U'; + else if((48 > Lat) && (Lat >= 40)) LetterDesignator = 'T'; + else if((40 > Lat) && (Lat >= 32)) LetterDesignator = 'S'; + else if((32 > Lat) && (Lat >= 24)) LetterDesignator = 'R'; + else if((24 > Lat) && (Lat >= 16)) LetterDesignator = 'Q'; + else if((16 > Lat) && (Lat >= 8)) LetterDesignator = 'P'; + else if(( 8 > Lat) && (Lat >= 0)) LetterDesignator = 'N'; + else if(( 0 > Lat) && (Lat >= -8)) LetterDesignator = 'M'; + else if((-8> Lat) && (Lat >= -16)) LetterDesignator = 'L'; + else if((-16 > Lat) && (Lat >= -24)) LetterDesignator = 'K'; + else if((-24 > Lat) && (Lat >= -32)) LetterDesignator = 'J'; + else if((-32 > Lat) && (Lat >= -40)) LetterDesignator = 'H'; + else if((-40 > Lat) && (Lat >= -48)) LetterDesignator = 'G'; + else if((-48 > Lat) && (Lat >= -56)) LetterDesignator = 'F'; + else if((-56 > Lat) && (Lat >= -64)) LetterDesignator = 'E'; + else if((-64 > Lat) && (Lat >= -72)) LetterDesignator = 'D'; + else if((-72 > Lat) && (Lat >= -80)) LetterDesignator = 'C'; + else LetterDesignator = 'Z'; //This is here as an error flag to show that the Latitude is outside the UTM limits + + return LetterDesignator; +} + + +void UTMtoLL(int ReferenceEllipsoid, const double UTMNorthing, const double UTMEasting, const char* UTMZone, + double *Lat, double *Long ) +{ +//converts UTM coords to lat/long. Equations from USGS Bulletin 1532 +//East Longitudes are positive, West longitudes are negative. +//North latitudes are positive, South latitudes are negative +//Lat and Long are in decimal degrees. + //Written by Chuck Gantz- chuck.gantz@globalstar.com + + double k0 = 0.9996; + double a = ellipsoid[ReferenceEllipsoid].EquatorialRadius; + double eccSquared = ellipsoid[ReferenceEllipsoid].eccentricitySquared; + double eccPrimeSquared; + double e1 = (1-sqrt(1-eccSquared))/(1+sqrt(1-eccSquared)); + double N1, T1, C1, R1, D, M; + double LongOrigin; + double mu, phi1, phi1Rad; + double x, y; + int ZoneNumber; + char* ZoneLetter; + int NorthernHemisphere; //1 for northern hemispher, 0 for southern + + x = UTMEasting - 500000.0; //remove 500,000 meter offset for longitude + y = UTMNorthing; + + ZoneNumber = strtoul(UTMZone, &ZoneLetter, 10); + if (*ZoneLetter == '\0') + { + NorthernHemisphere = 1; //no letter - assume northern hemisphere + } + else if((*ZoneLetter >= 'N' && *ZoneLetter <= 'X') || + (*ZoneLetter >= 'n' && *ZoneLetter <= 'x')) + { + NorthernHemisphere = 1; //point is in northern hemisphere + } + else + { + NorthernHemisphere = 0; //point is in southern hemisphere + y -= 10000000.0; //remove 10,000,000 meter offset used for southern hemisphere + } + + LongOrigin = (ZoneNumber - 1)*6 - 180 + 3; //+3 puts origin in middle of zone + + eccPrimeSquared = (eccSquared)/(1-eccSquared); + + M = y / k0; + mu = M/(a*(1-eccSquared/4-3*eccSquared*eccSquared/64-5*eccSquared*eccSquared*eccSquared/256)); + + phi1Rad = mu + (3*e1/2-27*e1*e1*e1/32)*sin(2*mu) + + (21*e1*e1/16-55*e1*e1*e1*e1/32)*sin(4*mu) + +(151*e1*e1*e1/96)*sin(6*mu); + phi1 = phi1Rad*rad2deg; + + N1 = a/sqrt(1-eccSquared*sin(phi1Rad)*sin(phi1Rad)); + T1 = tan(phi1Rad)*tan(phi1Rad); + C1 = eccPrimeSquared*cos(phi1Rad)*cos(phi1Rad); + R1 = a*(1-eccSquared)/pow(1-eccSquared*sin(phi1Rad)*sin(phi1Rad), 1.5); + D = x/(N1*k0); + + *Lat = phi1Rad - (N1*tan(phi1Rad)/R1)*(D*D/2-(5+3*T1+10*C1-4*C1*C1-9*eccPrimeSquared)*D*D*D*D/24 + +(61+90*T1+298*C1+45*T1*T1-252*eccPrimeSquared-3*C1*C1)*D*D*D*D*D*D/720); + *Lat = *Lat * rad2deg; + + *Long = (D-(1+2*T1+C1)*D*D*D/6+(5-2*C1+28*T1-3*C1*C1+8*eccPrimeSquared+24*T1*T1) + *D*D*D*D*D/120)/cos(phi1Rad); + *Long = LongOrigin + *Long * rad2deg; + +} diff --git a/utm/LatLong-UTMconversion.h b/utm/LatLong-UTMconversion.h new file mode 100644 index 0000000..e05bfa0 --- /dev/null +++ b/utm/LatLong-UTMconversion.h @@ -0,0 +1,30 @@ +//LatLong- UTM conversion..h +//definitions for lat/long to UTM and UTM to lat/lng conversions +#include + +#ifndef LATLONGCONV +#define LATLONGCONV + +void LLtoUTM(int ReferenceEllipsoid, const double Lat, const double Long, + double *UTMNorthing, double *UTMEasting, char* UTMZone); +void UTMtoLL(int ReferenceEllipsoid, const double UTMNorthing, const double UTMEasting, const char* UTMZone, + double *Lat, double *Long ); +char UTMLetterDesignator(double Lat); +void LLtoSwissGrid(const double Lat, const double Long, + double *SwissNorthing, double *SwissEasting); +void SwissGridtoLL(const double SwissNorthing, const double SwissEasting, + double *Lat, double *Long); + +struct Ellipsoid_s { + int id; + char* ellipsoidName; + double EquatorialRadius; + double eccentricitySquared; +}; + +typedef struct Ellipsoid_s Ellipsoid; + +#define WSG84 23 + + +#endif diff --git a/utm/README.txt b/utm/README.txt new file mode 100644 index 0000000..fff3b39 --- /dev/null +++ b/utm/README.txt @@ -0,0 +1,10 @@ + +Most of the files in this directory copied from + +http://www.gpsy.com/gpsinfo/geotoutm/ + + +A few minor modifications were made: + +1. Convert from C++ to C. +2. Make the zone check more robust. \ No newline at end of file diff --git a/utm/SwissGrid.cpp b/utm/SwissGrid.cpp new file mode 100644 index 0000000..b3dd3c5 --- /dev/null +++ b/utm/SwissGrid.cpp @@ -0,0 +1,140 @@ + +#include +#include "constants.h" +#include "LatLong- UTM conversion.h" + +//forward declarations +double CorrRatio(double LatRad, const double C); +double NewtonRaphson(const double initEstimate); + + +void LLtoSwissGrid(const double Lat, const double Long, + double &SwissNorthing, double &SwissEasting) +{ +//converts lat/long to Swiss Grid coords. Equations from "Supplementary PROJ.4 Notes- +//Swiss Oblique Mercator Projection", August 5, 1995, Release 4.3.3, by Gerald I. Evenden +//Lat and Long are in decimal degrees +//This transformation is, of course, only valid in Switzerland + //Written by Chuck Gantz- chuck.gantz@globalstar.com + double a = ellipsoid[3].EquatorialRadius; //Bessel ellipsoid + double eccSquared = ellipsoid[3].eccentricitySquared; + double ecc = sqrt(eccSquared); + + double LongOrigin = 7.43958333; //E7d26'22.500" + double LatOrigin = 46.95240556; //N46d57'8.660" + + double LatRad = Lat*deg2rad; + double LongRad = Long*deg2rad; + double LatOriginRad = LatOrigin*deg2rad; + double LongOriginRad = LongOrigin*deg2rad; + + double c = sqrt(1+((eccSquared * pow(cos(LatOriginRad), 4)) / (1-eccSquared))); + + double equivLatOrgRadPrime = asin(sin(LatOriginRad) / c); + + //eqn. 1 + double K = log(tan(FOURTHPI + equivLatOrgRadPrime/2)) + -c*(log(tan(FOURTHPI + LatOriginRad/2)) + - ecc/2 * log((1+ecc*sin(LatOriginRad)) / (1-ecc*sin(LatOriginRad)))); + + + double LongRadPrime = c*(LongRad - LongOriginRad); //eqn 2 + double w = c*(log(tan(FOURTHPI + LatRad/2)) + - ecc/2 * log((1+ecc*sin(LatRad)) / (1-ecc*sin(LatRad)))) + K; //eqn 1 + double LatRadPrime = 2 * (atan(exp(w)) - FOURTHPI); //eqn 1 + + //eqn 3 + double sinLatDoublePrime = cos(equivLatOrgRadPrime) * sin(LatRadPrime) + - sin(equivLatOrgRadPrime) * cos(LatRadPrime) * cos(LongRadPrime); + double LatRadDoublePrime = asin(sinLatDoublePrime); + + //eqn 4 + double sinLongDoublePrime = cos(LatRadPrime)*sin(LongRadPrime) / cos(LatRadDoublePrime); + double LongRadDoublePrime = asin(sinLongDoublePrime); + + double R = a*sqrt(1-eccSquared) / (1-eccSquared*sin(LatOriginRad) * sin(LatOriginRad)); + + SwissNorthing = R*log(tan(FOURTHPI + LatRadDoublePrime/2)) + 200000.0; //eqn 5 + SwissEasting = R*LongRadDoublePrime + 600000.0; //eqn 6 + +} + + +void SwissGridtoLL(const double SwissNorthing, const double SwissEasting, + double& Lat, double& Long) +{ + double a = ellipsoid[3].EquatorialRadius; //Bessel ellipsoid + double eccSquared = ellipsoid[3].eccentricitySquared; + double ecc = sqrt(eccSquared); + + double LongOrigin = 7.43958333; //E7d26'22.500" + double LatOrigin = 46.95240556; //N46d57'8.660" + + double LatOriginRad = LatOrigin*deg2rad; + double LongOriginRad = LongOrigin*deg2rad; + + double R = a*sqrt(1-eccSquared) / (1-eccSquared*sin(LatOriginRad) * sin(LatOriginRad)); + + double LatRadDoublePrime = 2*(atan(exp((SwissNorthing - 200000.0)/R)) - FOURTHPI); //eqn. 7 + double LongRadDoublePrime = (SwissEasting - 600000.0)/R; //eqn. 8 with equation corrected + + + double c = sqrt(1+((eccSquared * pow(cos(LatOriginRad), 4)) / (1-eccSquared))); + double equivLatOrgRadPrime = asin(sin(LatOriginRad) / c); + + double sinLatRadPrime = cos(equivLatOrgRadPrime)*sin(LatRadDoublePrime) + + sin(equivLatOrgRadPrime)*cos(LatRadDoublePrime)*cos(LongRadDoublePrime); + double LatRadPrime = asin(sinLatRadPrime); + + double sinLongRadPrime = cos(LatRadDoublePrime)*sin(LongRadDoublePrime)/cos(LatRadPrime); + double LongRadPrime = asin(sinLongRadPrime); + + Long = (LongRadPrime/c + LongOriginRad) * rad2deg; + + Lat = NewtonRaphson(LatRadPrime) * rad2deg; + +} + +double NewtonRaphson(const double initEstimate) +{ + double Estimate = initEstimate; + double tol = 0.00001; + double corr; + + double eccSquared = ellipsoid[3].eccentricitySquared; + double ecc = sqrt(eccSquared); + + double LatOrigin = 46.95240556; //N46d57'8.660" + double LatOriginRad = LatOrigin*deg2rad; + + double c = sqrt(1+((eccSquared * pow(cos(LatOriginRad), 4)) / (1-eccSquared))); + + double equivLatOrgRadPrime = asin(sin(LatOriginRad) / c); + + //eqn. 1 + double K = log(tan(FOURTHPI + equivLatOrgRadPrime/2)) + -c*(log(tan(FOURTHPI + LatOriginRad/2)) + - ecc/2 * log((1+ecc*sin(LatOriginRad)) / (1-ecc*sin(LatOriginRad)))); + double C = (K - log(tan(FOURTHPI + initEstimate/2)))/c; + + do + { + corr = CorrRatio(Estimate, C); + Estimate = Estimate - corr; + } + while (fabs(corr) > tol); + + return Estimate; +} + + + +double CorrRatio(double LatRad, const double C) +{ + double eccSquared = ellipsoid[3].eccentricitySquared; + double ecc = sqrt(eccSquared); + double corr = (C + log(tan(FOURTHPI + LatRad/2)) + - ecc/2 * log((1+ecc*sin(LatRad)) / (1-ecc*sin(LatRad)))) * (((1-eccSquared*sin(LatRad)*sin(LatRad)) * cos(LatRad)) / (1-eccSquared)); + + return corr; +} diff --git a/utm/UTMConversions.cpp b/utm/UTMConversions.cpp new file mode 100644 index 0000000..34e5ae9 --- /dev/null +++ b/utm/UTMConversions.cpp @@ -0,0 +1,39 @@ +//UTM Conversion.cpp- test program for lat/long to UTM and UTM to lat/long conversions +#include +#include +#include "LatLong-UTMconversion.h" + + +void main() +{ + double Lat = 47.37816667; + double Long = 8.23250000; + double UTMNorthing; + double UTMEasting; + double SwissNorthing; + double SwissEasting; + char UTMZone[4]; + int RefEllipsoid = 23;//WGS-84. See list with file "LatLong- UTM conversion.cpp" for id numbers + + cout << "Starting position(Lat, Long): " << Lat << " " << Long < +#include + +#include "LatLong-UTMconversion.h" + + +static void usage(); + + +void main (int argc, char *argv[]) +{ + double easting; + double northing; + double lat, lon; + char zone[100]; + int znum; + char *zlet; + + if (argc != 4) usage(); + + strcpy (zone, argv[1]); + znum = strtoul(zone, &zlet, 10); + + if (znum < 1 || znum > 60) { + fprintf (stderr, "Zone number is out of range.\n\n"); + usage(); + } + + //printf ("zlet = %c 0x%02x\n", *zlet, *zlet); + if (*zlet != '\0' && strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) == NULL) { + fprintf (stderr, "Latitudinal band must be one of CDEFGHJKLMNPQRSTUVWX.\n\n"); + usage(); + } + + easting = atof(argv[2]); + if (easting < 0 || easting > 999999) { + fprintf (stderr, "Easting value is out of range.\n\n"); + usage(); + } + + northing = atof(argv[3]); + if (northing < 0 || northing > 9999999) { + fprintf (stderr, "Northing value is out of range.\n\n"); + usage(); + } + + UTMtoLL (WSG84, northing, easting, zone, &lat, &lon); + + printf ("latitude = %f, longitude = %f\n", lat, lon); +} + + +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, "Example:\n"); + fprintf (stderr, "\tutm2ll 19T 306130 4726010\n"); + + exit (1); +} \ No newline at end of file diff --git a/version.h b/version.h new file mode 100644 index 0000000..165b8a4 --- /dev/null +++ b/version.h @@ -0,0 +1,7 @@ + +/* Dire Wolf version 1.0 */ + +#define APP_TOCALL "APDW" + +#define MAJOR_VERSION 1 +#define MINOR_VERSION 0 diff --git a/xmit.c b/xmit.c new file mode 100644 index 0000000..cde06d1 --- /dev/null +++ b/xmit.c @@ -0,0 +1,728 @@ +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * 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 +#include +#include +#include +#include +#include + +//#include +#include + +#if __WIN32__ +#include +#endif + +#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" + + +static int xmit_num_channels; /* Number of radio channels. */ + + +/* + * 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_bits_per_sec[MAX_CHANS]; /* Data transmission rate. */ + /* Often called baud rate which is equivalent in */ + /* this case but could be different with other */ + /* modulation techniques. */ + + +#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) + + + +static void * xmit_thread (void *arg); +static int wait_for_clear_channel (int channel, int nowait, int slotttime, int persist); + + +/*------------------------------------------------------------------- + * + * 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 to actually send the packets + * at the appropriate time. + * + *--------------------------------------------------------------------*/ + + + +void xmit_init (struct audio_s *p_modem) +{ + int j; +#if __WIN32__ + HANDLE xmit_th; +#else + //pthread_attr_t attr; + //struct sched_param sp; + pthread_t xmit_tid; +#endif + int e; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("xmit_init ( ... )\n"); +#endif + +/* + * 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. + */ + xmit_num_channels = p_modem->num_channels; + assert (xmit_num_channels >= 1 && xmit_num_channels <= MAX_CHANS); + + for (j=0; jbaud[j]; + xmit_slottime[j] = p_modem->slottime[j]; + xmit_persist[j] = p_modem->persist[j]; + xmit_txdelay[j] = p_modem->txdelay[j]; + xmit_txtail[j] = p_modem->txtail[j]; + } + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("xmit_init: about to call tq_init \n"); +#endif + tq_init (xmit_num_channels); + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("xmit_init: about to create thread \n"); +#endif + +//TODO: xmit thread should be higher priority to avoid +// underrun on the audio output device. + +#if __WIN32__ + xmit_th = _beginthreadex (NULL, 0, xmit_thread, NULL, 0, NULL); + if (xmit_th == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not create xmit thread\n"); + return; + } +#else + +#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, &attr, xmit_thread, (void *)0); + pthread_attr_destroy (&attr); +#else + e = pthread_create (&xmit_tid, NULL, xmit_thread, (void *)0); +#endif + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Could not create xmit thread"); + 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 + * + * + * 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; + } +} + +/*------------------------------------------------------------------- + * + * Name: xmit_thread + * + * Purpose: Initialize the transmit process. + * + * Inputs: None. + * + * 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. + * + * If more than one audio channel is being used, a separate + * pair of transmit queues is used for each channel. + * + * + * Thought for future research: + * + * 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 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. + * For now, one frame per transmission. + * + * 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 MAXFRAME configuration file item, + * we try setting the maximum number automatically. + * 1 for digipeated frames, 7 for others. + * + *--------------------------------------------------------------------*/ + +static void * xmit_thread (void *arg) +{ + packet_t pp; + unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; + int flen; + int c, p; + char stemp[1024]; /* max size needed? */ + int info_len; + unsigned char *pinfo; + 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 ok; + + int maxframe; /* Maximum number of frames for one transmission. */ + int numframe; /* 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. */ + + + + while (1) { + + tq_wait_while_empty (); +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("xmit_thread: woke up\n"); +#endif + + for (p=0; p 0) { + + pp = tq_remove (c, p); +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp); +#endif + ax25_format_addrs (pp, stemp); + info_len = ax25_get_info (pp, &pinfo); + text_color_set(DW_COLOR_XMIT); + dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L'); + dw_printf ("%s", stemp); /* stations followed by : */ + ax25_safe_print ((char *)pinfo, info_len, 0); + dw_printf ("\n"); + + flen = ax25_pack (pp, fbuf); + assert (flen <= sizeof(fbuf)); +/* + * Transmit the frame. + */ + num_bits += hdlc_send_frame (c, fbuf, flen); + numframe++; + ax25_delete (pp); + } + +/* + * Generous TXTAIL because we don't know exactly when the sound is done. + */ + + post_flags = MS_TO_BITS(xmit_txtail[c] * 10, c) / 8; + num_bits += hdlc_send_flags (c, post_flags, 1); + + +/* + * We don't know when the sound has actually been produced. + * hldc_send finishes before anything starts coming out of the speaker. + * It's all queued up somewhere. + * + * Calculate duration of entire frame in milliseconds. + * + * Subtract out elapsed time already since PTT was turned to determine + * how much longer to wait til we turn PTT off. + */ + duration = BITS_TO_MS(num_bits, c); + 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: maxframe = %d, numframe = %d\n", maxframe, numframe); +#endif + +/* + * Wait for all audio to be out before continuing. + * Provide a hint at delay required in case we don't have a + * way to ask the hardware when all the sound has been pushed out. + */ +// TODO: We have an issue if this is negative. That means +// we couldn't generate the data fast enough for the sound +// system output and there probably gaps in the signal. + + audio_wait(wait_more); + +/* + * Turn off transmitter. + */ + + ptt_set (c, 0); + } + else { +/* + * Timeout waiting for clear channel. + * Discard the packet. + * Display with ERROR color rather than XMIT color. + */ + + 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] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L'); + + dw_printf ("%s", stemp); /* stations followed by : */ + ax25_safe_print ((char *)pinfo, info_len, 0); + dw_printf ("\n"); + ax25_delete (pp); + + } + } /* for each channel */ + } /* for high priority then low priority */ + } + } + +} /* end xmit_thread */ + + + +/*------------------------------------------------------------------- + * + * Name: wait_for_clear_channel + * + * Purpose: Wait for the radio channel to be clear and any + * additional time for collision avoidance. + * + * Inputs: channel - Radio channel number. + * + * nowait - Should be true for the high priority queue + * (packets being digipeated). This will + * allow transmission immediately when the + * channel is clear rather than waiting a + * random amount of time. + * + * slottime - Amount of time to wait for each iteration + * of the waiting algorithm. 10 mSec units. + * + * persist - Probability of transmitting + * + * Returns: 1 for OK. 0 for timeout. + * + * Description: + * + * Transmit delay algorithm: + * + * Wait for channel to be clear. + * Return if nowait is true. + * + * 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. */ + +#define WAIT_TIMEOUT_MS (60 * 1000) +#define WAIT_CHECK_EVERY_MS 10 + +static int wait_for_clear_channel (int channel, int nowait, int slottime, int persist) +{ + int r; + int n; + + n = 0; + while (hdlc_rec_data_detect_any(channel)) { + SLEEP_MS(WAIT_CHECK_EVERY_MS); + n++; + if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) { + return 0; + } + } + + if (nowait) { + return 1; + } + + while (1) { + + SLEEP_MS (slottime * 10); + + if (hdlc_rec_data_detect_any(channel)) { + continue; + } + + r = rand() & 0xff; + if (r <= persist) { + return 1; + } + } + +} /* end wait_for_clear_channel */ + + + + +/* 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 time. */ + + + +double dtime_now (void) +{ +#if __WIN32__ + /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ + + FILETIME ft; + + GetSystemTimeAsFileTime (&ft); + + return ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + + (double)ft.dwLowDateTime ) / 10000000.) - 11644473600.); +#else + /* tv_sec is seconds from Jan 1, 1970. */ + + struct timespec ts; + int sec, ns; + double x1, x2; + double result; + + clock_gettime (CLOCK_REALTIME, &ts); + + sec = (int)(ts.tv_sec); + ns = (int)(ts.tv_nsec); + x1 = (double)(sec); + x2 = (double)(ns/1000000) *.001; + result = x1 + x2; + + /* Sometimes this returns NAN. How could that possibly happen? */ + /* This is REALLY BIZARRE! */ + /* Multiplying a number by a billionth often produces NAN. */ + /* Adding a fraction to a number over a billion often produces NAN. */ + + /* Turned out to be a hardware problem with one specific computer. */ + + if (isnan(result)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\ndtime_now(): %d, %d -> %.3f + %.3f -> NAN!!!\n\n", sec, ns, x1, x2); + } + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dtime_now() returns %.3f\n", result); +#endif + + return (result); +#endif +} + + +/* end xmit.c */ + + + diff --git a/xmit.h b/xmit.h new file mode 100644 index 0000000..da0bb50 --- /dev/null +++ b/xmit.h @@ -0,0 +1,24 @@ + + +#ifndef XMIT_H +#define XMIT_H 1 + +#include "audio.h" /* for struct audio_s */ + + +extern void xmit_init (struct audio_s *p_modem); + +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 double dtime_now (void); + +#endif + +/* end xmit.h */ +